@qvac/harness 0.7.0 → 1.0.0-rc.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 (204) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +239 -2
  3. package/dist-lib/index.d.ts +58 -3
  4. package/dist-lib/index.js +736 -239
  5. package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.d.ts +1 -0
  6. package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.js +10 -0
  7. package/dist-lib/lib/capabilities/completion-engine/actors/answering.d.ts +3 -0
  8. package/dist-lib/lib/capabilities/completion-engine/actors/answering.js +17 -0
  9. package/dist-lib/lib/capabilities/completion-engine/actors/approval.d.ts +4 -0
  10. package/dist-lib/lib/capabilities/completion-engine/actors/approval.js +29 -0
  11. package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.d.ts +4 -0
  12. package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.js +73 -0
  13. package/dist-lib/lib/capabilities/completion-engine/actors/execute.d.ts +41 -0
  14. package/dist-lib/lib/capabilities/completion-engine/actors/execute.js +49 -0
  15. package/dist-lib/lib/capabilities/completion-engine/actors/executing.d.ts +3 -0
  16. package/dist-lib/lib/capabilities/completion-engine/actors/executing.js +102 -0
  17. package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.d.ts +47 -0
  18. package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.js +87 -0
  19. package/dist-lib/lib/capabilities/completion-engine/actors/finishing.d.ts +2 -0
  20. package/dist-lib/lib/capabilities/completion-engine/actors/finishing.js +32 -0
  21. package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.d.ts +3 -0
  22. package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.js +75 -0
  23. package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.d.ts +3 -0
  24. package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.js +152 -0
  25. package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.d.ts +3 -0
  26. package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.js +94 -0
  27. package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.d.ts +9 -0
  28. package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.js +11 -0
  29. package/dist-lib/lib/capabilities/completion-engine/actors/preloading.d.ts +3 -0
  30. package/dist-lib/lib/capabilities/completion-engine/actors/preloading.js +114 -0
  31. package/dist-lib/lib/capabilities/completion-engine/actors/preparing.d.ts +3 -0
  32. package/dist-lib/lib/capabilities/completion-engine/actors/preparing.js +198 -0
  33. package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.d.ts +3 -0
  34. package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.js +71 -0
  35. package/dist-lib/lib/capabilities/completion-engine/actors/rejection.d.ts +18 -0
  36. package/dist-lib/lib/capabilities/completion-engine/actors/rejection.js +70 -0
  37. package/dist-lib/lib/capabilities/completion-engine/actors/resolving.d.ts +3 -0
  38. package/dist-lib/lib/capabilities/completion-engine/actors/resolving.js +60 -0
  39. package/dist-lib/lib/capabilities/completion-engine/actors/stream.d.ts +21 -0
  40. package/dist-lib/lib/capabilities/completion-engine/actors/stream.js +48 -0
  41. package/dist-lib/lib/capabilities/completion-engine/actors/streaming.d.ts +3 -0
  42. package/dist-lib/lib/capabilities/completion-engine/actors/streaming.js +156 -0
  43. package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.d.ts +7 -0
  44. package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.js +61 -0
  45. package/dist-lib/lib/capabilities/completion-engine/driver.d.ts +25 -0
  46. package/dist-lib/lib/capabilities/completion-engine/driver.js +553 -0
  47. package/dist-lib/lib/capabilities/completion-engine/durable.d.ts +61 -0
  48. package/dist-lib/lib/capabilities/completion-engine/durable.js +150 -0
  49. package/dist-lib/lib/capabilities/completion-engine/operations.d.ts +6 -0
  50. package/dist-lib/lib/capabilities/completion-engine/operations.js +239 -0
  51. package/dist-lib/lib/capabilities/completion-engine/ports.d.ts +3 -0
  52. package/dist-lib/lib/capabilities/completion-engine/ports.js +267 -0
  53. package/dist-lib/lib/capabilities/completion-engine/session.d.ts +76 -0
  54. package/dist-lib/lib/capabilities/completion-engine/session.js +70 -0
  55. package/dist-lib/lib/capabilities/completion-loop/index.d.ts +80 -0
  56. package/dist-lib/lib/capabilities/completion-loop/index.js +158 -0
  57. package/dist-lib/lib/capabilities/completion-loop/metrics.d.ts +5 -0
  58. package/dist-lib/lib/capabilities/completion-loop/metrics.js +29 -0
  59. package/dist-lib/lib/capabilities/completion-loop/round.d.ts +33 -0
  60. package/dist-lib/lib/capabilities/completion-loop/round.js +353 -0
  61. package/dist-lib/lib/capabilities/completion-loop/tool-execution.d.ts +60 -0
  62. package/dist-lib/lib/capabilities/completion-loop/tool-execution.js +270 -0
  63. package/dist-lib/lib/capabilities/completion-loop/turns.d.ts +23 -0
  64. package/dist-lib/lib/capabilities/completion-loop/turns.js +112 -0
  65. package/dist-lib/lib/capabilities/completion-runtime.d.ts +46 -0
  66. package/dist-lib/lib/capabilities/completion-runtime.js +74 -0
  67. package/dist-lib/lib/capabilities/completion.d.ts +18 -335
  68. package/dist-lib/lib/capabilities/completion.js +20 -1301
  69. package/dist-lib/lib/capabilities/download.d.ts +1 -1
  70. package/dist-lib/lib/capabilities/download.js +7 -2
  71. package/dist-lib/lib/capabilities/invoke.js +5 -3
  72. package/dist-lib/lib/capabilities/tool-plan.d.ts +35 -0
  73. package/dist-lib/lib/capabilities/tool-plan.js +66 -0
  74. package/dist-lib/lib/capabilities/tool-turns.d.ts +11 -2
  75. package/dist-lib/lib/capabilities/tool-turns.js +30 -2
  76. package/dist-lib/lib/capabilities/types.d.ts +287 -0
  77. package/dist-lib/lib/capabilities/types.js +1 -0
  78. package/dist-lib/lib/completion-lifecycle.d.ts +14 -0
  79. package/dist-lib/lib/completion-lifecycle.js +126 -0
  80. package/dist-lib/lib/connect.d.ts +10 -3
  81. package/dist-lib/lib/connect.js +329 -23
  82. package/dist-lib/lib/contract-types.d.ts +19 -2
  83. package/dist-lib/lib/harness-lifecycle.d.ts +66 -0
  84. package/dist-lib/lib/harness-lifecycle.js +129 -0
  85. package/dist-lib/lib/helpers/detach-json.d.ts +1 -0
  86. package/dist-lib/lib/helpers/detach-json.js +146 -0
  87. package/dist-lib/lib/helpers/markup-scanner.d.ts +26 -0
  88. package/dist-lib/lib/helpers/markup-scanner.js +156 -0
  89. package/dist-lib/lib/helpers/markup-stream-filter.js +138 -35
  90. package/dist-lib/lib/helpers/race-abort.d.ts +9 -4
  91. package/dist-lib/lib/helpers/race-abort.js +2 -7
  92. package/dist-lib/lib/helpers/recovery-required.d.ts +1 -0
  93. package/dist-lib/lib/helpers/recovery-required.js +1 -0
  94. package/dist-lib/lib/helpers/run-outcome.d.ts +10 -2
  95. package/dist-lib/lib/helpers/run-outcome.js +8 -5
  96. package/dist-lib/lib/helpers/tool-call-markup.d.ts +2 -0
  97. package/dist-lib/lib/helpers/tool-call-markup.js +197 -82
  98. package/dist-lib/lib/hooks/built-in/arg-repair.d.ts +1 -0
  99. package/dist-lib/lib/hooks/built-in/arg-repair.js +1 -0
  100. package/dist-lib/lib/hooks/built-in/egress.d.ts +1 -0
  101. package/dist-lib/lib/hooks/built-in/egress.js +2 -1
  102. package/dist-lib/lib/hooks/built-in/sandbox.d.ts +1 -0
  103. package/dist-lib/lib/hooks/built-in/sandbox.js +6 -1
  104. package/dist-lib/lib/hooks/built-in/skill-catalog.d.ts +1 -0
  105. package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
  106. package/dist-lib/lib/hooks/pipeline.d.ts +4 -1
  107. package/dist-lib/lib/hooks/pipeline.js +24 -2
  108. package/dist-lib/lib/hooks/run.d.ts +2 -1
  109. package/dist-lib/lib/hooks/run.js +19 -7
  110. package/dist-lib/lib/hooks/types.d.ts +17 -6
  111. package/dist-lib/lib/machine/completion/actions/approval.d.ts +3 -0
  112. package/dist-lib/lib/machine/completion/actions/approval.js +16 -0
  113. package/dist-lib/lib/machine/completion/actions/completion.d.ts +23 -0
  114. package/dist-lib/lib/machine/completion/actions/completion.js +142 -0
  115. package/dist-lib/lib/machine/completion/actions/skills.d.ts +9 -0
  116. package/dist-lib/lib/machine/completion/actions/skills.js +83 -0
  117. package/dist-lib/lib/machine/completion/actions/tools.d.ts +16 -0
  118. package/dist-lib/lib/machine/completion/actions/tools.js +173 -0
  119. package/dist-lib/lib/machine/completion/context.d.ts +9 -0
  120. package/dist-lib/lib/machine/completion/context.js +44 -0
  121. package/dist-lib/lib/machine/completion/contract.d.ts +6 -0
  122. package/dist-lib/lib/machine/completion/contract.js +1 -0
  123. package/dist-lib/lib/machine/completion/guards/retry.d.ts +9 -0
  124. package/dist-lib/lib/machine/completion/guards/retry.js +25 -0
  125. package/dist-lib/lib/machine/completion/implementations.d.ts +81 -0
  126. package/dist-lib/lib/machine/completion/implementations.js +152 -0
  127. package/dist-lib/lib/machine/completion/machine.d.ts +375 -0
  128. package/dist-lib/lib/machine/completion/machine.js +511 -0
  129. package/dist-lib/lib/machine/completion/ports.d.ts +65 -0
  130. package/dist-lib/lib/machine/completion/ports.js +224 -0
  131. package/dist-lib/lib/machine/completion/rules.d.ts +39 -0
  132. package/dist-lib/lib/machine/completion/rules.js +115 -0
  133. package/dist-lib/lib/machine/completion/types.d.ts +493 -0
  134. package/dist-lib/lib/machine/completion/types.js +1 -0
  135. package/dist-lib/lib/machine/engine.d.ts +93 -0
  136. package/dist-lib/lib/machine/engine.js +483 -0
  137. package/dist-lib/lib/machine/errors.d.ts +44 -0
  138. package/dist-lib/lib/machine/errors.js +373 -0
  139. package/dist-lib/lib/machine/guards.d.ts +2 -0
  140. package/dist-lib/lib/machine/guards.js +6 -0
  141. package/dist-lib/lib/machine/index.d.ts +20 -0
  142. package/dist-lib/lib/machine/index.js +11 -0
  143. package/dist-lib/lib/machine/replay-output.d.ts +24 -0
  144. package/dist-lib/lib/machine/replay-output.js +50 -0
  145. package/dist-lib/lib/machine/stable-stringify.d.ts +1 -0
  146. package/dist-lib/lib/machine/stable-stringify.js +11 -0
  147. package/dist-lib/lib/models/model-handle.d.ts +5 -1
  148. package/dist-lib/lib/models/model-handle.js +8 -3
  149. package/dist-lib/lib/models/model-list.d.ts +5 -3
  150. package/dist-lib/lib/models/model-pool.d.ts +18 -7
  151. package/dist-lib/lib/models/model-pool.js +126 -38
  152. package/dist-lib/lib/run-log/history-codec.d.ts +23 -0
  153. package/dist-lib/lib/run-log/history-codec.js +973 -0
  154. package/dist-lib/lib/run-log/patch.d.ts +6 -0
  155. package/dist-lib/lib/run-log/patch.js +93 -0
  156. package/dist-lib/lib/run-log/persisted-actor.d.ts +18 -0
  157. package/dist-lib/lib/run-log/persisted-actor.js +218 -0
  158. package/dist-lib/lib/run-log/protocol-types.d.ts +153 -0
  159. package/dist-lib/lib/run-log/protocol-types.js +1 -0
  160. package/dist-lib/lib/run-log/protocol.d.ts +27 -0
  161. package/dist-lib/lib/run-log/protocol.js +1056 -0
  162. package/dist-lib/lib/run-log/replay.d.ts +25 -0
  163. package/dist-lib/lib/run-log/replay.js +129 -0
  164. package/dist-lib/lib/run-log/run-journal.d.ts +125 -0
  165. package/dist-lib/lib/run-log/run-journal.js +783 -0
  166. package/dist-lib/lib/run-log-inspect.d.ts +25 -0
  167. package/dist-lib/lib/run-log-inspect.js +627 -0
  168. package/dist-lib/lib/run-log-protocol.d.ts +2 -0
  169. package/dist-lib/lib/run-log-protocol.js +1 -0
  170. package/dist-lib/lib/run-log-redact.d.ts +27 -0
  171. package/dist-lib/lib/run-log-redact.js +1107 -0
  172. package/dist-lib/lib/serve.js +308 -12
  173. package/dist-lib/lib/spawn.js +3 -2
  174. package/dist-lib/lib/tool-call-display.d.ts +1 -21
  175. package/dist-lib/lib/tools/exec.d.ts +2 -1
  176. package/dist-lib/lib/tools/exec.js +57 -2
  177. package/dist-lib/lib/tools/generate-image.js +1 -1
  178. package/dist-lib/lib/tools/generate-music.js +1 -1
  179. package/dist-lib/lib/tools/http-request.d.ts +2 -1
  180. package/dist-lib/lib/tools/http-request.js +2 -1
  181. package/dist-lib/lib/tools/mcp-call.js +8 -1
  182. package/dist-lib/lib/tools/operations/builders.d.ts +1 -0
  183. package/dist-lib/lib/tools/operations/builders.js +1 -0
  184. package/dist-lib/lib/tools/operations/compile.js +11 -1
  185. package/dist-lib/lib/tools/operations/manifest.js +1 -0
  186. package/dist-lib/lib/tools/outbound-guard.d.ts +8 -6
  187. package/dist-lib/lib/tools/outbound-guard.js +25 -8
  188. package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
  189. package/dist-lib/lib/tools/skills/catalog.js +35 -0
  190. package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +1 -0
  191. package/dist-lib/lib/tools/skills/cli-schema.js +25 -0
  192. package/dist-lib/lib/wire.d.ts +5 -0
  193. package/dist-lib/lib/wire.js +128 -5
  194. package/dist-lib/schema/harness.js +22 -6
  195. package/dist-lib/spec/hrpc/messages.js +100 -18
  196. package/dist-lib/spec/hyperschema/index.js +100 -18
  197. package/dist-lib/spec/hyperschema/schema.json +74 -1
  198. package/dist-lib/spec/hyperschema/types.d.ts +11 -3
  199. package/package.json +19 -1
  200. package/patches/@qvac%2Finference@0.20.0.patch +67 -0
  201. package/patches/bare-stow@0.1.5.patch +68 -0
  202. package/patches/hypercore-blob-server@1.15.0.patch +15 -0
  203. package/patches/localwatch@1.4.3.patch +13 -0
  204. package/patches/pyodide@314.0.2.patch +144 -0
