@possumtech/rummy 0.2.6 → 0.2.8

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.
Files changed (60) hide show
  1. package/.env.example +2 -3
  2. package/PLUGINS.md +105 -82
  3. package/bin/rummy.js +9 -2
  4. package/migrations/001_initial_schema.sql +53 -68
  5. package/package.json +4 -6
  6. package/service.js +2 -2
  7. package/src/agent/AgentLoop.js +91 -58
  8. package/src/agent/ContextAssembler.js +2 -2
  9. package/src/agent/KnownStore.js +30 -11
  10. package/src/agent/ProjectAgent.js +1 -3
  11. package/src/agent/TurnExecutor.js +119 -31
  12. package/src/agent/XmlParser.js +20 -0
  13. package/src/agent/known_checks.sql +5 -4
  14. package/src/agent/known_queries.sql +4 -3
  15. package/src/agent/known_store.sql +29 -15
  16. package/src/agent/loops.sql +63 -0
  17. package/src/agent/runs.sql +7 -7
  18. package/src/agent/schemes.sql +2 -2
  19. package/src/agent/turns.sql +3 -3
  20. package/src/hooks/PluginContext.js +1 -10
  21. package/src/hooks/RummyContext.js +16 -8
  22. package/src/plugins/ask_user/ask_user.js +3 -2
  23. package/src/plugins/cp/cp.js +7 -7
  24. package/src/plugins/current/current.js +3 -4
  25. package/src/plugins/engine/engine.sql +5 -3
  26. package/src/plugins/engine/turn_context.sql +9 -4
  27. package/src/plugins/env/docs.md +2 -0
  28. package/src/plugins/env/env.js +3 -2
  29. package/src/plugins/file/file.js +9 -19
  30. package/src/plugins/get/docs.md +7 -3
  31. package/src/plugins/get/get.js +22 -6
  32. package/src/plugins/hedberg/docs.md +0 -9
  33. package/src/plugins/hedberg/hedberg.js +2 -5
  34. package/src/plugins/hedberg/matcher.js +1 -1
  35. package/src/plugins/hedberg/patterns.js +6 -6
  36. package/src/plugins/helpers.js +2 -2
  37. package/src/plugins/index.js +28 -15
  38. package/src/plugins/instructions/instructions.js +1 -1
  39. package/src/plugins/known/known.js +9 -11
  40. package/src/plugins/mv/mv.js +7 -7
  41. package/src/plugins/previous/previous.js +6 -5
  42. package/src/plugins/progress/progress.js +6 -0
  43. package/src/plugins/prompt/prompt.js +9 -10
  44. package/src/plugins/rm/docs.md +3 -1
  45. package/src/plugins/rm/rm.js +24 -7
  46. package/src/plugins/rpc/rpc.js +33 -42
  47. package/src/plugins/set/docs.md +3 -1
  48. package/src/plugins/set/set.js +22 -16
  49. package/src/plugins/sh/sh.js +3 -2
  50. package/src/plugins/skills/skills.js +3 -4
  51. package/src/plugins/store/docs.md +2 -1
  52. package/src/plugins/store/store.js +14 -3
  53. package/src/plugins/summarize/summarize.js +1 -1
  54. package/src/plugins/telemetry/telemetry.js +17 -7
  55. package/src/plugins/unknown/unknown.js +3 -2
  56. package/src/plugins/update/update.js +1 -1
  57. package/src/server/ClientConnection.js +3 -5
  58. package/src/sql/v_model_context.sql +20 -23
  59. package/src/sql/v_run_log.sql +3 -3
  60. package/src/agent/prompt_queue.sql +0 -39
