@possumtech/rummy 0.5.0 → 2.0.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.
Files changed (157) hide show
  1. package/.env.example +42 -5
  2. package/PLUGINS.md +389 -194
  3. package/README.md +25 -8
  4. package/SPEC.md +934 -373
  5. package/bin/demo.js +166 -0
  6. package/bin/rummy.js +9 -3
  7. package/biome/no-fallbacks.grit +50 -0
  8. package/lang/en.json +2 -2
  9. package/migrations/001_initial_schema.sql +88 -37
  10. package/package.json +13 -11
  11. package/scriptify/ask_run.js +77 -0
  12. package/service.js +50 -9
  13. package/src/agent/AgentLoop.js +476 -335
  14. package/src/agent/ContextAssembler.js +4 -4
  15. package/src/agent/Entries.js +676 -0
  16. package/src/agent/ProjectAgent.js +30 -18
  17. package/src/agent/TurnExecutor.js +232 -421
  18. package/src/agent/XmlParser.js +99 -33
  19. package/src/agent/budget.js +56 -0
  20. package/src/agent/errors.js +22 -0
  21. package/src/agent/httpStatus.js +39 -0
  22. package/src/agent/known_checks.sql +8 -4
  23. package/src/agent/known_queries.sql +9 -13
  24. package/src/agent/known_store.sql +280 -125
  25. package/src/agent/materializeContext.js +104 -0
  26. package/src/agent/runs.sql +29 -7
  27. package/src/agent/schemes.sql +14 -3
  28. package/src/agent/tokens.js +6 -0
  29. package/src/agent/turns.sql +9 -9
  30. package/src/hooks/HookRegistry.js +6 -5
  31. package/src/hooks/Hooks.js +44 -3
  32. package/src/hooks/PluginContext.js +29 -21
  33. package/src/{server → hooks}/RpcRegistry.js +2 -1
  34. package/src/hooks/RummyContext.js +139 -35
  35. package/src/hooks/ToolRegistry.js +21 -16
  36. package/src/llm/LlmProvider.js +66 -89
  37. package/src/llm/errors.js +21 -0
  38. package/src/llm/retry.js +63 -0
  39. package/src/plugins/ask_user/README.md +1 -1
  40. package/src/plugins/ask_user/ask_user.js +37 -12
  41. package/src/plugins/ask_user/ask_userDoc.js +2 -25
  42. package/src/plugins/ask_user/ask_userDoc.md +10 -0
  43. package/src/plugins/budget/README.md +27 -25
  44. package/src/plugins/budget/budget.js +306 -88
  45. package/src/plugins/cp/README.md +2 -2
  46. package/src/plugins/cp/cp.js +29 -11
  47. package/src/plugins/cp/cpDoc.js +2 -15
  48. package/src/plugins/cp/cpDoc.md +7 -0
  49. package/src/plugins/engine/README.md +2 -2
  50. package/src/plugins/engine/engine.sql +4 -4
  51. package/src/plugins/engine/turn_context.sql +10 -10
  52. package/src/plugins/env/README.md +20 -5
  53. package/src/plugins/env/env.js +45 -6
  54. package/src/plugins/env/envDoc.js +2 -23
  55. package/src/plugins/env/envDoc.md +13 -0
  56. package/src/plugins/error/README.md +16 -0
  57. package/src/plugins/error/error.js +151 -0
  58. package/src/plugins/file/README.md +6 -6
  59. package/src/plugins/file/file.js +15 -2
  60. package/src/plugins/get/README.md +1 -1
  61. package/src/plugins/get/get.js +103 -48
  62. package/src/plugins/get/getDoc.js +2 -32
  63. package/src/plugins/get/getDoc.md +36 -0
  64. package/src/plugins/hedberg/README.md +1 -2
  65. package/src/plugins/hedberg/hedberg.js +8 -4
  66. package/src/plugins/hedberg/matcher.js +16 -17
  67. package/src/plugins/hedberg/normalize.js +0 -48
  68. package/src/plugins/helpers.js +42 -2
  69. package/src/plugins/index.js +146 -123
  70. package/src/plugins/instructions/README.md +35 -9
  71. package/src/plugins/instructions/instructions.js +244 -9
  72. package/src/plugins/instructions/instructions.md +33 -0
  73. package/src/plugins/instructions/instructions_104.md +7 -0
  74. package/src/plugins/instructions/instructions_105.md +38 -0
  75. package/src/plugins/instructions/instructions_106.md +21 -0
  76. package/src/plugins/instructions/instructions_107.md +10 -0
  77. package/src/plugins/instructions/instructions_108.md +0 -0
  78. package/src/plugins/instructions/protocol.js +12 -0
  79. package/src/plugins/known/README.md +2 -2
  80. package/src/plugins/known/known.js +68 -36
  81. package/src/plugins/known/knownDoc.js +2 -17
  82. package/src/plugins/known/knownDoc.md +8 -0
  83. package/src/plugins/log/README.md +48 -0
  84. package/src/plugins/log/log.js +129 -0
  85. package/src/plugins/mv/README.md +2 -2
  86. package/src/plugins/mv/mv.js +55 -22
  87. package/src/plugins/mv/mvDoc.js +2 -18
  88. package/src/plugins/mv/mvDoc.md +10 -0
  89. package/src/plugins/ollama/README.md +15 -0
  90. package/src/{llm/OllamaClient.js → plugins/ollama/ollama.js} +40 -18
  91. package/src/plugins/openai/README.md +17 -0
  92. package/src/plugins/openai/openai.js +120 -0
  93. package/src/plugins/openrouter/README.md +27 -0
  94. package/src/plugins/openrouter/openrouter.js +121 -0
  95. package/src/plugins/persona/README.md +20 -0
  96. package/src/plugins/persona/persona.js +9 -16
  97. package/src/plugins/policy/README.md +21 -0
  98. package/src/plugins/policy/policy.js +29 -14
  99. package/src/plugins/prompt/README.md +1 -1
  100. package/src/plugins/prompt/prompt.js +64 -16
  101. package/src/plugins/rm/README.md +1 -1
  102. package/src/plugins/rm/rm.js +56 -12
  103. package/src/plugins/rm/rmDoc.js +2 -20
  104. package/src/plugins/rm/rmDoc.md +13 -0
  105. package/src/plugins/rpc/README.md +2 -2
  106. package/src/plugins/rpc/rpc.js +525 -296
  107. package/src/plugins/set/README.md +1 -1
  108. package/src/plugins/set/set.js +318 -75
  109. package/src/plugins/set/setDoc.js +2 -35
  110. package/src/plugins/set/setDoc.md +22 -0
  111. package/src/plugins/sh/README.md +28 -5
  112. package/src/plugins/sh/sh.js +50 -6
  113. package/src/plugins/sh/shDoc.js +2 -23
  114. package/src/plugins/sh/shDoc.md +13 -0
  115. package/src/plugins/skill/README.md +23 -0
  116. package/src/plugins/skill/skill.js +14 -18
  117. package/src/plugins/stream/README.md +101 -0
  118. package/src/plugins/stream/stream.js +290 -0
  119. package/src/plugins/telemetry/README.md +1 -1
  120. package/src/plugins/telemetry/telemetry.js +129 -80
  121. package/src/plugins/think/README.md +1 -1
  122. package/src/plugins/think/think.js +12 -0
  123. package/src/plugins/think/thinkDoc.js +2 -15
  124. package/src/plugins/think/thinkDoc.md +7 -0
  125. package/src/plugins/unknown/README.md +3 -3
  126. package/src/plugins/unknown/unknown.js +47 -19
  127. package/src/plugins/unknown/unknownDoc.js +2 -21
  128. package/src/plugins/unknown/unknownDoc.md +11 -0
  129. package/src/plugins/update/README.md +1 -1
  130. package/src/plugins/update/update.js +83 -5
  131. package/src/plugins/update/updateDoc.js +2 -30
  132. package/src/plugins/update/updateDoc.md +8 -0
  133. package/src/plugins/xai/README.md +23 -0
  134. package/src/{llm/XaiClient.js → plugins/xai/xai.js} +58 -37
  135. package/src/plugins/yolo/yolo.js +192 -0
  136. package/src/server/ClientConnection.js +64 -37
  137. package/src/server/SocketServer.js +23 -10
  138. package/src/server/protocol.js +11 -0
  139. package/src/sql/v_model_context.sql +27 -31
  140. package/src/sql/v_run_log.sql +9 -14
  141. package/EXCEPTIONS.md +0 -46
  142. package/FIDELITY_CONTRACT.md +0 -172
  143. package/src/agent/KnownStore.js +0 -337
  144. package/src/agent/ResponseHealer.js +0 -241
  145. package/src/llm/OpenAiClient.js +0 -100
  146. package/src/llm/OpenRouterClient.js +0 -100
  147. package/src/plugins/budget/recovery.js +0 -47
  148. package/src/plugins/instructions/preamble.md +0 -45
  149. package/src/plugins/performed/README.md +0 -15
  150. package/src/plugins/performed/performed.js +0 -45
  151. package/src/plugins/previous/README.md +0 -16
  152. package/src/plugins/previous/previous.js +0 -56
  153. package/src/plugins/progress/README.md +0 -16
  154. package/src/plugins/progress/progress.js +0 -43
  155. package/src/plugins/summarize/README.md +0 -19
  156. package/src/plugins/summarize/summarize.js +0 -32
  157. package/src/plugins/summarize/summarizeDoc.js +0 -27