package/dist-lib/index.js CHANGED
@@ -5,6 +5,9 @@ import ReadyResource from 'ready-resource';
5
5
  import { probeGpuStats } from './lib/hardware/gpu-probe.js';
6
6
  import { probeCapabilityProfile } from './lib/hardware/capability-probe.js';
7
7
  import { closePythonWorker } from './lib/tools/exec/adapters/python/worker/active.js';
8
+ import { trackCompletion } from './lib/completion-lifecycle.js';
9
+ import { createHarnessLifecycle } from './lib/harness-lifecycle.js';
10
+ import { didDrainTimeOut, raceDrain } from './lib/machine/index.js';
8
11
  import { completion, imageSettingsFrom } from './lib/capabilities/completion.js';
9
12
  import { detectLanguage } from './lib/capabilities/detect-language.js';
10
13
  import { invoke } from './lib/capabilities/invoke.js';
@@ -24,7 +27,7 @@ import { AbortedError, raceAbort } from './lib/helpers/race-abort.js';
24
27
  import { audioGenDownloadMetadata, customProjectionMap, defaultProjectionMap, defaultTtsFor, defaultVoiceModel, diffusionDownloadMetadata, curatedModelAsset, infraModelsFor, modelAddonOf, modelNameToCapabilityProfile, musicModelSet, residentSrc, setCustomModels } from './lib/models/model-catalog.js';
25
28
  import { createModelStatusCache, customModelRows, invalidateModelStatusCache, mergeCustomRows, listModels } from './lib/models/model-list.js';
26
29
  import { deleteModelFiles, summarizeDelete } from './lib/models/model-delete.js';
27
- import { createEmbedPool, createLoadTracker, createModelPool, createVoicePool, awaitModelCapacity, evictIdle, evictIdleWhere, evictModel, evictModelWithin, isResident, leaseResidentModel, leaseCount, loadedModels, modelKey, modelOf, releaseModel, resolveLoadOptions, progressState } from './lib/models/model-pool.js';
30
+ import { createEmbedPool, createLoadTracker, createModelPool, createVoicePool, ModelUnloadError, awaitModelCapacity, evictIdle, evictIdleWhere, evictModel, evictModelWithin, isResident, leaseResidentModel, leaseCount, loadedModels, loadingModels, settledModels, modelKey, modelOf, releaseModel, resolveCtxSize, resolveLoadOptions, progressState } from './lib/models/model-pool.js';
28
31
  import { defaultContextSize } from './lib/models/context.js';
29
32
  import { resolveMainGpu } from './lib/models/gpu-selection.js';
30
33
  import { createIdleReaper } from './lib/models/idle-unload.js';
@@ -32,6 +35,8 @@ import { openclCacheDir } from './lib/models/opencl-cache.js';
32
35
  import { assertEmbedDevice, createEmbedConfig } from './lib/models/embed-config.js';
33
36
  import { createGpuFaultState, isSdkGpuDeviceLostError, isSdkModelWedgedError } from './lib/models/sdk-faults.js';
34
37
  import { createSdkLoader } from './lib/sdk-loader.js';
38
+ import { installAbortControllerPolyfill } from './lib/capabilities/completion-engine/abort-polyfill.js';
39
+ import { prepareCompletionRuntime } from './lib/capabilities/completion-runtime.js';
35
40
  import { createEditImageTool } from './lib/tools/edit-image.js';
36
41
  import { toolCatalog } from './lib/tools/catalog.js';
37
42
  import { createDiffusionRuntime, createGenerateImageTool } from './lib/tools/generate-image.js';
@@ -41,9 +46,9 @@ import { ensureBundledSkillsDir } from './lib/tools/skills/bundled-dir.js';
41
46
  import { ensureSkillsOverlayDir } from './lib/tools/skills/overlay-dir.js';
42
47
  import { copyBaseForEdit, deleteOverlaySkill, listManagedSkills, readSkillFile, writeSkillFile } from './lib/tools/skills/overlay-crud.js';
