@possumtech/rummy 0.2.8 → 0.3.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 (114) hide show
  1. package/.env.example +13 -2
  2. package/EXCEPTIONS.md +46 -0
  3. package/PLUGINS.md +422 -188
  4. package/SPEC.md +440 -106
  5. package/migrations/001_initial_schema.sql +5 -3
  6. package/package.json +17 -5
  7. package/service.js +5 -3
  8. package/src/agent/AgentLoop.js +252 -55
  9. package/src/agent/ContextAssembler.js +20 -4
  10. package/src/agent/KnownStore.js +82 -25
  11. package/src/agent/ProjectAgent.js +4 -1
  12. package/src/agent/ResponseHealer.js +86 -32
  13. package/src/agent/TurnExecutor.js +542 -207
  14. package/src/agent/XmlParser.js +77 -41
  15. package/src/agent/known_store.sql +68 -4
  16. package/src/agent/schemes.sql +3 -0
  17. package/src/agent/tokens.js +7 -21
  18. package/src/agent/turns.sql +15 -1
  19. package/src/hooks/HookRegistry.js +7 -0
  20. package/src/hooks/Hooks.js +15 -0
  21. package/src/hooks/PluginContext.js +14 -1
  22. package/src/hooks/RummyContext.js +16 -4
  23. package/src/hooks/ToolRegistry.js +77 -19
  24. package/src/llm/LlmProvider.js +27 -8
  25. package/src/llm/OpenAiClient.js +20 -0
  26. package/src/llm/OpenRouterClient.js +24 -2
  27. package/src/llm/XaiClient.js +47 -2
  28. package/src/plugins/ask_user/README.md +4 -4
  29. package/src/plugins/ask_user/ask_user.js +5 -5
  30. package/src/plugins/ask_user/ask_userDoc.js +29 -0
  31. package/src/plugins/budget/README.md +31 -0
  32. package/src/plugins/budget/budget.js +55 -0
  33. package/src/plugins/cp/README.md +5 -4
  34. package/src/plugins/cp/cp.js +10 -6
  35. package/src/plugins/cp/cpDoc.js +29 -0
  36. package/src/plugins/engine/engine.sql +1 -8
  37. package/src/plugins/engine/turn_context.sql +4 -9
  38. package/src/plugins/env/README.md +3 -4
  39. package/src/plugins/env/env.js +5 -5
  40. package/src/plugins/env/envDoc.js +29 -0
  41. package/src/plugins/file/README.md +9 -12
  42. package/src/plugins/file/file.js +34 -35
  43. package/src/plugins/get/README.md +2 -2
  44. package/src/plugins/get/get.js +77 -6
  45. package/src/plugins/get/getDoc.js +51 -0
  46. package/src/plugins/hedberg/hedberg.js +2 -1
  47. package/src/plugins/hedberg/matcher.js +10 -29
  48. package/src/plugins/hedberg/normalize.js +28 -0
  49. package/src/plugins/hedberg/patterns.js +25 -27
  50. package/src/plugins/hedberg/sed.js +17 -10
  51. package/src/plugins/index.js +66 -14
  52. package/src/plugins/instructions/README.md +6 -2
  53. package/src/plugins/instructions/instructions.js +20 -4
  54. package/src/plugins/instructions/preamble.md +19 -5
  55. package/src/plugins/known/README.md +10 -7
  56. package/src/plugins/known/known.js +23 -17
  57. package/src/plugins/known/knownDoc.js +34 -0
  58. package/src/plugins/mv/README.md +5 -4
  59. package/src/plugins/mv/mv.js +27 -6
  60. package/src/plugins/mv/mvDoc.js +45 -0
  61. package/src/plugins/performed/README.md +15 -0
  62. package/src/plugins/performed/performed.js +45 -0
  63. package/src/plugins/persona/persona.js +78 -0
  64. package/src/plugins/previous/README.md +3 -2
  65. package/src/plugins/previous/previous.js +33 -24
  66. package/src/plugins/progress/README.md +1 -2
  67. package/src/plugins/progress/progress.js +33 -21
  68. package/src/plugins/prompt/README.md +5 -5
  69. package/src/plugins/prompt/prompt.js +15 -17
  70. package/src/plugins/rm/README.md +4 -4
  71. package/src/plugins/rm/rm.js +32 -20
  72. package/src/plugins/rm/rmDoc.js +30 -0
  73. package/src/plugins/rpc/README.md +15 -28
  74. package/src/plugins/rpc/rpc.js +42 -77
  75. package/src/plugins/set/README.md +13 -12
  76. package/src/plugins/set/set.js +107 -16
  77. package/src/plugins/set/setDoc.js +49 -0
  78. package/src/plugins/sh/README.md +4 -4
  79. package/src/plugins/sh/sh.js +5 -5
  80. package/src/plugins/sh/shDoc.js +29 -0
  81. package/src/plugins/{skills/skills.js → skill/skill.js} +10 -51
  82. package/src/plugins/summarize/README.md +6 -5
  83. package/src/plugins/summarize/summarize.js +7 -6
  84. package/src/plugins/summarize/summarizeDoc.js +33 -0
  85. package/src/plugins/telemetry/telemetry.js +16 -9
  86. package/src/plugins/think/README.md +20 -0
  87. package/src/plugins/think/think.js +5 -0
  88. package/src/plugins/unknown/README.md +6 -5
  89. package/src/plugins/unknown/unknown.js +12 -9
  90. package/src/plugins/unknown/unknownDoc.js +31 -0
  91. package/src/plugins/update/README.md +3 -8
  92. package/src/plugins/update/update.js +7 -6
  93. package/src/plugins/update/updateDoc.js +33 -0
  94. package/src/server/ClientConnection.js +59 -45
  95. package/src/server/RpcRegistry.js +52 -4
  96. package/src/sql/v_model_context.sql +10 -25
  97. package/src/plugins/ask_user/docs.md +0 -2
  98. package/src/plugins/cp/docs.md +0 -2
  99. package/src/plugins/current/README.md +0 -14
  100. package/src/plugins/current/current.js +0 -47
  101. package/src/plugins/env/docs.md +0 -4
  102. package/src/plugins/get/docs.md +0 -10
  103. package/src/plugins/known/docs.md +0 -3
  104. package/src/plugins/mv/docs.md +0 -2
  105. package/src/plugins/rm/docs.md +0 -6
  106. package/src/plugins/set/docs.md +0 -6
  107. package/src/plugins/sh/docs.md +0 -2
  108. package/src/plugins/skills/README.md +0 -25
  109. package/src/plugins/store/README.md +0 -20
  110. package/src/plugins/store/docs.md +0 -6
  111. package/src/plugins/store/store.js +0 -63
  112. package/src/plugins/summarize/docs.md +0 -4
  113. package/src/plugins/unknown/docs.md +0 -5
  114. package/src/plugins/update/docs.md +0 -4
