pi-roundtable-sandbox 0.24.0 → 0.24.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/CHANGELOG.md +7 -0
- package/README.md +8 -0
- package/package.json +2 -2
- package/src/claim.ts +33 -5
- package/src/index.ts +1 -0
- package/src/plugin.ts +11 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.24.1] - 2026-10-29
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `sandbox({ replyText })`: rewrites each successful model reply on the host before it is split and posted (`(text, { channel, space }) => string | Promise<string>`), for example to convert stray Simplified Chinese to Traditional. Failure texts never pass through it, the worker's history keeps the model's own words, and a throw or a non-string fails the turn like any other failure. Host-only: no worker protocol change, no image rebuild. An empty result posts nothing, as an empty model reply does.
|
|
10
|
+
- `SandboxClaimOptions.reportFailure` receives the error as its second argument, and the plugin logs it with the channel.
|
|
11
|
+
|
|
5
12
|
## [0.24.0] - 2026-10-29
|
|
6
13
|
|
|
7
14
|
The core peer range is now `>=0.24.0 <0.25.0`. Every new option is optional; with none set, behavior, mounts, files on disk, the worker protocol and the public API are as in 0.23.x, and `PiSandboxRuntime` is unchanged.
|
package/README.md
CHANGED
|
@@ -177,6 +177,14 @@ Without `budgets` and `skills`, an oversized `prompt` is still cut at 8,192 byte
|
|
|
177
177
|
Any other cleartext URL still needs `allowHttp`, and credentials in the URL stay refused.
|
|
178
178
|
This applies to `modelUrl` only, not to MCP servers.
|
|
179
179
|
|
|
180
|
+
### Rewriting replies on the host
|
|
181
|
+
|
|
182
|
+
`replyText(text, { channel, space })` rewrites each successful model reply on the host before it is split and posted, and may return a promise.
|
|
183
|
+
Use it for what the prompt cannot guarantee, such as converting a model's stray Simplified Chinese to Traditional.
|
|
184
|
+
It never sees failure texts, and the history the worker keeps holds the model's own words.
|
|
185
|
+
A throw or a value that is not a string fails the turn like any other failure, and the error is logged with the channel; an empty string posts nothing, as an empty model reply does.
|
|
186
|
+
It needs no new worker image.
|
|
187
|
+
|
|
180
188
|
### Worker image protocol
|
|
181
189
|
|
|
182
190
|
The worker image carries the label `pi-roundtable-sandbox.protocol` with the worker protocol version (2 in this release, exported as `SANDBOX_PROTOCOL`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-roundtable-sandbox",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.1",
|
|
4
4
|
"description": "Sealed guest channels and an allow-listed credential broker for pi-roundtable",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@biomejs/biome": "2.5.15",
|
|
53
53
|
"@types/bun": "1.4.3",
|
|
54
54
|
"discord.js": "14.27.0",
|
|
55
|
-
"pi-roundtable": "0.24.
|
|
55
|
+
"pi-roundtable": "0.24.1",
|
|
56
56
|
"typebox": "1.3.37",
|
|
57
57
|
"typescript": "7.0.2"
|
|
58
58
|
}
|
package/src/claim.ts
CHANGED
|
@@ -8,13 +8,25 @@ import { splitReply } from "pi-roundtable/kit";
|
|
|
8
8
|
import type { SandboxChannelStore } from "./channel-store.ts";
|
|
9
9
|
import { type SandboxRuntime, SandboxTurnRefused } from "./runtime.ts";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Rewrites a successful model reply before it is posted, such as converting script or stripping
|
|
13
|
+
* markup. It runs on the host, after the sealed turn and before the reply is split into chunks;
|
|
14
|
+
* failure texts never pass through it. A throw, or a value that is not a string, fails the turn.
|
|
15
|
+
*/
|
|
16
|
+
export type SandboxReplyText = (
|
|
17
|
+
text: string,
|
|
18
|
+
context: { channel: ChannelKey; space?: string },
|
|
19
|
+
) => string | Promise<string>;
|
|
20
|
+
|
|
11
21
|
export interface SandboxClaimOptions {
|
|
12
22
|
/** `owns` adds channels in configured guilds; without it only `has` decides. */
|
|
13
23
|
channels: Pick<SandboxChannelStore, "has"> &
|
|
14
24
|
Partial<Pick<SandboxChannelStore, "owns">>;
|
|
15
25
|
runtime: Pick<SandboxRuntime, "runTurn" | "stop" | "startFresh">;
|
|
16
26
|
surfaces: Pick<SurfacePort, "sendReply" | "startTyping">;
|
|
17
|
-
|
|
27
|
+
/** Told of a failed turn, with what failed (a sandbox error, or replyText's own throw). */
|
|
28
|
+
reportFailure?: (channel: ChannelKey, error: unknown) => void;
|
|
29
|
+
replyText?: SandboxReplyText;
|
|
18
30
|
}
|
|
19
31
|
|
|
20
32
|
export function isSandboxAddress(message: InboundMessage): boolean {
|
|
@@ -26,6 +38,21 @@ export function isSandboxAddress(message: InboundMessage): boolean {
|
|
|
26
38
|
);
|
|
27
39
|
}
|
|
28
40
|
|
|
41
|
+
async function rewriteReply(
|
|
42
|
+
replyText: SandboxReplyText | undefined,
|
|
43
|
+
text: string,
|
|
44
|
+
message: InboundMessage,
|
|
45
|
+
): Promise<string> {
|
|
46
|
+
if (!replyText) return text;
|
|
47
|
+
const rewritten = await replyText(text, {
|
|
48
|
+
channel: message.channel,
|
|
49
|
+
...(message.space ? { space: message.space } : {}),
|
|
50
|
+
});
|
|
51
|
+
if (typeof rewritten !== "string")
|
|
52
|
+
throw new TypeError("replyText must return a string");
|
|
53
|
+
return rewritten;
|
|
54
|
+
}
|
|
55
|
+
|
|
29
56
|
/** Outranks the agent server's priority 100; ignored messages never fall through to host agents. */
|
|
30
57
|
export function sandboxClaim(options: SandboxClaimOptions): ChannelClaim {
|
|
31
58
|
const owns = (channel: ChannelKey, space?: string): boolean =>
|
|
@@ -68,15 +95,16 @@ export function sandboxClaim(options: SandboxClaimOptions): ChannelClaim {
|
|
|
68
95
|
text,
|
|
69
96
|
{ space: message.space },
|
|
70
97
|
);
|
|
98
|
+
const posted = reply.ok
|
|
99
|
+
? await rewriteReply(options.replyText, reply.text, message)
|
|
100
|
+
: "The sandbox turn failed.";
|
|
71
101
|
await options.surfaces.sendReply(message.channel, {
|
|
72
|
-
chunks: splitReply(
|
|
73
|
-
reply.ok ? reply.text : "The sandbox turn failed.",
|
|
74
|
-
),
|
|
102
|
+
chunks: splitReply(posted),
|
|
75
103
|
});
|
|
76
104
|
} catch (error) {
|
|
77
105
|
// A stop or a full queue is the runtime working as designed, not a failure.
|
|
78
106
|
if (!(error instanceof SandboxTurnRefused))
|
|
79
|
-
options.reportFailure?.(message.channel);
|
|
107
|
+
options.reportFailure?.(message.channel, error);
|
|
80
108
|
await options.surfaces.sendReply(message.channel, {
|
|
81
109
|
chunks: ["The sandbox turn failed or was stopped."],
|
|
82
110
|
});
|
package/src/index.ts
CHANGED
package/src/plugin.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { dirname, isAbsolute, resolve } from "node:path";
|
|
|
3
3
|
import { type ChannelKey, definePlugin, serviceKey } from "pi-roundtable";
|
|
4
4
|
import { DISCORD } from "pi-roundtable/discord";
|
|
5
5
|
import { SandboxChannelStore } from "./channel-store.ts";
|
|
6
|
-
import { sandboxClaim } from "./claim.ts";
|
|
6
|
+
import { type SandboxReplyText, sandboxClaim } from "./claim.ts";
|
|
7
7
|
import { sandboxCommands } from "./commands.ts";
|
|
8
8
|
import { SandboxRuntime, type SandboxRuntimeOptions } from "./runtime.ts";
|
|
9
9
|
|
|
@@ -23,6 +23,12 @@ export interface SandboxOptions
|
|
|
23
23
|
provider?: string;
|
|
24
24
|
/** Read before each model call with its channel and speaker; never falls back to the host login. */
|
|
25
25
|
apiKey?: SandboxRuntimeOptions["apiKey"];
|
|
26
|
+
/**
|
|
27
|
+
* Rewrites each successful model reply on the host before it is posted, for example to
|
|
28
|
+
* convert Simplified to Traditional Chinese. Failure texts are not passed to it; a throw
|
|
29
|
+
* fails the turn.
|
|
30
|
+
*/
|
|
31
|
+
replyText?: SandboxReplyText;
|
|
26
32
|
}
|
|
27
33
|
export interface SandboxService {
|
|
28
34
|
channels: SandboxChannelStore;
|
|
@@ -75,10 +81,11 @@ export function sandbox(options: SandboxOptions) {
|
|
|
75
81
|
channels,
|
|
76
82
|
runtime,
|
|
77
83
|
surfaces: context.surfaces,
|
|
78
|
-
|
|
84
|
+
...(options.replyText ? { replyText: options.replyText } : {}),
|
|
85
|
+
reportFailure: (channel, error) =>
|
|
79
86
|
context.logger.error(
|
|
80
|
-
{ channel },
|
|
81
|
-
"sandbox turn failed; inspect container cleanup
|
|
87
|
+
{ channel, err: error },
|
|
88
|
+
"sandbox turn failed; inspect the error, then container cleanup, broker configuration and replyText",
|
|
82
89
|
),
|
|
83
90
|
}),
|
|
84
91
|
],
|