43
48
  import { watchSkillsOverlay } from './lib/tools/skills/overlay-watch.js';
44
- import { errorMessage } from './lib/guards.js';
49
+ import { errorMessage, isRecord } from './lib/guards.js';
50
+ import { redactedRecoveryFields } from './lib/wire.js';
45
51
  import { hlog } from './lib/log.js';
46
- import { harness } from './spec/index.js';
47
52
  import { installResolutionDetector, resolutionDetail } from './lib/resolution-detector.js';
48
53
  import { probeBinVersions } from './lib/tools/skills/bin-check.js';
49
54
  import { bareRuntime } from './lib/tools/exec/bare-runtime.js';
@@ -73,8 +78,8 @@ export { serveHarness } from './lib/serve.js';
73
78
  export { hlog, setHarnessLog, setHarnessLogSink } from './lib/log.js';
74
79
  export { harnessHooks } from './lib/hooks/registry.js';
75
80
  export { EgressGuardHook, SkillCatalogGuardHook } from './lib/hooks/built-in/index.js';
76
- const HarnessState = harness.getEnum('@harness/state');
77
81
  const BIN_VERSION_PROBE_TTL_MS = 45_000;
82
+ const SUSPEND_SETTLE_MS = 500;
78
83
  const TRANSCRIBE_STREAM_MIN_BYTES = 16_000 * 2 * 60;
79
84
  const TRANSCRIPTION_MODEL_LABEL = 'transcription model';
80
85
  const DIFFUSION_EVICT_WAIT_MS = 15_000;