@@ -21,53 +21,66 @@ export default class ClientConnection {
21
21
  this.#projectAgent = new ProjectAgent(db, hooks);
22
22
 
23
23
  this.#ws.on("message", (data) => this.#handleMessage(data));
24
+ this.#ws.on("close", () => this.#teardown());
24
25
 
25
26
  this.#setupNotifications();
26
27
  }
27
28
 
28
- #setupNotifications() {
29
- this.#hooks.run.progress.on((payload) => {
30
- if (payload.projectId === this.#context.projectId) {
31
- this.#sendNotification("run/progress", {
32
- run: payload.run,
33
- turn: payload.turn,
34
- status: payload.status,
35
- });
36
- }
37
- });
29
+ #onProgress = (payload) => {
30
+ if (payload.projectId === this.#context.projectId) {
31
+ this.#sendNotification("run/progress", {
32
+ run: payload.run,
33
+ turn: payload.turn,
34
+ status: payload.status,
35
+ });
36
+ }
37
+ };
38
38
 
39
- this.#hooks.ui.render.on((payload) => {
40
- if (payload.projectId === this.#context.projectId) {
41
- this.#sendNotification("ui/render", {
42
- text: payload.text,
43
- append: payload.append,
44
- });
45
- }
46
- });
39
+ #onRender = (payload) => {
40
+ if (payload.projectId === this.#context.projectId) {
41
+ this.#sendNotification("ui/render", {
42
+ text: payload.text,
43
+ append: payload.append,
44
+ });
45
+ }
46
+ };
47
47
 
