@modelprofile.com/flexharness 11.1.1 → 11.3.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.codesandbox.d.ts +58 -7
- package/dist_ts/classes.codesandbox.js +267 -34
- package/dist_ts/classes.codesandbox.worker.d.ts +6 -1
- package/dist_ts/classes.codesandbox.worker.js +34 -8
- package/dist_ts/classes.flexharness.callbacks.d.ts +6 -0
- package/dist_ts/classes.flexharness.callbacks.js +24 -4
- package/dist_ts/classes.flexharness.core.d.ts +8 -0
- package/dist_ts/classes.flexharness.core.js +29 -1
- package/dist_ts/classes.flexharness.lifecycle.js +7 -1
- package/dist_ts/classes.flexharness.runs.js +9 -7
- package/dist_ts/classes.flexharness.tooldispatch.js +2 -2
- package/dist_ts/interfaces.d.ts +9 -1
- package/dist_ts/interfaces.flexharness.d.ts +5 -3
- package/dist_ts/plugins.d.ts +3 -1
- package/dist_ts/plugins.js +2 -2
- package/dist_ts_chat_harness/classes.chatclient.d.ts +7 -4
- package/dist_ts_chat_harness/classes.chatclient.js +3 -3
- package/dist_ts_chat_harness/classes.transcript.d.ts +1 -0
- package/dist_ts_chat_harness/classes.transcript.js +16 -1
- package/dist_ts_chat_harness_server/classes.chathub.js +2 -1
- package/dist_ts_chat_harness_server/classes.chatsubscription.d.ts +6 -3
- package/dist_ts_chat_harness_server/classes.chatsubscription.js +18 -4
- package/dist_ts_chat_harness_server/interfaces.d.ts +6 -0
- package/package.json +3 -3
- package/readme.md +5 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.codesandbox.ts +303 -34
- package/ts/classes.codesandbox.worker.ts +38 -8
- package/ts/classes.flexharness.callbacks.ts +22 -3
- package/ts/classes.flexharness.core.ts +31 -0
- package/ts/classes.flexharness.lifecycle.ts +5 -0
- package/ts/classes.flexharness.runs.ts +8 -5
- package/ts/classes.flexharness.tooldispatch.ts +1 -1
- package/ts/interfaces.flexharness.ts +5 -3
- package/ts/interfaces.ts +10 -1
- package/ts/plugins.ts +3 -1
- package/ts/readme.md +5 -2
- package/ts_chat_harness/classes.chatclient.ts +9 -6
- package/ts_chat_harness/classes.transcript.ts +19 -0
- package/ts_chat_harness_server/classes.chathub.ts +1 -0
- package/ts_chat_harness_server/classes.chatsubscription.ts +19 -3
- package/ts_chat_harness_server/interfaces.ts +6 -0
- package/ts_chat_harness_server/readme.md +1 -1
|
@@ -9,6 +9,10 @@ const emptyPending = () => ({
|
|
|
9
9
|
history: false,
|
|
10
10
|
steers: [],
|
|
11
11
|
});
|
|
12
|
+
/** Resolves after the time; its timer keeps no process alive. */
|
|
13
|
+
const delay = (msArg) => new Promise((resolve) => {
|
|
14
|
+
setTimeout(resolve, msArg).unref?.();
|
|
15
|
+
});
|
|
12
16
|
const hasPending = (pendingArg) => pendingArg.reset
|
|
13
17
|
|| pendingArg.rows
|
|
14
18
|
|| pendingArg.deltas.length > 0
|
|
@@ -19,8 +23,9 @@ const hasPending = (pendingArg) => pendingArg.reset
|
|
|
19
23
|
|| pendingArg.steers.length > 0;
|
|
20
24
|
/**
|
|
21
25
|
* One tab following one conversation. A `FlexHarnessTranscript` takes every event of the harness
|
|
22
|
-
* instance; what changed reaches the tab as numbered updates, one at a time
|
|
23
|
-
* loses track, or an update the tab does not acknowledge,
|
|
26
|
+
* instance; what changed reaches the tab as numbered updates, one at a time and at most one per
|
|
27
|
+
* `minUpdateIntervalMs`. A transcript that loses track, or an update the tab does not acknowledge,
|
|
28
|
+
* makes the next update a `reset`.
|
|
24
29
|
*/
|
|
25
30
|
export class FlexHarnessChatSubscription {
|
|
26
31
|
id;
|
|
@@ -40,6 +45,8 @@ export class FlexHarnessChatSubscription {
|
|
|
40
45
|
sentRows = new Map();
|
|
41
46
|
pending = emptyPending();
|
|
42
47
|
sending = false;
|
|
48
|
+
/** when the last update was taken to be sent, by `performance.now()` */
|
|
49
|
+
lastUpdateAt = Number.NEGATIVE_INFINITY;
|
|
43
50
|
closed = false;
|
|
44
51
|
constructor(optionsArg) {
|
|
45
52
|
this.id = optionsArg.id;
|
|
@@ -142,7 +149,7 @@ export class FlexHarnessChatSubscription {
|
|
|
142
149
|
this.closeSelf();
|
|
143
150
|
}
|
|
144
151
|
}
|
|
145
|
-
/** Sends what is pending, one update at a time, in seq order. */
|
|
152
|
+
/** Sends what is pending, one update at a time, in seq order, `minUpdateIntervalMs` apart. */
|
|
146
153
|
async pump() {
|
|
147
154
|
if (this.sending)
|
|
148
155
|
return;
|
|
@@ -150,6 +157,12 @@ export class FlexHarnessChatSubscription {
|
|
|
150
157
|
let inFlight = null;
|
|
151
158
|
try {
|
|
152
159
|
while (!this.closed && this.state === 'live' && hasPending(this.pending)) {
|
|
160
|
+
const wait = this.lastUpdateAt + this.limits.minUpdateIntervalMs - performance.now();
|
|
161
|
+
if (wait > 0) {
|
|
162
|
+
// what changes meanwhile goes into this update
|
|
163
|
+
await delay(wait);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
153
166
|
const peer = await this.peers.find(this.tab);
|
|
154
167
|
if (!peer) {
|
|
155
168
|
this.closeSelf();
|
|
@@ -158,6 +171,7 @@ export class FlexHarnessChatSubscription {
|
|
|
158
171
|
if (this.closed || this.state !== 'live')
|
|
159
172
|
return;
|
|
160
173
|
inFlight = await this.takeUpdate();
|
|
174
|
+
this.lastUpdateAt = performance.now();
|
|
161
175
|
await peer.update(inFlight);
|
|
162
176
|
inFlight = null;
|
|
163
177
|
}
|
|
@@ -233,4 +247,4 @@ export class FlexHarnessChatSubscription {
|
|
|
233
247
|
this.onClosed(this);
|
|
234
248
|
}
|
|
235
249
|
}
|
|
236
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5jaGF0c3Vic2NyaXB0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHNfY2hhdF9oYXJuZXNzX3NlcnZlci9jbGFzc2VzLmNoYXRzdWJzY3JpcHRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFpQnhDLE1BQU0sWUFBWSxHQUFHLEdBQW1CLEVBQUUsQ0FBQyxDQUFDO0lBQzFDLEtBQUssRUFBRSxLQUFLO0lBQ1osSUFBSSxFQUFFLEtBQUs7SUFDWCxNQUFNLEVBQUUsRUFBRTtJQUNWLE1BQU0sRUFBRSxLQUFLO0lBQ2IsS0FBSyxFQUFFLEtBQUs7SUFDWixXQUFXLEVBQUUsS0FBSztJQUNsQixPQUFPLEVBQUUsS0FBSztJQUNkLE1BQU0sRUFBRSxFQUFFO0NBQ1gsQ0FBQyxDQUFDO0FBRUgsTUFBTSxVQUFVLEdBQUcsQ0FBQyxVQUEwQixFQUFXLEVBQUUsQ0FDekQsVUFBVSxDQUFDLEtBQUs7T0FDYixVQUFVLENBQUMsSUFBSTtPQUNmLFVBQVUsQ0FBQyxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUM7T0FDNUIsVUFBVSxDQUFDLE1BQU07T0FDakIsVUFBVSxDQUFDLEtBQUs7T0FDaEIsVUFBVSxDQUFDLFdBQVc7T0FDdEIsVUFBVSxDQUFDLE9BQU87T0FDbEIsVUFBVSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDO0FBc0JsQzs7OztHQUlHO0FBQ0gsTUFBTSxPQUFPLDJCQUEyQjtJQUN0QixFQUFFLENBQVM7SUFDWCxHQUFHLENBQXlCO0lBQzVCLE9BQU8sQ0FBUztJQUNoQixTQUFTLENBQVM7SUFFakIsT0FBTyxDQUFzQztJQUM3QyxLQUFLLENBQXdCO0lBQzdCLE1BQU0sQ0FBeUI7SUFDL0IsUUFBUSxDQUF5RDtJQUNqRSxVQUFVLENBQWdDO0lBQzNELDhFQUE4RTtJQUN0RSxLQUFLLEdBQXVCLFNBQVMsQ0FBQztJQUN0QyxRQUFR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
|
|
250
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5jaGF0c3Vic2NyaXB0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHNfY2hhdF9oYXJuZXNzX3NlcnZlci9jbGFzc2VzLmNoYXRzdWJzY3JpcHRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFpQnhDLE1BQU0sWUFBWSxHQUFHLEdBQW1CLEVBQUUsQ0FBQyxDQUFDO0lBQzFDLEtBQUssRUFBRSxLQUFLO0lBQ1osSUFBSSxFQUFFLEtBQUs7SUFDWCxNQUFNLEVBQUUsRUFBRTtJQUNWLE1BQU0sRUFBRSxLQUFLO0lBQ2IsS0FBSyxFQUFFLEtBQUs7SUFDWixXQUFXLEVBQUUsS0FBSztJQUNsQixPQUFPLEVBQUUsS0FBSztJQUNkLE1BQU0sRUFBRSxFQUFFO0NBQ1gsQ0FBQyxDQUFDO0FBRUgsaUVBQWlFO0FBQ2pFLE1BQU0sS0FBSyxHQUFHLENBQUMsS0FBYSxFQUFpQixFQUFFLENBQzdDLElBQUksT0FBTyxDQUFPLENBQUMsT0FBTyxFQUFFLEVBQUU7SUFDNUIsVUFBVSxDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDO0FBQ3ZDLENBQUMsQ0FBQyxDQUFDO0FBRUwsTUFBTSxVQUFVLEdBQUcsQ0FBQyxVQUEwQixFQUFXLEVBQUUsQ0FDekQsVUFBVSxDQUFDLEtBQUs7T0FDYixVQUFVLENBQUMsSUFBSTtPQUNmLFVBQVUsQ0FBQyxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUM7T0FDNUIsVUFBVSxDQUFDLE1BQU07T0FDakIsVUFBVSxDQUFDLEtBQUs7T0FDaEIsVUFBVSxDQUFDLFdBQVc7T0FDdEIsVUFBVSxDQUFDLE9BQU87T0FDbEIsVUFBVSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDO0FBc0JsQzs7Ozs7R0FLRztBQUNILE1BQU0sT0FBTywyQkFBMkI7SUFDdEIsRUFBRSxDQUFTO0lBQ1gsR0FBRyxDQUF5QjtJQUM1QixPQUFPLENBQVM7SUFDaEIsU0FBUyxDQUFTO0lBRWpCLE9BQU8sQ0FBc0M7SUFDN0MsS0FBSyxDQUF3QjtJQUM3QixNQUFNLENBQXlCO0lBQy9CLFFBQVEsQ0FBeUQ7SUFDakUsVUFBVSxDQUFnQztJQUMzRCw4RUFBOEU7SUFDdEUsS0FBSyxHQUF1QixTQUFTLENBQUM7SUFDdEMsUUFBUSxHQUErRSxFQUFFLENBQUM7SUFDMUYsR0FBRyxHQUFHLENBQUMsQ0FBQztJQUNoQiw2REFBNkQ7SUFDckQsUUFBUSxHQUFHLElBQUksR0FBRyxFQUFrQixDQUFDO0lBQ3JDLE9BQU8sR0FBbUIsWUFBWSxFQUFFLENBQUM7SUFDekMsT0FBTyxHQUFHLEtBQUssQ0FBQztJQUN4Qix3RUFBd0U7SUFDaEUsWUFBWSxHQUFHLE1BQU0sQ0FBQyxpQkFBaUIsQ0FBQztJQUN4QyxNQUFNLEdBQUcsS0FBSyxDQUFDO0lBRXZCLFlBQVksVUFBK0M7UUFDekQsSUFBSSxDQUFDLEVBQUUsR0FBRyxVQUFVLENBQUMsRUFBRSxDQUFDO1FBQ3hCLElBQUksQ0FBQyxHQUFHLEdBQUcsVUFBVSxDQUFDLEdBQUcsQ0FBQztRQUMxQixJQUFJLENBQUMsT0FBTyxHQUFHLFVBQVUsQ0FBQyxPQUFPLENBQUM7UUFDbEMsSUFBSSxDQUFDLFNBQVMsR0FBRyxVQUFVLENBQUMsU0FBUyxDQUFDO1FBQ3RDLElBQUksQ0FBQyxPQUFPLEdBQUcsVUFBVSxDQUFDLE9BQU8sQ0FBQztRQUNsQyxJQUFJLENBQUMsS0FBSyxHQUFHLFVBQVUsQ0FBQyxLQUFLLENBQUM7UUFDOUIsSUFBSSxDQUFDLE1BQU0sR0FBRyxVQUFVLENBQUMsTUFBTSxDQUFDO1FBQ2hDLElBQUksQ0FBQyxRQUFRLEdBQUcsVUFBVSxDQUFDLFFBQVEsQ0FBQztRQUNwQyxJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksT0FBTyxDQUFDLHFCQUFxQixDQUFDLEVBQUUsT0FBTyxFQUFFLElBQUksQ0FBQyxPQUFPLEVBQUUsU0FBUyxFQUFFLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQyxDQUFDO0lBQzVHLENBQUM7SUFFRCxzRkFBc0Y7SUFDL0UsU0FBUyxDQUFDLFFBQW1DLEVBQUUsWUFBZ0M7UUFDcEYsSUFBSSxJQUFJLENBQUMsTUFBTTtZQUFFLE9BQU87UUFDeEIsSUFBSSxJQUFJLENBQUMsS0FBSyxLQUFLLFNBQVM7WUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxFQUFFLEtBQUssRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLFlBQVksRUFBRSxDQUFDLENBQUM7O1lBQzFGLElBQUksQ0FBQyxVQUFVLENBQUMsUUFBUSxFQUFFLFlBQVksQ0FBQyxDQUFDO0lBQy9DLENBQUM7SUFFRCwrRkFBK0Y7SUFDeEYsS0FBSyxDQUFDLElBQUk7UUFDZixJQUFJLENBQUMsS0FBSyxHQUFHLFNBQVMsQ0FBQztRQUN2QixJQUFJLENBQUMsUUFBUSxHQUFHLEVBQUUsQ0FBQztRQUNuQixNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzdFLElBQUksSUFBSSxDQUFDLE1BQU07WUFBRSxPQUFPO1FBQ3hCLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDO1lBQ25CLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUTtZQUN2QixXQUFXLEVBQUUsSUFBSSxDQUFDLGtCQUFrQjtZQUNwQyxPQUFPLEVBQUUsSUFBSSxDQUFDLE9BQU87WUFDckIsUUFBUSxFQUFFLElBQUksQ0FBQyxRQUFRO1NBQ3hCLENBQUMsQ0FBQztRQUNILE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUM7UUFDL0IsSUFBSSxDQUFDLFFBQVEsR0FBRyxFQUFFLENBQUM7UUFDbkIsSUFBSSxDQUFDLEtBQUssR0FBRyxNQUFNLENBQUM7UUFDcEIsSUFBSSxDQUFDLE9BQU8sR0FBRyxFQUFFLEdBQUcsWUFBWSxFQUFFLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUMvRSxLQUFLLE1BQU0sS0FBSyxJQUFJLFFBQVEsRUFBRSxDQUFDO1lBQzdCLElBQUksSUFBSSxDQUFDLEtBQUssS0FBSyxNQUFNLEVBQUUsQ0FBQztnQkFDMUIsSUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7Z0JBQzFCLFNBQVM7WUFDWCxDQUFDO1lBQ0QsSUFBSSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsS0FBSyxFQUFFLEtBQUssQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUNoRCxDQUFDO1FBQ0QsS0FBSyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDbkIsQ0FBQztJQUVELHFEQUFxRDtJQUM5QyxLQUFLO1FBQ1YsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUM7UUFDbkIsSUFBSSxDQUFDLFFBQVEsR0FBRyxFQUFFLENBQUM7SUFDckIsQ0FBQztJQUVPLFVBQVUsQ0FBQyxRQUFtQyxFQUFFLFlBQWdDO1FBQ3RGLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsVUFBVSxDQUFDLFFBQVEsRUFBRSxFQUFFLGlCQUFpQixFQUFFLFlBQVksRUFBRSxDQUFDLENBQUM7UUFDekYsSUFBSSxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDbEIsS0FBSyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDbkIsT0FBTztRQUNULENBQUM7UUFDRCxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDO1FBQzdCLElBQUksUUFBUSxDQUFDLE9BQU8sS0FBSyxJQUFJLENBQUMsT0FBTyxJQUFJLFFBQVEsQ0FBQyxTQUFTLEtBQUssSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQy9FLElBQUksUUFBUSxDQUFDLElBQUksS0FBSyxzQkFBc0IsRUFBRSxDQUFDO2dCQUM3QyxPQUFPLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxFQUFFLE9BQU8sRUFBRSxRQUFRLENBQUMsT0FBTyxFQUFFLE9BQU8sRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDO1lBQ3pFLENBQUM7aUJBQU0sSUFBSSxRQUFRLENBQUMsSUFBSSxLQUFLLGNBQWMsRUFBRSxDQUFDO2dCQUM1QyxLQUFLLE1BQU0sT0FBTyxJQUFJLFFBQVEsQ0FBQyxpQkFBaUI7b0JBQUUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLFdBQVcsRUFBRSxDQUFDLENBQUM7Z0JBQ3pHLE9BQU8sQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDO1lBQ3pCLENBQUM7UUFDSCxDQUFDO1FBQ0QsSUFBSSxNQUFNLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDcEIsOEVBQThFO1lBQzlFLE9BQU8sQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO1lBQ3BCLE9BQU8sQ0FBQyxNQUFNLEdBQUcsRUFBRSxDQUFDO1FBQ3RCLENBQUM7YUFBTSxJQUFJLE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ2pELE9BQU8sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1lBQ3RDLElBQUksT0FBTyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO2dCQUN6RCxPQUFPLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztnQkFDcEIsT0FBTyxDQUFDLE1BQU0sR0FBRyxFQUFFLENBQUM7WUFDdEIsQ0FBQztRQUNILENBQUM7UUFDRCxJQUFJLE1BQU0sQ0FBQyxNQUFNO1lBQUUsT0FBTyxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUM7UUFDekMsSUFBSSxNQUFNLENBQUMsS0FBSztZQUFFLE9BQU8sQ0FBQyxLQUFLLEdBQUcsSUFBSSxDQUFDO1FBQ3ZDLElBQUksTUFBTSxDQUFDLFdBQVc7WUFBRSxPQUFPLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQztRQUNuRCxJQUFJLFVBQVUsQ0FBQyxPQUFPLENBQUM7WUFBRSxLQUFLLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUM1QyxDQUFDO0lBRUQscUZBQXFGO0lBQzdFLEtBQUssQ0FBQyxNQUFNO1FBQ2xCLElBQUksSUFBSSxDQUFDLEtBQUssS0FBSyxTQUFTLElBQUksSUFBSSxDQUFDLE1BQU07WUFBRSxPQUFPO1FBQ3BELElBQUksQ0FBQztZQUNILE1BQU0sSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQ3BCLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxtRkFBbUY7WUFDbkYsSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDO1FBQ25CLENBQUM7SUFDSCxDQUFDO0lBRUQsOEZBQThGO0lBQ3RGLEtBQUssQ0FBQyxJQUFJO1FBQ2hCLElBQUksSUFBSSxDQUFDLE9BQU87WUFBRSxPQUFPO1FBQ3pCLElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDO1FBQ3BCLElBQUksUUFBUSxHQUFtQixJQUFJLENBQUM7UUFDcEMsSUFBSSxDQUFDO1lBQ0gsT0FBTyxDQUFDLElBQUksQ0FBQyxNQUFNLElBQUksSUFBSSxDQUFDLEtBQUssS0FBSyxNQUFNLElBQUksVUFBVSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO2dCQUN6RSxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsWUFBWSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsbUJBQW1CLEdBQUcsV0FBVyxDQUFDLEdBQUcsRUFBRSxDQUFDO2dCQUNyRixJQUFJLElBQUksR0FBRyxDQUFDLEVBQUUsQ0FBQztvQkFDYiwrQ0FBK0M7b0JBQy9DLE1BQU0sS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO29CQUNsQixTQUFTO2dCQUNYLENBQUM7Z0JBQ0QsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7Z0JBQzdDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztvQkFDVixJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7b0JBQ2pCLE9BQU87Z0JBQ1QsQ0FBQztnQkFDRCxJQUFJLElBQUksQ0FBQyxNQUFNLElBQUksSUFBSSxDQUFDLEtBQUssS0FBSyxNQUFNO29CQUFFLE9BQU87Z0JBQ2pELFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztnQkFDbkMsSUFBSSxDQUFDLFlBQVksR0FBRyxXQUFXLENBQUMsR0FBRyxFQUFFLENBQUM7Z0JBQ3RDLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQztnQkFDNUIsUUFBUSxHQUFHLElBQUksQ0FBQztZQUNsQixDQUFDO1FBQ0gsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLG9GQUFvRjtZQUNwRixJQUFJLENBQUMsT0FBTyxHQUFHO2dCQUNiLEdBQUcsWUFBWSxFQUFFO2dCQUNqQixLQUFLLEVBQUUsSUFBSTtnQkFDWCxNQUFNLEVBQUUsQ0FBQyxHQUFHLENBQUMsUUFBUSxFQUFFLE1BQU0sSUFBSSxFQUFFLENBQUMsRUFBRSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDO2FBQzlELENBQUM7UUFDSixDQUFDO2dCQUFTLENBQUM7WUFDVCxJQUFJLENBQUMsT0FBTyxHQUFHLEtBQUssQ0FBQztRQUN2QixDQUFDO0lBQ0gsQ0FBQztJQUVPLEtBQUssQ0FBQyxVQUFVO1FBQ3RCLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUM7UUFDN0IsSUFBSSxDQUFDLE9BQU8sR0FBRyxZQUFZLEVBQUUsQ0FBQztRQUM5QixNQUFNLE1BQU0sR0FBWSxFQUFFLGNBQWMsRUFBRSxJQUFJLENBQUMsRUFBRSxFQUFFLEdBQUcsRUFBRSxFQUFFLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUNyRSxNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDO1FBQzVCLE1BQU0sSUFBSSxHQUFHLEtBQUssSUFBSSxPQUFPLENBQUMsSUFBSSxJQUFJLE9BQU8sQ0FBQyxNQUFNLElBQUksT0FBTyxDQUFDLEtBQUssSUFBSSxPQUFPLENBQUMsV0FBVztZQUMxRixDQUFDLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUU7WUFDeEIsQ0FBQyxDQUFDLElBQUksQ0FBQztRQUNULElBQUksS0FBSyxFQUFFLENBQUM7WUFDVixNQUFNLENBQUMsS0FBSyxHQUFHLElBQUksQ0FBQztZQUNwQixJQUFJLENBQUMsUUFBUSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ3hCLENBQUM7UUFDRCxJQUFJLElBQUksSUFBSSxDQUFDLEtBQUssSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztZQUNwQyxNQUFNLElBQUksR0FBRyxJQUFJLEdBQUcsRUFBa0IsQ0FBQztZQUN2QyxNQUFNLE9BQU8sR0FBOEIsRUFBRSxDQUFDO1lBQzlDLEtBQUssTUFBTSxHQUFHLElBQUksSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO2dCQUNoQyxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxDQUFDO2dCQUNqQyxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsS0FBSyxJQUFJO29CQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7Z0JBQzFELElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLEVBQUUsRUFBRSxJQUFJLENBQUMsQ0FBQztZQUN6QixDQUFDO1lBQ0QsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUM7WUFDckIsTUFBTSxDQUFDLElBQUksR0FBRyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDO1FBQ3ZFLENBQUM7YUFBTSxJQUFJLE9BQU8sQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDakMsTUFBTSxDQUFDLE1BQU0sR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDO1FBQ2pDLENBQUM7UUFDRCxJQUFJLElBQUksSUFBSSxDQUFDLEtBQUssSUFBSSxPQUFPLENBQUMsTUFBTSxDQUFDO1lBQUUsTUFBTSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDO1FBQ25FLElBQUksSUFBSSxJQUFJLENBQUMsS0FBSyxJQUFJLE9BQU8sQ0FBQyxLQUFLLENBQUM7WUFBRSxNQUFNLENBQUMsS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUM7UUFDaEUsSUFBSSxJQUFJLElBQUksQ0FBQyxLQUFLLElBQUksT0FBTyxDQUFDLFdBQVcsQ0FBQztZQUFFLE1BQU0sQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQztRQUNsRixJQUFJLEtBQUssSUFBSSxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDN0IsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7WUFDekMsSUFBSSxPQUFPO2dCQUFFLE1BQU0sQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO1FBQ3hDLENBQUM7UUFDRCxJQUFJLE9BQU8sQ0FBQyxNQUFNLENBQUMsTUFBTTtZQUFFLE1BQU0sQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQztRQUMxRCxPQUFPLE1BQU0sQ0FBQztJQUNoQixDQUFDO0lBRU8sS0FBSyxDQUFDLFdBQVc7UUFDdkIsSUFBSSxDQUFDO1lBQ0gsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLHVCQUF1QixDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1lBQ3RGLE9BQU8sRUFBRSxhQUFhLEVBQUUsSUFBSSxDQUFDLGFBQWEsRUFBRSxhQUFhLEVBQUUsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO1FBQ2xGLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCx3RkFBd0Y7WUFDeEYsT0FBTyxTQUFTLENBQUM7UUFDbkIsQ0FBQztJQUNILENBQUM7SUFFTyxTQUFTO1FBQ2YsSUFBSSxJQUFJLENBQUMsTUFBTTtZQUFFLE9BQU87UUFDeEIsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ2IsSUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUN0QixDQUFDO0NBQ0YifQ==
|
|
@@ -70,6 +70,12 @@ export interface IFlexHarnessChatLimits {
|
|
|
70
70
|
toolTimeoutMs: number;
|
|
71
71
|
/** Deltas one update may carry; a tab further behind gets the changed rows. Default: 2000. */
|
|
72
72
|
maxPendingDeltas: number;
|
|
73
|
+
/**
|
|
74
|
+
* The least time between two updates of one subscription; what changes meanwhile goes into the
|
|
75
|
+
* next. It bounds the requests a streaming turn costs a transport that answers every update with
|
|
76
|
+
* one, as TypedSocket pulls do. Default: 100 ms.
|
|
77
|
+
*/
|
|
78
|
+
minUpdateIntervalMs: number;
|
|
73
79
|
/** Characters of a message's text. Default: 100 000. */
|
|
74
80
|
maxMessageChars: number;
|
|
75
81
|
/** Attachments of one message. Default: 16. */
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"author": "Task Venture Capital GmbH",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "@modelprofile.com/flexharness",
|
|
5
|
-
"version": "11.
|
|
5
|
+
"version": "11.3.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"description": "Managed agent sessions with tools, MCP, chat, OCR, documents and browser control through focused subpath imports.",
|
|
8
8
|
"main": "./dist_ts/index.js",
|
|
@@ -87,8 +87,8 @@
|
|
|
87
87
|
},
|
|
88
88
|
"dependencies": {
|
|
89
89
|
"@cfworker/json-schema": "^4.1.1",
|
|
90
|
-
"@modelprofile.com/flexharness-agent": "11.
|
|
91
|
-
"@modelprofile.com/flexharness-models": "11.
|
|
90
|
+
"@modelprofile.com/flexharness-agent": "11.3.0",
|
|
91
|
+
"@modelprofile.com/flexharness-models": "11.3.0",
|
|
92
92
|
"@push.rocks/smartrequest": "^5.5.1",
|
|
93
93
|
"ai": "^7.0.111",
|
|
94
94
|
"zod": "^4.6.5"
|
package/readme.md
CHANGED
|
@@ -1065,6 +1065,9 @@ const unsubscribe = harness.subscribe((event) => {
|
|
|
1065
1065
|
event.textUtf8Bytes,
|
|
1066
1066
|
);
|
|
1067
1067
|
break;
|
|
1068
|
+
case 'part.removed':
|
|
1069
|
+
removePart(event.sessionId, event.messageIndex, event.partIndex);
|
|
1070
|
+
break;
|
|
1068
1071
|
case 'permission.requested':
|
|
1069
1072
|
showPermission(event.request);
|
|
1070
1073
|
break;
|
|
@@ -1100,13 +1103,13 @@ const unsubscribe = harness.subscribe((event) => {
|
|
|
1100
1103
|
unsubscribe();
|
|
1101
1104
|
```
|
|
1102
1105
|
|
|
1103
|
-
Events are discriminated, deeply immutable values with one global sequence within each `FlexHarness` instance. Every `IFlexEventBase` carries the exact source `sessionGenerationId` and `sessionGenerationSequence`; `session.deleted` retains the deleted generation after live metadata is removed. Listener exceptions are isolated from runs and other listeners. Every accepted queue entry emits `prompt.queued` and exactly one `prompt.finished`. Every accepted steer emits `prompt.steer.accepted`, and `prompt.steer.applied`, after the `message.*` events of its user message, once the run took it in; `run.finished` lists it in `unappliedSteerIds` unless it is in the model context of later turns. Durable promotion additionally emits `prompt.started`, and actual model preparation emits `prompt.running`; cancellation or failure can omit either intermediate event. Existing durable run/message terminal events precede `prompt.finished`. Every callback-backed streamed text part emits exactly one `part.completed` event before the corresponding `run.finished` event: once the model step that streamed it is committed, so its text is final and saved, or with the run's terminal projection when that step did not commit. Text a later model step streams starts a new part. Every `part.started`, `part.delta`, `part.updated`, and `part.
|
|
1106
|
+
Events are discriminated, deeply immutable values with one global sequence within each `FlexHarness` instance. Every `IFlexEventBase` carries the exact source `sessionGenerationId` and `sessionGenerationSequence`; `session.deleted` retains the deleted generation after live metadata is removed. Listener exceptions are isolated from runs and other listeners. Every accepted queue entry emits `prompt.queued` and exactly one `prompt.finished`. Every accepted steer emits `prompt.steer.accepted`, and `prompt.steer.applied`, after the `message.*` events of its user message, once the run took it in; `run.finished` lists it in `unappliedSteerIds` unless it is in the model context of later turns. Durable promotion additionally emits `prompt.started`, and actual model preparation emits `prompt.running`; cancellation or failure can omit either intermediate event. Existing durable run/message terminal events precede `prompt.finished`. Every callback-backed streamed text part that stays in its message emits exactly one `part.completed` event before the corresponding `run.finished` event: once the model step that streamed it is committed, so its text is final and saved, or with the run's terminal projection when that step did not commit. Text a later model step streams starts a new part. When a model call fails and is retried, the text and reasoning parts it streamed are not in the session: `part.removed` takes each of them out of its message, last part first, and the retry streams its own parts. Its `partIndex` is the part's position before it left; the parts after it move up by one. A removed part has no further events, and `part.removed` does not count against `callbackLimits`. The tool parts of calls the failed model call executed stay. Every `part.started`, `part.delta`, `part.updated`, `part.completed`, and `part.removed` event carries zero-based `messageIndex` and `partIndex` coordinates from the session's authoritative message and part sequences.
|
|
1104
1107
|
|
|
1105
1108
|
`getSessionView(scopeId, sessionId)` reads a session's metadata, visible messages and pending permission requests in one step, together with the instance's event `sequence` they include. The messages include what a running turn has streamed so far, so a consumer that applies every event of the instance with a greater `sequence` follows the session exactly from the view on; events up to `sequence` are already in it. The read waits while a queued write or a change that emits events for the session is in progress; a session that keeps changing through 64 consecutive waits is refused with `FlexHarnessSessionBusyError`, and the read can be repeated. Session metadata is current: status changes such as `waiting_permission` emit no event. A permission request its run leaves unanswered leaves `pendingPermissions` without an event of its own; the run's `run.finished` follows.
|
|
1106
1109
|
|
|
1107
1110
|
`part.started`, `part.updated`, and `part.completed` are snapshot events with a complete immutable `part`. Tool and reasoning parts carry `startedAt` (ISO time the call or reasoning began) and, once they reach a terminal status, `completedAt`; parts recorded by earlier versions have neither, so a consumer falls back to the message times for them. A newly streamed text part emits `part.started` with empty text before its first delta; reasoning parts also start empty. `part.delta` is a separate delta-only event: it has no cumulative `part`, and carries `partType`, the required exact source `delta`, and `baseTextUtf8Bytes`/`textUtf8Bytes` for the cumulative text before and after that delta. The counters remain correct when a UTF-16 surrogate pair is split across callbacks. Exact deltas are never truncated, including a single delta above the 96 KiB message-transfer text limit. `part.updated` remains a cumulative replacement snapshot for running tool output or metadata, not a text delta. `session.history.changed` carries `direction: 'undo' | 'redo' | 'branch'`; `runId` is present for undo and redo. Events contain public IDs, exact delta payloads, and public snapshots only; they do not expose prompt payloads, the resolved scope object, storage key, model object, provider options, or raw storage key.
|
|
1108
1111
|
|
|
1109
|
-
Part events narrow through the exported `TFlexPartEvent` union. `IFlexPartEventBase` contains their shared coordinates, `IFlexPartSnapshotEvent` owns snapshot events and their complete `part`,
|
|
1112
|
+
Part events narrow through the exported `TFlexPartEvent` union. `IFlexPartEventBase` contains their shared coordinates, `IFlexPartSnapshotEvent` owns snapshot events and their complete `part`, `IFlexPartDeltaEvent` owns exact delta-only events and their UTF-8 counters, and `IFlexPartRemovedEvent` is `part.removed`, which carries only the coordinates.
|
|
1110
1113
|
|
|
1111
1114
|
### Migrating Part Events to 5.x
|
|
1112
1115
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
|
-
import { FlexHarnessValidationError } from './errors.js';
|
|
2
|
+
import { FlexHarnessClosedError, FlexHarnessValidationError } from './errors.js';
|
|
3
3
|
import type { IFlexCodeModeLimits, IFlexCodeRunFailure, TJsonValue } from './interfaces.js';
|
|
4
4
|
import type { TCodeWorkerPhase, TCodeWorkerReport, TCodeWorkerRequest } from './classes.codesandbox.worker.js';
|
|
5
5
|
import { maxTransferMetadataBytes, truncateUtf8 } from './utils.transfer.js';
|
|
@@ -27,11 +27,44 @@ export type TFlexCodeRunResult =
|
|
|
27
27
|
| { ok: true; value: TJsonValue; calls: number; logs?: string }
|
|
28
28
|
| { ok: false; failure: IFlexCodeRunFailure };
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* When a sandbox replaces its workers. A worker runs programs one after another, each in a QuickJS
|
|
32
|
+
* runtime of its own that it frees after the program, and is ended after a limit stopped a program,
|
|
33
|
+
* when it fails, after `maxPrograms`, once its WebAssembly memory grew past `maxMemoryBytes`, or after
|
|
34
|
+
* waiting `idleMs` for a program.
|
|
35
|
+
*/
|
|
36
|
+
export interface IFlexCodeSandboxPolicy {
|
|
37
|
+
/** Programs one worker runs before it is replaced. */
|
|
38
|
+
maxPrograms: number;
|
|
39
|
+
/** The WebAssembly memory a worker may have grown to and still run another program; it never shrinks. */
|
|
40
|
+
maxMemoryBytes: number;
|
|
41
|
+
/** How long a worker waits for its next program before it ends. */
|
|
42
|
+
idleMs: number;
|
|
43
|
+
/** Workers kept waiting for programs, across scopes; the one that waited longest goes first. */
|
|
44
|
+
maxWaiting: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const FLEX_CODE_SANDBOX_DEFAULT_POLICY: Readonly<IFlexCodeSandboxPolicy> = Object.freeze({
|
|
48
|
+
maxPrograms: 100,
|
|
49
|
+
maxMemoryBytes: 64 * 1024 * 1024,
|
|
50
|
+
idleMs: 60_000,
|
|
51
|
+
maxWaiting: 4,
|
|
52
|
+
});
|
|
53
|
+
|
|
30
54
|
type TStop =
|
|
31
55
|
| { kind: 'limit'; limit: NonNullable<IFlexCodeRunFailure['limit']>; message: string }
|
|
32
56
|
| { kind: 'aborted'; message: string }
|
|
33
57
|
| { kind: 'host'; error: unknown };
|
|
34
58
|
|
|
59
|
+
/** What a worker hands the program it serves: everything but its own bookkeeping. */
|
|
60
|
+
type TCodeRunReport = Exclude<TCodeWorkerReport, { kind: 'released' }>;
|
|
61
|
+
|
|
62
|
+
interface ICodeWorkerClient {
|
|
63
|
+
report(report: TCodeRunReport): void;
|
|
64
|
+
/** The worker failed or exited by itself; it is ended. */
|
|
65
|
+
fail(error: unknown): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
35
68
|
/** How long calls in flight get to settle after their program stopped and their signal aborted. */
|
|
36
69
|
const stopGraceMs = 5_000;
|
|
37
70
|
/**
|
|
@@ -40,6 +73,8 @@ const stopGraceMs = 5_000;
|
|
|
40
73
|
* array many times over, can run far longer.
|
|
41
74
|
*/
|
|
42
75
|
const cpuGraceMs = 250;
|
|
76
|
+
/** How long a worker gets to free the runtime of a program that ended by itself before it is ended. */
|
|
77
|
+
const releaseGraceMs = 1_000;
|
|
43
78
|
|
|
44
79
|
const programPrefix = 'globalThis.__flexMain = async function () {\n';
|
|
45
80
|
const programSuffix = '\n};';
|
|
@@ -50,16 +85,32 @@ const workerUrl = new URL(`./classes.codesandbox.worker${plugins.path.extname(im
|
|
|
50
85
|
let enginePromise: Promise<plugins.ICodeSandboxEngine> | undefined;
|
|
51
86
|
|
|
52
87
|
/**
|
|
53
|
-
* Runs `run_code` programs in QuickJS-ng compiled to WebAssembly
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
88
|
+
* Runs `run_code` programs in QuickJS-ng compiled to WebAssembly, with no globals beyond the language,
|
|
89
|
+
* `api`, `ApiError` and `console`, and every limit of `IFlexCodeModeLimits` enforced. Program types are
|
|
90
|
+
* stripped with amaro first.
|
|
91
|
+
*
|
|
92
|
+
* The sandbox owns its worker threads. Each runs one program at a time, in a QuickJS runtime and
|
|
93
|
+
* context of its own that it frees once the program ended, so nothing of a program reaches the next,
|
|
94
|
+
* and serves only programs of one scope: programs of different scopes never share a WebAssembly
|
|
95
|
+
* instance. `IFlexCodeSandboxPolicy` says when a worker is replaced; `dispose()` stops the programs
|
|
96
|
+
* running and ends every worker.
|
|
57
97
|
*/
|
|
58
98
|
export class FlexCodeSandbox {
|
|
59
|
-
private
|
|
99
|
+
private readonly workers = new Set<CodeWorker>();
|
|
100
|
+
/** Workers waiting for a program, the one that waited longest first. */
|
|
101
|
+
private readonly waiting: CodeWorker[] = [];
|
|
102
|
+
private readonly runs = new Map<CodeRun, Promise<unknown>>();
|
|
103
|
+
private started = 0;
|
|
104
|
+
private closed = false;
|
|
105
|
+
private disposal?: Promise<void>;
|
|
106
|
+
|
|
107
|
+
private constructor(
|
|
108
|
+
private readonly engine: plugins.ICodeSandboxEngine,
|
|
109
|
+
private readonly policy: Readonly<IFlexCodeSandboxPolicy>,
|
|
110
|
+
) {}
|
|
60
111
|
|
|
61
|
-
/** Loads QuickJS and amaro, once per process. */
|
|
62
|
-
public static async load(): Promise<FlexCodeSandbox> {
|
|
112
|
+
/** Loads QuickJS and amaro, once per process, into a sandbox with workers of its own. */
|
|
113
|
+
public static async load(policy: Partial<IFlexCodeSandboxPolicy> = {}): Promise<FlexCodeSandbox> {
|
|
63
114
|
enginePromise ??= plugins.importCodeSandboxEngine().catch((error: unknown) => {
|
|
64
115
|
enginePromise = undefined;
|
|
65
116
|
throw new FlexHarnessValidationError(
|
|
@@ -68,22 +119,224 @@ export class FlexCodeSandbox {
|
|
|
68
119
|
{ cause: error },
|
|
69
120
|
);
|
|
70
121
|
});
|
|
71
|
-
return new FlexCodeSandbox(await enginePromise);
|
|
122
|
+
return new FlexCodeSandbox(await enginePromise, { ...FLEX_CODE_SANDBOX_DEFAULT_POLICY, ...policy });
|
|
72
123
|
}
|
|
73
124
|
|
|
74
|
-
|
|
125
|
+
/** Workers started so far, alive now, and waiting for a program. */
|
|
126
|
+
public get workerCounts(): { started: number; live: number; waiting: number } {
|
|
127
|
+
return { started: this.started, live: this.workers.size, waiting: this.waiting.length };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Starts a worker for the scope's next program, unless one waits for it already. */
|
|
131
|
+
public prewarm(scope?: string): void {
|
|
132
|
+
if (this.closed || this.policy.maxWaiting === 0) return;
|
|
133
|
+
if (this.waiting.some((worker) => worker.programs === 0 || worker.scope === scope)) return;
|
|
134
|
+
this.park(this.spawn());
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Runs a program on a worker that waits for the scope, or on a new one. */
|
|
138
|
+
public async run(
|
|
75
139
|
source: string,
|
|
76
140
|
host: IFlexCodeHost,
|
|
77
141
|
limits: Readonly<IFlexCodeModeLimits>,
|
|
78
142
|
signal?: AbortSignal,
|
|
143
|
+
scope?: string,
|
|
79
144
|
): Promise<TFlexCodeRunResult> {
|
|
80
|
-
|
|
145
|
+
if (this.closed) throw new FlexHarnessClosedError();
|
|
146
|
+
const run = new CodeRun(this.engine, source, host, limits, signal, () => this.lease(scope));
|
|
147
|
+
const outcome = run.start();
|
|
148
|
+
this.runs.set(run, outcome);
|
|
149
|
+
try {
|
|
150
|
+
return await outcome;
|
|
151
|
+
} finally {
|
|
152
|
+
this.runs.delete(run);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Stops the programs running, and ends every worker once they settled. */
|
|
157
|
+
public dispose(): Promise<void> {
|
|
158
|
+
if (!this.disposal) {
|
|
159
|
+
this.closed = true;
|
|
160
|
+
this.disposal = this.endAll();
|
|
161
|
+
}
|
|
162
|
+
return this.disposal;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private async endAll(): Promise<void> {
|
|
166
|
+
for (const run of this.runs.keys()) run.abort('The code sandbox was disposed.');
|
|
167
|
+
await Promise.allSettled(this.runs.values());
|
|
168
|
+
await Promise.all([...this.workers].map((worker) => worker.end(false)));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** A waiting worker of the scope, the one that waited least; else one that has served no scope yet. */
|
|
172
|
+
private lease(scope: string | undefined): CodeWorker {
|
|
173
|
+
let index = this.lastWaiting((worker) => worker.programs > 0 && worker.scope === scope);
|
|
174
|
+
if (index === -1) index = this.lastWaiting((worker) => worker.programs === 0);
|
|
175
|
+
const worker = index === -1 ? this.spawn() : this.waiting.splice(index, 1)[0]!;
|
|
176
|
+
worker.claim(scope);
|
|
177
|
+
return worker;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private lastWaiting(matches: (worker: CodeWorker) => boolean): number {
|
|
181
|
+
for (let index = this.waiting.length - 1; index >= 0; index -= 1) {
|
|
182
|
+
if (matches(this.waiting[index]!)) return index;
|
|
183
|
+
}
|
|
184
|
+
return -1;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private spawn(): CodeWorker {
|
|
188
|
+
const worker = new CodeWorker({
|
|
189
|
+
returned: (returned) => this.returned(returned),
|
|
190
|
+
ended: (ended, replace) => this.ended(ended, replace),
|
|
191
|
+
});
|
|
192
|
+
this.workers.add(worker);
|
|
193
|
+
this.started += 1;
|
|
194
|
+
return worker;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** A worker freed its program's runtime: it waits for the next program, or is replaced. */
|
|
198
|
+
private returned(worker: CodeWorker): void {
|
|
199
|
+
if (this.closed) {
|
|
200
|
+
void worker.end(false);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (worker.programs >= this.policy.maxPrograms || worker.memoryBytes > this.policy.maxMemoryBytes) {
|
|
204
|
+
void worker.end(true);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
this.park(worker);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private park(worker: CodeWorker): void {
|
|
211
|
+
worker.wait(this.policy.idleMs);
|
|
212
|
+
this.waiting.push(worker);
|
|
213
|
+
while (this.waiting.length > this.policy.maxWaiting) void this.waiting.shift()!.end(false);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** A worker that served programs and was ended before its time is replaced in the background. */
|
|
217
|
+
private ended(worker: CodeWorker, replace: boolean): void {
|
|
218
|
+
this.workers.delete(worker);
|
|
219
|
+
const index = this.waiting.indexOf(worker);
|
|
220
|
+
if (index !== -1) this.waiting.splice(index, 1);
|
|
221
|
+
if (replace) this.prewarm(worker.scope);
|
|
81
222
|
}
|
|
82
223
|
}
|
|
83
224
|
|
|
84
|
-
|
|
85
|
-
|
|
225
|
+
/**
|
|
226
|
+
* A worker thread of a sandbox, and the WebAssembly instance it loads QuickJS into. It is `waiting`
|
|
227
|
+
* for a program, `serving` one, `releasing` the runtime of a program that ended by itself, or `ended`.
|
|
228
|
+
* Once it served a program, it serves only programs of that program's scope.
|
|
229
|
+
*/
|
|
230
|
+
class CodeWorker {
|
|
231
|
+
public state: 'waiting' | 'serving' | 'releasing' | 'ended' = 'waiting';
|
|
232
|
+
public programs = 0;
|
|
233
|
+
public scope?: string;
|
|
234
|
+
/** Its WebAssembly memory after the last program, which never shrinks. */
|
|
235
|
+
public memoryBytes = 0;
|
|
236
|
+
private ready = false;
|
|
237
|
+
private readonly thread: plugins.Worker;
|
|
238
|
+
private client?: ICodeWorkerClient;
|
|
239
|
+
private idleTimer?: ReturnType<typeof setTimeout>;
|
|
240
|
+
private releaseTimer?: ReturnType<typeof setTimeout>;
|
|
241
|
+
/** Settles the `leave()` of the program it releases, once it is returned or ended. */
|
|
242
|
+
private handedBack?: () => void;
|
|
86
243
|
private terminated?: Promise<unknown>;
|
|
244
|
+
|
|
245
|
+
constructor(private readonly owner: {
|
|
246
|
+
returned(worker: CodeWorker): void;
|
|
247
|
+
ended(worker: CodeWorker, replace: boolean): void;
|
|
248
|
+
}) {
|
|
249
|
+
this.thread = new plugins.Worker(workerUrl);
|
|
250
|
+
this.thread.on('message', (report: TCodeWorkerReport) => this.receive(report));
|
|
251
|
+
this.thread.on('error', (error: unknown) => this.fail(error));
|
|
252
|
+
this.thread.on('exit', (code: number) => this.fail(new Error(`The code mode worker exited with code ${code}.`)));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Takes the worker out of waiting for a program of the scope. */
|
|
256
|
+
public claim(scope: string | undefined): void {
|
|
257
|
+
clearTimeout(this.idleTimer);
|
|
258
|
+
this.thread.ref();
|
|
259
|
+
this.state = 'serving';
|
|
260
|
+
this.programs += 1;
|
|
261
|
+
this.scope = scope;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Hands the worker's reports to the program it serves, starting with `ready` once QuickJS is loaded. */
|
|
265
|
+
public serve(client: ICodeWorkerClient): void {
|
|
266
|
+
this.client = client;
|
|
267
|
+
if (this.ready) client.report({ kind: 'ready' });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
public send(request: TCodeWorkerRequest): void {
|
|
271
|
+
this.thread.postMessage(request);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* The program is over. A worker whose program ended by itself frees its runtime and is returned to
|
|
276
|
+
* the sandbox, or ended if that takes `releaseGraceMs`; any other is ended now, whatever it computes.
|
|
277
|
+
* Settles once the worker is returned or ended, so the program's next one finds it waiting.
|
|
278
|
+
*/
|
|
279
|
+
public leave(reusable: boolean): Promise<unknown> {
|
|
280
|
+
this.client = undefined;
|
|
281
|
+
if (!reusable || this.state !== 'serving') return this.end(true);
|
|
282
|
+
this.state = 'releasing';
|
|
283
|
+
this.releaseTimer = setTimeout(() => void this.end(true), releaseGraceMs);
|
|
284
|
+
return new Promise<void>((resolve) => {
|
|
285
|
+
this.handedBack = resolve;
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Waits for the next program, keeping the process alive no longer than its own work does. */
|
|
290
|
+
public wait(idleMs: number): void {
|
|
291
|
+
this.state = 'waiting';
|
|
292
|
+
this.thread.unref();
|
|
293
|
+
this.idleTimer = setTimeout(() => void this.end(false), idleMs);
|
|
294
|
+
this.idleTimer.unref();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Terminates the thread, which frees its WebAssembly instance; `replace` asks for a successor. */
|
|
298
|
+
public end(replace: boolean): Promise<unknown> {
|
|
299
|
+
if (!this.terminated) {
|
|
300
|
+
this.state = 'ended';
|
|
301
|
+
this.client = undefined;
|
|
302
|
+
clearTimeout(this.idleTimer);
|
|
303
|
+
clearTimeout(this.releaseTimer);
|
|
304
|
+
this.terminated = this.thread.terminate();
|
|
305
|
+
const handedBack = this.handedBack;
|
|
306
|
+
this.handedBack = undefined;
|
|
307
|
+
if (handedBack) void this.terminated.then(handedBack);
|
|
308
|
+
this.owner.ended(this, replace && this.programs > 0);
|
|
309
|
+
}
|
|
310
|
+
return this.terminated;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private receive(report: TCodeWorkerReport): void {
|
|
314
|
+
if (report.kind === 'released') {
|
|
315
|
+
if (this.state !== 'releasing') return;
|
|
316
|
+
clearTimeout(this.releaseTimer);
|
|
317
|
+
this.memoryBytes = report.memoryBytes;
|
|
318
|
+
this.owner.returned(this);
|
|
319
|
+
this.handedBack?.();
|
|
320
|
+
this.handedBack = undefined;
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (report.kind === 'ready') this.ready = true;
|
|
324
|
+
this.client?.report(report);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private fail(error: unknown): void {
|
|
328
|
+
if (this.terminated) return;
|
|
329
|
+
const client = this.client;
|
|
330
|
+
void this.end(true);
|
|
331
|
+
client?.fail(error);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
class CodeRun {
|
|
336
|
+
private worker?: CodeWorker;
|
|
337
|
+
private left?: Promise<unknown>;
|
|
338
|
+
/** The program ended by itself, so its worker can run the next one. */
|
|
339
|
+
private reusable = false;
|
|
87
340
|
private program = '';
|
|
88
341
|
private readonly calls = new AbortController();
|
|
89
342
|
private readonly queue: Array<() => void> = [];
|
|
@@ -111,6 +364,7 @@ class CodeRun {
|
|
|
111
364
|
private readonly host: IFlexCodeHost,
|
|
112
365
|
private readonly limits: Readonly<IFlexCodeModeLimits>,
|
|
113
366
|
private readonly signal: AbortSignal | undefined,
|
|
367
|
+
private readonly lease: () => CodeWorker,
|
|
114
368
|
) {}
|
|
115
369
|
|
|
116
370
|
public async start(): Promise<TFlexCodeRunResult> {
|
|
@@ -139,7 +393,7 @@ class CodeRun {
|
|
|
139
393
|
this.signal?.addEventListener('abort', onAbort, { once: true });
|
|
140
394
|
try {
|
|
141
395
|
if (this.signal?.aborted) onAbort();
|
|
142
|
-
else this.
|
|
396
|
+
else this.attach();
|
|
143
397
|
return await outcome;
|
|
144
398
|
} finally {
|
|
145
399
|
this.signal?.removeEventListener('abort', onAbort);
|
|
@@ -147,17 +401,20 @@ class CodeRun {
|
|
|
147
401
|
}
|
|
148
402
|
}
|
|
149
403
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
this.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
404
|
+
/** Stops the program from outside, as an abort. */
|
|
405
|
+
public abort(message: string): void {
|
|
406
|
+
this.halt({ kind: 'aborted', message });
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private attach(): void {
|
|
410
|
+
this.worker = this.lease();
|
|
411
|
+
this.worker.serve({
|
|
412
|
+
report: (report) => this.receive(report),
|
|
413
|
+
fail: (error) => this.halt({ kind: 'host', error }),
|
|
157
414
|
});
|
|
158
415
|
}
|
|
159
416
|
|
|
160
|
-
private receive(report:
|
|
417
|
+
private receive(report: TCodeRunReport): void {
|
|
161
418
|
if (this.closing) return;
|
|
162
419
|
switch (report.kind) {
|
|
163
420
|
case 'ready':
|
|
@@ -190,7 +447,7 @@ class CodeRun {
|
|
|
190
447
|
this.halt({ kind: 'host', error: new Error(`The code mode prelude failed: ${report.dumped}`) });
|
|
191
448
|
return;
|
|
192
449
|
}
|
|
193
|
-
this.
|
|
450
|
+
this.finish(this.failure(this.classify(report, report.phase)));
|
|
194
451
|
return;
|
|
195
452
|
case 'stopped':
|
|
196
453
|
this.halt(report.limit === 'cpu'
|
|
@@ -203,7 +460,7 @@ class CodeRun {
|
|
|
203
460
|
/** Has the worker compute: start the program, or go on with an answer. */
|
|
204
461
|
private send(request: TCodeWorkerRequest): void {
|
|
205
462
|
this.busy += 1;
|
|
206
|
-
this.worker!.
|
|
463
|
+
this.worker!.send(request);
|
|
207
464
|
this.armWatchdog();
|
|
208
465
|
}
|
|
209
466
|
|
|
@@ -295,13 +552,13 @@ class CodeRun {
|
|
|
295
552
|
if (outcome.ok) {
|
|
296
553
|
if (unawaited > 0) {
|
|
297
554
|
// Calls the program did not await are not part of its result: it fails, and proposes nothing.
|
|
298
|
-
this.
|
|
555
|
+
this.finish(this.failure({
|
|
299
556
|
error: 'program_error',
|
|
300
557
|
message: `The program returned with ${unawaited} call${unawaited === 1 ? '' : 's'} it did not await; await every call.`,
|
|
301
558
|
}));
|
|
302
559
|
return;
|
|
303
560
|
}
|
|
304
|
-
this.
|
|
561
|
+
this.finish({
|
|
305
562
|
ok: true,
|
|
306
563
|
value: outcome.value ?? null,
|
|
307
564
|
calls: Math.min(this.callCount, this.limits.maxCalls),
|
|
@@ -309,7 +566,7 @@ class CodeRun {
|
|
|
309
566
|
});
|
|
310
567
|
return;
|
|
311
568
|
}
|
|
312
|
-
this.
|
|
569
|
+
this.finish(this.failure(this.classify(outcome, 'program')));
|
|
313
570
|
}
|
|
314
571
|
|
|
315
572
|
private classify(
|
|
@@ -363,12 +620,21 @@ class CodeRun {
|
|
|
363
620
|
: { error: 'aborted', message: reason.message }));
|
|
364
621
|
}
|
|
365
622
|
|
|
623
|
+
/**
|
|
624
|
+
* The program ended by itself with this outcome. Its worker runs the next program, unless the
|
|
625
|
+
* program reached a limit: such a worker is not trusted with another.
|
|
626
|
+
*/
|
|
627
|
+
private finish(result: TFlexCodeRunResult): void {
|
|
628
|
+
this.reusable = result.ok || result.failure.error !== 'limit_exceeded';
|
|
629
|
+
this.done(result);
|
|
630
|
+
}
|
|
631
|
+
|
|
366
632
|
private done(result: TFlexCodeRunResult): void {
|
|
367
633
|
this.close(() => this.settle(result));
|
|
368
634
|
}
|
|
369
635
|
|
|
370
636
|
/**
|
|
371
|
-
* Decides the run's outcome and
|
|
637
|
+
* Decides the run's outcome and leaves the program's worker. Calls not started yet never start. Calls
|
|
372
638
|
* in flight are told to stop through their signal, and the outcome is delivered once they settled,
|
|
373
639
|
* so no call runs on after its program; a host call that ignores its signal gets `stopGraceMs`, and
|
|
374
640
|
* its answer is dropped.
|
|
@@ -377,7 +643,7 @@ class CodeRun {
|
|
|
377
643
|
if (this.closing) return;
|
|
378
644
|
this.closing = true;
|
|
379
645
|
this.queue.length = 0;
|
|
380
|
-
void this.
|
|
646
|
+
void this.leave();
|
|
381
647
|
if (this.inFlight.size === 0) {
|
|
382
648
|
deliver();
|
|
383
649
|
return;
|
|
@@ -408,11 +674,14 @@ class CodeRun {
|
|
|
408
674
|
this.wallTimer = setTimeout(check, Math.max(10, Math.min(250, this.limits.wallMs)));
|
|
409
675
|
}
|
|
410
676
|
|
|
411
|
-
/**
|
|
412
|
-
|
|
677
|
+
/**
|
|
678
|
+
* Hands the worker back: for the next program if this one ended by itself, ended otherwise, whatever
|
|
679
|
+
* it computes, which frees its WebAssembly instance with it.
|
|
680
|
+
*/
|
|
681
|
+
private leave(): Promise<unknown> {
|
|
413
682
|
clearTimeout(this.watchdog);
|
|
414
|
-
this.
|
|
415
|
-
return this.
|
|
683
|
+
this.left ??= this.worker?.leave(this.reusable) ?? Promise.resolve();
|
|
684
|
+
return this.left;
|
|
416
685
|
}
|
|
417
686
|
|
|
418
687
|
private async dispose(): Promise<void> {
|
|
@@ -420,6 +689,6 @@ class CodeRun {
|
|
|
420
689
|
if (this.wallTimer) clearTimeout(this.wallTimer);
|
|
421
690
|
this.queue.length = 0;
|
|
422
691
|
this.calls.abort();
|
|
423
|
-
await this.
|
|
692
|
+
await this.leave();
|
|
424
693
|
}
|
|
425
694
|
}
|