@qvac/harness 0.8.0 → 0.8.6

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 (94) hide show
  1. package/CHANGELOG.md +97 -1
  2. package/README.md +49 -0
  3. package/dist-lib/index.d.ts +31 -2
  4. package/dist-lib/index.js +365 -53
  5. package/dist-lib/lib/audio-transcode-bare.d.ts +1 -1
  6. package/dist-lib/lib/audio-transcode-bare.js +22 -25
  7. package/dist-lib/lib/audio-transcode-node.d.ts +1 -1
  8. package/dist-lib/lib/audio-transcode-node.js +1 -3
  9. package/dist-lib/lib/capabilities/audio-resample.d.ts +1 -1
  10. package/dist-lib/lib/capabilities/audio-resample.js +7 -8
  11. package/dist-lib/lib/capabilities/completion.d.ts +5 -2
  12. package/dist-lib/lib/capabilities/completion.js +22 -4
  13. package/dist-lib/lib/capabilities/download.d.ts +3 -1
  14. package/dist-lib/lib/capabilities/download.js +1 -1
  15. package/dist-lib/lib/capabilities/preview-model-source.d.ts +1 -0
  16. package/dist-lib/lib/capabilities/preview-model-source.js +1 -0
  17. package/dist-lib/lib/capabilities/usage-breakdown.js +4 -1
  18. package/dist-lib/lib/capabilities/voice.d.ts +5 -1
  19. package/dist-lib/lib/capabilities/voice.js +94 -26
  20. package/dist-lib/lib/connect.d.ts +4 -1
  21. package/dist-lib/lib/connect.js +14 -1
  22. package/dist-lib/lib/contract-types.d.ts +10 -0
  23. package/dist-lib/lib/docx/index.d.ts +1 -1
  24. package/dist-lib/lib/docx/index.js +16 -24
  25. package/dist-lib/lib/models/download-gate.d.ts +20 -0
  26. package/dist-lib/lib/models/download-gate.js +73 -0
  27. package/dist-lib/lib/models/memory-cap.d.ts +13 -0
  28. package/dist-lib/lib/models/memory-cap.js +21 -0
  29. package/dist-lib/lib/models/model-catalog-constants.d.ts +3 -1
  30. package/dist-lib/lib/models/model-catalog-constants.js +9 -1
  31. package/dist-lib/lib/models/model-catalog.d.ts +6 -2
  32. package/dist-lib/lib/models/model-catalog.js +35 -3
  33. package/dist-lib/lib/models/model-handle.d.ts +8 -1
  34. package/dist-lib/lib/models/model-handle.js +20 -2
  35. package/dist-lib/lib/models/model-list.d.ts +8 -1
  36. package/dist-lib/lib/models/model-list.js +23 -0
  37. package/dist-lib/lib/models/model-pool.d.ts +33 -5
  38. package/dist-lib/lib/models/model-pool.js +97 -29
  39. package/dist-lib/lib/office/extract.d.ts +9 -0
  40. package/dist-lib/lib/office/extract.js +19 -0
  41. package/dist-lib/lib/office/index.d.ts +1 -1
  42. package/dist-lib/lib/office/index.js +74 -19
  43. package/dist-lib/lib/serve.js +18 -2
  44. package/dist-lib/lib/tools/exec/adapters/python/module-distributions.js +0 -3
  45. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.d.ts +7 -50
  46. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +38 -695
  47. package/dist-lib/lib/tools/exec/adapters/python/run-session.js +1 -85
  48. package/dist-lib/lib/tools/exec.js +9 -20
  49. package/dist-lib/lib/tools/generate-image.d.ts +4 -2
  50. package/dist-lib/lib/tools/generate-image.js +5 -2
  51. package/dist-lib/lib/tools/generate-music.d.ts +4 -2
  52. package/dist-lib/lib/tools/generate-music.js +5 -2
  53. package/dist-lib/lib/tools/office/ops.d.ts +13 -0
  54. package/dist-lib/lib/tools/office/ops.js +100 -0
  55. package/dist-lib/lib/tools/office.d.ts +12 -0
  56. package/dist-lib/lib/tools/office.js +486 -0
  57. package/dist-lib/lib/tools/translate.d.ts +3 -1
  58. package/dist-lib/lib/tools/translate.js +6 -1
  59. package/dist-lib/schema/harness.js +41 -3
  60. package/dist-lib/spec/bind.js +9 -0
  61. package/dist-lib/spec/calls.js +3 -0
  62. package/dist-lib/spec/hrpc/hrpc.json +39 -0
  63. package/dist-lib/spec/hrpc/index.js +39 -3
  64. package/dist-lib/spec/hrpc/messages.js +93 -10
  65. package/dist-lib/spec/hyperschema/index.js +93 -10
  66. package/dist-lib/spec/hyperschema/schema.json +57 -1
  67. package/dist-lib/spec/hyperschema/types.d.ts +19 -2
  68. package/package.json +13 -9
  69. package/wheels/manifest.json +1 -160
  70. package/worker/harness-sidecar.mjs +3 -1
  71. package/dist-lib/lib/office/pptx.d.ts +0 -1
  72. package/dist-lib/lib/office/pptx.js +0 -26
  73. package/dist-lib/lib/office/python.d.ts +0 -8
  74. package/dist-lib/lib/office/python.js +0 -32
  75. package/dist-lib/lib/office/xlsx.d.ts +0 -1
  76. package/dist-lib/lib/office/xlsx.js +0 -26
  77. package/dist-lib/lib/tools/exec/adapters/python/create-effect.d.ts +0 -7
  78. package/dist-lib/lib/tools/exec/adapters/python/create-effect.js +0 -318
  79. package/wheels/cffi-2.0.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
  80. package/wheels/cryptography-47.0.0-cp314-abi3-pyemscripten_2026_0_wasm32.whl +0 -0
  81. package/wheels/defusedxml-0.7.1-py2.py3-none-any.whl +0 -0
  82. package/wheels/et_xmlfile-2.0.0-py3-none-any.whl +0 -0
  83. package/wheels/fonttools-4.62.1-py3-none-any.whl +0 -0
  84. package/wheels/fpdf2-2.8.8-py3-none-any.whl +0 -0
  85. package/wheels/lxml-6.0.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
  86. package/wheels/openpyxl-3.1.5-py2.py3-none-any.whl +0 -0
  87. package/wheels/pillow-12.2.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
  88. package/wheels/pycparser-3.0-py3-none-any.whl +0 -0
  89. package/wheels/pypdf-6.15.0-py3-none-any.whl +0 -0
  90. package/wheels/python_docx-1.2.0-py3-none-any.whl +0 -0
  91. package/wheels/python_pptx-1.0.2-py3-none-any.whl +0 -0
  92. package/wheels/six-1.17.0-py2.py3-none-any.whl +0 -0
  93. package/wheels/typing_extensions-4.15.0-py3-none-any.whl +0 -0
  94. package/wheels/xlsxwriter-3.2.9-py3-none-any.whl +0 -0
