@modelprofile.com/flexharness 10.6.0 → 10.8.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.
@@ -6,13 +6,30 @@ function base64Utf8(text) {
6
6
  }
7
7
  return btoa(binary);
8
8
  }
9
+ const textMediaTypePattern = /^(?:text\/[^;]+|application\/(?:csv|json|xml|yaml|[^;]+\+(?:json|xml|yaml)))\s*(?:;|$)/iu;
10
+ const charsetParameterPattern = /;\s*charset\s*=/iu;
11
+ /**
12
+ * The media type a text attachment is sent with. Its text is sent as UTF-8; a media type that
13
+ * does not already name text, such as `application/x-openscad`, gains `charset=utf-8`, so a
14
+ * provider that reads only some files natively can still tell the part is text.
15
+ */
16
+ function textAttachmentMediaType(mediaType) {
17
+ return textMediaTypePattern.test(mediaType) || charsetParameterPattern.test(mediaType)
18
+ ? mediaType
19
+ : `${mediaType}; charset=utf-8`;
20
+ }
9
21
  function attachmentToPromptPart(attachment) {
10
22
  if (attachment.truncated) {
11
23
  throw new Error(`Attachment "${attachment.name}" is truncated and cannot be sent.`);
12
24
  }
13
25
  const described = { mediaType: attachment.mediaType, name: attachment.name };
14
26
  if (attachment.kind === 'text' && attachment.text !== undefined) {
15
- return { type: 'file', data: base64Utf8(attachment.text), ...described };
27
+ return {
28
+ type: 'file',
29
+ data: base64Utf8(attachment.text),
30
+ mediaType: textAttachmentMediaType(attachment.mediaType),
31
+ name: attachment.name,
32
+ };
16
33
  }
17
34
  if (attachment.dataBase64 === undefined) {
18
35
  throw new Error(`Attachment "${attachment.name}" carries no payload.`);
@@ -93,4 +110,4 @@ export async function runFlexHarnessChatIntent(harness, intent) {
93
110
  return undefined;
94
111
  }
95
112
  }
96
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZnVuY3Rpb25zLmludGVudHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19jaGF0X2hhcm5lc3MvZnVuY3Rpb25zLmludGVudHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBaURBLFNBQVMsVUFBVSxDQUFDLElBQVk7SUFDOUIsTUFBTSxLQUFLLEdBQUcsSUFBSSxXQUFXLEVBQUUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDN0MsSUFBSSxNQUFNLEdBQUcsRUFBRSxDQUFDO0lBQ2hCLEtBQUssSUFBSSxNQUFNLEdBQUcsQ0FBQyxFQUFFLE1BQU0sR0FBRyxLQUFLLENBQUMsTUFBTSxFQUFFLE1BQU0sSUFBSSxNQUFNLEVBQUUsQ0FBQztRQUM3RCxNQUFNLElBQUksTUFBTSxDQUFDLFlBQVksQ0FBQyxHQUFHLEtBQUssQ0FBQyxRQUFRLENBQUMsTUFBTSxFQUFFLE1BQU0sR0FBRyxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBQzVFLENBQUM7SUFDRCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztBQUN0QixDQUFDO0FBRUQsU0FBUyxzQkFBc0IsQ0FBQyxVQUFzQztJQUNwRSxJQUFJLFVBQVUsQ0FBQyxTQUFTLEVBQUUsQ0FBQztRQUN6QixNQUFNLElBQUksS0FBSyxDQUFDLGVBQWUsVUFBVSxDQUFDLElBQUksb0NBQW9DLENBQUMsQ0FBQztJQUN0RixDQUFDO0lBQ0QsTUFBTSxTQUFTLEdBQUcsRUFBRSxTQUFTLEVBQUUsVUFBVSxDQUFDLFNBQVMsRUFBRSxJQUFJLEVBQUUsVUFBVSxDQUFDLElBQUksRUFBRSxDQUFDO0lBQzdFLElBQUksVUFBVSxDQUFDLElBQUksS0FBSyxNQUFNLElBQUksVUFBVSxDQUFDLElBQUksS0FBSyxTQUFTLEVBQUUsQ0FBQztRQUNoRSxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsVUFBVSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsRUFBRSxHQUFH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
113
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZnVuY3Rpb25zLmludGVudHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19jaGF0X2hhcm5lc3MvZnVuY3Rpb25zLmludGVudHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBaURBLFNBQVMsVUFBVSxDQUFDLElBQVk7SUFDOUIsTUFBTSxLQUFLLEdBQUcsSUFBSSxXQUFXLEVBQUUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDN0MsSUFBSSxNQUFNLEdBQUcsRUFBRSxDQUFDO0lBQ2hCLEtBQUssSUFBSSxNQUFNLEdBQUcsQ0FBQyxFQUFFLE1BQU0sR0FBRyxLQUFLLENBQUMsTUFBTSxFQUFFLE1BQU0sSUFBSSxNQUFNLEVBQUUsQ0FBQztRQUM3RCxNQUFNLElBQUksTUFBTSxDQUFDLFlBQVksQ0FBQyxHQUFHLEtBQUssQ0FBQyxRQUFRLENBQUMsTUFBTSxFQUFFLE1BQU0sR0FBRyxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBQzVFLENBQUM7SUFDRCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztBQUN0QixDQUFDO0FBRUQsTUFBTSxvQkFBb0IsR0FBRywwRkFBMEYsQ0FBQztBQUN4SCxNQUFNLHVCQUF1QixHQUFHLG1CQUFtQixDQUFDO0FBRXBEOzs7O0dBSUc7QUFDSCxTQUFTLHVCQUF1QixDQUFDLFNBQWlCO0lBQ2hELE9BQU8sb0JBQW9CLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLHVCQUF1QixDQUFDLElBQUksQ0FBQyxTQUFTLENBQUM7UUFDcEYsQ0FBQyxDQUFDLFNBQVM7UUFDWCxDQUFDLENBQUMsR0FBRyxTQUFTLGlCQUFpQixDQUFDO0FBQ3BDLENBQUM7QUFFRCxTQUFTLHNCQUFzQixDQUFDLFVBQXNDO0lBQ3BFLElBQUksVUFBVSxDQUFDLFNBQVMsRUFBRSxDQUFDO1FBQ3pCLE1BQU0sSUFBSSxLQUFLLENBQUMsZUFBZSxVQUFVLENBQUMsSUFBSSxvQ0FBb0MsQ0FBQyxDQUFDO0lB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
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": "10.6.0",
5
+ "version": "10.8.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",
@@ -74,8 +74,8 @@
74
74
  }
