@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
@@ -3,30 +3,26 @@ CREATE VIEW IF NOT EXISTS v_model_context AS
3
3
  WITH
4
4
  visible AS (
5
5
  SELECT
6
- ke.run_id
7
- , ke.id
8
- , ke.path
9
- , ke.body
10
- , ke.scheme
11
- , ke.status
12
- , ke.fidelity
13
- , ke.turn
14
- , ke.updated_at
15
- , ke.attributes
16
- , ke.tokens
6
+ rv.run_id
7
+ , rv.id
8
+ , e.path
9
+ , e.body
10
+ , e.scheme
11
+ , rv.state
12
+ , rv.outcome
13
+ , rv.visibility
14
+ , rv.turn
15
+ , rv.updated_at
16
+ , e.attributes
17
17
  , COALESCE(s.category, 'logging') AS category
18
18
  , CASE
19
- -- Archived entries not in context
20
- WHEN ke.fidelity = 'archived' THEN NULL
21
- -- 202 Accepted (proposed) hidden until resolved
22
- WHEN ke.status = 202 THEN NULL
23
- -- Audit schemes (model_visible = 0) hidden
19
+ WHEN rv.visibility = 'archived' THEN NULL
24
20
  WHEN s.model_visible = 0 THEN NULL
25
- -- Everything else visible at its fidelity
26
- ELSE ke.fidelity
27
- END AS visible_fidelity
28
- FROM known_entries AS ke
29
- JOIN schemes AS s ON s.name = COALESCE(ke.scheme, 'file')
21
+ ELSE rv.visibility
22
+ END AS effective_visibility
23
+ FROM run_views AS rv
24
+ JOIN entries AS e ON e.id = rv.entry_id
25
+ JOIN schemes AS s ON s.name = COALESCE(e.scheme, 'file')
30
26
  ),
31
27
  projected AS (
32
28
  SELECT
@@ -34,27 +30,28 @@ projected AS (
34
30
  , id
35
31
  , path
36
32
  , scheme
37
- , status
38
- , visible_fidelity AS fidelity
33
+ , state
34
+ , outcome
35
+ , effective_visibility AS visibility
39
36
  , turn
40
37
  , updated_at
41
38
  , attributes
42
39
  -- Category comes from schemes table — plugins declare it via registerScheme().
43
40
  , category
44
- , tokens
45
41
  , CASE
46
- WHEN visible_fidelity IN ('promoted', 'demoted') THEN body
42
+ WHEN effective_visibility IN ('visible', 'summarized') THEN body
47
43
  ELSE ''
48
44
  END AS body
49
45
  FROM visible
50
- WHERE visible_fidelity IS NOT NULL
46
+ WHERE effective_visibility IS NOT NULL
51
47
  )
52
48
  SELECT
53
49
  run_id
54
50
  , path
55
51
  , scheme
56
- , fidelity
57
- , status
52
+ , visibility
53
+ , state
54
+ , outcome
58
55
  , body
59
56
  , attributes
60
57
  , category
@@ -71,13 +68,12 @@ SELECT
71
68
  ELSE 5
72
69
  END
73
70
  , CASE scheme WHEN 'skill' THEN 0 ELSE 1 END
74
- , CASE fidelity
75
- WHEN 'demoted' THEN 0
71
+ , CASE visibility
72
+ WHEN 'summarized' THEN 0
76
73
  ELSE 1
77
74
  END
78
75
  , turn
79
76
  , updated_at
80
77
  , id
81
78
  ) AS ordinal
82
- , countTokens(body) AS tokens
83
79
  FROM projected;
@@ -4,20 +4,15 @@ SELECT
4
4
  ke.run_id
5
5
  , ke.path
6
6
  , ke.body
