@possumtech/rummy 0.5.0 → 2.0.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 +21 -5
- package/PLUGINS.md +389 -194
- package/README.md +25 -8
- package/SPEC.md +850 -373
- package/bin/demo.js +166 -0
- package/bin/rummy.js +9 -3
- package/biome/no-fallbacks.grit +50 -0
- package/lang/en.json +2 -2
- package/migrations/001_initial_schema.sql +88 -37
- package/package.json +6 -4
- package/service.js +50 -9
- package/src/agent/AgentLoop.js +460 -330
- package/src/agent/ContextAssembler.js +4 -4
- package/src/agent/Entries.js +655 -0
- package/src/agent/ProjectAgent.js +30 -18
- package/src/agent/TurnExecutor.js +229 -421
- package/src/agent/XmlParser.js +99 -33
- package/src/agent/budget.js +56 -0
- package/src/agent/errors.js +22 -0
- package/src/agent/httpStatus.js +39 -0
- package/src/agent/known_checks.sql +8 -4
- package/src/agent/known_queries.sql +9 -13
- package/src/agent/known_store.sql +275 -125
- package/src/agent/materializeContext.js +102 -0
- package/src/agent/runs.sql +10 -7
- package/src/agent/schemes.sql +14 -3
- package/src/agent/turns.sql +9 -9
- package/src/hooks/HookRegistry.js +6 -5
- package/src/hooks/Hooks.js +44 -3
- package/src/hooks/PluginContext.js +29 -21
- package/src/{server → hooks}/RpcRegistry.js +2 -1
- package/src/hooks/RummyContext.js +135 -35
- package/src/hooks/ToolRegistry.js +21 -16
- package/src/llm/LlmProvider.js +64 -90
- package/src/llm/errors.js +21 -0
- package/src/plugins/ask_user/README.md +1 -1
- package/src/plugins/ask_user/ask_user.js +37 -12
- package/src/plugins/ask_user/ask_userDoc.js +2 -25
- package/src/plugins/ask_user/ask_userDoc.md +10 -0
- package/src/plugins/budget/README.md +27 -25
- package/src/plugins/budget/budget.js +260 -88
- package/src/plugins/cp/README.md +2 -2
- package/src/plugins/cp/cp.js +29 -11
- package/src/plugins/cp/cpDoc.js +2 -15
- package/src/plugins/cp/cpDoc.md +7 -0
- package/src/plugins/engine/README.md +2 -2
- package/src/plugins/engine/engine.sql +4 -4
- package/src/plugins/engine/turn_context.sql +10 -10
- package/src/plugins/env/README.md +20 -5
- package/src/plugins/env/env.js +45 -6
- package/src/plugins/env/envDoc.js +2 -23
- package/src/plugins/env/envDoc.md +13 -0
- package/src/plugins/error/README.md +16 -0
- package/src/plugins/error/error.js +151 -0
- package/src/plugins/file/README.md +6 -6
- package/src/plugins/file/file.js +15 -2
- package/src/plugins/get/README.md +1 -1
- package/src/plugins/get/get.js +103 -48
- package/src/plugins/get/getDoc.js +2 -32
- package/src/plugins/get/getDoc.md +36 -0
- package/src/plugins/hedberg/README.md +1 -2
- package/src/plugins/hedberg/hedberg.js +8 -4
- package/src/plugins/hedberg/matcher.js +16 -17
- package/src/plugins/hedberg/normalize.js +0 -48
- package/src/plugins/helpers.js +42 -2
- package/src/plugins/index.js +146 -123
- package/src/plugins/instructions/README.md +35 -9
- package/src/plugins/instructions/instructions.js +122 -9
- package/src/plugins/instructions/instructions.md +25 -0
- package/src/plugins/instructions/instructions_104.md +7 -0
- package/src/plugins/instructions/instructions_105.md +46 -0
- package/src/plugins/instructions/instructions_106.md +0 -0
- package/src/plugins/instructions/instructions_107.md +0 -0
- package/src/plugins/instructions/instructions_108.md +8 -0
- package/src/plugins/instructions/protocol.js +12 -0
- package/src/plugins/known/README.md +2 -2
- package/src/plugins/known/known.js +67 -36
- package/src/plugins/known/knownDoc.js +2 -17
- package/src/plugins/known/knownDoc.md +8 -0
- package/src/plugins/log/README.md +48 -0
- package/src/plugins/log/log.js +109 -0
- package/src/plugins/mv/README.md +2 -2
- package/src/plugins/mv/mv.js +55 -22
- package/src/plugins/mv/mvDoc.js +2 -18
- package/src/plugins/mv/mvDoc.md +10 -0
- package/src/plugins/ollama/README.md +15 -0
- package/src/{llm/OllamaClient.js → plugins/ollama/ollama.js} +40 -18
- package/src/plugins/openai/README.md +17 -0
- package/src/plugins/openai/openai.js +120 -0
- package/src/plugins/openrouter/README.md +27 -0
- package/src/plugins/openrouter/openrouter.js +121 -0
- package/src/plugins/persona/README.md +20 -0
- package/src/plugins/persona/persona.js +9 -16
- package/src/plugins/policy/README.md +21 -0
- package/src/plugins/policy/policy.js +29 -14
- package/src/plugins/prompt/README.md +1 -1
- package/src/plugins/prompt/prompt.js +58 -16
- package/src/plugins/rm/README.md +1 -1
- package/src/plugins/rm/rm.js +56 -12
- package/src/plugins/rm/rmDoc.js +2 -20
- package/src/plugins/rm/rmDoc.md +13 -0
- package/src/plugins/rpc/README.md +2 -2
- package/src/plugins/rpc/rpc.js +515 -296
- package/src/plugins/set/README.md +1 -1
- package/src/plugins/set/set.js +318 -75
- package/src/plugins/set/setDoc.js +2 -35
- package/src/plugins/set/setDoc.md +22 -0
- package/src/plugins/sh/README.md +28 -5
- package/src/plugins/sh/sh.js +50 -6
- package/src/plugins/sh/shDoc.js +2 -23
- package/src/plugins/sh/shDoc.md +13 -0
- package/src/plugins/skill/README.md +23 -0
- package/src/plugins/skill/skill.js +14 -18
- package/src/plugins/stream/README.md +101 -0
- package/src/plugins/stream/stream.js +290 -0
- package/src/plugins/telemetry/README.md +1 -1
- package/src/plugins/telemetry/telemetry.js +129 -80
- package/src/plugins/think/README.md +1 -1
- package/src/plugins/think/think.js +12 -0
- package/src/plugins/think/thinkDoc.js +2 -15
- package/src/plugins/think/thinkDoc.md +7 -0
- package/src/plugins/unknown/README.md +3 -3
- package/src/plugins/unknown/unknown.js +47 -19
- package/src/plugins/unknown/unknownDoc.js +2 -21
- package/src/plugins/unknown/unknownDoc.md +11 -0
- package/src/plugins/update/README.md +1 -1
- package/src/plugins/update/update.js +67 -5
- package/src/plugins/update/updateDoc.js +2 -30
- package/src/plugins/update/updateDoc.md +8 -0
- package/src/plugins/xai/README.md +23 -0
- package/src/{llm/XaiClient.js → plugins/xai/xai.js} +58 -37
- package/src/server/ClientConnection.js +64 -37
- package/src/server/SocketServer.js +23 -10
- package/src/server/protocol.js +11 -0
- package/src/sql/v_model_context.sql +27 -31
- package/src/sql/v_run_log.sql +9 -14
- package/EXCEPTIONS.md +0 -46
- package/FIDELITY_CONTRACT.md +0 -172
- package/src/agent/KnownStore.js +0 -337
- package/src/agent/ResponseHealer.js +0 -241
- package/src/llm/OpenAiClient.js +0 -100
- package/src/llm/OpenRouterClient.js +0 -100
- package/src/plugins/budget/recovery.js +0 -47
- package/src/plugins/instructions/preamble.md +0 -45
- package/src/plugins/performed/README.md +0 -15
- package/src/plugins/performed/performed.js +0 -45
- package/src/plugins/previous/README.md +0 -16
- package/src/plugins/previous/previous.js +0 -56
- package/src/plugins/progress/README.md +0 -16
- package/src/plugins/progress/progress.js +0 -43
- package/src/plugins/summarize/README.md +0 -19
- package/src/plugins/summarize/summarize.js +0 -32
- package/src/plugins/summarize/summarizeDoc.js +0 -27
|
@@ -29,9 +29,8 @@ export default class HookRegistry {
|
|
|
29
29
|
await p.callback(rummy);
|
|
30
30
|
if (this.#debug) {
|
|
31
31
|
const duration = (performance.now() - start).toFixed(2);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
);
|
|
32
|
+
const name = p.callback.name ? p.callback.name : "anonymous";
|
|
33
|
+
console.log(`[PIPELINE] Processor ${name} took ${duration}ms`);
|
|
35
34
|
}
|
|
36
35
|
}
|
|
37
36
|
}
|
|
@@ -46,7 +45,8 @@ export default class HookRegistry {
|
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
async applyFilters(tag, value, ...args) {
|
|
49
|
-
const hooks = this.#filters.get(tag)
|
|
48
|
+
const hooks = this.#filters.get(tag);
|
|
49
|
+
if (!hooks) return value;
|
|
50
50
|
let result = value;
|
|
51
51
|
for (const h of hooks) {
|
|
52
52
|
result = await h.callback(result, ...args);
|
|
@@ -71,7 +71,8 @@ export default class HookRegistry {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
async emitEvent(tag, ...args) {
|
|
74
|
-
const hooks = this.#events.get(tag)
|
|
74
|
+
const hooks = this.#events.get(tag);
|
|
75
|
+
if (!hooks) return;
|
|
75
76
|
for (const h of hooks) {
|
|
76
77
|
await h.callback(...args);
|
|
77
78
|
}
|
package/src/hooks/Hooks.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import HookRegistry from "./HookRegistry.js";
|
|
2
|
+
import RpcRegistry from "./RpcRegistry.js";
|
|
2
3
|
import ToolRegistry from "./ToolRegistry.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -56,10 +57,25 @@ export default function createHooks(debug = false) {
|
|
|
56
57
|
turn: {
|
|
57
58
|
started: createEvent("turn.started"),
|
|
58
59
|
response: createEvent("turn.response"),
|
|
59
|
-
proposal: createEvent("turn.proposal"),
|
|
60
|
-
proposing: createEvent("turn.proposing"),
|
|
61
60
|
completed: createEvent("turn.completed"),
|
|
62
61
|
},
|
|
62
|
+
proposal: {
|
|
63
|
+
prepare: createEvent("proposal.prepare"),
|
|
64
|
+
pending: createEvent("proposal.pending"),
|
|
65
|
+
// Plugins veto acceptance by returning {allow:false, outcome, body}.
|
|
66
|
+
// Used e.g. by set plugin's readonly constraint check.
|
|
67
|
+
accepting: createFilter("proposal.accepting"),
|
|
68
|
+
// Plugins compose the resolved body based on path/action. Default
|
|
69
|
+
// is output || "". Used e.g. by set plugin to preserve the
|
|
70
|
+
// model's proposed content as the resolved body.
|
|
71
|
+
content: createFilter("proposal.content"),
|
|
72
|
+
// Fires after a proposal resolves with action="accept". Plugins
|
|
73
|
+
// perform their side effects (file materialize, unlink, stream
|
|
74
|
+
// setup, etc.) here — NOT in AgentLoop.resolve.
|
|
75
|
+
accepted: createEvent("proposal.accepted"),
|
|
76
|
+
// Fires after a proposal resolves with action="error" or "reject".
|
|
77
|
+
rejected: createEvent("proposal.rejected"),
|
|
78
|
+
},
|
|
63
79
|
assembly: {
|
|
64
80
|
system: createFilter("assembly.system"),
|
|
65
81
|
user: createFilter("assembly.user"),
|
|
@@ -82,6 +98,25 @@ export default function createHooks(debug = false) {
|
|
|
82
98
|
},
|
|
83
99
|
messages: createFilter("llm.messages"),
|
|
84
100
|
response: createFilter("llm.response"),
|
|
101
|
+
// Reasoning merge filter. Subscribers contribute per-tag
|
|
102
|
+
// reasoning text (e.g. the think plugin's <think>…</think>)
|
|
103
|
+
// to the model's reasoning_content field. Fires between parse
|
|
104
|
+
// and turn.response.
|
|
105
|
+
reasoning: createFilter("llm.reasoning"),
|
|
106
|
+
// LLM provider registry. Plugins contribute entries shaped:
|
|
107
|
+
// {
|
|
108
|
+
// name: string,
|
|
109
|
+
// matches: (modelAlias) => boolean,
|
|
110
|
+
// completion: (messages, modelAlias, options) => Promise<response>,
|
|
111
|
+
// getContextSize: (modelAlias) => Promise<number>,
|
|
112
|
+
// }
|
|
113
|
+
// Each provider owns a prefix namespace (e.g. "openai/", "ollama/",
|
|
114
|
+
// "openrouter/"). LlmProvider picks the first provider whose
|
|
115
|
+
// matches() returns true. No catchall — if a model alias doesn't
|
|
116
|
+
// match any registered provider, the request fails with a clear
|
|
117
|
+
// "no provider registered" error. External plugins add new
|
|
118
|
+
// prefixes without namespace collision.
|
|
119
|
+
providers: [],
|
|
85
120
|
},
|
|
86
121
|
file: {},
|
|
87
122
|
prompt: {
|
|
@@ -100,6 +135,12 @@ export default function createHooks(debug = false) {
|
|
|
100
135
|
materialized: createEvent("context.materialized"),
|
|
101
136
|
},
|
|
102
137
|
action: {},
|
|
138
|
+
error: {
|
|
139
|
+
log: createEvent("error.log"),
|
|
140
|
+
},
|
|
141
|
+
stream: {
|
|
142
|
+
cancelled: createEvent("stream.cancelled"),
|
|
143
|
+
},
|
|
103
144
|
ui: {
|
|
104
145
|
render: createEvent("ui.render"),
|
|
105
146
|
notify: createEvent("ui.notify"),
|
|
@@ -117,7 +158,7 @@ export default function createHooks(debug = false) {
|
|
|
117
158
|
response: {
|
|
118
159
|
result: createFilter("rpc.response.result"),
|
|
119
160
|
},
|
|
120
|
-
registry:
|
|
161
|
+
registry: new RpcRegistry(),
|
|
121
162
|
},
|
|
122
163
|
agent: {},
|
|
123
164
|
tools,
|
|
@@ -10,8 +10,6 @@
|
|
|
10
10
|
export default class PluginContext {
|
|
11
11
|
#name;
|
|
12
12
|
#hooks;
|
|
13
|
-
#db = null;
|
|
14
|
-
#store = null;
|
|
15
13
|
|
|
16
14
|
constructor(name, hooks) {
|
|
17
15
|
this.#name = name;
|
|
@@ -22,22 +20,6 @@ export default class PluginContext {
|
|
|
22
20
|
return this.#name;
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
get db() {
|
|
26
|
-
return this.#db;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
set db(value) {
|
|
30
|
-
this.#db = value;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
get entries() {
|
|
34
|
-
return this.#store;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
set entries(value) {
|
|
38
|
-
this.#store = value;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
23
|
#schemes = [];
|
|
42
24
|
|
|
43
25
|
get hooks() {
|
|
@@ -48,16 +30,36 @@ export default class PluginContext {
|
|
|
48
30
|
return this.#schemes;
|
|
49
31
|
}
|
|
50
32
|
|
|
51
|
-
registerScheme({
|
|
33
|
+
registerScheme({
|
|
34
|
+
name,
|
|
35
|
+
modelVisible = 1,
|
|
36
|
+
category = "logging",
|
|
37
|
+
scope = "run",
|
|
38
|
+
writableBy = ["model", "plugin"],
|
|
39
|
+
} = {}) {
|
|
52
40
|
if (!PluginContext.CATEGORIES.has(category)) {
|
|
53
41
|
throw new Error(
|
|
54
42
|
`Invalid category "${category}". Must be one of: ${[...PluginContext.CATEGORIES].join(", ")}`,
|
|
55
43
|
);
|
|
56
44
|
}
|
|
45
|
+
if (!PluginContext.SCOPES.has(scope)) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Invalid scope "${scope}". Must be one of: ${[...PluginContext.SCOPES].join(", ")}`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
for (const w of writableBy) {
|
|
51
|
+
if (!PluginContext.WRITERS.has(w)) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Invalid writer "${w}" in writableBy. Must be one of: ${[...PluginContext.WRITERS].join(", ")}`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
57
|
this.#schemes.push({
|
|
58
58
|
name: name || this.#name,
|
|
59
59
|
model_visible: modelVisible,
|
|
60
60
|
category,
|
|
61
|
+
default_scope: scope,
|
|
62
|
+
writable_by: JSON.stringify(writableBy),
|
|
61
63
|
});
|
|
62
64
|
}
|
|
63
65
|
|
|
@@ -65,6 +67,12 @@ export default class PluginContext {
|
|
|
65
67
|
new Set(["data", "logging", "unknown", "prompt"]),
|
|
66
68
|
);
|
|
67
69
|
|
|
70
|
+
static SCOPES = Object.freeze(new Set(["run", "project", "global"]));
|
|
71
|
+
|
|
72
|
+
static WRITERS = Object.freeze(
|
|
73
|
+
new Set(["model", "plugin", "client", "system"]),
|
|
74
|
+
);
|
|
75
|
+
|
|
68
76
|
ensureTool() {
|
|
69
77
|
this.#hooks.tools.ensureTool(this.#name);
|
|
70
78
|
}
|
|
@@ -78,7 +86,7 @@ export default class PluginContext {
|
|
|
78
86
|
/**
|
|
79
87
|
* Register a named callback for this plugin.
|
|
80
88
|
* "handler" registers the tool handler.
|
|
81
|
-
* "
|
|
89
|
+
* "visible"/"summarized" register visibility projections.
|
|
82
90
|
* "docs" sets tool documentation.
|
|
83
91
|
* Everything else resolves to a hook event.
|
|
84
92
|
*/
|
|
@@ -88,7 +96,7 @@ export default class PluginContext {
|
|
|
88
96
|
this.#hooks.tools.onHandle(this.#name, callback, priority);
|
|
89
97
|
return;
|
|
90
98
|
}
|
|
91
|
-
if (event === "
|
|
99
|
+
if (event === "visible" || event === "summarized") {
|
|
92
100
|
this.#hooks.tools.onView(this.#name, callback, event);
|
|
93
101
|
return;
|
|
94
102
|
}
|
|
@@ -57,7 +57,8 @@ export default class RpcRegistry {
|
|
|
57
57
|
if (!params.path) throw new Error("path is required");
|
|
58
58
|
if (!params.run) throw new Error("run is required");
|
|
59
59
|
const { rummy } = await buildRunContext(hooks, ctx, params.run);
|
|
60
|
-
|
|
60
|
+
const { body = "" } = params;
|
|
61
|
+
await dispatchTool(hooks, rummy, name, params.path, body, {
|
|
61
62
|
path: params.path,
|
|
62
63
|
to: params.to,
|
|
63
64
|
...params.attributes,
|
|
@@ -2,17 +2,40 @@
|
|
|
2
2
|
* RummyContext provides a unified, semantic API for plugins to interact with
|
|
3
3
|
* the Turn node tree and core resources like the Database and Project metadata.
|
|
4
4
|
*/
|
|
5
|
+
// Entries write verbs that should automatically carry the caller's
|
|
6
|
+
// writer identity. Handler-issued writes on behalf of the model default
|
|
7
|
+
// to writer=model; plugin background writes (set via rummy from a hook
|
|
8
|
+
// with writer: "plugin" or "system" in ctx) get the context's writer.
|
|
9
|
+
const WRITE_VERBS = new Set(["set", "rm", "cp", "mv", "update"]);
|
|
10
|
+
|
|
11
|
+
// Defaults applied at construction so every plugin-facing getter
|
|
12
|
+
// returns a predictable shape without per-access fallbacks.
|
|
13
|
+
const CONTEXT_DEFAULTS = Object.freeze({
|
|
14
|
+
hooks: null,
|
|
15
|
+
activeFiles: [],
|
|
16
|
+
sequence: 0,
|
|
17
|
+
runId: null,
|
|
18
|
+
turnId: null,
|
|
19
|
+
loopId: null,
|
|
20
|
+
toolSet: null,
|
|
21
|
+
contextSize: null,
|
|
22
|
+
systemPrompt: "",
|
|
23
|
+
loopPrompt: "",
|
|
24
|
+
writer: "model",
|
|
25
|
+
});
|
|
26
|
+
|
|
5
27
|
export default class RummyContext {
|
|
6
28
|
#root;
|
|
7
29
|
#context;
|
|
30
|
+
#wrappedStore;
|
|
8
31
|
|
|
9
32
|
constructor(root, context) {
|
|
10
33
|
this.#root = root;
|
|
11
|
-
this.#context = context;
|
|
34
|
+
this.#context = { ...CONTEXT_DEFAULTS, ...context };
|
|
12
35
|
}
|
|
13
36
|
|
|
14
37
|
get hooks() {
|
|
15
|
-
return this.#context.hooks
|
|
38
|
+
return this.#context.hooks;
|
|
16
39
|
}
|
|
17
40
|
|
|
18
41
|
get db() {
|
|
@@ -20,7 +43,19 @@ export default class RummyContext {
|
|
|
20
43
|
}
|
|
21
44
|
|
|
22
45
|
get entries() {
|
|
23
|
-
|
|
46
|
+
if (this.#wrappedStore) return this.#wrappedStore;
|
|
47
|
+
const store = this.#context.store;
|
|
48
|
+
if (!store) return null;
|
|
49
|
+
const writer = this.writer;
|
|
50
|
+
this.#wrappedStore = new Proxy(store, {
|
|
51
|
+
get(target, prop) {
|
|
52
|
+
const val = target[prop];
|
|
53
|
+
if (typeof val !== "function") return val;
|
|
54
|
+
if (!WRITE_VERBS.has(prop)) return val.bind(target);
|
|
55
|
+
return (args = {}) => val.call(target, { writer, ...args });
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
return this.#wrappedStore;
|
|
24
59
|
}
|
|
25
60
|
|
|
26
61
|
get project() {
|
|
@@ -28,7 +63,7 @@ export default class RummyContext {
|
|
|
28
63
|
}
|
|
29
64
|
|
|
30
65
|
get activeFiles() {
|
|
31
|
-
return this.#context.activeFiles
|
|
66
|
+
return this.#context.activeFiles;
|
|
32
67
|
}
|
|
33
68
|
|
|
34
69
|
get type() {
|
|
@@ -40,19 +75,19 @@ export default class RummyContext {
|
|
|
40
75
|
}
|
|
41
76
|
|
|
42
77
|
get sequence() {
|
|
43
|
-
return this.#context.sequence
|
|
78
|
+
return this.#context.sequence;
|
|
44
79
|
}
|
|
45
80
|
|
|
46
81
|
get runId() {
|
|
47
|
-
return this.#context.runId
|
|
82
|
+
return this.#context.runId;
|
|
48
83
|
}
|
|
49
84
|
|
|
50
85
|
get turnId() {
|
|
51
|
-
return this.#context.turnId
|
|
86
|
+
return this.#context.turnId;
|
|
52
87
|
}
|
|
53
88
|
|
|
54
89
|
get loopId() {
|
|
55
|
-
return this.#context.loopId
|
|
90
|
+
return this.#context.loopId;
|
|
56
91
|
}
|
|
57
92
|
|
|
58
93
|
get noRepo() {
|
|
@@ -67,20 +102,34 @@ export default class RummyContext {
|
|
|
67
102
|
return this.#context.noWeb === true;
|
|
68
103
|
}
|
|
69
104
|
|
|
105
|
+
get noProposals() {
|
|
106
|
+
return this.#context.noProposals === true;
|
|
107
|
+
}
|
|
108
|
+
|
|
70
109
|
get toolSet() {
|
|
71
|
-
return this.#context.toolSet
|
|
110
|
+
return this.#context.toolSet;
|
|
72
111
|
}
|
|
73
112
|
|
|
74
113
|
get contextSize() {
|
|
75
|
-
return this.#context.contextSize
|
|
114
|
+
return this.#context.contextSize;
|
|
76
115
|
}
|
|
77
116
|
|
|
78
117
|
get systemPrompt() {
|
|
79
|
-
return this.#context.systemPrompt
|
|
118
|
+
return this.#context.systemPrompt;
|
|
80
119
|
}
|
|
81
120
|
|
|
82
121
|
get loopPrompt() {
|
|
83
|
-
return this.#context.loopPrompt
|
|
122
|
+
return this.#context.loopPrompt;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Writer identity for Entries permission checks. Defaults to
|
|
127
|
+
* 'model' — handlers write on behalf of the model's emitted command.
|
|
128
|
+
* Non-handler plugin code (streaming callbacks, background emissions)
|
|
129
|
+
* passes `writer: 'plugin'` or `'system'` explicitly.
|
|
130
|
+
*/
|
|
131
|
+
get writer() {
|
|
132
|
+
return this.#context.writer;
|
|
84
133
|
}
|
|
85
134
|
|
|
86
135
|
get system() {
|
|
@@ -101,51 +150,83 @@ export default class RummyContext {
|
|
|
101
150
|
|
|
102
151
|
// --- Tool methods (same operations the model uses) ---
|
|
103
152
|
|
|
104
|
-
async set({
|
|
153
|
+
async set({
|
|
154
|
+
path,
|
|
155
|
+
body = "",
|
|
156
|
+
state = "resolved",
|
|
157
|
+
outcome = null,
|
|
158
|
+
visibility,
|
|
159
|
+
attributes,
|
|
160
|
+
} = {}) {
|
|
105
161
|
if (!path) {
|
|
106
162
|
path = await this.entries.slugPath(
|
|
107
163
|
this.runId,
|
|
108
164
|
"known",
|
|
109
|
-
body
|
|
165
|
+
body,
|
|
110
166
|
attributes?.summary,
|
|
111
167
|
);
|
|
112
168
|
}
|
|
113
|
-
await this.entries.
|
|
114
|
-
this.runId,
|
|
115
|
-
this.sequence,
|
|
169
|
+
await this.entries.set({
|
|
170
|
+
runId: this.runId,
|
|
171
|
+
turn: this.sequence,
|
|
116
172
|
path,
|
|
117
|
-
body
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
173
|
+
body,
|
|
174
|
+
state,
|
|
175
|
+
outcome,
|
|
176
|
+
visibility,
|
|
177
|
+
attributes,
|
|
178
|
+
loopId: this.loopId,
|
|
179
|
+
});
|
|
121
180
|
return path;
|
|
122
181
|
}
|
|
123
182
|
|
|
124
183
|
async get(path) {
|
|
125
|
-
await this.entries.
|
|
184
|
+
await this.entries.get({
|
|
185
|
+
runId: this.runId,
|
|
186
|
+
turn: this.sequence,
|
|
187
|
+
path: path,
|
|
188
|
+
bodyFilter: null,
|
|
189
|
+
});
|
|
126
190
|
}
|
|
127
191
|
|
|
128
|
-
async
|
|
129
|
-
await this.entries.
|
|
192
|
+
async rm(path) {
|
|
193
|
+
await this.entries.rm({ runId: this.runId, path: path });
|
|
130
194
|
}
|
|
131
195
|
|
|
132
|
-
async
|
|
133
|
-
|
|
196
|
+
async update(body, { status = 102, attributes = {} } = {}) {
|
|
197
|
+
return this.entries.update({
|
|
198
|
+
runId: this.runId,
|
|
199
|
+
turn: this.sequence,
|
|
200
|
+
body,
|
|
201
|
+
status,
|
|
202
|
+
attributes,
|
|
203
|
+
loopId: this.loopId,
|
|
204
|
+
});
|
|
134
205
|
}
|
|
135
206
|
|
|
136
207
|
async mv(from, to) {
|
|
137
208
|
const body = await this.entries.getBody(this.runId, from);
|
|
138
209
|
if (body === null) return;
|
|
139
|
-
await this.entries.
|
|
210
|
+
await this.entries.set({
|
|
211
|
+
runId: this.runId,
|
|
212
|
+
turn: this.sequence,
|
|
213
|
+
path: to,
|
|
214
|
+
body,
|
|
215
|
+
state: "resolved",
|
|
140
216
|
loopId: this.loopId,
|
|
141
217
|
});
|
|
142
|
-
await this.entries.
|
|
218
|
+
await this.entries.rm({ runId: this.runId, path: from });
|
|
143
219
|
}
|
|
144
220
|
|
|
145
221
|
async cp(from, to) {
|
|
146
222
|
const body = await this.entries.getBody(this.runId, from);
|
|
147
223
|
if (body === null) return;
|
|
148
|
-
await this.entries.
|
|
224
|
+
await this.entries.set({
|
|
225
|
+
runId: this.runId,
|
|
226
|
+
turn: this.sequence,
|
|
227
|
+
path: to,
|
|
228
|
+
body,
|
|
229
|
+
state: "resolved",
|
|
149
230
|
loopId: this.loopId,
|
|
150
231
|
});
|
|
151
232
|
}
|
|
@@ -160,9 +241,16 @@ export default class RummyContext {
|
|
|
160
241
|
return this.entries.getAttributes(this.runId, path);
|
|
161
242
|
}
|
|
162
243
|
|
|
163
|
-
async
|
|
244
|
+
async getState(path) {
|
|
164
245
|
const row = await this.entries.getState(this.runId, path);
|
|
165
|
-
|
|
246
|
+
if (!row) return null;
|
|
247
|
+
return row.state;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async getOutcome(path) {
|
|
251
|
+
const row = await this.entries.getState(this.runId, path);
|
|
252
|
+
if (!row) return null;
|
|
253
|
+
return row.outcome;
|
|
166
254
|
}
|
|
167
255
|
|
|
168
256
|
async getEntry(path) {
|
|
@@ -171,11 +259,16 @@ export default class RummyContext {
|
|
|
171
259
|
path,
|
|
172
260
|
null,
|
|
173
261
|
);
|
|
174
|
-
|
|
262
|
+
if (results.length === 0) return null;
|
|
263
|
+
return results[0];
|
|
175
264
|
}
|
|
176
265
|
|
|
177
266
|
async setAttributes(path, attrs) {
|
|
178
|
-
return this.entries.
|
|
267
|
+
return this.entries.set({
|
|
268
|
+
runId: this.runId,
|
|
269
|
+
path: path,
|
|
270
|
+
attributes: attrs,
|
|
271
|
+
});
|
|
179
272
|
}
|
|
180
273
|
|
|
181
274
|
async getEntries(pattern, bodyFilter) {
|
|
@@ -184,7 +277,13 @@ export default class RummyContext {
|
|
|
184
277
|
|
|
185
278
|
async log(message) {
|
|
186
279
|
const path = `content://${Date.now()}`;
|
|
187
|
-
await this.entries.
|
|
280
|
+
await this.entries.set({
|
|
281
|
+
runId: this.runId,
|
|
282
|
+
turn: this.sequence,
|
|
283
|
+
path,
|
|
284
|
+
body: message,
|
|
285
|
+
state: "resolved",
|
|
286
|
+
});
|
|
188
287
|
}
|
|
189
288
|
|
|
190
289
|
// --- Node tree methods ---
|
|
@@ -194,7 +293,8 @@ export default class RummyContext {
|
|
|
194
293
|
const childArray = Array.isArray(children) ? children : [children];
|
|
195
294
|
for (const child of childArray) {
|
|
196
295
|
if (typeof child === "string") {
|
|
197
|
-
node.content
|
|
296
|
+
if (node.content === null) node.content = "";
|
|
297
|
+
node.content += child;
|
|
198
298
|
} else if (child && typeof child === "object") {
|
|
199
299
|
node.children.push(child);
|
|
200
300
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Tool display order: gather → reason → act → communicate.
|
|
2
2
|
// Position in the list implies priority to the model.
|
|
3
|
+
// `update` is pinned last — it's the turn-closer, not an action.
|
|
3
4
|
const TOOL_ORDER = [
|
|
4
5
|
"think",
|
|
5
6
|
"unknown",
|
|
@@ -12,18 +13,18 @@ const TOOL_ORDER = [
|
|
|
12
13
|
"cp",
|
|
13
14
|
"mv",
|
|
14
15
|
"ask_user",
|
|
15
|
-
"update",
|
|
16
|
-
"summarize",
|
|
17
16
|
"search",
|
|
18
17
|
];
|
|
19
18
|
|
|
20
19
|
function sortByPriority(names) {
|
|
21
20
|
return names.toSorted((a, b) => {
|
|
21
|
+
if (a === "update") return 1;
|
|
22
|
+
if (b === "update") return -1;
|
|
22
23
|
const ia = TOOL_ORDER.indexOf(a);
|
|
23
24
|
const ib = TOOL_ORDER.indexOf(b);
|
|
24
25
|
if (ia === -1 && ib === -1) return a.localeCompare(b);
|
|
25
26
|
if (ia === -1) return 1;
|
|
26
|
-
if (ib === -1) return 1;
|
|
27
|
+
if (ib === -1) return -1;
|
|
27
28
|
return ia - ib;
|
|
28
29
|
});
|
|
29
30
|
}
|
|
@@ -39,10 +40,9 @@ export default class ToolRegistry {
|
|
|
39
40
|
this.#tools.set(scheme, Object.freeze({}));
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
// without deleting.
|
|
43
|
+
// Hidden tools dispatch on direct emission but don't appear in any
|
|
44
|
+
// model-facing tool list. Internal schemes (e.g. <known>, <unknown>)
|
|
45
|
+
// the model writes via <set path="scheme://..."> instead.
|
|
46
46
|
markHidden(scheme) {
|
|
47
47
|
this.#hidden.add(scheme);
|
|
48
48
|
}
|
|
@@ -62,30 +62,35 @@ export default class ToolRegistry {
|
|
|
62
62
|
list.sort((a, b) => a.priority - b.priority);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
onView(scheme, fn,
|
|
65
|
+
onView(scheme, fn, visibility = "visible") {
|
|
66
66
|
if (!this.#views.has(scheme)) this.#views.set(scheme, new Map());
|
|
67
|
-
this.#views.get(scheme).set(
|
|
67
|
+
this.#views.get(scheme).set(visibility, fn);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
async view(scheme, entry) {
|
|
71
|
-
const
|
|
72
|
-
if (!
|
|
71
|
+
const visibilityMap = this.#views.get(scheme);
|
|
72
|
+
if (!visibilityMap) {
|
|
73
73
|
throw new Error(
|
|
74
74
|
`No view registered for scheme '${scheme}'. ` +
|
|
75
75
|
`Every tool must define how its entries appear in the model view.`,
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
const
|
|
80
|
-
|
|
79
|
+
const visibility =
|
|
80
|
+
entry.visibility === undefined ? "visible" : entry.visibility;
|
|
81
|
+
const fn = visibilityMap.get(visibility);
|
|
81
82
|
if (!fn) return "";
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
const body = await fn(entry);
|
|
85
|
+
// View handlers MAY return undefined or null to mean "no projected
|
|
86
|
+
// body at this visibility" — normalize at this boundary so callers
|
|
87
|
+
// get a predictable string.
|
|
88
|
+
return body == null ? "" : body;
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
hasView(scheme) {
|
|
87
|
-
const
|
|
88
|
-
return
|
|
92
|
+
const visibilityMap = this.#views.get(scheme);
|
|
93
|
+
return visibilityMap?.size > 0;
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
async dispatch(scheme, entry, rummy) {
|