75
75
  },
76
76
  "dependencies": {
77
- "@modelprofile.com/flexharness-agent": "10.6.0",
78
- "@modelprofile.com/flexharness-models": "10.6.0",
77
+ "@modelprofile.com/flexharness-agent": "10.8.0",
78
+ "@modelprofile.com/flexharness-models": "10.8.0",
79
79
  "@push.rocks/smartrequest": "^5.5.1",
80
80
  "ai": "^7.0.111",
81
81
  "zod": "^4.6.5"
@@ -92,7 +92,7 @@
92
92
  "lit": "^3.2.0",
93
93
  "@types/react": "^19.0.0",
94
94
  "@lossless.org/client": "^1.17.0",
95
- "@design.estate/dees-catalog": "^17.0.0"
95
+ "@design.estate/dees-catalog": "^17.0.0 || ^19.5.0"
96
96
  },
97
97
  "peerDependenciesMeta": {
98
98
  "@modelcontextprotocol/sdk": {
package/readme.md CHANGED
@@ -62,7 +62,7 @@ or provider SDKs.
62
62
  | `/chat` | Portable streaming `ChatSession`, history and usage | None |
63
63
  | `/chat/cli` | Ink/React terminal chat | `ink ink-text-input react`; TypeScript: `@types/react` |
64
64
  | `/chat/web` | Lit `flexchat-window`, `flexchat-message`, `flexchat-input` | `lit` |
65
- | `/chat/harness` | `FlexHarnessTranscript` and intents for the `@design.estate/dees-catalog` harness components, browser-safe | `@design.estate/dees-catalog` (types only) |
65
+ | `/chat/harness` | `FlexHarnessTranscript` and intents for the `@design.estate/dees-catalog` harness components, browser-safe | `@design.estate/dees-catalog` `^17.0.0 \|\| ^19.5.0` (types only) |
66
66
  | `/kvm` | Browser control, terminal framing and OCR observation | `puppeteer` |
67
67
  | `/documents` | PDF and image documents, `extractTextFromPdf` | `@push.rocks/smartpdf`; for image documents `sharp` |
68
68
  | `/mcp` | MCP clients and AI SDK tool conversion | `@modelcontextprotocol/sdk` |
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/flexharness',
6
- version: '10.6.0',
6
+ version: '10.8.0',
7
7
  description: 'The FlexHarness toolbox source repository and shared dependency catalog.'
8
8
  }
@@ -397,6 +397,8 @@ interface IActiveRun {
397
397
  callbackParts: TFlexMessagePart[];
398
398
  partTextUtf8Bytes: Map<string, number>;
399
399
  callbacksClosed: boolean;
400
+ /** Set once the run emitted its `run.finished` event. */
401
+ finishedEventEmitted: boolean;
400
402
  /** The usage the provider reported for the run's model calls so far. */
401
403
  usage: IFlexUsage;
402
404
  reasoningPartIds: Map<string, string>;
@@ -4216,6 +4218,7 @@ export class FlexHarness<TScope = unknown> {
4216
4218
  callbackParts: [],
4217
4219
  partTextUtf8Bytes: new Map(),
4218
4220
  callbacksClosed: false,
4221
+ finishedEventEmitted: false,
4219
4222
  usage: {
4220
4223
  inputTokens: 0,
4221
4224
  outputTokens: 0,
@@ -4873,12 +4876,17 @@ export class FlexHarness<TScope = unknown> {
4873
4876
  // child waiting for permission or model I/O can keep a failed parent alive indefinitely.
4874
4877
  if (run.backgroundSubagents.size > 0 && !run.controller.signal.aborted) run.controller.abort(error);
4875
4878
  await Promise.allSettled([...run.backgroundSubagents]);
4876
- if (run.state.lifecycle === 'fenced') {
4877
- await this.fenceNamespace(run.state, run, error);
4878
- throw error;
4879
+ try {
4880
+ if (run.state.lifecycle === 'fenced') {
4881
+ await this.fenceNamespace(run.state, run, error);
4882
+ throw error;
4883
+ }
4884
+ if (run.state.lifecycle === 'retired') throw error;
4885
+ return await this.finishFailedRun(run, error, generated);
4886
+ } catch (finalError) {
4887
+ this.emitUnpublishedRunFinish(run, finalError, generated);
4888
+ throw finalError;
4879
4889
  }
4880
- if (run.state.lifecycle === 'retired') throw error;
4881
- return this.finishFailedRun(run, error, generated);
4882
4890
  } finally {
4883
4891
  run.callbacksClosed = true;
4884
4892
  this.rejectRunPermissions(
@@ -4949,7 +4957,10 @@ export class FlexHarness<TScope = unknown> {
4949
4957
  const snapshot = await run.stored.eventStore.load(run.stored.session.sessionId);
4950
4958
  const events = snapshot?.events ?? [];
4951
4959
  outcome = this.canonicalOutcomes(events).get(run.runId);
4952
- run.steering.keptInContext = plugins.isGenerationInModelContext(events, run.runId);
4960
+ // A generation without a durable outcome is interrupted by the next load, which leaves it
4961
+ // out of the model context.
4962
+ run.steering.keptInContext = outcome !== undefined
4963
+ && plugins.isGenerationInModelContext(events, run.runId);
4953
4964
  this.refreshAutomaticArchiveHorizon(run.stored);
4954
4965
  } catch (outcomeError) {
4955
4966
  errors.push(this.projectExternalError(run, outcomeError, 'persistence'));
@@ -7984,6 +7995,25 @@ export class FlexHarness<TScope = unknown> {
7984
7995
  }
7985
7996
  }
7986
7997
 
7998
+ /**
7999
+ * Ends a run that fails without publishing a terminal projection, because its namespace was
8000
+ * fenced or retired or its durable outcome could not be established: listeners still receive
8001
+ * its terminal message events and its one `run.finished`. Nothing here is persisted; the next
8002
+ * load of the namespace repairs the stored projection from the canonical outcome.
8003
+ */
8004
+ private emitUnpublishedRunFinish(run: IActiveRun, error: unknown, generated: boolean): void {
8005
+ if (run.finishedEventEmitted || run.state.tombstones.has(run.sessionId)) return;
8006
+ const cancelled = run.ownerCancellation !== undefined && run.internalFailure === undefined;
8007
+ const terminalError = cancelled
8008
+ ? run.ownerCancellation!
8009
+ : error instanceof FlexHarnessRunError
8010
+ ? error
8011
+ : this.projectExternalError(run, error, generated ? 'persistence' : 'agentSession');
8012
+ const terminal = this.buildTerminal(run, cancelled ? 'cancelled' : 'failed', undefined, terminalError);
8013
+ this.emitTerminalProjection(run, terminal);
8014
+ this.emitFinalRunEvent(run, terminal.assistantMessage, terminalError, cancelled);
8015
+ }
8016
+
7987
8017
  private emitFinalRunEvent(
7988
8018
  run: IActiveRun,
7989
8019
  message: IFlexMessage,
@@ -7993,6 +8023,7 @@ export class FlexHarness<TScope = unknown> {
7993
8023
  const status: IFlexRunFinishedEvent['status'] = error
7994
8024
  ? cancelled ? 'cancelled' : 'failed'
7995
8025
  : 'completed';
8026
+ run.finishedEventEmitted = true;
7996
8027
  const steering = this.closeRunSteering(run.steering);
7997
8028
  this.emitEvent(run.scopeId, run.stored.session, {
7998
8029
  type: 'run.finished',
package/ts/readme.md CHANGED
@@ -62,7 +62,7 @@ or provider SDKs.
62
62
  | `/chat` | Portable streaming `ChatSession`, history and usage | None |
63
63
  | `/chat/cli` | Ink/React terminal chat | `ink ink-text-input react`; TypeScript: `@types/react` |
64
64
  | `/chat/web` | Lit `flexchat-window`, `flexchat-message`, `flexchat-input` | `lit` |
65
- | `/chat/harness` | `FlexHarnessTranscript` and intents for the `@design.estate/dees-catalog` harness components, browser-safe | `@design.estate/dees-catalog` (types only) |
65
+ | `/chat/harness` | `FlexHarnessTranscript` and intents for the `@design.estate/dees-catalog` harness components, browser-safe | `@design.estate/dees-catalog` `^17.0.0 \|\| ^19.5.0` (types only) |
66
66
  | `/kvm` | Browser control, terminal framing and OCR observation | `puppeteer` |
67
67
  | `/documents` | PDF and image documents, `extractTextFromPdf` | `@push.rocks/smartpdf`; for image documents `sharp` |
68
68
  | `/mcp` | MCP clients and AI SDK tool conversion | `@modelcontextprotocol/sdk` |
@@ -56,13 +56,32 @@ function base64Utf8(text: string): string {
56
56
  return btoa(binary);
57
57
  }
58
58
 
59
+ const textMediaTypePattern = /^(?:text\/[^;]+|application\/(?:csv|json|xml|yaml|[^;]+\+(?:json|xml|yaml)))\s*(?:;|$)/iu;
60
+ const charsetParameterPattern = /;\s*charset\s*=/iu;
61
+
62
+ /**
63
+ * The media type a text attachment is sent with. Its text is sent as UTF-8; a media type that
64
+ * does not already name text, such as `application/x-openscad`, gains `charset=utf-8`, so a
65
+ * provider that reads only some files natively can still tell the part is text.
66
+ */
67
+ function textAttachmentMediaType(mediaType: string): string {
68
+ return textMediaTypePattern.test(mediaType) || charsetParameterPattern.test(mediaType)
69
+ ? mediaType
70
+ : `${mediaType}; charset=utf-8`;
71
+ }
72
+
59
73
  function attachmentToPromptPart(attachment: plugins.IHarnessAttachment): TFlexPromptPart {
60
74
  if (attachment.truncated) {
61
75
  throw new Error(`Attachment "${attachment.name}" is truncated and cannot be sent.`);
62
76
  }
63
77
  const described = { mediaType: attachment.mediaType, name: attachment.name };
64
78
  if (attachment.kind === 'text' && attachment.text !== undefined) {
65
- return { type: 'file', data: base64Utf8(attachment.text), ...described };
79
+ return {
80
+ type: 'file',
81
+ data: base64Utf8(attachment.text),
82
+ mediaType: textAttachmentMediaType(attachment.mediaType),
83
+ name: attachment.name,
84
+ };
66
85
  }
67
86
  if (attachment.dataBase64 === undefined) {
68
87
  throw new Error(`Attachment "${attachment.name}" carries no payload.`);
@@ -4,6 +4,8 @@ A FlexHarness session as the inputs of the `@design.estate/dees-catalog` harness
4
4
  pnpm add @modelprofile.com/flexharness @design.estate/dees-catalog
5
5
  ```
6
6
 
7
+ The optional peer range is `^17.0.0 || ^19.5.0`; the package builds and is tested against 19.5.
8
+
7
9
  The subpath imports only types from `@design.estate/dees-catalog` and no runtime code at all, so it runs in the browser beside the components as well as on the server beside the harness.
8
10
 
9
11
  ```typescript
@@ -87,7 +89,7 @@ The intent functions map component events to plain `TFlexHarnessChatIntent` valu
87
89
 
88
90
  Intents are untrusted client input: a client can name any `scopeId` and `sessionId`. Before calling `runFlexHarnessChatIntent`, the server authenticates the caller and checks, or replaces, the intent's target with a session the caller may act on. The harness itself checks only that a permission belongs to the session the intent names, not who is asking.
89
91
 
90
- - `harness-send`: text and attachments become prompt parts. A text attachment is sent as a UTF-8 file part; a truncated attachment, or one without a payload, throws. A message sent while idle, queued behind the running turn, or delivered as `next-turn` becomes `enqueuePrompt`. A steer joins the running turn, `transcript.activeQueueId`, through `steerPrompt`, and answers on that turn's model; with no running turn, or when its turn ended before the harness took it, it is the next turn's input and is queued with the prompt options. A steer id comes from `crypto.randomUUID()`, which browsers offer only in secure contexts (HTTPS or localhost). The composer's `model` becomes the prompt's `modelHint`; a host that offers accounts or reasoning efforts passes its own `modelHint`.
92
+ - `harness-send`: text and attachments become prompt parts. A text attachment is sent as a UTF-8 file part; when its media type does not already name text (`text/*`, JSON, XML, YAML, CSV or a `+json`, `+xml` or `+yaml` type), it gains `charset=utf-8`, for example `application/x-openscad; charset=utf-8`, so a provider that reads only some files natively can still send it as text; a truncated attachment, or one without a payload, throws. A message sent while idle, queued behind the running turn, or delivered as `next-turn` becomes `enqueuePrompt`. A steer joins the running turn, `transcript.activeQueueId`, through `steerPrompt`, and answers on that turn's model; with no running turn, or when its turn ended before the harness took it, it is the next turn's input and is queued with the prompt options. A steer id comes from `crypto.randomUUID()`, which browsers offer only in secure contexts (HTTPS or localhost). The composer's `model` becomes the prompt's `modelHint`; a host that offers accounts or reasoning efforts passes its own `modelHint`.
91
93
  - `harness-permission-response`: `reject` rejects; an approval with `remember` is `always`, otherwise `once`. A request with `requiresExplicitResponse` refuses `always`.
92
94
  - `harness-abort`: `abort()` of the session.
93
95