@possumtech/rummy 0.3.0 → 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.
- package/.env.example +2 -1
- package/PLUGINS.md +1 -1
- package/SPEC.md +181 -38
- package/migrations/001_initial_schema.sql +1 -1
- package/package.json +7 -3
- package/service.js +5 -3
- package/src/agent/AgentLoop.js +182 -136
- package/src/agent/ContextAssembler.js +2 -0
- package/src/agent/KnownStore.js +28 -85
- package/src/agent/ResponseHealer.js +65 -31
- package/src/agent/TurnExecutor.js +326 -181
- package/src/agent/XmlParser.js +5 -2
- package/src/agent/known_store.sql +48 -0
- package/src/agent/tokens.js +1 -0
- package/src/agent/turns.sql +5 -0
- package/src/hooks/HookRegistry.js +7 -0
- package/src/hooks/Hooks.js +1 -4
- package/src/hooks/ToolRegistry.js +2 -8
- package/src/plugins/budget/README.md +2 -14
- package/src/plugins/budget/budget.js +15 -39
- package/src/plugins/cp/cp.js +1 -1
- package/src/plugins/cp/cpDoc.js +1 -1
- package/src/plugins/get/get.js +71 -1
- package/src/plugins/get/getDoc.js +14 -4
- package/src/plugins/hedberg/matcher.js +10 -29
- package/src/plugins/instructions/preamble.md +16 -6
- package/src/plugins/known/known.js +4 -10
- package/src/plugins/known/knownDoc.js +15 -14
- package/src/plugins/mv/mv.js +18 -1
- package/src/plugins/mv/mvDoc.js +15 -1
- package/src/plugins/{current → performed}/README.md +4 -3
- package/src/plugins/{current/current.js → performed/performed.js} +15 -20
- package/src/plugins/previous/README.md +2 -1
- package/src/plugins/previous/previous.js +31 -25
- package/src/plugins/progress/README.md +1 -2
- package/src/plugins/progress/progress.js +15 -29
- package/src/plugins/prompt/prompt.js +0 -7
- package/src/plugins/rm/rm.js +27 -15
- package/src/plugins/rm/rmDoc.js +3 -3
- package/src/plugins/set/set.js +55 -19
- package/src/plugins/set/setDoc.js +6 -2
- package/src/plugins/telemetry/telemetry.js +14 -9
- package/src/plugins/unknown/README.md +2 -1
- package/src/plugins/unknown/unknown.js +5 -4
- package/src/server/ClientConnection.js +59 -45
- package/src/sql/v_model_context.sql +3 -13
- package/src/plugins/budget/BudgetGuard.js +0 -74
|
@@ -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
|
-
#
|
|
29
|
-
this.#
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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, {
|
|
@@ -14,6 +14,7 @@ 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
19
|
-- Archived entries not in context
|
|
19
20
|
WHEN ke.fidelity = 'archive' THEN NULL
|
|
@@ -38,23 +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
|
-
-- Four roles: data, logging, unknown, prompt.
|
|
46
|
-
-- These are structural — see PluginContext.CATEGORIES.
|
|
47
|
-
-- 'tool' is internal (model_visible=0 in practice).
|
|
48
|
-
-- Default is 'logging' — plugins opt into 'data' explicitly.
|
|
49
|
-
, CASE
|
|
50
|
-
WHEN scheme IS NULL THEN 'data'
|
|
51
|
-
WHEN scheme IN ('http', 'https') THEN 'data'
|
|
52
|
-
WHEN scheme IN ('known', 'skill') THEN 'data'
|
|
53
|
-
WHEN scheme = 'unknown' THEN 'unknown'
|
|
54
|
-
WHEN scheme = 'prompt' THEN 'prompt'
|
|
55
|
-
WHEN scheme = 'tool' THEN 'tool'
|
|
56
|
-
ELSE 'logging'
|
|
57
|
-
END AS category
|
|
58
48
|
FROM visible
|
|
59
49
|
WHERE visible_fidelity IS NOT NULL
|
|
60
50
|
)
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { countTokens } from "../../agent/tokens.js";
|
|
2
|
-
|
|
3
|
-
export class BudgetExceeded extends Error {
|
|
4
|
-
constructor(path, requested, remaining) {
|
|
5
|
-
super(
|
|
6
|
-
`Budget exceeded: ${path} needs ${requested} tokens, ${remaining} remaining`,
|
|
7
|
-
);
|
|
8
|
-
this.name = "BudgetExceeded";
|
|
9
|
-
this.status = 413;
|
|
10
|
-
this.path = path;
|
|
11
|
-
this.requested = requested;
|
|
12
|
-
this.remaining = remaining;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default class BudgetGuard {
|
|
17
|
-
#ceiling;
|
|
18
|
-
#baseline;
|
|
19
|
-
#spent;
|
|
20
|
-
#tripped;
|
|
21
|
-
#tripSource;
|
|
22
|
-
|
|
23
|
-
constructor(ceiling, baseline) {
|
|
24
|
-
this.#ceiling = ceiling ?? null;
|
|
25
|
-
this.#baseline = baseline;
|
|
26
|
-
this.#spent = 0;
|
|
27
|
-
this.#tripped = false;
|
|
28
|
-
this.#tripSource = null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
get isTripped() {
|
|
32
|
-
return this.#tripped;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
get tripSource() {
|
|
36
|
-
return this.#tripSource;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
get remaining() {
|
|
40
|
-
if (this.#ceiling === null) return Infinity;
|
|
41
|
-
return this.#ceiling - this.#baseline - this.#spent;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
get spent() {
|
|
45
|
-
return this.#spent;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
check(tokens, path) {
|
|
49
|
-
if (this.#ceiling === null) return;
|
|
50
|
-
if (this.#tripped) throw new BudgetExceeded(path, tokens, 0);
|
|
51
|
-
if (tokens <= 0) return;
|
|
52
|
-
const remaining = this.remaining;
|
|
53
|
-
if (tokens > remaining) throw new BudgetExceeded(path, tokens, remaining);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
charge(tokens) {
|
|
57
|
-
if (tokens > 0) this.#spent += tokens;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
trip(source) {
|
|
61
|
-
this.#tripped = true;
|
|
62
|
-
this.#tripSource = source;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Compute the token delta for an upsert. New entry = full cost.
|
|
67
|
-
* Update = difference between new and old body.
|
|
68
|
-
*/
|
|
69
|
-
static delta(newBody, existingBody) {
|
|
70
|
-
const newTokens = countTokens(newBody);
|
|
71
|
-
const oldTokens = existingBody ? countTokens(existingBody) : 0;
|
|
72
|
-
return newTokens - oldTokens;
|
|
73
|
-
}
|
|
74
|
-
}
|