@possumtech/rummy 0.2.7 → 0.3.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 +12 -3
- package/EXCEPTIONS.md +46 -0
- package/PLUGINS.md +454 -197
- package/SPEC.md +284 -93
- package/migrations/001_initial_schema.sql +57 -70
- package/package.json +16 -10
- package/service.js +1 -1
- package/src/agent/AgentLoop.js +254 -70
- package/src/agent/ContextAssembler.js +18 -4
- package/src/agent/KnownStore.js +156 -23
- package/src/agent/ProjectAgent.js +5 -4
- package/src/agent/ResponseHealer.js +21 -1
- package/src/agent/TurnExecutor.js +393 -115
- package/src/agent/XmlParser.js +92 -39
- package/src/agent/known_checks.sql +5 -4
- package/src/agent/known_queries.sql +4 -3
- package/src/agent/known_store.sql +45 -15
- package/src/agent/loops.sql +63 -0
- package/src/agent/runs.sql +7 -7
- package/src/agent/schemes.sql +5 -2
- package/src/agent/tokens.js +6 -21
- package/src/agent/turns.sql +13 -4
- package/src/hooks/Hooks.js +18 -0
- package/src/hooks/PluginContext.js +14 -10
- package/src/hooks/RummyContext.js +30 -10
- package/src/hooks/ToolRegistry.js +83 -19
- package/src/llm/LlmProvider.js +27 -8
- package/src/llm/OpenAiClient.js +20 -0
- package/src/llm/OpenRouterClient.js +24 -2
- package/src/llm/XaiClient.js +47 -2
- package/src/plugins/ask_user/README.md +4 -4
- package/src/plugins/ask_user/ask_user.js +8 -7
- package/src/plugins/ask_user/ask_userDoc.js +29 -0
- package/src/plugins/budget/BudgetGuard.js +74 -0
- package/src/plugins/budget/README.md +43 -0
- package/src/plugins/budget/budget.js +79 -0
- package/src/plugins/cp/README.md +5 -4
- package/src/plugins/cp/cp.js +16 -12
- package/src/plugins/cp/cpDoc.js +29 -0
- package/src/plugins/current/README.md +4 -4
- package/src/plugins/current/current.js +12 -10
- package/src/plugins/engine/engine.sql +5 -10
- package/src/plugins/engine/turn_context.sql +13 -13
- package/src/plugins/env/README.md +3 -4
- package/src/plugins/env/env.js +8 -7
- package/src/plugins/env/envDoc.js +29 -0
- package/src/plugins/file/README.md +9 -12
- package/src/plugins/file/file.js +34 -45
- package/src/plugins/get/README.md +2 -2
- package/src/plugins/get/get.js +28 -11
- package/src/plugins/get/getDoc.js +41 -0
- package/src/plugins/hedberg/docs.md +0 -9
- package/src/plugins/hedberg/hedberg.js +4 -6
- package/src/plugins/hedberg/matcher.js +1 -1
- package/src/plugins/hedberg/normalize.js +28 -0
- package/src/plugins/hedberg/patterns.js +31 -33
- package/src/plugins/hedberg/sed.js +17 -10
- package/src/plugins/helpers.js +2 -2
- package/src/plugins/index.js +93 -28
- package/src/plugins/instructions/README.md +6 -2
- package/src/plugins/instructions/instructions.js +21 -5
- package/src/plugins/instructions/preamble.md +9 -5
- package/src/plugins/known/README.md +10 -7
- package/src/plugins/known/known.js +33 -23
- package/src/plugins/known/knownDoc.js +33 -0
- package/src/plugins/mv/README.md +5 -4
- package/src/plugins/mv/mv.js +16 -12
- package/src/plugins/mv/mvDoc.js +31 -0
- package/src/plugins/persona/persona.js +78 -0
- package/src/plugins/previous/README.md +2 -2
- package/src/plugins/previous/previous.js +12 -8
- package/src/plugins/progress/progress.js +44 -12
- package/src/plugins/prompt/README.md +5 -5
- package/src/plugins/prompt/prompt.js +23 -19
- package/src/plugins/rm/README.md +4 -4
- package/src/plugins/rm/rm.js +29 -12
- package/src/plugins/rm/rmDoc.js +30 -0
- package/src/plugins/rpc/README.md +15 -28
- package/src/plugins/rpc/rpc.js +63 -107
- package/src/plugins/set/README.md +13 -12
- package/src/plugins/set/set.js +82 -21
- package/src/plugins/set/setDoc.js +45 -0
- package/src/plugins/sh/README.md +4 -4
- package/src/plugins/sh/sh.js +8 -7
- package/src/plugins/sh/shDoc.js +29 -0
- package/src/plugins/{skills/skills.js → skill/skill.js} +12 -54
- package/src/plugins/summarize/README.md +6 -5
- package/src/plugins/summarize/summarize.js +7 -6
- package/src/plugins/summarize/summarizeDoc.js +33 -0
- package/src/plugins/telemetry/telemetry.js +20 -8
- package/src/plugins/think/README.md +20 -0
- package/src/plugins/think/think.js +5 -0
- package/src/plugins/unknown/README.md +5 -5
- package/src/plugins/unknown/unknown.js +11 -8
- package/src/plugins/unknown/unknownDoc.js +31 -0
- package/src/plugins/update/README.md +3 -8
- package/src/plugins/update/update.js +7 -6
- package/src/plugins/update/updateDoc.js +33 -0
- package/src/server/ClientConnection.js +3 -5
- package/src/server/RpcRegistry.js +52 -4
- package/src/sql/v_model_context.sql +31 -39
- package/src/sql/v_run_log.sql +3 -3
- package/src/agent/prompt_queue.sql +0 -39
- package/src/plugins/ask_user/docs.md +0 -2
- package/src/plugins/cp/docs.md +0 -2
- package/src/plugins/env/docs.md +0 -2
- package/src/plugins/get/docs.md +0 -6
- package/src/plugins/known/docs.md +0 -3
- package/src/plugins/mv/docs.md +0 -2
- package/src/plugins/rm/docs.md +0 -4
- package/src/plugins/set/docs.md +0 -4
- package/src/plugins/sh/docs.md +0 -2
- package/src/plugins/skills/README.md +0 -25
- package/src/plugins/store/README.md +0 -20
- package/src/plugins/store/docs.md +0 -5
- package/src/plugins/store/store.js +0 -52
- package/src/plugins/summarize/docs.md +0 -4
- package/src/plugins/unknown/docs.md +0 -5
- package/src/plugins/update/docs.md +0 -4
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Tool doc for <sh>. Each entry: [text, rationale].
|
|
2
|
+
// Text goes to the model. Rationale stays in source.
|
|
3
|
+
// Changing ANY line requires reading ALL rationales first.
|
|
4
|
+
const LINES = [
|
|
5
|
+
// --- Syntax
|
|
6
|
+
["## <sh>[command]</sh> - Run a shell command with side effects"],
|
|
7
|
+
|
|
8
|
+
// --- Examples: install and test — real mutations
|
|
9
|
+
[
|
|
10
|
+
"Example: <sh>npm install express</sh>",
|
|
11
|
+
"Package install. Shows a real side-effect command.",
|
|
12
|
+
],
|
|
13
|
+
[
|
|
14
|
+
"Example: <sh>npm test</sh>",
|
|
15
|
+
"Test execution. Another common side-effect action.",
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
// --- Constraints
|
|
19
|
+
[
|
|
20
|
+
"* YOU MUST NOT use <sh/> to read, create, or edit files — use <get/> and <set/>",
|
|
21
|
+
"Forces file operations through the entry system. Prevents untracked mutations.",
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"* YOU MUST use <env/> for commands without side effects",
|
|
25
|
+
"Reinforces the env/sh split. Read = env, mutate = sh.",
|
|
26
|
+
],
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export default LINES.map(([text]) => text).join("\n");
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
|
-
export default class
|
|
4
|
+
export default class Skill {
|
|
5
5
|
#core;
|
|
6
6
|
|
|
7
7
|
constructor(core) {
|
|
8
8
|
this.#core = core;
|
|
9
9
|
core.registerScheme({
|
|
10
10
|
name: "skill",
|
|
11
|
-
|
|
12
|
-
category: "knowledge",
|
|
11
|
+
category: "data",
|
|
13
12
|
});
|
|
13
|
+
core.hooks.tools.onView("skill", (entry) => entry.body);
|
|
14
|
+
|
|
14
15
|
const r = core.hooks.rpc.registry;
|
|
15
16
|
|
|
16
17
|
r.register("skill/add", {
|
|
@@ -23,19 +24,12 @@ export default class Skills {
|
|
|
23
24
|
|
|
24
25
|
const body = await loadFile("skills", params.name);
|
|
25
26
|
const store = ctx.projectAgent.entries;
|
|
26
|
-
await store.upsert(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
body,
|
|
31
|
-
"full",
|
|
32
|
-
{
|
|
33
|
-
attributes: {
|
|
34
|
-
name: params.name,
|
|
35
|
-
source: filePath("skills", params.name),
|
|
36
|
-
},
|
|
27
|
+
await store.upsert(runRow.id, 0, `skill://${params.name}`, body, 200, {
|
|
28
|
+
attributes: {
|
|
29
|
+
name: params.name,
|
|
30
|
+
source: filePath("skills", params.name),
|
|
37
31
|
},
|
|
38
|
-
);
|
|
32
|
+
});
|
|
39
33
|
|
|
40
34
|
return { status: "ok", skill: params.name };
|
|
41
35
|
},
|
|
@@ -84,10 +78,10 @@ export default class Skills {
|
|
|
84
78
|
);
|
|
85
79
|
return entries.map((e) => ({
|
|
86
80
|
name: e.path.replace("skill://", ""),
|
|
87
|
-
|
|
81
|
+
status: e.status,
|
|
88
82
|
}));
|
|
89
83
|
},
|
|
90
|
-
description: "List skills active on a run. Returns [{ name,
|
|
84
|
+
description: "List skills active on a run. Returns [{ name, status }].",
|
|
91
85
|
params: { run: "string — run alias" },
|
|
92
86
|
requiresInit: true,
|
|
93
87
|
});
|
|
@@ -98,43 +92,7 @@ export default class Skills {
|
|
|
98
92
|
requiresInit: true,
|
|
99
93
|
});
|
|
100
94
|
|
|
101
|
-
|
|
102
|
-
handler: async (params, ctx) => {
|
|
103
|
-
if (!params.run) throw new Error("run is required");
|
|
104
|
-
|
|
105
|
-
const runRow = await ctx.db.get_run_by_alias.get({ alias: params.run });
|
|
106
|
-
if (!runRow) throw new Error(`Run not found: ${params.run}`);
|
|
107
|
-
|
|
108
|
-
let text = params.text;
|
|
109
|
-
if (params.name && !text) {
|
|
110
|
-
text = await loadFile("personas", params.name);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
await ctx.db.update_run_config.run({
|
|
114
|
-
id: runRow.id,
|
|
115
|
-
temperature: null,
|
|
116
|
-
persona: text || null,
|
|
117
|
-
context_limit: null,
|
|
118
|
-
model: null,
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
return { status: "ok" };
|
|
122
|
-
},
|
|
123
|
-
description:
|
|
124
|
-
"Set persona on a run. Pass name or text. Pass neither to clear.",
|
|
125
|
-
params: {
|
|
126
|
-
run: "string — run alias",
|
|
127
|
-
name: "string? — persona filename (without .md)",
|
|
128
|
-
text: "string? — raw persona text (overrides name)",
|
|
129
|
-
},
|
|
130
|
-
requiresInit: true,
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
r.register("listPersonas", {
|
|
134
|
-
handler: async () => listAvailable("personas"),
|
|
135
|
-
description: "List available persona files. Returns [{ name, path }].",
|
|
136
|
-
requiresInit: true,
|
|
137
|
-
});
|
|
95
|
+
// Persona methods extracted to persona plugin.
|
|
138
96
|
}
|
|
139
97
|
}
|
|
140
98
|
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
# summarize
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Lifecycle signal — the model declares it has completed the task.
|
|
4
4
|
|
|
5
5
|
## Registration
|
|
6
6
|
|
|
7
7
|
- **Tool**: `summarize`
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **Handler**: None — projection only.
|
|
8
|
+
- **Category**: `logging`
|
|
9
|
+
- **Handler**: None — recorded by TurnExecutor as a lifecycle signal.
|
|
11
10
|
|
|
12
11
|
## Projection
|
|
13
12
|
|
|
@@ -15,4 +14,6 @@ Shows `summarize` followed by the entry body.
|
|
|
15
14
|
|
|
16
15
|
## Behavior
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
If the model sends `<summarize>` but actions in the same turn failed,
|
|
18
|
+
TurnExecutor overrides it to `<update>` — the model's assertion that
|
|
19
|
+
it's done is false.
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import docs from "./summarizeDoc.js";
|
|
2
2
|
|
|
3
3
|
export default class Summarize {
|
|
4
4
|
#core;
|
|
5
5
|
|
|
6
6
|
constructor(core) {
|
|
7
7
|
this.#core = core;
|
|
8
|
-
core.
|
|
8
|
+
core.ensureTool();
|
|
9
|
+
core.registerScheme({ category: "logging" });
|
|
9
10
|
core.on("full", this.full.bind(this));
|
|
10
11
|
core.on("summary", this.summary.bind(this));
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
);
|
|
12
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
13
|
+
docsMap.summarize = docs;
|
|
14
|
+
return docsMap;
|
|
15
|
+
});
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
full(entry) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Tool doc for <summarize>. Each entry: [text, rationale].
|
|
2
|
+
// Text goes to the model. Rationale stays in source.
|
|
3
|
+
// Changing ANY line requires reading ALL rationales first.
|
|
4
|
+
const LINES = [
|
|
5
|
+
// --- Syntax
|
|
6
|
+
["## <summarize>[answer or summary]</summarize> - Signal completion"],
|
|
7
|
+
|
|
8
|
+
// --- Examples: answer and task completion
|
|
9
|
+
[
|
|
10
|
+
"Example: <summarize>The port is 8080</summarize>",
|
|
11
|
+
"Direct answer. Shows summarize as the vehicle for delivering answers.",
|
|
12
|
+
],
|
|
13
|
+
[
|
|
14
|
+
"Example: <summarize>Installed express, updated config</summarize>",
|
|
15
|
+
"Task summary. Shows summarize for action completion.",
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
// --- Constraints: RFC-style MUST/MUST NOT
|
|
19
|
+
[
|
|
20
|
+
"* YOU MUST use <summarize> when done — describes the final state",
|
|
21
|
+
"Completion signal. Without this, the loop continues indefinitely.",
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"* YOU MUST NOT use <summarize> if still working — use <update/> instead",
|
|
25
|
+
"Mutual exclusion with update. Prevents premature completion.",
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
"* YOU MUST keep <summarize> to <= 80 characters",
|
|
29
|
+
"Length cap. Matches the summary attribute constraint. Prevents verbose output.",
|
|
30
|
+
],
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export default LINES.map(([text]) => text).join("\n");
|
|
@@ -75,22 +75,28 @@ export default class Telemetry {
|
|
|
75
75
|
result,
|
|
76
76
|
responseMessage,
|
|
77
77
|
content,
|
|
78
|
-
commands,
|
|
79
78
|
unparsed,
|
|
79
|
+
assembledTokens,
|
|
80
80
|
systemMsg,
|
|
81
81
|
userMsg,
|
|
82
82
|
}) {
|
|
83
|
-
const { entries: store, runId } = rummy;
|
|
83
|
+
const { entries: store, runId, loopId } = rummy;
|
|
84
84
|
|
|
85
85
|
// assistant://N — the model's raw response
|
|
86
|
-
await store.upsert(runId, turn, `assistant://${turn}`, content,
|
|
86
|
+
await store.upsert(runId, turn, `assistant://${turn}`, content, 200, {
|
|
87
|
+
loopId,
|
|
88
|
+
});
|
|
87
89
|
|
|
88
90
|
// system://N, user://N — assembled messages as audit
|
|
89
91
|
if (systemMsg) {
|
|
90
|
-
await store.upsert(runId, turn, `system://${turn}`, systemMsg,
|
|
92
|
+
await store.upsert(runId, turn, `system://${turn}`, systemMsg, 200, {
|
|
93
|
+
loopId,
|
|
94
|
+
});
|
|
91
95
|
}
|
|
92
96
|
if (userMsg) {
|
|
93
|
-
await store.upsert(runId, turn, `user://${turn}`, userMsg,
|
|
97
|
+
await store.upsert(runId, turn, `user://${turn}`, userMsg, 200, {
|
|
98
|
+
loopId,
|
|
99
|
+
});
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
// model://N — raw API response diagnostics
|
|
@@ -105,7 +111,8 @@ export default class Telemetry {
|
|
|
105
111
|
usage: result.usage || null,
|
|
106
112
|
model: result.model || null,
|
|
107
113
|
}),
|
|
108
|
-
|
|
114
|
+
200,
|
|
115
|
+
{ loopId },
|
|
109
116
|
);
|
|
110
117
|
|
|
111
118
|
// reasoning://N
|
|
@@ -115,13 +122,16 @@ export default class Telemetry {
|
|
|
115
122
|
turn,
|
|
116
123
|
`reasoning://${turn}`,
|
|
117
124
|
responseMessage.reasoning_content,
|
|
118
|
-
|
|
125
|
+
200,
|
|
126
|
+
{ loopId },
|
|
119
127
|
);
|
|
120
128
|
}
|
|
121
129
|
|
|
122
130
|
// content://N — unparsed text
|
|
123
131
|
if (unparsed) {
|
|
124
|
-
await store.upsert(runId, turn, `content://${turn}`, unparsed,
|
|
132
|
+
await store.upsert(runId, turn, `content://${turn}`, unparsed, 200, {
|
|
133
|
+
loopId,
|
|
134
|
+
});
|
|
125
135
|
}
|
|
126
136
|
|
|
127
137
|
// Commit usage stats
|
|
@@ -139,6 +149,8 @@ export default class Telemetry {
|
|
|
139
149
|
0;
|
|
140
150
|
await rummy.db.update_turn_stats.run({
|
|
141
151
|
id: rummy.turnId,
|
|
152
|
+
context_tokens: assembledTokens ?? 0,
|
|
153
|
+
reasoning_content: responseMessage?.reasoning_content || null,
|
|
142
154
|
prompt_tokens: usage.prompt_tokens ?? 0,
|
|
143
155
|
cached_tokens: cachedTokens ?? 0,
|
|
144
156
|
completion_tokens: usage.completion_tokens ?? 0,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# think
|
|
2
|
+
|
|
3
|
+
Provides a `<think>` tag for model reasoning. Not a tool — does not
|
|
4
|
+
appear in the tool list.
|
|
5
|
+
|
|
6
|
+
## Registration
|
|
7
|
+
|
|
8
|
+
- **Scheme**: `think` — `category: "logging"`, `model_visible: 0`
|
|
9
|
+
- **No handler, no view, no tool registration**
|
|
10
|
+
|
|
11
|
+
## Behavior
|
|
12
|
+
|
|
13
|
+
The model writes `<think>reasoning</think>` before tool commands.
|
|
14
|
+
XmlParser captures it, TurnExecutor records it as a `think://` entry.
|
|
15
|
+
Invisible to the model on subsequent turns (`model_visible: 0`).
|
|
16
|
+
Available for debugging and audit.
|
|
17
|
+
|
|
18
|
+
Models with server-side reasoning (extended thinking) use that
|
|
19
|
+
capability independently. The `<think>` tag is a floor — every model
|
|
20
|
+
gets at least this.
|
|
@@ -7,9 +7,9 @@ The Rumsfeld mechanism. The model registers what it doesn't know before acting.
|
|
|
7
7
|
## Registration
|
|
8
8
|
|
|
9
9
|
- **Tool**: `unknown`
|
|
10
|
-
- **
|
|
11
|
-
- **Category**: structural
|
|
10
|
+
- **Category**: `unknown`
|
|
12
11
|
- **Handler**: None — recorded by TurnExecutor, deduplicated against existing unknowns.
|
|
12
|
+
- **Filter**: `assembly.system` at priority 300 — renders `<unknowns>` section.
|
|
13
13
|
|
|
14
14
|
## Projection
|
|
15
15
|
|
|
@@ -18,6 +18,6 @@ The Rumsfeld mechanism. The model registers what it doesn't know before acting.
|
|
|
18
18
|
## Behavior
|
|
19
19
|
|
|
20
20
|
Unknowns are sticky — they persist across turns until the model explicitly
|
|
21
|
-
|
|
22
|
-
`<env>`, or `<ask_user>`, then removes resolved ones
|
|
23
|
-
|
|
21
|
+
removes them with `<rm>`. The model investigates unknowns using `<get>`,
|
|
22
|
+
`<env>`, or `<ask_user>`, then removes resolved ones. Server deduplicates
|
|
23
|
+
on insert. Turn numbers shown on each unknown for temporal reasoning.
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import docs from "./unknownDoc.js";
|
|
2
2
|
|
|
3
3
|
export default class Unknown {
|
|
4
4
|
#core;
|
|
5
5
|
|
|
6
6
|
constructor(core) {
|
|
7
7
|
this.#core = core;
|
|
8
|
+
core.ensureTool();
|
|
8
9
|
core.registerScheme({
|
|
9
|
-
|
|
10
|
-
category: "knowledge",
|
|
10
|
+
category: "unknown",
|
|
11
11
|
});
|
|
12
12
|
core.on("full", this.full.bind(this));
|
|
13
13
|
core.filter("assembly.system", this.assembleUnknowns.bind(this), 300);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
);
|
|
14
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
15
|
+
docsMap.unknown = docs;
|
|
16
|
+
return docsMap;
|
|
17
|
+
});
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
full(entry) {
|
|
@@ -25,7 +25,10 @@ export default class Unknown {
|
|
|
25
25
|
const entries = ctx.rows.filter((r) => r.category === "unknown");
|
|
26
26
|
if (entries.length === 0) return content;
|
|
27
27
|
|
|
28
|
-
const lines = entries.map(
|
|
28
|
+
const lines = entries.map(
|
|
29
|
+
(u) =>
|
|
30
|
+
`<unknown path="${u.path}" turn="${u.source_turn || u.turn}">${u.body}</unknown>`,
|
|
31
|
+
);
|
|
29
32
|
return `${content}\n\n<unknowns>\n${lines.join("\n")}\n</unknowns>`;
|
|
30
33
|
}
|
|
31
34
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Tool doc for <unknown>. Each entry: [text, rationale].
|
|
2
|
+
// Text goes to the model. Rationale stays in source.
|
|
3
|
+
// Changing ANY line requires reading ALL rationales first.
|
|
4
|
+
const LINES = [
|
|
5
|
+
// --- Syntax: body = what you need to learn
|
|
6
|
+
[
|
|
7
|
+
`## <unknown>[specific thing I need to learn]</unknown> - Track open questions`,
|
|
8
|
+
],
|
|
9
|
+
|
|
10
|
+
// --- Examples: concrete unknowns, not abstract
|
|
11
|
+
[
|
|
12
|
+
`Example: <unknown path="unknown://answer">contents of answer.txt</unknown>`,
|
|
13
|
+
`Specific and actionable. Shows that unknowns are concrete investigation targets.`,
|
|
14
|
+
],
|
|
15
|
+
[
|
|
16
|
+
`Example: <unknown>which database adapter is configured</unknown>`,
|
|
17
|
+
`Domain question. Shows unknowns for configuration/architecture questions.`,
|
|
18
|
+
],
|
|
19
|
+
|
|
20
|
+
// --- Lifecycle: register → investigate → resolve
|
|
21
|
+
[
|
|
22
|
+
`* Investigate with Tool Commands`,
|
|
23
|
+
`Cross-tool lifecycle: unknowns drive get/env/ask_user actions.`,
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
`* When resolved or irrelevant, remove with <rm path="unknown://..."/>`,
|
|
27
|
+
`Cross-tool lifecycle: rm cleans resolved unknowns from context.`,
|
|
28
|
+
],
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export default LINES.map(([text]) => text).join("\n");
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
# update
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Lifecycle signal — the model declares it has more work to do.
|
|
4
4
|
|
|
5
5
|
## Registration
|
|
6
6
|
|
|
7
7
|
- **Tool**: `update`
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **Handler**: None — projection only.
|
|
8
|
+
- **Category**: `logging`
|
|
9
|
+
- **Handler**: None — recorded by TurnExecutor as a lifecycle signal.
|
|
11
10
|
|
|
12
11
|
## Projection
|
|
13
12
|
|
|
14
13
|
Shows `update` followed by the entry body.
|
|
15
|
-
|
|
16
|
-
## Behavior
|
|
17
|
-
|
|
18
|
-
No handler logic. Allows the model to emit progress/status entries that appear in context via projection.
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import docs from "./updateDoc.js";
|
|
2
2
|
|
|
3
3
|
export default class Update {
|
|
4
4
|
#core;
|
|
5
5
|
|
|
6
6
|
constructor(core) {
|
|
7
7
|
this.#core = core;
|
|
8
|
-
core.
|
|
8
|
+
core.ensureTool();
|
|
9
|
+
core.registerScheme({ category: "logging" });
|
|
9
10
|
core.on("full", this.full.bind(this));
|
|
10
11
|
core.on("summary", this.summary.bind(this));
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
);
|
|
12
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
13
|
+
docsMap.update = docs;
|
|
14
|
+
return docsMap;
|
|
15
|
+
});
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
full(entry) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Tool doc for <update>. Each entry: [text, rationale].
|
|
2
|
+
// Text goes to the model. Rationale stays in source.
|
|
3
|
+
// Changing ANY line requires reading ALL rationales first.
|
|
4
|
+
const LINES = [
|
|
5
|
+
// --- Syntax
|
|
6
|
+
["## <update>[brief status]</update> - Signal continuation"],
|
|
7
|
+
|
|
8
|
+
// --- Examples: research progress and multi-step work
|
|
9
|
+
[
|
|
10
|
+
"Example: <update>Reading config files</update>",
|
|
11
|
+
"Progress checkpoint. Shows update as a status signal, not a log entry.",
|
|
12
|
+
],
|
|
13
|
+
[
|
|
14
|
+
"Example: <update>Found 3 issues, fixing first</update>",
|
|
15
|
+
"Multi-step progress. Shows update for ongoing work.",
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
// --- Constraints: RFC-style MUST/MUST NOT
|
|
19
|
+
[
|
|
20
|
+
"* YOU MUST use <update> if still working — describes the current state",
|
|
21
|
+
"Continuation signal. Triggers the next turn in the loop.",
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"* YOU MUST NOT use <update> if done — use <summarize/> instead",
|
|
25
|
+
"Mutual exclusion with summarize. Prevents infinite loops.",
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
"* YOU MUST keep <update> to <= 80 characters",
|
|
29
|
+
"Length cap. Prevents models from writing essays in status updates.",
|
|
30
|
+
],
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export default LINES.map(([text]) => text).join("\n");
|
|
@@ -110,7 +110,7 @@ export default class ClientConnection {
|
|
|
110
110
|
|
|
111
111
|
try {
|
|
112
112
|
const logRow = await this.#db.log_rpc_call.get({
|
|
113
|
-
project_id: this.#context.projectId
|
|
113
|
+
project_id: this.#context.projectId ?? null,
|
|
114
114
|
method,
|
|
115
115
|
rpc_id: id,
|
|
116
116
|
params: params ? JSON.stringify(params) : null,
|
|
@@ -184,10 +184,8 @@ export default class ClientConnection {
|
|
|
184
184
|
} catch {}
|
|
185
185
|
}
|
|
186
186
|
} catch (error) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
console.error(`[DEBUG] Stack: ${error.stack}`);
|
|
190
|
-
}
|
|
187
|
+
console.error(`[RUMMY] RPC Error: ${error.message}`);
|
|
188
|
+
console.error(`[RUMMY] Stack: ${error.stack}`);
|
|
191
189
|
this.#send({
|
|
192
190
|
jsonrpc: "2.0",
|
|
193
191
|
error: { code: -32603, message: error.message },
|
|
@@ -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
|
-
|
|
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 };
|