@@ -33,6 +33,12 @@ export default class Progress {
33
33
  const status = [tokenInfo, unknownInfo].filter(Boolean).join(" · ");
34
34
  if (status) parts.push(status);
35
35
 
36
+ if (ctx.demoted?.length > 0) {
37
+ parts.push(
38
+ `⚠ ${ctx.demoted.length} entries demoted to summary to fit context budget. Use <get/> to restore.`,
39
+ );
40
+ }
41
+
36
42
  if (hasCurrent) {
37
43
  parts.push(
38
44
  "The above actions were performed in response to the following prompt:",
@@ -8,21 +8,20 @@ export default class Prompt {
8
8
  }
9
9
 
10
10
  async onTurnStarted({ rummy, mode, prompt, isContinuation }) {
11
- const { entries: store, sequence: turn, runId } = rummy;
11
+ const { entries: store, sequence: turn, runId, loopId } = rummy;
12
12
 
13
13
  if (!isContinuation && prompt) {
14
- await store.upsert(runId, turn, `prompt://${turn}`, "", "info", {
14
+ await store.upsert(runId, turn, `prompt://${turn}`, "", 200, {
15
15
  attributes: { mode },
16
+ loopId,
17
+ });
18
+ await store.upsert(runId, turn, `${mode}://${turn}`, prompt, 200, {
19
+ loopId,
16
20
  });
17
- await store.upsert(runId, turn, `${mode}://${turn}`, prompt, "info");
18
21
  } else {
19
- await store.upsert(
20
- runId,
21
- turn,
22
- `progress://${turn}`,
23
- prompt || "",
24
- "info",
25
- );
22
+ await store.upsert(runId, turn, `progress://${turn}`, prompt || "", 200, {
23
+ loopId,
24
+ });
26
25
  }
27
26
  }
28
27
 
@@ -1,4 +1,6 @@
1
1
  ## <rm path="[path/to/file]"/> - Remove a file or entry
2
2
  Example: <rm path="src/config.js"/>
3
- Example: <rm path="unknown://42"/>
3
+ Example: <rm path="known://donald-rumsfeld-was-born-in-1932"/>
4
+ Example: <rm path="known://temp_*" preview/> (preview before deleting)
4
5
  * <rm/> removes the file or entry from context and deletes it PERMANENTLY
6
+ * Paths accept globs — use `preview` to check matches before deleting
@@ -1,13 +1,12 @@
1
1
  import { readFileSync } from "node:fs";
2
+ import KnownStore from "../../agent/KnownStore.js";
2
3
 
3
4
  export default class Rm {
4
5
  #core;
5
6
 
6
7
  constructor(core) {
7
8
  this.#core = core;
8
- core.registerScheme({
9
- validStates: ["full", "proposed", "pass", "rejected", "error", "pattern"],
10
- });
9
+ core.registerScheme();
11
10
  core.on("handler", this.handler.bind(this));
12
11
  core.on("full", this.full.bind(this));
13
12
  core.on("summary", this.summary.bind(this));
@@ -18,24 +17,42 @@ export default class Rm {
18
17
  }
19
18
 
20
19
  async handler(entry, rummy) {
21
- const { entries: store, sequence: turn, runId } = rummy;
20
+ const { entries: store, sequence: turn, runId, loopId } = rummy;
22
21
  const target = entry.attributes.path;
22
+ if (!target) {
23
+ await store.upsert(runId, turn, entry.resultPath, "", 400, {
24
+ attributes: { error: "path is required" },
25
+ loopId,
26
+ });
27
+ return;
28
+ }
29
+ const normalized = KnownStore.normalizePath(target);
23
30
  const matches = await store.getEntriesByPattern(
24
31
  runId,
25
- target,
32
+ normalized,
26
33
  entry.attributes.body,
27
34
  );
28
35
 
36
+ if (matches.length === 0) {
37
+ await store.upsert(runId, turn, entry.resultPath, "", 404, {
38
+ attributes: { path: target, error: `${target} not found` },
39
+ loopId,
40
+ });
41
+ return;
42
+ }
43
+
29
44
  for (const match of matches) {
30
45
  const resultPath = `rm://${match.path}`;
31
46
  if (match.scheme === null) {
32
- await store.upsert(runId, turn, resultPath, match.path, "proposed", {
47
+ await store.upsert(runId, turn, resultPath, match.path, 202, {
33
48
  attributes: { path: match.path },
49
+ loopId,
34
50
  });
35
51
  } else {
36
52
  await store.remove(runId, match.path);
37
- await store.upsert(runId, turn, resultPath, match.path, "pass", {
53
+ await store.upsert(runId, turn, resultPath, match.path, 200, {
38
54
  attributes: { path: match.path },
55
+ loopId,
39
56
  });
40
57
  }
41
58
  }
@@ -67,7 +67,7 @@ export default class Rpc {
67
67
  const row = await ctx.db.upsert_model.get({
68
68
  alias: params.alias,
69
69
  actual: params.actual,
70
- context_length: params.contextLength || null,
70
+ context_length: params.contextLength ?? null,
71
71
  });
72
72
  return { id: row.id, alias: params.alias };
73
73
  },
@@ -90,7 +90,7 @@ export default class Rpc {
90
90
 
91
91
  // --- Entry operations (same dispatch as model) ---
92
92
 
93
- r.register("read", {
93
+ r.register("get", {
94
94
  handler: async (params, ctx) => {
95
95
  if (!params.path) throw new Error("path is required");
96
96
 
@@ -159,7 +159,7 @@ export default class Rpc {
159
159
  requiresInit: true,
160
160
  });
161
161
 
162
- r.register("write", {
162
+ r.register("set", {
163
163
  handler: async (params, ctx) => {
164
164
  if (!params.path) throw new Error("path is required");
165
165
  if (!params.run) throw new Error("run is required");
@@ -169,19 +169,15 @@ export default class Rpc {
169
169
  if (scheme) {
170
170
  await rummy.set({
171
171
  path: params.path,
172
- body: params.body || "",
173
- state: params.state || "full",
172
+ body: params.body,
173
+ status: params.status || 200,
174
174
  attributes: params.attributes,
175
175
  });
176
176
  } else {
177
- await dispatchTool(
178
- hooks,
179
- rummy,
180
- "set",
181
- params.path,
182
- params.body || "",
183
- { path: params.path, ...params.attributes },
184
- );
177
+ await dispatchTool(hooks, rummy, "set", params.path, params.body, {
178
+ path: params.path,
179
+ ...params.attributes,
180
+ });
185
181
  }
186
182
  return { status: "ok" };
187
183
  },
@@ -190,13 +186,13 @@ export default class Rpc {
190
186
  run: "string — run alias",
191
187
  path: "string — entry path",
192
188
  body: "string? — entry content",
193
- state: "string? — entry state (default: full)",
189
+ status: "number? — HTTP status code (default: 200)",
194
190
  attributes: "object? — JSON attributes",
195
191
  },
196
192
  requiresInit: true,
197
193
  });
198
194
 
199
- r.register("delete", {
195
+ r.register("rm", {
200
196
  handler: async (params, ctx) => {
201
197
  if (!params.path) throw new Error("path is required");
202
198
  if (!params.run) throw new Error("run is required");
@@ -225,14 +221,15 @@ export default class Rpc {
225
221
  if (!run) return [];
226
222
  const entries = await ctx.projectAgent.entries.getEntriesByPattern(
227
223
  run.id,
228
- params.pattern || "*",
229
- params.body || null,
230
- { limit: params.limit, offset: params.offset },
224
+ params.pattern ?? "*",
225
+ params.body ?? null,
226
+ { limit: params.limit ?? null, offset: params.offset ?? null },
231
227
  );
232
228
  return entries.map((e) => ({
233
229
  path: e.path,
234
230
  scheme: e.scheme,
235
- state: e.state,
231
+ status: e.status,
232
+ fidelity: e.fidelity,
236
233
  tokens: e.tokens_full,
237
234
  }));
238
235
  },
@@ -256,7 +253,7 @@ export default class Rpc {
256
253
  const runRow = await ctx.db.create_run.get({
257
254
  project_id: ctx.projectId,
258
255
  parent_run_id: null,
259
- model: params.model,
256
+ model: params.model ?? null,
260
257
  alias,
261
258
  temperature: params.temperature ?? null,
262
259
  persona: params.persona ?? null,
@@ -283,8 +280,8 @@ export default class Rpc {
283
280
  params.prompt,
284
281
  params.run,
285
282
  {
286
- temperature: params.temperature,
287
- persona: params.persona,
283
+ temperature: params.temperature ?? null,
284
+ persona: params.persona ?? null,
288
285
  contextLimit: params.contextLimit,
289
286
  noContext: params.noContext,
290
287
  fork: params.fork,
@@ -315,8 +312,8 @@ export default class Rpc {
315
312
  params.prompt,
316
313
  params.run,
317
314
  {
318
- temperature: params.temperature,
319
- persona: params.persona,
315
+ temperature: params.temperature ?? null,
316
+ persona: params.persona ?? null,
320
317
  contextLimit: params.contextLimit,
321
318
  noContext: params.noContext,
322
319
  fork: params.fork,
@@ -358,7 +355,7 @@ export default class Rpc {
358
355
  ctx.projectAgent.abortRun(runRow.id);
359
356
  await ctx.db.update_run_status.run({
360
357
  id: runRow.id,
361
- status: "aborted",
358
+ status: 499,
362
359
  });
363
360
  return { status: "ok" };
364
361
  },
@@ -447,7 +444,7 @@ export default class Rpc {
447
444
  run: row.alias,
448
445
  status: row.status,
449
446
  turn: row.turn,
450
- summary: row.summary || "",
447
+ summary: row.summary,
451
448
  created: row.created_at,
452
449
  }));
453
450
  },
@@ -512,8 +509,8 @@ export default class Rpc {
512
509
  };
513
510
  }),
514
511
  },
515
- last_user_prompt: promptRow?.body || "",
516
- last_summary: summaryRow?.body || "",
512
+ last_user_prompt: promptRow?.body,
513
+ last_summary: summaryRow?.body,
517
514
  };
518
515
  },
519
516
  description: "Full run detail.",
@@ -558,25 +555,19 @@ async function buildRunContext(hooks, ctx, runAlias) {
558
555
 
559
556
  async function dispatchTool(hooks, rummy, scheme, path, body, attributes) {
560
557
  const store = rummy.entries;
561
- const resultPath = await store.dedup(rummy.runId, scheme, path || "");
558
+ const resultPath = await store.dedup(rummy.runId, scheme, path);
562
559
 
563
- await store.upsert(
564
- rummy.runId,
565
- rummy.sequence,
566
- resultPath,
567
- body || "",
568
- "full",
569
- {
570
- attributes: attributes || null,
571
- },
572
- );
560
+ await store.upsert(rummy.runId, rummy.sequence, resultPath, body, 200, {
561
+ attributes: attributes,
562
+ loopId: rummy.loopId,
563
+ });
573
564
 
574
565
  const entry = {
575
566
  scheme,
576
567
  path: resultPath,
577
- body: body || "",
578
- attributes: attributes || {},
579
- state: "full",
568
+ body: body,
569
+ attributes: attributes,
570
+ status: 200,
580
571
  resultPath,
581
572
  };
582
573
 
@@ -1,4 +1,6 @@
1
1
  ## <set path="[path/to/file]">[edit]</set> - Edit a file or entry
2
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
3
+ * All editing syntaxes supported: s/old/new/, {"search":"old","replace":"new"}, literal SEARCH/REPLACE blocks
4
+ * Chain multiple replacements: `s/old/new/ s/foo/bar/`
5
+ * Regex patterns use /slashes/: `s/console\.log.*/\/\/ removed/g`
4
6
  * Do not use <sh/> or <env/> to read, create, update, or delete files or entries
@@ -9,9 +9,7 @@ export default class Set {
9
9
 
10
10
  constructor(core) {
11
11
  this.#core = core;
12
- core.registerScheme({
13
- validStates: ["full", "proposed", "pass", "rejected", "error", "pattern"],
14
- });
12
+ core.registerScheme();
15
13
  core.on("handler", this.handler.bind(this));
16
14
  core.on("full", this.full.bind(this));
17
15
  core.on("summary", this.summary.bind(this));
@@ -23,7 +21,7 @@ export default class Set {
23
21
  }
24
22
 
25
23
  async handler(entry, rummy) {
26
- const { entries: store, sequence: turn, runId } = rummy;
24
+ const { entries: store, sequence: turn, runId, loopId } = rummy;
27
25
  const attrs = entry.attributes;
28
26
 
29
27
  if (attrs.blocks || attrs.search != null) {
@@ -45,7 +43,7 @@ export default class Set {
45
43
  attrs.path,
46
44
  attrs.body,
47
45
  matches,
48
- true,
46
+ { preview: true, loopId },
49
47
  );
50
48
  return;
51
49
  }
@@ -57,8 +55,9 @@ export default class Set {
57
55
  if (scheme === null) {
58
56
  const udiff = generatePatch(target, "", entry.body || "");
59
57
  const merge = `<<<<<<< SEARCH\n=======\n${entry.body || ""}\n>>>>>>> REPLACE`;
60
- await store.upsert(runId, turn, entry.resultPath, "", "proposed", {
58
+ await store.upsert(runId, turn, entry.resultPath, "", 202, {
61
59
  attributes: { file: target, patch: udiff, merge },
60
+ loopId,
62
61
  });
63
62
  } else if (attrs.filter || target.includes("*")) {
64
63
  const matches = await store.getEntriesByPattern(
@@ -80,9 +79,10 @@ export default class Set {
80
79
  target,
81
80
  attrs.filter,
82
81
  matches,
82
+ { loopId },
83
83
  );
84
84
  } else {
85
- await store.upsert(runId, turn, target, entry.body, "full");
85
+ await store.upsert(runId, turn, target, entry.body, 200, { loopId });
86
86
  }
87
87
  }
88
88
 
@@ -103,13 +103,14 @@ export default class Set {
103
103
  }
104
104
 
105
105
  async #processEdit(rummy, entry, attrs) {
106
- const { entries: store, sequence: turn, runId } = rummy;
106
+ const { entries: store, sequence: turn, runId, loopId } = rummy;
107
107
  const target = attrs.path;
108
108
  const matches = await store.getEntriesByPattern(runId, target, attrs.body);
109
109
 
110
110
  if (matches.length === 0) {
111
- await store.upsert(runId, turn, entry.resultPath, "", "error", {
111
+ await store.upsert(runId, turn, entry.resultPath, "", 404, {
112
112
  attributes: { file: target, error: `${target} not found in context` },
113
+ loopId,
113
114
  });
114
115
  return;
115
116
  }
@@ -121,8 +122,9 @@ export default class Set {
121
122
  const existingAttrs = await rummy.getAttributes(canonicalPath);
122
123
  const revisions = existingAttrs?.revisions || [];
123
124
  revisions.push(revision);
124
- await store.upsert(runId, turn, canonicalPath, "", "full", {
125
+ await store.upsert(runId, turn, canonicalPath, "", 200, {
125
126
  attributes: { file: match.path, revisions },
127
+ loopId,
126
128
  });
127
129
  if (KnownStore.normalizePath(entry.resultPath) !== canonicalPath) {
128
130
  await store.remove(runId, entry.resultPath);
@@ -133,7 +135,7 @@ export default class Set {
133
135
  const { patch, searchText, replaceText, warning, error } =
134
136
  Set.#applyRevision(match.body, attrs);
135
137
 
136
- const state = error ? "error" : "pass";
138
+ const status = error ? 409 : 200;
137
139
  const resultPath = `set://${match.path}`;
138
140
  const udiff = patch ? generatePatch(match.path, match.body, patch) : null;
139
141
  const merge =
@@ -143,7 +145,7 @@ export default class Set {
143
145
  const beforeTokens = match.tokens_full || 0;
144
146
  const afterTokens = patch ? (patch.length / 4) | 0 : beforeTokens;
145
147
 
146
- await store.upsert(runId, turn, resultPath, match.body, state, {
148
+ await store.upsert(runId, turn, resultPath, match.body, status, {
147
149
  attributes: {
148
150
  file: match.path,
149
151
  patch: udiff,
@@ -153,16 +155,19 @@ export default class Set {
153
155
  warning,
154
156
  error,
155
157
  },
158
+ loopId,
156
159
  });
157
160
 
158
- if (state === "pass" && patch) {
159
- await store.upsert(runId, turn, match.path, patch, match.state);
161
+ if (status === 200 && patch) {
162
+ await store.upsert(runId, turn, match.path, patch, match.status, {
163
+ loopId,
164
+ });
160
165
  }
161
166
  }
162
167
  }
163
168
 
164
169
  async #materializeRevisions({ rummy }) {
165
- const { entries: store, sequence: turn, runId } = rummy;
170
+ const { entries: store, sequence: turn, runId, loopId } = rummy;
166
171
  const setEntries = await store.getEntriesByPattern(runId, "set://*");
167
172
 
168
173
  for (const entry of setEntries) {
@@ -198,7 +203,7 @@ export default class Set {
198
203
  }
199
204
  }
200
205
 
201
- const state = lastError ? "error" : "proposed";
206
+ const state = lastError ? 409 : 202;
202
207
  const udiff =
203
208
  current !== original
204
209
  ? generatePatch(filePath, original, current)
@@ -217,6 +222,7 @@ export default class Set {
217
222
  warning: lastWarning,
218
223
  error: lastError,
219
224
  },
225
+ loopId,
220
226
  });
221
227
  }
222
228
  }
@@ -16,9 +16,10 @@ export default class Sh {
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, "proposed", {
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
 
@@ -8,7 +8,6 @@ export default class Skills {
8
8
  this.#core = core;
9
9
  core.registerScheme({
10
10
  name: "skill",
11
- validStates: ["full", "stored"],
12
11
  category: "knowledge",
13
12
  });
14
13
  const r = core.hooks.rpc.registry;
@@ -28,7 +27,7 @@ export default class Skills {
28
27
  runRow.next_turn,
29
28
  `skill://${params.name}`,
30
29
  body,
31
- "full",
30
+ 200,
32
31
  {
33
32
  attributes: {
34
33
  name: params.name,
@@ -84,10 +83,10 @@ export default class Skills {
84
83
  );
85
84
  return entries.map((e) => ({
86
85
  name: e.path.replace("skill://", ""),
87
- state: e.state,
86
+ status: e.status,
88
87
  }));
89
88
  },
90
- description: "List skills active on a run. Returns [{ name, state }].",
89
+ description: "List skills active on a run. Returns [{ name, status }].",
91
90
  params: { run: "string — run alias" },
92
91
  requiresInit: true,
93
92
  });
@@ -1,5 +1,6 @@
1
1
  ## <store path="[path/to/file]"/> - Store a file or entry
2
2
  Example: <store path="src/config.js"/>
3
- Example: <store path="unknown://42"/>
3
+ Example: <store path="src/**/*.test.js"/> (store all test files at once)
4
4
  * <store/> removes the file or entry from context, but does not delete it
5
5
  * A stored file or entry can be restored with <get/>
6
+ * Paths accept globs for bulk operations
@@ -6,7 +6,7 @@ export default class Store {
6
6
 
7
7
  constructor(core) {
8
8
  this.#core = core;
9
- core.registerScheme({ validStates: ["full", "stored", "pattern"] });
9
+ core.registerScheme();
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));
@@ -17,8 +17,15 @@ export default class Store {
17
17
  }
18
18
 
19
19
  async handler(entry, rummy) {
20
- const { entries: store, sequence: turn, runId } = rummy;
20
+ const { entries: store, sequence: turn, runId, loopId } = rummy;
21
21
  const target = entry.attributes.path;
22
+ if (!target) {
23
+ await store.upsert(runId, turn, entry.resultPath, "", 400, {
24
+ attributes: { error: "path is required" },
25
+ loopId,
26
+ });
27
+ return;
28
+ }
22
29
  const bodyFilter = entry.attributes.body || null;
23
30
  const isPattern = bodyFilter || target.includes("*");
24
31
  const matches = await store.getEntriesByPattern(runId, target, bodyFilter);
@@ -33,12 +40,16 @@ export default class Store {
33
40
  target,
34
41
  bodyFilter,
35
42
  matches,
43
+ { loopId },
36
44
  );
37
45
  } else {
38
46
  const paths = matches.map((m) => m.path).join(", ");
39
47
  const body =
40
48
  matches.length > 0 ? `${paths} stored` : `${target} not found`;
41
- await store.upsert(runId, turn, entry.resultPath, body, "stored");
49
+ await store.upsert(runId, turn, entry.resultPath, body, 200, {
50
+ fidelity: "stored",
51
+ loopId,
52
+ });
42
53
  }
43
54
  }
44
55
 
@@ -5,7 +5,7 @@ export default class Summarize {
5
5
 
6
6
  constructor(core) {
7
7
  this.#core = core;
8
- core.registerScheme({ validStates: ["summary"], category: "structural" });
8
+ core.registerScheme({ category: "structural" });
9
9
  core.on("full", this.full.bind(this));
10
10
  core.on("summary", this.summary.bind(this));
11
11
  const docs = readFileSync(new URL("./docs.md", import.meta.url), "utf8");
@@ -80,17 +80,23 @@ export default class Telemetry {
80
80
  systemMsg,
81
81
  userMsg,
82
82
  }) {
83
- const { entries: store, runId } = rummy;
83
+ const { entries: store, runId, loopId } = rummy;
84
84
 
85
85
  // assistant://N — the model's raw response
86
- await store.upsert(runId, turn, `assistant://${turn}`, content, "info");
86
+ await store.upsert(runId, turn, `assistant://${turn}`, content, 200, {
87
+ loopId,
88
+ });
87
89
 
88
90
  // system://N, user://N — assembled messages as audit
89
91
  if (systemMsg) {
90
- await store.upsert(runId, turn, `system://${turn}`, systemMsg, "info");
92
+ await store.upsert(runId, turn, `system://${turn}`, systemMsg, 200, {
93
+ loopId,
94
+ });
91
95
  }
92
96
  if (userMsg) {
93
- await store.upsert(runId, turn, `user://${turn}`, userMsg, "info");
97
+ await store.upsert(runId, turn, `user://${turn}`, userMsg, 200, {
98
+ loopId,
99
+ });
94
100
  }
95
101
 
96
102
  // model://N — raw API response diagnostics
@@ -105,7 +111,8 @@ export default class Telemetry {
105
111
  usage: result.usage || null,
106
112
  model: result.model || null,
107
113
  }),
108
- "info",
114
+ 200,
115
+ { loopId },
109
116
  );
110
117
 
111
118
  // reasoning://N
@@ -115,13 +122,16 @@ export default class Telemetry {
115
122
  turn,
116
123
  `reasoning://${turn}`,
117
124
  responseMessage.reasoning_content,
118
- "info",
125
+ 200,
126
+ { loopId },
119
127
  );
120
128
  }
121
129
 
122
130
  // content://N — unparsed text
123
131
  if (unparsed) {
124
- await store.upsert(runId, turn, `content://${turn}`, unparsed, "info");
132
+ await store.upsert(runId, turn, `content://${turn}`, unparsed, 200, {
133
+ loopId,
134
+ });
125
135
  }
126
136
 
127
137
  // Commit usage stats
@@ -6,7 +6,6 @@ export default class Unknown {
6
6
  constructor(core) {
7
7
  this.#core = core;
8
8
  core.registerScheme({
9
- validStates: ["full", "stored"],
10
9
  category: "knowledge",
11
10
  });
12
11
  core.on("full", this.full.bind(this));
@@ -25,7 +24,9 @@ export default class Unknown {
25
24
  const entries = ctx.rows.filter((r) => r.category === "unknown");
26
25
  if (entries.length === 0) return content;
27
26
 
28
- const lines = entries.map((u) => `<unknown>${u.body}</unknown>`);
27
+ const lines = entries.map(
28
+ (u) => `<unknown path="${u.path}">${u.body}</unknown>`,
29
+ );
29
30
  return `${content}\n\n<unknowns>\n${lines.join("\n")}\n</unknowns>`;
30
31
  }
31
32
  }
@@ -5,7 +5,7 @@ export default class Update {
5
5
 
6
6
  constructor(core) {
7
7
  this.#core = core;
8
- core.registerScheme({ validStates: ["info"], category: "structural" });
8
+ core.registerScheme({ category: "structural" });
9
9
  core.on("full", this.full.bind(this));
10
10
  core.on("summary", this.summary.bind(this));
11
11
  const docs = readFileSync(new URL("./docs.md", import.meta.url), "utf8");
@@ -110,7 +110,7 @@ export default class ClientConnection {
110
110
 
111
111
  try {
112
112
  const logRow = await this.#db.log_rpc_call.get({
113
- project_id: this.#context.projectId || null,
113
+ project_id: this.#context.projectId ?? null,
114
114
  method,
115
115
  rpc_id: id,
116
116
  params: params ? JSON.stringify(params) : null,
@@ -184,10 +184,8 @@ export default class ClientConnection {
184
184
  } catch {}
185
185
  }
186
186
  } catch (error) {
187
- if (debug) {
188
- console.error(`[SOCKET] ERR: ${error.message}`);
189
- console.error(`[DEBUG] Stack: ${error.stack}`);
190
- }
187
+ console.error(`[RUMMY] RPC Error: ${error.message}`);
188
+ console.error(`[RUMMY] Stack: ${error.stack}`);
191
189
  this.#send({
192
190
  jsonrpc: "2.0",
193
191
  error: { code: -32603, message: error.message },