@modelprofile.com/flexharness 10.7.0 → 10.9.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/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.7.0",
5
+ "version": "10.9.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.7.0",
78
- "@modelprofile.com/flexharness-models": "10.7.0",
77
+ "@modelprofile.com/flexharness-agent": "10.9.0",
78
+ "@modelprofile.com/flexharness-models": "10.9.0",
79
79
  "@push.rocks/smartrequest": "^5.5.1",
80
80
  "ai": "^7.0.111",
81
81
  "zod": "^4.6.5"
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/flexharness',
6
- version: '10.7.0',
6
+ version: '10.9.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',