48
- this.#hooks.ui.notify.on((payload) => {
49
- if (payload.projectId === this.#context.projectId) {
50
- this.#sendNotification("ui/notify", {
51
- text: payload.text,
52
- level: payload.level,
53
- });
54
- }
55
- });
48
+ #onNotify = (payload) => {
49
+ if (payload.projectId === this.#context.projectId) {
50
+ this.#sendNotification("ui/notify", {
51
+ text: payload.text,
52
+ level: payload.level,
53
+ });
54
+ }
55
+ };
56
56
 
57
- this.#hooks.run.state.on((payload) => {
58
- if (payload.projectId === this.#context.projectId) {
59
- this.#sendNotification("run/state", {
60
- run: payload.run,
61
- turn: payload.turn,
62
- status: payload.status,
63
- summary: payload.summary,
64
- history: payload.history,
65
- unknowns: payload.unknowns,
66
- proposed: payload.proposed,
67
- telemetry: payload.telemetry,
68
- });
69
- }
70
- });
57
+ #onState = (payload) => {
58
+ if (payload.projectId === this.#context.projectId) {
59
+ this.#sendNotification("run/state", {
60
+ run: payload.run,
61
+ turn: payload.turn,
62
+ status: payload.status,
63
+ summary: payload.summary,
64
+ history: payload.history,
65
+ unknowns: payload.unknowns,
66
+ proposed: payload.proposed,
67
+ telemetry: payload.telemetry,
68
+ });
69
+ }
70
+ };
71
+
72
+ #setupNotifications() {
73
+ this.#hooks.run.progress.on(this.#onProgress);
74
+ this.#hooks.ui.render.on(this.#onRender);
75
+ this.#hooks.ui.notify.on(this.#onNotify);
76
+ this.#hooks.run.state.on(this.#onState);
77
+ }
78
+
79
+ #teardown() {
80
+ this.#hooks.run.progress.off(this.#onProgress);
81
+ this.#hooks.ui.render.off(this.#onRender);
82
+ this.#hooks.ui.notify.off(this.#onNotify);
83
+ this.#hooks.run.state.off(this.#onState);
71
84
  }
72
85
 
73
86
  #buildHandlerContext() {
@@ -135,10 +148,11 @@ export default class ClientConnection {
135
148
  );
136
149
  } else {
137
150
  const timeout = Number(process.env.RUMMY_RPC_TIMEOUT) || 10_000;
151
+ let timer;
138
152
  result = await Promise.race([
139
153
  registration.handler(params || {}, this.#buildHandlerContext()),
140
- new Promise((_, reject) =>
141
- setTimeout(
154
+ new Promise((_, reject) => {
155
+ timer = setTimeout(
142
156
  () =>
143
157
  reject(
144
158
  new Error(
@@ -149,9 +163,9 @@ export default class ClientConnection {
149
163
  ),
150
164
  ),
151
165
  timeout,
152
- ),
153
- ),
154
- ]);
166
+ );
167
+ }),
168
+ ]).finally(() => clearTimeout(timer));
155
169
  }
156
170
 
157
171
  const finalResult = await this.#hooks.rpc.response.result.filter(result, {
@@ -12,8 +12,6 @@ export default class RpcRegistry {
12
12
  longRunning = false,
13
13
  },