package/CHANGELOG.md CHANGED
@@ -8,6 +8,100 @@ While the major version is `0`, a breaking change bumps the minor version.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.8.6] - 2026-10-08
12
+
13
+ ### Added
14
+
15
+ - `setModelMemoryCap(percent)` lets loaded models fill that share of the
16
+ device's memory budget, so chat models a request loaded stay loaded beside
17
+ each other while they fit, and a chat switching between them loads neither
18
+ again. A load that would not fit unloads idle models one at a time: image and
19
+ music models first, then embedding, speech and transcription models, then
20
+ chat models a request loaded, then those loaded by hand, least recently used
21
+ first. Each load is sized by `assessModelFit`, and `list()` rows of loaded
22
+ models carry the estimate as `memoryBytes`; `memoryUsage()` answers their
23
+ total. A manual `loadModel` of any kind never unloads another model. `0`, a mobile device, and a host
24
+ that never sets a cap keep one chat model a request loaded, as before
25
+ (QVAC-25735). (#358)
26
+ - `setDownloadsAllowed({ allowed })`, in process and over hrpc, lets a host hold
27
+ model downloads. While downloads are not allowed, a load whose model is not on
28
+ the device emits a `model` event with `status: 'held'` and the model's
29
+ download name and `addon`, then waits. It goes ahead once a `download()` puts
30
+ the model on the device or downloads are allowed again; aborting the caller's
31
+ signal ends it with `AbortedError`, and a wait that ends without loading
32
+ reports `error` with `message: 'aborted'`. This covers the image, music and translation
33
+ runtimes and every pooled load (chat, embedding, speech, transcription).
34
+ `download()` is never held, and downloads are allowed by default. Over hrpc,
35
+ every model event frame now carries an `addon` field, which is `null` unless
36
+ the event is `held` or the `error` that ends a held wait. (#340)
37
+ - Ternary Bonsai 2 27B joins the curated catalog in two builds,
38
+ `Ternary-Bonsai-2-27B-PQ2_0` (7.2 GB) and `Ternary-Bonsai-2-27B-PTQ1_0`
39
+ (5.9 GB), each with its vision projection. Both sit in the `high` tier, by
40
+ their memory footprint rather than their parameter count. As a Qwen3.8-27B
41
+ fine-tune whose name the SDK's dialect router does not recognise, it parses
42
+ tool calls as `qwen35`; a catalog entry can now name its tool dialect. (#303)
43
+
44
+ ### Changed
45
+
46
+ - `@qvac/sdk` and `@qvac/inference` 0.21.0, with `@qvac/llm-llamacpp` 0.55.0.
47
+ `@qvac/model-fit` leaves the native graph, and `@qvac/fabric` now ships its
48
+ prebuilds as per-platform packages (`@qvac/fabric-<os>-<cpu>`), so a host
49
+ that packages the addons exempts those names from any install age gate.
50
+ The fit check passes the model's source to the SDK's new `loadModel`-shaped
51
+ `assessModelFit` input. (#302)
52
+ - Transcription streams long audio to the model in 300 s segments instead of
53
+ decoding the whole clip first, so peak memory no longer grows with the clip's
54
+ length (a 3.8 h clip peaked at about 0.9 GB above baseline, down from 1.8
55
+ GB). Clips under 60 s are transcribed as before. (#291)
56
+
57
+ ### Fixed
58
+
59
+ - A run that preloads a skill (`@skill`, or a skill named in prose) caches its
60
+ prompt under a child of the chat's key for the length of the run, instead of
61
+ prefilling the whole prompt on every round of a tool loop. The child key is
62
+ deleted when the run ends. (#305)
63
+
64
+ ## [0.8.5] - 2026-10-06
65
+
66
+ ### Added
67
+
68
+ - Skill-gated `pptx`, `docx` and `xlsx` tools on the office engine. A call takes
69
+ an `attachmentId` (or `create` options), `ops` that name engine methods with
70
+ named arguments, and an `output` file name; with an `attachmentId` alone it
71
+ returns the file's contents. The first failing op saves nothing and returns
72
+ the engine's error. An edit is never saved under the attached file's own
73
+ name. Saving the same file again in one turn answers with the one already
74
+ attached, and a changed file under that name is refused. The one-line answer
75
+ comes back as `reply`. These are the tools the presentations, word and excel
76
+ skills in `@qvac/skills` 0.1.12 call. (#286)
77
+
78
+ ### Changed
79
+
80
+ - **Breaking:** `@qvac/skills` is now 0.1.12, whose office and pdf skills run on
81
+ the `pptx`, `docx`, `xlsx` and `pdf` tools instead of Python. A host that
82
+ passes its own `skillsDir` needs 0.1.12 as well: older office skills import
83
+ Python libraries this release no longer ships. (#286)
84
+ - `extractOfficeText` and `extractDocxText` read .docx, .xlsx and .pptx in
85
+ process on the engine instead of starting Pyodide, and read at most 1M cells
86
+ across a workbook. Extracted workbook booleans read `true`/`false` instead of
87
+ `True`/`False`. `closeOfficeWorker` and `closeDocxWorker` remain as no-ops.
88
+ (#286)
89
+ - `exec` refuses Python that imports `pptx`, `docx` or `openpyxl`, or declares
90
+ their packages, before it runs, and names the tool (or the skill that grants
91
+ it) to use instead. (#287)
92
+ - **Breaking:** Python run through `exec` asks for approval like any other
93
+ write, including a run that only builds a new PDF. (#289)
94
+
95
+ ### Removed
96
+
97
+ - **Breaking:** the vendored Python wheels for python-pptx, python-docx,
98
+ openpyxl, fpdf2 and pypdf, and their dependencies. Only micropip is still
99
+ seeded; any other package installs from PyPI, which sandbox mode refuses.
100
+ Use the office and `pdf` tools. (#286, #289)
101
+ - The `exec` guidance written for python-pptx, python-docx and fpdf2 code:
102
+ their error hints and source checks, and the shrink-to-fit pass on saved
103
+ decks. (#288, #289)
104
+
11
105
  ## [0.8.0] - 2026-10-05
12
106
 
13
107
  ### Added
@@ -1108,7 +1202,9 @@ Tagged but never published to npm; these changes first shipped in 0.2.12.
1108
1202
  - GPU stats are read through `bare-gpu-info`, so every dependency installs from
1109
1203
  the public npm registry. (#12)
1110
1204
 
1111
- [Unreleased]: https://github.com/tetherto/qvac-harness/compare/v0.8.0...HEAD
1205
+ [Unreleased]: https://github.com/tetherto/qvac-harness/compare/v0.8.6...HEAD
1206
+ [0.8.6]: https://github.com/tetherto/qvac-harness/compare/v0.8.5...v0.8.6
1207
+ [0.8.5]: https://github.com/tetherto/qvac-harness/compare/v0.8.0...v0.8.5
1112
1208
  [0.8.0]: https://github.com/tetherto/qvac-harness/compare/v0.7.3...v0.8.0
1113
1209
  [0.7.3]: https://github.com/tetherto/qvac-harness/compare/v0.7.2...v0.7.3
1114
1210
  [0.7.2]: https://github.com/tetherto/qvac-harness/compare/v0.7.1...v0.7.2
package/README.md CHANGED
@@ -268,6 +268,55 @@ unloaded, and the next call loads what it needs again. The clock covers the
268
268
  whole device: any call resets it, whichever model it uses. `0`, the default, is
269
269
  Never.
270
270
 
271
+ ## Keeping several models in memory
272
+
273
+ ```js
274
+ await harness.setModelMemoryCap(85)
275
+ ```
276
+
277
+ Loaded models may fill that percent of the device's `memoryBudget`, so two chat
278
+ models that fit stay loaded and a chat switching between them loads neither
279
+ again. A model is unloaded only when the next load would not fit, one at a time:
280
+ idle image and music models first, then embedding, speech and transcription
281
+ models, then chat models a request loaded, then chat models loaded by hand, the
282
+ least recently used first within each. A model a request holds stays, and a
283
+ load past the cap goes ahead once nothing else can give way, so a model larger
284
+ than the cap loads alone. A manual `loadModel`, of any kind, still never unloads
285
+ another model: beside what is loaded it must fit the cap, or it is refused with
286
+ `reason: 'memory'`. Lowering the cap unloads idle models past it at once. Loads
287
+ under way count by their estimate, so two at once never count on the same room.
288
+
289
+ Each load is sized by the SDK's `assessModelFit` for exactly the load it runs,
290
+ falling back to the download size, and `list()` rows of loaded models carry that
291
+ estimate as `memoryBytes`. `memoryUsage()` answers what every loaded model holds,
292
+ whichever list rows a host reads. `0`, and any cap on a mobile device, keeps one chat
293
+ model a request loaded, as without a cap; a host that never calls it keeps that
294
+ behaviour and sizes nothing.
295
+
296
+ ## Holding model downloads
297
+
298
+ ```js
299
+ await harness.setDownloadsAllowed({ allowed: false })
300
+ ```
301
+
302
+ With downloads not allowed, a load whose model is not on the device does not
303
+ download it. This covers every load the harness makes: chat, embedding, speech,
304
+ transcription, image, music and translation. The load emits one `model` event
305
+ with `status: 'held'`, the model's download name and its `addon`, then waits.
306
+ Each `download()` that completes re-checks every waiting load, and a load goes
307
+ ahead once its model is on the device, or once
308
+ `setDownloadsAllowed({ allowed: true })` arrives, with no retry from the caller.
309
+ Aborting the caller's signal ends the wait with `AbortedError`, and a suspend or
310
+ close ends every wait. A wait that ends without loading reports `error` with
311
+ `message: 'aborted'`. A released wait reports nothing of its own: the `loading`
312
+ and `ready` after it name the model the way the pool does, which for embedding,
313
+ speech and transcription is not the row name. So a host clears `held` when its
314
+ `download()` of the row completes, when it allows downloads again, or on that
315
+ `error`. A model already on the device loads with no wait. The
316
+ check is the one `list({ cachedOnly: true })` makes, so a model counts as on the
317
+ device only when its companions and projection are too. `download()` itself is
318
+ never held. Downloads are allowed by default.
319
+
271
320
  ## Office extraction
272
321
 
273
322
  Hosts can supply the bundled Python extractor to their document pipeline:
@@ -10,7 +10,7 @@ import { type EmbedContext } from './lib/capabilities/embed.ts';
10
10
  import { type KvCacheDeletionResult } from './lib/capabilities/kv-cache.ts';
11
11
  import type { LlmGenerationParams } from './lib/models/llm-presets.ts';
12
12
  import { type SpeakContext, type SpeakResult, type SpeakStreamContext, type TranscribeContext } from './lib/capabilities/voice.ts';
13
- import type { CapabilityInfo, CredentialEntry, HarnessDiagnostics, InfraModel, LoadModelResult, SkillsOptions, SkillWriteResult, UnloadModelContext, UnloadModelResult } from './lib/contract-types.ts';
13
+ import type { CapabilityInfo, CredentialEntry, HarnessDiagnostics, InfraModel, LoadModelResult, MemoryUsage, SkillsOptions, SkillWriteResult, UnloadModelContext, UnloadModelResult } from './lib/contract-types.ts';
14
14
  import { type CustomModelEntry } from './lib/models/model-catalog.ts';
15
15
  import { type ListModelsOptions, type ModelInfo } from './lib/models/model-list.ts';
16
16
  import { type DeleteModelSummary } from './lib/models/model-delete.ts';
@@ -54,7 +54,7 @@ export { isAvailableState, SKILL_STATE, type SkillState } from './lib/tools/skil
54
54
  export { connectHarness, HARNESS_DIED, RemoteHarness } from './lib/connect.ts';
55
55
  export type { KvCacheDeletionResult } from './lib/capabilities/kv-cache.ts';
56
56
  export type { AgentKnobs, ApprovalCall, ChatMessage, CredentialEntry, DownloadContext, HarnessContract, HarnessDiagnostics, HarnessEvent, HarnessInfo, HarnessStatus, InfraModel, ModelState, SkillsOptions, SkillWriteResult } from './lib/contract-types.ts';
57
- import type { HarnessInfo } from './lib/contract-types.ts';
57
+ import type { DownloadsAllowedOptions, HarnessInfo } from './lib/contract-types.ts';
58
58
  export { serveHarness } from './lib/serve.ts';
59
59
  export { hlog, setHarnessLog, setHarnessLogSink } from './lib/log.ts';
60
60
  export { harnessHooks } from './lib/hooks/registry.ts';
@@ -142,6 +142,8 @@ export declare class Harness extends ReadyResource {
142
142
  private readonly _pinned;
143
143
  private readonly _replying;
144
144
  private readonly _modelStatus;
145
+ private readonly _downloads;
146
+ private readonly _hold;
145
147
  private _models;
146
148
  private _embedModels;
147
149
  private readonly _retiredEmbedModels;
@@ -184,6 +186,11 @@ export declare class Harness extends ReadyResource {
184
186
  private _capabilityInflight;
185
187
  private readonly _modelFaults;
186
188
  private readonly _idle;
189
+ private _sizeLoads;
190
+ private _capBytes;
191
+ private readonly _reserved;
192
+ private readonly _admission;
193
+ private readonly _byHand;
187
194
  constructor({ tools, sdkLoader, skillsDir, modelsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, completionModelCap, credentialStore, platform, gpuStats }?: HarnessOptions);
188
195
  _open(): Promise<void>;
189
196
  get suspended(): boolean;
@@ -213,6 +220,10 @@ export declare class Harness extends ReadyResource {
213
220
  private _projectionFor;
214
221
  setCustomModels(entries: ReadonlyArray<CustomModelEntry>): void;
215
222
  setModelIdleTimeout(ms: number): void;
223
+ setModelMemoryCap(percent: number): Promise<void>;
224
+ memoryUsage(): Promise<MemoryUsage>;
225
+ setDownloadsAllowed({ allowed }: DownloadsAllowedOptions): void;
226
+ private _isModelCached;
216
227
  previewModelSource(context: PreviewModelSourceContext): Promise<ModelSourcePreview>;
217
228
  private _assessFit;
218
229
  describe(): Promise<HarnessInfo>;
@@ -234,6 +245,7 @@ export declare class Harness extends ReadyResource {
234
245
  private _notifyModel;
235
246
  private _collectRetiredEmbedPool;
236
247
  private _releaseEmbedResource;
248
+ private _collectRetiredEmbedPoolAfter;
237
249
  private _releaseEmbedResourceAfter;
238
250
  private _destroyEmbedPools;
239
251
  private _enqueueEmbedControl;
@@ -275,6 +287,23 @@ export declare class Harness extends ReadyResource {
275
287
  private _loadAndRelease;
276
288
  private _unloadIdleGenerators;
277
289
  private _fits;
290
+ private _curatedFit;
291
+ private _admitByHand;
292
+ private _replacedBy;
293
+ private _sizeByHand;
294
+ private _loadByHand;
295
+ private _admit;
296
+ private _makeRoomForGenerator;
297
+ private _yieldGenerators;
298
+ private _beforeLoad;
299
+ private _reserve;
300
+ private _sizeLoad;
301
+ private _makeRoom;
302
+ private _capVictims;
303
+ private _victimRank;
304
+ private _usedBytes;
305
+ private _settledBytes;
306
+ private _memoryByModel;
278
307
  private _yieldPins;
279
308
  private _residency;
280
309
  private _completionLoadOptions;