@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.
Files changed (44) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/classes.codesandbox.d.ts +58 -7
  3. package/dist_ts/classes.codesandbox.js +267 -34
  4. package/dist_ts/classes.codesandbox.worker.d.ts +6 -1
  5. package/dist_ts/classes.codesandbox.worker.js +34 -8
  6. package/dist_ts/classes.flexharness.callbacks.d.ts +6 -0
  7. package/dist_ts/classes.flexharness.callbacks.js +24 -4
  8. package/dist_ts/classes.flexharness.core.d.ts +8 -0
  9. package/dist_ts/classes.flexharness.core.js +29 -1
  10. package/dist_ts/classes.flexharness.lifecycle.js +7 -1
  11. package/dist_ts/classes.flexharness.runs.js +9 -7
  12. package/dist_ts/classes.flexharness.tooldispatch.js +2 -2
  13. package/dist_ts/interfaces.d.ts +9 -1
  14. package/dist_ts/interfaces.flexharness.d.ts +5 -3
  15. package/dist_ts/plugins.d.ts +3 -1
  16. package/dist_ts/plugins.js +2 -2
  17. package/dist_ts_chat_harness/classes.chatclient.d.ts +7 -4
  18. package/dist_ts_chat_harness/classes.chatclient.js +3 -3
  19. package/dist_ts_chat_harness/classes.transcript.d.ts +1 -0
  20. package/dist_ts_chat_harness/classes.transcript.js +16 -1
  21. package/dist_ts_chat_harness_server/classes.chathub.js +2 -1
  22. package/dist_ts_chat_harness_server/classes.chatsubscription.d.ts +6 -3
  23. package/dist_ts_chat_harness_server/classes.chatsubscription.js +18 -4
  24. package/dist_ts_chat_harness_server/interfaces.d.ts +6 -0
  25. package/package.json +3 -3
  26. package/readme.md +5 -2
  27. package/ts/00_commitinfo_data.ts +1 -1
  28. package/ts/classes.codesandbox.ts +303 -34
  29. package/ts/classes.codesandbox.worker.ts +38 -8
  30. package/ts/classes.flexharness.callbacks.ts +22 -3
  31. package/ts/classes.flexharness.core.ts +31 -0
  32. package/ts/classes.flexharness.lifecycle.ts +5 -0
  33. package/ts/classes.flexharness.runs.ts +8 -5
  34. package/ts/classes.flexharness.tooldispatch.ts +1 -1
  35. package/ts/interfaces.flexharness.ts +5 -3
  36. package/ts/interfaces.ts +10 -1
  37. package/ts/plugins.ts +3 -1
  38. package/ts/readme.md +5 -2
  39. package/ts_chat_harness/classes.chatclient.ts +9 -6
  40. package/ts_chat_harness/classes.transcript.ts +19 -0
  41. package/ts_chat_harness_server/classes.chathub.ts +1 -0
  42. package/ts_chat_harness_server/classes.chatsubscription.ts +19 -3
  43. package/ts_chat_harness_server/interfaces.ts +6 -0
  44. 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. A transcript that
23
- * loses track, or an update the tab does not acknowledge, makes the next update a `reset`.
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5jaGF0c3Vic2NyaXB0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHNfY2hhdF9oYXJuZXNzX3NlcnZlci9jbGFzc2VzLmNoYXRzdWJzY3JpcHRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFpQnhDLE1BQU0sWUFBWSxHQUFHLEdBQW1CLEVBQUUsQ0FBQyxDQUFDO0lBQzFDLEtBQUssRUFBRSxLQUFLO0lBQ1osSUFBSSxFQUFFLEtBQUs7SUFDWCxNQUFNLEVBQUUsRUFBRTtJQUNWLE1BQU0sRUFBRSxLQUFLO0lBQ2IsS0FBSyxFQUFFLEtBQUs7SUFDWixXQUFXLEVBQUUsS0FBSztJQUNsQixPQUFPLEVBQUUsS0FBSztJQUNkLE1BQU0sRUFBRSxFQUFFO0NBQ1gsQ0FBQyxDQUFDO0FBRUgsaUVBQWlFO0FBQ2pFLE1BQU0sS0FBSyxHQUFHLENBQUMsS0FBYSxFQUFpQixFQUFFLENBQzdDLElBQUksT0FBTyxDQUFPLENBQUMsT0FBTyxFQUFFLEVBQUU7SUFDNUIsVUFBVSxDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDO0FBQ3ZDLENBQUMsQ0FBQyxDQUFDO0FBRUwsTUFBTSxVQUFVLEdBQUcsQ0FBQyxVQUEwQixFQUFXLEVBQUUsQ0FDekQsVUFBVSxDQUFDLEtBQUs7T0FDYixVQUFVLENBQUMsSUFBSTtPQUNmLFVBQVUsQ0FBQyxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUM7T0FDNUIsVUFBVSxDQUFDLE1BQU07T0FDakIsVUFBVSxDQUFDLEtBQUs7T0FDaEIsVUFBVSxDQUFDLFdBQVc7T0FDdEIsVUFBVSxDQUFDLE9BQU87T0FDbEIsVUFBVSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDO0FBc0JsQzs7Ozs7R0FLRztBQUNILE1BQU0sT0FBTywyQkFBMkI7SUFDdEIsRUFBRSxDQUFTO0lBQ1gs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
@@ -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.1.1",
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.1.1",
91
- "@modelprofile.com/flexharness-models": "11.1.1",
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.completed` event carries zero-based `messageIndex` and `partIndex` coordinates from the session's authoritative message and part sequences.
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`, and `IFlexPartDeltaEvent` owns exact delta-only events and their UTF-8 counters.
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
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/flexharness',
6
- version: '11.1.1',
6
+ version: '11.3.0',
7
7
  description: 'The FlexHarness toolbox source repository and shared dependency catalog.'
8
8
  }
@@ -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: every program in a worker thread and
54
- * WebAssembly instance of its own, with no globals beyond the language, `api`, `ApiError` and
55
- * `console`, and every limit of `IFlexCodeModeLimits` enforced. Program types are stripped with amaro
56
- * first.
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 constructor(private readonly engine: plugins.ICodeSandboxEngine) {}
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
- public run(
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
- return new CodeRun(this.engine, source, host, limits, signal).start();
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
- class CodeRun {
85
- private worker?: plugins.Worker;
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.spawn();
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
- private spawn(): void {
151
- const worker = new plugins.Worker(workerUrl);
152
- this.worker = worker;
153
- worker.on('message', (report: TCodeWorkerReport) => this.receive(report));
154
- worker.on('error', (error: unknown) => this.halt({ kind: 'host', error }));
155
- worker.on('exit', (code: number) => {
156
- this.halt({ kind: 'host', error: new Error(`The code mode worker exited with code ${code}.`) });
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: TCodeWorkerReport): void {
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.done(this.failure(this.classify(report, report.phase)));
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!.postMessage(request);
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.done(this.failure({
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.done({
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.done(this.failure(this.classify(outcome, 'program')));
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 ends the program's worker. Calls not started yet never start. Calls
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.terminate();
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
- /** Ends the program's worker, whatever it computes, which frees its WebAssembly instance with it. */
412
- private terminate(): Promise<unknown> {
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.terminated ??= this.worker?.terminate() ?? Promise.resolve();
415
- return this.terminated;
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.terminate();
692
+ await this.leave();
424
693
  }
425
694
  }