@@ -1,18 +1,15 @@
1
1
  import { Parser } from "htmlparser2";
2
2
  import { parseEditContent } from "../plugins/hedberg/edits.js";
3
- import { normalizeAttrs, parseJsonEdit } from "../plugins/hedberg/normalize.js";
3
+ import { parseJsonEdit } from "../plugins/hedberg/normalize.js";
4
4
  import { parseSed } from "../plugins/hedberg/sed.js";
5
5
 
6
6
  const STORE_TOOLS = new Set(["get", "rm", "set", "mv", "cp", "search"]);
7
7
  export const ALL_TOOLS = new Set([
8
8
  ...STORE_TOOLS,
9
- "known",
10
9
  "sh",
11
10
  "env",
12
11
  "ask_user",
13
- "summarize",
14
12
  "update",
15
- "unknown",
16
13
  "think",
17
14
  ]);
18
15
 
@@ -20,8 +17,7 @@ export const ALL_TOOLS = new Set([
20
17
  * Resolve the competing attr-vs-body philosophies per tool.
21
18
  * If the canonical attribute is missing, the body fills it. Silent.
22
19
  */
23
- function resolveCommand(name, attrs, rawBody) {
24
- const a = normalizeAttrs(attrs);
20
+ function resolveCommand(name, a, rawBody) {
25
21
  const trimmed = rawBody.trim();
26
22
 
27
23
  if (name === "set") {
@@ -88,47 +84,48 @@ function resolveCommand(name, attrs, rawBody) {
88
84
  preview: a.preview,
89
85
  };
90
86
  }
91
- // Plain write or fidelity change
87
+ // Plain write or visibility change
92
88
  const body = trimmed || a.body || "";
93
89
  return { name, ...a, body };
94
90
  }
95
91
 
96
- if (name === "summarize" || name === "update" || name === "unknown") {
92
+ if (name === "update") {
97
93
  const body = trimmed || a.body || "";
98
- return { name, body };
99
- }
100
-
101
- if (name === "known") {
102
- const body = trimmed || a.body || "";
103
- const path = a.path || null;
104
- return { name, ...a, path, body };
94
+ const status = a.status ? Number(a.status) : 102;
95
+ return { name, ...a, body, status };
105
96
  }
106
97
 
107
98
  if (name === "get" || name === "rm") {
108
- const path = a.path || trimmed || null;
109
- return { name, path, body: a.body, preview: a.preview };
99
+ // Spread `a` so `line`, `limit`, `visibility`, and future attrs
100
+ // reach the handler. Earlier narrow extraction silently dropped
101
+ // `line=/limit=` and stranded the partial-read path advertised
102
+ // in getDoc.
103
+ return { name, ...a, path: a.path || trimmed || null };
110
104
  }
111
105
 
112
106
  if (name === "search") {
113
107
  const path = a.path || trimmed || null;
114
108
  const results = a.results ? Number(a.results) : null;
115
- return { name, path, results };
109
+ return { name, ...a, path, results };
116
110
  }
117
111
 
118
112
  if (name === "mv" || name === "cp") {
119
- const to = a.to || trimmed || null;
120
- return { name, path: a.path, to };
113
+ // Spread `a` so `visibility` reaches the handler. mvDoc
114
+ // advertises `<mv path="known://..." visibility="summarized"/>`
115
+ // for batch visibility changes and was silently stripping that
116
+ // attr before.
117
+ return { name, ...a, path: a.path, to: a.to || trimmed || null };
121
118
  }
122
119
 
123
120
  if (name === "sh" || name === "env") {
124
121
  const command = a.command || trimmed || null;
125
- return { name, command };
122
+ return { name, ...a, command };
126
123
  }
127
124
 
128
125
  if (name === "ask_user") {
129
126
  const question = a.question || null;
130
127
  const options = a.options || trimmed || null;
131
- return { name, question, options };
128
+ return { name, ...a, question, options };
132
129
  }
133
130
 
134
131
  return { name, ...a, body: trimmed || a.body };
@@ -143,7 +140,7 @@ export default class XmlParser {
143
140
  * @param {string} content - Raw model response text
144
141
  * @returns {{ commands: Array, warnings: string[], unparsed: string }}
145
142
  */
146
- static MAX_COMMANDS = Number(process.env.RUMMY_MAX_COMMANDS) || 99;
143
+ static MAX_COMMANDS = Number(process.env.RUMMY_MAX_COMMANDS);
147
144
 
148
145
  static parse(content) {
149
146
  if (!content) return { commands: [], warnings: [], unparsed: "" };
@@ -155,10 +152,24 @@ export default class XmlParser {
155
152
  const warnings = [];
156
153
  const textChunks = [];
157
154
 
155
+ // Pre-flight: neutralize tool tags inside markdown code spans.
156
+ // Models quote instructions containing `<get/>` etc. — the parser
157
+ // would treat them as real tool calls. Replace the angle brackets
158
+ // inside backtick spans so htmlparser2 ignores them.
159
+ const codeNeutralized = XmlParser.#neutralizeCodeSpans(normalized);
160
+
161
+ // Pre-flight: fix mismatched close tags that htmlparser2 silently
162
+ // drops (making our onclosetag recovery code unreachable). Must run
163
+ // before balanceAttrQuotes since the mismatch scan needs clean tags.
164
+ const mismatchFixed = XmlParser.#correctMismatchedCloses(
165
+ codeNeutralized,
166
+ warnings,
167
+ );
168
+
158
169
  // Pre-flight: balance unclosed attribute quotes that would otherwise
159
170
  // cause htmlparser2 to consume the rest of input as a single attribute
160
171
  // value, silently dropping every subsequent tool call.
161
- const balanced = XmlParser.#balanceAttrQuotes(normalized, warnings);
172
+ const balanced = XmlParser.#balanceAttrQuotes(mismatchFixed, warnings);
162
173
  let current = null;
163
174
  let ended = false;
164
175
  let capped = false;
@@ -194,9 +205,7 @@ export default class XmlParser {
194
205
  const attrStr = Object.entries(attrs)
195
206
  .map(([k, v]) => (v === "" ? k : `${k}="${v}"`))
196
207
  .join(" ");
197
- current.rawBody += attrStr
198
- ? `<${name} ${attrStr}>`
199
- : `<${name}>`;
208
+ current.rawBody += attrStr ? `<${name} ${attrStr}>` : `<${name}>`;
200
209
  current.nested ||= [];
201
210
  current.nested.push(name);
202
211
  return;
@@ -228,10 +237,7 @@ export default class XmlParser {
228
237
  if (current) {
229
238
  // Matching nested close — pop stack, keep as text.
230
239
  const nested = current.nested;
231
- if (
232
- nested.length > 0 &&
233
- nested[nested.length - 1] === name
234
- ) {
240
+ if (nested.length > 0 && nested[nested.length - 1] === name) {
235
241
  nested.pop();
236
242
  current.rawBody += `</${name}>`;
237
243
  return;
@@ -337,6 +343,58 @@ export default class XmlParser {
337
343
  return repaired;
338
344
  }
339
345
 
346
+ /**
347
+ * Correct mismatched close tags before htmlparser2 sees them.
348
+ *
349
+ * htmlparser2 silently drops close tags that don't match the currently
350
+ * open element (e.g. `<set>body</known>` — `</known>` vanishes). This
351
+ * makes the explicit mismatch recovery in onclosetag unreachable and
352
+ * causes all subsequent sibling commands to be absorbed as body text.
353
+ *
354
+ * Conservative: only corrects when the mismatch is at the outermost
355
+ * tool depth (stack.length === 1). Nested mismatches inside body text
356
+ * are left for htmlparser2 + body opacity to handle normally.
357
+ */
358
+ /**
359
+ * Neutralize XML tags inside markdown code spans so the parser
360
+ * doesn't treat quoted tool names as real commands.
361
+ * `<get/>` → `&lt;get/&gt;` (htmlparser2 ignores entities)
362
+ */
363
+ static #neutralizeCodeSpans(content) {
364
+ return content.replace(/`([^`]*)`/g, (match, inner) => {
365
+ if (!/<\/?[\w]/.test(inner)) return match;
366
+ return `\`${inner.replace(/</g, "&lt;").replace(/>/g, "&gt;")}\``;
367
+ });
368
+ }
369
+
370
+ static #correctMismatchedCloses(content, warnings) {
371
+ const stack = [];
372
+ return content.replace(
373
+ /<(\/?)(\w+)([^>]*?)(\/?)>/g,
374
+ (match, slash, tag, _attrs, selfClose) => {
375
+ if (!ALL_TOOLS.has(tag)) return match;
376
+ if (selfClose === "/") return match;
377
+ if (slash === "/") {
378
+ if (stack.length === 0) return match;
379
+ if (stack[stack.length - 1] === tag) {
380
+ stack.pop();
381
+ return match;
382
+ }
383
+ if (stack.length === 1) {
384
+ const top = stack.pop();
385
+ warnings.push(
386
+ `Mismatched </${tag}> closing <${top}> — corrected to </${top}>`,
387
+ );
388
+ return `</${top}>`;
389
+ }
390
+ return match;
391
+ }
392
+ stack.push(tag);
393
+ return match;
394
+ },
395
+ );
396
+ }
397
+
340
398
  /**
341
399
  * Normalize native tool call formats to rummy XML.
342
400
  * Models sometimes emit their training-format tool calls instead of
@@ -359,7 +417,7 @@ export default class XmlParser {
359
417
  (match, qualifiedName, params) => {
360
418
  const name = qualifiedName.match(/\w+$/)?.[0] ?? qualifiedName;
361
419
  if (!ALL_TOOLS.has(name)) {
362
- return `<error>Unknown tool '${qualifiedName}' in <|tool_call> format. Use XML tool commands listed above.</error>`;
420
+ return `<error>Unknown command '${qualifiedName}' in <|tool_call> format. Use XML commands listed above.</error>`;
363
421
  }
364
422
  const valueMatch = params.match(
365
423
  /[=:]\s*(?:<\|"\|>([^<]*?)<\|"\|>|"([^"]*)"|'([^']*)'|([^,}]+))/,
@@ -422,12 +480,20 @@ export default class XmlParser {
422
480
  result = result.replace(
423
481
  /<\|tool_call>[\s\S]*?(?:<\|?tool_call\|?>|<\/\w+>|$)/g,
424
482
  () =>
425
- "<error>Native tool call format not supported. Use the XML tool commands listed above (e.g. a get tag with a path attribute, or a set tag with path and body).</error>",
483
+ "<error>Native tool call format not supported. Use the XML commands listed above (e.g. a get tag with a path attribute, or a set tag with path and body).</error>",
426
484
  );
427
485
 
428
486
  // Strip any orphan chat-format quote tokens left after replacement.
429
487
  result = result.replace(/<\|"\|>/g, '"');
430
488
 
489
+ // Gemma sometimes leaks OpenAI-harmony channel markers around its
490
+ // real XML output: `<|channel>thought\n<channel|>…<set path=…/>`.
491
+ // These aren't tool calls (handled above), they're role/channel
492
+ // tokens. Strip any remaining `<|name>` / `<name|>` pseudo-tags
493
+ // before the XML parser sees them.
494
+ result = result.replace(/<\|[\w:/-]+>/g, "");
495
+ result = result.replace(/<[\w:/-]+\|>/g, "");
496
+
431
497
  return result;
432
498
  }
433
499
  }
@@ -0,0 +1,56 @@
1
+ import { countTokens } from "./tokens.js";
2
+
3
+ const CEILING_RATIO = Number(process.env.RUMMY_BUDGET_CEILING);
4
+ if (!CEILING_RATIO) throw new Error("RUMMY_BUDGET_CEILING must be set");
5
+
6
+ export function ceiling(contextSize) {
7
+ return Math.floor(contextSize * CEILING_RATIO);
8
+ }
9
+
10
+ /**
11
+ * Sum assembled-message token counts.
12
+ * Used by the budget enforce gate, which has the real messages.
13
+ */
14
+ export function measureMessages(messages) {
15
+ return messages.reduce((sum, m) => sum + countTokens(m.content), 0);
16
+ }
17
+
18
+ /**
19
+ * Sum projected row body token counts — what's actually in the packet
20
+ * for each entry at its current visibility. Used by prompt.js while
21
+ * generating the <prompt> tag (before assembly completes).
22
+ */
23
+ export function measureRows(rows) {
24
+ return rows.reduce((sum, r) => sum + countTokens(r.body), 0);
25
+ }
26
+
27
+ /**
28
+ * Single source of truth for budget numbers. Every caller — prompt.js
29
+ * generating the <prompt> tag, budget.js enforcing the ceiling,
30
+ * AgentLoop emitting telemetry — passes in its own measured totalTokens
31
+ * and reads the same object back. No fallbacks: callers produce the
32
+ * measurement they have.
33
+ *
34
+ * Returns:
35
+ * ceiling — floor(contextSize × CEILING_RATIO), the hard wall
36
+ * totalTokens — echoed back (the full packet size the caller measured)
37
+ * tokenUsage — same as totalTokens. Kept under this name for the
38
+ * `<prompt tokenUsage="N">` attribute on the wire. Must
39
+ * agree with totalTokens so the model's math is honest.
40
+ * tokensFree — ceiling − totalTokens (floor 0)
41
+ * overflow — max(0, totalTokens − ceiling)
42
+ * ok — overflow === 0
43
+ */
44
+ export function computeBudget({ contextSize, totalTokens }) {
45
+ const cap = ceiling(contextSize);
46
+ const tokensFree = Math.max(0, cap - totalTokens);
47
+ const overflow = Math.max(0, totalTokens - cap);
48
+ return {
49
+ ceiling: cap,
50
+ totalTokens,
51
+ tokenUsage: totalTokens,
52
+ tokensFree,
53
+ overflow,
54
+ ok: overflow === 0,
55
+ };
56
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Typed errors for the agent/Entries layer. Callers catch by type,
3
+ * not by regex.
4
+ */
5
+
6
+ /**
7
+ * Thrown when a writer tier isn't permitted to write to a scheme.
8
+ * See SPEC writer_tiers: schemes declare writable_by = subset of
9
+ * {system, plugin, client, model}. A write from an excluded tier
10
+ * rejects with this error.
11
+ */
12
+ export class PermissionError extends Error {
13
+ constructor(scheme, writer, allowed) {
14
+ super(
15
+ `403: writer "${writer}" not permitted for scheme "${scheme ?? "file"}" (allowed: ${allowed.join(", ")})`,
16
+ );
17
+ this.name = "PermissionError";
18
+ this.scheme = scheme ?? "file";
19
+ this.writer = writer;
20
+ this.allowed = [...allowed];
21
+ }
22
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Map the entry-layer (state, outcome) tuple to an HTTP status number for
3
+ * model-facing tag rendering.
4
+ *
5
+ * Model-facing tags still carry `status="NNN"` because the model's
6
+ * vocabulary (instructions + tooldocs + training) is HTTP-shaped. The DB
7
+ * stores categorical state + textual outcome (see SPEC entries); this helper
8
+ * is the one-way translation for rendering.
9
+ *
10
+ * Outcome strings prefixed with a 3-digit HTTP code (e.g.
11
+ * `"overflow:413:..."` or `"permission:403:..."`) extract the code
12
+ * verbatim. Otherwise state maps to a canonical HTTP:
13
+ *
14
+ * resolved → 200
15
+ * proposed → 202
16
+ * streaming → 102
17
+ * cancelled → 499
18
+ * failed → 500 (unless outcome carries a code)
19
+ */
20
+ export function stateToStatus(state, outcome = null) {
21
+ if (outcome) {
22
+ const match = /(\d{3})/.exec(outcome);
23
+ if (match) return Number(match[1]);
24
+ }
25
+ switch (state) {
26
+ case "resolved":
27
+ return 200;
28
+ case "proposed":
29
+ return 202;
30
+ case "streaming":
31
+ return 102;
32
+ case "cancelled":
33
+ return 499;
34
+ case "failed":
35
+ return 500;
36
+ default:
37
+ throw new Error(`stateToStatus: unknown state "${state}"`);
38
+ }
39
+ }
@@ -17,27 +17,31 @@ SELECT path, body, attributes, turn
17
17
  FROM known_entries
18
18
  WHERE
19
19
  run_id = :run_id
20
- AND status = 202;
20
+ AND state = 'proposed';
21
21
 
22
22
  -- PREP: has_rejections
23
+ -- Any failed entry in this loop counts as a rejection. Callers use
24
+ -- this to mark the turn as having errors. Specific failure categories
25
+ -- live in run_views.outcome (permission:, overflow:, validation:, ...).
23
26
  SELECT COUNT(*) AS count
24
27
  FROM known_entries
25
28
  WHERE
26
29
  run_id = :run_id
27
30
  AND loop_id = :loop_id
28
- AND status = 403;
31
+ AND state = 'failed';
29
32
 
30
33
  -- PREP: has_accepted_actions
31
34
  SELECT COUNT(*) AS count
32
35
  FROM known_entries
33
36
  WHERE
34
37
  run_id = :run_id
35
- AND status = 200
38
+ AND state = 'resolved'
36
39
  AND scheme IN ('set', 'sh', 'rm', 'mv', 'cp');
37
40
 
38
41
  -- PREP: get_file_entries
39
- SELECT path, status, fidelity, hash, updated_at
42
+ SELECT path, state, outcome, visibility, hash, updated_at
40
43
  FROM known_entries
41
44
  WHERE
42
45
  run_id = :run_id
43
46
  AND scheme IS NULL;
47
+
@@ -1,11 +1,13 @@
1
1
  -- PREP: get_known_entries
2
- SELECT path, scheme, status, fidelity, body, turn, hash, attributes, tokens
2
+ SELECT
3
+ path, scheme, state, outcome, visibility, body, turn, hash
4
+ , attributes, countTokens(body) AS tokens, scope, loop_id
3
5
  FROM known_entries
4
6
  WHERE run_id = :run_id
5
7
  ORDER BY path;
6
8
 
7
9
  -- PREP: get_results
8
- SELECT tool, target, status, path, body
10
+ SELECT tool, state, outcome, path, body, turn, attributes
9
11
  FROM v_run_log
10
12
  WHERE run_id = :run_id;
11
13
 
@@ -18,7 +20,7 @@ WHERE
18
20
  ORDER BY id;
19
21
 
20
22
  -- PREP: get_turn_audit
21
- SELECT path, scheme, status, fidelity, turn, body, attributes
23
+ SELECT path, scheme, state, outcome, visibility, turn, body, attributes
22
24
  FROM known_entries
23
25
  WHERE
24
26
  run_id = :run_id
@@ -53,24 +55,18 @@ ORDER BY id DESC
53
55
  LIMIT 1;
54
56
 
55
57
  -- PREP: get_latest_summary
58
+ -- Updates live in the unified log namespace at log://turn_N/update/<slug>,
59
+ -- not at a dedicated "update" scheme. Match path shape instead of scheme.
56
60
  SELECT body
57
61
  FROM known_entries
58
62
  WHERE
59
63
  run_id = :run_id
60
64
  AND loop_id = :loop_id
61
- AND scheme = 'summarize'
65
+ AND path LIKE 'log://turn_%/update/%'
62
66
  ORDER BY id DESC
63
67
  LIMIT 1;
64
68
 
65
- -- PREP: get_history
66
- SELECT ke.path, ke.status, ke.body, ke.attributes, ke.turn
67
- FROM known_entries AS ke
68
- JOIN schemes AS s ON s.name = COALESCE(ke.scheme, 'file')
69
- WHERE
70
- ke.run_id = :run_id
71
- AND ke.scheme IS NOT NULL
72
- AND s.category NOT IN ('knowledge', 'file', 'audit')
73
- ORDER BY ke.id;
69
+ -- get_history retired — use get_results (v_run_log) for both run/state and getRun.
74
70
 
75
71
  -- PREP: get_content
76
72
  SELECT path, body, turn