@possumtech/rummy 0.3.0 → 0.4.0
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 +13 -1
- package/PLUGINS.md +1 -1
- package/README.md +5 -1
- package/SPEC.md +211 -54
- package/migrations/001_initial_schema.sql +3 -4
- package/package.json +7 -3
- package/service.js +5 -3
- package/src/agent/AgentLoop.js +183 -238
- package/src/agent/ContextAssembler.js +2 -0
- package/src/agent/KnownStore.js +36 -85
- package/src/agent/ResponseHealer.js +65 -31
- package/src/agent/TurnExecutor.js +284 -382
- package/src/agent/XmlParser.js +28 -4
- package/src/agent/known_queries.sql +1 -1
- package/src/agent/known_store.sql +32 -34
- package/src/agent/runs.sql +2 -2
- 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 +2 -4
- package/src/hooks/ToolRegistry.js +8 -13
- package/src/plugins/ask_user/ask_userDoc.js +3 -8
- package/src/plugins/budget/README.md +26 -30
- package/src/plugins/budget/budget.js +69 -36
- package/src/plugins/budget/recovery.js +47 -0
- package/src/plugins/cp/cp.js +1 -1
- package/src/plugins/cp/cpDoc.js +5 -10
- package/src/plugins/env/envDoc.js +3 -8
- package/src/plugins/get/get.js +70 -2
- package/src/plugins/get/getDoc.js +19 -16
- package/src/plugins/hedberg/matcher.js +10 -29
- package/src/plugins/helpers.js +2 -2
- package/src/plugins/instructions/instructions.js +3 -2
- package/src/plugins/instructions/preamble.md +33 -12
- package/src/plugins/known/known.js +66 -17
- package/src/plugins/known/knownDoc.js +7 -10
- package/src/plugins/mv/mv.js +18 -1
- package/src/plugins/mv/mvDoc.js +9 -10
- package/src/plugins/{current → performed}/README.md +4 -3
- package/src/plugins/{current/current.js → performed/performed.js} +15 -20
- package/src/plugins/policy/policy.js +47 -0
- 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 +10 -60
- package/src/plugins/prompt/prompt.js +10 -8
- package/src/plugins/rm/rm.js +27 -15
- package/src/plugins/rm/rmDoc.js +6 -11
- package/src/plugins/rpc/rpc.js +3 -1
- package/src/plugins/set/set.js +125 -92
- package/src/plugins/set/setDoc.js +28 -37
- package/src/plugins/sh/shDoc.js +2 -7
- package/src/plugins/summarize/summarize.js +7 -0
- package/src/plugins/summarize/summarizeDoc.js +6 -11
- package/src/plugins/telemetry/telemetry.js +14 -9
- package/src/plugins/think/think.js +12 -0
- package/src/plugins/think/thinkDoc.js +18 -0
- package/src/plugins/unknown/README.md +2 -1
- package/src/plugins/unknown/unknown.js +26 -4
- package/src/plugins/unknown/unknownDoc.js +9 -14
- package/src/plugins/update/update.js +7 -0
- package/src/plugins/update/updateDoc.js +6 -11
- package/src/server/ClientConnection.js +69 -45
- package/src/sql/v_model_context.sql +7 -17
- package/src/plugins/budget/BudgetGuard.js +0 -74
|
@@ -7,6 +7,7 @@ export default class Update {
|
|
|
7
7
|
this.#core = core;
|
|
8
8
|
core.ensureTool();
|
|
9
9
|
core.registerScheme({ category: "logging" });
|
|
10
|
+
core.on("handler", this.handler.bind(this));
|
|
10
11
|
core.on("full", this.full.bind(this));
|
|
11
12
|
core.on("summary", this.summary.bind(this));
|
|
12
13
|
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
@@ -15,6 +16,12 @@ export default class Update {
|
|
|
15
16
|
});
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
async handler(entry, rummy) {
|
|
20
|
+
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
21
|
+
const statusPath = await store.slugPath(runId, "update", entry.body);
|
|
22
|
+
await store.upsert(runId, turn, statusPath, entry.body, 200, { loopId });
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
full(entry) {
|
|
19
26
|
return `# update\n${entry.body}`;
|
|
20
27
|
}
|
|
@@ -2,31 +2,26 @@
|
|
|
2
2
|
// Text goes to the model. Rationale stays in source.
|
|
3
3
|
// Changing ANY line requires reading ALL rationales first.
|
|
4
4
|
const LINES = [
|
|
5
|
-
// --- Syntax
|
|
6
5
|
["## <update>[brief status]</update> - Signal continuation"],
|
|
7
|
-
|
|
8
|
-
// --- Examples: research progress and multi-step work
|
|
9
6
|
[
|
|
10
7
|
"Example: <update>Reading config files</update>",
|
|
11
|
-
"Progress checkpoint.
|
|
8
|
+
"Progress checkpoint. Status signal, not a log entry.",
|
|
12
9
|
],
|
|
13
10
|
[
|
|
14
11
|
"Example: <update>Found 3 issues, fixing first</update>",
|
|
15
|
-
"Multi-step progress.
|
|
12
|
+
"Multi-step progress. Ongoing work.",
|
|
16
13
|
],
|
|
17
|
-
|
|
18
|
-
// --- Constraints: RFC-style MUST/MUST NOT
|
|
19
14
|
[
|
|
20
|
-
"* YOU MUST use <update> if still working — describes the current state",
|
|
21
|
-
"Continuation signal. Triggers the next turn
|
|
15
|
+
"* YOU MUST use <update></update> if still working — describes the current state",
|
|
16
|
+
"Continuation signal. Triggers the next turn.",
|
|
22
17
|
],
|
|
23
18
|
[
|
|
24
19
|
"* YOU MUST NOT use <update> if done — use <summarize/> instead",
|
|
25
|
-
"Mutual exclusion with summarize.
|
|
20
|
+
"Mutual exclusion with summarize.",
|
|
26
21
|
],
|
|
27
22
|
[
|
|
28
23
|
"* YOU MUST keep <update> to <= 80 characters",
|
|
29
|
-
"Length cap.
|
|
24
|
+
"Length cap.",
|
|
30
25
|
],
|
|
31
26
|
];
|
|
32
27
|
|
|
@@ -21,53 +21,76 @@ 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
|
+
#onProposal = (payload) => {
|
|
40
|
+
if (payload.projectId === this.#context.projectId) {
|
|
41
|
+
this.#sendNotification("run/proposal", {
|
|
42
|
+
run: payload.run,
|
|
43
|
+
proposed: payload.proposed,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
#onRender = (payload) => {
|
|
49
|
+
if (payload.projectId === this.#context.projectId) {
|
|
50
|
+
this.#sendNotification("ui/render", {
|
|
51
|
+
text: payload.text,
|
|
52
|
+
append: payload.append,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
57
|
+
#onNotify = (payload) => {
|
|
58
|
+
if (payload.projectId === this.#context.projectId) {
|
|
59
|
+
this.#sendNotification("ui/notify", {
|
|
60
|
+
text: payload.text,
|
|
61
|
+
level: payload.level,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
#onState = (payload) => {
|
|
67
|
+
if (payload.projectId === this.#context.projectId) {
|
|
68
|
+
this.#sendNotification("run/state", {
|
|
69
|
+
run: payload.run,
|
|
70
|
+
turn: payload.turn,
|
|
71
|
+
status: payload.status,
|
|
72
|
+
summary: payload.summary,
|
|
73
|
+
history: payload.history,
|
|
74
|
+
unknowns: payload.unknowns,
|
|
75
|
+
telemetry: payload.telemetry,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
#setupNotifications() {
|
|
81
|
+
this.#hooks.run.progress.on(this.#onProgress);
|
|
82
|
+
this.#hooks.turn.proposal.on(this.#onProposal);
|
|
83
|
+
this.#hooks.ui.render.on(this.#onRender);
|
|
84
|
+
this.#hooks.ui.notify.on(this.#onNotify);
|
|
85
|
+
this.#hooks.run.state.on(this.#onState);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#teardown() {
|
|
89
|
+
this.#hooks.run.progress.off(this.#onProgress);
|
|
90
|
+
this.#hooks.turn.proposal.off(this.#onProposal);
|
|
91
|
+
this.#hooks.ui.render.off(this.#onRender);
|
|
92
|
+
this.#hooks.ui.notify.off(this.#onNotify);
|
|
93
|
+
this.#hooks.run.state.off(this.#onState);
|
|
71
94
|
}
|
|
72
95
|
|
|
73
96
|
#buildHandlerContext() {
|
|
@@ -135,10 +158,11 @@ export default class ClientConnection {
|
|
|
135
158
|
);
|
|
136
159
|
} else {
|
|
137
160
|
const timeout = Number(process.env.RUMMY_RPC_TIMEOUT) || 10_000;
|
|
161
|
+
let timer;
|
|
138
162
|
result = await Promise.race([
|
|
139
163
|
registration.handler(params || {}, this.#buildHandlerContext()),
|
|
140
|
-
new Promise((_, reject) =>
|
|
141
|
-
setTimeout(
|
|
164
|
+
new Promise((_, reject) => {
|
|
165
|
+
timer = setTimeout(
|
|
142
166
|
() =>
|
|
143
167
|
reject(
|
|
144
168
|
new Error(
|
|
@@ -149,9 +173,9 @@ export default class ClientConnection {
|
|
|
149
173
|
),
|
|
150
174
|
),
|
|
151
175
|
timeout,
|
|
152
|
-
)
|
|
153
|
-
),
|
|
154
|
-
]);
|
|
176
|
+
);
|
|
177
|
+
}),
|
|
178
|
+
]).finally(() => clearTimeout(timer));
|
|
155
179
|
}
|
|
156
180
|
|
|
157
181
|
const finalResult = await this.#hooks.rpc.response.result.filter(result, {
|
|
@@ -13,7 +13,8 @@ visible AS (
|
|
|
13
13
|
, ke.turn
|
|
14
14
|
, ke.updated_at
|
|
15
15
|
, ke.attributes
|
|
16
|
-
, ke.tokens
|
|
16
|
+
, ke.tokens
|
|
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,13 @@ 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
|
|
44
|
+
, tokens
|
|
41
45
|
, CASE
|
|
42
46
|
WHEN visible_fidelity IN ('full', 'summary') THEN body
|
|
43
47
|
ELSE ''
|
|
44
48
|
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
49
|
FROM visible
|
|
59
50
|
WHERE visible_fidelity IS NOT NULL
|
|
60
51
|
)
|
|
@@ -81,9 +72,8 @@ SELECT
|
|
|
81
72
|
END
|
|
82
73
|
, CASE scheme WHEN 'skill' THEN 0 ELSE 1 END
|
|
83
74
|
, CASE fidelity
|
|
84
|
-
WHEN '
|
|
85
|
-
|
|
86
|
-
ELSE 2
|
|
75
|
+
WHEN 'summary' THEN 0
|
|
76
|
+
ELSE 1
|
|
87
77
|
END
|
|
88
78
|
, turn
|
|
89
79
|
, updated_at
|
|
@@ -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
|
-
}
|