@possumtech/rummy 2.0.0 → 2.1.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 +31 -5
- package/BENCH_ENVIRONMENT.md +230 -0
- package/CLIENT_INTERFACE.md +396 -0
- package/PLUGINS.md +93 -1
- package/SPEC.md +389 -28
- package/bin/postinstall.js +2 -2
- package/bin/rummy.js +2 -2
- package/last_run.txt +5617 -0
- package/migrations/001_initial_schema.sql +2 -1
- package/package.json +13 -9
- package/scriptify/ask_run.js +77 -0
- package/scriptify/cache_probe.js +66 -0
- package/scriptify/cache_probe_grok.js +74 -0
- package/service.js +22 -11
- package/src/agent/AgentLoop.js +62 -157
- package/src/agent/ContextAssembler.js +2 -9
- package/src/agent/Entries.js +54 -98
- package/src/agent/ProjectAgent.js +4 -11
- package/src/agent/TurnExecutor.js +48 -83
- package/src/agent/XmlParser.js +247 -273
- package/src/agent/budget.js +5 -28
- package/src/agent/config.js +38 -0
- package/src/agent/errors.js +7 -13
- package/src/agent/httpStatus.js +1 -19
- package/src/agent/known_queries.sql +1 -1
- package/src/agent/known_store.sql +12 -2
- package/src/agent/materializeContext.js +15 -18
- package/src/agent/pathEncode.js +5 -0
- package/src/agent/rummyHome.js +9 -0
- package/src/agent/runs.sql +37 -0
- package/src/agent/tokens.js +7 -7
- package/src/hooks/HookRegistry.js +1 -16
- package/src/hooks/Hooks.js +8 -33
- package/src/hooks/PluginContext.js +3 -21
- package/src/hooks/RpcRegistry.js +1 -4
- package/src/hooks/RummyContext.js +6 -16
- package/src/hooks/ToolRegistry.js +5 -15
- package/src/llm/LlmProvider.js +41 -33
- package/src/llm/errors.js +41 -4
- package/src/llm/openaiStream.js +125 -0
- package/src/llm/retry.js +109 -0
- package/src/plugins/budget/budget.js +55 -76
- package/src/plugins/cli/README.md +87 -0
- package/src/plugins/cli/bin.js +61 -0
- package/src/plugins/cli/cli.js +120 -0
- package/src/plugins/env/README.md +2 -1
- package/src/plugins/env/env.js +4 -6
- package/src/plugins/env/envDoc.md +2 -2
- package/src/plugins/error/error.js +23 -23
- package/src/plugins/file/file.js +2 -22
- package/src/plugins/get/get.js +12 -34
- package/src/plugins/get/getDoc.md +8 -6
- package/src/plugins/hedberg/edits.js +1 -11
- package/src/plugins/hedberg/hedberg.js +3 -26
- package/src/plugins/hedberg/normalize.js +1 -5
- package/src/plugins/hedberg/patterns.js +4 -15
- package/src/plugins/hedberg/sed.js +1 -7
- package/src/plugins/helpers.js +28 -20
- package/src/plugins/index.js +25 -41
- package/src/plugins/instructions/README.md +18 -0
- package/src/plugins/instructions/instructions.js +97 -38
- package/src/plugins/instructions/instructions.md +24 -15
- package/src/plugins/instructions/instructions_104.md +5 -4
- package/src/plugins/instructions/instructions_105.md +29 -36
- package/src/plugins/instructions/instructions_106.md +22 -0
- package/src/plugins/instructions/instructions_107.md +17 -0
- package/src/plugins/instructions/instructions_108.md +0 -8
- package/src/plugins/known/README.md +26 -6
- package/src/plugins/known/known.js +37 -34
- package/src/plugins/log/README.md +2 -2
- package/src/plugins/log/log.js +27 -34
- package/src/plugins/ollama/ollama.js +50 -66
- package/src/plugins/openai/openai.js +26 -44
- package/src/plugins/openrouter/openrouter.js +28 -52
- package/src/plugins/policy/README.md +8 -2
- package/src/plugins/policy/policy.js +8 -21
- package/src/plugins/prompt/README.md +22 -0
- package/src/plugins/prompt/prompt.js +14 -16
- package/src/plugins/rm/rm.js +5 -2
- package/src/plugins/rm/rmDoc.md +4 -4
- package/src/plugins/rpc/README.md +2 -1
- package/src/plugins/rpc/rpc.js +62 -48
- package/src/plugins/set/README.md +5 -1
- package/src/plugins/set/set.js +23 -33
- package/src/plugins/set/setDoc.md +1 -1
- package/src/plugins/sh/README.md +2 -1
- package/src/plugins/sh/sh.js +5 -11
- package/src/plugins/sh/shDoc.md +2 -2
- package/src/plugins/stream/README.md +6 -5
- package/src/plugins/stream/stream.js +6 -35
- package/src/plugins/telemetry/telemetry.js +26 -19
- package/src/plugins/think/think.js +4 -7
- package/src/plugins/unknown/unknown.js +8 -13
- package/src/plugins/update/update.js +42 -25
- package/src/plugins/update/updateDoc.md +3 -3
- package/src/plugins/xai/xai.js +30 -20
- package/src/plugins/yolo/yolo.js +159 -0
- package/src/server/ClientConnection.js +17 -47
- package/src/server/SocketServer.js +14 -14
- package/src/server/protocol.js +1 -10
- package/src/sql/functions/slugify.js +5 -7
- package/src/sql/v_model_context.sql +4 -11
- package/turns/cli_1777462658211/turn_001.txt +772 -0
- package/turns/cli_1777462658211/turn_002.txt +606 -0
- package/turns/cli_1777462658211/turn_003.txt +667 -0
- package/turns/cli_1777462658211/turn_004.txt +297 -0
- package/turns/cli_1777462658211/turn_005.txt +301 -0
- package/turns/cli_1777462658211/turn_006.txt +262 -0
- package/turns/cli_1777465095132/turn_001.txt +715 -0
- package/turns/cli_1777465095132/turn_002.txt +236 -0
- package/turns/cli_1777465095132/turn_003.txt +287 -0
- package/turns/cli_1777465095132/turn_004.txt +694 -0
- package/turns/cli_1777465095132/turn_005.txt +422 -0
- package/turns/cli_1777465095132/turn_006.txt +365 -0
- package/turns/cli_1777465095132/turn_007.txt +885 -0
- package/turns/cli_1777465095132/turn_008.txt +1277 -0
- package/turns/cli_1777465095132/turn_009.txt +736 -0
|
@@ -1,16 +1,12 @@
|
|
|
1
|
+
import config from "../../agent/config.js";
|
|
1
2
|
import msg from "../../agent/messages.js";
|
|
3
|
+
import { chatCompletionStream } from "../../llm/openaiStream.js";
|
|
2
4
|
|
|
3
|
-
const FETCH_TIMEOUT =
|
|
4
|
-
if (!FETCH_TIMEOUT) throw new Error("RUMMY_FETCH_TIMEOUT must be set");
|
|
5
|
+
const { FETCH_TIMEOUT } = config;
|
|
5
6
|
|
|
6
7
|
const PROVIDER = "openai";
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
* OpenAI-compatible LLM provider plugin. Registers with hooks.llm.providers
|
|
10
|
-
* if OPENAI_BASE_URL is set in env; silently inert otherwise. Handles
|
|
11
|
-
* model aliases of the form `openai/{modelName}` — the first path
|
|
12
|
-
* segment picks the provider, the rest is whatever the API expects.
|
|
13
|
-
*/
|
|
9
|
+
// Inert unless OPENAI_BASE_URL is set; openai/{model} aliases.
|
|
14
10
|
export default class OpenAi {
|
|
15
11
|
#baseUrl;
|
|
16
12
|
#apiKey;
|
|
@@ -42,47 +38,36 @@ export default class OpenAi {
|
|
|
42
38
|
? AbortSignal.any([options.signal, timeoutSignal])
|
|
43
39
|
: timeoutSignal;
|
|
44
40
|
|
|
45
|
-
const headers = {
|
|
41
|
+
const headers = {};
|
|
46
42
|
if (this.#apiKey) headers.Authorization = `Bearer ${this.#apiKey}`;
|
|
47
43
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const parts = [m.reasoning_content, m.reasoning, m.thinking].filter(
|
|
68
|
-
Boolean,
|
|
69
|
-
);
|
|
70
|
-
m.reasoning_content =
|
|
71
|
-
parts.length > 0 ? [...new Set(parts)].join("\n") : null;
|
|
72
|
-
|
|
73
|
-
// Full reasoning dump is centralized in telemetry.js on every
|
|
74
|
-
// provider — keeping it out of provider plugins avoids double
|
|
75
|
-
// printing and per-provider drift.
|
|
44
|
+
try {
|
|
45
|
+
return await chatCompletionStream({
|
|
46
|
+
url: `${this.#baseUrl}/v1/chat/completions`,
|
|
47
|
+
headers,
|
|
48
|
+
body,
|
|
49
|
+
signal,
|
|
50
|
+
});
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if (err.status) {
|
|
53
|
+
const wrapped = new Error(
|
|
54
|
+
msg("error.openai_api", { status: `${err.status} - ${err.body}` }),
|
|
55
|
+
{ cause: err },
|
|
56
|
+
);
|
|
57
|
+
wrapped.status = err.status;
|
|
58
|
+
wrapped.body = err.body;
|
|
59
|
+
wrapped.retryAfter = err.retryAfter;
|
|
60
|
+
throw wrapped;
|
|
61
|
+
}
|
|
62
|
+
throw err;
|
|
76
63
|
}
|
|
77
|
-
|
|
78
|
-
return data;
|
|
79
64
|
}
|
|
80
65
|
|
|
81
66
|
async #getContextSize(_model) {
|
|
82
67
|
const headers = { "Content-Type": "application/json" };
|
|
83
68
|
if (this.#apiKey) headers.Authorization = `Bearer ${this.#apiKey}`;
|
|
84
69
|
|
|
85
|
-
//
|
|
70
|
+
// llama.cpp /props returns runtime n_ctx; absent on vanilla OpenAI.
|
|
86
71
|
try {
|
|
87
72
|
const propsResponse = await fetch(`${this.#baseUrl}/props`, {
|
|
88
73
|
headers,
|
|
@@ -93,10 +78,7 @@ export default class OpenAi {
|
|
|
93
78
|
const runtimeCtx = props?.default_generation_settings?.n_ctx;
|
|
94
79
|
if (runtimeCtx) return runtimeCtx;
|
|
95
80
|
}
|
|
96
|
-
} catch (_err) {
|
|
97
|
-
// /props is a llama.cpp extension; absent on vanilla OpenAI.
|
|
98
|
-
// Fall through to /v1/models for the training-context-size hint.
|
|
99
|
-
}
|
|
81
|
+
} catch (_err) {}
|
|
100
82
|
|
|
101
83
|
// Fall back to /v1/models for training context.
|
|
102
84
|
const response = await fetch(`${this.#baseUrl}/v1/models`, {
|
|
@@ -1,20 +1,12 @@
|
|
|
1
|
+
import config from "../../agent/config.js";
|
|
1
2
|
import msg from "../../agent/messages.js";
|
|
3
|
+
import { chatCompletionStream } from "../../llm/openaiStream.js";
|
|
2
4
|
|
|
3
|
-
const FETCH_TIMEOUT =
|
|
4
|
-
if (!FETCH_TIMEOUT) throw new Error("RUMMY_FETCH_TIMEOUT must be set");
|
|
5
|
+
const { FETCH_TIMEOUT } = config;
|
|
5
6
|
|
|
6
7
|
const PROVIDER = "openrouter";
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
* OpenRouter LLM provider plugin. Handles model aliases of the form
|
|
10
|
-
* `openrouter/{publisher}/{modelName}`. Strips only the provider
|
|
11
|
-
* segment — OpenRouter's own API expects the `publisher/model` form,
|
|
12
|
-
* so that's exactly what's passed through to it (e.g.
|
|
13
|
-
* `openrouter/anthropic/claude-3-opus` → API receives
|
|
14
|
-
* `anthropic/claude-3-opus`).
|
|
15
|
-
*
|
|
16
|
-
* Inert if OPENROUTER_API_KEY / OPENROUTER_BASE_URL aren't set.
|
|
17
|
-
*/
|
|
9
|
+
// Inert unless OPENROUTER_API_KEY+OPENROUTER_BASE_URL set; openrouter/{publisher}/{model} aliases.
|
|
18
10
|
export default class OpenRouter {
|
|
19
11
|
#apiKey;
|
|
20
12
|
#baseUrl;
|
|
@@ -48,52 +40,36 @@ export default class OpenRouter {
|
|
|
48
40
|
? AbortSignal.any([options.signal, timeoutSignal])
|
|
49
41
|
: timeoutSignal;
|
|
50
42
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"HTTP-Referer": process.env.RUMMY_HTTP_REFERER,
|
|
57
|
-
"X-Title": process.env.RUMMY_X_TITLE,
|
|
58
|
-
},
|
|
59
|
-
body: JSON.stringify(body),
|
|
60
|
-
signal,
|
|
61
|
-
});
|
|
43
|
+
const headers = {
|
|
44
|
+
Authorization: `Bearer ${this.#apiKey}`,
|
|
45
|
+
"HTTP-Referer": process.env.RUMMY_HTTP_REFERER,
|
|
46
|
+
"X-Title": process.env.RUMMY_X_TITLE,
|
|
47
|
+
};
|
|
62
48
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
49
|
+
try {
|
|
50
|
+
return await chatCompletionStream({
|
|
51
|
+
url: `${this.#baseUrl}/chat/completions`,
|
|
52
|
+
headers,
|
|
53
|
+
body,
|
|
54
|
+
signal,
|
|
55
|
+
});
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (err.status === 401 || err.status === 403) {
|
|
66
58
|
throw new Error(
|
|
67
59
|
msg("error.openrouter_auth", {
|
|
68
|
-
status: `${
|
|
60
|
+
status: `${err.status} - ${err.body}`,
|
|
69
61
|
}),
|
|
70
62
|
);
|
|
71
63
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
for (const choice of data.choices) {
|
|
81
|
-
const cm = choice.message;
|
|
82
|
-
if (!cm) continue;
|
|
83
|
-
const details = cm.reasoning_details
|
|
84
|
-
? cm.reasoning_details.map((d) => d.text)
|
|
85
|
-
: [];
|
|
86
|
-
const parts = [
|
|
87
|
-
cm.reasoning_content,
|
|
88
|
-
cm.reasoning,
|
|
89
|
-
cm.thinking,
|
|
90
|
-
...details,
|
|
91
|
-
].filter(Boolean);
|
|
92
|
-
cm.reasoning_content =
|
|
93
|
-
parts.length > 0 ? [...new Set(parts)].join("\n") : null;
|
|
64
|
+
if (err.status) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
msg("error.openrouter_api", {
|
|
67
|
+
status: `${err.status} - ${err.body}`,
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
throw err;
|
|
94
72
|
}
|
|
95
|
-
|
|
96
|
-
return data;
|
|
97
73
|
}
|
|
98
74
|
|
|
99
75
|
async #getContextSize(model) {
|
|
@@ -101,7 +77,7 @@ export default class OpenRouter {
|
|
|
101
77
|
|
|
102
78
|
const res = await fetch(`${this.#baseUrl}/models`, {
|
|
103
79
|
headers: { Authorization: `Bearer ${this.#apiKey}` },
|
|
104
|
-
signal: AbortSignal.timeout(
|
|
80
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
105
81
|
});
|
|
106
82
|
if (!res.ok) {
|
|
107
83
|
throw new Error(
|
|
@@ -6,8 +6,14 @@ was started in `ask` mode.
|
|
|
6
6
|
|
|
7
7
|
## Registration
|
|
8
8
|
|
|
9
|
-
- **Filter**: `entry.recording` (priority 1) —
|
|
10
|
-
|
|
9
|
+
- **Filter**: `entry.recording` (priority 1) — the validation /
|
|
10
|
+
transform hook in TurnExecutor's RECORD phase. Runs after the
|
|
11
|
+
command is parsed but before the audit row is committed. Returning
|
|
12
|
+
an object with `state: "failed"` (or `"cancelled"`) short-circuits
|
|
13
|
+
recording and skips DISPATCH for that command. Plugins may also
|
|
14
|
+
return a transformed entry (modified body, attributes, path) for
|
|
15
|
+
the recorder to commit. Filter signature:
|
|
16
|
+
`(entry, { store, runId, turn, loopId, mode })`.
|
|
11
17
|
|
|
12
18
|
## Rejections (ask mode only)
|
|
13
19
|
|
|
@@ -1,39 +1,28 @@
|
|
|
1
1
|
import Entries from "../../agent/Entries.js";
|
|
2
2
|
|
|
3
3
|
export default class Policy {
|
|
4
|
-
#core;
|
|
5
|
-
|
|
6
4
|
constructor(core) {
|
|
7
|
-
this.#core = core;
|
|
8
5
|
core.filter("entry.recording", this.#enforceAskMode.bind(this), 1);
|
|
9
6
|
}
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
store: ctx.store,
|
|
14
|
-
runId: ctx.runId,
|
|
15
|
-
turn: ctx.turn,
|
|
16
|
-
loopId: ctx.loopId,
|
|
17
|
-
message,
|
|
18
|
-
});
|
|
8
|
+
#fail(entry, body) {
|
|
9
|
+
return { ...entry, body, state: "failed", outcome: "permission" };
|
|
19
10
|
}
|
|
20
11
|
|
|
21
12
|
async #enforceAskMode(entry, ctx) {
|
|
22
13
|
if (ctx.mode !== "ask") return entry;
|
|
23
14
|
|
|
24
15
|
if (entry.scheme === "sh") {
|
|
25
|
-
|
|
26
|
-
return { ...entry, state: "failed", outcome: "permission" };
|
|
16
|
+
return this.#fail(entry, "Rejected <sh> in ask mode");
|
|
27
17
|
}
|
|
28
18
|
|
|
29
19
|
if (entry.scheme === "set" && entry.attributes?.path) {
|
|
30
20
|
const scheme = Entries.scheme(entry.attributes.path);
|
|
31
21
|
if (scheme === null && entry.body) {
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
return this.#fail(
|
|
23
|
+
entry,
|
|
34
24
|
`Rejected file edit to ${entry.attributes.path} in ask mode`,
|
|
35
25
|
);
|
|
36
|
-
return { ...entry, state: "failed", outcome: "permission" };
|
|
37
26
|
}
|
|
38
27
|
}
|
|
39
28
|
|
|
@@ -41,19 +30,17 @@ export default class Policy {
|
|
|
41
30
|
const pathAttr = entry.attributes?.path || entry.path;
|
|
42
31
|
const scheme = Entries.scheme(pathAttr);
|
|
43
32
|
if (scheme === null) {
|
|
44
|
-
|
|
45
|
-
return { ...entry, state: "failed", outcome: "permission" };
|
|
33
|
+
return this.#fail(entry, `Rejected file rm of ${pathAttr} in ask mode`);
|
|
46
34
|
}
|
|
47
35
|
}
|
|
48
36
|
|
|
49
37
|
if (entry.scheme === "mv" || entry.scheme === "cp") {
|
|
50
38
|
const destScheme = Entries.scheme(entry.attributes?.to);
|
|
51
39
|
if (destScheme === null) {
|
|
52
|
-
|
|
53
|
-
|
|
40
|
+
return this.#fail(
|
|
41
|
+
entry,
|
|
54
42
|
`Rejected ${entry.scheme} to file ${entry.attributes?.to} in ask mode`,
|
|
55
43
|
);
|
|
56
|
-
return { ...entry, state: "failed", outcome: "permission" };
|
|
57
44
|
}
|
|
58
45
|
}
|
|
59
46
|
|
|
@@ -14,3 +14,25 @@ Finds the latest `prompt://` entry in the turn_context rows. The mode
|
|
|
14
14
|
attribute (available tool list) and optional `warn` attribute in ask
|
|
15
15
|
mode. Falls back to the mode passed by the core if no prompt entry
|
|
16
16
|
exists.
|
|
17
|
+
|
|
18
|
+
## Archived prompts: the singular exception to invisibility
|
|
19
|
+
|
|
20
|
+
`v_model_context.sql` filters archived entries out of the model's
|
|
21
|
+
context — every scheme **except `prompt`**. Archived `prompt://`
|
|
22
|
+
entries flow through with `effective_visibility = 'archived'` and
|
|
23
|
+
their body suppressed (per `projected.body`'s visibility CASE). The
|
|
24
|
+
plugin then renders the tag with full attributes (`path`,
|
|
25
|
+
`visibility="archived"`, etc.) but empty body.
|
|
26
|
+
|
|
27
|
+
The exception exists because the prompt is run identity: every other
|
|
28
|
+
archived entry is recoverable by pattern search if the model ever
|
|
29
|
+
needs it back, but the prompt is the question the run is answering.
|
|
30
|
+
A model that loses sight of its prompt cannot honestly act. Keeping
|
|
31
|
+
the archived prompt's path visible lets the model emit
|
|
32
|
+
`<get path="prompt://N"/>` to promote it back if it archived
|
|
33
|
+
prematurely (or step back to an earlier stage via
|
|
34
|
+
`<update status="174">`).
|
|
35
|
+
|
|
36
|
+
This is the only entry-type exception to the "archived = invisible"
|
|
37
|
+
contract. New schemes that warrant similar treatment should be added
|
|
38
|
+
explicitly here, not by accident.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const SUMMARIZED_PROMPT_CHAR_CAP = 500;
|
|
2
|
+
|
|
1
3
|
export default class Prompt {
|
|
2
4
|
#core;
|
|
3
5
|
|
|
@@ -7,22 +9,23 @@ export default class Prompt {
|
|
|
7
9
|
core.hooks.tools.onView(
|
|
8
10
|
"prompt",
|
|
9
11
|
(entry) => {
|
|
10
|
-
const limit = 500;
|
|
11
12
|
const full = entry.body;
|
|
12
|
-
if (full.length <=
|
|
13
|
-
return `${full.slice(0,
|
|
13
|
+
if (full.length <= SUMMARIZED_PROMPT_CHAR_CAP) return full;
|
|
14
|
+
return `${full.slice(0, SUMMARIZED_PROMPT_CHAR_CAP)}\n[truncated — promote to see the complete prompt]`;
|
|
14
15
|
},
|
|
15
16
|
"summarized",
|
|
16
17
|
);
|
|
17
18
|
core.on("turn.started", this.onTurnStarted.bind(this));
|
|
18
|
-
core.filter("assembly.user", this.assemblePrompt.bind(this),
|
|
19
|
+
core.filter("assembly.user", this.assemblePrompt.bind(this), 225);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
async onTurnStarted({ rummy, mode, prompt, isContinuation }) {
|
|
22
23
|
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
23
24
|
|
|
24
25
|
if (!isContinuation && prompt) {
|
|
25
|
-
// prompt
|
|
26
|
+
// New prompt = new cycle; archive prior cycle's prompts/logs (knowns/unknowns persist).
|
|
27
|
+
await store.archivePriorPromptArtifacts(runId, turn);
|
|
28
|
+
|
|
26
29
|
await store.set({
|
|
27
30
|
runId,
|
|
28
31
|
turn,
|
|
@@ -37,7 +40,7 @@ export default class Prompt {
|
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
async assemblePrompt(content, ctx) {
|
|
40
|
-
const { rows,
|
|
43
|
+
const { rows, toolSet } = ctx;
|
|
41
44
|
const promptEntry = rows.findLast(
|
|
42
45
|
(r) => r.category === "prompt" && r.scheme === "prompt",
|
|
43
46
|
);
|
|
@@ -57,12 +60,7 @@ export default class Prompt {
|
|
|
57
60
|
let warn = "";
|
|
58
61
|
if (mode === "ask") warn = ' warn="File editing disallowed."';
|
|
59
62
|
|
|
60
|
-
//
|
|
61
|
-
// `reverted="N"` attribute on <prompt>. Historical error
|
|
62
|
-
// entries sit in <log> but read as ambient noise; this signal
|
|
63
|
-
// is dynamic and always fresh — the model sees that its
|
|
64
|
-
// promotions last turn were reverted, in the same spot where
|
|
65
|
-
// it reads budget numbers.
|
|
63
|
+
// reverted="N" surfaces last turn's 413 demotion count next to budget numbers.
|
|
66
64
|
let reverted = "";
|
|
67
65
|
const priorTurn = ctx.turn - 1;
|
|
68
66
|
if (priorTurn >= 1) {
|
|
@@ -88,9 +86,9 @@ export default class Prompt {
|
|
|
88
86
|
? ` visibility="${promptEntry.visibility}"`
|
|
89
87
|
: "";
|
|
90
88
|
const tokens =
|
|
91
|
-
promptEntry?.aTokens != null
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return `${content}<prompt mode="${mode}"${path} commands="${commands}"${warn}${reverted}${visibility}${tokens}>${body}</prompt>`;
|
|
89
|
+
promptEntry?.aTokens != null ? ` tokens="${promptEntry.aTokens}"` : "";
|
|
90
|
+
const lines =
|
|
91
|
+
promptEntry?.vLines != null ? ` lines="${promptEntry.vLines}"` : "";
|
|
92
|
+
return `${content}<prompt mode="${mode}"${path} commands="${commands}"${warn}${reverted}${visibility}${tokens}${lines}>${body}</prompt>`;
|
|
95
93
|
}
|
|
96
94
|
}
|
package/src/plugins/rm/rm.js
CHANGED
|
@@ -27,9 +27,12 @@ export default class Rm {
|
|
|
27
27
|
await ctx.entries.rm({ runId: ctx.runId, path: target });
|
|
28
28
|
if (ctx.projectRoot) {
|
|
29
29
|
const { unlink } = await import("node:fs/promises");
|
|
30
|
-
const { join } = await import("node:path");
|
|
30
|
+
const { isAbsolute, join } = await import("node:path");
|
|
31
|
+
const targetPath = isAbsolute(target)
|
|
32
|
+
? target
|
|
33
|
+
: join(ctx.projectRoot, target);
|
|
31
34
|
try {
|
|
32
|
-
await unlink(
|
|
35
|
+
await unlink(targetPath);
|
|
33
36
|
} catch (err) {
|
|
34
37
|
// File may already be absent — entry rm'd regardless.
|
|
35
38
|
if (err.code !== "ENOENT") throw err;
|
package/src/plugins/rm/rmDoc.md
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
Example: <rm path="src/config.js"/>
|
|
4
4
|
<!-- File removal. Simplest form. -->
|
|
5
5
|
|
|
6
|
-
Example: <rm path="known://temp_*"
|
|
7
|
-
<!--
|
|
6
|
+
Example: <rm path="known://temp_*" manifest/>
|
|
7
|
+
<!-- Optional: Manifest before deleting. Safety pattern for bulk operations. -->
|
|
8
8
|
|
|
9
9
|
* Permanent. Prefer <set path="..." visibility="archived"/> to preserve for later retrieval
|
|
10
10
|
<!-- Nudges toward archive over rm. Path attr included so the model sees a complete invocation shape, not a fragment. -->
|
|
11
11
|
|
|
12
|
-
* `
|
|
13
|
-
<!-- Canonical
|
|
12
|
+
* `manifest` lists what paths would be affected without performing the operation.
|
|
13
|
+
<!-- Canonical manifest teaching lives here — rm is the most intuitive 'check before committing' case. Model generalizes to cp/mv/get by analogy. Advanced uses (e.g. archive rediscovery via <get manifest>) belong in persona/skill docs, not here. -->
|
|
@@ -29,4 +29,5 @@ all registered tools.
|
|
|
29
29
|
- `getRuns`, `getRun`
|
|
30
30
|
|
|
31
31
|
### Notifications
|
|
32
|
-
- `run/
|
|
32
|
+
- `run/changed` — pulse: an entry under this run changed; client reconciles via `getEntries(run, { since })`.
|
|
33
|
+
- `ui/render`, `ui/notify`, `stream/cancelled`
|
package/src/plugins/rpc/rpc.js
CHANGED
|
@@ -24,10 +24,7 @@ export default class Rpc {
|
|
|
24
24
|
description: "Returns { methods, notifications } catalog.",
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
//
|
|
28
|
-
// The client surface is a thin projection of the plugin API.
|
|
29
|
-
// Six verbs, each takes an object of entry-grammar params.
|
|
30
|
-
// Writer is fixed to "client"; permissions enforced per scheme.
|
|
27
|
+
// Primitives (SPEC #primitives); writer fixed to "client".
|
|
31
28
|
|
|
32
29
|
r.register("set", {
|
|
33
30
|
handler: async (params, ctx) => {
|
|
@@ -139,8 +136,9 @@ export default class Rpc {
|
|
|
139
136
|
return { ok: true, path };
|
|
140
137
|
},
|
|
141
138
|
description:
|
|
142
|
-
"Write
|
|
143
|
-
"signal. Not general — this is the
|
|
139
|
+
"Write a status update at log://turn_N/update/<slug> carrying a " +
|
|
140
|
+
"turn's continuation/terminal signal. Not general — this is the " +
|
|
141
|
+
"lifecycle verb.",
|
|
144
142
|
params: {
|
|
145
143
|
run: "string — run alias",
|
|
146
144
|
body: "string — update text",
|
|
@@ -151,9 +149,7 @@ export default class Rpc {
|
|
|
151
149
|
requiresInit: true,
|
|
152
150
|
});
|
|
153
151
|
|
|
154
|
-
// Connection handshake
|
|
155
|
-
// the project identity for this connection and announces the
|
|
156
|
-
// server's protocol version.
|
|
152
|
+
// Connection handshake; project identity + protocol version.
|
|
157
153
|
r.register("rummy/hello", {
|
|
158
154
|
handler: async (params, ctx) => {
|
|
159
155
|
const { RUMMY_PROTOCOL_VERSION } = await import(
|
|
@@ -311,11 +307,18 @@ export default class Rpc {
|
|
|
311
307
|
r.register("getEntries", {
|
|
312
308
|
handler: async (params, ctx) => {
|
|
313
309
|
const runRow = await this.#resolveRun(params.run, ctx);
|
|
314
|
-
const {
|
|
310
|
+
const {
|
|
311
|
+
pattern = "*",
|
|
312
|
+
bodyFilter = null,
|
|
313
|
+
since = null,
|
|
314
|
+
limit = null,
|
|
315
|
+
withBody = false,
|
|
316
|
+
} = params;
|
|
315
317
|
const rows = await ctx.projectAgent.entries.getEntriesByPattern(
|
|
316
318
|
runRow.id,
|
|
317
319
|
pattern,
|
|
318
320
|
bodyFilter,
|
|
321
|
+
{ since, limit },
|
|
319
322
|
);
|
|
320
323
|
return rows
|
|
321
324
|
.filter((e) => !params.scheme || e.scheme === params.scheme)
|
|
@@ -323,30 +326,43 @@ export default class Rpc {
|
|
|
323
326
|
.filter(
|
|
324
327
|
(e) => !params.visibility || e.visibility === params.visibility,
|
|
325
328
|
)
|
|
326
|
-
.map((e) =>
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
329
|
+
.map((e) => {
|
|
330
|
+
const row = {
|
|
331
|
+
id: e.id,
|
|
332
|
+
path: e.path,
|
|
333
|
+
scheme: e.scheme,
|
|
334
|
+
state: e.state,
|
|
335
|
+
outcome: e.outcome,
|
|
336
|
+
visibility: e.visibility,
|
|
337
|
+
turn: e.turn,
|
|
338
|
+
tokens: e.tokens,
|
|
339
|
+
attributes:
|
|
340
|
+
typeof e.attributes === "string"
|
|
341
|
+
? JSON.parse(e.attributes)
|
|
342
|
+
: e.attributes,
|
|
343
|
+
};
|
|
344
|
+
if (withBody) row.body = e.body;
|
|
345
|
+
return row;
|
|
346
|
+
});
|
|
339
347
|
},
|
|
340
348
|
description:
|
|
341
349
|
"List entries matching a pattern. Read-only — no promotion. " +
|
|
342
|
-
"Optional filters: scheme, state, visibility, bodyFilter."
|
|
350
|
+
"Optional filters: scheme, state, visibility, bodyFilter. " +
|
|
351
|
+
"Pass `withBody: true` to include `body` on each row (omitted by default to keep pulse-reconcile traffic lean). " +
|
|
352
|
+
"For incremental sync after a `run/changed` pulse, pass `since` (last seen entry id); " +
|
|
353
|
+
"use `limit` to chunk catch-up.",
|
|
343
354
|
params: {
|
|
344
355
|
run: "string — run alias",
|
|
345
356
|
pattern: "string? — glob pattern (default '*')",
|
|
346
357
|
scheme: "string? — filter by scheme (e.g. 'file')",
|
|
347
358
|
state: "string? — filter by state",
|
|
348
359
|
visibility: "string? — filter by visibility",
|
|
349
|
-
bodyFilter:
|
|
360
|
+
bodyFilter:
|
|
361
|
+
"string? — filter rows by content of body (substring/glob; NOT for body inclusion — see withBody)",
|
|
362
|
+
withBody:
|
|
363
|
+
"boolean? — include `body` field on each returned row (default false)",
|
|
364
|
+
since: "number? — only entries with id > since (insertion-ordered)",
|
|
365
|
+
limit: "number? — cap result count",
|
|
350
366
|
},
|
|
351
367
|
requiresInit: true,
|
|
352
368
|
});
|
|
@@ -436,9 +452,10 @@ export default class Rpc {
|
|
|
436
452
|
|
|
437
453
|
// --- Notifications ---
|
|
438
454
|
|
|
439
|
-
r.registerNotification(
|
|
440
|
-
|
|
441
|
-
|
|
455
|
+
r.registerNotification(
|
|
456
|
+
"run/changed",
|
|
457
|
+
"Pulse: an entry under this run changed. Query with `getEntries(run, { pattern, since })` to reconcile.",
|
|
458
|
+
);
|
|
442
459
|
r.registerNotification(
|
|
443
460
|
"stream/cancelled",
|
|
444
461
|
"Server-initiated stream cancellation.",
|
|
@@ -446,8 +463,7 @@ export default class Rpc {
|
|
|
446
463
|
r.registerNotification("ui/render", "Streaming output.");
|
|
447
464
|
r.registerNotification("ui/notify", "Toast notification.");
|
|
448
465
|
|
|
449
|
-
//
|
|
450
|
-
// Checked at request time — no timing dependency on plugin load order.
|
|
466
|
+
// Any registered tool is callable via RPC; resolved at request time.
|
|
451
467
|
r.setToolFallback(hooks, buildRunContext, dispatchTool);
|
|
452
468
|
}
|
|
453
469
|
|
|
@@ -464,18 +480,14 @@ export default class Rpc {
|
|
|
464
480
|
async #dispatchSet(params, ctx) {
|
|
465
481
|
if (!params.path) throw new Error("set: path is required");
|
|
466
482
|
|
|
467
|
-
// run://
|
|
468
|
-
// alias starts a run loop; a state transition cancels or resolves.
|
|
483
|
+
// run:// = lifecycle surface (start run, cancel, resolve).
|
|
469
484
|
if (params.path.startsWith("run://")) {
|
|
470
485
|
return await this.#dispatchRunSet(params, ctx);
|
|
471
486
|
}
|
|
472
487
|
|
|
473
488
|
const runRow = await this.#resolveRun(params.run, ctx);
|
|
474
489
|
|
|
475
|
-
// State
|
|
476
|
-
// AgentLoop.resolve, which applies scheme-specific side effects
|
|
477
|
-
// (patch application for set://, file removal for rm://, stream
|
|
478
|
-
// setup for sh:// / env://, etc.).
|
|
490
|
+
// State transitions on proposed entries → AgentLoop.resolve for scheme-specific effects.
|
|
479
491
|
if (params.state && !params.append && !params.pattern) {
|
|
480
492
|
const current = await ctx.projectAgent.entries.getState(
|
|
481
493
|
runRow.id,
|
|
@@ -521,10 +533,7 @@ export default class Rpc {
|
|
|
521
533
|
async #dispatchRunSet(params, ctx) {
|
|
522
534
|
let alias = params.path.slice("run://".length);
|
|
523
535
|
|
|
524
|
-
// Empty alias
|
|
525
|
-
// Matches AgentLoop.#generateAlias so server- and client-initiated
|
|
526
|
-
// runs share one naming scheme. Clients that want a specific name
|
|
527
|
-
// pass it in the path; anonymous starts get the synthesized one.
|
|
536
|
+
// Empty alias → ${model}_${epoch}; mirrors AgentLoop.#generateAlias.
|
|
528
537
|
if (!alias) {
|
|
529
538
|
const { attributes: attrs = {} } = params;
|
|
530
539
|
if (!attrs.model) {
|
|
@@ -576,12 +585,11 @@ export default class Rpc {
|
|
|
576
585
|
noInteraction: attrs.noInteraction,
|
|
577
586
|
noWeb: attrs.noWeb,
|
|
578
587
|
noProposals: attrs.noProposals,
|
|
588
|
+
yolo: attrs.yolo,
|
|
579
589
|
fork: attrs.fork,
|
|
580
590
|
};
|
|
581
591
|
const { body = "" } = params;
|
|
582
|
-
// Fire-and-forget
|
|
583
|
-
// ProjectAgent exposes .ask/.act wrappers over AgentLoop#run; route
|
|
584
|
-
// by mode rather than calling the private loop directly.
|
|
592
|
+
// Fire-and-forget; client watches state via entry notifications.
|
|
585
593
|
const kickoff =
|
|
586
594
|
mode === "act"
|
|
587
595
|
? ctx.projectAgent.act(
|
|
@@ -604,10 +612,7 @@ export default class Rpc {
|
|
|
604
612
|
return { ok: true, alias };
|
|
605
613
|
}
|
|
606
614
|
|
|
607
|
-
//
|
|
608
|
-
// can return the child alias, then kick off the loop against it.
|
|
609
|
-
// fork needs a brand-new run row with parent_run_id set; inject()
|
|
610
|
-
// would just add another prompt to the parent.
|
|
615
|
+
// fork=true → new child run with parent_run_id; inject() would only add a prompt to parent.
|
|
611
616
|
const attrs = params.attributes ? params.attributes : {};
|
|
612
617
|
if (attrs.fork === true) {
|
|
613
618
|
const { mode } = attrs;
|
|
@@ -638,6 +643,7 @@ export default class Rpc {
|
|
|
638
643
|
noInteraction: attrs.noInteraction,
|
|
639
644
|
noWeb: attrs.noWeb,
|
|
640
645
|
noProposals: attrs.noProposals,
|
|
646
|
+
yolo: attrs.yolo,
|
|
641
647
|
// fork already applied — pass false to reuse the child row.
|
|
642
648
|
fork: false,
|
|
643
649
|
};
|
|
@@ -673,7 +679,15 @@ export default class Rpc {
|
|
|
673
679
|
`set run://: attributes.mode is required on inject and must be "ask" or "act" (got ${JSON.stringify(mode)})`,
|
|
674
680
|
);
|
|
675
681
|
}
|
|
676
|
-
|
|
682
|
+
const options = {
|
|
683
|
+
temperature: attrs.temperature,
|
|
684
|
+
noRepo: attrs.noRepo,
|
|
685
|
+
noInteraction: attrs.noInteraction,
|
|
686
|
+
noWeb: attrs.noWeb,
|
|
687
|
+
noProposals: attrs.noProposals,
|
|
688
|
+
yolo: attrs.yolo,
|
|
689
|
+
};
|
|
690
|
+
await ctx.projectAgent.inject(alias, params.body, mode, options);
|
|
677
691
|
return { ok: true, alias };
|
|
678
692
|
}
|
|
679
693
|
|