Repository navigation
Commit f040a92
v0.75.0: plugin helpers (tw), in-place edits that count, per-block streaming (#341)
* Plugin helpers: a global `tw`, in-place edits that count, per-block streaming
Plugins get a built-in helper object and no longer have to return the
object they edited.
- `tw` (crates/tw-plugin/src/helpers.js) is plain JavaScript evaluated
together with the bridge at build time and baked into the snapshot:
appendSystem, texts, toolCalls, mapStrings, replace, streamReplace,
lines and pairs. It captures every builtin it uses before plugin code
runs, throws English errors that name a missing permission, and the
host trusts its output no more than hand-written plugin output. `tw` is
added to the sandbox's global allow-list.
- Replacement rules: one left-to-right scan, the first listed rule wins
at a position, replaced text is never rescanned, string finds are
literal, regular expressions behave like String.prototype.replace, and
secret placeholders are never matched or split.
- tw.streamReplace holds back only what a later piece could still
change and buffers each text block separately. The host now passes the
block number to the sandbox on every onReplyText / onReplyTextEnd call
(guest ABI 2); plugin code cannot see it.
- onRequest and onToolCall: when the hook returns undefined, the bridge
serializes the object it was given; if that differs from the input it
is handed to the host exactly as if the hook had returned it.
Otherwise the result stays "unchanged" without involving the host.
- The two default plugins use the helpers. Their behaviour is unchanged;
manifests.json is regenerated.
Tests cover every helper (normal use, misuse, missing permissions,
tampered builtins), a randomized split property test that runs inside
the sandbox (whole-text replace equals a naive reference, and any split
streamed through streamReplace equals the whole-text result without
cutting a placeholder or a surrogate pair), interleaved blocks, the
in-place rules for both hooks, and end-to-end runs through the gateway.
The latency example gains a "read a view, no return" case.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Count a stream-mode plugin's text as changed per block, not per held piece
A stream-mode reply plugin that holds text back hands out pieces that
differ from what it was given, so every such call counted as "changed"
in the run record. tw.streamReplace with a regular expression holds the
last characters of every piece, so nearly every answer was recorded as
changed even when no rule matched.
The gateway now compares, per text block, everything the plugin was
given with everything it handed back (in placeholder form). Matched
parts are dropped as they come; only the stretch that has not matched
yet is kept, so a block is not stored twice. The block counts as one
change if the two differ at the end. A plugin skipped after an error
counts only if what it already handed out differs; text it was still
holding goes on unchanged. Block mode keeps its per-block comparison.
Also drop a stale comment in the guest's Cargo.toml that described files
the repository no longer has.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* tw.replace: much faster on dense text
A 1 MB request with about 108,000 matches took about 130 ms of sandbox
CPU in tw.replace, close to the 200 ms request-hook limit and over it on
slower machines.
- All-string rules on a longer text: when no two find strings can
overlap (neither contains the other and no end of one starts another),
the single left-to-right scan picks exactly each rule's own leftmost
non-overlapping occurrences, so every rule is applied with one native
split/join. A replacement that shares no UTF-16 code unit with the
find strings still to come is written directly; otherwise a private-use
character absent from the text and the rules holds its place until all
rules are done, so replaced text is never rescanned and a removal
cannot join its neighbours into a new match. Duplicate find strings
keep the first rule. Other rule sets fall back to the scan.
- tw.streamReplace uses the same path when a section ends (at a
placeholder or the end of the block) and, when no find string overlaps
itself, for the part of a piece before the hold-back point.
- The general scan calls captured builtins through bound functions
instead of Reflect.apply wrappers, keeps each rule's next match on the
rule itself and writes plain replacements inline. The check for a
trailing partial placeholder looks for "<" natively.
Sandbox CPU on a 1 MB text with about 108,000 matches, before / after:
string rules 132 / 20 ms, mixed with a regular expression 147 / 112 ms,
streamed in 64-character pieces 477 / 181 ms (strings) and 502 / 210 ms
(mixed). The randomized split property test now also generates
all-string rule sets on long texts with coarse splits, which covers the
new path; the dense test no longer depends on machine speed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* v0.75.0
Plugin helpers (tw) and in-place edits; every choice of an answer is
checked by the dangerous-command rules and restored per choice; reply
plugin fixes for split placeholders, skipped plugins and answers with
several choices. Protocol 48 and the request store schema unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>1 parent 4184876 commit f040a92
28 files changed
Lines changed: 3363 additions & 168 deletions
File tree
- crates
- tw-gateway
- src/plugin
- defaults
- reply
- tests
- sandbox/tests
- tests
- tw-plugin
- examples
- guest
- src
- src
- tests
- common
- corpus
- release-notes
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
57 | | - | |
| 57 | + | |
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
35 | | - | |
| 35 | + | |
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
| |||
68 | 68 | | |
69 | 69 | | |
70 | 70 | | |
71 | | - | |
| 71 | + | |
72 | 72 | | |
73 | 73 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
12 | 15 | | |
13 | 16 | | |
14 | 17 | | |
| |||
36 | 39 | | |
37 | 40 | | |
38 | 41 | | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
| 42 | + | |
43 | 43 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
16 | 20 | | |
17 | 21 | | |
18 | 22 | | |
| |||
52 | 56 | | |
53 | 57 | | |
54 | 58 | | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | 59 | | |
70 | 60 | | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
76 | | - | |
77 | | - | |
78 | | - | |
79 | | - | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
84 | 64 | | |
85 | 65 | | |
86 | 66 | | |
87 | | - | |
88 | | - | |
89 | | - | |
| 67 | + | |
| 68 | + | |
90 | 69 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
43 | 44 | | |
44 | 45 | | |
45 | 46 | | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
46 | 50 | | |
47 | 51 | | |
48 | | - | |
| 52 | + | |
49 | 53 | | |
50 | | - | |
| 54 | + | |
51 | 55 | | |
52 | 56 | | |
53 | 57 | | |
| |||
302 | 306 | | |
303 | 307 | | |
304 | 308 | | |
305 | | - | |
| 309 | + | |
| 310 | + | |
306 | 311 | | |
307 | 312 | | |
308 | 313 | | |
309 | 314 | | |
310 | 315 | | |
311 | 316 | | |
312 | 317 | | |
313 | | - | |
| 318 | + | |
314 | 319 | | |
315 | 320 | | |
316 | 321 | | |
317 | | - | |
| 322 | + | |
318 | 323 | | |
319 | 324 | | |
320 | 325 | | |
| |||
0 commit comments