@possumtech/rummy 0.2.7 → 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 +12 -3
- package/EXCEPTIONS.md +46 -0
- package/PLUGINS.md +454 -197
- package/SPEC.md +284 -93
- package/migrations/001_initial_schema.sql +57 -70
- package/package.json +16 -10
- package/service.js +1 -1
- package/src/agent/AgentLoop.js +254 -70
- package/src/agent/ContextAssembler.js +18 -4
- package/src/agent/KnownStore.js +156 -23
- package/src/agent/ProjectAgent.js +5 -4
- package/src/agent/ResponseHealer.js +21 -1
- package/src/agent/TurnExecutor.js +393 -115
- package/src/agent/XmlParser.js +92 -39
- package/src/agent/known_checks.sql +5 -4
- package/src/agent/known_queries.sql +4 -3
- package/src/agent/known_store.sql +45 -15
- package/src/agent/loops.sql +63 -0
- package/src/agent/runs.sql +7 -7
- package/src/agent/schemes.sql +5 -2
- package/src/agent/tokens.js +6 -21
- package/src/agent/turns.sql +13 -4
- package/src/hooks/Hooks.js +18 -0
- package/src/hooks/PluginContext.js +14 -10
- package/src/hooks/RummyContext.js +30 -10
- 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 +8 -7
- 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 +16 -12
- package/src/plugins/cp/cpDoc.js +29 -0
- package/src/plugins/current/README.md +4 -4
- package/src/plugins/current/current.js +12 -10
- package/src/plugins/engine/engine.sql +5 -10
- package/src/plugins/engine/turn_context.sql +13 -13
- package/src/plugins/env/README.md +3 -4
- package/src/plugins/env/env.js +8 -7
- package/src/plugins/env/envDoc.js +29 -0
- package/src/plugins/file/README.md +9 -12
- package/src/plugins/file/file.js +34 -45
- package/src/plugins/get/README.md +2 -2
- package/src/plugins/get/get.js +28 -11
- package/src/plugins/get/getDoc.js +41 -0
- package/src/plugins/hedberg/docs.md +0 -9
- package/src/plugins/hedberg/hedberg.js +4 -6
- package/src/plugins/hedberg/matcher.js +1 -1
- package/src/plugins/hedberg/normalize.js +28 -0
- package/src/plugins/hedberg/patterns.js +31 -33
- package/src/plugins/hedberg/sed.js +17 -10
- package/src/plugins/helpers.js +2 -2
- package/src/plugins/index.js +93 -28
- package/src/plugins/instructions/README.md +6 -2
- package/src/plugins/instructions/instructions.js +21 -5
- package/src/plugins/instructions/preamble.md +9 -5
- package/src/plugins/known/README.md +10 -7
- package/src/plugins/known/known.js +33 -23
- package/src/plugins/known/knownDoc.js +33 -0
- package/src/plugins/mv/README.md +5 -4
- package/src/plugins/mv/mv.js +16 -12
- 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 +12 -8
- package/src/plugins/progress/progress.js +44 -12
- package/src/plugins/prompt/README.md +5 -5
- package/src/plugins/prompt/prompt.js +23 -19
- package/src/plugins/rm/README.md +4 -4
- package/src/plugins/rm/rm.js +29 -12
- package/src/plugins/rm/rmDoc.js +30 -0
- package/src/plugins/rpc/README.md +15 -28
- package/src/plugins/rpc/rpc.js +63 -107
- package/src/plugins/set/README.md +13 -12
- package/src/plugins/set/set.js +82 -21
- package/src/plugins/set/setDoc.js +45 -0
- package/src/plugins/sh/README.md +4 -4
- package/src/plugins/sh/sh.js +8 -7
- package/src/plugins/sh/shDoc.js +29 -0
- package/src/plugins/{skills/skills.js → skill/skill.js} +12 -54
- 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 +20 -8
- 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 +11 -8
- 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 +3 -5
- package/src/server/RpcRegistry.js +52 -4
- package/src/sql/v_model_context.sql +31 -39
- package/src/sql/v_run_log.sql +3 -3
- package/src/agent/prompt_queue.sql +0 -39
- package/src/plugins/ask_user/docs.md +0 -2
- package/src/plugins/cp/docs.md +0 -2
- package/src/plugins/env/docs.md +0 -2
- package/src/plugins/get/docs.md +0 -6
- package/src/plugins/known/docs.md +0 -3
- package/src/plugins/mv/docs.md +0 -2
- package/src/plugins/rm/docs.md +0 -4
- package/src/plugins/set/docs.md +0 -4
- 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 -5
- package/src/plugins/store/store.js +0 -52
- 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";
|
|
@@ -67,7 +66,7 @@ export default class Rpc {
|
|
|
67
66
|
const row = await ctx.db.upsert_model.get({
|
|
68
67
|
alias: params.alias,
|
|
69
68
|
actual: params.actual,
|
|
70
|
-
context_length: params.contextLength
|
|
69
|
+
context_length: params.contextLength ?? null,
|
|
71
70
|
});
|
|
72
71
|
return { id: row.id, alias: params.alias };
|
|
73
72
|
},
|
|
@@ -90,15 +89,15 @@ export default class Rpc {
|
|
|
90
89
|
|
|
91
90
|
// --- Entry operations (same dispatch as model) ---
|
|
92
91
|
|
|
93
|
-
|
|
92
|
+
// Override: get has persist flag for file constraint management
|
|
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,105 +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("write", {
|
|
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
|
-
state: params.state || "full",
|
|
174
|
-
attributes: params.attributes,
|
|
175
|
-
});
|
|
176
|
-
} else {
|
|
177
|
-
await dispatchTool(
|
|
178
|
-
hooks,
|
|
179
|
-
rummy,
|
|
180
|
-
"set",
|
|
181
|
-
params.path,
|
|
182
|
-
params.body || "",
|
|
183
|
-
{ path: params.path, ...params.attributes },
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
return { status: "ok" };
|
|
187
|
-
},
|
|
188
|
-
description: "Create or update an entry.",
|
|
189
|
-
params: {
|
|
190
|
-
run: "string — run alias",
|
|
191
|
-
path: "string — entry path",
|
|
192
|
-
body: "string? — entry content",
|
|
193
|
-
state: "string? — entry state (default: full)",
|
|
194
|
-
attributes: "object? — JSON attributes",
|
|
195
|
-
},
|
|
196
|
-
requiresInit: true,
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
r.register("delete", {
|
|
200
|
-
handler: async (params, ctx) => {
|
|
201
|
-
if (!params.path) throw new Error("path is required");
|
|
202
|
-
if (!params.run) throw new Error("run is required");
|
|
203
|
-
const { rummy } = await buildRunContext(hooks, ctx, params.run);
|
|
204
|
-
await dispatchTool(hooks, rummy, "rm", params.path, "", {
|
|
205
|
-
path: params.path,
|
|
206
|
-
});
|
|
207
|
-
return { status: "ok" };
|
|
208
|
-
},
|
|
209
|
-
description: "Remove an entry.",
|
|
210
|
-
params: {
|
|
211
|
-
run: "string — run alias",
|
|
212
|
-
path: "string — entry path",
|
|
213
|
-
},
|
|
214
|
-
requiresInit: true,
|
|
215
|
-
});
|
|
216
|
-
|
|
217
161
|
r.register("getEntries", {
|
|
218
162
|
handler: async (params, ctx) => {
|
|
219
163
|
let run;
|
|
@@ -225,14 +169,15 @@ export default class Rpc {
|
|
|
225
169
|
if (!run) return [];
|
|
226
170
|
const entries = await ctx.projectAgent.entries.getEntriesByPattern(
|
|
227
171
|
run.id,
|
|
228
|
-
params.pattern
|
|
229
|
-
params.body
|
|
230
|
-
{ limit: params.limit, offset: params.offset },
|
|
172
|
+
params.pattern ?? "*",
|
|
173
|
+
params.body ?? null,
|
|
174
|
+
{ limit: params.limit ?? null, offset: params.offset ?? null },
|
|
231
175
|
);
|
|
232
176
|
return entries.map((e) => ({
|
|
233
177
|
path: e.path,
|
|
234
178
|
scheme: e.scheme,
|
|
235
|
-
|
|
179
|
+
status: e.status,
|
|
180
|
+
fidelity: e.fidelity,
|
|
236
181
|
tokens: e.tokens_full,
|
|
237
182
|
}));
|
|
238
183
|
},
|
|
@@ -256,7 +201,7 @@ export default class Rpc {
|
|
|
256
201
|
const runRow = await ctx.db.create_run.get({
|
|
257
202
|
project_id: ctx.projectId,
|
|
258
203
|
parent_run_id: null,
|
|
259
|
-
model: params.model,
|
|
204
|
+
model: params.model ?? null,
|
|
260
205
|
alias,
|
|
261
206
|
temperature: params.temperature ?? null,
|
|
262
207
|
persona: params.persona ?? null,
|
|
@@ -283,10 +228,12 @@ export default class Rpc {
|
|
|
283
228
|
params.prompt,
|
|
284
229
|
params.run,
|
|
285
230
|
{
|
|
286
|
-
temperature: params.temperature,
|
|
287
|
-
persona: params.persona,
|
|
231
|
+
temperature: params.temperature ?? null,
|
|
232
|
+
persona: params.persona ?? null,
|
|
288
233
|
contextLimit: params.contextLimit,
|
|
289
|
-
|
|
234
|
+
noRepo: params.noRepo,
|
|
235
|
+
noInteraction: params.noInteraction,
|
|
236
|
+
noWeb: params.noWeb,
|
|
290
237
|
fork: params.fork,
|
|
291
238
|
},
|
|
292
239
|
);
|
|
@@ -300,7 +247,9 @@ export default class Rpc {
|
|
|
300
247
|
temperature: "number?",
|
|
301
248
|
persona: "string?",
|
|
302
249
|
contextLimit: "number?",
|
|
303
|
-
|
|
250
|
+
noRepo: "boolean?",
|
|
251
|
+
noInteraction: "boolean? — disable ask_user tool",
|
|
252
|
+
noWeb: "boolean? — disable search and URL fetch",
|
|
304
253
|
fork: "boolean?",
|
|
305
254
|
},
|
|
306
255
|
requiresInit: true,
|
|
@@ -315,10 +264,12 @@ export default class Rpc {
|
|
|
315
264
|
params.prompt,
|
|
316
265
|
params.run,
|
|
317
266
|
{
|
|
318
|
-
temperature: params.temperature,
|
|
319
|
-
persona: params.persona,
|
|
267
|
+
temperature: params.temperature ?? null,
|
|
268
|
+
persona: params.persona ?? null,
|
|
320
269
|
contextLimit: params.contextLimit,
|
|
321
|
-
|
|
270
|
+
noRepo: params.noRepo,
|
|
271
|
+
noInteraction: params.noInteraction,
|
|
272
|
+
noWeb: params.noWeb,
|
|
322
273
|
fork: params.fork,
|
|
323
274
|
},
|
|
324
275
|
);
|
|
@@ -332,7 +283,9 @@ export default class Rpc {
|
|
|
332
283
|
temperature: "number?",
|
|
333
284
|
persona: "string?",
|
|
334
285
|
contextLimit: "number?",
|
|
335
|
-
|
|
286
|
+
noRepo: "boolean?",
|
|
287
|
+
noInteraction: "boolean? — disable ask_user tool",
|
|
288
|
+
noWeb: "boolean? — disable search and URL fetch",
|
|
336
289
|
fork: "boolean?",
|
|
337
290
|
},
|
|
338
291
|
requiresInit: true,
|
|
@@ -358,7 +311,7 @@ export default class Rpc {
|
|
|
358
311
|
ctx.projectAgent.abortRun(runRow.id);
|
|
359
312
|
await ctx.db.update_run_status.run({
|
|
360
313
|
id: runRow.id,
|
|
361
|
-
status:
|
|
314
|
+
status: 499,
|
|
362
315
|
});
|
|
363
316
|
return { status: "ok" };
|
|
364
317
|
},
|
|
@@ -447,7 +400,7 @@ export default class Rpc {
|
|
|
447
400
|
run: row.alias,
|
|
448
401
|
status: row.status,
|
|
449
402
|
turn: row.turn,
|
|
450
|
-
summary: row.summary
|
|
403
|
+
summary: row.summary,
|
|
451
404
|
created: row.created_at,
|
|
452
405
|
}));
|
|
453
406
|
},
|
|
@@ -512,8 +465,8 @@ export default class Rpc {
|
|
|
512
465
|
};
|
|
513
466
|
}),
|
|
514
467
|
},
|
|
515
|
-
last_user_prompt: promptRow?.body
|
|
516
|
-
last_summary: summaryRow?.body
|
|
468
|
+
last_user_prompt: promptRow?.body,
|
|
469
|
+
last_summary: summaryRow?.body,
|
|
517
470
|
};
|
|
518
471
|
},
|
|
519
472
|
description: "Full run detail.",
|
|
@@ -527,6 +480,10 @@ export default class Rpc {
|
|
|
527
480
|
r.registerNotification("run/progress", "Turn status.");
|
|
528
481
|
r.registerNotification("ui/render", "Streaming output.");
|
|
529
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);
|
|
530
487
|
}
|
|
531
488
|
}
|
|
532
489
|
|
|
@@ -547,7 +504,7 @@ async function buildRunContext(hooks, ctx, runAlias) {
|
|
|
547
504
|
sequence: runRow.next_turn,
|
|
548
505
|
runId: runRow.id,
|
|
549
506
|
turnId: null,
|
|
550
|
-
|
|
507
|
+
noRepo: false,
|
|
551
508
|
contextSize: null,
|
|
552
509
|
systemPrompt: "",
|
|
553
510
|
loopPrompt: "",
|
|
@@ -558,25 +515,24 @@ async function buildRunContext(hooks, ctx, runAlias) {
|
|
|
558
515
|
|
|
559
516
|
async function dispatchTool(hooks, rummy, scheme, path, body, attributes) {
|
|
560
517
|
const store = rummy.entries;
|
|
561
|
-
const resultPath = await store.dedup(
|
|
562
|
-
|
|
563
|
-
await store.upsert(
|
|
518
|
+
const resultPath = await store.dedup(
|
|
564
519
|
rummy.runId,
|
|
520
|
+
scheme,
|
|
521
|
+
path,
|
|
565
522
|
rummy.sequence,
|
|
566
|
-
resultPath,
|
|
567
|
-
body || "",
|
|
568
|
-
"full",
|
|
569
|
-
{
|
|
570
|
-
attributes: attributes || null,
|
|
571
|
-
},
|
|
572
523
|
);
|
|
573
524
|
|
|
525
|
+
await store.upsert(rummy.runId, rummy.sequence, resultPath, body, 200, {
|
|
526
|
+
attributes: attributes,
|
|
527
|
+
loopId: rummy.loopId,
|
|
528
|
+
});
|
|
529
|
+
|
|
574
530
|
const entry = {
|
|
575
531
|
scheme,
|
|
576
532
|
path: resultPath,
|
|
577
|
-
body: body
|
|
578
|
-
attributes: attributes
|
|
579
|
-
|
|
533
|
+
body: body,
|
|
534
|
+
attributes: attributes,
|
|
535
|
+
status: 200,
|
|
580
536
|
resultPath,
|
|
581
537
|
};
|
|
582
538
|
|
|
@@ -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 {
|
|
@@ -9,23 +11,74 @@ export default class Set {
|
|
|
9
11
|
|
|
10
12
|
constructor(core) {
|
|
11
13
|
this.#core = core;
|
|
12
|
-
core.registerScheme(
|
|
13
|
-
validStates: ["full", "proposed", "pass", "rejected", "error", "pattern"],
|
|
14
|
-
});
|
|
14
|
+
core.registerScheme();
|
|
15
15
|
core.on("handler", this.handler.bind(this));
|
|
16
16
|
core.on("full", this.full.bind(this));
|
|
17
17
|
core.on("summary", this.summary.bind(this));
|
|
18
18
|
core.on("turn.proposing", this.#materializeRevisions.bind(this));
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
);
|
|
19
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
20
|
+
docsMap.set = docs;
|
|
21
|
+
return docsMap;
|
|
22
|
+
});
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
async handler(entry, rummy) {
|
|
26
|
-
const { entries: store, sequence: turn, runId } = rummy;
|
|
26
|
+
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
27
27
|
const attrs = entry.attributes;
|
|
28
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
|
+
|
|
29
82
|
if (attrs.blocks || attrs.search != null) {
|
|
30
83
|
await this.#processEdit(rummy, entry, attrs);
|
|
31
84
|
return;
|
|
@@ -45,7 +98,7 @@ export default class Set {
|
|
|
45
98
|
attrs.path,
|
|
46
99
|
attrs.body,
|
|
47
100
|
matches,
|
|
48
|
-
true,
|
|
101
|
+
{ preview: true, loopId },
|
|
49
102
|
);
|
|
50
103
|
return;
|
|
51
104
|
}
|
|
@@ -57,8 +110,9 @@ export default class Set {
|
|
|
57
110
|
if (scheme === null) {
|
|
58
111
|
const udiff = generatePatch(target, "", entry.body || "");
|
|
59
112
|
const merge = `<<<<<<< SEARCH\n=======\n${entry.body || ""}\n>>>>>>> REPLACE`;
|
|
60
|
-
await store.upsert(runId, turn, entry.resultPath, "",
|
|
113
|
+
await store.upsert(runId, turn, entry.resultPath, "", 202, {
|
|
61
114
|
attributes: { file: target, patch: udiff, merge },
|
|
115
|
+
loopId,
|
|
62
116
|
});
|
|
63
117
|
} else if (attrs.filter || target.includes("*")) {
|
|
64
118
|
const matches = await store.getEntriesByPattern(
|
|
@@ -80,9 +134,10 @@ export default class Set {
|
|
|
80
134
|
target,
|
|
81
135
|
attrs.filter,
|
|
82
136
|
matches,
|
|
137
|
+
{ loopId },
|
|
83
138
|
);
|
|
84
139
|
} else {
|
|
85
|
-
await store.upsert(runId, turn, target, entry.body,
|
|
140
|
+
await store.upsert(runId, turn, target, entry.body, 200, { loopId });
|
|
86
141
|
}
|
|
87
142
|
}
|
|
88
143
|
|
|
@@ -103,13 +158,14 @@ export default class Set {
|
|
|
103
158
|
}
|
|
104
159
|
|
|
105
160
|
async #processEdit(rummy, entry, attrs) {
|
|
106
|
-
const { entries: store, sequence: turn, runId } = rummy;
|
|
161
|
+
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
107
162
|
const target = attrs.path;
|
|
108
163
|
const matches = await store.getEntriesByPattern(runId, target, attrs.body);
|
|
109
164
|
|
|
110
165
|
if (matches.length === 0) {
|
|
111
|
-
await store.upsert(runId, turn, entry.resultPath, "",
|
|
166
|
+
await store.upsert(runId, turn, entry.resultPath, "", 404, {
|
|
112
167
|
attributes: { file: target, error: `${target} not found in context` },
|
|
168
|
+
loopId,
|
|
113
169
|
});
|
|
114
170
|
return;
|
|
115
171
|
}
|
|
@@ -121,8 +177,9 @@ export default class Set {
|
|
|
121
177
|
const existingAttrs = await rummy.getAttributes(canonicalPath);
|
|
122
178
|
const revisions = existingAttrs?.revisions || [];
|
|
123
179
|
revisions.push(revision);
|
|
124
|
-
await store.upsert(runId, turn, canonicalPath, "",
|
|
180
|
+
await store.upsert(runId, turn, canonicalPath, "", 200, {
|
|
125
181
|
attributes: { file: match.path, revisions },
|
|
182
|
+
loopId,
|
|
126
183
|
});
|
|
127
184
|
if (KnownStore.normalizePath(entry.resultPath) !== canonicalPath) {
|
|
128
185
|
await store.remove(runId, entry.resultPath);
|
|
@@ -133,7 +190,7 @@ export default class Set {
|
|
|
133
190
|
const { patch, searchText, replaceText, warning, error } =
|
|
134
191
|
Set.#applyRevision(match.body, attrs);
|
|
135
192
|
|
|
136
|
-
const
|
|
193
|
+
const status = error ? 409 : 200;
|
|
137
194
|
const resultPath = `set://${match.path}`;
|
|
138
195
|
const udiff = patch ? generatePatch(match.path, match.body, patch) : null;
|
|
139
196
|
const merge =
|
|
@@ -143,7 +200,7 @@ export default class Set {
|
|
|
143
200
|
const beforeTokens = match.tokens_full || 0;
|
|
144
201
|
const afterTokens = patch ? (patch.length / 4) | 0 : beforeTokens;
|
|
145
202
|
|
|
146
|
-
await store.upsert(runId, turn, resultPath, match.body,
|
|
203
|
+
await store.upsert(runId, turn, resultPath, match.body, status, {
|
|
147
204
|
attributes: {
|
|
148
205
|
file: match.path,
|
|
149
206
|
patch: udiff,
|
|
@@ -153,16 +210,19 @@ export default class Set {
|
|
|
153
210
|
warning,
|
|
154
211
|
error,
|
|
155
212
|
},
|
|
213
|
+
loopId,
|
|
156
214
|
});
|
|
157
215
|
|
|
158
|
-
if (
|
|
159
|
-
await store.upsert(runId, turn, match.path, patch, match.
|
|
216
|
+
if (status === 200 && patch) {
|
|
217
|
+
await store.upsert(runId, turn, match.path, patch, match.status, {
|
|
218
|
+
loopId,
|
|
219
|
+
});
|
|
160
220
|
}
|
|
161
221
|
}
|
|
162
222
|
}
|
|
163
223
|
|
|
164
224
|
async #materializeRevisions({ rummy }) {
|
|
165
|
-
const { entries: store, sequence: turn, runId } = rummy;
|
|
225
|
+
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
166
226
|
const setEntries = await store.getEntriesByPattern(runId, "set://*");
|
|
167
227
|
|
|
168
228
|
for (const entry of setEntries) {
|
|
@@ -198,7 +258,7 @@ export default class Set {
|
|
|
198
258
|
}
|
|
199
259
|
}
|
|
200
260
|
|
|
201
|
-
const state = lastError ?
|
|
261
|
+
const state = lastError ? 409 : 202;
|
|
202
262
|
const udiff =
|
|
203
263
|
current !== original
|
|
204
264
|
? generatePatch(filePath, original, current)
|
|
@@ -217,6 +277,7 @@ export default class Set {
|
|
|
217
277
|
warning: lastWarning,
|
|
218
278
|
error: lastError,
|
|
219
279
|
},
|
|
280
|
+
loopId,
|
|
220
281
|
});
|
|
221
282
|
}
|
|
222
283
|
}
|
|
@@ -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,16 +9,17 @@ 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) {
|
|
19
|
-
const { entries: store, sequence: turn, runId } = rummy;
|
|
20
|
-
await store.upsert(runId, turn, entry.resultPath, entry.body,
|
|
19
|
+
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
20
|
+
await store.upsert(runId, turn, entry.resultPath, entry.body, 202, {
|
|
21
21
|
attributes: entry.attributes,
|
|
22
|
+
loopId,
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
25
|
|