7
- , ke.status
8
- , COALESCE(ke.scheme, 'file') AS tool
9
- , COALESCE(
10
- json_extract(ke.attributes, '$.command')
11
- , json_extract(ke.attributes, '$.file')
12
- , json_extract(ke.attributes, '$.path')
13
- , json_extract(ke.attributes, '$.question')
14
- , ''
15
- ) AS target
7
+ , ke.state
8
+ , ke.outcome
9
+ , ke.turn
10
+ , ke.scheme AS tool
11
+ , ke.attributes
16
12
  FROM known_entries AS ke
17
- JOIN schemes AS s ON s.name = COALESCE(ke.scheme, 'file')
13
+ JOIN schemes AS s ON s.name = ke.scheme
18
14
  WHERE
19
- ke.scheme IS NOT NULL
20
- AND ke.status != 202
21
- AND s.category NOT IN ('knowledge', 'file')
22
- AND ke.scheme NOT IN ('system', 'reasoning', 'model', 'content')
15
+ s.category IN ('logging', 'prompt', 'unknown')
16
+ AND ke.state != 'proposed'
17
+ AND ke.scheme != 'run'
23
18
  ORDER BY ke.id;
package/EXCEPTIONS.md DELETED
@@ -1,46 +0,0 @@
1
- # EXCEPTIONS.md — Documented Backbone Responsibilities
2
-
3
- Operations that bypass the plugin protocol. Each must justify WHY it
4
- can't go through the standard tool handler path. If the justification
5
- is weak, the exception should be eliminated.
6
-
7
- ## Resolved
8
-
9
- ### File.setConstraint / File.dropConstraint
10
-
11
- **What:** Direct DB writes to `file_constraints` table.
12
- **Justification:** File constraints are project-level config — they
13
- define which files a project cares about. This is backbone, not tool
14
- dispatch. Entry promotion/demotion that follows constraints now goes
15
- through the standard tool handler chain via `dispatchTool`.
16
- **Boundary documented:** SPEC.md §2.3.
17
-
18
- ## Currently Identified
19
-
20
- ### 1. TurnExecutor#record — tool-specific handling
21
-
22
- **What:** `known`, `unknown`, `summarize`, `update` have special-case
23
- code in `#record` (dedup, slug paths, lifecycle classification).
24
- **Bypasses:** These tools don't go through the same dispatch path as
25
- `get`, `set`, `rm` etc.
26
- **Justification:** Lifecycle signals (`summarize`, `update`) are state
27
- declarations, not tool operations — they always dispatch and cannot be
28
- 409'd. `known` and `unknown` generate their own paths from body content
29
- (slug paths). The classification is a fundamental architectural split
30
- (lifecycle vs action), not a protocol violation.
31
-
32
- ### 2. Token math — multiple measurement points
33
-
34
- **What:** `known_entries.tokens`, `turn_context.tokens`,
35
- `turns.context_tokens`, `countTokens()` estimates.
36
- **Bypasses:** No single function call, but a strict rule.
37
- **Justification:** Each serves a different purpose. `known_entries.tokens`
38
- is display-only (model sees entry sizes in `<knowns>`). `turn_context.tokens`
39
- is per-turn snapshot. `turns.context_tokens` is assembled ground truth for
40
- budget. The rule: budget decisions use ONLY assembled message tokens.
41
- DB tokens are NEVER used for budget. Documented in PLUGINS.md §7.5.
42
-
43
- ---
44
-
45
- *This file should shrink over time. Every entry is a debt to be paid
46
- or a boundary to be justified.*
@@ -1,172 +0,0 @@
1
- # Fidelity Contract — Observed State vs Intended
2
-
3
- ## Observed Behavior (traced from test/mab/results/2026-04-14T15-13-55-950Z/last_run.txt, turn 24)
4
-
5
- ### Flow
6
-
7
- ```
8
- Model emits tool
9
-
10
- Tool handler stores body in known_entries.body (raw, as model wrote it)
11
-
12
- Next turn: TurnExecutor materializes context
13
-
14
- For each row: hooks.tools.view(scheme, entry) → plugin's view hook returns projected body
15
-
16
- Projected body stored in turn_context.body with fidelity-projected token count
17
-
18
- Assembly phase: section renderers (knowns, unknowns, previous, performed) pull from ctx.rows (which has projected body) and render tags
19
-
20
- Model sees the assembled <knowns>, <previous>, etc. sections in the system prompt
21
- ```
22
-
23
- ### Fidelity Values (from code)
24
-
25
- - **full**: fully shown
26
- - **summary**: "compact" shown — but WHAT "compact" means varies per plugin
27
- - **archive**: excluded by `v_model_context` SQL before reaching any renderer (clean)
28
-
29
- ## Three Breaks in the Intended Contract
30
-
31
- ### Break 1 — Plugins disagree on what summary means
32
-
33
- Every plugin that registers view hooks decides what body to project per fidelity. Observed:
34
-
35
- | Plugin | full() | summary() |
36
- |--------|--------|-----------|
37
- | known | `# known ${path}\n${body}` | **same as full** (wrong) |
38
- | prompt | `body` | **500-char truncation + marker** (correct) |
39
- | budget | `body` | `body` (ok — budget is naturally short) |
40
- | skill | `body` | `body` (inherited default) |
41
- | unknown | varies — needs audit | needs audit |
42
- | others | needs audit | needs audit |
43
-
44
- The `known` plugin's `summary()` returning the full body is a direct contract violation. The summary view should return a compact representation of the entry, not the same full body.
45
-
46
- ### Break 2 — Renderers re-apply fidelity logic
47
-
48
- Two renderers currently re-check entry fidelity and override the plugin's projection:
49
-
50
- **`known.js` `renderKnownTag`** (lines 111-115):
51
- ```js
52
- if (entry.fidelity === "archive") return "";
53
- if (entry.fidelity === "summary") {
54
- return `<${tag} path="${entry.path}"...${summary}${fidelity}${tokens}${flag}/>`;
55
- }
56
- return `<${tag} path="${entry.path}"...${summary}${fidelity}${tokens}${flag}>${entry.body}</${tag}>`;
57
- ```
58
-
59
- This ignores entry.body at summary fidelity and renders self-closing. It's a workaround for known.summary() returning the wrong content. Belt over broken suspenders.
60
-
61
- **`previous.js` `renderToolTag`** (my edit this session):
62
- ```js
63
- if (entry.fidelity === "full") {
64
- return `<${entry.scheme} ${attrs}>${body}</${entry.scheme}>`;
65
- }
66
- // summary: self-closing with summary attr
67
- ```
68
-
69
- I added this fidelity re-check when I should have trusted the plugin's projected body. Same mistake as known, added today.
70
-
71
- ### Break 3 — Model writes scheme headers into body
72
-
73
- Every known/update/unknown entry in the DB has a body that starts with `# known known://path\n`, `# update\n`, or `# unknown\n`. The model writes this because the examples in the system prompt render tags with the body prefixed by `# ${scheme} ${path}\n`.
74
-
75
- Then the plugin's `full()` hook prepends ANOTHER `# ${scheme} ${path}\n` when projecting. Result: duplicate headers in the rendered output.
76
-
77
- Observed in turn 16 update body: `"# update\n# update\nDocuments 20-22 indexed and archived."`
78
-
79
- And in unknown paths: the slug-generation for pathless unknowns takes the body including the `# unknown\n` prefix, resulting in URL-encoded paths like:
80
- ```
81
- unknown://%23%20unknown%0ADocument%2023%20is%20missing%20from%20the%20prompt.
82
- ```
83
-
84
- ## The Intended Contract
85
-
86
- Based on the user's stated philosophy ("surface problems, don't solve them; plugin decides, renderer renders"):
87
-
88
- ### Layer 1 — Plugin decides per fidelity
89
-
90
- Each plugin registers view hooks that return the body content for each fidelity value:
91
-
92
- ```js
93
- core.hooks.tools.onView("known", (entry) => entry.body, "full");
94
- core.hooks.tools.onView("known", (entry) => "", "summary");
95
- ```
96
-
97
- At archive, no view hook is called (v_model_context excludes them).
98
-
99
- ### Layer 2 — Renderer shows the projected body
100
-
101
- Renderers take the projected body from `ctx.rows[].body`:
102
- - If non-empty, wrap in tag with body
103
- - If empty, render self-closing tag
104
-
105
- Renderers do NOT re-check entry.fidelity. They trust the plugin's projection.
106
-
107
- ### Layer 3 — Tag attributes always present
108
-
109
- Tag attributes visible in both full and summary rendering:
110
- - `path` — always
111
- - `summary` — if present in entry.attributes.summary
112
- - `turn` — if source_turn is set
113
- - `status` — if status is set
114
- - `fidelity` — always (the value itself)
115
- - `tokens` — always (full-cost value, unchanged by fidelity per `set_fidelity` SQL)
116
-
117
- ### Per-plugin view decisions (revised)
118
-
119
- | Plugin | Category | Full body | Summary body | Notes |
120
- |--------|----------|-----------|--------------|-------|
121
- | known | data | `entry.body` (no `# known` prefix) | `""` | Tag's summary attr carries the keywords |
122
- | unknown | unknown | `entry.body` | `""` | Same pattern as known/skill — summary attr carries the label |
123
- | prompt | prompt | `entry.body` | 500-char truncation with `[truncated...]` | Current behavior is correct |
124
- | budget | logging | `entry.body` | `entry.body` | Feedback signal — always full |
125
- | update | logging | `entry.body` | `entry.body` | Already 80-char capped |
126
- | summarize | logging | `entry.body` | `entry.body` | Already 80-char capped |
127
- | get | logging | result body | `""` | Just the action tag at summary |
128
- | set, rm, cp, mv | logging | result body | `""` | Just the action tag at summary |
129
- | env, sh | logging | output | `""` | Just the action tag at summary |
130
- | search | logging | results | `""` | Just the action tag at summary |
131
- | skill | data | `entry.body` | `""` | Same as known |
132
- | file | data | `entry.body` | `""` | Same as known |
133
- | http, https | data | — | — | **Move to rummy.web plugin** — not in core |
134
-
135
- ## The Body-Header Problem
136
-
137
- Separate from fidelity: the model writes `# scheme path` into the body because examples show that shape. Plugin view hooks then prepend another header.
138
-
139
- **Rule**: `# scheme` prefix belongs only in **logging** scheme outputs (tool execution results where the prefix identifies the log entry type). Non-logging schemes (known, unknown, prompt, data entries) should have no body prefix — tag attributes identify the entry.
140
-
141
- **What to remove**:
142
- - `known.js` `full()`: remove `# known ${entry.path}\n` prefix — just return `entry.body`
143
- - `unknown.js` `full()`: remove any `# unknown\n` prefix
144
- - Tooldoc examples for known/unknown that show bodies starting with `# scheme path` — remove so model stops copying
145
-
146
- **What to keep**:
147
- - Logging plugins (update, summarize, budget, get, set, etc.) may keep `# scheme` prefixes if present — they're describing tool execution results.
148
-
149
- ## Test Plan
150
-
151
- To enforce the contract:
152
-
153
- 1. **Per-plugin unit tests**: Each plugin with fidelity-sensitive views tests `full(entry)` and `summary(entry)` return the expected content.
154
- 2. **Renderer tests**: Each section renderer (knowns, previous, performed, unknowns) tests that it trusts `entry.body` without re-checking fidelity.
155
- 3. **Integration test**: Load a DB with entries at each fidelity, assemble context, verify:
156
- - Archive entries absent from any section
157
- - Summary entries visible as compact tags
158
- - Full entries visible with body
159
- - No double headers in bodies
160
- 4. **Contract lint**: Grep for `entry.fidelity ===` in renderer files — should have zero matches.
161
-
162
- ## Deliverable Order
163
-
164
- Before touching code, this document should be reviewed. Once aligned, the fix order would be:
165
-
166
- 1. Fix plugin view hooks to return correct body per fidelity
167
- 2. Remove fidelity re-checks from renderers
168
- 3. Remove the `# scheme path` header prepending (plugin-side) and examples (tooldoc-side)
169
- 4. Write tests per the plan above
170
- 5. Regenerate a sample context packet to confirm clean output
171
-
172
- No silent interventions. No belt-and-suspenders logic. Plugin projects, renderer renders, model sees honest representation.
@@ -1,337 +0,0 @@
1
- import slugify from "../sql/functions/slugify.js";
2
-
3
- export default class KnownStore {
4
- #db;
5
- #onChanged;
6
- #schemes = new Map();
7
- #seq = 0;
8
- #pendingResolutions = new Map();
9
-
10
- constructor(db, { onChanged } = {}) {
11
- this.#db = db;
12
- this.#onChanged = onChanged || null;
13
- }
14
-
15
- async loadSchemes(db) {
16
- const rows = await (db || this.#db).get_all_schemes.all();
17
- this.#schemes.clear();
18
- for (const row of rows) {
19
- this.#schemes.set(row.name, row);
20
- }
21
- }
22
-
23
- #emitChanged(runId, path, changeType) {
24
- if (this.#onChanged) this.#onChanged({ runId, path, changeType });
25
- }
26
-
27
- static scheme(path) {
28
- const idx = path.indexOf("://");
29
- return idx > 0 ? path.slice(0, idx) : null;
30
- }
31
-
32
- static normalizePath(path) {
33
- if (!path?.includes("://")) return path;
34
- const sep = path.indexOf("://");
35
- const scheme = path.slice(0, sep).toLowerCase();
36
- const rest = path.slice(sep + 3);
37
- try {
38
- // Decode first (idempotent), then encode — but preserve slashes
39
- const decoded = decodeURIComponent(rest);
40
- return `${scheme}://${decoded.split("/").map(encodeURIComponent).join("/")}`;
41
- } catch {
42
- return `${scheme}://${rest.split("/").map(encodeURIComponent).join("/")}`;
43
- }
44
- }
45
-
46
- async nextTurn(runId) {
47
- const row = await this.#db.next_turn.get({ run_id: runId });
48
- return row.turn;
49
- }
50
-
51
- async dedup(runId, scheme, target, turn) {
52
- const encodedTarget = encodeURIComponent(target);
53
- const turnPrefix = turn ? `turn_${turn}/` : "";
54
- const candidate = `${scheme}://${turnPrefix}${encodedTarget}`;
55
- const existing = await this.#db.get_entry_body.get({
56
- run_id: runId,
57
- path: candidate,
58
- });
59
- if (!existing) return candidate;
60
- return `${candidate}_${++this.#seq}`;
61
- }
62
-
63
- async slugPath(runId, scheme, content, summary) {
64
- const source = summary || content || "";
65
- const base = slugify(source);
66
- const prefix = `${scheme}://`;
67
-
68
- if (!base) return `${prefix}${++this.#seq}`;
69
-
70
- const candidate = `${prefix}${base}`;
71
- const existing = await this.#db.get_entry_body.get({
72
- run_id: runId,
73
- path: candidate,
74
- });
75
- if (!existing) return candidate;
76
-
77
- return `${prefix}${base}_${++this.#seq}`;
78
- }
79
-
80
- async upsert(
81
- runId,
82
- turn,
83
- path,
84
- body,
85
- status,
86
- {
87
- fidelity = "promoted",
88
- attributes = null,
89
- hash = null,
90
- updatedAt = null,
91
- loopId = null,
92
- } = {},
93
- ) {
94
- const normalized = KnownStore.normalizePath(path);
95
- await this.#db.upsert_known_entry.run({
96
- run_id: runId,
97
- loop_id: loopId,
98
- turn,
99
- path: normalized,
100
- body,
101
- status,
102
- fidelity,
103
- hash,
104
- attributes: attributes ? JSON.stringify(attributes) : null,
105
- updated_at: updatedAt,
106
- });
107
- this.#emitChanged(runId, normalized, "upsert");
108
- }
109
-
110
- async promote(runId, path, turn) {
111
- const normalized = KnownStore.normalizePath(path);
112
- await this.#db.promote_path.run({
113
- run_id: runId,
114
- path: normalized,
115
- turn,
116
- });
117
- this.#emitChanged(runId, normalized, "promote");
118
- }
119
-
120
- async setFileFidelity(runId, pattern, fidelity) {
121
- const result = await this.#db.set_file_fidelity.run({
122
- run_id: runId,
123
- pattern,
124
- fidelity,
125
- });
126
- if (result.changes === 0) {
127
- await this.upsert(runId, 0, pattern, "", 200, { fidelity });
128
- }
129
- this.#emitChanged(runId, pattern, "fidelity");
130
- }
131
-
132
- async setFidelity(runId, path, fidelity) {
133
- const normalized = KnownStore.normalizePath(path);
134
- await this.#db.set_fidelity.run({
135
- run_id: runId,
136
- path: normalized,
137
- fidelity,
138
- });
139
- this.#emitChanged(runId, normalized, "fidelity");
140
- }
141
-
142
- async demote(runId, path) {
143
- const normalized = KnownStore.normalizePath(path);
144
- await this.#db.demote_path.run({
145
- run_id: runId,
146
- path: normalized,
147
- });
148
- this.#emitChanged(runId, normalized, "demote");
149
- }
150
-
151
- async remove(runId, path) {
152
- const normalized = KnownStore.normalizePath(path);
153
- await this.#db.delete_known_entry.run({
154
- run_id: runId,
155
- path: normalized,
156
- });
157
- this.#emitChanged(runId, normalized, "remove");
158
- }
159
-
160
- async removeFilesByPattern(runId, pattern) {
161
- await this.#db.delete_file_entries_by_pattern.run({
162
- run_id: runId,
163
- pattern,
164
- });
165
- this.#emitChanged(runId, pattern, "remove");
166
- }
167
-
168
- static #bodyPattern(body) {
169
- return body || null;
170
- }
171
-
172
- async promoteByPattern(runId, path, body, turn) {
173
- await this.#db.promote_by_pattern.run({
174
- run_id: runId,
175
- path,
176
- body: KnownStore.#bodyPattern(body),
177
- turn,
178
- });
179
- this.#emitChanged(runId, path, "promote");
180
- }
181
-
182
- async demoteByPattern(runId, path, body) {
183
- await this.#db.demote_by_pattern.run({
184
- run_id: runId,
185
- path,
186
- body: KnownStore.#bodyPattern(body),
187
- });
188
- this.#emitChanged(runId, path, "demote");
189
- }
190
-
191
- async getEntriesByPattern(runId, path, body, { limit, offset } = {}) {
192
- return this.#db.get_entries_by_pattern.all({
193
- run_id: runId,
194
- path,
195
- body: KnownStore.#bodyPattern(body),
196
- limit: limit ?? null,
197
- offset: offset ?? null,
198
- });
199
- }
200
-
201
- async deleteByPattern(runId, path, body) {
202
- await this.#db.delete_entries_by_pattern.run({
203
- run_id: runId,
204
- path,
205
- body: KnownStore.#bodyPattern(body),
206
- });
207
- this.#emitChanged(runId, path, "remove");
208
- }
209
-
210
- async updateBodyByPattern(runId, path, body, newBody) {
211
- await this.#db.update_body_by_pattern.run({
212
- run_id: runId,
213
- path,
214
- body: KnownStore.#bodyPattern(body),
215
- new_body: newBody,
216
- });
217
- this.#emitChanged(runId, path, "body");
218
- }
219
-
220
- async resolve(runId, path, status, body) {
221
- const normalized = KnownStore.normalizePath(path);
222
- await this.#db.resolve_known_entry.run({
223
- run_id: runId,
224
- path: normalized,
225
- status,
226
- body,
227
- });
228
- this.#emitChanged(runId, normalized, "resolve");
229
- const key = `${runId}:${normalized}`;
230
- const resolver = this.#pendingResolutions.get(key);
231
- if (resolver) {
232
- this.#pendingResolutions.delete(key);
233
- resolver();
234
- }
235
- }
236
-
237
- waitForResolution(runId, path) {
238
- const normalized = KnownStore.normalizePath(path);
239
- const key = `${runId}:${normalized}`;
240
- return new Promise((resolve) => {
241
- this.#pendingResolutions.set(key, resolve);
242
- });
243
- }
244
-
245
- async restoreSummarizedPrompts(runId) {
246
- await this.#db.restore_summarized_prompts.run({ run_id: runId });
247
- this.#emitChanged(runId, "prompt://batch", "fidelity");
248
- }
249
-
250
- async getLog(runId) {
251
- return this.#db.get_results.all({ run_id: runId });
252
- }
253
-
254
- async getEntries(runId) {
255
- return this.#db.get_known_entries.all({ run_id: runId });
256
- }
257
-
258
- async getFileEntries(runId) {
259
- return this.#db.get_file_entries.all({ run_id: runId });
260
- }
261
-
262
- async getFileStatesByPattern(runId, pattern) {
263
- return this.#db.get_file_states_by_pattern.all({ run_id: runId, pattern });
264
- }
265
-
266
- async hasRejections(runId, loopId) {
267
- const row = await this.#db.has_rejections.get({
268
- run_id: runId,
269
- loop_id: loopId,
270
- });
271
- return row.count > 0;
272
- }
273
-
274
- async hasAcceptedActions(runId) {
275
- const row = await this.#db.has_accepted_actions.get({ run_id: runId });
276
- return row.count > 0;
277
- }
278
-
279
- async getUnresolved(runId) {
280
- return this.#db.get_unresolved.all({ run_id: runId });
281
- }
282
-
283
- async countUnknowns(runId) {
284
- const row = await this.#db.count_unknowns.get({ run_id: runId });
285
- return row.count;
286
- }
287
-
288
- async getUnknownValues(runId) {
289
- const rows = await this.#db.get_unknown_values.all({ run_id: runId });
290
- return new Set(rows.map((r) => r.body));
291
- }
292
-
293
- async getBody(runId, path) {
294
- const row = await this.#db.get_entry_body.get({
295
- run_id: runId,
296
- path: KnownStore.normalizePath(path),
297
- });
298
- return row?.body ?? null;
299
- }
300
-
301
- async setAttributes(runId, path, attrs) {
302
- const normalized = KnownStore.normalizePath(path);
303
- await this.#db.update_entry_attributes.run({
304
- run_id: runId,
305
- path: normalized,
306
- attributes: JSON.stringify(attrs),
307
- });
308
- this.#emitChanged(runId, normalized, "attributes");
309
- }
310
-
311
- async getState(runId, path) {
312
- return this.#db.get_entry_state.get({
313
- run_id: runId,
314
- path: KnownStore.normalizePath(path),
315
- });
316
- }
317
-
318
- async getAttributes(runId, path) {
319
- const row = await this.#db.get_entry_attributes.get({
320
- run_id: runId,
321
- path: KnownStore.normalizePath(path),
322
- });
323
- return row?.attributes ? JSON.parse(row.attributes) : null;
324
- }
325
-
326
- async getTurnAudit(runId, turn) {
327
- return this.#db.get_turn_audit.all({ run_id: runId, turn });
328
- }
329
-
330
- static toolFromPath(path) {
331
- return KnownStore.scheme(path);
332
- }
333
-
334
- static isSystemPath(path) {
335
- return path.includes("://");
336
- }
337
- }