@@ -101,6 +106,7 @@ export class Harness extends ReadyResource {
101
106
  _models;
102
107
  _embedModels;
103
108
  _retiredEmbedModels;
109
+ _faultedEmbedKeys = new WeakMap();
104
110
  _embedLane = createEmbedLane();
105
111
  _embedFaults;
106
112
  _embedGpuLayers;
@@ -109,7 +115,23 @@ export class Harness extends ReadyResource {
109
115
  _ttsModels;
110
116
  _transcriptionModels;
111
117
  _voiceLane = createVoiceLane();
112
- _state;
118
+ _lifecycle = createHarnessLifecycle({
119
+ suspend: () => this._suspendRuntimes(),
120
+ resume: () => this._resumeRuntimes()
121
+ });
122
+ _detachedRuns = new Set();
123
+ _effectDrainWaiters = new Set();
124
+ _activeWork = 0;
125
+ _workDrainWaiters = new Set();
126
+ _runtimeDraining = false;
127
+ _runtimeUnloadFailed = false;
128
+ _poolUnloadFailed = false;
129
+ _poolCleanupFailed = false;
130
+ _failedModelUnloads = new Set();
131
+ _sdkClosed = false;
132
+ _runtimesReleased = false;
133
+ _closeRetryNeeded = false;
134
+ _closeRequested = false;
113
135
  _skillsDir;
114
136
  _modelsDir;
115
137
  _overlayWatcher = null;
@@ -134,6 +156,7 @@ export class Harness extends ReadyResource {
134
156
  _musicSdk;
135
157
  _translationSdk;
136
158
  _musicRuntime;
159
+ _completionRuns;
137
160
  _translationRuntime;
138
161
  _capabilityTools;
139
162
  _capability = null;
@@ -142,6 +165,7 @@ export class Harness extends ReadyResource {
142
165
  _idle;
143
166
  constructor({ tools = [], sdkLoader, skillsDir, modelsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, completionModelCap, credentialStore, platform, gpuStats } = {}) {
144
167
  super();
168
+ installAbortControllerPolyfill();
145
169
  this._sdk = sdkLoader ?? createSdkLoader();
146
170
  this._llmDevice = llmDevice;
147
171
  this._gpuStats = gpuStats;
@@ -214,7 +238,10 @@ export class Harness extends ReadyResource {
214
238
  ...music,
215
239
  loads: this._loads,
216
240
  loadSdk: () => this._loadMusicSdk(),
217
- onProgress: (model, progress) => this.emit('model', progressState(model, progress)),
241
+ onProgress: (model, progress) => {
242
+ const state = progressState(model, progress);
243
+ this.emit('model', state);
244
+ },
218
245
  onBeforeLoad: async (signal) => {
219
246
  if (this._models.destroyed)
220
247
  return;
@@ -253,7 +280,6 @@ export class Harness extends ReadyResource {
253
280
  ...(this._musicRuntime ? [createGenerateMusicTool(this._musicRuntime, musicClamps)] : [])
254
281
  ];
255
282
  this._projectionModel = projectionModel ?? defaultProjectionMap();
256
- this._state = HarnessState.RESUME;
257
283
  this._skillsDir = skillsDir;
258
284
  this._modelsDir = modelsDir;
259
285
  this._supportedSkills = [];
@@ -262,6 +288,7 @@ export class Harness extends ReadyResource {
262
288
  this._platform = platform;
263
289
  this._diffusionSdk = null;
264
290
  this._musicSdk = null;
291
+ this._completionRuns = new Set();
265
292
  this._translationSdk = null;
266
293
  this._models = this._createPool();
267
294
  this._embedModels = this._createEmbedPool();
@@ -274,13 +301,18 @@ export class Harness extends ReadyResource {
274
301
  });
275
302
  }
276
303
  async _open() {
277
- installResolutionDetector(import.meta);
278
- await this._reloadSkills();
279
- await this._startOverlayWatch();
280
- hlog('open', { skills: this._supportedSkills.length, tools: this._tools.length });
304
+ try {
305
+ installResolutionDetector(import.meta);
306
+ await this._reloadSkills();
307
+ await this._startOverlayWatch();
308
+ hlog('open', { skills: this._supportedSkills.length, tools: this._tools.length });
309
+ }
310
+ catch (error) {
311
+ throw error;
312
+ }
281
313
  }
282
314
  get suspended() {
283
- return this._state === HarnessState.SUSPEND;
315
+ return this._lifecycle.suspended;
284
316
  }
285
317
  get skillNames() {
286
318
  return this._supportedSkills.map((skill) => skill.name);
@@ -393,6 +425,7 @@ export class Harness extends ReadyResource {
393
425
  async skills(agentId, modelName, options) {
394
426
  if (!this.opened)
395
427
  await this.ready();
428
+ this._assertNotClosing();
396
429
  const platform = this._platform ?? detectPlatform();
397
430
  const refresh = options?.refresh === true;
398
431
  return resolveSkillStatuses(this._allSkills, {
@@ -558,13 +591,22 @@ export class Harness extends ReadyResource {
558
591
  };
559
592
  }
560
593
  _loadedModels() {
561
- const pools = [
594
+ return this._pools().flatMap((pool) => loadedModels(pool));
595
+ }
596
+ _loadingModels() {
597
+ return new Set(this._pools().flatMap((pool) => loadingModels(pool)));
598
+ }
599
+ _settledModels() {
600
+ return new Set(this._pools().flatMap((pool) => settledModels(pool)));
601
+ }
602
+ _pools() {
603
+ return [
604
+ this._models,
562
605
  this._embedModels,
563
606
  ...this._retiredEmbedModels,
564
607
  this._ttsModels,
565
608
  this._transcriptionModels
566
609
  ];
567
- return [...loadedModels(this._models), ...pools.flatMap((pool) => loadedModels(pool))];
568
610
  }
569
611
  _loadPluginSdk(current, store, importPlugin) {
570
612
  let pending = current();
@@ -632,19 +674,24 @@ export class Harness extends ReadyResource {
632
674
  };
633
675
  }
634
676
  _createPool() {
635
- const loadSdk = this._sdk.load;
677
+ const loadSdk = () => this._loadModelLifecycleSdk();
636
678
  return createModelPool(loadSdk, (state) => this._notifyModel(state), {
637
679
  deviceConfig: this._deviceConfig(),
638
- loads: this._loads
680
+ loads: this._loads,
681
+ onUnloadFailure: (modelId, error) => this._recordModelUnloadFailure(modelId, error)
639
682
  });
640
683
  }
684
+ _loadModelLifecycleSdk() {
685
+ return this._sdk.load();
686
+ }
641
687
  _createEmbedPool() {
642
688
  const loadSdk = () => this._sdk.loadEmbed();
643
689
  return createEmbedPool(loadSdk, (state) => this._notifyModel(state), {
644
690
  faults: this._embedFaults,
645
691
  batchSize: this._embedBatchSize,
646
692
  openclCacheDir,
647
- loads: this._loads
693
+ loads: this._loads,
694
+ onUnloadFailure: (modelId, error) => this._recordModelUnloadFailure(modelId, error)
648
695
  });
649
696
  }
650
697
  _createTtsPool() {
@@ -653,7 +700,8 @@ export class Harness extends ReadyResource {
653
700
  modelType: 'tts-ggml',
654
701
  addon: 'tts',
655
702
  modelConfig: TTS_MODEL_CONFIG,
656
- loads: this._loads
703
+ loads: this._loads,
704
+ onUnloadFailure: (modelId, error) => this._recordModelUnloadFailure(modelId, error)
657
705
  });
658
706
  }
659
707
  _createTranscriptionPool() {
@@ -663,12 +711,18 @@ export class Harness extends ReadyResource {
663
711
  addon: 'parakeet',
664
712
  modelConfig: { useGPU: this._transcriptionDevice !== 'cpu' },
665
713
  openclCacheDir,
666
- loads: this._loads
714
+ loads: this._loads,
715
+ onUnloadFailure: (modelId, error) => this._recordModelUnloadFailure(modelId, error)
667
716
  });
668
717
  }
718
+ _recordModelUnloadFailure(modelId, error) {
719
+ this._failedModelUnloads.add(modelId);
720
+ this._poolUnloadFailed = true;
721
+ this._runtimeUnloadFailed = true;
722
+ hlog('model unload failed', { modelId, err: errorMessage(error) });
723
+ }
669
724
  _notifyModel(state) {
670
- const catalogChanged = state.status === 'ready' || state.status === 'unloaded';
671
- if (catalogChanged) {
725
+ if (state.status === 'ready' || state.status === 'unloaded' || state.status === 'error') {
672
726
  invalidateModelStatusCache(this._modelStatus);
673
727
  }
674
728
  if (state.status === 'unloaded') {
@@ -678,33 +732,175 @@ export class Harness extends ReadyResource {
678
732
  }
679
733
  }
680
734
  }
681
- this.emit('model', { ...state, catalogChanged, loaded: this._loadedModels() });
735
+ this.emit('model', {
736
+ ...state,
737
+ catalogChanged: state.catalogChanged === true,
738
+ loaded: this._loadedModels()
739
+ });
682
740
  }
683
741
  async _collectRetiredEmbedPool(pool) {
684
742
  if (!this._retiredEmbedModels.has(pool) || pool.idle !== pool.size)
685
743
  return;
744
+ try {
745
+ await pool.destroy();
746
+ }
747
+ catch (error) {
748
+ if (!(error instanceof ModelUnloadError)) {
749
+ this._poolCleanupFailed = true;
750
+ this._poolUnloadFailed = true;
751
+ this._runtimeUnloadFailed = true;
752
+ }
753
+ throw error;
754
+ }
686
755
  this._retiredEmbedModels.delete(pool);
687
- await pool.destroy();
688
756
  }
689
757
  async _releaseEmbedResource(pool, key) {
690
758
  releaseModel(pool, key);
759
+ const faulted = this._faultedEmbedKeys.get(pool);
760
+ if (faulted?.has(key)) {
761
+ await evictIdle(pool);
762
+ if (!isResident(pool, key)) {
763
+ faulted.delete(key);
764
+ }
765
+ }
691
766
  await this._collectRetiredEmbedPool(pool);
692
767
  }
693
- _releaseEmbedResourceAfter(settled, pool, key) {
694
- settled
695
- .then(() => this._releaseEmbedResource(pool, key), () => this._releaseEmbedResource(pool, key))
768
+ async _releaseEmbedResourceBeforeWork(releasing, releaseWork) {
769
+ try {
770
+ await releasing;
771
+ }
772
+ finally {
773
+ releaseWork();
774
+ }
775
+ }
776
+ _markEmbedFault({ pool, key, faults, generation }) {
777
+ faults.recordFault(generation);
778
+ let faulted = this._faultedEmbedKeys.get(pool);
779
+ if (!faulted) {
780
+ faulted = new Set();
781
+ this._faultedEmbedKeys.set(pool, faulted);
782
+ }
783
+ faulted.add(key);
784
+ }
785
+ _releaseEmbedResourceAfter(settled, fault) {
786
+ return settled
787
+ .then(() => this._releaseEmbedResource(fault.pool, fault.key), (error) => {
788
+ if (isSdkGpuDeviceLostError(error)) {
789
+ this._markEmbedFault(fault);
790
+ }
791
+ return this._releaseEmbedResource(fault.pool, fault.key);
792
+ })
696
793
  .catch(noop);
697
794
  }
698
795
  async _destroyEmbedPools() {
699
- const pools = [this._embedModels, ...this._retiredEmbedModels];
700
- this._retiredEmbedModels.clear();
701
- await Promise.all(pools.map((pool) => pool.destroy()));
796
+ const pools = [this._embedModels, ...this._retiredEmbedModels].filter((pool) => Boolean(pool));
797
+ const outcomes = await Promise.allSettled(pools.map(async (pool) => {
798
+ await pool.destroy();
799
+ this._retiredEmbedModels.delete(pool);
800
+ }));
801
+ const generic = outcomes.find((outcome) => outcome.status === 'rejected' && !(outcome.reason instanceof ModelUnloadError));
802
+ if (generic?.status === 'rejected') {
803
+ throw generic.reason;
804
+ }
805
+ const failed = outcomes.find((outcome) => outcome.status === 'rejected');
806
+ if (failed?.status === 'rejected') {
807
+ throw failed.reason;
808
+ }
702
809
  }
703
810
  _enqueueEmbedControl(run) {
704
811
  const next = this._embedControl.then(run, run);
705
812
  this._embedControl = next.catch(noop);
706
813
  return next;
707
814
  }
815
+ async _runLifecycle(type, { open = true } = {}) {
816
+ if (!this.opened) {
817
+ if (open) {
818
+ await this.ready();
819
+ }
820
+ }
821
+ if (this._closeRequested || this.closing) {
822
+ return;
823
+ }
824
+ await this._lifecycle.send(type);
825
+ }
826
+ _abortCompletions() {
827
+ for (const run of this._completionRuns) {
828
+ run.abort();
829
+ }
830
+ }
831
+ _assertNotClosing() {
832
+ if (this._closeRequested || this.closing || this.closed) {
833
+ throw new Error('harness is closing');
834
+ }
835
+ }
836
+ _enterWork() {
837
+ this._assertNotClosing();
838
+ if (this._runtimeDraining) {
839
+ throw new Error('harness is suspended');
840
+ }
841
+ if (this._runtimeUnloadFailed) {
842
+ throw new Error('Runtime unload failed; close the harness before loading models again');
843
+ }
844
+ this._activeWork++;
845
+ let released = false;
846
+ return () => {
847
+ if (released)
848
+ return;
849
+ released = true;
850
+ if (--this._activeWork !== 0) {
851
+ return;
852
+ }
853
+ for (const resolve of this._workDrainWaiters) {
854
+ resolve();
855
+ }
856
+ this._workDrainWaiters.clear();
857
+ };
858
+ }
859
+ _waitForWorkDrain() {
860
+ if (!this._activeWork) {
861
+ return Promise.resolve();
862
+ }
863
+ return new Promise((resolve) => this._workDrainWaiters.add(resolve));
864
+ }
865
+ _effectsSettled() {
866
+ return !this._detachedRuns.size && ![...this._completionRuns].some((run) => run.started);
867
+ }
868
+ _notifyEffectDrain() {
869
+ if (!this._effectsSettled())
870
+ return;
871
+ for (const resolve of this._effectDrainWaiters) {
872
+ resolve();
873
+ }
874
+ this._effectDrainWaiters.clear();
875
+ }
876
+ _waitForEffectDrain() {
877
+ if (this._effectsSettled()) {
878
+ return Promise.resolve();
879
+ }
880
+ return new Promise((resolve) => this._effectDrainWaiters.add(resolve));
881
+ }
882
+ _trackDetachedRun(pending, runId) {
883
+ const tracked = pending.catch((error) => {
884
+ hlog('detached completion effect failed', { runId, err: errorMessage(error) });
885
+ });
886
+ this._detachedRuns.add(tracked);
887
+ void tracked
888
+ .finally(() => {
889
+ this._detachedRuns.delete(tracked);
890
+ this._notifyEffectDrain();
891
+ })
892
+ .catch(noop);
893
+ }
894
+ async _closeCompletions(runs = this._completionRuns) {
895
+ const closed = await Promise.allSettled([...runs].map((run) => run.close()));
896
+ for (const outcome of closed) {
897
+ if (outcome.status === 'rejected') {
898
+ hlog('completion close failed', {
899
+ err: errorMessage(outcome.reason)
900
+ });
901
+ }
902
+ }
903
+ }
708
904
  getEmbeddingDevice() {
709
905
  return this._embedFaults.device;
710
906
  }
@@ -715,43 +911,61 @@ export class Harness extends ReadyResource {
715
911
  async _setEmbeddingDevice(device) {
716
912
  if (!this.opened)
717
913
  await this.ready();
718
- if (this.closing)
719
- throw new Error('harness is closing');
720
- if (this._embedFaults.device === device) {
721
- return;
722
- }
723
- this._embedFaults = createGpuFaultState({ device, gpuLayers: this._embedGpuLayers });
724
914
  if (this.suspended) {
915
+ this._assertNotClosing();
916
+ if (this._embedFaults.device === device) {
917
+ return;
918
+ }
919
+ this._embedFaults = createGpuFaultState({ device, gpuLayers: this._embedGpuLayers });
725
920
  return;
726
921
  }
727
- const retired = this._embedModels;
728
- this._retiredEmbedModels.add(retired);
729
- this._embedModels = this._createEmbedPool();
730
- await this._collectRetiredEmbedPool(retired);
922
+ const releaseWork = this._enterWork();
923
+ try {
924
+ if (this._embedFaults.device === device) {
925
+ return;
926
+ }
927
+ this._embedFaults = createGpuFaultState({ device, gpuLayers: this._embedGpuLayers });
928
+ if (this.suspended) {
929
+ return;
930
+ }
931
+ const retired = this._embedModels;
932
+ this._retiredEmbedModels.add(retired);
933
+ this._embedModels = this._createEmbedPool();
934
+ await this._collectRetiredEmbedPool(retired);
935
+ }
936
+ finally {
937
+ releaseWork();
938
+ }
731
939
  }
732
940
  async presuspend() {
733
- if (this.closing)
734
- return;
735
- this._state = HarnessState.PRESUSPEND;
736
- hlog('presuspend');
941
+ await this._runLifecycle('PRESUSPEND', { open: false });
737
942
  }
738
943
  async suspend() {
739
- hlog('suspend called');
740
- if (this._state === HarnessState.SUSPEND) {
741
- return;
742
- }
743
- if (this.closing) {
744
- return;
944
+ const result = await raceDrain(this._runLifecycle('SUSPEND'), SUSPEND_SETTLE_MS);
945
+ if (didDrainTimeOut(result)) {
946
+ throw new Error('Harness suspend is still waiting for active work to settle');
745
947
  }
746
- this._state = HarnessState.SUSPEND;
948
+ }
949
+ async _suspendRuntimes() {
950
+ this._runtimeDraining = true;
747
951
  hlog('suspending', { models: loadedModels(this._models).map((entry) => entry.model) });
952
+ this._abortCompletions();
953
+ this._loads.drain();
954
+ await this._closeCompletions([...this._completionRuns].filter((run) => run.started));
955
+ await this._waitForEffectDrain();
748
956
  const pending = this._sdk.peek();
749
957
  if (pending) {
750
- this._loads.drain(await pending);
958
+ const sdk = await pending.catch(noop);
959
+ if (sdk) {
960
+ this._loads.drain(sdk);
961
+ }
962
+ }
963
+ await this._waitForWorkDrain();
964
+ if (!(await this._releaseRuntimes())) {
965
+ throw new Error('Runtime unload failed; close the harness before loading models again');
751
966
  }
752
- await this._releaseRuntimes();
753
- await this._sdkLifecycle('suspend');
754
967
  invalidateModelStatusCache(this._modelStatus);
968
+ await this._sdkLifecycle('suspend');
755
969
  hlog('suspended');
756
970
  }
757
971
  async _sdkLifecycle(op) {
@@ -765,35 +979,60 @@ export class Harness extends ReadyResource {
765
979
  catch (err) {
766
980
  hlog(`sdk ${op} failed`, { err: errorMessage(err) });
767
981
  if (op === 'suspend') {
768
- return;
982
+ throw err;
769
983
  }
770
984
  await sdk.resume().catch((retryErr) => {
771
985
  hlog('sdk resume retry failed', {
772
986
  err: errorMessage(retryErr)
773
987
  });
988
+ throw retryErr;
774
989
  });
775
990
  }
776
991
  }
777
992
  async _releaseRuntimes() {
778
993
  const released = await Promise.allSettled([
779
- this._models.destroy(),
994
+ this._models?.destroy(),
780
995
  this._destroyEmbedPools(),
781
- this._ttsModels.destroy(),
782
- this._transcriptionModels.destroy(),
996
+ this._ttsModels?.destroy(),
997
+ this._transcriptionModels?.destroy(),
783
998
  this._imageRuntime?.unload(),
784
999
  this._musicRuntime?.unload(),
785
1000
  this._translationRuntime.unload()
786
1001
  ]);
787
- for (const outcome of released) {
1002
+ for (const [index, outcome] of released.entries()) {
788
1003
  if (outcome.status !== 'rejected')
789
1004
  continue;
1005
+ if (index < 4 && !(outcome.reason instanceof ModelUnloadError)) {
1006
+ this._poolCleanupFailed = true;
1007
+ }
790
1008
  const err = outcome.reason;
791
1009
  hlog('unload failed', { err: errorMessage(err) });
792
1010
  }
1011
+ const sdk = await this._sdk.peek()?.catch(noop);
1012
+ if (sdk) {
1013
+ for (const modelId of this._failedModelUnloads) {
1014
+ try {
1015
+ await sdk.unloadModel({ modelId });
1016
+ this._failedModelUnloads.delete(modelId);
1017
+ invalidateModelStatusCache(this._modelStatus);
1018
+ }
1019
+ catch (error) {
1020
+ hlog('model unload retry failed', {
1021
+ modelId,
1022
+ err: errorMessage(error)
1023
+ });
1024
+ }
1025
+ }
1026
+ }
1027
+ this._poolUnloadFailed = this._poolCleanupFailed || this._failedModelUnloads.size > 0;
1028
+ this._runtimeUnloadFailed =
1029
+ this._poolUnloadFailed || released.slice(4).some((outcome) => outcome.status === 'rejected');
1030
+ return !this._runtimeUnloadFailed;
793
1031
  }
794
1032
  async _unloadIdleModels() {
795
- if (!this.opened || this.closing || this.suspended)
1033
+ if (!this.opened || this.closing || this.suspended || this._runtimeDraining) {
796
1034
  return;
1035
+ }
797
1036
  hlog('idle unload', { models: this._loadedModels().map((entry) => entry.model) });
798
1037
  const released = await Promise.allSettled([
799
1038
  evictIdle(this._models),
@@ -813,62 +1052,164 @@ export class Harness extends ReadyResource {
813
1052
  invalidateModelStatusCache(this._modelStatus);
814
1053
  }
815
1054
  async resume() {
816
- hlog('resume called');
817
- if (this._state === HarnessState.RESUME) {
818
- return;
819
- }
820
- if (this.closing) {
821
- return;
822
- }
823
- if (this._state === HarnessState.PRESUSPEND) {
824
- this._state = HarnessState.RESUME;
825
- return;
1055
+ await this._runLifecycle('RESUME');
1056
+ }
1057
+ async _resumeRuntimes() {
1058
+ this._assertNotClosing();
1059
+ if (this._runtimeUnloadFailed) {
1060
+ throw new Error('Runtime unload failed; close the harness before loading models again');
826
1061
  }
827
- this._state = HarnessState.RESUME;
828
1062
  hlog('resuming', { pools: 'recreated' });
829
1063
  this._loads.undrain();
830
1064
  this._models = this._createPool();
831
1065
  this._embedModels = this._createEmbedPool();
832
1066
  this._ttsModels = this._createTtsPool();
833
1067
  this._transcriptionModels = this._createTranscriptionPool();
834
- await this._sdkLifecycle('resume');
1068
+ try {
1069
+ await this._sdkLifecycle('resume');
1070
+ this._runtimeDraining = false;
1071
+ }
1072
+ catch (error) {
1073
+ const sdk = await this._sdk.peek()?.catch(noop);
1074
+ if (sdk) {
1075
+ this._loads.drain(sdk);
1076
+ }
1077
+ await this._releaseRuntimes();
1078
+ throw error;
1079
+ }
835
1080
  }
836
1081
  async wakeup() {
837
- if (this.closing)
838
- return;
839
- this._state = HarnessState.WAKEUP;
840
- hlog('wakeup');
841
- await this.resume();
1082
+ await this._runLifecycle('WAKEUP');
1083
+ }
1084
+ ready() {
1085
+ if (this._closeRequested || this.closing || this.closed) {
1086
+ return Promise.reject(new Error('harness is closing'));
1087
+ }
1088
+ return super.ready();
842
1089
  }
843
1090
  async _close() {
844
1091
  hlog('closing');
845
1092
  this._idle.close();
846
- this._overlayWatcher?.stop();
847
- this._overlayWatcher = null;
848
- const pending = this._sdk.peek();
849
- if (pending) {
850
- this._loads.drain(await pending);
1093
+ this._lifecycle.cancelPending();
1094
+ this._abortCompletions();
1095
+ this._loads.drain();
1096
+ await this._lifecycle.settled();
1097
+ await this._finishClose();
1098
+ }
1099
+ close() {
1100
+ this._closeRequested = true;
1101
+ if (this._closeRetryNeeded) {
1102
+ this.closing = null;
1103
+ this._closeRetryNeeded = false;
1104
+ }
1105
+ const pending = !this.opened && this.opening !== null ? this._closeWhileOpening() : super.close();
1106
+ void pending.catch(() => {
1107
+ if (this.closing === pending) {
1108
+ this._closeRetryNeeded = true;
1109
+ }
1110
+ });
1111
+ const bounded = raceDrain(pending).then((result) => {
1112
+ if (didDrainTimeOut(result)) {
1113
+ throw new Error('Harness close is still waiting for active work to settle');
1114
+ }
1115
+ });
1116
+ void bounded.catch(noop);
1117
+ return bounded;
1118
+ }
1119
+ _closeWhileOpening() {
1120
+ if (this.closing)
1121
+ return this.closing;
1122
+ const opening = this.opening;
1123
+ const pending = (async () => {
1124
+ await opening?.catch(noop);
1125
+ this._abortCompletions();
1126
+ this._loads.drain();
1127
+ await this._finishClose();
1128
+ this.closed = true;
1129
+ this.emit('close');
1130
+ })();
1131
+ this.closing = pending;
1132
+ return pending;
1133
+ }
1134
+ async _finishClose() {
1135
+ let completed = false;
1136
+ try {
1137
+ const pending = this._sdk.peek();
1138
+ if (pending) {
1139
+ const sdk = await pending.catch(noop);
1140
+ if (sdk) {
1141
+ this._loads.drain(sdk);
1142
+ }
1143
+ }
1144
+ await this._closeCompletions();
1145
+ await this._waitForEffectDrain();
1146
+ await this._waitForWorkDrain();
1147
+ this._overlayWatcher?.stop();
1148
+ this._overlayWatcher = null;
1149
+ if (!this._runtimesReleased) {
1150
+ let [released] = await Promise.all([this._releaseRuntimes(), closePythonWorker()]);
1151
+ if (!released && !this._poolUnloadFailed) {
1152
+ released = await this._releaseRuntimes();
1153
+ }
1154
+ this._runtimesReleased = released;
1155
+ }
1156
+ let sdkCloseFailed = false;
1157
+ let sdkCloseError;
1158
+ const sdk = !this._sdkClosed && !this._runtimeUnloadFailed ? await this._sdk.peek()?.catch(noop) : null;
1159
+ if (sdk) {
1160
+ try {
1161
+ await sdk.close();
1162
+ this._sdkClosed = true;
1163
+ }
1164
+ catch (error) {
1165
+ sdkCloseFailed = true;
1166
+ sdkCloseError = error;
1167
+ hlog('sdk close failed', { err: errorMessage(error) });
1168
+ }
1169
+ }
1170
+ if (sdkCloseFailed) {
1171
+ throw sdkCloseError;
1172
+ }
1173
+ if (this._runtimeUnloadFailed) {
1174
+ throw new Error('Runtime unload failed; model cleanup could not be confirmed');
1175
+ }
1176
+ completed = true;
851
1177
  }
852
- await Promise.all([this._releaseRuntimes(), closePythonWorker()]);
853
- const sdk = await this._sdk.peek()?.catch(noop);
854
- if (sdk) {
855
- await sdk.close();
1178
+ finally {
1179
+ if (completed) {
1180
+ this._lifecycle.stop();
1181
+ }
856
1182
  }
857
1183
  }
858
1184
  download(args) {
1185
+ this._assertNotClosing();
1186
+ let releaseWork = null;
859
1187
  const projection = (args.addon ?? 'llm') === 'llm' ? this._projectionFor(args.model) : undefined;
860
1188
  const multiFile = diffusionDownloadMetadata(args.model, args.addon) ??
861
1189
  audioGenDownloadMetadata(args.model, args.addon);
862
1190
  return download(async () => {
863
1191
  if (!this.opened)
864
1192
  await this.ready();
865
- return this._sdk.load();
1193
+ releaseWork = this._enterWork();
1194
+ try {
1195
+ const sdk = await this._sdk.load();
1196
+ this._assertNotClosing();
1197
+ return sdk;
1198
+ }
1199
+ catch (error) {
1200
+ releaseWork?.();
1201
+ releaseWork = null;
1202
+ throw error;
1203
+ }
866
1204
  }, {
867
1205
  ...args,
868
1206
  projection,
869
1207
  companions: multiFile?.companions,
870
1208
  assetSizes: multiFile?.assetSizes
871
- }, () => invalidateModelStatusCache(this._modelStatus));
1209
+ }, () => invalidateModelStatusCache(this._modelStatus), () => {
1210
+ releaseWork?.();
1211
+ releaseWork = null;
1212
+ });
872
1213
  }
873
1214
  embed(context) {
874
1215
  return this._busy(() => this._embed(context));
@@ -876,8 +1217,10 @@ export class Harness extends ReadyResource {
876
1217
  async _embed({ model, texts, signal }) {
877
1218
  if (!this.opened)
878
1219
  await this.ready();
879
- if (this.suspended)
1220
+ this._assertNotClosing();
1221
+ if (this.suspended) {
880
1222
  throw new Error('harness is suspended');
1223
+ }
881
1224
  if (texts.length === 0) {
882
1225
  return [];
883
1226
  }
@@ -885,13 +1228,23 @@ export class Harness extends ReadyResource {
885
1228
  const faults = this._embedFaults;
886
1229
  const generation = faults.generation;
887
1230
  const key = faults.key(residentSrc(model, 'embeddings'));
888
- const handle = pool.open(key, model);
1231
+ const releaseWork = this._enterWork();
1232
+ let handle;
1233
+ try {
1234
+ handle = pool.open(key, model);
1235
+ }
1236
+ catch (error) {
1237
+ releaseWork();
1238
+ throw error;
1239
+ }
889
1240
  let loaded = null;
890
1241
  let job = null;
891
1242
  let releaseAfter = null;
1243
+ let drainWorkAfter = null;
892
1244
  try {
893
1245
  loaded = await raceAbort(handle.load(), signal);
894
1246
  const sdk = await this._sdk.loadEmbed();
1247
+ this._assertNotClosing();
895
1248
  job = this._embedLane(sdk, { modelId: loaded.modelId, texts });
896
1249
  const vectors = await raceAbort(job, signal);
897
1250
  faults.reset();
@@ -899,26 +1252,35 @@ export class Harness extends ReadyResource {
899
1252
  }
900
1253
  catch (err) {
901
1254
  if (err instanceof AbortedError) {
902
- releaseAfter = job ?? (this._retiredEmbedModels.has(pool) ? handle.peek() : null);
1255
+ if (job || this._retiredEmbedModels.has(pool)) {
1256
+ releaseAfter = job ?? handle.peek();
1257
+ }
1258
+ else {
1259
+ drainWorkAfter = handle.peek();
1260
+ }
903
1261
  }
904
1262
  if (isSdkGpuDeviceLostError(err)) {
905
- faults.recordFault(generation);
906
- if (loaded) {
907
- const dead = loaded.modelId;
908
- await this._sdk
909
- .loadEmbed()
910
- .then((sdk) => sdk.unloadModel({ modelId: dead }))
911
- .catch(noop);
912
- }
1263
+ this._markEmbedFault({ pool, key, faults, generation });
913
1264
  }
914
1265
  throw err;
915
1266
  }
916
1267
  finally {
917
1268
  if (releaseAfter) {
918
- this._releaseEmbedResourceAfter(releaseAfter, pool, key);
1269
+ void this._releaseEmbedResourceAfter(releaseAfter, {
1270
+ pool,
1271
+ key,
1272
+ faults,
1273
+ generation
1274
+ }).finally(releaseWork);
919
1275
  }
920
1276
  else {
921
- await this._releaseEmbedResource(pool, key);
1277
+ const releasing = this._releaseEmbedResource(pool, key);
1278
+ if (drainWorkAfter) {
1279
+ void Promise.allSettled([releasing, drainWorkAfter]).then(releaseWork);
1280
+ }
1281
+ else {
1282
+ await this._releaseEmbedResourceBeforeWork(releasing, releaseWork);
1283
+ }
922
1284
  }
923
1285
  }
924
1286
  }
@@ -941,7 +1303,16 @@ export class Harness extends ReadyResource {
941
1303
  throw new Error('harness is suspended');
942
1304
  const addon = category === 'tts' ? 'tts' : 'parakeet';
943
1305
  const name = residentSrc(model || (await this._defaultVoiceSrc(category)), addon);
944
- const handle = pool.open(name, name);
1306
+ this._assertNotClosing();
1307
+ const releaseWork = this._enterWork();
1308
+ let handle;
1309
+ try {
1310
+ handle = pool.open(name, name);
1311
+ }
1312
+ catch (error) {
1313
+ releaseWork();
1314
+ throw error;
1315
+ }
945
1316
  let job = null;
946
1317
  let releaseAfter = null;
947
1318
  let sdk = null;
@@ -949,6 +1320,7 @@ export class Harness extends ReadyResource {
949
1320
  try {
950
1321
  modelId = (await raceAbort(handle.load(), signal)).modelId;
951
1322
  sdk = (await this._sdk.loadVoice());
1323
+ this._assertNotClosing();
952
1324
  const boundSdk = sdk;
953
1325
  const boundId = modelId;
954
1326
  job = this._voiceLane(() => run(boundSdk, boundId));
@@ -959,7 +1331,7 @@ export class Harness extends ReadyResource {
959
1331
  if (sdk && modelId) {
960
1332
  void sdk.cancel({ modelId, operation: 'inference' }).catch(noop);
961
1333
  }
962
- releaseAfter = job;
1334
+ releaseAfter = job ?? handle.peek();
963
1335
  }
964
1336
  throw err;
965
1337
  }
@@ -968,10 +1340,15 @@ export class Harness extends ReadyResource {
968
1340
  releaseModel(pool, name);
969
1341
  };
970
1342
  if (releaseAfter) {
971
- void releaseAfter.then(release, release);
1343
+ void releaseAfter.then(release, release).finally(releaseWork).catch(noop);
972
1344
  }
973
1345
  else {
974
- release();
1346
+ try {
1347
+ release();
1348
+ }
1349
+ finally {
1350
+ releaseWork();
1351
+ }
975
1352
  }
976
1353
  }
977
1354
  }
@@ -1009,9 +1386,18 @@ export class Harness extends ReadyResource {
1009
1386
  if (this.suspended)
1010
1387
  throw new Error('harness is suspended');
1011
1388
  const name = residentSrc(model || (await this._defaultVoiceSrc('tts')), 'tts');
1389
+ this._assertNotClosing();
1012
1390
  const pool = this._ttsModels;
1013
- const handle = pool.open(name, name);
1014
1391
  const chunks = pushQueue();
1392
+ const releaseWork = this._enterWork();
1393
+ let handle;
1394
+ try {
1395
+ handle = pool.open(name, name);
1396
+ }
1397
+ catch (error) {
1398
+ releaseWork();
1399
+ throw error;
1400
+ }
1015
1401
  let sdk = null;
1016
1402
  let modelId = null;
1017
1403
  const cancel = () => {
@@ -1024,6 +1410,7 @@ export class Harness extends ReadyResource {
1024
1410
  try {
1025
1411
  modelId = (await handle.load()).modelId;
1026
1412
  sdk = (await this._sdk.loadVoice());
1413
+ this._assertNotClosing();
1027
1414
  if (signal?.aborted) {
1028
1415
  return;
1029
1416
  }
@@ -1046,53 +1433,98 @@ export class Harness extends ReadyResource {
1046
1433
  if (!settled) {
1047
1434
  cancel();
1048
1435
  }
1049
- void job.catch(noop).finally(() => {
1050
- releaseModel(pool, name);
1051
- });
1436
+ void job
1437
+ .catch(noop)
1438
+ .finally(() => {
1439
+ try {
1440
+ releaseModel(pool, name);
1441
+ }
1442
+ finally {
1443
+ releaseWork();
1444
+ }
1445
+ })
1446
+ .catch(noop);
1052
1447
  }
1053
1448
  }
1054
1449
  async list(options = {}) {
1055
1450
  if (!this.opened)
1056
1451
  await this.ready();
1057
- const sdk = await this._sdk.load();
1058
- const [rows, custom] = await Promise.all([
1059
- listModels(sdk, options, {
1452
+ const releaseWork = this._enterWork();
1453
+ try {
1454
+ const sdk = await this._sdk.load();
1455
+ this._assertNotClosing();
1456
+ const rows = await listModels(sdk, options, {
1060
1457
  cache: this._modelStatus,
1061
1458
  projectionFor: (name) => this._projectionFor(name)
1062
- }),
1063
- customModelRows(options, {
1064
- modelsDir: this._modelsDir,
1065
- loaded: new Set(this._loadedModels().map((entry) => entry.model))
1066
- })
1067
- ]);
1068
- return mergeCustomRows(rows, custom);
1459
+ });
1460
+ const loaded = this._settledModels();
1461
+ const loading = this._loadingModels();
1462
+ const custom = await customModelRows(options, { modelsDir: this._modelsDir, loaded });
1463
+ return mergeCustomRows(rows, custom).map((row) => ({
1464
+ ...row,
1465
+ isLoading: loading.has(row.name)
1466
+ }));
1467
+ }
1468
+ finally {
1469
+ releaseWork();
1470
+ }
1069
1471
  }
1070
1472
  async deleteModel(name) {
1071
1473
  if (!this.opened)
1072
1474
  await this.ready();
1073
- const sdk = await this._sdk.load();
1074
- const result = await deleteModelFiles(sdk, name, { modelsDir: this._modelsDir });
1075
- invalidateModelStatusCache(this._modelStatus);
1076
- return summarizeDelete(result);
1475
+ const releaseWork = this._enterWork();
1476
+ try {
1477
+ const sdk = await this._sdk.load();
1478
+ this._assertNotClosing();
1479
+ const result = await deleteModelFiles(sdk, name, { modelsDir: this._modelsDir });
1480
+ invalidateModelStatusCache(this._modelStatus);
1481
+ return summarizeDelete(result);
1482
+ }
1483
+ finally {
1484
+ releaseWork();
1485
+ }
1077
1486
  }
1078
1487
  async clearKvCache() {
1079
1488
  if (!this.opened)
1080
1489
  await this.ready();
1081
- return deleteAllKvCaches(() => this._sdk.load());
1490
+ const releaseWork = this._enterWork();
1491
+ try {
1492
+ return await deleteAllKvCaches(() => {
1493
+ this._assertNotClosing();
1494
+ return this._sdk.load();
1495
+ });
1496
+ }
1497
+ finally {
1498
+ releaseWork();
1499
+ }
1082
1500
  }
1083
1501
  async deleteKvCache(kvCacheKey) {
1084
1502
  if (!this.opened)
1085
1503
  await this.ready();
1086
- return deleteKvCacheKey(() => this._sdk.load(), kvCacheKey);
1504
+ const releaseWork = this._enterWork();
1505
+ try {
1506
+ return await deleteKvCacheKey(() => {
1507
+ this._assertNotClosing();
1508
+ return this._sdk.load();
1509
+ }, kvCacheKey);
1510
+ }
1511
+ finally {
1512
+ releaseWork();
1513
+ }
1087
1514
  }
1088
1515
  async infraModels(profile) {
1089
1516
  return infraModelsFor(profile);
1090
1517
  }
1091
1518
  async capability() {
1092
- if (this._capability)
1519
+ if (!this.opened)
1520
+ await this.ready();
1521
+ this._assertNotClosing();
1522
+ if (this._capability) {
1093
1523
  return this._capability;
1094
- if (this._capabilityInflight)
1524
+ }
1525
+ if (this._capabilityInflight) {
1095
1526
  return this._capabilityInflight;
1527
+ }
1096
1528
  const reads = {
1097
1529
  totalmem,
1098
1530
  platform,
@@ -1118,6 +1550,7 @@ export class Harness extends ReadyResource {
1118
1550
  };
1119
1551
  this._capabilityInflight = probeCapabilityProfile(reads)
1120
1552
  .then((capability) => {
1553
+ capability = { ...capability, hostRunLog: true, hostRunLogVersion: 1 };
1121
1554
  this._capability = capability;
1122
1555
  return capability;
1123
1556
  })
@@ -1132,23 +1565,26 @@ export class Harness extends ReadyResource {
1132
1565
  async _warmModel({ model, agent, signal }) {
1133
1566
  if (!this.opened)
1134
1567
  await this.ready();
1135
- if (this.suspended)
1568
+ this._assertNotClosing();
1569
+ if (this.suspended) {
1136
1570
  return;
1571
+ }
1137
1572
  const target = this._residency(model, agent);
1138
- const resident = isResident(target.pool, target.key);
1139
- hlog('warm model', { model, resident });
1140
- if (target.pool === this._models && !resident) {
1573
+ hlog('warm model', { model, resident: isResident(target.pool, target.key) });
1574
+ await this._loadAndRelease(target, signal, async () => {
1575
+ if (target.pool !== this._models || isResident(target.pool, target.key)) {
1576
+ return true;
1577
+ }
1141
1578
  const roomed = await this._makeRoomForCompletion(model, {
1142
1579
  ctxSize: target.ctxSize,
1143
1580
  signal,
1144
1581
  deadlineMs: WARM_ROOM_WAIT_MS
1145
1582
  });
1146
- if (!roomed) {
1147
- return;
1583
+ if (roomed) {
1584
+ await this._unloadIdleGenerators();
1148
1585
  }
1149
- await this._unloadIdleGenerators();
1150
- }
1151
- await this._loadAndRelease(target, signal);
1586
+ return roomed;
1587
+ });
1152
1588
  }
1153
1589
  loadModel(context) {
1154
1590
  return this._busy(() => this._loadModel(context));
@@ -1202,13 +1638,9 @@ export class Harness extends ReadyResource {
1202
1638
  }
1203
1639
  return { unloaded };
1204
1640
  }
1205
- async _loadAndRelease(target, signal) {
1206
- try {
1207
- await raceAbort(target.open().load(), signal);
1208
- }
1209
- finally {
1210
- releaseModel(target.pool, target.key);
1211
- }
1641
+ async _loadAndRelease({ pool, key, open }, signal, prepare = async () => true) {
1642
+ const lease = await this._acquireModelLease({ pool, key, open, signal, prepare });
1643
+ lease?.release();
1212
1644
  }
1213
1645
  async _unloadIdleGenerators() {
1214
1646
  await this._imageRuntime?.unloadIdle().catch(noop);
@@ -1271,6 +1703,57 @@ export class Harness extends ReadyResource {
1271
1703
  projectionModel: this._projectionFor(model)
1272
1704
  }, this._deviceConfig());
1273
1705
  }
1706
+ async _acquireModelLease({ pool, key, open, signal, prepare }) {
1707
+ const releaseWork = this._enterWork();
1708
+ let pendingLoad = null;
1709
+ let opened = false;
1710
+ let released = false;
1711
+ const release = () => {
1712
+ if (released) {
1713
+ return;
1714
+ }
1715
+ released = true;
1716
+ try {
1717
+ if (opened) {
1718
+ releaseModel(pool, key);
1719
+ }
1720
+ }
1721
+ finally {
1722
+ releaseWork();
1723
+ }
1724
+ };
1725
+ try {
1726
+ if (!(await prepare())) {
1727
+ release();
1728
+ return null;
1729
+ }
1730
+ if (signal?.aborted) {
1731
+ throw new AbortedError();
1732
+ }
1733
+ if (this.suspended || this._runtimeDraining) {
1734
+ throw new Error('harness is suspended');
1735
+ }
1736
+ this._assertNotClosing();
1737
+ if (this._runtimeUnloadFailed) {
1738
+ throw new Error('Runtime unload failed; close the harness before loading models again');
1739
+ }
1740
+ const handle = open();
1741
+ opened = true;
1742
+ const loading = handle.load();
1743
+ pendingLoad = loading;
1744
+ const loaded = await raceAbort(loading, signal);
1745
+ return { modelId: loaded.modelId, release };
1746
+ }
1747
+ catch (error) {
1748
+ if (pendingLoad) {
1749
+ void pendingLoad.then(release, release).catch(noop);
1750
+ }
1751
+ else {
1752
+ release();
1753
+ }
1754
+ throw error;
1755
+ }
1756
+ }
1274
1757
  invoke(context) {
1275
1758
  return this._busyStream(() => this._invoke(context));
1276
1759
  }
@@ -1278,8 +1761,10 @@ export class Harness extends ReadyResource {
1278
1761
  if (!this.opened)
1279
1762
  await this.ready();
1280
1763
  if (this.suspended) {
1764
+ this._assertNotClosing();
1281
1765
  return yield { type: 'aborted' };
1282
1766
  }
1767
+ const releaseWork = this._enterWork();
1283
1768
  hlog('invoke start', { capability, enhance });
1284
1769
  try {
1285
1770
  yield* invoke({
@@ -1299,117 +1784,101 @@ export class Harness extends ReadyResource {
1299
1784
  }
1300
1785
  finally {
1301
1786
  hlog('invoke end', { capability });
1787
+ releaseWork();
1302
1788
  }
1303
1789
  }
1304
1790
  completion(context) {
1305
- return this._busyStream(() => this._completion(context));
1791
+ this._assertNotClosing();
1792
+ if (this._runtimeDraining) {
1793
+ throw new Error('harness is suspended');
1794
+ }
1795
+ if (this._runtimeUnloadFailed) {
1796
+ throw new Error('Runtime unload failed; close the harness before loading models again');
1797
+ }
1798
+ const tracked = trackCompletion((signal) => this._busyStream(() => this._runCompletion({ ...context, signal })), context.signal, (run) => {
1799
+ this._completionRuns.delete(run);
1800
+ this._notifyEffectDrain();
1801
+ });
1802
+ const { iterator, run } = tracked;
1803
+ this._completionRuns.add(run);
1804
+ return iterator;
1306
1805
  }
1307
- async *_completion({ model, agent, messages, cacheKey, thinkingDefault, toolConfigs, generationParams, responseFormat, captureInferenceCalls, grantedDirs, grantedDrives, driveOp, grantedWriteDirs, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal }) {
1806
+ async *_runCompletion(request) {
1308
1807
  if (!this.opened)
1309
1808
  await this.ready();
1310
- if (this.suspended)
1311
- throw new Error('harness is suspended');
1312
- const loadOpts = this._completionLoadOptions(model, agent);
1313
- const projectionModel = loadOpts.projectionModel;
1314
- const key = modelKey(model, loadOpts);
1315
- const pool = this._models;
1316
- const resident = isResident(pool, key);
1317
- hlog('completion start', { model, resident, projection: Boolean(projectionModel) });
1318
- if (!resident) {
1319
- await this._makeRoomForCompletion(model, { ctxSize: loadOpts.ctxSize, signal });
1320
- await this._unloadIdleGenerators();
1809
+ this._assertNotClosing();
1810
+ if (this.suspended || this._runtimeDraining) {
1811
+ yield { type: 'aborted' };
1812
+ return;
1321
1813
  }
1322
- const acquireRound = async () => {
1323
- if (!isResident(pool, key)) {
1324
- await this._makeRoomForCompletion(model, { ctxSize: loadOpts.ctxSize, signal });
1325
- await this._unloadIdleGenerators();
1326
- }
1327
- try {
1328
- const { modelId } = await raceAbort(pool.open(key, model, loadOpts).load(), signal);
1329
- return {
1330
- modelId,
1331
- release: () => {
1332
- releaseModel(pool, key);
1333
- }
1334
- };
1814
+ const { boot, onStep, hostAppendTimeoutMs, requestDigestKey, ...provided } = request;
1815
+ let modelId = request.model ?? '';
1816
+ let key = null;
1817
+ try {
1818
+ const runtime = async (run) => {
1819
+ modelId = run.model ?? run.modelId;
1820
+ const runContext = { ...run, model: run.model ?? run.modelId };
1821
+ const lease = await this._completionRuntime(runContext);
1822
+ if (!key) {
1823
+ this._replying.push(lease.key);
1824
+ }
1825
+ key = lease.key;
1826
+ return lease;
1827
+ };
1828
+ const args = { ...provided, lockOwner: this };
1829
+ for await (const event of completion(runtime, args, {
1830
+ boot,
1831
+ onStep,
1832
+ hostAppendTimeoutMs,
1833
+ requestDigestKey,
1834
+ resumeRequest: provided,
1835
+ onDetached: (pending, runId) => this._trackDetachedRun(pending, runId)
1836
+ })) {
1837
+ yield event;
1335
1838
  }
1336
- catch (err) {
1337
- releaseModel(pool, key);
1338
- throw err;
1839
+ if (key) {
1840
+ this._modelFaults.delete(modelId);
1339
1841
  }
1340
- };
1341
- const handle = pool.open(key, model, loadOpts);
1342
- let held = true;
1343
- const releaseHeld = () => {
1344
- if (!held)
1345
- return;
1346
- held = false;
1347
- releaseModel(pool, key);
1348
- };
1349
- this._replying.push(key);
1350
- try {
1351
- const { modelId, ctxSize } = await raceAbort(handle.load(), signal);
1352
- hlog('model ready', { model });
1353
- const sdk = await this._sdk.load();
1354
- const turns = projectionModel ? messages : messages.map(({ attachments, ...turn }) => turn);
1355
- const remoteRoots = driveOp && grantedDrives?.length ? createRemoteRoots(grantedDrives, driveOp) : [];
1356
- const fsRoots = [
1357
- ...(grantedDirs?.length ? await createLocalRoots(grantedDirs) : []),
1358
- ...remoteRoots
1359
- ];
1360
- const fsWriteRoots = [
1361
- ...(grantedWriteDirs?.length ? await createLocalRoots(grantedWriteDirs) : []),
1362
- ...remoteRoots.filter((root) => root.writable)
1363
- ];
1364
- releaseHeld();
1365
- yield* completion(sdk, {
1366
- modelId,
1367
- acquireRound,
1368
- model,
1369
- agent,
1370
- ctxSize,
1371
- cacheKey,
1372
- thinkingDefault,
1373
- generationParams,
1374
- responseFormat,
1375
- captureInferenceCalls,
1376
- messages: turns,
1377
- tools: this._tools,
1378
- skillCatalog: await this._completionSkills(agent?.id ?? null, model),
1379
- toolConfigs,
1380
- fsRoots,
1381
- fsWriteRoots,
1382
- approve,
1383
- attach,
1384
- fetchAttachment,
1385
- ragSearch,
1386
- progress,
1387
- askUser,
1388
- signal
1389
- });
1390
- this._modelFaults.delete(model);
1391
1842
  }
1392
- catch (err) {
1393
- if (err instanceof AbortedError || this.suspended) {
1394
- hlog('completion aborted', { model, suspended: this.suspended });
1395
- yield { type: 'aborted' };
1843
+ catch (error) {
1844
+ yield* this._completionFailed(error, modelId, key);
1845
+ }
1846
+ finally {
1847
+ if (key) {
1848
+ hlog('completion end', { model: modelId });
1849
+ this._replying.splice(this._replying.indexOf(key), 1);
1850
+ void this._imageRuntime?.unloadIdle().catch(noop);
1851
+ void this._musicRuntime?.unloadIdle().catch(noop);
1396
1852
  }
1397
- else {
1398
- const message = errorMessage(err);
1399
- hlog('completion failed', { model, ...resolutionDetail(err), err: message });
1400
- const wedged = isSdkModelWedgedError(err) ? await this._evictWedgedModel(model, key) : null;
1401
- yield { type: 'error', message: wedged ? wedged.message : message };
1402
- if (wedged?.fatal) {
1403
- process.exit(1);
1404
- }
1853
+ }
1854
+ }
1855
+ async *_completionFailed(err, model, key) {
1856
+ if (err instanceof AbortedError || this.suspended || this._runtimeDraining) {
1857
+ hlog('completion aborted', { model, suspended: this.suspended || this._runtimeDraining });
1858
+ yield { type: 'aborted' };
1859
+ return;
1860
+ }
1861
+ const message = errorMessage(err);
1862
+ const name = err instanceof Error ? err.name : undefined;
1863
+ hlog('completion failed', { model, ...resolutionDetail(err), err: message });
1864
+ if (key && isSdkModelWedgedError(err)) {
1865
+ const wedged = await this._evictWedgedModel(model, key);
1866
+ yield completionErrorEvent(wedged.message, name, err);
1867
+ if (wedged.fatal) {
1868
+ process.exit(1);
1405
1869
  }
1406
1870
  }
1871
+ else {
1872
+ yield completionErrorEvent(message, name, err);
1873
+ }
1874
+ }
1875
+ async _completionRuntime(request) {
1876
+ const releaseWork = this._enterWork();
1877
+ try {
1878
+ return await prepareCompletionRuntime(this, request);
1879
+ }
1407
1880
  finally {
1408
- hlog('completion end', { model });
1409
- this._replying.splice(this._replying.indexOf(key), 1);
1410
- releaseHeld();
1411
- void this._imageRuntime?.unloadIdle().catch(noop);
1412
- void this._musicRuntime?.unloadIdle().catch(noop);
1881
+ releaseWork();
1413
1882
  }
1414
1883
  }
1415
1884
  _residentEnhancer() {
@@ -1499,4 +1968,32 @@ export class Harness extends ReadyResource {
1499
1968
  }
1500
1969
  }
1501
1970
  }
1971
+ function completionErrorEvent(message, name, error) {
1972
+ const event = { type: 'error', message, ...completionErrorDetails(error) };
1973
+ if (name) {
1974
+ event.name = name;
1975
+ }
1976
+ return event;
1977
+ }
1978
+ function completionErrorDetails(error) {
1979
+ if (!isRecord(error))
1980
+ return {};
1981
+ if (error.code === 'PENDING_ARGS_REDACTED') {
1982
+ return { code: 'PENDING_ARGS_REDACTED', ...redactedRecoveryFields(error) };
1983
+ }
1984
+ if (error.name !== 'RunIncompatible') {
1985
+ return {};
1986
+ }
1987
+ const match = errorMessage(error).match(/^Incompatible run log: (machine|log|xstate) version (\d{1,10}(?:\.\d{1,10}){0,3}(?:-[A-Za-z0-9.-]{1,16})?) \(expected (\d{1,10}(?:\.\d{1,10}){0,3}(?:-[A-Za-z0-9.-]{1,16})?)\)/);
1988
+ if (!match) {
1989
+ return {};
1990
+ }
1991
+ const kind = match[1];
1992
+ const found = match[2];
1993
+ const expected = match[3];
1994
+ if ((kind !== 'machine' && kind !== 'log' && kind !== 'xstate') || !found || !expected) {
1995
+ return {};
1996
+ }
1997
+ return { kind, found, expected };
1998
+ }
1502
1999
  function noop() { }