@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,46 +1,39 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Distillation Stage: YOU MUST select an unknown:// entry, then discover its source entries and distill them into known:// entries
|
|
2
2
|
|
|
3
|
-
YOU MUST
|
|
4
|
-
YOU MUST
|
|
5
|
-
YOU MUST
|
|
6
|
-
YOU MUST
|
|
7
|
-
YOU MUST demote source entries to "summarized" after extracting and decomposing their relevant information into known:// entries.
|
|
8
|
-
YOU MUST demote the unknown:// entries to "summarized" after they are referenced or resolved by known:// entries.
|
|
9
|
-
YOU MUST demote all irrelevant source entries and log events to maximize FCRM.
|
|
10
|
-
Tip: Source entry "summarized" information is not reliable. Only place "visible" source entry information in known:// entries.
|
|
11
|
-
Tip: A "relevant" source entry that has been successfully distilled into known:// entries is no longer relevant.
|
|
12
|
-
Tip: Discover, Distill, and Demote per source entry, not globally, to maximize FCRM.
|
|
3
|
+
YOU MUST create topical, taxonomized, and tagged known:// entries to resolve the selected unknown:// entry.
|
|
4
|
+
YOU MUST reference all related source entries and prompts in the `# Related` list
|
|
5
|
+
YOU MUST ONLY populate known entries with promoted information, NOT from your own training data or opinion.
|
|
6
|
+
YOU MUST immediately demote unknowns, source entries, prompts, and log events after they are distilled, irrelevant, or resolved.
|
|
13
7
|
|
|
14
|
-
|
|
8
|
+
* Check the `tokens="N"` of the source entries against the `tokensFree="N"` constraint before promoting entries.
|
|
9
|
+
* You can use <get path="..." manifest/> to list paths and their token amounts for bulk operations without performing them.
|
|
10
|
+
* You can use <get path="..." line="X" limit="Y"/> to read subsets of entries that would exceed your `tokensFree` budget.
|
|
11
|
+
* Don't accidentally set the current prompt to `archived`.
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
Example:
|
|
14
|
+
<get path="**" manifest>capital</get>
|
|
15
|
+
<get path="prompt://3" line="1" limit="100"/>
|
|
17
16
|
|
|
18
|
-
<set path="trivia/capitals.csv" visibility="visible"/>
|
|
17
|
+
<set path="trivia/capitals.csv" visibility="visible"/>
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
<set path="known://countries/france/capital" summary="countries,france,capital,geography,trivia">
|
|
20
|
+
# Related
|
|
21
|
+
[trivia question](prompt://3)
|
|
22
|
+
[unknown resolving](unknown://countries/france/capital)
|
|
23
|
+
[source entry](trivia/capitals.csv)
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
# Capital of France
|
|
26
|
+
The capital of France is Paris.
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
[source entry](trivia/capitals.csv)
|
|
30
|
-
</set>
|
|
28
|
+
{...}
|
|
29
|
+
</set>
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<set path="
|
|
35
|
-
<set path="
|
|
36
|
-
<set path="unknown://countries/poland/capital" summary="REJECTED: Irrelevant" visibility="summarized"/>
|
|
37
|
-
<set path="https://en.wikipedia.org/wiki/Paris,_Texas" summary="REJECTED: Wrong Paris" visibility="summarized"/>
|
|
38
|
-
<set path="log://turn_1/set/*" visibility="archived"/>
|
|
39
|
-
<set path="log://turn_1/get/trivia/*" visibility="archived"/>
|
|
40
|
-
<set path="log://turn_2/get/capital%20of%20france" visibility="archived"/>
|
|
31
|
+
<set path="prompt://3" visibility="summarized"/>
|
|
32
|
+
<set path="unknown://countries/france/capital" visibility="summarized"/>
|
|
33
|
+
<set path="unknown://countries/france/seat_of_government" summary="RESOLVED: Not necessary" visibility="summarized"/>
|
|
34
|
+
<set path="trivia/capitals.csv" visibility="summarized"/>
|
|
41
35
|
|
|
42
36
|
## Turn Termination (CHOOSE ONLY ONE):
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
Discovery Stage Completion: <update status="158">all unknowns (if any) referenced or resolved by known entries</update>
|
|
37
|
+
* Decomposition Stage Return: <update status="154">additional unknowns identified; returning to Decomposition Stage</update>
|
|
38
|
+
* Distillation Stage Continuation: <update status="155">discovering and distilling more for the selected unknown</update>
|
|
39
|
+
* Distillation Stage Completion: <update status="156">this unknown's known entries written</update>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Demotion Stage: YOU MUST demote all source entries, prompts, and log events that are now distilled or no longer relevant
|
|
2
|
+
|
|
3
|
+
Example:
|
|
4
|
+
<set path="prompt://2" summary="All information distilled into knowns" visibility="summarized"/>
|
|
5
|
+
<set path="trivia/capitals.csv" visibility="summarized"/>
|
|
6
|
+
<set path="unknown://countries/france/capital" visibility="summarized"/>
|
|
7
|
+
<set path="unknown://countries/poland/capital" summary="REJECTED: Irrelevant" visibility="summarized"/>
|
|
8
|
+
<set path="https://en.wikipedia.org/wiki/Paris,_Texas" summary="REJECTED: Wrong Paris" visibility="summarized"/>
|
|
9
|
+
<set path="log://turn_1/**" visibility="archived"/>
|
|
10
|
+
<set path="log://turn_2/**" visibility="archived"/>
|
|
11
|
+
<set path="log://turn_3/set/**" visibility="archived"/>
|
|
12
|
+
<set path="log://turn_3/get/**" visibility="archived"/>
|
|
13
|
+
<set path="log://turn_3/search/**" visibility="archived"/>
|
|
14
|
+
|
|
15
|
+
* You need room to think. Demote large prompts and source entries, then iterate them with <get path="..." line="N" limit="N"/> as necessary.
|
|
16
|
+
* When demoting prompts, prefer "summarized" to "archived" to avoid losing necessary context.
|
|
17
|
+
|
|
18
|
+
## Turn Termination (CHOOSE ONLY ONE):
|
|
19
|
+
* Decomposition Stage Return: <update status="164">additional unknowns identified; returning to Decomposition Stage</update>
|
|
20
|
+
* Distillation Stage Return: <update status="165">more unknowns remain; returning to Distillation Stage</update>
|
|
21
|
+
* Demotion Stage Continuation: <update status="166">demoting more distilled or irrelevant entries, prompts, and log events</update>
|
|
22
|
+
* Demotion Stage Completion: <update status="167">all unknowns resolved and demoted; ready for Deployment Stage</update>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Deployment Stage: YOU MUST act on the prompt.
|
|
2
|
+
|
|
3
|
+
YOU MUST attempt to deterministically verify your actions, outputs, or answers before declaring completion, if possible.
|
|
4
|
+
|
|
5
|
+
Example: verifying deliverable before completion
|
|
6
|
+
<set path="sum.js">console.log(process.argv.slice(2).reduce((a, b) => a + Number(b), 0));</set>
|
|
7
|
+
<sh>[ -f sum.js ] && node --version && node sum.js 2 2 | grep -qx 4</sh>
|
|
8
|
+
<update status="177">sum.js written, node available, ran cleanly, correct output?</update>
|
|
9
|
+
|
|
10
|
+
Example: <update status="200">Paris</update>
|
|
11
|
+
|
|
12
|
+
## Turn Termination (CHOOSE ONLY ONE):
|
|
13
|
+
* Decomposition Stage Return: <update status="174">additional unknowns identified; returning to Decomposition Stage</update>
|
|
14
|
+
* Distillation Stage Return: <update status="175">selected unknown not yet resolved; returning to Distillation Stage</update>
|
|
15
|
+
* Demotion Stage Return: <update status="176">context not yet sufficiently demoted; returning to Demotion Stage</update>
|
|
16
|
+
* Deployment Stage Continuation: <update status="177">performing more actions</update>
|
|
17
|
+
* Deployment Stage Completion: <update status="200">{direct answer (summary of actions performed if prompt not a question)}</update>
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
YOU MUST act on the prompt.
|
|
2
|
-
|
|
3
|
-
Turn Termination (CHOOSE ONLY ONE):
|
|
4
|
-
|
|
5
|
-
Definition Stage Return: <update status="184">returning to definition stage</update>
|
|
6
|
-
Discovery Stage Return: <update status="185">returning to discovery stage</update>
|
|
7
|
-
Deployment Stage Continuation: <update status="188">performing more actions</update>
|
|
8
|
-
Deployment Stage Completion: <update status="200">summary of actions performed, or direct answer</update>
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
# known {#known_plugin}
|
|
2
2
|
|
|
3
|
-
Writes knowledge entries into the store at full visibility
|
|
3
|
+
Writes knowledge entries into the store at full visibility, and renders
|
|
4
|
+
the project's data surface as the bifurcated `<summarized>` /
|
|
5
|
+
`<visible>` blocks at the top of the user message.
|
|
4
6
|
|
|
5
7
|
## Registration
|
|
6
8
|
|
|
7
9
|
- **Tool**: `known`
|
|
8
10
|
- **Category**: `data`
|
|
9
11
|
- **Handler**: Upserts the entry body at the target path with status 200.
|
|
10
|
-
- **
|
|
12
|
+
- **Filters**:
|
|
13
|
+
- `assembly.user` priority 50 — renders `<summarized>`.
|
|
14
|
+
- `assembly.user` priority 75 — renders `<visible>`.
|
|
11
15
|
|
|
12
16
|
## Projection
|
|
13
17
|
|
|
@@ -15,7 +19,23 @@ Shows `# known {path}` followed by the entry body.
|
|
|
15
19
|
|
|
16
20
|
## Assembly
|
|
17
21
|
|
|
18
|
-
Filters
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
Filters `ctx.rows` where `category === "data"`. Two separate blocks
|
|
23
|
+
emit at the top of the user message in this order:
|
|
24
|
+
|
|
25
|
+
- `<summarized>` — each data entry whose visibility is `visible` or
|
|
26
|
+
`summarized`, rendered under its scheme tag with the plugin's
|
|
27
|
+
summary projection as body (truncated knowns, code symbols,
|
|
28
|
+
page abstracts — whatever the plugin's `summary()` hook produces).
|
|
29
|
+
Plus the named carve-out: archived prompts pass through
|
|
30
|
+
(visibility="archived") so the model can `<get>` the active prompt
|
|
31
|
+
back after demotion.
|
|
32
|
+
- `<visible>` — each data entry whose visibility is `visible`,
|
|
33
|
+
rendered with the plugin's visible projection (full body) as the
|
|
34
|
+
tag body. A visible entry appears in *both* blocks: summary
|
|
35
|
+
projection up top, full body below.
|
|
36
|
+
|
|
37
|
+
This split lets `<summarized>` stay cache-stable across promote/demote
|
|
38
|
+
operations — only `<visible>` mutates when the model promotes a
|
|
39
|
+
summary or demotes a visible entry. Third-party plugins that register
|
|
40
|
+
with `category: "data"` automatically appear in both blocks under
|
|
41
|
+
their scheme tag.
|
|
@@ -12,10 +12,9 @@ export default class Known {
|
|
|
12
12
|
core.on("handler", this.handler.bind(this));
|
|
13
13
|
core.on("visible", this.full.bind(this));
|
|
14
14
|
core.on("summarized", this.summary.bind(this));
|
|
15
|
-
core.filter("assembly.
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
// model emits <known> directly out of habit.
|
|
15
|
+
core.filter("assembly.user", this.assembleSummarized.bind(this), 50);
|
|
16
|
+
core.filter("assembly.user", this.assembleVisible.bind(this), 75);
|
|
17
|
+
// Hidden tool: written via <set path="known://...">; handler tolerates direct <known>.
|
|
19
18
|
core.markHidden();
|
|
20
19
|
}
|
|
21
20
|
|
|
@@ -23,23 +22,20 @@ export default class Known {
|
|
|
23
22
|
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
24
23
|
if (!entry.body) return;
|
|
25
24
|
|
|
26
|
-
// Size gate
|
|
27
25
|
const entryTokens = countTokens(entry.body);
|
|
28
26
|
if (entryTokens > MAX_ENTRY_TOKENS) {
|
|
29
|
-
const rejectPath = await store.slugPath(runId, "known", entry.body);
|
|
30
27
|
await store.set({
|
|
31
28
|
runId,
|
|
32
29
|
turn,
|
|
33
|
-
|
|
30
|
+
loopId,
|
|
31
|
+
path: entry.resultPath,
|
|
34
32
|
body: `Entry too large (${entryTokens} tokens, max ${MAX_ENTRY_TOKENS}). Sort the information, ideas, or plans carefully into multiple entries.`,
|
|
35
33
|
state: "failed",
|
|
36
34
|
outcome: `overflow:${entryTokens}`,
|
|
37
|
-
loopId,
|
|
38
35
|
});
|
|
39
36
|
return;
|
|
40
37
|
}
|
|
41
38
|
|
|
42
|
-
// Resolve path: explicit or auto-generated slug
|
|
43
39
|
let knownPath = entry.attributes?.path;
|
|
44
40
|
if (knownPath && !knownPath.includes("://")) {
|
|
45
41
|
knownPath = `known://${knownPath}`;
|
|
@@ -53,9 +49,7 @@ export default class Known {
|
|
|
53
49
|
);
|
|
54
50
|
}
|
|
55
51
|
|
|
56
|
-
// Dedup:
|
|
57
|
-
// new body means "preserve the existing entry's body" (e.g. the
|
|
58
|
-
// model is updating attributes only).
|
|
52
|
+
// Dedup: existing path → update; empty body preserves existing body.
|
|
59
53
|
const existing = await store.getEntriesByPattern(runId, knownPath, null);
|
|
60
54
|
if (existing.length > 0) {
|
|
61
55
|
const nextBody = entry.body === "" ? existing[0].body : entry.body;
|
|
@@ -86,31 +80,45 @@ export default class Known {
|
|
|
86
80
|
return entry.body;
|
|
87
81
|
}
|
|
88
82
|
|
|
89
|
-
// Summarized
|
|
90
|
-
// doesn't lose the plot when budget auto-demotion kicks in on its
|
|
91
|
-
// own work. Anything larger gets capped so a pathologically big
|
|
92
|
-
// known doesn't saturate the packet at summarized visibility
|
|
93
|
-
// either. Matches the pattern on `<prompt>` summarized view.
|
|
83
|
+
// Summarized: first 500 chars; matches <prompt> summarized.
|
|
94
84
|
summary(entry) {
|
|
95
85
|
if (!entry.body) return "";
|
|
96
86
|
if (entry.body.length <= 500) return entry.body;
|
|
97
87
|
return `${entry.body.slice(0, 500)}\n[truncated — promote to see the full body]`;
|
|
98
88
|
}
|
|
99
89
|
|
|
100
|
-
|
|
101
|
-
|
|
90
|
+
// Identity-keyed summary lines: every data entry the run is tracking
|
|
91
|
+
// at visibility=visible or visibility=summarized.
|
|
92
|
+
async assembleSummarized(content, ctx) {
|
|
93
|
+
const entries = ctx.rows.filter(
|
|
94
|
+
(r) =>
|
|
95
|
+
r.category === "data" &&
|
|
96
|
+
(r.visibility === "visible" || r.visibility === "summarized"),
|
|
97
|
+
);
|
|
98
|
+
if (entries.length === 0) return content;
|
|
99
|
+
const lines = entries.map((e) =>
|
|
100
|
+
renderContextTag(e, e.sBody != null ? e.sBody : e.body),
|
|
101
|
+
);
|
|
102
|
+
return `${content}<summarized>\n${lines.join("\n")}\n</summarized>\n`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async assembleVisible(content, ctx) {
|
|
106
|
+
const entries = ctx.rows.filter(
|
|
107
|
+
(r) => r.category === "data" && r.visibility === "visible",
|
|
108
|
+
);
|
|
102
109
|
if (entries.length === 0) return content;
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
const lines = entries.map((e) =>
|
|
111
|
+
renderContextTag(e, e.vBody != null ? e.vBody : e.body),
|
|
112
|
+
);
|
|
113
|
+
return `${content}<visible>\n${lines.join("\n")}\n</visible>\n`;
|
|
106
114
|
}
|
|
107
115
|
}
|
|
108
116
|
|
|
109
|
-
function renderContextTag(entry,
|
|
110
|
-
// schemeOf() returns NULL / "" for bare file paths; translate for the tag.
|
|
117
|
+
function renderContextTag(entry, projectedBody) {
|
|
111
118
|
const tag = entry.scheme ? entry.scheme : "file";
|
|
112
119
|
const turn = entry.source_turn ? ` turn="${entry.source_turn}"` : "";
|
|
113
120
|
const tokens = entry.aTokens != null ? ` tokens="${entry.aTokens}"` : "";
|
|
121
|
+
const lines = entry.vLines != null ? ` lines="${entry.vLines}"` : "";
|
|
114
122
|
const attrs =
|
|
115
123
|
typeof entry.attributes === "string"
|
|
116
124
|
? JSON.parse(entry.attributes)
|
|
@@ -128,21 +136,16 @@ function renderContextTag(entry, demotedSet) {
|
|
|
128
136
|
const stateAttr =
|
|
129
137
|
entry.state && entry.state !== "resolved" ? ` state="${entry.state}"` : "";
|
|
130
138
|
const outcomeAttr = entry.outcome ? ` outcome="${entry.outcome}"` : "";
|
|
131
|
-
const visibility =
|
|
132
|
-
? ` visibility="
|
|
133
|
-
: "";
|
|
134
|
-
const flag = demotedSet?.has(entry.path) ? " demoted" : "";
|
|
135
|
-
// Always render summary attribute on knowns — empty value hints the model
|
|
136
|
-
// it forgot to add searchable keywords.
|
|
139
|
+
const visibility =
|
|
140
|
+
entry.visibility === "archived" ? ` visibility="archived"` : "";
|
|
137
141
|
const summaryText =
|
|
138
142
|
typeof attrs?.summary === "string"
|
|
139
143
|
? attrs.summary.replace(/"/g, "'").slice(0, 80)
|
|
140
144
|
: "";
|
|
141
145
|
const summary = ` summary="${summaryText}"`;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return `<${tag} path="${entry.path}"${attrStr}>${entry.body}</${tag}>`;
|
|
146
|
+
const attrStr = `${turn}${status}${stateAttr}${outcomeAttr}${summary}${visibility}${tokens}${lines}`;
|
|
147
|
+
if (projectedBody) {
|
|
148
|
+
return `<${tag} path="${entry.path}"${attrStr}>${projectedBody}</${tag}>`;
|
|
146
149
|
}
|
|
147
150
|
return `<${tag} path="${entry.path}"${attrStr}/>`;
|
|
148
151
|
}
|
|
@@ -29,7 +29,7 @@ size. Resolution:
|
|
|
29
29
|
own body tokens.
|
|
30
30
|
- `sh` and `env` own multiple streaming channels (`sh://turn_N/{slug}_N`)
|
|
31
31
|
— no single target to point at. `tokens=` is omitted; the channels
|
|
32
|
-
render their own tokens in `<
|
|
32
|
+
render their own tokens in `<visible>`.
|
|
33
33
|
|
|
34
34
|
## Behavior
|
|
35
35
|
|
|
@@ -43,6 +43,6 @@ Log entries (`log://turn_N/{action}/{slug}`) are audit records —
|
|
|
43
43
|
summary, exit status, references to where the data lives — and never
|
|
44
44
|
carry the payload itself. Payload for streaming actions lives under the
|
|
45
45
|
producer's own scheme (`sh://`, `env://`, future `search://`, etc.) at
|
|
46
|
-
`category=data`, and is rendered inside `<
|
|
46
|
+
`category=data`, and is rendered inside `<visible>` by the known
|
|
47
47
|
plugin. Scheme determines category; data and logging never share a
|
|
48
48
|
scheme. See [scheme_category_split](#scheme_category_split).
|
package/src/plugins/log/log.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { stateToStatus } from "../../agent/httpStatus.js";
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
// content. For these, the action's cost lives on a separate data entry
|
|
5
|
-
// (sh/env: streaming channels; set/mv/cp: the target entry). Report
|
|
6
|
-
// tokens from the target when we can resolve it (set/mv/cp via
|
|
7
|
-
// attrs.path); omit entirely for sh/env (multiple channels, no single
|
|
8
|
-
// target to point at).
|
|
3
|
+
// sh/env span multiple channels; channels render their own tokens in <visible>.
|
|
9
4
|
const STREAM_NO_TOKENS = new Set(["sh", "env"]);
|
|
10
5
|
|
|
11
6
|
export default class Log {
|
|
@@ -17,10 +12,7 @@ export default class Log {
|
|
|
17
12
|
}
|
|
18
13
|
|
|
19
14
|
async assembleLog(content, ctx) {
|
|
20
|
-
//
|
|
21
|
-
// most recent prompt is rendered separately by the prompt plugin
|
|
22
|
-
// as `<prompt>`; everything older lives in the log so the model
|
|
23
|
-
// can see the full question history across a sustained run.
|
|
15
|
+
// Includes prior prompts; the latest prompt is rendered separately as <prompt>.
|
|
24
16
|
const latestPrompt = ctx.rows.findLast(
|
|
25
17
|
(r) => r.category === "prompt" && r.scheme === "prompt",
|
|
26
18
|
);
|
|
@@ -39,10 +31,7 @@ export default class Log {
|
|
|
39
31
|
}
|
|
40
32
|
}
|
|
41
33
|
|
|
42
|
-
//
|
|
43
|
-
// action — the plugin/tool that produced this log entry (set, get,
|
|
44
|
-
// search, update, error, etc.). Used as the XML tag name. Prompt
|
|
45
|
-
// entries live at prompt://N; they render as <prompt> in history.
|
|
34
|
+
// Action segment of log://turn_N/action/slug → XML tag.
|
|
46
35
|
function actionFromPath(path) {
|
|
47
36
|
if (path?.startsWith("prompt://")) return "prompt";
|
|
48
37
|
const match = path?.match(/^log:\/\/turn_\d+\/([^/]+)\//);
|
|
@@ -63,23 +52,30 @@ function renderLogTag(entry, rowsByPath) {
|
|
|
63
52
|
: entry.state
|
|
64
53
|
? stateToStatus(entry.state, entry.outcome)
|
|
65
54
|
: null;
|
|
66
|
-
|
|
55
|
+
// Suppress status on prompts; uniform 200 carries no signal.
|
|
56
|
+
const status =
|
|
57
|
+
statusValue != null && action !== "prompt"
|
|
58
|
+
? ` status="${statusValue}"`
|
|
59
|
+
: "";
|
|
67
60
|
const outcomeAttr = entry.outcome ? ` outcome="${entry.outcome}"` : "";
|
|
68
|
-
//
|
|
69
|
-
// represents — what the model would free by demoting it. For actions
|
|
70
|
-
// that reference a separate data entry (get/set/mv/cp), resolve via
|
|
71
|
-
// attrs.path and report the target's aTokens. For actions whose log
|
|
72
|
-
// body IS the cost-bearing content (search/update/error/ask_user,
|
|
73
|
-
// plus <get> slice reads), use the log entry's own aTokens. sh/env
|
|
74
|
-
// span multiple channel entries and are omitted — the channels
|
|
75
|
-
// render their own tokens in <context>.
|
|
61
|
+
// tokens = aTokens of the thing this tag represents (target via attrs.path, else self).
|
|
76
62
|
const isSlice = attrs?.lineStart != null;
|
|
77
63
|
const targetEntry = attrs?.path ? rowsByPath.get(attrs.path) : null;
|
|
78
64
|
let tokenSource = null;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
65
|
+
let lineSource = null;
|
|
66
|
+
if (STREAM_NO_TOKENS.has(action)) {
|
|
67
|
+
tokenSource = null;
|
|
68
|
+
lineSource = null;
|
|
69
|
+
} else if (isSlice) {
|
|
70
|
+
tokenSource = entry.aTokens;
|
|
71
|
+
lineSource = entry.vLines;
|
|
72
|
+
} else if (targetEntry) {
|
|
73
|
+
tokenSource = targetEntry.aTokens;
|
|
74
|
+
lineSource = targetEntry.vLines;
|
|
75
|
+
} else {
|
|
76
|
+
tokenSource = entry.aTokens;
|
|
77
|
+
lineSource = entry.vLines;
|
|
78
|
+
}
|
|
83
79
|
const tokens = tokenSource != null ? ` tokens="${tokenSource}"` : "";
|
|
84
80
|
const summary =
|
|
85
81
|
typeof attrs?.summary === "string"
|
|
@@ -89,16 +85,13 @@ function renderLogTag(entry, rowsByPath) {
|
|
|
89
85
|
typeof attrs?.query === "string" ? ` query="${attrs.query}"` : "";
|
|
90
86
|
const command =
|
|
91
87
|
typeof attrs?.command === "string" ? ` command="${attrs.command}"` : "";
|
|
92
|
-
// target= is the path the action touched (e.g. the file/known that was
|
|
93
|
-
// set, the URL that was fetched). Plugins store it in attrs.path when
|
|
94
|
-
// they write the log entry.
|
|
95
88
|
const target = attrs?.path ? ` target="${attrs.path}"` : "";
|
|
96
|
-
// Slice reads
|
|
97
|
-
// the <get> tag surfaces `lines="a-b/total"` — a concrete handle for
|
|
98
|
-
// the model to re-issue or compare against another slice.
|
|
89
|
+
// Slice reads emit lines="a-b/total"; others emit simple lines="N".
|
|
99
90
|
const lines = isSlice
|
|
100
91
|
? ` lines="${attrs.lineStart}-${attrs.lineEnd}/${attrs.totalLines}"`
|
|
101
|
-
:
|
|
92
|
+
: lineSource != null
|
|
93
|
+
? ` lines="${lineSource}"`
|
|
94
|
+
: "";
|
|
102
95
|
|
|
103
96
|
const attrStr = `${target}${status}${outcomeAttr}${query}${command}${summary}${lines}${tokens}`;
|
|
104
97
|
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
+
import config from "../../agent/config.js";
|
|
1
2
|
import msg from "../../agent/messages.js";
|
|
3
|
+
import { chatCompletionStream } from "../../llm/openaiStream.js";
|
|
4
|
+
import { retryWithBackoff } from "../../llm/retry.js";
|
|
2
5
|
|
|
3
|
-
const FETCH_TIMEOUT =
|
|
4
|
-
if (!FETCH_TIMEOUT) throw new Error("RUMMY_FETCH_TIMEOUT must be set");
|
|
6
|
+
const { FETCH_TIMEOUT } = config;
|
|
5
7
|
|
|
6
8
|
const PROVIDER = "ollama";
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
* Ollama LLM provider plugin. Registers with hooks.llm.providers if
|
|
10
|
-
* OLLAMA_BASE_URL is set; inert otherwise. Handles model aliases of the
|
|
11
|
-
* form `ollama/{modelName}` — e.g. `ollama/llama3.1:8b` or
|
|
12
|
-
* `ollama/library/qwen:7b` (Ollama accepts both bare and
|
|
13
|
-
* registry-qualified model names).
|
|
14
|
-
*/
|
|
10
|
+
// Inert unless OLLAMA_BASE_URL is set; ollama/{model[/registry]} aliases.
|
|
15
11
|
export default class Ollama {
|
|
16
12
|
#baseUrl;
|
|
17
13
|
|
|
@@ -41,70 +37,58 @@ export default class Ollama {
|
|
|
41
37
|
? AbortSignal.any([options.signal, timeoutSignal])
|
|
42
38
|
: timeoutSignal;
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const data = await response.json();
|
|
59
|
-
|
|
60
|
-
for (const choice of data.choices) {
|
|
61
|
-
const m = choice.message;
|
|
62
|
-
if (!m) continue;
|
|
63
|
-
const parts = [m.reasoning_content, m.reasoning, m.thinking].filter(
|
|
64
|
-
Boolean,
|
|
65
|
-
);
|
|
66
|
-
m.reasoning_content =
|
|
67
|
-
parts.length > 0 ? [...new Set(parts)].join("\n") : null;
|
|
40
|
+
try {
|
|
41
|
+
return await chatCompletionStream({
|
|
42
|
+
url: `${this.#baseUrl}/v1/chat/completions`,
|
|
43
|
+
headers: {},
|
|
44
|
+
body,
|
|
45
|
+
signal,
|
|
46
|
+
});
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (err.status) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
msg("error.ollama_api", { status: `${err.status} - ${err.body}` }),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
throw err;
|
|
68
54
|
}
|
|
69
|
-
|
|
70
|
-
return data;
|
|
71
55
|
}
|
|
72
56
|
|
|
73
57
|
async #getContextSize(model) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!response.ok) {
|
|
83
|
-
throw new Error(
|
|
84
|
-
msg("error.ollama_show_failed", {
|
|
85
|
-
status: response.status,
|
|
86
|
-
baseUrl: this.#baseUrl,
|
|
87
|
-
}),
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
const data = await response.json();
|
|
91
|
-
if (data.model_info) {
|
|
92
|
-
for (const [key, value] of Object.entries(data.model_info)) {
|
|
93
|
-
if (key.endsWith(".context_length")) return value;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
throw new Error(msg("error.ollama_no_context_length", { model }));
|
|
97
|
-
} catch (err) {
|
|
98
|
-
if (err.message.includes("Ollama")) throw err;
|
|
99
|
-
if (attempt < 2) {
|
|
100
|
-
await new Promise((r) => setTimeout(r, (attempt + 1) * 2000));
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
58
|
+
const fetchContext = async () => {
|
|
59
|
+
const response = await fetch(`${this.#baseUrl}/api/show`, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: { "Content-Type": "application/json" },
|
|
62
|
+
body: JSON.stringify({ model }),
|
|
63
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
64
|
+
});
|
|
65
|
+
if (!response.ok) {
|
|
103
66
|
throw new Error(
|
|
104
|
-
msg("error.
|
|
105
|
-
|
|
67
|
+
msg("error.ollama_show_failed", {
|
|
68
|
+
status: response.status,
|
|
69
|
+
baseUrl: this.#baseUrl,
|
|
70
|
+
}),
|
|
106
71
|
);
|
|
107
72
|
}
|
|
73
|
+
const data = await response.json();
|
|
74
|
+
if (data.model_info) {
|
|
75
|
+
for (const [key, value] of Object.entries(data.model_info)) {
|
|
76
|
+
if (key.endsWith(".context_length")) return value;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
throw new Error(msg("error.ollama_no_context_length", { model }));
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
return await retryWithBackoff(fetchContext, {
|
|
83
|
+
deadlineMs: FETCH_TIMEOUT,
|
|
84
|
+
isRetryable: (err) => !err.message.includes("Ollama"),
|
|
85
|
+
});
|
|
86
|
+
} catch (err) {
|
|
87
|
+
if (err.message.includes("Ollama")) throw err;
|
|
88
|
+
throw new Error(
|
|
89
|
+
msg("error.ollama_unreachable", { baseUrl: this.#baseUrl }),
|
|
90
|
+
{ cause: err },
|
|
91
|
+
);
|
|
108
92
|
}
|
|
109
93
|
}
|
|
110
94
|
}
|