14
14
  ) {
15
- if (this.#methods.has(name))
16
- throw new Error(`RPC method '${name}' already registered.`);
17
15
  this.#methods.set(
18
16
  name,
19
17
  Object.freeze({
@@ -26,16 +24,57 @@ export default class RpcRegistry {
26
24
  );
27
25
  }
28
26
 
27
+ #toolFallback = null;
28
+
29
+ /**
30
+ * Set a fallback that auto-dispatches any registered tool via RPC.
31
+ * Checked at request time — tools registered after this call still work.
32
+ */
33
+ setToolFallback(hooks, buildRunContext, dispatchTool) {
34
+ this.#toolFallback = { hooks, buildRunContext, dispatchTool };
35
+ }
36
+
29
37
  registerNotification(name, description = "") {
30
38
  this.#notifications.set(name, Object.freeze({ description }));
31
39
  }
32
40
 
33
41
  get(name) {
34
- return this.#methods.get(name);
42
+ const method = this.#methods.get(name);
43
+ if (method) return method;
44
+ return this.#resolveToolFallback(name);
35
45
  }
36
46
 
37
47
  has(name) {
38
- return this.#methods.has(name);
48
+ return this.#methods.has(name) || !!this.#resolveToolFallback(name);
49
+ }
50
+
51
+ #resolveToolFallback(name) {
52
+ if (!this.#toolFallback) return undefined;
53
+ const { hooks, buildRunContext, dispatchTool } = this.#toolFallback;
54
+ if (!hooks.tools.has(name)) return undefined;
55
+ return Object.freeze({
56
+ handler: async (params, ctx) => {
57
+ if (!params.path) throw new Error("path is required");
58
+ if (!params.run) throw new Error("run is required");
59
+ const { rummy } = await buildRunContext(hooks, ctx, params.run);
60
+ await dispatchTool(hooks, rummy, name, params.path, params.body || "", {
61
+ path: params.path,
62
+ to: params.to,
63
+ ...params.attributes,
64
+ });
65
+ return { status: "ok" };
66
+ },
67
+ description: `Dispatch ${name} tool.`,
68
+ params: {
69
+ run: "string — run alias",
70
+ path: "string — entry path",
71
+ body: "string? — entry content",
72
+ to: "string? — destination path",
73
+ attributes: "object? — JSON attributes",
74
+ },
75
+ requiresInit: true,
76
+ longRunning: false,
77
+ });
39
78
  }
40
79
 
41
80
  discover() {
@@ -43,6 +82,15 @@ export default class RpcRegistry {
43
82
  for (const [name, def] of this.#methods) {
44
83
  methods[name] = { description: def.description, params: def.params };
45
84
  }
85
+ // Include auto-dispatched tools not explicitly registered
86
+ if (this.#toolFallback) {
87
+ for (const name of this.#toolFallback.hooks.tools.names) {
88
+ if (methods[name]) continue;
89
+ const def = this.#resolveToolFallback(name);
90
+ if (def)
91
+ methods[name] = { description: def.description, params: def.params };
92
+ }
93
+ }
46
94
  const notifications = {};
47
95
  for (const [name, def] of this.#notifications) {
48
96
  notifications[name] = { description: def.description };
@@ -14,9 +14,10 @@ visible AS (
14
14
  , ke.updated_at
15
15
  , ke.attributes
16
16
  , ke.tokens AS tokens_full
17
+ , COALESCE(s.category, 'logging') AS category
17
18
  , CASE
18
- -- Stored entries not in context
19
- WHEN ke.fidelity = 'stored' THEN NULL
19
+ -- Archived entries not in context
20
+ WHEN ke.fidelity = 'archive' THEN NULL
20
21
  -- 202 Accepted (proposed) hidden until resolved
21
22
  WHEN ke.status = 202 THEN NULL
22
23
  -- Audit schemes (model_visible = 0) hidden
@@ -38,24 +39,12 @@ projected AS (
38
39
  , turn
39
40
  , updated_at
40
41
  , attributes
42
+ -- Category comes from schemes table — plugins declare it via registerScheme().
43
+ , category
41
44
  , CASE
42
45
  WHEN visible_fidelity IN ('full', 'summary') THEN body
43
46
  ELSE ''
44
47
  END AS body
45
- , CASE
46
- WHEN scheme IS NULL AND visible_fidelity IN ('full', 'summary') THEN 'file'
47
- WHEN scheme IS NULL THEN 'file_index'
48
- WHEN scheme IN ('http', 'https') AND visible_fidelity IN ('full', 'summary') THEN 'file'
49
- WHEN scheme IN ('http', 'https') THEN 'file_index'
50
- WHEN scheme IN ('known', 'skill') AND visible_fidelity = 'full' THEN 'known'
51
- WHEN scheme IN ('known', 'skill') THEN 'known_index'
52
- WHEN scheme = 'unknown' THEN 'unknown'
53
- WHEN scheme IN ('ask', 'act', 'progress') THEN 'prompt'
54
- WHEN scheme = 'summarize' THEN 'structural'
55
- WHEN scheme = 'update' THEN 'structural'
56
- WHEN scheme = 'tool' THEN 'tool'
57
- ELSE 'result'
58
- END AS category
59
48
  FROM visible
60
49
  WHERE visible_fidelity IS NOT NULL
61
50
  )
@@ -74,15 +63,11 @@ SELECT
74
63
  ORDER BY
75
64
  CASE category
76
65
  WHEN 'tool' THEN 1
77
- WHEN 'known' THEN 2
78
- WHEN 'known_index' THEN 2
79
- WHEN 'file_index' THEN 2
80
- WHEN 'file' THEN 2
81
- WHEN 'result' THEN 3
82
- WHEN 'structural' THEN 4
83
- WHEN 'unknown' THEN 5
84
- WHEN 'prompt' THEN 6
85
- ELSE 6
66
+ WHEN 'data' THEN 2
67
+ WHEN 'logging' THEN 3
68
+ WHEN 'unknown' THEN 4
69
+ WHEN 'prompt' THEN 5
70
+ ELSE 5
86
71
  END
87
72
  , CASE scheme WHEN 'skill' THEN 0 ELSE 1 END
88
73
  , CASE fidelity
@@ -1,2 +0,0 @@
1
- ## <ask_user question="[Question?]">[option1; option2; ...]</ask_user>
2
- Example: <ask_user question="Which test framework?">Mocha; Jest; Node Native</ask_user>
@@ -1,2 +0,0 @@
1
- ## <cp path="[path/to/origin"]>[path/to/destination]</cp> - Copy a file or entry
2
- Example: <cp path="docs/example.txt">docs/example_copy.txt</cp>
@@ -1,14 +0,0 @@
1
- # current
2
-
3
- Renders the `<current>` section of the user message — the active loop's
4
- model responses, tool results, and agent warnings.
5
-
6
- ## Registration
7
-
8
- - **Filter**: `assembly.user` at priority 100
9
-
10
- ## Behavior
11
-
12
- Filters turn_context rows where `category` is `result` or `structural`
13
- and `source_turn >= loopStartTurn`. Renders each entry chronologically
14
- with status symbols. Empty on the first turn of a loop.
@@ -1,47 +0,0 @@
1
- export default class Current {
2
- #core;
3
-
4
- constructor(core) {
5
- this.#core = core;
6
- core.filter("assembly.user", this.assembleCurrent.bind(this), 100);
7
- }
8
-
9
- async assembleCurrent(content, ctx) {
10
- const entries = ctx.rows.filter(
11
- (r) =>
12
- (r.category === "result" || r.category === "structural") &&
13
- r.source_turn >= ctx.loopStartTurn,
14
- );
15
- if (entries.length === 0) return content;
16
-
17
- const lines = await Promise.all(
18
- entries.map((e) => renderToolTag(e, this.#core)),
19
- );
20
- return `${content}<current>\n${lines.join("\n")}\n</current>\n`;
21
- }
22
- }
23
-
24
- async function renderToolTag(entry, core) {
25
- const attrs =
26
- typeof entry.attributes === "string"
27
- ? JSON.parse(entry.attributes)
28
- : entry.attributes;
29
-
30
- const path = `${entry.scheme}://${attrs?.path || attrs?.file || attrs?.command || ""}`;
31
- const status = entry.status ? ` status="${entry.status}"` : "";
32
-
33
- let body;
34
- try {
35
- body = await core.hooks.tools.view(entry.scheme, {
36
- ...entry,
37
- attributes: attrs,
38
- });
39
- } catch {
40
- body = entry.body;
41
- }
42
-
43
- if (body) {
44
- return `<tool path="${path}"${status}>${body}</tool>`;
45
- }
46
- return `<tool path="${path}"${status}/>`;
47
- }
@@ -1,4 +0,0 @@
1
- ## <env>[command]</env> - Run an exploratory shell command
2
- Example: <env>npm --version</env>
3
- * Do not use <env/> to read or list files — use <get path="*" preview/> instead
4
- * For commands with side effects, use <sh/> instead
@@ -1,10 +0,0 @@
1
- ## <get>[path/to/file]</get> - Load a file or entry into context
2
- Example: <get>docs/example.txt</get>
3
- Example: <get>known://auth_flow</get>
4
- Example: <get path="src/**/*.js" preview/> (list matching files without loading)
5
- Example: <get path="src/*.js" body="TODO" preview/> (find files containing TODO)
6
- * Paths accept globs: `src/**/*.js`, `known://api_*`
7
- * Adding `preview` shows matches without loading into context
8
- * Use `body` attribute to filter by content
9
- * Use "known://" paths to recall stored information
10
- * When irrelevant or resolved, use <store/> to remove from context
@@ -1,3 +0,0 @@
1
- ## <known>[information]</known> - Save knowledge
2
- Example: <known>Donald Rumsfeld was born in 1932</known>
3
- Example: <known path="known://auth">OAuth2 PKCE</known>
@@ -1,2 +0,0 @@
1
- ## <mv path="[path/to/origin"]>[path/to/destination]</mv> - Move a file or entry
2
- Example: <mv path="known://active_user">known://inactive_user</mv>
@@ -1,6 +0,0 @@
1
- ## <rm path="[path/to/file]"/> - Remove a file or entry
2
- Example: <rm path="src/config.js"/>
3
- Example: <rm path="known://donald-rumsfeld-was-born-in-1932"/>
4
- Example: <rm path="known://temp_*" preview/> (preview before deleting)
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,6 +0,0 @@
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"}, literal SEARCH/REPLACE blocks
4
- * Chain multiple replacements: `s/old/new/ s/foo/bar/`
5
- * Regex patterns use /slashes/: `s/console\.log.*/\/\/ removed/g`
6
- * Do not use <sh/> or <env/> to read, create, update, or delete files or entries
@@ -1,2 +0,0 @@
1
- ## <sh>[command]</sh> - Run a shell command with side effects
2
- Example: <sh>npm install</sh>
@@ -1,25 +0,0 @@
1
- # skills
2
-
3
- Manages skills and personas via RPC methods. Skills are stackable per-run entries; personas are exclusive per-run configuration.
4
-
5
- ## Registration
6
-
7
- - **No tool handler** — registers RPC methods on `hooks.rpc.registry`.
8
-
9
- ## RPC Methods
10
-
11
- ### Skills
12
- - `skill/add` — Load a skill from `config/skills/{name}.md` into the run as a `skill://` entry at full state.
13
- - `skill/remove` — Remove a skill entry from a run.
14
- - `getSkills` — List active skills on a run.
15
- - `listSkills` — List available skill files from disk.
16
-
17
- ### Personas
18
- - `persona/set` — Set persona on a run. Load from `config/personas/{name}.md` by name, pass raw text, or clear by omitting both.
19
- - `listPersonas` — List available persona files from disk.
20
-
21
- ## Behavior
22
-
23
- - Skills stack: multiple skills can be active on a run simultaneously as separate `skill://` entries.
24
- - Personas are exclusive: setting a persona replaces the previous one (stored as a run column, not an entry).
25
- - File paths resolve from `RUMMY_HOME` environment variable.
@@ -1,20 +0,0 @@
1
- # store
2
-
3
- Demotes entries from active context to stored (background) state.
4
-
5
- ## Registration
6
-
7
- - **Tool**: `store`
8
- - **Modes**: ask, act
9
- - **Category**: ask
10
- - **Handler**: Matches entries by pattern, demotes them via `demoteByPattern`, and records the result.
11
-
12
- ## Projection
13
-
14
- Shows `store {path}`.
15
-
16
- ## Behavior
17
-
18
- - Pattern queries (globs or body filters) produce a summary of matched paths.
19
- - Exact path queries report "{path} stored" or "{path} not found".
20
- - Stored entries remain in the database but are excluded from model context.
@@ -1,6 +0,0 @@
1
- ## <store path="[path/to/file]"/> - Store a file or entry
2
- Example: <store path="src/config.js"/>
3
- Example: <store path="src/**/*.test.js"/> (store all test files at once)
4
- * <store/> removes the file or entry from context, but does not delete it
5
- * A stored file or entry can be restored with <get/>
6
- * Paths accept globs for bulk operations
@@ -1,63 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { storePatternResult } from "../helpers.js";
3
-
4
- export default class Store {
5
- #core;
6
-
7
- constructor(core) {
8
- this.#core = core;
9
- core.registerScheme();
10
- core.on("handler", this.handler.bind(this));
11
- core.on("full", this.full.bind(this));
12
- core.on("summary", this.summary.bind(this));
13
- const docs = readFileSync(new URL("./docs.md", import.meta.url), "utf8");
14
- core.filter("instructions.toolDocs", async (content) =>
15
- content ? `${content}\n\n${docs}` : docs,
16
- );
17
- }
18
-
19
- async handler(entry, rummy) {
20
- const { entries: store, sequence: turn, runId, loopId } = rummy;
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 bodyFilter = entry.attributes.body || null;
30
- const isPattern = bodyFilter || target.includes("*");
31
- const matches = await store.getEntriesByPattern(runId, target, bodyFilter);
32
- await store.demoteByPattern(runId, target, bodyFilter);
33
-
34
- if (isPattern) {
35
- await storePatternResult(
36
- store,
37
- runId,
38
- turn,
39
- "store",
40
- target,
41
- bodyFilter,
42
- matches,
43
- { loopId },
44
- );
45
- } else {
46
- const paths = matches.map((m) => m.path).join(", ");
47
- const body =
48
- matches.length > 0 ? `${paths} stored` : `${target} not found`;
49
- await store.upsert(runId, turn, entry.resultPath, body, 200, {
50
- fidelity: "stored",
51
- loopId,
52
- });
53
- }
54
- }
55
-
56
- full(entry) {
57
- return `# store ${entry.attributes.path || entry.path}`;
58
- }
59
-
60
- summary(entry) {
61
- return this.full(entry);
62
- }
63
- }
@@ -1,4 +0,0 @@
1
- ## <summarize>[Answer or summary]</summarize>
2
- * Describe the final state
3
- * ONLY use if done
4
- * Keep brief (<= 80 characters)
@@ -1,5 +0,0 @@
1
- ## <unknown>[what you need to learn]</unknown> - Track open questions
2
- Example: <unknown>contents of answer.txt</unknown>
3
- Example: <unknown>which database adapter is configured</unknown>
4
- * Use get, env, or ask_user to investigate unknowns
5
- * When irrelevant or resolved, use <rm/> to remove from context.
@@ -1,4 +0,0 @@
1
- ## <update>[Brief update]</update>
2
- * Describe the current state
3
- * DO NOT use if done
4
- * Keep brief (<= 80 characters)