@possumtech/rummy 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +55 -0
- package/LICENSE +21 -0
- package/PLUGINS.md +302 -0
- package/README.md +41 -0
- package/SPEC.md +524 -0
- package/lang/en.json +34 -0
- package/migrations/001_initial_schema.sql +226 -0
- package/package.json +54 -0
- package/service.js +143 -0
- package/src/agent/AgentLoop.js +553 -0
- package/src/agent/ContextAssembler.js +29 -0
- package/src/agent/KnownStore.js +254 -0
- package/src/agent/ProjectAgent.js +101 -0
- package/src/agent/ResponseHealer.js +134 -0
- package/src/agent/TurnExecutor.js +457 -0
- package/src/agent/XmlParser.js +247 -0
- package/src/agent/known_checks.sql +42 -0
- package/src/agent/known_queries.sql +80 -0
- package/src/agent/known_store.sql +161 -0
- package/src/agent/messages.js +17 -0
- package/src/agent/prompt_queue.sql +39 -0
- package/src/agent/runs.sql +114 -0
- package/src/agent/schemes.sql +3 -0
- package/src/agent/sessions.sql +51 -0
- package/src/agent/tokens.js +28 -0
- package/src/agent/turns.sql +36 -0
- package/src/hooks/HookRegistry.js +72 -0
- package/src/hooks/Hooks.js +115 -0
- package/src/hooks/PluginContext.js +116 -0
- package/src/hooks/RummyContext.js +181 -0
- package/src/hooks/ToolRegistry.js +83 -0
- package/src/llm/LlmProvider.js +107 -0
- package/src/llm/OllamaClient.js +88 -0
- package/src/llm/OpenAiClient.js +80 -0
- package/src/llm/OpenRouterClient.js +78 -0
- package/src/llm/XaiClient.js +113 -0
- package/src/plugins/ask_user/README.md +18 -0
- package/src/plugins/ask_user/ask_user.js +48 -0
- package/src/plugins/ask_user/docs.md +2 -0
- package/src/plugins/cp/README.md +18 -0
- package/src/plugins/cp/cp.js +55 -0
- package/src/plugins/cp/docs.md +2 -0
- package/src/plugins/current/README.md +14 -0
- package/src/plugins/current/current.js +48 -0
- package/src/plugins/engine/README.md +12 -0
- package/src/plugins/engine/engine.sql +18 -0
- package/src/plugins/engine/turn_context.sql +51 -0
- package/src/plugins/env/README.md +14 -0
- package/src/plugins/env/docs.md +2 -0
- package/src/plugins/env/env.js +32 -0
- package/src/plugins/file/README.md +25 -0
- package/src/plugins/file/file.js +85 -0
- package/src/plugins/get/README.md +19 -0
- package/src/plugins/get/docs.md +6 -0
- package/src/plugins/get/get.js +53 -0
- package/src/plugins/hedberg/README.md +72 -0
- package/src/plugins/hedberg/docs.md +9 -0
- package/src/plugins/hedberg/edits.js +65 -0
- package/src/plugins/hedberg/hedberg.js +89 -0
- package/src/plugins/hedberg/matcher.js +181 -0
- package/src/plugins/hedberg/normalize.js +41 -0
- package/src/plugins/hedberg/patterns.js +452 -0
- package/src/plugins/hedberg/sed.js +48 -0
- package/src/plugins/helpers.js +22 -0
- package/src/plugins/index.js +180 -0
- package/src/plugins/instructions/README.md +11 -0
- package/src/plugins/instructions/instructions.js +37 -0
- package/src/plugins/instructions/preamble.md +12 -0
- package/src/plugins/known/README.md +18 -0
- package/src/plugins/known/docs.md +3 -0
- package/src/plugins/known/known.js +57 -0
- package/src/plugins/mv/README.md +18 -0
- package/src/plugins/mv/docs.md +2 -0
- package/src/plugins/mv/mv.js +56 -0
- package/src/plugins/previous/README.md +15 -0
- package/src/plugins/previous/previous.js +50 -0
- package/src/plugins/progress/README.md +17 -0
- package/src/plugins/progress/progress.js +44 -0
- package/src/plugins/prompt/README.md +16 -0
- package/src/plugins/prompt/prompt.js +45 -0
- package/src/plugins/rm/README.md +18 -0
- package/src/plugins/rm/docs.md +4 -0
- package/src/plugins/rm/rm.js +51 -0
- package/src/plugins/rpc/README.md +45 -0
- package/src/plugins/rpc/rpc.js +587 -0
- package/src/plugins/set/README.md +32 -0
- package/src/plugins/set/docs.md +4 -0
- package/src/plugins/set/set.js +268 -0
- package/src/plugins/sh/README.md +18 -0
- package/src/plugins/sh/docs.md +2 -0
- package/src/plugins/sh/sh.js +32 -0
- package/src/plugins/skills/README.md +25 -0
- package/src/plugins/skills/skills.js +175 -0
- package/src/plugins/store/README.md +20 -0
- package/src/plugins/store/docs.md +5 -0
- package/src/plugins/store/store.js +52 -0
- package/src/plugins/summarize/README.md +18 -0
- package/src/plugins/summarize/docs.md +4 -0
- package/src/plugins/summarize/summarize.js +24 -0
- package/src/plugins/telemetry/README.md +19 -0
- package/src/plugins/telemetry/rpc_log.sql +28 -0
- package/src/plugins/telemetry/telemetry.js +186 -0
- package/src/plugins/unknown/README.md +23 -0
- package/src/plugins/unknown/docs.md +5 -0
- package/src/plugins/unknown/unknown.js +31 -0
- package/src/plugins/update/README.md +18 -0
- package/src/plugins/update/docs.md +4 -0
- package/src/plugins/update/update.js +24 -0
- package/src/server/ClientConnection.js +228 -0
- package/src/server/RpcRegistry.js +52 -0
- package/src/server/SocketServer.js +43 -0
- package/src/sql/file_constraints.sql +15 -0
- package/src/sql/functions/countTokens.js +7 -0
- package/src/sql/functions/hedmatch.js +8 -0
- package/src/sql/functions/hedreplace.js +8 -0
- package/src/sql/functions/hedsearch.js +8 -0
- package/src/sql/functions/schemeOf.js +7 -0
- package/src/sql/functions/slugify.js +6 -0
- package/src/sql/v_model_context.sql +101 -0
- package/src/sql/v_run_log.sql +23 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
-- PREP: count_unknowns
|
|
2
|
+
SELECT COUNT(*) AS count
|
|
3
|
+
FROM known_entries
|
|
4
|
+
WHERE
|
|
5
|
+
run_id = :run_id
|
|
6
|
+
AND scheme = 'unknown';
|
|
7
|
+
|
|
8
|
+
-- PREP: get_unknown_values
|
|
9
|
+
SELECT body
|
|
10
|
+
FROM known_entries
|
|
11
|
+
WHERE
|
|
12
|
+
run_id = :run_id
|
|
13
|
+
AND scheme = 'unknown';
|
|
14
|
+
|
|
15
|
+
-- PREP: get_unresolved
|
|
16
|
+
SELECT path, body, attributes, turn
|
|
17
|
+
FROM known_entries
|
|
18
|
+
WHERE
|
|
19
|
+
run_id = :run_id
|
|
20
|
+
AND state = 'proposed';
|
|
21
|
+
|
|
22
|
+
-- PREP: has_rejections
|
|
23
|
+
SELECT COUNT(*) AS count
|
|
24
|
+
FROM known_entries
|
|
25
|
+
WHERE
|
|
26
|
+
run_id = :run_id
|
|
27
|
+
AND state = 'rejected';
|
|
28
|
+
|
|
29
|
+
-- PREP: has_accepted_actions
|
|
30
|
+
SELECT COUNT(*) AS count
|
|
31
|
+
FROM known_entries
|
|
32
|
+
WHERE
|
|
33
|
+
run_id = :run_id
|
|
34
|
+
AND state = 'pass'
|
|
35
|
+
AND scheme IN ('set', 'sh', 'rm', 'mv', 'cp');
|
|
36
|
+
|
|
37
|
+
-- PREP: get_file_entries
|
|
38
|
+
SELECT path, state, hash, updated_at
|
|
39
|
+
FROM known_entries
|
|
40
|
+
WHERE
|
|
41
|
+
run_id = :run_id
|
|
42
|
+
AND scheme IS NULL;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
-- PREP: get_known_entries
|
|
2
|
+
SELECT path, scheme, state, body, turn, hash, attributes
|
|
3
|
+
FROM known_entries
|
|
4
|
+
WHERE run_id = :run_id
|
|
5
|
+
ORDER BY path;
|
|
6
|
+
|
|
7
|
+
-- PREP: get_results
|
|
8
|
+
SELECT tool, target, status, path, body
|
|
9
|
+
FROM v_run_log
|
|
10
|
+
WHERE run_id = :run_id;
|
|
11
|
+
|
|
12
|
+
-- PREP: get_unknowns
|
|
13
|
+
SELECT path, body
|
|
14
|
+
FROM known_entries
|
|
15
|
+
WHERE
|
|
16
|
+
run_id = :run_id
|
|
17
|
+
AND scheme = 'unknown'
|
|
18
|
+
ORDER BY id;
|
|
19
|
+
|
|
20
|
+
-- PREP: get_turn_audit
|
|
21
|
+
SELECT path, scheme, state, turn, body, attributes
|
|
22
|
+
FROM known_entries
|
|
23
|
+
WHERE
|
|
24
|
+
run_id = :run_id
|
|
25
|
+
AND turn = :turn
|
|
26
|
+
ORDER BY id;
|
|
27
|
+
|
|
28
|
+
-- PREP: get_reasoning
|
|
29
|
+
SELECT path, body, turn
|
|
30
|
+
FROM known_entries
|
|
31
|
+
WHERE
|
|
32
|
+
run_id = :run_id
|
|
33
|
+
AND scheme = 'reasoning'
|
|
34
|
+
ORDER BY id;
|
|
35
|
+
|
|
36
|
+
-- PREP: get_latest_user_prompt
|
|
37
|
+
SELECT body
|
|
38
|
+
FROM known_entries
|
|
39
|
+
WHERE
|
|
40
|
+
run_id = :run_id
|
|
41
|
+
AND scheme IN ('ask', 'act')
|
|
42
|
+
AND body != ''
|
|
43
|
+
ORDER BY id DESC
|
|
44
|
+
LIMIT 1;
|
|
45
|
+
|
|
46
|
+
-- PREP: get_latest_prompt
|
|
47
|
+
SELECT path, scheme, body, attributes
|
|
48
|
+
FROM known_entries
|
|
49
|
+
WHERE
|
|
50
|
+
run_id = :run_id
|
|
51
|
+
AND scheme = 'prompt'
|
|
52
|
+
ORDER BY id DESC
|
|
53
|
+
LIMIT 1;
|
|
54
|
+
|
|
55
|
+
-- PREP: get_latest_summary
|
|
56
|
+
SELECT body
|
|
57
|
+
FROM known_entries
|
|
58
|
+
WHERE
|
|
59
|
+
run_id = :run_id
|
|
60
|
+
AND scheme = 'summarize'
|
|
61
|
+
ORDER BY id DESC
|
|
62
|
+
LIMIT 1;
|
|
63
|
+
|
|
64
|
+
-- PREP: get_history
|
|
65
|
+
SELECT ke.path, ke.state AS status, ke.body, ke.attributes, ke.turn
|
|
66
|
+
FROM known_entries AS ke
|
|
67
|
+
JOIN schemes AS s ON s.name = COALESCE(ke.scheme, 'file')
|
|
68
|
+
WHERE
|
|
69
|
+
ke.run_id = :run_id
|
|
70
|
+
AND ke.scheme IS NOT NULL
|
|
71
|
+
AND s.category NOT IN ('knowledge', 'file', 'audit')
|
|
72
|
+
ORDER BY ke.id;
|
|
73
|
+
|
|
74
|
+
-- PREP: get_content
|
|
75
|
+
SELECT path, body, turn
|
|
76
|
+
FROM known_entries
|
|
77
|
+
WHERE
|
|
78
|
+
run_id = :run_id
|
|
79
|
+
AND scheme = 'content'
|
|
80
|
+
ORDER BY id;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
-- PREP: upsert_known_entry
|
|
2
|
+
INSERT INTO known_entries (
|
|
3
|
+
run_id, turn, path, body, state, hash, attributes
|
|
4
|
+
, tokens, tokens_full, updated_at
|
|
5
|
+
)
|
|
6
|
+
VALUES (
|
|
7
|
+
:run_id, :turn, :path, :body, :state, :hash, COALESCE(:attributes, '{}')
|
|
8
|
+
, countTokens(:body)
|
|
9
|
+
, countTokens(:body)
|
|
10
|
+
, COALESCE(:updated_at, CURRENT_TIMESTAMP)
|
|
11
|
+
)
|
|
12
|
+
ON CONFLICT (run_id, path) DO UPDATE SET
|
|
13
|
+
body = excluded.body
|
|
14
|
+
, state = excluded.state
|
|
15
|
+
, hash = COALESCE(excluded.hash, known_entries.hash)
|
|
16
|
+
, attributes = COALESCE(excluded.attributes, known_entries.attributes)
|
|
17
|
+
, turn = excluded.turn
|
|
18
|
+
, tokens = countTokens(excluded.body)
|
|
19
|
+
, tokens_full = countTokens(excluded.body)
|
|
20
|
+
, write_count = known_entries.write_count + 1
|
|
21
|
+
, updated_at = COALESCE(excluded.updated_at, CURRENT_TIMESTAMP);
|
|
22
|
+
|
|
23
|
+
-- PREP: recount_tokens
|
|
24
|
+
UPDATE known_entries
|
|
25
|
+
SET tokens = :tokens, tokens_full = :tokens
|
|
26
|
+
WHERE run_id = :run_id AND path = :path;
|
|
27
|
+
|
|
28
|
+
-- PREP: get_stale_tokens
|
|
29
|
+
SELECT path, body
|
|
30
|
+
FROM known_entries
|
|
31
|
+
WHERE
|
|
32
|
+
run_id = :run_id
|
|
33
|
+
AND turn = :turn;
|
|
34
|
+
|
|
35
|
+
-- PREP: delete_known_entry
|
|
36
|
+
DELETE FROM known_entries
|
|
37
|
+
WHERE run_id = :run_id AND path = :path;
|
|
38
|
+
|
|
39
|
+
-- PREP: delete_file_entries_by_pattern
|
|
40
|
+
DELETE FROM known_entries
|
|
41
|
+
WHERE run_id = :run_id AND hedmatch(:pattern, path) AND scheme IS NULL;
|
|
42
|
+
|
|
43
|
+
-- PREP: resolve_known_entry
|
|
44
|
+
UPDATE known_entries
|
|
45
|
+
SET
|
|
46
|
+
state = :state
|
|
47
|
+
, body = :body
|
|
48
|
+
, updated_at = CURRENT_TIMESTAMP
|
|
49
|
+
WHERE run_id = :run_id AND path = :path;
|
|
50
|
+
|
|
51
|
+
-- PREP: set_file_state
|
|
52
|
+
UPDATE known_entries
|
|
53
|
+
SET
|
|
54
|
+
state = :state
|
|
55
|
+
, tokens = CASE
|
|
56
|
+
WHEN :state = 'summary' THEN countTokens(body)
|
|
57
|
+
ELSE tokens_full
|
|
58
|
+
END
|
|
59
|
+
, updated_at = CURRENT_TIMESTAMP
|
|
60
|
+
WHERE run_id = :run_id AND hedmatch(:pattern, path) AND scheme IS NULL;
|
|
61
|
+
|
|
62
|
+
-- PREP: promote_path
|
|
63
|
+
UPDATE known_entries
|
|
64
|
+
SET
|
|
65
|
+
state = 'full'
|
|
66
|
+
, turn = :turn
|
|
67
|
+
, tokens = tokens_full
|
|
68
|
+
, updated_at = CURRENT_TIMESTAMP
|
|
69
|
+
WHERE run_id = :run_id AND path = :path;
|
|
70
|
+
|
|
71
|
+
-- PREP: demote_path
|
|
72
|
+
UPDATE known_entries
|
|
73
|
+
SET
|
|
74
|
+
state = 'stored'
|
|
75
|
+
, tokens = 0
|
|
76
|
+
, updated_at = CURRENT_TIMESTAMP
|
|
77
|
+
WHERE run_id = :run_id AND path = :path;
|
|
78
|
+
|
|
79
|
+
-- PREP: get_entry_body
|
|
80
|
+
SELECT body
|
|
81
|
+
FROM known_entries
|
|
82
|
+
WHERE run_id = :run_id AND path = :path;
|
|
83
|
+
|
|
84
|
+
-- PREP: get_entry_state
|
|
85
|
+
SELECT state, scheme, turn
|
|
86
|
+
FROM known_entries
|
|
87
|
+
WHERE run_id = :run_id AND path = :path;
|
|
88
|
+
|
|
89
|
+
-- PREP: get_file_states_by_pattern
|
|
90
|
+
SELECT path, state, turn
|
|
91
|
+
FROM known_entries
|
|
92
|
+
WHERE run_id = :run_id AND hedmatch(:pattern, path) AND scheme IS NULL
|
|
93
|
+
ORDER BY path;
|
|
94
|
+
|
|
95
|
+
-- PREP: update_entry_attributes
|
|
96
|
+
UPDATE known_entries
|
|
97
|
+
SET
|
|
98
|
+
attributes = json_patch(attributes, :attributes)
|
|
99
|
+
, updated_at = CURRENT_TIMESTAMP
|
|
100
|
+
WHERE run_id = :run_id AND path = :path;
|
|
101
|
+
|
|
102
|
+
-- PREP: get_entry_attributes
|
|
103
|
+
SELECT attributes
|
|
104
|
+
FROM known_entries
|
|
105
|
+
WHERE run_id = :run_id AND path = :path;
|
|
106
|
+
|
|
107
|
+
-- PREP: promote_by_pattern
|
|
108
|
+
UPDATE known_entries
|
|
109
|
+
SET
|
|
110
|
+
state = 'full'
|
|
111
|
+
, turn = :turn
|
|
112
|
+
, tokens = tokens_full
|
|
113
|
+
, updated_at = CURRENT_TIMESTAMP
|
|
114
|
+
WHERE
|
|
115
|
+
run_id = :run_id
|
|
116
|
+
AND hedmatch(:path, path)
|
|
117
|
+
AND (:body IS NULL OR hedsearch(:body, body));
|
|
118
|
+
|
|
119
|
+
-- PREP: demote_by_pattern
|
|
120
|
+
UPDATE known_entries
|
|
121
|
+
SET
|
|
122
|
+
state = 'stored'
|
|
123
|
+
, tokens = 0
|
|
124
|
+
, updated_at = CURRENT_TIMESTAMP
|
|
125
|
+
WHERE
|
|
126
|
+
run_id = :run_id
|
|
127
|
+
AND hedmatch(:path, path)
|
|
128
|
+
AND (:body IS NULL OR hedsearch(:body, body));
|
|
129
|
+
|
|
130
|
+
-- PREP: get_entries_by_pattern
|
|
131
|
+
SELECT path, body, scheme, state, tokens_full, attributes
|
|
132
|
+
FROM known_entries
|
|
133
|
+
WHERE
|
|
134
|
+
run_id = :run_id
|
|
135
|
+
AND hedmatch(:path, path)
|
|
136
|
+
AND (:body IS NULL OR hedsearch(:body, body))
|
|
137
|
+
ORDER BY path
|
|
138
|
+
LIMIT
|
|
139
|
+
COALESCE(:limit, -1)
|
|
140
|
+
OFFSET
|
|
141
|
+
COALESCE(:offset, 0);
|
|
142
|
+
|
|
143
|
+
-- PREP: delete_entries_by_pattern
|
|
144
|
+
DELETE FROM known_entries
|
|
145
|
+
WHERE
|
|
146
|
+
run_id = :run_id
|
|
147
|
+
AND hedmatch(:path, path)
|
|
148
|
+
AND (:body IS NULL OR hedsearch(:body, body));
|
|
149
|
+
|
|
150
|
+
-- PREP: update_body_by_pattern
|
|
151
|
+
UPDATE known_entries
|
|
152
|
+
SET
|
|
153
|
+
body = :new_body
|
|
154
|
+
, tokens = countTokens(:new_body)
|
|
155
|
+
, tokens_full = countTokens(:new_body)
|
|
156
|
+
, write_count = write_count + 1
|
|
157
|
+
, updated_at = CURRENT_TIMESTAMP
|
|
158
|
+
WHERE
|
|
159
|
+
run_id = :run_id
|
|
160
|
+
AND hedmatch(:path, path)
|
|
161
|
+
AND (:body IS NULL OR hedsearch(:body, body));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const catalog = JSON.parse(
|
|
7
|
+
readFileSync(join(__dirname, "../../lang/en.json"), "utf8"),
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
export default function msg(key, params = {}) {
|
|
11
|
+
const template = catalog[key];
|
|
12
|
+
if (!template) throw new Error(`Missing message key: ${key}`);
|
|
13
|
+
return template.replace(/\{(\w+)\}/g, (_, name) => {
|
|
14
|
+
if (name in params) return String(params[name]);
|
|
15
|
+
return `{${name}}`;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
-- PREP: enqueue_prompt
|
|
2
|
+
INSERT INTO prompt_queue (run_id, mode, model, prompt, config)
|
|
3
|
+
VALUES (:run_id, :mode, :model, :prompt, :config)
|
|
4
|
+
RETURNING id;
|
|
5
|
+
|
|
6
|
+
-- PREP: claim_next_prompt
|
|
7
|
+
UPDATE prompt_queue
|
|
8
|
+
SET status = 'active'
|
|
9
|
+
WHERE
|
|
10
|
+
id = (
|
|
11
|
+
SELECT
|
|
12
|
+
id
|
|
13
|
+
FROM prompt_queue
|
|
14
|
+
WHERE run_id = :run_id AND status = 'pending'
|
|
15
|
+
ORDER BY id
|
|
16
|
+
LIMIT 1
|
|
17
|
+
)
|
|
18
|
+
RETURNING id, run_id, mode, model, prompt, config;
|
|
19
|
+
|
|
20
|
+
-- PREP: complete_prompt
|
|
21
|
+
UPDATE prompt_queue
|
|
22
|
+
SET status = 'completed', result = :result
|
|
23
|
+
WHERE id = :id;
|
|
24
|
+
|
|
25
|
+
-- PREP: abort_active_prompt
|
|
26
|
+
UPDATE prompt_queue
|
|
27
|
+
SET status = 'aborted'
|
|
28
|
+
WHERE run_id = :run_id AND status = 'active';
|
|
29
|
+
|
|
30
|
+
-- PREP: get_pending_prompts
|
|
31
|
+
SELECT id, mode, model, prompt, status, created_at
|
|
32
|
+
FROM prompt_queue
|
|
33
|
+
WHERE run_id = :run_id AND status IN ('pending', 'active')
|
|
34
|
+
ORDER BY id;
|
|
35
|
+
|
|
36
|
+
-- PREP: reset_active_prompts
|
|
37
|
+
UPDATE prompt_queue
|
|
38
|
+
SET status = 'pending'
|
|
39
|
+
WHERE status = 'active';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
-- PREP: create_run
|
|
2
|
+
INSERT INTO runs (
|
|
3
|
+
project_id
|
|
4
|
+
, parent_run_id
|
|
5
|
+
, model
|
|
6
|
+
, alias
|
|
7
|
+
, temperature
|
|
8
|
+
, persona
|
|
9
|
+
, context_limit
|
|
10
|
+
)
|
|
11
|
+
VALUES (
|
|
12
|
+
:project_id
|
|
13
|
+
, :parent_run_id
|
|
14
|
+
, :model
|
|
15
|
+
, :alias
|
|
16
|
+
, :temperature
|
|
17
|
+
, :persona
|
|
18
|
+
, :context_limit
|
|
19
|
+
)
|
|
20
|
+
RETURNING id;
|
|
21
|
+
|
|
22
|
+
-- PREP: get_run_by_alias
|
|
23
|
+
SELECT
|
|
24
|
+
id, project_id, parent_run_id, model, status, alias
|
|
25
|
+
, temperature, persona, context_limit, next_turn, created_at
|
|
26
|
+
FROM runs
|
|
27
|
+
WHERE alias = :alias;
|
|
28
|
+
|
|
29
|
+
-- PREP: get_run_by_id
|
|
30
|
+
SELECT
|
|
31
|
+
id, project_id, parent_run_id, model, status, alias
|
|
32
|
+
, temperature, persona, context_limit, next_turn, created_at
|
|
33
|
+
FROM runs
|
|
34
|
+
WHERE id = :id;
|
|
35
|
+
|
|
36
|
+
-- PREP: get_runs_by_project
|
|
37
|
+
SELECT
|
|
38
|
+
r.alias
|
|
39
|
+
, r.status
|
|
40
|
+
, r.created_at
|
|
41
|
+
, r.next_turn - 1 AS turn
|
|
42
|
+
, (
|
|
43
|
+
SELECT ke.body
|
|
44
|
+
FROM known_entries AS ke
|
|
45
|
+
WHERE
|
|
46
|
+
ke.run_id = r.id
|
|
47
|
+
AND ke.scheme = 'summarize'
|
|
48
|
+
ORDER BY ke.id DESC
|
|
49
|
+
LIMIT 1
|
|
50
|
+
) AS summary
|
|
51
|
+
FROM runs AS r
|
|
52
|
+
WHERE r.project_id = :project_id
|
|
53
|
+
ORDER BY r.created_at DESC
|
|
54
|
+
LIMIT
|
|
55
|
+
COALESCE(:limit, -1)
|
|
56
|
+
OFFSET
|
|
57
|
+
COALESCE(:offset, 0);
|
|
58
|
+
|
|
59
|
+
-- PREP: rename_run
|
|
60
|
+
UPDATE runs
|
|
61
|
+
SET alias = :new_alias
|
|
62
|
+
WHERE id = :id AND alias = :old_alias;
|
|
63
|
+
|
|
64
|
+
-- PREP: update_run_status
|
|
65
|
+
UPDATE runs SET status = :status WHERE id = :id;
|
|
66
|
+
|
|
67
|
+
-- PREP: update_run_config
|
|
68
|
+
UPDATE runs SET
|
|
69
|
+
temperature = COALESCE(:temperature, temperature)
|
|
70
|
+
, persona = COALESCE(:persona, persona)
|
|
71
|
+
, context_limit = COALESCE(:context_limit, context_limit)
|
|
72
|
+
, model = COALESCE(:model, model)
|
|
73
|
+
WHERE id = :id;
|
|
74
|
+
|
|
75
|
+
-- PREP: next_turn
|
|
76
|
+
UPDATE runs
|
|
77
|
+
SET next_turn = next_turn + 1
|
|
78
|
+
WHERE id = :run_id
|
|
79
|
+
RETURNING next_turn - 1 AS turn;
|
|
80
|
+
|
|
81
|
+
-- PREP: fork_known_entries
|
|
82
|
+
INSERT INTO known_entries (
|
|
83
|
+
run_id, turn, path, body, state
|
|
84
|
+
, hash, attributes, tokens, tokens_full, refs, write_count
|
|
85
|
+
)
|
|
86
|
+
SELECT
|
|
87
|
+
:new_run_id, turn, path, body, state
|
|
88
|
+
, hash, attributes, tokens, tokens_full, refs, write_count
|
|
89
|
+
FROM known_entries
|
|
90
|
+
WHERE run_id = :parent_run_id;
|
|
91
|
+
|
|
92
|
+
-- PREP: get_active_runs
|
|
93
|
+
SELECT r.id
|
|
94
|
+
FROM runs AS r
|
|
95
|
+
WHERE
|
|
96
|
+
r.project_id = :project_id
|
|
97
|
+
AND r.status IN ('queued', 'running', 'proposed');
|
|
98
|
+
|
|
99
|
+
-- PREP: get_latest_run
|
|
100
|
+
SELECT r.id
|
|
101
|
+
FROM runs AS r
|
|
102
|
+
WHERE r.project_id = :project_id
|
|
103
|
+
ORDER BY r.created_at DESC
|
|
104
|
+
LIMIT 1;
|
|
105
|
+
|
|
106
|
+
-- PREP: get_all_runs
|
|
107
|
+
SELECT r.id
|
|
108
|
+
FROM runs AS r
|
|
109
|
+
WHERE r.project_id = :project_id;
|
|
110
|
+
|
|
111
|
+
-- PREP: abort_stuck_runs
|
|
112
|
+
UPDATE runs
|
|
113
|
+
SET status = 'aborted'
|
|
114
|
+
WHERE status IN ('running', 'queued');
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
-- PREP: upsert_project
|
|
2
|
+
INSERT INTO projects (name, project_root, config_path)
|
|
3
|
+
VALUES (:name, :project_root, :config_path)
|
|
4
|
+
ON CONFLICT (name) DO UPDATE SET
|
|
5
|
+
project_root = COALESCE(excluded.project_root, projects.project_root)
|
|
6
|
+
, config_path = COALESCE(excluded.config_path, projects.config_path)
|
|
7
|
+
RETURNING id;
|
|
8
|
+
|
|
9
|
+
-- PREP: get_project_by_id
|
|
10
|
+
SELECT id, name, project_root, config_path, created_at
|
|
11
|
+
FROM projects
|
|
12
|
+
WHERE id = :id;
|
|
13
|
+
|
|
14
|
+
-- PREP: get_project_by_name
|
|
15
|
+
SELECT id, name, project_root, config_path, created_at
|
|
16
|
+
FROM projects
|
|
17
|
+
WHERE name = :name;
|
|
18
|
+
|
|
19
|
+
-- PREP: upsert_model
|
|
20
|
+
INSERT INTO models (alias, actual, context_length)
|
|
21
|
+
VALUES (:alias, :actual, :context_length)
|
|
22
|
+
ON CONFLICT (alias) DO UPDATE SET
|
|
23
|
+
actual = excluded.actual
|
|
24
|
+
, context_length = COALESCE(excluded.context_length, models.context_length)
|
|
25
|
+
RETURNING id;
|
|
26
|
+
|
|
27
|
+
-- PREP: get_model_by_alias
|
|
28
|
+
SELECT id, alias, actual, context_length
|
|
29
|
+
FROM models
|
|
30
|
+
WHERE alias = :alias;
|
|
31
|
+
|
|
32
|
+
-- PREP: get_models
|
|
33
|
+
SELECT id, alias, actual, context_length
|
|
34
|
+
FROM models
|
|
35
|
+
ORDER BY alias
|
|
36
|
+
LIMIT
|
|
37
|
+
COALESCE(:limit, -1)
|
|
38
|
+
OFFSET
|
|
39
|
+
COALESCE(:offset, 0);
|
|
40
|
+
|
|
41
|
+
-- PREP: update_model_context_length
|
|
42
|
+
UPDATE models SET context_length = :context_length WHERE alias = :alias;
|
|
43
|
+
|
|
44
|
+
-- PREP: delete_model
|
|
45
|
+
DELETE FROM models WHERE alias = :alias;
|
|
46
|
+
|
|
47
|
+
-- PREP: purge_old_runs
|
|
48
|
+
DELETE FROM runs
|
|
49
|
+
WHERE
|
|
50
|
+
status IN ('completed', 'aborted')
|
|
51
|
+
AND created_at < datetime('now', '-' || :retention_days || ' days');
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token counting with tiktoken (o200k_base) and simple fallback.
|
|
3
|
+
* o200k_base is the tokenizer for GPT-4o and newer OpenAI models.
|
|
4
|
+
* Better multilingual and code handling than cl100k_base.
|
|
5
|
+
* Exact counts vary by model tokenizer — these are for budgeting, not billing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
let encoder = null;
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const tiktoken = await import("tiktoken");
|
|
12
|
+
encoder = tiktoken.get_encoding("o200k_base");
|
|
13
|
+
} catch {
|
|
14
|
+
// tiktoken unavailable — use character-based estimate
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function countTokens(text) {
|
|
18
|
+
if (!text) return 0;
|
|
19
|
+
if (encoder) {
|
|
20
|
+
try {
|
|
21
|
+
const tokens = encoder.encode(text);
|
|
22
|
+
return tokens.length;
|
|
23
|
+
} catch {
|
|
24
|
+
// Fallback on encoding error
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return Math.ceil(text.length / 4);
|
|
28
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
-- PREP: create_turn
|
|
2
|
+
INSERT INTO turns (run_id, sequence)
|
|
3
|
+
VALUES (:run_id, :sequence)
|
|
4
|
+
RETURNING id, sequence;
|
|
5
|
+
|
|
6
|
+
-- PREP: update_turn_stats
|
|
7
|
+
UPDATE turns
|
|
8
|
+
SET
|
|
9
|
+
prompt_tokens = :prompt_tokens
|
|
10
|
+
, cached_tokens = :cached_tokens
|
|
11
|
+
, completion_tokens = :completion_tokens
|
|
12
|
+
, reasoning_tokens = :reasoning_tokens
|
|
13
|
+
, total_tokens = :total_tokens
|
|
14
|
+
, cost = :cost
|
|
15
|
+
WHERE id = :id;
|
|
16
|
+
|
|
17
|
+
-- PREP: get_run_usage
|
|
18
|
+
SELECT
|
|
19
|
+
COALESCE(SUM(prompt_tokens), 0) AS prompt_tokens
|
|
20
|
+
, COALESCE(SUM(cached_tokens), 0) AS cached_tokens
|
|
21
|
+
, COALESCE(SUM(completion_tokens), 0) AS completion_tokens
|
|
22
|
+
, COALESCE(SUM(reasoning_tokens), 0) AS reasoning_tokens
|
|
23
|
+
, COALESCE(SUM(total_tokens), 0) AS total_tokens
|
|
24
|
+
, COALESCE(SUM(cost), 0) AS cost
|
|
25
|
+
FROM turns
|
|
26
|
+
WHERE run_id = :run_id;
|
|
27
|
+
|
|
28
|
+
-- PREP: get_run_log
|
|
29
|
+
SELECT ke.path, ke.state AS status, ke.body, ke.attributes
|
|
30
|
+
FROM known_entries AS ke
|
|
31
|
+
JOIN schemes AS s ON s.name = COALESCE(ke.scheme, 'file')
|
|
32
|
+
WHERE
|
|
33
|
+
ke.run_id = :run_id
|
|
34
|
+
AND ke.scheme IS NOT NULL
|
|
35
|
+
AND s.category NOT IN ('knowledge')
|
|
36
|
+
ORDER BY ke.id;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HookRegistry manages a simple, priority-ordered pipeline of processors.
|
|
3
|
+
* It also supports basic event emitters for side-effects.
|
|
4
|
+
*/
|
|
5
|
+
export default class HookRegistry {
|
|
6
|
+
#processors = [];
|
|
7
|
+
#events = new Map();
|
|
8
|
+
#filters = new Map();
|
|
9
|
+
#debug;
|
|
10
|
+
|
|
11
|
+
constructor(debug = false) {
|
|
12
|
+
this.#debug = debug;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Register a processor for the Turn XML Document.
|
|
17
|
+
*/
|
|
18
|
+
onTurn(callback, priority = 10) {
|
|
19
|
+
this.#processors.push({ callback, priority });
|
|
20
|
+
this.#processors.sort((a, b) => a.priority - b.priority);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Run all registered Turn processors.
|
|
25
|
+
*/
|
|
26
|
+
async processTurn(rummy) {
|
|
27
|
+
for (const p of this.#processors) {
|
|
28
|
+
const start = performance.now();
|
|
29
|
+
await p.callback(rummy);
|
|
30
|
+
if (this.#debug) {
|
|
31
|
+
const duration = (performance.now() - start).toFixed(2);
|
|
32
|
+
console.log(
|
|
33
|
+
`[PIPELINE] Processor ${p.callback.name || "anonymous"} took ${duration}ms`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Standard WordPress-style Filters for non-DOM data.
|
|
41
|
+
*/
|
|
42
|
+
addFilter(tag, callback, priority = 10) {
|
|
43
|
+
if (!this.#filters.has(tag)) this.#filters.set(tag, []);
|
|
44
|
+
this.#filters.get(tag).push({ callback, priority });
|
|
45
|
+
this.#filters.get(tag).sort((a, b) => a.priority - b.priority);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async applyFilters(tag, value, ...args) {
|
|
49
|
+
const hooks = this.#filters.get(tag) || [];
|
|
50
|
+
let result = value;
|
|
51
|
+
for (const h of hooks) {
|
|
52
|
+
result = await h.callback(result, ...args);
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Standard WordPress-style Events for side-effects.
|
|
59
|
+
*/
|
|
60
|
+
addEvent(tag, callback, priority = 10) {
|
|
61
|
+
if (!this.#events.has(tag)) this.#events.set(tag, []);
|
|
62
|
+
this.#events.get(tag).push({ callback, priority });
|
|
63
|
+
this.#events.get(tag).sort((a, b) => a.priority - b.priority);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async emitEvent(tag, ...args) {
|
|
67
|
+
const hooks = this.#events.get(tag) || [];
|
|
68
|
+
for (const h of hooks) {
|
|
69
|
+
await h.callback(...args);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|