@possumtech/rummy 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +55 -0
- package/LICENSE +21 -0
- package/PLUGINS.md +302 -0
- package/README.md +41 -0
- package/SPEC.md +524 -0
- package/lang/en.json +34 -0
- package/migrations/001_initial_schema.sql +226 -0
- package/package.json +54 -0
- package/service.js +143 -0
- package/src/agent/AgentLoop.js +553 -0
- package/src/agent/ContextAssembler.js +29 -0
- package/src/agent/KnownStore.js +254 -0
- package/src/agent/ProjectAgent.js +101 -0
- package/src/agent/ResponseHealer.js +134 -0
- package/src/agent/TurnExecutor.js +457 -0
- package/src/agent/XmlParser.js +247 -0
- package/src/agent/known_checks.sql +42 -0
- package/src/agent/known_queries.sql +80 -0
- package/src/agent/known_store.sql +161 -0
- package/src/agent/messages.js +17 -0
- package/src/agent/prompt_queue.sql +39 -0
- package/src/agent/runs.sql +114 -0
- package/src/agent/schemes.sql +3 -0
- package/src/agent/sessions.sql +51 -0
- package/src/agent/tokens.js +28 -0
- package/src/agent/turns.sql +36 -0
- package/src/hooks/HookRegistry.js +72 -0
- package/src/hooks/Hooks.js +115 -0
- package/src/hooks/PluginContext.js +116 -0
- package/src/hooks/RummyContext.js +181 -0
- package/src/hooks/ToolRegistry.js +83 -0
- package/src/llm/LlmProvider.js +107 -0
- package/src/llm/OllamaClient.js +88 -0
- package/src/llm/OpenAiClient.js +80 -0
- package/src/llm/OpenRouterClient.js +78 -0
- package/src/llm/XaiClient.js +113 -0
- package/src/plugins/ask_user/README.md +18 -0
- package/src/plugins/ask_user/ask_user.js +48 -0
- package/src/plugins/ask_user/docs.md +2 -0
- package/src/plugins/cp/README.md +18 -0
- package/src/plugins/cp/cp.js +55 -0
- package/src/plugins/cp/docs.md +2 -0
- package/src/plugins/current/README.md +14 -0
- package/src/plugins/current/current.js +48 -0
- package/src/plugins/engine/README.md +12 -0
- package/src/plugins/engine/engine.sql +18 -0
- package/src/plugins/engine/turn_context.sql +51 -0
- package/src/plugins/env/README.md +14 -0
- package/src/plugins/env/docs.md +2 -0
- package/src/plugins/env/env.js +32 -0
- package/src/plugins/file/README.md +25 -0
- package/src/plugins/file/file.js +85 -0
- package/src/plugins/get/README.md +19 -0
- package/src/plugins/get/docs.md +6 -0
- package/src/plugins/get/get.js +53 -0
- package/src/plugins/hedberg/README.md +72 -0
- package/src/plugins/hedberg/docs.md +9 -0
- package/src/plugins/hedberg/edits.js +65 -0
- package/src/plugins/hedberg/hedberg.js +89 -0
- package/src/plugins/hedberg/matcher.js +181 -0
- package/src/plugins/hedberg/normalize.js +41 -0
- package/src/plugins/hedberg/patterns.js +452 -0
- package/src/plugins/hedberg/sed.js +48 -0
- package/src/plugins/helpers.js +22 -0
- package/src/plugins/index.js +180 -0
- package/src/plugins/instructions/README.md +11 -0
- package/src/plugins/instructions/instructions.js +37 -0
- package/src/plugins/instructions/preamble.md +12 -0
- package/src/plugins/known/README.md +18 -0
- package/src/plugins/known/docs.md +3 -0
- package/src/plugins/known/known.js +57 -0
- package/src/plugins/mv/README.md +18 -0
- package/src/plugins/mv/docs.md +2 -0
- package/src/plugins/mv/mv.js +56 -0
- package/src/plugins/previous/README.md +15 -0
- package/src/plugins/previous/previous.js +50 -0
- package/src/plugins/progress/README.md +17 -0
- package/src/plugins/progress/progress.js +44 -0
- package/src/plugins/prompt/README.md +16 -0
- package/src/plugins/prompt/prompt.js +45 -0
- package/src/plugins/rm/README.md +18 -0
- package/src/plugins/rm/docs.md +4 -0
- package/src/plugins/rm/rm.js +51 -0
- package/src/plugins/rpc/README.md +45 -0
- package/src/plugins/rpc/rpc.js +587 -0
- package/src/plugins/set/README.md +32 -0
- package/src/plugins/set/docs.md +4 -0
- package/src/plugins/set/set.js +268 -0
- package/src/plugins/sh/README.md +18 -0
- package/src/plugins/sh/docs.md +2 -0
- package/src/plugins/sh/sh.js +32 -0
- package/src/plugins/skills/README.md +25 -0
- package/src/plugins/skills/skills.js +175 -0
- package/src/plugins/store/README.md +20 -0
- package/src/plugins/store/docs.md +5 -0
- package/src/plugins/store/store.js +52 -0
- package/src/plugins/summarize/README.md +18 -0
- package/src/plugins/summarize/docs.md +4 -0
- package/src/plugins/summarize/summarize.js +24 -0
- package/src/plugins/telemetry/README.md +19 -0
- package/src/plugins/telemetry/rpc_log.sql +28 -0
- package/src/plugins/telemetry/telemetry.js +186 -0
- package/src/plugins/unknown/README.md +23 -0
- package/src/plugins/unknown/docs.md +5 -0
- package/src/plugins/unknown/unknown.js +31 -0
- package/src/plugins/update/README.md +18 -0
- package/src/plugins/update/docs.md +4 -0
- package/src/plugins/update/update.js +24 -0
- package/src/server/ClientConnection.js +228 -0
- package/src/server/RpcRegistry.js +52 -0
- package/src/server/SocketServer.js +43 -0
- package/src/sql/file_constraints.sql +15 -0
- package/src/sql/functions/countTokens.js +7 -0
- package/src/sql/functions/hedmatch.js +8 -0
- package/src/sql/functions/hedreplace.js +8 -0
- package/src/sql/functions/hedsearch.js +8 -0
- package/src/sql/functions/schemeOf.js +7 -0
- package/src/sql/functions/slugify.js +6 -0
- package/src/sql/v_model_context.sql +101 -0
- package/src/sql/v_run_log.sql +23 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
import KnownStore from "../../agent/KnownStore.js";
|
|
2
|
+
import msg from "../../agent/messages.js";
|
|
3
|
+
import RummyContext from "../../hooks/RummyContext.js";
|
|
4
|
+
import File from "../file/file.js";
|
|
5
|
+
|
|
6
|
+
export default class Rpc {
|
|
7
|
+
#core;
|
|
8
|
+
|
|
9
|
+
constructor(core) {
|
|
10
|
+
this.#core = core;
|
|
11
|
+
const hooks = core.hooks;
|
|
12
|
+
const r = hooks.rpc.registry;
|
|
13
|
+
|
|
14
|
+
// --- Protocol ---
|
|
15
|
+
|
|
16
|
+
r.register("ping", {
|
|
17
|
+
handler: async () => ({}),
|
|
18
|
+
description: "Liveness check.",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
r.register("discover", {
|
|
22
|
+
handler: async (_params, ctx) => ctx.rpcRegistry.discover(),
|
|
23
|
+
description: "Returns { methods, notifications } catalog.",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
r.register("init", {
|
|
27
|
+
handler: async (params, ctx) => {
|
|
28
|
+
const result = await ctx.projectAgent.init(
|
|
29
|
+
params.name,
|
|
30
|
+
params.projectRoot,
|
|
31
|
+
params.configPath,
|
|
32
|
+
);
|
|
33
|
+
ctx.setContext(result.projectId, params.projectRoot);
|
|
34
|
+
return result;
|
|
35
|
+
},
|
|
36
|
+
description: "Initialize project. Returns { projectId }.",
|
|
37
|
+
params: {
|
|
38
|
+
name: "string — project name (unique identifier)",
|
|
39
|
+
projectRoot: "string — absolute path to source code",
|
|
40
|
+
configPath: "string? — path to rummy config directory",
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// --- Models ---
|
|
45
|
+
|
|
46
|
+
r.register("getModels", {
|
|
47
|
+
handler: async (params, ctx) => {
|
|
48
|
+
const rows = await ctx.db.get_models.all({
|
|
49
|
+
limit: params.limit ?? null,
|
|
50
|
+
offset: params.offset ?? null,
|
|
51
|
+
});
|
|
52
|
+
return rows.map((m) => ({
|
|
53
|
+
alias: m.alias,
|
|
54
|
+
actual: m.actual,
|
|
55
|
+
context_length: m.context_length,
|
|
56
|
+
}));
|
|
57
|
+
},
|
|
58
|
+
description: "List available models.",
|
|
59
|
+
params: {
|
|
60
|
+
limit: "number? — max results",
|
|
61
|
+
offset: "number? — skip first N results",
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
r.register("addModel", {
|
|
66
|
+
handler: async (params, ctx) => {
|
|
67
|
+
const row = await ctx.db.upsert_model.get({
|
|
68
|
+
alias: params.alias,
|
|
69
|
+
actual: params.actual,
|
|
70
|
+
context_length: params.contextLength || null,
|
|
71
|
+
});
|
|
72
|
+
return { id: row.id, alias: params.alias };
|
|
73
|
+
},
|
|
74
|
+
description: "Add or update a model. Returns { id, alias }.",
|
|
75
|
+
params: {
|
|
76
|
+
alias: "string — short name for the model",
|
|
77
|
+
actual: "string — provider/model identifier",
|
|
78
|
+
contextLength: "number? — context window size in tokens",
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
r.register("removeModel", {
|
|
83
|
+
handler: async (params, ctx) => {
|
|
84
|
+
await ctx.db.delete_model.run({ alias: params.alias });
|
|
85
|
+
return { status: "ok" };
|
|
86
|
+
},
|
|
87
|
+
description: "Remove a model by alias.",
|
|
88
|
+
params: { alias: "string — model alias to remove" },
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// --- Entry operations (same dispatch as model) ---
|
|
92
|
+
|
|
93
|
+
r.register("read", {
|
|
94
|
+
handler: async (params, ctx) => {
|
|
95
|
+
if (!params.path) throw new Error("path is required");
|
|
96
|
+
|
|
97
|
+
if (params.persist) {
|
|
98
|
+
const visibility = params.readonly ? "readonly" : "active";
|
|
99
|
+
return File.activate(
|
|
100
|
+
ctx.db,
|
|
101
|
+
ctx.projectAgent.entries,
|
|
102
|
+
ctx.projectId,
|
|
103
|
+
params.path,
|
|
104
|
+
visibility,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!params.run) throw new Error("run is required");
|
|
109
|
+
const { rummy } = await buildRunContext(hooks, ctx, params.run);
|
|
110
|
+
await dispatchTool(hooks, rummy, "get", params.path, "", {
|
|
111
|
+
path: params.path,
|
|
112
|
+
});
|
|
113
|
+
return { status: "ok" };
|
|
114
|
+
},
|
|
115
|
+
description: "Promote entry to full state.",
|
|
116
|
+
params: {
|
|
117
|
+
path: "string — file path or glob pattern",
|
|
118
|
+
run: "string? — run alias (required without persist)",
|
|
119
|
+
persist: "boolean? — create file constraint",
|
|
120
|
+
readonly: "boolean? — with persist, set readonly instead of active",
|
|
121
|
+
},
|
|
122
|
+
requiresInit: true,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
r.register("store", {
|
|
126
|
+
handler: async (params, ctx) => {
|
|
127
|
+
if (!params.path) throw new Error("path is required");
|
|
128
|
+
|
|
129
|
+
if (params.clear) {
|
|
130
|
+
return File.drop(ctx.db, ctx.projectId, params.path);
|
|
131
|
+
}
|
|
132
|
+
if (params.persist) {
|
|
133
|
+
if (params.ignore) {
|
|
134
|
+
return File.ignore(
|
|
135
|
+
ctx.db,
|
|
136
|
+
ctx.projectAgent.entries,
|
|
137
|
+
ctx.projectId,
|
|
138
|
+
params.path,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return File.drop(ctx.db, ctx.projectId, params.path);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!params.run) throw new Error("run is required");
|
|
145
|
+
const { rummy } = await buildRunContext(hooks, ctx, params.run);
|
|
146
|
+
await dispatchTool(hooks, rummy, "store", params.path, "", {
|
|
147
|
+
path: params.path,
|
|
148
|
+
});
|
|
149
|
+
return { status: "ok" };
|
|
150
|
+
},
|
|
151
|
+
description: "Demote entry to stored state.",
|
|
152
|
+
params: {
|
|
153
|
+
path: "string — file path or glob pattern",
|
|
154
|
+
run: "string? — run alias (required without persist)",
|
|
155
|
+
persist: "boolean? — create file constraint",
|
|
156
|
+
ignore: "boolean? — with persist, exclude from scan",
|
|
157
|
+
clear: "boolean? — remove existing constraint",
|
|
158
|
+
},
|
|
159
|
+
requiresInit: true,
|
|
160
|
+
});
|
|
161
|
+
|
|
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
|
+
r.register("getEntries", {
|
|
218
|
+
handler: async (params, ctx) => {
|
|
219
|
+
let run;
|
|
220
|
+
if (params.run) {
|
|
221
|
+
run = await ctx.db.get_run_by_alias.get({ alias: params.run });
|
|
222
|
+
} else {
|
|
223
|
+
run = await ctx.db.get_latest_run.get({ project_id: ctx.projectId });
|
|
224
|
+
}
|
|
225
|
+
if (!run) return [];
|
|
226
|
+
const entries = await ctx.projectAgent.entries.getEntriesByPattern(
|
|
227
|
+
run.id,
|
|
228
|
+
params.pattern || "*",
|
|
229
|
+
params.body || null,
|
|
230
|
+
{ limit: params.limit, offset: params.offset },
|
|
231
|
+
);
|
|
232
|
+
return entries.map((e) => ({
|
|
233
|
+
path: e.path,
|
|
234
|
+
scheme: e.scheme,
|
|
235
|
+
state: e.state,
|
|
236
|
+
tokens: e.tokens_full,
|
|
237
|
+
}));
|
|
238
|
+
},
|
|
239
|
+
description: "Query entries by pattern.",
|
|
240
|
+
params: {
|
|
241
|
+
pattern: "string? — glob pattern (default: *)",
|
|
242
|
+
body: "string? — filter by body content",
|
|
243
|
+
run: "string? — run alias (default: latest run)",
|
|
244
|
+
limit: "number? — max results",
|
|
245
|
+
offset: "number? — skip first N results",
|
|
246
|
+
},
|
|
247
|
+
requiresInit: true,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// --- Runs ---
|
|
251
|
+
|
|
252
|
+
r.register("startRun", {
|
|
253
|
+
handler: async (params, ctx) => {
|
|
254
|
+
if (!params.model) throw new Error("model is required");
|
|
255
|
+
const alias = `${params.model}_${Date.now()}`;
|
|
256
|
+
const runRow = await ctx.db.create_run.get({
|
|
257
|
+
project_id: ctx.projectId,
|
|
258
|
+
parent_run_id: null,
|
|
259
|
+
model: params.model,
|
|
260
|
+
alias,
|
|
261
|
+
temperature: params.temperature ?? null,
|
|
262
|
+
persona: params.persona ?? null,
|
|
263
|
+
context_limit: params.contextLimit ?? null,
|
|
264
|
+
});
|
|
265
|
+
return { run: alias, id: runRow.id };
|
|
266
|
+
},
|
|
267
|
+
description: "Pre-create a run. Returns { run, id }.",
|
|
268
|
+
params: {
|
|
269
|
+
model: "string — model alias (required)",
|
|
270
|
+
temperature: "number? — 0 to 2",
|
|
271
|
+
persona: "string?",
|
|
272
|
+
contextLimit: "number?",
|
|
273
|
+
},
|
|
274
|
+
requiresInit: true,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
r.register("ask", {
|
|
278
|
+
handler: async (params, ctx) => {
|
|
279
|
+
if (!params.model) throw new Error("model is required");
|
|
280
|
+
return ctx.projectAgent.ask(
|
|
281
|
+
ctx.projectId,
|
|
282
|
+
params.model,
|
|
283
|
+
params.prompt,
|
|
284
|
+
params.run,
|
|
285
|
+
{
|
|
286
|
+
temperature: params.temperature,
|
|
287
|
+
persona: params.persona,
|
|
288
|
+
contextLimit: params.contextLimit,
|
|
289
|
+
noContext: params.noContext,
|
|
290
|
+
fork: params.fork,
|
|
291
|
+
},
|
|
292
|
+
);
|
|
293
|
+
},
|
|
294
|
+
description: "Non-mutating query. Model required.",
|
|
295
|
+
longRunning: true,
|
|
296
|
+
params: {
|
|
297
|
+
prompt: "string — user message",
|
|
298
|
+
model: "string — model alias (required)",
|
|
299
|
+
run: "string? — continue existing run",
|
|
300
|
+
temperature: "number?",
|
|
301
|
+
persona: "string?",
|
|
302
|
+
contextLimit: "number?",
|
|
303
|
+
noContext: "boolean?",
|
|
304
|
+
fork: "boolean?",
|
|
305
|
+
},
|
|
306
|
+
requiresInit: true,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
r.register("act", {
|
|
310
|
+
handler: async (params, ctx) => {
|
|
311
|
+
if (!params.model) throw new Error("model is required");
|
|
312
|
+
return ctx.projectAgent.act(
|
|
313
|
+
ctx.projectId,
|
|
314
|
+
params.model,
|
|
315
|
+
params.prompt,
|
|
316
|
+
params.run,
|
|
317
|
+
{
|
|
318
|
+
temperature: params.temperature,
|
|
319
|
+
persona: params.persona,
|
|
320
|
+
contextLimit: params.contextLimit,
|
|
321
|
+
noContext: params.noContext,
|
|
322
|
+
fork: params.fork,
|
|
323
|
+
},
|
|
324
|
+
);
|
|
325
|
+
},
|
|
326
|
+
description: "Mutating directive. Model required.",
|
|
327
|
+
longRunning: true,
|
|
328
|
+
params: {
|
|
329
|
+
prompt: "string — user message",
|
|
330
|
+
model: "string — model alias (required)",
|
|
331
|
+
run: "string? — continue existing run",
|
|
332
|
+
temperature: "number?",
|
|
333
|
+
persona: "string?",
|
|
334
|
+
contextLimit: "number?",
|
|
335
|
+
noContext: "boolean?",
|
|
336
|
+
fork: "boolean?",
|
|
337
|
+
},
|
|
338
|
+
requiresInit: true,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
r.register("run/resolve", {
|
|
342
|
+
handler: async (params, ctx) =>
|
|
343
|
+
ctx.projectAgent.resolve(params.run, params.resolution),
|
|
344
|
+
description: "Resolve a proposed entry. Returns { run, status }.",
|
|
345
|
+
longRunning: true,
|
|
346
|
+
params: {
|
|
347
|
+
run: "string — run alias",
|
|
348
|
+
resolution: "{ path, action: 'accept'|'reject', output? }",
|
|
349
|
+
},
|
|
350
|
+
requiresInit: true,
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
r.register("run/abort", {
|
|
354
|
+
handler: async (params, ctx) => {
|
|
355
|
+
const runRow = await ctx.db.get_run_by_alias.get({ alias: params.run });
|
|
356
|
+
if (!runRow)
|
|
357
|
+
throw new Error(msg("error.run_not_found", { runId: params.run }));
|
|
358
|
+
ctx.projectAgent.abortRun(runRow.id);
|
|
359
|
+
await ctx.db.update_run_status.run({
|
|
360
|
+
id: runRow.id,
|
|
361
|
+
status: "aborted",
|
|
362
|
+
});
|
|
363
|
+
return { status: "ok" };
|
|
364
|
+
},
|
|
365
|
+
description: "Abort run.",
|
|
366
|
+
params: { run: "string — run alias" },
|
|
367
|
+
requiresInit: true,
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
r.register("run/rename", {
|
|
371
|
+
handler: async (params, ctx) => {
|
|
372
|
+
const { run, name } = params;
|
|
373
|
+
if (!name || !/^[a-zA-Z0-9_]+$/.test(name)) {
|
|
374
|
+
throw new Error(msg("error.run_name_invalid"));
|
|
375
|
+
}
|
|
376
|
+
const runRow = await ctx.db.get_run_by_alias.get({ alias: run });
|
|
377
|
+
if (!runRow)
|
|
378
|
+
throw new Error(msg("error.run_not_found", { runId: run }));
|
|
379
|
+
try {
|
|
380
|
+
await ctx.db.rename_run.run({
|
|
381
|
+
id: runRow.id,
|
|
382
|
+
old_alias: runRow.alias,
|
|
383
|
+
new_alias: name,
|
|
384
|
+
});
|
|
385
|
+
} catch (err) {
|
|
386
|
+
if (err.message.includes("UNIQUE"))
|
|
387
|
+
throw new Error(msg("error.run_name_taken", { name }));
|
|
388
|
+
throw err;
|
|
389
|
+
}
|
|
390
|
+
return { run: name };
|
|
391
|
+
},
|
|
392
|
+
description: "Rename a run.",
|
|
393
|
+
params: {
|
|
394
|
+
run: "string — current run alias",
|
|
395
|
+
name: "string — new name",
|
|
396
|
+
},
|
|
397
|
+
requiresInit: true,
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
r.register("run/inject", {
|
|
401
|
+
handler: async (params, ctx) =>
|
|
402
|
+
ctx.projectAgent.inject(params.run, params.message),
|
|
403
|
+
description: "Inject a message into a run.",
|
|
404
|
+
longRunning: true,
|
|
405
|
+
params: {
|
|
406
|
+
run: "string — run alias",
|
|
407
|
+
message: "string — message to inject",
|
|
408
|
+
},
|
|
409
|
+
requiresInit: true,
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
r.register("run/config", {
|
|
413
|
+
handler: async (params, ctx) => {
|
|
414
|
+
const runRow = await ctx.db.get_run_by_alias.get({ alias: params.run });
|
|
415
|
+
if (!runRow)
|
|
416
|
+
throw new Error(msg("error.run_not_found", { runId: params.run }));
|
|
417
|
+
await ctx.db.update_run_config.run({
|
|
418
|
+
id: runRow.id,
|
|
419
|
+
temperature: params.temperature ?? null,
|
|
420
|
+
persona: params.persona ?? null,
|
|
421
|
+
context_limit: params.contextLimit ?? null,
|
|
422
|
+
model: params.model ?? null,
|
|
423
|
+
});
|
|
424
|
+
return { status: "ok" };
|
|
425
|
+
},
|
|
426
|
+
description: "Update run configuration.",
|
|
427
|
+
params: {
|
|
428
|
+
run: "string — run alias",
|
|
429
|
+
temperature: "number?",
|
|
430
|
+
persona: "string?",
|
|
431
|
+
contextLimit: "number?",
|
|
432
|
+
model: "string?",
|
|
433
|
+
},
|
|
434
|
+
requiresInit: true,
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// --- Queries ---
|
|
438
|
+
|
|
439
|
+
r.register("getRuns", {
|
|
440
|
+
handler: async (params, ctx) => {
|
|
441
|
+
const rows = await ctx.db.get_runs_by_project.all({
|
|
442
|
+
project_id: ctx.projectId,
|
|
443
|
+
limit: params.limit ?? null,
|
|
444
|
+
offset: params.offset ?? null,
|
|
445
|
+
});
|
|
446
|
+
return rows.map((row) => ({
|
|
447
|
+
run: row.alias,
|
|
448
|
+
status: row.status,
|
|
449
|
+
turn: row.turn,
|
|
450
|
+
summary: row.summary || "",
|
|
451
|
+
created: row.created_at,
|
|
452
|
+
}));
|
|
453
|
+
},
|
|
454
|
+
description: "List runs for the current project.",
|
|
455
|
+
params: {
|
|
456
|
+
limit: "number?",
|
|
457
|
+
offset: "number?",
|
|
458
|
+
},
|
|
459
|
+
requiresInit: true,
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
r.register("getRun", {
|
|
463
|
+
handler: async (params, ctx) => {
|
|
464
|
+
const run = await ctx.db.get_run_by_alias.get({ alias: params.run });
|
|
465
|
+
if (!run)
|
|
466
|
+
throw new Error(msg("error.run_not_found", { runId: params.run }));
|
|
467
|
+
|
|
468
|
+
const [telemetry, reasoning, content, history, promptRow, summaryRow] =
|
|
469
|
+
await Promise.all([
|
|
470
|
+
ctx.db.get_run_usage.get({ run_id: run.id }),
|
|
471
|
+
ctx.db.get_reasoning.all({ run_id: run.id }),
|
|
472
|
+
ctx.db.get_content.all({ run_id: run.id }),
|
|
473
|
+
ctx.db.get_history.all({ run_id: run.id }),
|
|
474
|
+
ctx.db.get_latest_user_prompt.get({ run_id: run.id }),
|
|
475
|
+
ctx.db.get_latest_summary.get({ run_id: run.id }),
|
|
476
|
+
]);
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
run: run.alias,
|
|
480
|
+
turn: run.next_turn - 1,
|
|
481
|
+
status: run.status,
|
|
482
|
+
model: run.model,
|
|
483
|
+
temperature: run.temperature,
|
|
484
|
+
persona: run.persona,
|
|
485
|
+
context_limit: run.context_limit,
|
|
486
|
+
context: {
|
|
487
|
+
telemetry: {
|
|
488
|
+
prompt_tokens: telemetry.prompt_tokens,
|
|
489
|
+
completion_tokens: telemetry.completion_tokens,
|
|
490
|
+
total_tokens: telemetry.total_tokens,
|
|
491
|
+
cost: telemetry.cost,
|
|
492
|
+
},
|
|
493
|
+
reasoning: reasoning.map((r) => ({
|
|
494
|
+
path: r.path,
|
|
495
|
+
body: r.body,
|
|
496
|
+
turn: r.turn,
|
|
497
|
+
})),
|
|
498
|
+
content: content.map((c) => ({
|
|
499
|
+
path: c.path,
|
|
500
|
+
body: c.body,
|
|
501
|
+
turn: c.turn,
|
|
502
|
+
})),
|
|
503
|
+
history: history.map((h) => {
|
|
504
|
+
const scheme = h.path.split("://")[0];
|
|
505
|
+
return {
|
|
506
|
+
scheme,
|
|
507
|
+
path: h.path,
|
|
508
|
+
status: h.status,
|
|
509
|
+
body: h.body,
|
|
510
|
+
attributes: h.attributes ? JSON.parse(h.attributes) : null,
|
|
511
|
+
turn: h.turn,
|
|
512
|
+
};
|
|
513
|
+
}),
|
|
514
|
+
},
|
|
515
|
+
last_user_prompt: promptRow?.body || "",
|
|
516
|
+
last_summary: summaryRow?.body || "",
|
|
517
|
+
};
|
|
518
|
+
},
|
|
519
|
+
description: "Full run detail.",
|
|
520
|
+
params: { run: "string — run alias" },
|
|
521
|
+
requiresInit: true,
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
// --- Notifications ---
|
|
525
|
+
|
|
526
|
+
r.registerNotification("run/state", "Turn state update.");
|
|
527
|
+
r.registerNotification("run/progress", "Turn status.");
|
|
528
|
+
r.registerNotification("ui/render", "Streaming output.");
|
|
529
|
+
r.registerNotification("ui/notify", "Toast notification.");
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async function buildRunContext(hooks, ctx, runAlias) {
|
|
534
|
+
const runRow = await ctx.db.get_run_by_alias.get({ alias: runAlias });
|
|
535
|
+
if (!runRow) throw new Error(msg("error.run_not_found", { runId: runAlias }));
|
|
536
|
+
const project = await ctx.db.get_project_by_id.get({ id: runRow.project_id });
|
|
537
|
+
return {
|
|
538
|
+
runRow,
|
|
539
|
+
rummy: new RummyContext(
|
|
540
|
+
{ tag: "rpc", attrs: {}, content: null, children: [] },
|
|
541
|
+
{
|
|
542
|
+
hooks,
|
|
543
|
+
db: ctx.db,
|
|
544
|
+
store: ctx.projectAgent.entries,
|
|
545
|
+
project,
|
|
546
|
+
type: null,
|
|
547
|
+
sequence: runRow.next_turn,
|
|
548
|
+
runId: runRow.id,
|
|
549
|
+
turnId: null,
|
|
550
|
+
noContext: false,
|
|
551
|
+
contextSize: null,
|
|
552
|
+
systemPrompt: "",
|
|
553
|
+
loopPrompt: "",
|
|
554
|
+
},
|
|
555
|
+
),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async function dispatchTool(hooks, rummy, scheme, path, body, attributes) {
|
|
560
|
+
const store = rummy.entries;
|
|
561
|
+
const resultPath = await store.dedup(rummy.runId, scheme, path || "");
|
|
562
|
+
|
|
563
|
+
await store.upsert(
|
|
564
|
+
rummy.runId,
|
|
565
|
+
rummy.sequence,
|
|
566
|
+
resultPath,
|
|
567
|
+
body || "",
|
|
568
|
+
"full",
|
|
569
|
+
{
|
|
570
|
+
attributes: attributes || null,
|
|
571
|
+
},
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
const entry = {
|
|
575
|
+
scheme,
|
|
576
|
+
path: resultPath,
|
|
577
|
+
body: body || "",
|
|
578
|
+
attributes: attributes || {},
|
|
579
|
+
state: "full",
|
|
580
|
+
resultPath,
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
await hooks.tools.dispatch(scheme, entry, rummy);
|
|
584
|
+
await hooks.entry.created.emit(entry);
|
|
585
|
+
|
|
586
|
+
return entry;
|
|
587
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# set
|
|
2
|
+
|
|
3
|
+
Writes or edits entry content. Handles new files, full overwrites, SEARCH/REPLACE edits, and pattern updates.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- **set.js** — Plugin registration and edit dispatch logic.
|
|
8
|
+
- **HeuristicMatcher.js** — Fuzzy SEARCH/REPLACE matching. Handles whitespace/indentation differences and escaped characters when literal match fails.
|
|
9
|
+
- **HeuristicMatcher.test.js** — Tests for HeuristicMatcher.
|
|
10
|
+
|
|
11
|
+
## Registration
|
|
12
|
+
|
|
13
|
+
- **Tool**: `set`
|
|
14
|
+
- **Modes**: ask, act
|
|
15
|
+
- **Category**: act
|
|
16
|
+
- **Handler**: Routes to different paths based on attributes:
|
|
17
|
+
- `blocks` or `search` — SEARCH/REPLACE edit via `processEdit`.
|
|
18
|
+
- `preview` — pattern preview (dry run).
|
|
19
|
+
- K/V path — direct upsert at `full` state.
|
|
20
|
+
- File path — produces `proposed` entry with udiff patch.
|
|
21
|
+
- Glob/filter — bulk update via `updateBodyByPattern`.
|
|
22
|
+
|
|
23
|
+
## Projection
|
|
24
|
+
|
|
25
|
+
Shows `set {file}` with token delta (`before→after tokens`). Includes the merge conflict block when a SEARCH/REPLACE was performed.
|
|
26
|
+
|
|
27
|
+
## Behavior
|
|
28
|
+
|
|
29
|
+
- **Literal match first**: SEARCH text is matched literally against the entry body.
|
|
30
|
+
- **Heuristic fallback**: On literal failure, `HeuristicMatcher.matchAndPatch` attempts fuzzy matching with warnings.
|
|
31
|
+
- **Patch generation**: `generatePatch` produces unified diff format for client display.
|
|
32
|
+
- File writes are always `proposed`; K/V writes resolve immediately.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
## <set path="[path/to/file]">[edit]</set> - Edit a file or entry
|
|
2
|
+
Example: <set path="src/config.js">s/base_url = http:\/\/localhost/base_url = http:\/\/0.0.0.0/g s/port = 3000/port = 8080/g</set>
|
|
3
|
+
* All editing syntaxes supported: s/old/new/, {"search":"old","replace":"new"}, SEARCH/REPLACE blocks
|
|
4
|
+
* Do not use <sh/> or <env/> to read, create, update, or delete files or entries
|