@possumtech/rummy 2.2.1 → 2.3.1
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/package.json +14 -6
- package/service.js +18 -10
- package/src/agent/AgentLoop.js +2 -11
- package/src/agent/ContextAssembler.js +34 -3
- package/src/agent/Entries.js +16 -89
- package/src/agent/ProjectAgent.js +1 -16
- package/src/agent/TurnExecutor.js +12 -52
- package/src/agent/XmlParser.js +30 -117
- package/src/agent/errors.js +3 -22
- package/src/agent/materializeContext.js +3 -11
- package/src/hooks/Hooks.js +0 -29
- package/src/lib/hedberg/hedberg.js +4 -14
- package/src/lib/hedberg/marker.js +15 -59
- package/src/llm/LlmProvider.js +13 -26
- package/src/llm/errors.js +3 -11
- package/src/llm/openaiStream.js +6 -46
- package/src/plugins/ask_user/ask_user.js +12 -17
- package/src/plugins/budget/README.md +46 -8
- package/src/plugins/budget/budget.js +23 -42
- package/src/plugins/cp/cp.js +28 -18
- package/src/plugins/env/env.js +11 -7
- package/src/plugins/error/error.js +8 -37
- package/src/plugins/get/get.js +42 -24
- package/src/plugins/google/google.js +23 -3
- package/src/plugins/helpers.js +34 -50
- package/src/plugins/instructions/README.md +2 -2
- package/src/plugins/instructions/instructions-user.md +1 -1
- package/src/plugins/instructions/instructions.js +19 -6
- package/src/plugins/known/known.js +1 -8
- package/src/plugins/log/log.js +15 -1
- package/src/plugins/mv/mv.js +29 -19
- package/src/plugins/persona/persona.js +4 -4
- package/src/plugins/prompt/README.md +1 -1
- package/src/plugins/prompt/prompt.js +1 -1
- package/src/plugins/rm/rm.js +26 -15
- package/src/plugins/rm/rmDoc.md +0 -2
- package/src/plugins/set/set.js +37 -84
- package/src/plugins/set/setDoc.md +16 -16
- package/src/plugins/sh/sh.js +10 -8
- package/src/plugins/skill/skillDoc.md +1 -1
- package/src/plugins/unknown/README.md +1 -1
- package/src/plugins/unknown/unknown.js +2 -6
- package/src/plugins/update/update.js +3 -2
- package/src/plugins/update/updateDoc.md +1 -1
- package/.env.example +0 -152
- package/.xai.key +0 -1
- package/PLUGINS.md +0 -962
- package/SPEC.md +0 -1897
- package/biome/no-fallbacks.grit +0 -50
- package/gemini.key +0 -1
|
@@ -12,7 +12,7 @@ export default class Prompt {
|
|
|
12
12
|
"summarized",
|
|
13
13
|
);
|
|
14
14
|
core.on("turn.started", this.onTurnStarted.bind(this));
|
|
15
|
-
core.filter("assembly.user", this.assemblePrompt.bind(this),
|
|
15
|
+
core.filter("assembly.user", this.assemblePrompt.bind(this), 30);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
async onTurnStarted({ rummy, mode, prompt, isContinuation }) {
|
package/src/plugins/rm/rm.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import Entries from "../../agent/Entries.js";
|
|
2
|
-
import {
|
|
2
|
+
import { countTokens } from "../../agent/tokens.js";
|
|
3
|
+
import {
|
|
4
|
+
projectEmission,
|
|
5
|
+
storePatternResult,
|
|
6
|
+
summarizeEmission,
|
|
7
|
+
} from "../helpers.js";
|
|
3
8
|
import docs from "./rmDoc.js";
|
|
4
9
|
|
|
5
10
|
const LOG_ACTION_RE = /^log:\/\/turn_\d+\/(\w+)\//;
|
|
@@ -64,9 +69,6 @@ export default class Rm {
|
|
|
64
69
|
entry.attributes.body,
|
|
65
70
|
);
|
|
66
71
|
|
|
67
|
-
// Manifest: list what would be removed without performing the rm.
|
|
68
|
-
// Safety idiom for destructive bulk ops — the model can audit a
|
|
69
|
-
// glob's reach before committing to it.
|
|
70
72
|
if (entry.attributes.manifest !== undefined) {
|
|
71
73
|
await storePatternResult(
|
|
72
74
|
store,
|
|
@@ -98,23 +100,29 @@ export default class Rm {
|
|
|
98
100
|
const fileMatches = matches.filter((m) => m.scheme === null);
|
|
99
101
|
const schemeMatches = matches.filter((m) => m.scheme !== null);
|
|
100
102
|
|
|
101
|
-
// Scheme entries: remove all, write one aggregate result entry
|
|
102
103
|
for (const match of schemeMatches)
|
|
103
104
|
await store.rm({ runId: runId, path: match.path });
|
|
104
105
|
if (schemeMatches.length > 0) {
|
|
105
|
-
const
|
|
106
|
+
const beforeTokens = schemeMatches.reduce(
|
|
107
|
+
(n, m) => n + countTokens(m.body),
|
|
108
|
+
0,
|
|
109
|
+
);
|
|
106
110
|
await store.set({
|
|
107
111
|
runId,
|
|
108
112
|
turn,
|
|
109
113
|
path: entry.resultPath,
|
|
110
|
-
body:
|
|
114
|
+
body: "",
|
|
111
115
|
state: "resolved",
|
|
112
|
-
attributes: {
|
|
116
|
+
attributes: {
|
|
117
|
+
path: target,
|
|
118
|
+
beforeActionTokens: beforeTokens,
|
|
119
|
+
afterActionTokens: 0,
|
|
120
|
+
},
|
|
113
121
|
loopId,
|
|
114
122
|
});
|
|
115
123
|
}
|
|
116
124
|
|
|
117
|
-
// File
|
|
125
|
+
// File matches: individual proposals (require user resolution).
|
|
118
126
|
if (fileMatches.length > 0 && schemeMatches.length > 0)
|
|
119
127
|
await store.rm({ runId: runId, path: entry.resultPath });
|
|
120
128
|
for (const match of fileMatches) {
|
|
@@ -126,20 +134,23 @@ export default class Rm {
|
|
|
126
134
|
runId,
|
|
127
135
|
turn,
|
|
128
136
|
path: resultPath,
|
|
129
|
-
body:
|
|
137
|
+
body: "",
|
|
130
138
|
state: "proposed",
|
|
131
|
-
attributes: {
|
|
139
|
+
attributes: {
|
|
140
|
+
path: match.path,
|
|
141
|
+
beforeActionTokens: countTokens(match.body),
|
|
142
|
+
afterActionTokens: 0,
|
|
143
|
+
},
|
|
132
144
|
loopId,
|
|
133
145
|
});
|
|
134
146
|
}
|
|
135
147
|
}
|
|
136
148
|
|
|
137
149
|
full(entry) {
|
|
138
|
-
|
|
139
|
-
return entry.body ? `${header}\n${entry.body}` : header;
|
|
150
|
+
return projectEmission(entry.body);
|
|
140
151
|
}
|
|
141
152
|
|
|
142
|
-
summary() {
|
|
143
|
-
return
|
|
153
|
+
summary(entry) {
|
|
154
|
+
return summarizeEmission(entry.body);
|
|
144
155
|
}
|
|
145
156
|
}
|
package/src/plugins/rm/rmDoc.md
CHANGED
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Example: <rm path="src/config.js"/>
|
|
4
4
|
<!-- File removal. Simplest form. -->
|
|
5
|
-
|
|
6
5
|
Example: <rm path="known://countries/france/*" manifest/>
|
|
7
6
|
<!-- Manifest before deleting. Safety pattern for bulk operations. -->
|
|
8
|
-
|
|
9
7
|
Example: <rm path="log://turn_3/get/**"/>
|
|
10
8
|
<!-- Bulk delete by glob. Recursive scheme glob; clears a turn's get logs in one call. -->
|
|
11
9
|
|
package/src/plugins/set/set.js
CHANGED
|
@@ -2,7 +2,11 @@ import Entries from "../../agent/Entries.js";
|
|
|
2
2
|
import { countTokens } from "../../agent/tokens.js";
|
|
3
3
|
import Hedberg, { generatePatch } from "../../lib/hedberg/hedberg.js";
|
|
4
4
|
import File from "../file/file.js";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
projectEmission,
|
|
7
|
+
storePatternResult,
|
|
8
|
+
summarizeEmission,
|
|
9
|
+
} from "../helpers.js";
|
|
6
10
|
import docs from "./setDoc.js";
|
|
7
11
|
|
|
8
12
|
const VALID_VISIBILITY = { archived: 1, summarized: 1, visible: 1 };
|
|
@@ -13,10 +17,6 @@ function isSetProposal(path) {
|
|
|
13
17
|
return m?.[1] === "set";
|
|
14
18
|
}
|
|
15
19
|
|
|
16
|
-
// Cap the size of the current-body context surfaced on conflict. Big
|
|
17
|
-
// enough for typical known:// entries (plans, notes) and a useful slice
|
|
18
|
-
// of files; small enough that a 100k-line file doesn't blow the budget
|
|
19
|
-
// on every conflict. The model can `<get>` the path for the full body.
|
|
20
20
|
const CONFLICT_FEEDBACK_MAX_CHARS = 4000;
|
|
21
21
|
function truncateForFeedback(body) {
|
|
22
22
|
if (body == null) return null;
|
|
@@ -41,9 +41,7 @@ export default class Set {
|
|
|
41
41
|
});
|
|
42
42
|
core.filter("proposal.accepting", this.#vetoReadonly.bind(this));
|
|
43
43
|
core.filter("proposal.content", this.#preferExistingBody.bind(this));
|
|
44
|
-
//
|
|
45
|
-
// path-coupled. Any plugin emitting a proposal in that shape
|
|
46
|
-
// (set, cp, future tools) gets fs materialization for free.
|
|
44
|
+
// Shape-coupled (attrs.path + attrs.patched) — cp/set share one materializer.
|
|
47
45
|
core.on("proposal.accepted", this.#materializeFile.bind(this));
|
|
48
46
|
}
|
|
49
47
|
|
|
@@ -73,19 +71,13 @@ export default class Set {
|
|
|
73
71
|
|
|
74
72
|
async #materializeFile(ctx) {
|
|
75
73
|
const { attrs, runId, projectId, projectRoot, db, entries } = ctx;
|
|
76
|
-
// Shape gate, not path gate: any accepted proposal whose
|
|
77
|
-
// attributes describe a file materialization (target path +
|
|
78
|
-
// authoritative patched body) lands a fresh file body and writes
|
|
79
|
-
// to disk. Lets cp/set/future tools share one materializer.
|
|
80
74
|
if (!attrs?.path || attrs?.patched == null) return;
|
|
81
75
|
|
|
82
76
|
const existing = await entries.getBody(runId, attrs.path);
|
|
83
77
|
const isNewFile = existing === null;
|
|
84
78
|
const patched = attrs.patched;
|
|
85
79
|
const turn = (await db.get_run_by_id.get({ id: runId })).next_turn;
|
|
86
|
-
// Visibility precedence: explicit
|
|
87
|
-
// the model's tag attribute through) > current entry visibility
|
|
88
|
-
// (preserves an earlier <get>'s promotion) > scheme default.
|
|
80
|
+
// Visibility precedence: explicit attr > existing state > scheme default.
|
|
89
81
|
const existingState = await entries.getState(runId, attrs.path);
|
|
90
82
|
const visibility = attrs.visibility
|
|
91
83
|
? attrs.visibility
|
|
@@ -120,11 +112,7 @@ export default class Set {
|
|
|
120
112
|
const rawTags = typeof attrs.tags === "string" ? attrs.tags : null;
|
|
121
113
|
const tagsText = rawTags ? rawTags.slice(0, 80) : null;
|
|
122
114
|
|
|
123
|
-
// log:// is
|
|
124
|
-
// updates are fine (no body); rewriting the body destroys history.
|
|
125
|
-
// Models reach for this when the Demote example pattern primes
|
|
126
|
-
// `<set ... visibility="summarized">` and they tack on a body line —
|
|
127
|
-
// 405 here teaches the shape that's actually allowed.
|
|
115
|
+
// log:// is immutable; visibility flips OK, body rewrites are not.
|
|
128
116
|
if (attrs.path?.startsWith("log://") && entry.body) {
|
|
129
117
|
await store.set({
|
|
130
118
|
runId,
|
|
@@ -159,11 +147,6 @@ export default class Set {
|
|
|
159
147
|
return;
|
|
160
148
|
}
|
|
161
149
|
|
|
162
|
-
// Refuse parse-error edits (e.g., malformed sed). Without this the
|
|
163
|
-
// XmlParser would have either silently produced a corrupted edit
|
|
164
|
-
// or fallen through to body-replace, overwriting the target with
|
|
165
|
-
// the literal sed text. Surfacing the error gives the model a
|
|
166
|
-
// concrete signal it can adapt to.
|
|
167
150
|
if (attrs.error) {
|
|
168
151
|
await store.set({
|
|
169
152
|
runId,
|
|
@@ -178,10 +161,7 @@ export default class Set {
|
|
|
178
161
|
return;
|
|
179
162
|
}
|
|
180
163
|
|
|
181
|
-
// Manifest: universal preview gate
|
|
182
|
-
// branch so visibility flips, SEARCH/REPLACE edits, sed substitutions,
|
|
183
|
-
// pattern writes, and direct writes all support
|
|
184
|
-
// "list-without-doing" with the same flag.
|
|
164
|
+
// Manifest: universal preview gate, fires before any operational branch.
|
|
185
165
|
if (attrs.manifest !== undefined && attrs.path) {
|
|
186
166
|
const matches = await store.getEntriesByPattern(
|
|
187
167
|
runId,
|
|
@@ -251,36 +231,28 @@ export default class Set {
|
|
|
251
231
|
return;
|
|
252
232
|
}
|
|
253
233
|
|
|
254
|
-
// Build the new content. Either from the marker-parsed operation
|
|
255
|
-
// list (NEW / PREPEND / APPEND / REPLACE / DELETE / SEARCH+REPLACE)
|
|
256
|
-
// or from the plain body (full-replace shorthand).
|
|
257
234
|
const target = attrs.path;
|
|
258
235
|
if (!target) return;
|
|
259
236
|
let newContent;
|
|
260
237
|
if (attrs.operations) {
|
|
261
238
|
const existing = await store.getBody(runId, target);
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
error: `${target} not found in context`,
|
|
277
|
-
},
|
|
278
|
-
});
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
239
|
+
// Missing-path recovery: search_replace → append (replace text only),
|
|
240
|
+
// delete → drop. Lets the model's edit-shaped emission land on a
|
|
241
|
+
// fresh path without first having to write a NEW.
|
|
242
|
+
const operations =
|
|
243
|
+
existing === null
|
|
244
|
+
? attrs.operations.flatMap((op) => {
|
|
245
|
+
if (op.op === "search_replace") {
|
|
246
|
+
return [{ op: "append", content: op.replace }];
|
|
247
|
+
}
|
|
248
|
+
if (op.op === "delete") return [];
|
|
249
|
+
return [op];
|
|
250
|
+
})
|
|
251
|
+
: attrs.operations;
|
|
252
|
+
if (operations.length === 0) return;
|
|
281
253
|
const result = Set.#applyOperations(
|
|
282
254
|
existing == null ? "" : existing,
|
|
283
|
-
|
|
255
|
+
operations,
|
|
284
256
|
);
|
|
285
257
|
if (result.error) {
|
|
286
258
|
await store.set({
|
|
@@ -308,8 +280,7 @@ export default class Set {
|
|
|
308
280
|
if (newContent !== undefined) {
|
|
309
281
|
const scheme = Entries.scheme(target);
|
|
310
282
|
if (scheme === null) {
|
|
311
|
-
// File write
|
|
312
|
-
// writes to disk on accept.
|
|
283
|
+
// File write: proposed entry; #materializeFile writes to disk on accept.
|
|
313
284
|
const existing = await store.getBody(runId, target);
|
|
314
285
|
const oldContent = existing == null ? "" : existing;
|
|
315
286
|
const udiff = generatePatch(target, oldContent, newContent);
|
|
@@ -319,22 +290,20 @@ export default class Set {
|
|
|
319
290
|
runId,
|
|
320
291
|
turn,
|
|
321
292
|
path: entry.resultPath,
|
|
322
|
-
body:
|
|
293
|
+
body: attrs.inner,
|
|
323
294
|
state: "proposed",
|
|
324
295
|
attributes: {
|
|
325
296
|
path: target,
|
|
326
297
|
patch: udiff,
|
|
327
298
|
patched: newContent,
|
|
328
|
-
beforeTokens,
|
|
329
|
-
afterTokens,
|
|
299
|
+
beforeActionTokens: beforeTokens,
|
|
300
|
+
afterActionTokens: afterTokens,
|
|
330
301
|
tags: tagsText,
|
|
331
302
|
},
|
|
332
303
|
loopId,
|
|
333
304
|
});
|
|
334
305
|
} else if (attrs.filter || target.includes("*")) {
|
|
335
|
-
// Pattern body-update:
|
|
336
|
-
// entry. Operations don't apply here (this is a bulk
|
|
337
|
-
// metadata-flavored body assignment).
|
|
306
|
+
// Pattern body-update: bulk body assignment, no operations.
|
|
338
307
|
const matches = await store.getEntriesByPattern(
|
|
339
308
|
runId,
|
|
340
309
|
target,
|
|
@@ -357,7 +326,6 @@ export default class Set {
|
|
|
357
326
|
{ loopId },
|
|
358
327
|
);
|
|
359
328
|
} else {
|
|
360
|
-
// Direct scheme write; same diff-against-existing shape as file writes.
|
|
361
329
|
const existing = await store.getBody(runId, target);
|
|
362
330
|
const oldContent = existing == null ? "" : existing;
|
|
363
331
|
const udiff = generatePatch(target, oldContent, newContent);
|
|
@@ -378,21 +346,20 @@ export default class Set {
|
|
|
378
346
|
runId,
|
|
379
347
|
turn,
|
|
380
348
|
path: entry.resultPath,
|
|
381
|
-
body:
|
|
349
|
+
body: attrs.inner,
|
|
382
350
|
state: "resolved",
|
|
383
351
|
loopId,
|
|
384
352
|
attributes: {
|
|
385
353
|
path: target,
|
|
386
354
|
patch: udiff,
|
|
387
|
-
beforeTokens,
|
|
388
|
-
afterTokens,
|
|
355
|
+
beforeActionTokens: beforeTokens,
|
|
356
|
+
afterActionTokens: afterTokens,
|
|
389
357
|
tags: tagsText,
|
|
390
358
|
},
|
|
391
359
|
});
|
|
392
360
|
}
|
|
393
361
|
}
|
|
394
362
|
|
|
395
|
-
// Apply visibility after all write operations
|
|
396
363
|
if (visibilityAttr && attrs.path) {
|
|
397
364
|
const target = attrs.path;
|
|
398
365
|
const scheme = Entries.scheme(target);
|
|
@@ -415,38 +382,24 @@ export default class Set {
|
|
|
415
382
|
|
|
416
383
|
full(entry) {
|
|
417
384
|
const attrs = entry.attributes;
|
|
418
|
-
const target = attrs.path || entry.path;
|
|
419
385
|
if (attrs.error) {
|
|
420
|
-
const
|
|
386
|
+
const target = attrs.path || entry.path;
|
|
387
|
+
const lines = [`error at ${target}: ${attrs.error}`];
|
|
421
388
|
if (attrs.attempted) {
|
|
422
389
|
lines.push("", "--- attempted ---", attrs.attempted);
|
|
423
390
|
}
|
|
424
391
|
if (attrs.currentBody != null) {
|
|
425
392
|
lines.push("", `--- current body of ${target} ---`, attrs.currentBody);
|
|
426
393
|
}
|
|
427
|
-
return lines.join("\n");
|
|
394
|
+
return projectEmission(lines.join("\n"));
|
|
428
395
|
}
|
|
429
|
-
|
|
430
|
-
attrs.beforeTokens != null
|
|
431
|
-
? ` ${attrs.beforeTokens}→${attrs.afterTokens} tokens`
|
|
432
|
-
: "";
|
|
433
|
-
if (!attrs.patch) return `# set ${target}${tokens}`;
|
|
434
|
-
return `# set ${target}${tokens}\n${attrs.patch}`;
|
|
396
|
+
return projectEmission(entry.body);
|
|
435
397
|
}
|
|
436
398
|
|
|
437
399
|
summary(entry) {
|
|
438
|
-
|
|
439
|
-
// Contract: summarized projections are ≤ SUMMARY_MAX_CHARS. The
|
|
440
|
-
// merge body for an edit can be many KB; truncate. The model
|
|
441
|
-
// reads the full body via promotion to visible if it needs the
|
|
442
|
-
// edit's exact content.
|
|
443
|
-
return entry.body.slice(0, SUMMARY_MAX_CHARS);
|
|
400
|
+
return summarizeEmission(entry.body);
|
|
444
401
|
}
|
|
445
402
|
|
|
446
|
-
// Walk the parsed marker operation list against a starting body, returning
|
|
447
|
-
// the final body or the first error. SEARCH/REPLACE and DELETE go through
|
|
448
|
-
// Hedberg.replace (fuzzy whitespace match); NEW/REPLACE/PREPEND/APPEND
|
|
449
|
-
// are direct string operations.
|
|
450
403
|
static #applyOperations(currentBody, operations) {
|
|
451
404
|
let body = currentBody;
|
|
452
405
|
for (const op of operations) {
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
## <set path="{path}" tags="{topical,searchable,folksonomic,internal,tags}">[content or edit]</set> - Create, edit, or update an entry or file
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
* The <set/> command's SEARCH/REPLACE string literal syntax uses HEREDOC instead of git conflict markers
|
|
5
|
-
* The `{SEARCH|REPLACE|NEW|APPEND|PREPEND|DELETE} Operative Labels determine the type of edit
|
|
3
|
+
YOU SHOULD prefer minimal and multiple atomic edits to reduce the frequency and severity of conflicts and errors
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
* The <set/> command requires matching HEREDOC label string literal syntax
|
|
6
|
+
|
|
7
|
+
* Special Operative Labels: ({SEARCH|REPLACE|NEW|PREPEND|APPEND|DELETE}) dictate the type of edit
|
|
8
|
+
SEARCH/REPLACE - SEARCH/REPLACE string literal syntax uses HEREDOC in place of git conflict markers
|
|
9
|
+
NEW - Create (or clobber) entry content
|
|
10
|
+
PREPEND - Prepend content at beginning of existing entry
|
|
11
|
+
APPEND - Append content to end of existing entry
|
|
12
|
+
DELETE - Delete matching content in existing entry
|
|
8
13
|
|
|
9
14
|
Example:
|
|
10
|
-
<set path="src/main.go"
|
|
15
|
+
<set path="src/main.go"><<SEARCH
|
|
11
16
|
exact
|
|
12
17
|
text
|
|
13
18
|
to be
|
|
@@ -17,27 +22,23 @@ Example:
|
|
|
17
22
|
replacement
|
|
18
23
|
text
|
|
19
24
|
REPLACE</set>
|
|
20
|
-
<!-- SEARCH/REPLACE: surgical edit, fuzzy on whitespace. Multiple pairs in one body apply in order. -->
|
|
21
25
|
|
|
22
26
|
Example:
|
|
23
|
-
<set path="src/main.go"><<NEW
|
|
27
|
+
<set path="src/main.go" tags="go,source,unlinted"><<NEW
|
|
24
28
|
package main
|
|
25
29
|
|
|
26
30
|
func main() {}
|
|
27
31
|
NEW</set>
|
|
28
|
-
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
<set path="known://plan" tags="docs"><<PREPEND0
|
|
35
|
+
Documenting the <<PREPEND label
|
|
36
|
+
PREPEND0</set>
|
|
29
37
|
|
|
30
38
|
Example:
|
|
31
39
|
<set path="known://plan" tags="plan,project,todo"><<APPEND
|
|
32
40
|
- [ ] new task
|
|
33
41
|
APPEND</set>
|
|
34
|
-
<!-- APPEND adds to the end; PREPEND to the start. -->
|
|
35
|
-
|
|
36
|
-
Example:
|
|
37
|
-
<set path="known://plan" tags="docs"><<PREPEND0
|
|
38
|
-
Documenting the <<PREPEND label
|
|
39
|
-
PREPEND0</set>
|
|
40
|
-
<!-- APPEND adds to the end; PREPEND to the start. -->
|
|
41
42
|
|
|
42
43
|
Example:
|
|
43
44
|
<set path="src/main.go"><<DELETE
|
|
@@ -49,4 +50,3 @@ Example:
|
|
|
49
50
|
<set path="docs/guide.md" tags="docs"><<GUIDE
|
|
50
51
|
The pair is <<SEARCH ... SEARCH<<REPLACE ... REPLACE.
|
|
51
52
|
GUIDE</set>
|
|
52
|
-
<!-- Any IDENT brackets opaque body. Use a custom IDENT (GUIDE, EOF, DOC, file paths, etc.) for bodies that contain `<<` literally. -->
|
package/src/plugins/sh/sh.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
logPathToDataBase,
|
|
3
|
+
projectEmission,
|
|
4
|
+
streamSummary,
|
|
5
|
+
summarizeEmission,
|
|
6
|
+
} from "../helpers.js";
|
|
2
7
|
import docs from "./shDoc.js";
|
|
3
8
|
|
|
4
9
|
const LOG_ACTION_RE = /^log:\/\/turn_\d+\/(\w+)\//;
|
|
@@ -8,10 +13,6 @@ export default class Sh {
|
|
|
8
13
|
|
|
9
14
|
constructor(core) {
|
|
10
15
|
this.#core = core;
|
|
11
|
-
// Streaming stdout/stderr is time-indexed activity output, not
|
|
12
|
-
// topic-indexed state — category="logging" so it renders in <log>
|
|
13
|
-
// adjacent to its action entry, not in <summary>/<visible> next
|
|
14
|
-
// to knowns and files. SPEC #streaming_entries.
|
|
15
16
|
core.registerScheme({ category: "logging" });
|
|
16
17
|
core.on("handler", this.handler.bind(this));
|
|
17
18
|
core.on("visible", this.full.bind(this));
|
|
@@ -46,13 +47,11 @@ export default class Sh {
|
|
|
46
47
|
runId: ctx.runId,
|
|
47
48
|
path: ctx.path,
|
|
48
49
|
state: "resolved",
|
|
49
|
-
body: `ran '${command}' (in progress). Output: ${dataBase}_1, ${dataBase}_2`,
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
async handler(entry, rummy) {
|
|
54
54
|
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
55
|
-
// 202 with command summary, empty body; stdout/stderr entries created on accept.
|
|
56
55
|
await store.set({
|
|
57
56
|
runId,
|
|
58
57
|
turn,
|
|
@@ -64,11 +63,14 @@ export default class Sh {
|
|
|
64
63
|
});
|
|
65
64
|
}
|
|
66
65
|
|
|
66
|
+
// log:// entries: emission, tab-indented. sh:// entries: stream bytes verbatim.
|
|
67
67
|
full(entry) {
|
|
68
|
-
|
|
68
|
+
if (entry.path.startsWith("log://")) return projectEmission(entry.body);
|
|
69
|
+
return entry.body;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
summary(entry) {
|
|
73
|
+
if (entry.path.startsWith("log://")) return summarizeEmission(entry.body);
|
|
72
74
|
return streamSummary("sh", entry);
|
|
73
75
|
}
|
|
74
76
|
}
|
|
@@ -9,7 +9,7 @@ The Rumsfeld mechanism. The model registers what it doesn't know before acting.
|
|
|
9
9
|
- **Tool**: `unknown`
|
|
10
10
|
- **Category**: `unknown`
|
|
11
11
|
- **Handler**: None — recorded by TurnExecutor, deduplicated against existing unknowns.
|
|
12
|
-
- **Filter**: `assembly.
|
|
12
|
+
- **Filter**: `assembly.system` at priority 350 — renders `<unknowns>` at the bottom of the system message (after `<summary>` 200, `<visible>` 250, `<log>` 300). Open work surfaces alongside the rest of the accumulated state in system; the user message holds prompt + budget + per-turn requirements.
|
|
13
13
|
|
|
14
14
|
## Projection
|
|
15
15
|
|
|
@@ -9,10 +9,8 @@ export default class Unknown {
|
|
|
9
9
|
core.on("handler", this.handler.bind(this));
|
|
10
10
|
core.on("visible", this.full.bind(this));
|
|
11
11
|
core.on("summarized", this.summary.bind(this));
|
|
12
|
-
core.filter("assembly.
|
|
13
|
-
//
|
|
14
|
-
// via <set path="unknown://..."/>. The unknown:// scheme lifecycle
|
|
15
|
-
// is taught in instructions-user.md, not in a separate tooldoc.
|
|
12
|
+
core.filter("assembly.system", this.assembleUnknowns.bind(this), 350);
|
|
13
|
+
// Written via <set path="unknown://...">; lifecycle in instructions-user.md.
|
|
16
14
|
core.markHidden();
|
|
17
15
|
}
|
|
18
16
|
|
|
@@ -33,7 +31,6 @@ export default class Unknown {
|
|
|
33
31
|
return;
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
// tags > body for slug; lets the model round-trip via <get>.
|
|
37
34
|
const unknownPath = await store.slugPath(
|
|
38
35
|
runId,
|
|
39
36
|
"unknown",
|
|
@@ -54,7 +51,6 @@ export default class Unknown {
|
|
|
54
51
|
return entry.body;
|
|
55
52
|
}
|
|
56
53
|
|
|
57
|
-
// First SUMMARY_MAX_CHARS of the body. Matches <known> / <prompt>.
|
|
58
54
|
summary(entry) {
|
|
59
55
|
if (!entry.body) return "";
|
|
60
56
|
return entry.body.slice(0, SUMMARY_MAX_CHARS);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { projectEmission } from "../helpers.js";
|
|
1
2
|
import docs from "./updateDoc.js";
|
|
2
3
|
|
|
3
|
-
const CONTRACT_REMINDER = "
|
|
4
|
+
const CONTRACT_REMINDER = "UPDATE missing";
|
|
4
5
|
|
|
5
6
|
const EMPTY_RESPONSE_REMINDER = "Response empty";
|
|
6
7
|
|
|
@@ -55,7 +56,7 @@ export default class Update {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
full(entry) {
|
|
58
|
-
return
|
|
59
|
+
return projectEmission(entry.body);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
summary(entry) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
## <update status="N">{ direct answer or one-line summary }</update> - Turn termination
|
|
1
|
+
## <update status="N">{ direct one-line answer or one-line summary }</update> - Turn termination
|
|
2
2
|
|
|
3
3
|
YOU MUST conclude every turn with one (and only one) <update status="N"></update>.
|
|
4
4
|
YOU MUST keep the update body to <= 80 characters.
|