@qvac/harness 0.8.5 → 0.8.7
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/CHANGELOG.md +1278 -472
- package/README.md +49 -0
- package/dist-lib/index.d.ts +42 -2
- package/dist-lib/index.js +447 -58
- package/dist-lib/lib/audio-transcode-bare.d.ts +1 -1
- package/dist-lib/lib/audio-transcode-bare.js +22 -25
- package/dist-lib/lib/audio-transcode-node.d.ts +1 -1
- package/dist-lib/lib/audio-transcode-node.js +1 -3
- package/dist-lib/lib/capabilities/audio-resample.d.ts +1 -1
- package/dist-lib/lib/capabilities/audio-resample.js +7 -8
- package/dist-lib/lib/capabilities/completion.d.ts +6 -3
- package/dist-lib/lib/capabilities/completion.js +96 -49
- package/dist-lib/lib/capabilities/download.d.ts +3 -1
- package/dist-lib/lib/capabilities/download.js +1 -1
- package/dist-lib/lib/capabilities/preview-model-source.d.ts +1 -0
- package/dist-lib/lib/capabilities/preview-model-source.js +1 -0
- package/dist-lib/lib/capabilities/tool-selection.d.ts +1 -3
- package/dist-lib/lib/capabilities/tool-selection.js +2 -4
- package/dist-lib/lib/capabilities/tool-turns.d.ts +0 -1
- package/dist-lib/lib/capabilities/tool-turns.js +0 -6
- package/dist-lib/lib/capabilities/usage-breakdown.js +4 -1
- package/dist-lib/lib/capabilities/voice.d.ts +5 -1
- package/dist-lib/lib/capabilities/voice.js +94 -26
- package/dist-lib/lib/connect.d.ts +4 -1
- package/dist-lib/lib/connect.js +16 -1
- package/dist-lib/lib/contract-types.d.ts +12 -0
- package/dist-lib/lib/helpers/tool-call-markup.d.ts +1 -0
- package/dist-lib/lib/helpers/tool-call-markup.js +3 -0
- package/dist-lib/lib/models/context-choice.d.ts +15 -0
- package/dist-lib/lib/models/context-choice.js +55 -0
- package/dist-lib/lib/models/download-gate.d.ts +20 -0
- package/dist-lib/lib/models/download-gate.js +73 -0
- package/dist-lib/lib/models/memory-cap.d.ts +13 -0
- package/dist-lib/lib/models/memory-cap.js +21 -0
- package/dist-lib/lib/models/model-catalog-constants.d.ts +3 -1
- package/dist-lib/lib/models/model-catalog-constants.js +9 -1
- package/dist-lib/lib/models/model-catalog.d.ts +6 -2
- package/dist-lib/lib/models/model-catalog.js +35 -3
- package/dist-lib/lib/models/model-handle.d.ts +8 -1
- package/dist-lib/lib/models/model-handle.js +20 -2
- package/dist-lib/lib/models/model-list.d.ts +8 -1
- package/dist-lib/lib/models/model-list.js +23 -0
- package/dist-lib/lib/models/model-pool.d.ts +35 -5
- package/dist-lib/lib/models/model-pool.js +97 -32
- package/dist-lib/lib/models/sdk-faults.d.ts +1 -0
- package/dist-lib/lib/models/sdk-faults.js +6 -0
- package/dist-lib/lib/office/engine/pptx/picture-area.d.ts +23 -0
- package/dist-lib/lib/office/engine/pptx/picture-area.js +84 -0
- package/dist-lib/lib/office/engine/pptx/presentation.d.ts +2 -0
- package/dist-lib/lib/office/engine/pptx/presentation.js +55 -17
- package/dist-lib/lib/office/engine/pptx/types.d.ts +1 -0
- package/dist-lib/lib/office/engine/xlsx/workbook.d.ts +1 -0
- package/dist-lib/lib/office/engine/xlsx/workbook.js +34 -3
- package/dist-lib/lib/serve.js +23 -3
- package/dist-lib/lib/spawn.js +4 -10
- package/dist-lib/lib/tools/exec.d.ts +4 -1
- package/dist-lib/lib/tools/exec.js +55 -11
- package/dist-lib/lib/tools/generate-image.d.ts +4 -2
- package/dist-lib/lib/tools/generate-image.js +5 -2
- package/dist-lib/lib/tools/generate-music.d.ts +4 -2
- package/dist-lib/lib/tools/generate-music.js +5 -2
- package/dist-lib/lib/tools/office/ops.js +1 -1
- package/dist-lib/lib/tools/office.js +29 -6
- package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
- package/dist-lib/lib/tools/skills/catalog.js +2 -0
- package/dist-lib/lib/tools/skills/cli-schema-check.d.ts +1 -0
- package/dist-lib/lib/tools/skills/cli-schema-check.js +3 -0
- package/dist-lib/lib/tools/skills/cli-schema-load.js +10 -0
- package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +2 -0
- package/dist-lib/lib/tools/skills/cli-schema.js +6 -3
- package/dist-lib/lib/tools/skills/frontmatter-parser.d.ts +1 -0
- package/dist-lib/lib/tools/skills/manifest-parse.d.ts +1 -0
- package/dist-lib/lib/tools/skills/manifest-parse.js +12 -0
- package/dist-lib/lib/tools/skills/mention.d.ts +0 -1
- package/dist-lib/lib/tools/skills/mention.js +0 -3
- package/dist-lib/lib/tools/translate.d.ts +3 -1
- package/dist-lib/lib/tools/translate.js +6 -1
- package/dist-lib/schema/harness.js +43 -4
- package/dist-lib/spec/bind.js +9 -0
- package/dist-lib/spec/calls.js +3 -0
- package/dist-lib/spec/hrpc/hrpc.json +39 -0
- package/dist-lib/spec/hrpc/index.js +39 -3
- package/dist-lib/spec/hrpc/messages.js +101 -13
- package/dist-lib/spec/hyperschema/index.js +101 -13
- package/dist-lib/spec/hyperschema/schema.json +62 -1
- package/dist-lib/spec/hyperschema/types.d.ts +20 -2
- package/package.json +8 -10
- package/worker/harness-sidecar.mjs +2 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const FRAME = 0.8;
|
|
2
|
+
const MARGIN = 0.03;
|
|
3
|
+
const MIN_BAND = 0.15;
|
|
4
|
+
const MIN_SPLIT = 0.5;
|
|
5
|
+
export function framed(slide) {
|
|
6
|
+
return {
|
|
7
|
+
x: Math.round((slide.width * (1 - FRAME)) / 2),
|
|
8
|
+
y: Math.round((slide.height * (1 - FRAME)) / 2),
|
|
9
|
+
width: Math.round(slide.width * FRAME),
|
|
10
|
+
height: Math.round(slide.height * FRAME)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function fitted(size, area) {
|
|
14
|
+
const scale = Math.min(area.width / size.width, area.height / size.height);
|
|
15
|
+
const width = Math.round(size.width * scale);
|
|
16
|
+
const height = Math.round(size.height * scale);
|
|
17
|
+
return {
|
|
18
|
+
x: area.x + Math.round((area.width - width) / 2),
|
|
19
|
+
y: area.y + Math.round((area.height - height) / 2),
|
|
20
|
+
width,
|
|
21
|
+
height
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function bandBelow(slide, shapes, margin) {
|
|
25
|
+
const top = Math.max(...shapes.map(({ box }) => box.y + box.height)) + margin;
|
|
26
|
+
const band = {
|
|
27
|
+
x: margin,
|
|
28
|
+
y: top,
|
|
29
|
+
width: slide.width - 2 * margin,
|
|
30
|
+
height: slide.height - top - margin
|
|
31
|
+
};
|
|
32
|
+
return band.height >= slide.height * MIN_BAND ? band : null;
|
|
33
|
+
}
|
|
34
|
+
function halves(box, gap) {
|
|
35
|
+
if (box.width >= box.height) {
|
|
36
|
+
const half = Math.round((box.width - gap) / 2);
|
|
37
|
+
const rest = box.width - half - gap;
|
|
38
|
+
return [
|
|
39
|
+
{ ...box, width: half },
|
|
40
|
+
{ ...box, x: box.x + half + gap, width: rest }
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
const half = Math.round((box.height - gap) / 2);
|
|
44
|
+
const rest = box.height - half - gap;
|
|
45
|
+
return [
|
|
46
|
+
{ ...box, height: half },
|
|
47
|
+
{ ...box, y: box.y + half + gap, height: rest }
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
function split(shape, gap) {
|
|
51
|
+
const [kept, freed] = halves(shape.box, gap);
|
|
52
|
+
const box = shape.role === 'picture' ? fitted(shape.box, kept) : kept;
|
|
53
|
+
return { box: freed, cleared: [], beside: { shape, box } };
|
|
54
|
+
}
|
|
55
|
+
function largest(shapes) {
|
|
56
|
+
return shapes.reduce((best, shape) => !best || shape.box.width * shape.box.height > best.box.width * best.box.height ? shape : best, undefined);
|
|
57
|
+
}
|
|
58
|
+
function overlaps(a, b) {
|
|
59
|
+
return a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
|
|
60
|
+
}
|
|
61
|
+
function place(slide, shapes) {
|
|
62
|
+
const slot = shapes.find((shape) => shape.role === 'slot');
|
|
63
|
+
if (slot) {
|
|
64
|
+
return { box: slot.box, cleared: [slot], beside: null };
|
|
65
|
+
}
|
|
66
|
+
const whole = { box: framed(slide), cleared: [], beside: null };
|
|
67
|
+
if (!shapes.length) {
|
|
68
|
+
return whole;
|
|
69
|
+
}
|
|
70
|
+
const margin = Math.round(slide.height * MARGIN);
|
|
71
|
+
const band = bandBelow(slide, shapes, margin);
|
|
72
|
+
if (band) {
|
|
73
|
+
return { box: band, cleared: [], beside: null };
|
|
74
|
+
}
|
|
75
|
+
const bodies = shapes.filter((shape) => shape.role === 'body');
|
|
76
|
+
const wide = bodies.filter((shape) => shape.box.width >= slide.width * MIN_SPLIT);
|
|
77
|
+
const shared = largest(wide) ?? largest(shapes.filter((shape) => shape.role === 'picture')) ?? largest(bodies);
|
|
78
|
+
return shared ? split(shared, margin) : whole;
|
|
79
|
+
}
|
|
80
|
+
export function pictureArea(slide, shapes) {
|
|
81
|
+
const area = place(slide, shapes.filter((shape) => shape.role !== 'blank'));
|
|
82
|
+
const covered = shapes.filter((shape) => shape.role === 'blank' && overlaps(shape.box, area.box));
|
|
83
|
+
return { ...area, cleared: [...area.cleared, ...covered] };
|
|
84
|
+
}
|
|
@@ -12,6 +12,7 @@ import * as fit from './fit.js';
|
|
|
12
12
|
import * as template from './template.js';
|
|
13
13
|
import * as slides from './slides.js';
|
|
14
14
|
import * as notes from './notes.js';
|
|
15
|
+
import { fitted, framed, pictureArea } from './picture-area.js';
|
|
15
16
|
import { pick, describe, layoutName, layoutType, BODY_TYPES } from './read.js';
|
|
16
17
|
import { SHAPE_ORDER, shapesOf, placeholderFrom, pictureElement, textBoxElement, tableElement, chartFrameElement, setBackground, nextShapeId } from './shapes.js';
|
|
17
18
|
const { a: A, p: P, r: R, p14: P14 } = xml.ns;
|
|
@@ -154,16 +155,19 @@ function checkSlideText(doc, at, opts) {
|
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
|
-
function
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
158
|
+
function roleOf(shape) {
|
|
159
|
+
const { el, kind, placeholder } = shape;
|
|
160
|
+
if (kind === 'picture') {
|
|
161
|
+
return 'picture';
|
|
162
|
+
}
|
|
163
|
+
if (el.localName !== 'sp' || !placeholder || !BODY_TYPES.has(placeholder)) {
|
|
164
|
+
return 'other';
|
|
165
|
+
}
|
|
166
|
+
const txBody = xml.child(el, P, 'txBody');
|
|
167
|
+
if (txBody && text.readParagraphs(txBody).some((p) => p.text.trim())) {
|
|
168
|
+
return placeholder === 'subTitle' ? 'other' : 'body';
|
|
169
|
+
}
|
|
170
|
+
return placeholder === 'obj' ? 'slot' : 'blank';
|
|
167
171
|
}
|
|
168
172
|
function tableBody(tc) {
|
|
169
173
|
const existing = xml.child(tc, A, 'txBody');
|
|
@@ -366,6 +370,9 @@ export class Presentation {
|
|
|
366
370
|
const layoutDoc = this.package.xml(layout);
|
|
367
371
|
const doc = slideFromLayout(layoutDoc);
|
|
368
372
|
checkSlideText(doc, at, opts);
|
|
373
|
+
if (opts.image !== undefined) {
|
|
374
|
+
probe(opts.image);
|
|
375
|
+
}
|
|
369
376
|
this.insertSlide({ doc, layout, at, ids: all.map((e) => e.id), before: all[at]?.el ?? null });
|
|
370
377
|
if (opts.title !== undefined) {
|
|
371
378
|
this.setText({ slide: at, shape: 'title' }, opts.title);
|
|
@@ -373,14 +380,25 @@ export class Presentation {
|
|
|
373
380
|
if (opts.body !== undefined) {
|
|
374
381
|
this.setText({ slide: at, shape: 'body' }, opts.body);
|
|
375
382
|
}
|
|
376
|
-
|
|
383
|
+
if (opts.image !== undefined) {
|
|
384
|
+
this.addPicture(at, opts.image);
|
|
385
|
+
}
|
|
386
|
+
const shapes = shapesOf(this.package.xml(slides.slideAt(this.deck, at).part));
|
|
387
|
+
return { index: at, layout: layoutName(layoutDoc), shapes: shapes.map(describe) };
|
|
377
388
|
}
|
|
378
389
|
addPicture(slide, bytes, opts = {}) {
|
|
379
390
|
const entry = slides.slideAt(this.deck, slide);
|
|
380
|
-
const
|
|
391
|
+
const area = this.pictureArea(entry.part, opts);
|
|
392
|
+
const box = this.pictureBox(probe(bytes), opts, area.box);
|
|
381
393
|
const { rId } = media.addImage(this.package, bytes, { source: entry.part, dir: '../media' });
|
|
382
394
|
const doc = this.package.xml(entry.part);
|
|
383
395
|
const id = nextShapeId(doc);
|
|
396
|
+
for (const shape of area.cleared) {
|
|
397
|
+
xml.remove(shape.el);
|
|
398
|
+
}
|
|
399
|
+
if (area.beside) {
|
|
400
|
+
this.narrow(entry.part, area.beside);
|
|
401
|
+
}
|
|
384
402
|
this.spTree(entry.part).appendChild(pictureElement(doc, { id, rId, box, name: opts.name, description: opts.description }));
|
|
385
403
|
this.package.touch(entry.part);
|
|
386
404
|
return { id, ...box };
|
|
@@ -722,14 +740,34 @@ export class Presentation {
|
|
|
722
740
|
}
|
|
723
741
|
return box;
|
|
724
742
|
}
|
|
725
|
-
|
|
743
|
+
narrow(part, { shape, box }) {
|
|
744
|
+
writeXfrm(shape.el, box);
|
|
745
|
+
const txBody = xml.child(shape.el, P, 'txBody');
|
|
746
|
+
const estimate = txBody ? this.estimate(part, shape.el, txBody) : null;
|
|
747
|
+
if (txBody && estimate?.overflow) {
|
|
748
|
+
applyAutofit(txBody, estimate);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
pictureArea(part, opts) {
|
|
752
|
+
if (opts.x !== undefined || opts.y !== undefined) {
|
|
753
|
+
return { box: framed(this.slideSize), cleared: [], beside: null };
|
|
754
|
+
}
|
|
755
|
+
const shapes = shapesOf(this.package.xml(part)).flatMap((shape) => {
|
|
756
|
+
if (shape.group !== null || SKIPPED_PLACEHOLDERS.has(shape.placeholder ?? '')) {
|
|
757
|
+
return [];
|
|
758
|
+
}
|
|
759
|
+
const box = inherit.box({ pkg: this.package, slide: part, el: shape.el });
|
|
760
|
+
return box ? [{ el: shape.el, box, role: roleOf(shape) }] : [];
|
|
761
|
+
});
|
|
762
|
+
return pictureArea(this.slideSize, shapes);
|
|
763
|
+
}
|
|
764
|
+
pictureBox(info, opts, area) {
|
|
726
765
|
checkPlacement(opts);
|
|
727
|
-
const slide = this.slideSize;
|
|
728
766
|
const ratio = info.height / info.width;
|
|
729
767
|
let { width, height } = opts;
|
|
730
768
|
if (width === undefined && height === undefined) {
|
|
731
769
|
const natural = { width: info.width * EMU_PER_PIXEL, height: info.height * EMU_PER_PIXEL };
|
|
732
|
-
const scale = Math.min(1,
|
|
770
|
+
const scale = Math.min(1, area.width / natural.width, area.height / natural.height);
|
|
733
771
|
width = Math.round(natural.width * scale);
|
|
734
772
|
height = Math.round(natural.height * scale);
|
|
735
773
|
}
|
|
@@ -742,8 +780,8 @@ export class Presentation {
|
|
|
742
780
|
if (!width || !height) {
|
|
743
781
|
throw new DocsError('INVALID_BOX', 'the picture would have no visible size');
|
|
744
782
|
}
|
|
745
|
-
const x = opts.x ?? Math.max(0, Math.round((
|
|
746
|
-
const y = opts.y ?? Math.max(0, Math.round((
|
|
783
|
+
const x = opts.x ?? Math.max(0, area.x + Math.round((area.width - width) / 2));
|
|
784
|
+
const y = opts.y ?? Math.max(0, area.y + Math.round((area.height - height) / 2));
|
|
747
785
|
return { x, y, width, height };
|
|
748
786
|
}
|
|
749
787
|
}
|
|
@@ -2,6 +2,7 @@ import { Package } from '../opc/package.js';
|
|
|
2
2
|
import * as xml from '../xml.js';
|
|
3
3
|
import * as media from '../opc/media.js';
|
|
4
4
|
import { probe } from '../image.js';
|
|
5
|
+
import { points } from '../units.js';
|
|
5
6
|
import * as refs from './refs.js';
|
|
6
7
|
import * as formula from './formula.js';
|
|
7
8
|
import { Sheet, SHEET_ORDER } from './sheet.js';
|
|
@@ -170,6 +171,12 @@ function pictureAnchor(doc, anchor, spec) {
|
|
|
170
171
|
root.appendChild(d('clientData'));
|
|
171
172
|
return root;
|
|
172
173
|
}
|
|
174
|
+
function markerCell(marker) {
|
|
175
|
+
return {
|
|
176
|
+
row: Number(xml.text(xml.child(marker, XDR, 'row'))),
|
|
177
|
+
col: Number(xml.text(xml.child(marker, XDR, 'col')))
|
|
178
|
+
};
|
|
179
|
+
}
|
|
173
180
|
function nextDrawingId(doc) {
|
|
174
181
|
return (Math.max(0, ...xml.descendants(doc, XDR, 'cNvPr').map((el) => Number(el.getAttribute('id')) || 0)) + 1);
|
|
175
182
|
}
|
|
@@ -421,7 +428,7 @@ export class Workbook {
|
|
|
421
428
|
const target = this.sheet(sheet);
|
|
422
429
|
const info = probe(bytes);
|
|
423
430
|
const o = isRecord(opts) ? opts : {};
|
|
424
|
-
const at =
|
|
431
|
+
const at = o.cell === undefined ? this.besideData(target) : refs.parse(o.cell);
|
|
425
432
|
const size = imageSize(o, info);
|
|
426
433
|
const drawing = this.ensureDrawing(target);
|
|
427
434
|
const { rId } = media.addImage(this.package, bytes, { source: drawing, dir: '../media' });
|
|
@@ -791,7 +798,8 @@ export class Workbook {
|
|
|
791
798
|
const from = xml.child(anchor, XDR, 'from');
|
|
792
799
|
const info = chart.readChart(this.package.xml(rel.part));
|
|
793
800
|
if (from) {
|
|
794
|
-
|
|
801
|
+
const { row, col } = markerCell(from);
|
|
802
|
+
info.cell = refs.cell(row, col);
|
|
795
803
|
}
|
|
796
804
|
out.push(info);
|
|
797
805
|
}
|
|
@@ -823,7 +831,30 @@ export class Workbook {
|
|
|
823
831
|
}
|
|
824
832
|
besideData(sheet) {
|
|
825
833
|
const used = sheet.usedRange();
|
|
826
|
-
|
|
834
|
+
const col = used ? used.right + 2 : 0;
|
|
835
|
+
return { row: Math.max(used ? used.top : 0, ...this.rowsBelowDrawings(sheet, col)), col };
|
|
836
|
+
}
|
|
837
|
+
rowsBelowDrawings(sheet, col) {
|
|
838
|
+
const drawing = this.drawingOf(sheet);
|
|
839
|
+
if (!drawing) {
|
|
840
|
+
return [];
|
|
841
|
+
}
|
|
842
|
+
const format = xml.child(sheet.root, X, 'sheetFormatPr');
|
|
843
|
+
const rowHeight = points(Number(format?.getAttribute('defaultRowHeight') ?? 15));
|
|
844
|
+
return xml.children(this.package.root(drawing), XDR).flatMap((anchor) => {
|
|
845
|
+
const from = xml.child(anchor, XDR, 'from');
|
|
846
|
+
if (!from || markerCell(from).col !== col) {
|
|
847
|
+
return [];
|
|
848
|
+
}
|
|
849
|
+
const to = xml.child(anchor, XDR, 'to');
|
|
850
|
+
if (to) {
|
|
851
|
+
return [markerCell(to).row + 2];
|
|
852
|
+
}
|
|
853
|
+
const ext = xml.child(anchor, XDR, 'ext');
|
|
854
|
+
return ext
|
|
855
|
+
? [markerCell(from).row + Math.ceil(Number(ext.getAttribute('cy')) / rowHeight) + 1]
|
|
856
|
+
: [];
|
|
857
|
+
});
|
|
827
858
|
}
|
|
828
859
|
drawingOf(sheet) {
|
|
829
860
|
const ref = xml.child(sheet.root, X, 'drawing');
|
package/dist-lib/lib/serve.js
CHANGED
|
@@ -8,7 +8,7 @@ import HRPC from '../spec/hrpc/index.js';
|
|
|
8
8
|
import { isCapability } from './capabilities/invoke.js';
|
|
9
9
|
import { CUSTOM_TASK_TYPES } from './models/model-catalog.js';
|
|
10
10
|
import { unsupportedKvCacheDeletion } from './capabilities/kv-cache.js';
|
|
11
|
-
import { errorMessage } from './guards.js';
|
|
11
|
+
import { errorMessage, isRecord } from './guards.js';
|
|
12
12
|
import { hlog } from './log.js';
|
|
13
13
|
import { pushQueue } from './queue.js';
|
|
14
14
|
import { TextCoalescer } from './helpers/text-coalescer.js';
|
|
@@ -23,7 +23,8 @@ const MODEL_STATUSES = new Set([
|
|
|
23
23
|
'ready',
|
|
24
24
|
'error',
|
|
25
25
|
'unloading',
|
|
26
|
-
'unloaded'
|
|
26
|
+
'unloaded',
|
|
27
|
+
'held'
|
|
27
28
|
]);
|
|
28
29
|
const DOWNLOAD_PROGRESS_INTERVAL_MS = 500;
|
|
29
30
|
const WIRE_HIGH_WATER_MARK = 64;
|
|
@@ -50,6 +51,20 @@ export function serveHarness(stream, harness) {
|
|
|
50
51
|
await harness.setModelIdleTimeout?.(ms);
|
|
51
52
|
return { ok: true };
|
|
52
53
|
},
|
|
54
|
+
setModelMemoryCap: async ({ percent }) => {
|
|
55
|
+
await harness.setModelMemoryCap?.(percent);
|
|
56
|
+
return { ok: true };
|
|
57
|
+
},
|
|
58
|
+
memoryUsage: async () => {
|
|
59
|
+
if (!harness.memoryUsage) {
|
|
60
|
+
throw new Error('harness does not support memoryUsage');
|
|
61
|
+
}
|
|
62
|
+
return harness.memoryUsage();
|
|
63
|
+
},
|
|
64
|
+
setDownloadsAllowed: async ({ allowed }) => {
|
|
65
|
+
await harness.setDownloadsAllowed?.({ allowed: allowed === true });
|
|
66
|
+
return { ok: true };
|
|
67
|
+
},
|
|
53
68
|
listManagedSkills: async () => ({
|
|
54
69
|
skills: (await harness.listManagedSkills?.()) ?? []
|
|
55
70
|
}),
|
|
@@ -282,7 +297,8 @@ function modelEvents(harness) {
|
|
|
282
297
|
total,
|
|
283
298
|
message: state.message,
|
|
284
299
|
catalogChanged,
|
|
285
|
-
loaded
|
|
300
|
+
loaded,
|
|
301
|
+
addon: state.addon
|
|
286
302
|
};
|
|
287
303
|
const now = Date.now();
|
|
288
304
|
if (state.status === 'downloading') {
|
|
@@ -509,10 +525,14 @@ function completion(harness) {
|
|
|
509
525
|
model: frame.model ?? '',
|
|
510
526
|
agent: frame.agent ?? {},
|
|
511
527
|
cacheKey: frame.cacheKey ?? undefined,
|
|
528
|
+
session: frame.session ?? undefined,
|
|
512
529
|
thinkingDefault: frame.thinkingDefault ?? undefined,
|
|
513
530
|
generationParams: frame.generationParams ?? undefined,
|
|
514
531
|
responseFormat: frame.responseFormat ?? undefined,
|
|
515
532
|
captureInferenceCalls: frame.captureInferenceCalls ?? undefined,
|
|
533
|
+
typedText: isRecord(frame.payload) && typeof frame.payload.typedText === 'string'
|
|
534
|
+
? frame.payload.typedText
|
|
535
|
+
: undefined,
|
|
516
536
|
messages: (frame.messages ?? []).map(({ role, content, attachments }) => ({
|
|
517
537
|
role,
|
|
518
538
|
content,
|
package/dist-lib/lib/spawn.js
CHANGED
|
@@ -46,9 +46,8 @@ export function spawnHarness(opts) {
|
|
|
46
46
|
batchSize: opts.embedBatchSize
|
|
47
47
|
});
|
|
48
48
|
return connectHarness(async () => {
|
|
49
|
-
const { default:
|
|
50
|
-
const {
|
|
51
|
-
const sidecar = new Sidecar(opts.entry, [
|
|
49
|
+
const { default: launch } = await import('bare-supervisor/runner/sidecar');
|
|
50
|
+
const { ipc, exit } = launch(opts.entry, [
|
|
52
51
|
opts.skillsDir ?? '',
|
|
53
52
|
JSON.stringify(opts.sdkStorage),
|
|
54
53
|
JSON.stringify(embedConfig),
|
|
@@ -60,13 +59,8 @@ export function spawnHarness(opts) {
|
|
|
60
59
|
opts.musicClamps ? JSON.stringify(opts.musicClamps) : '',
|
|
61
60
|
opts.wheelsDir ?? '',
|
|
62
61
|
opts.gpuStats === undefined ? '' : JSON.stringify(opts.gpuStats)
|
|
63
|
-
]);
|
|
64
|
-
|
|
65
|
-
sidecar.stderr.on('data', (data) => logger?.warn('harness', data.toString().trimEnd()));
|
|
66
|
-
const ipc = wrap(sidecar);
|
|
67
|
-
const died = new Promise((resolve) => {
|
|
68
|
-
sidecar.once('exit', (code) => resolve(`harness exited (${code}) before ready`));
|
|
69
|
-
});
|
|
62
|
+
], logger);
|
|
63
|
+
const died = exit.then((code) => `harness exited (${code}) before ready`);
|
|
70
64
|
const failure = await Promise.race([ipc.ready.then(() => null), died]);
|
|
71
65
|
if (failure) {
|
|
72
66
|
throw new Error(failure);
|
|
@@ -14,6 +14,7 @@ export interface ExecToolOptions {
|
|
|
14
14
|
readonly pythonNetwork?: boolean;
|
|
15
15
|
readonly credentialStore?: CredentialStoreFn;
|
|
16
16
|
readonly skillSettings?: SkillSettingsFn;
|
|
17
|
+
readonly skillEnv?: SkillEnvFn;
|
|
17
18
|
readonly realpath?: Realpath;
|
|
18
19
|
readonly deniedRoots?: readonly string[];
|
|
19
20
|
readonly skillsDir?: string;
|
|
@@ -23,4 +24,6 @@ export interface ScriptRun {
|
|
|
23
24
|
trusted: boolean;
|
|
24
25
|
}
|
|
25
26
|
export declare function resolveScriptRun(rawArgs: Record<string, unknown>, skillsDir: string | undefined): ScriptRun | ExecError | null;
|
|
26
|
-
export
|
|
27
|
+
export type SkillEnvFn = (skill: string) => Readonly<Record<string, string>> | undefined;
|
|
28
|
+
export declare function declaredEnv(declared: Readonly<Record<string, string>> | undefined, session: string | undefined): Record<string, string>;
|
|
29
|
+
export declare function createExecTool({ spawn, killProcess, loadPyodide, disablePyodide, pythonNetwork, credentialStore, skillSettings, skillEnv, realpath, deniedRoots, skillsDir }?: ExecToolOptions): ToolDefinition;
|
|
@@ -6,7 +6,7 @@ import { namedHosts } from './exec/adapters/python/named-hosts.js';
|
|
|
6
6
|
import { pythonReachesNetwork } from './exec/adapters/python/net-shim.js';
|
|
7
7
|
import { createShellAdapter } from './exec/adapters/shell/shell-adapter.js';
|
|
8
8
|
import { routeObsidianCommand } from './exec/obsidian/route.js';
|
|
9
|
-
import { MAX_OUTPUT_CHARS_LIMIT } from './exec/runner.js';
|
|
9
|
+
import { MAX_OUTPUT_CHARS, MAX_OUTPUT_CHARS_LIMIT, TRUNCATED_OUTPUT_SUFFIX } from './exec/runner.js';
|
|
10
10
|
import { bareBin, firstCommandToken, sanitizeCommand } from './exec/scope.js';
|
|
11
11
|
import { FILES_NEED_PYTHON_ERROR, isPythonSourceShaped, PACKAGES_NEED_PYTHON_ERROR, pythonRedirectError, startsLikePython, videoToolError } from './exec/shell-guidance.js';
|
|
12
12
|
import { shellArgv } from './exec/shell-argv.js';
|
|
@@ -47,6 +47,30 @@ function shellEffect(command, toolOps) {
|
|
|
47
47
|
}
|
|
48
48
|
return matched.every((op) => op.annotation(argv) === 'read') ? 'read' : 'write';
|
|
49
49
|
}
|
|
50
|
+
function shellOutputCap(command, toolOps) {
|
|
51
|
+
const { ops, argv } = resolveShellOps(command, toolOps);
|
|
52
|
+
const caps = ops.map((op) => op.outputCap(argv)).filter((cap) => cap !== undefined);
|
|
53
|
+
return caps.length > 0 ? Math.min(...caps) : undefined;
|
|
54
|
+
}
|
|
55
|
+
function noteCeiling(result, ceiling) {
|
|
56
|
+
if (!result || typeof result !== 'object')
|
|
57
|
+
return result;
|
|
58
|
+
const out = { ...result };
|
|
59
|
+
for (const stream of ['stdout', 'stderr']) {
|
|
60
|
+
const text = out[stream];
|
|
61
|
+
if (typeof text === 'string' && text.endsWith(TRUNCATED_OUTPUT_SUFFIX)) {
|
|
62
|
+
out[stream] =
|
|
63
|
+
text.slice(0, -TRUNCATED_OUTPUT_SUFFIX.length) +
|
|
64
|
+
`… [truncated at this skill's ${ceiling}-character limit for this command; a larger maxOutputChars does not apply]`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
function cappedOutput(requested, ceiling) {
|
|
70
|
+
if (ceiling === undefined)
|
|
71
|
+
return requested;
|
|
72
|
+
return Math.min(requested ?? MAX_OUTPUT_CHARS, ceiling);
|
|
73
|
+
}
|
|
50
74
|
function resolveExecution({ adapters, fallback }, args, ctx) {
|
|
51
75
|
for (const adapter of adapters) {
|
|
52
76
|
const plan = adapter.resolve(args, ctx);
|
|
@@ -289,18 +313,33 @@ function checkExecutionScope(args, { toolOps, disablePyodide, sandbox, network }
|
|
|
289
313
|
}
|
|
290
314
|
return checkShellScope(command, toolOps);
|
|
291
315
|
}
|
|
292
|
-
async function skillsEnv(skillSettings, ctx) {
|
|
316
|
+
async function skillsEnv(skillSettings, skillEnv, ctx) {
|
|
293
317
|
const skills = ctx?.skills ?? [];
|
|
294
318
|
if (skills.length === 0) {
|
|
295
319
|
return undefined;
|
|
296
320
|
}
|
|
297
321
|
const env = {};
|
|
298
322
|
for (const skill of skills) {
|
|
299
|
-
|
|
323
|
+
if (skillSettings) {
|
|
324
|
+
Object.assign(env, settingsEnv(skill, await skillSettings(skill, ctx?.agentId)));
|
|
325
|
+
}
|
|
326
|
+
Object.assign(env, declaredEnv(skillEnv?.(skill), ctx?.session));
|
|
300
327
|
}
|
|
301
328
|
return Object.keys(env).length > 0 ? env : undefined;
|
|
302
329
|
}
|
|
303
|
-
export function
|
|
330
|
+
export function declaredEnv(declared, session) {
|
|
331
|
+
const env = {};
|
|
332
|
+
for (const [name, value] of Object.entries(declared ?? {})) {
|
|
333
|
+
if (!value.includes('${session}')) {
|
|
334
|
+
env[name] = value;
|
|
335
|
+
}
|
|
336
|
+
else if (session) {
|
|
337
|
+
env[name] = value.replaceAll('${session}', session);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return env;
|
|
341
|
+
}
|
|
342
|
+
export function createExecTool({ spawn, killProcess, loadPyodide, disablePyodide = false, pythonNetwork = !disablePyodide && pythonReachesNetwork(), credentialStore, skillSettings, skillEnv, realpath = defaultRealpath, deniedRoots, skillsDir } = {}) {
|
|
304
343
|
const adapters = [createPythonAdapter(disablePyodide, loadPyodide, pythonNetwork)];
|
|
305
344
|
const fallback = createShellAdapter({ spawn, killProcess, loadRuntime: bareRuntime });
|
|
306
345
|
return {
|
|
@@ -349,7 +388,7 @@ export function createExecTool({ spawn, killProcess, loadPyodide, disablePyodide
|
|
|
349
388
|
},
|
|
350
389
|
maxOutputChars: {
|
|
351
390
|
type: 'number',
|
|
352
|
-
description: 'Optional stdout/stderr cap in characters (default 8192, max 65536). Raise when reading long content, e.g. a full note.'
|
|
391
|
+
description: 'Optional stdout/stderr cap in characters (default 8192, max 65536). Raise when reading long content, e.g. a full note. A skill may cap one of its commands lower; the truncation note says so, and raising this does not lift that cap.'
|
|
353
392
|
},
|
|
354
393
|
script: {
|
|
355
394
|
type: 'string',
|
|
@@ -564,10 +603,12 @@ export function createExecTool({ spawn, killProcess, loadPyodide, disablePyodide
|
|
|
564
603
|
timeout = Math.min(timeout, remaining);
|
|
565
604
|
}
|
|
566
605
|
}
|
|
567
|
-
const
|
|
568
|
-
if (
|
|
569
|
-
return
|
|
606
|
+
const requestedCap = parsePositiveInt(rawArgs.maxOutputChars, 'maxOutputChars', MAX_OUTPUT_CHARS_LIMIT);
|
|
607
|
+
if (requestedCap && typeof requestedCap === 'object') {
|
|
608
|
+
return requestedCap;
|
|
570
609
|
}
|
|
610
|
+
const ceiling = shellOutputCap(command, ctx?.toolOps);
|
|
611
|
+
const maxOutputChars = cappedOutput(requestedCap, ceiling);
|
|
571
612
|
if (!isPythonLanguage(rawArgs.language)) {
|
|
572
613
|
const obsidian = await routeObsidianCommand(command, ctx, {
|
|
573
614
|
credentialStore,
|
|
@@ -581,7 +622,7 @@ export function createExecTool({ spawn, killProcess, loadPyodide, disablePyodide
|
|
|
581
622
|
return obsidian;
|
|
582
623
|
}
|
|
583
624
|
}
|
|
584
|
-
const
|
|
625
|
+
const childEnv = await skillsEnv(skillSettings, skillEnv, ctx);
|
|
585
626
|
try {
|
|
586
627
|
const execution = resolveExecution({ adapters, fallback }, rawArgs, {
|
|
587
628
|
attach: ctx?.attach,
|
|
@@ -593,13 +634,16 @@ export function createExecTool({ spawn, killProcess, loadPyodide, disablePyodide
|
|
|
593
634
|
approval: ctx?.approval,
|
|
594
635
|
confirmConnect: ctx?.askUser ? connectAsker(ctx.askUser) : undefined
|
|
595
636
|
});
|
|
596
|
-
|
|
637
|
+
const result = await execution.run({
|
|
597
638
|
cwd,
|
|
598
639
|
timeoutMs: timeout,
|
|
599
640
|
maxOutputChars,
|
|
600
641
|
signal: ctx?.signal,
|
|
601
|
-
env:
|
|
642
|
+
env: childEnv
|
|
602
643
|
});
|
|
644
|
+
return ceiling !== undefined && maxOutputChars === ceiling
|
|
645
|
+
? noteCeiling(result, ceiling)
|
|
646
|
+
: result;
|
|
603
647
|
}
|
|
604
648
|
catch (err) {
|
|
605
649
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -3,6 +3,7 @@ import type { ToolDefinition } from '../capabilities/completion.ts';
|
|
|
3
3
|
import type { ModelSrc } from '../models/context.ts';
|
|
4
4
|
import { type ImageClamps } from '../models/model-catalog.ts';
|
|
5
5
|
import { type LoadTracker, type TrackedLoad } from '../models/model-pool.ts';
|
|
6
|
+
import type { HoldFn } from '../models/download-gate.ts';
|
|
6
7
|
import { type ModelRegistrySdk } from '../models/model-source.ts';
|
|
7
8
|
export declare const MAX_DIM = 1024;
|
|
8
9
|
export declare const DIM_STEP = 64;
|
|
@@ -68,7 +69,8 @@ export interface DiffusionRuntimeOptions extends ImageModelSet {
|
|
|
68
69
|
loadSdk: () => Promise<DiffusionSdk>;
|
|
69
70
|
onProgress?: (model: string, progress: Record<string, unknown>) => void;
|
|
70
71
|
onLoaded?: (model: string) => void;
|
|
71
|
-
onBeforeLoad?: (signal
|
|
72
|
+
onBeforeLoad?: (signal: BareAbortSignal | undefined, srcs: ReadonlyArray<string>) => Promise<void>;
|
|
73
|
+
hold?: HoldFn;
|
|
72
74
|
loads?: LoadTracker;
|
|
73
75
|
stallMs?: number;
|
|
74
76
|
}
|
|
@@ -87,7 +89,7 @@ export interface DiffusionRuntime {
|
|
|
87
89
|
}
|
|
88
90
|
export declare function generationPercentTick(onPercent: (percentage: number) => void, encodePhases?: number): (tick: DiffusionTick) => void;
|
|
89
91
|
export declare const IMAGE_LOAD_STALL_ERROR = "image model download stalled";
|
|
90
|
-
export declare function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, loads, stallMs, ...base }: DiffusionRuntimeOptions): DiffusionRuntime;
|
|
92
|
+
export declare function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, hold, loads, stallMs, ...base }: DiffusionRuntimeOptions): DiffusionRuntime;
|
|
91
93
|
export declare const BUSY_ERROR = "an image generation is already running; try again when it finishes";
|
|
92
94
|
export interface ImageToolError {
|
|
93
95
|
error: string;
|
|
@@ -39,7 +39,7 @@ function diffusionProgressName(set) {
|
|
|
39
39
|
return entry?.name ?? set.model;
|
|
40
40
|
}
|
|
41
41
|
export const IMAGE_LOAD_STALL_ERROR = 'image model download stalled';
|
|
42
|
-
export function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, loads = createLoadTracker(), stallMs = IMAGE_LOAD_STALL_MS, ...base }) {
|
|
42
|
+
export function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, hold, loads = createLoadTracker(), stallMs = IMAGE_LOAD_STALL_MS, ...base }) {
|
|
43
43
|
let loading = null;
|
|
44
44
|
let residentFingerprint = null;
|
|
45
45
|
let busy = false;
|
|
@@ -76,13 +76,16 @@ export function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBefore
|
|
|
76
76
|
ensureLoaded: async (sdk, signal, modelChoice) => {
|
|
77
77
|
const set = (modelChoice && imageModelFor(modelChoice)) || base;
|
|
78
78
|
const fingerprint = fingerprintOf(set);
|
|
79
|
+
if (hold && !(loading && residentFingerprint === fingerprint)) {
|
|
80
|
+
await hold(diffusionProgressName(set), 'diffusion', signal);
|
|
81
|
+
}
|
|
79
82
|
await lock.lock();
|
|
80
83
|
try {
|
|
81
84
|
if (loading && residentFingerprint !== fingerprint) {
|
|
82
85
|
await doUnload();
|
|
83
86
|
}
|
|
84
87
|
if (!loading && onBeforeLoad) {
|
|
85
|
-
await onBeforeLoad(signal);
|
|
88
|
+
await onBeforeLoad(signal, [set.model, set.llmModel, set.vaeModel].filter((src) => src !== undefined));
|
|
86
89
|
}
|
|
87
90
|
residentFingerprint = fingerprint;
|
|
88
91
|
loading ??= (async () => {
|
|
@@ -2,6 +2,7 @@ import type { AbortSignal as BareAbortSignal } from 'bare-abort-controller';
|
|
|
2
2
|
import type { MusicSettings, ToolDefinition, ToolExecuteContext } from '../capabilities/completion.ts';
|
|
3
3
|
import type { ModelSrc } from '../models/context.ts';
|
|
4
4
|
import { type LoadTracker, type TrackedLoad } from '../models/model-pool.ts';
|
|
5
|
+
import type { HoldFn } from '../models/download-gate.ts';
|
|
5
6
|
import { type ModelRegistrySdk } from '../models/model-source.ts';
|
|
6
7
|
export declare const MAX_DURATION_SECONDS = 300;
|
|
7
8
|
export declare const MIN_DURATION_SECONDS = 1;
|
|
@@ -75,7 +76,8 @@ export interface MusicRuntimeOptions extends MusicModelStages {
|
|
|
75
76
|
inferenceSteps?: number;
|
|
76
77
|
useGpu?: boolean;
|
|
77
78
|
onProgress?: (model: string, progress: Record<string, unknown>) => void;
|
|
78
|
-
onBeforeLoad?: (signal
|
|
79
|
+
onBeforeLoad?: (signal: BareAbortSignal | undefined, srcs: ReadonlyArray<string>) => Promise<void>;
|
|
80
|
+
hold?: HoldFn;
|
|
79
81
|
loads?: LoadTracker;
|
|
80
82
|
}
|
|
81
83
|
export interface LoadedMusicModel {
|
|
@@ -97,7 +99,7 @@ export interface MusicRuntime {
|
|
|
97
99
|
release(): void;
|
|
98
100
|
}
|
|
99
101
|
export declare function musicPercentTick(onPercent: (percentage: number) => void): (tick: MusicTick) => void;
|
|
100
|
-
export declare function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, loads, inferenceSteps: baseSteps, useGpu, ...base }: MusicRuntimeOptions): MusicRuntime;
|
|
102
|
+
export declare function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, hold, loads, inferenceSteps: baseSteps, useGpu, ...base }: MusicRuntimeOptions): MusicRuntime;
|
|
101
103
|
export declare const BUSY_ERROR = "a music generation is already running; try again when it finishes";
|
|
102
104
|
export interface MusicToolError {
|
|
103
105
|
error: string;
|
|
@@ -49,7 +49,7 @@ function musicProgressName(dit) {
|
|
|
49
49
|
const entry = AUDIOGEN_CATALOG.find((candidate) => candidate.dit.src === dit || candidate.dit.name === dit);
|
|
50
50
|
return entry?.name ?? dit;
|
|
51
51
|
}
|
|
52
|
-
export function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, loads = createLoadTracker(), inferenceSteps: baseSteps, useGpu = true, ...base }) {
|
|
52
|
+
export function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, hold, loads = createLoadTracker(), inferenceSteps: baseSteps, useGpu = true, ...base }) {
|
|
53
53
|
let loading = null;
|
|
54
54
|
let residentFingerprint = null;
|
|
55
55
|
let busy = false;
|
|
@@ -89,13 +89,16 @@ export function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, loads =
|
|
|
89
89
|
const stages = stagesFor(modelChoice, base);
|
|
90
90
|
const effectiveSteps = steps ?? baseSteps;
|
|
91
91
|
const fingerprint = fingerprintOf(stages, effectiveSteps);
|
|
92
|
+
if (hold && !(loading && residentFingerprint === fingerprint)) {
|
|
93
|
+
await hold(musicProgressName(stages.dit), 'audiogen', signal);
|
|
94
|
+
}
|
|
92
95
|
await lock.lock();
|
|
93
96
|
try {
|
|
94
97
|
if (loading && residentFingerprint !== fingerprint) {
|
|
95
98
|
await doUnload();
|
|
96
99
|
}
|
|
97
100
|
if (!loading && onBeforeLoad) {
|
|
98
|
-
await onBeforeLoad(signal);
|
|
101
|
+
await onBeforeLoad(signal, [stages.dit, stages.textEnc, stages.lm, stages.vae]);
|
|
99
102
|
}
|
|
100
103
|
residentFingerprint = fingerprint;
|
|
101
104
|
loading ??= (async () => {
|