@qvac/harness 0.8.6 → 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 +1259 -507
- package/dist-lib/index.d.ts +11 -0
- package/dist-lib/index.js +102 -25
- package/dist-lib/lib/capabilities/completion.d.ts +4 -4
- package/dist-lib/lib/capabilities/completion.js +86 -57
- 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/connect.js +2 -0
- package/dist-lib/lib/contract-types.d.ts +2 -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/model-pool.d.ts +3 -1
- package/dist-lib/lib/models/model-pool.js +8 -11
- 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 +5 -1
- 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/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/schema/harness.js +2 -1
- package/dist-lib/spec/hrpc/messages.js +10 -5
- package/dist-lib/spec/hyperschema/index.js +10 -5
- package/dist-lib/spec/hyperschema/schema.json +6 -1
- package/dist-lib/spec/hyperschema/types.d.ts +2 -1
- package/package.json +5 -7
- package/worker/harness-sidecar.mjs +2 -1
|
@@ -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));
|
|
@@ -7,7 +7,7 @@ export const PPTX_OPS = {
|
|
|
7
7
|
setText: { slots: [['slide', 'shape'], 'text'] },
|
|
8
8
|
setCellText: { slots: [['slide', 'shape', 'row', 'column'], 'text'] },
|
|
9
9
|
setStyle: { slots: [['slide', 'shape'], 'style', ['paragraph']] },
|
|
10
|
-
addSlide: { slots: [['like', 'layout', 'at', 'title', 'body']] },
|
|
10
|
+
addSlide: { slots: [['like', 'layout', 'at', 'title', 'body', 'image']] },
|
|
11
11
|
addPicture: { slots: ['slide', 'image', [...BOX, 'name', 'description']], inches: true },
|
|
12
12
|
addTextBox: { slots: ['slide', [...BOX, 'text', 'style', 'fill', 'name']], inches: true },
|
|
13
13
|
addTable: { slots: ['slide', [...BOX, 'rows', 'header', 'name']], inches: true },
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import b4a from 'b4a';
|
|
2
2
|
import { errorMessage, isRecord } from '../guards.js';
|
|
3
|
-
import { docx, pptx, units, xlsx } from '../office/engine/index.js';
|
|
3
|
+
import { DocsError, docx, pptx, units, xlsx } from '../office/engine/index.js';
|
|
4
4
|
import { stageInputs } from './exec/adapters/python/attachments.js';
|
|
5
5
|
import { checkOutputBytes, RUN_OUTPUT_BUDGET_BYTES } from './exec/adapters/python/output-policy.js';
|
|
6
6
|
import { DOCX_OPS, opArguments, opKeys, PPTX_OPS, withAliases, XLSX_OPS } from './office/ops.js';
|
|
7
7
|
const READ_CELL_LIMIT = 10_000;
|
|
8
8
|
const attachedThisTurn = new WeakMap();
|
|
9
9
|
const FIRST_SHEET = /^sheet ?1$/i;
|
|
10
|
+
const NEW_DECK_HINT = '; a new deck has only the slides its addSlide ops make, slides count from 0, and an op on a slide goes after the addSlide that makes it';
|
|
10
11
|
const SHAPE_ROLES = new Set(['title', 'subtitle', 'body']);
|
|
11
12
|
const HOISTED = ['attachmentId', 'output'];
|
|
12
13
|
const WHERE_IDS_ARE = 'copy the id from the [Attached file "…" — attachmentId: …] line or an earlier tool result';
|
|
@@ -25,6 +26,9 @@ export function shapeFor(deck, op) {
|
|
|
25
26
|
const found = deck.read()[slide]?.shapes.find((entry) => entry.name.toLowerCase() === wanted);
|
|
26
27
|
return found && found.id !== null ? { ...op, shape: found.id } : op;
|
|
27
28
|
}
|
|
29
|
+
function newDeckHint(err, created) {
|
|
30
|
+
return created && err instanceof DocsError && err.code === 'SLIDE_NOT_FOUND' ? NEW_DECK_HINT : '';
|
|
31
|
+
}
|
|
28
32
|
export function sheetFor(book, op, created) {
|
|
29
33
|
if (typeof op.sheet !== 'string')
|
|
30
34
|
return op;
|
|
@@ -70,7 +74,8 @@ const PPTX_FORMAT = {
|
|
|
70
74
|
create: (options) => pptx.create(options),
|
|
71
75
|
read: (deck) => deck.read(),
|
|
72
76
|
count: (deck) => ({ slides: deck.slideCount }),
|
|
73
|
-
prepare: (deck, op) => shapeFor(deck, op)
|
|
77
|
+
prepare: (deck, op) => shapeFor(deck, op),
|
|
78
|
+
hint: newDeckHint
|
|
74
79
|
};
|
|
75
80
|
const DOCX_FORMAT = {
|
|
76
81
|
name: 'docx',
|
|
@@ -141,7 +146,11 @@ function parseOp(format, op, i) {
|
|
|
141
146
|
return `ops[${i}] sheets lists the sheets and takes no arguments; to add a sheet use { op: "addSheet", name }`;
|
|
142
147
|
}
|
|
143
148
|
const takes = keys.length ? `it takes ${keys.join(', ')}` : 'it takes no arguments';
|
|
144
|
-
|
|
149
|
+
const nested = unknown.filter((key) => format.ops[key]);
|
|
150
|
+
const apart = nested.length
|
|
151
|
+
? `; ${nested.map((name) => `{ "op": "${name}", ... }`).join(' and ')} goes in ops as its own entry, next to this one, not inside it`
|
|
152
|
+
: '';
|
|
153
|
+
return `ops[${i}] ${op.op} does not take ${unknown.join(', ')}; ${takes}${apart}`;
|
|
145
154
|
}
|
|
146
155
|
function parseOps(format, ops) {
|
|
147
156
|
if (!Array.isArray(ops)) {
|
|
@@ -252,8 +261,11 @@ async function imageInput(op, i, ctx) {
|
|
|
252
261
|
}
|
|
253
262
|
return { attachmentId: id ? await imageId(ctx, id) : undefined, path: `image-${i}.png` };
|
|
254
263
|
}
|
|
264
|
+
function takesImage(op) {
|
|
265
|
+
return op.spec.slots.includes('image') || op.args.image !== undefined;
|
|
266
|
+
}
|
|
255
267
|
async function stagedImages(request, ctx) {
|
|
256
|
-
const wanted = request.ops.flatMap((op, i) => (
|
|
268
|
+
const wanted = request.ops.flatMap((op, i) => (takesImage(op) ? [i] : []));
|
|
257
269
|
const images = new Map();
|
|
258
270
|
if (!wanted.length) {
|
|
259
271
|
return images;
|
|
@@ -358,7 +370,8 @@ function runOps({ format, document, request, images }) {
|
|
|
358
370
|
results.push(runOp(document, op, input));
|
|
359
371
|
}
|
|
360
372
|
catch (err) {
|
|
361
|
-
|
|
373
|
+
const hint = format.hint?.(err, created) ?? '';
|
|
374
|
+
return { error: `ops[${i}] ${op.name}: ${errorMessage(err)}${hint}` };
|
|
362
375
|
}
|
|
363
376
|
}
|
|
364
377
|
return results;
|
|
@@ -452,6 +465,7 @@ async function execute(format, args, ctx) {
|
|
|
452
465
|
const renamed = output !== request.output
|
|
453
466
|
? `Saved as ${output}, so the attached ${fileName} keeps its own name. `
|
|
454
467
|
: '';
|
|
468
|
+
const unplaced = !!ctx.turnImages?.length && !images.size;
|
|
455
469
|
return {
|
|
456
470
|
results,
|
|
457
471
|
attachments: [attachment],
|
|
@@ -459,9 +473,18 @@ async function execute(format, args, ctx) {
|
|
|
459
473
|
reply: `${attachment.fileName} — ${countLine(counts)}`,
|
|
460
474
|
next: renamed +
|
|
461
475
|
`Done: ${attachment.fileName} ${repeated ? 'was already' : 'is'} attached to the chat and shown to the user. ` +
|
|
462
|
-
|
|
476
|
+
afterSave(format, attachment.attachmentId, unplaced)
|
|
463
477
|
};
|
|
464
478
|
}
|
|
479
|
+
function afterSave(format, attachmentId, unplaced) {
|
|
480
|
+
const reply = 'Your answer is the reply value alone, word for word.';
|
|
481
|
+
if (!unplaced) {
|
|
482
|
+
return `Do not call ${format.name} again for it. ${reply}`;
|
|
483
|
+
}
|
|
484
|
+
return (`The image the user attached to their message is not in it. If they asked for that image in the ${format.noun}, ` +
|
|
485
|
+
`call ${format.name} once more with attachmentId "${attachmentId}" and one op that adds it with "image": {}. ` +
|
|
486
|
+
`Otherwise: ${reply}`);
|
|
487
|
+
}
|
|
465
488
|
function createOfficeTool(format) {
|
|
466
489
|
return {
|
|
467
490
|
name: format.name,
|
|
@@ -82,6 +82,7 @@ function mergeOverlay(base, overlay) {
|
|
|
82
82
|
credentials: base.credentials,
|
|
83
83
|
allowList: base.allowList,
|
|
84
84
|
platform: base.platform,
|
|
85
|
+
env: base.env,
|
|
85
86
|
operations: base.operations,
|
|
86
87
|
mcpReads: base.mcpReads
|
|
87
88
|
};
|
|
@@ -166,6 +167,7 @@ async function loadEntry(skillsDir, dir, origin) {
|
|
|
166
167
|
requires: meta.requires,
|
|
167
168
|
install: meta.install,
|
|
168
169
|
setup: meta.setup,
|
|
170
|
+
env: origin === 'overlay' ? undefined : meta.env,
|
|
169
171
|
cliValidator: cli ? buildCliValidator(cli) : undefined,
|
|
170
172
|
stub: meta.stub,
|
|
171
173
|
minCapabilityProfile: meta.minCapabilityProfile || undefined,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { JsonSchema } from './cli-schema-types.ts';
|
|
2
2
|
export declare function buildSchemaAnnotation(schema: JsonSchema): (argv: readonly string[]) => 'read' | 'write';
|
|
3
|
+
export declare function buildSchemaOutputCap(schema: JsonSchema): (argv: readonly string[]) => number | undefined;
|
|
3
4
|
export declare function buildSchemaCheck(schema: JsonSchema, resourcePaths: readonly string[] | undefined): (argv: readonly string[]) => string | null;
|
|
@@ -13,6 +13,9 @@ export function buildSchemaAnnotation(schema) {
|
|
|
13
13
|
return walkCommands(argv, schema).node['x-effect'] === 'read' ? 'read' : 'write';
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
+
export function buildSchemaOutputCap(schema) {
|
|
17
|
+
return (argv) => walkCommands(argv, schema).node['x-max-output-chars'];
|
|
18
|
+
}
|
|
16
19
|
function basename(p) {
|
|
17
20
|
return p.slice(Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\')) + 1);
|
|
18
21
|
}
|
|
@@ -28,6 +28,9 @@ export async function resolveCliSchema(skillDir, bin, readFile = (p) => fs.promi
|
|
|
28
28
|
if (!resourceEffectsValid(schema)) {
|
|
29
29
|
return { bin, schemaFile: CLI_SCHEMA_FILENAME };
|
|
30
30
|
}
|
|
31
|
+
if (!outputCapsValid(schema)) {
|
|
32
|
+
return { bin, schemaFile: CLI_SCHEMA_FILENAME };
|
|
33
|
+
}
|
|
31
34
|
const resourcePaths = resolveResourcePaths(skillDir, schema);
|
|
32
35
|
return {
|
|
33
36
|
bin,
|
|
@@ -63,6 +66,13 @@ function resourceEffectsValid(schema) {
|
|
|
63
66
|
const files = new Set(schema['x-resource-files'] ?? []);
|
|
64
67
|
return Object.entries(effects).every(([file, effect]) => effect === 'read' && files.has(file));
|
|
65
68
|
}
|
|
69
|
+
function outputCapsValid(schema) {
|
|
70
|
+
const cap = schema['x-max-output-chars'];
|
|
71
|
+
if (cap !== undefined && !(Number.isInteger(cap) && cap > 0)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return Object.values(schema.properties ?? {}).every(outputCapsValid);
|
|
75
|
+
}
|
|
66
76
|
function resolveResourcePaths(skillDir, schema) {
|
|
67
77
|
const resources = schema['x-resource-files'] ?? [];
|
|
68
78
|
return resources
|
|
@@ -13,6 +13,7 @@ export interface JsonSchema {
|
|
|
13
13
|
readonly 'x-resource-files'?: readonly string[];
|
|
14
14
|
readonly 'x-resource-effects'?: Readonly<Record<string, string>>;
|
|
15
15
|
readonly 'x-effect'?: string;
|
|
16
|
+
readonly 'x-max-output-chars'?: number;
|
|
16
17
|
readonly [key: string]: unknown;
|
|
17
18
|
}
|
|
18
19
|
export interface SkillCli {
|
|
@@ -27,4 +28,5 @@ export interface CliValidator {
|
|
|
27
28
|
readonly check: (argv: readonly string[]) => string | null;
|
|
28
29
|
readonly annotation: (argv: readonly string[]) => 'read' | 'write';
|
|
29
30
|
readonly effect: (argv: readonly string[]) => 'read' | 'write';
|
|
31
|
+
readonly outputCap: (argv: readonly string[]) => number | undefined;
|
|
30
32
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { CLI_SCHEMA_FILENAME } from './cli-schema-types.js';
|
|
2
2
|
export { argvToObject } from './cli-argv.js';
|
|
3
3
|
export { loadSkillCli, resolveCliSchema, execBinFromTools } from './cli-schema-load.js';
|
|
4
|
-
import { buildSchemaAnnotation, buildSchemaCheck } from './cli-schema-check.js';
|
|
4
|
+
import { buildSchemaAnnotation, buildSchemaCheck, buildSchemaOutputCap } from './cli-schema-check.js';
|
|
5
5
|
export function buildCliValidator(def) {
|
|
6
6
|
const bin = def.bin.toLowerCase();
|
|
7
7
|
if (!def.schema) {
|
|
@@ -9,16 +9,19 @@ export function buildCliValidator(def) {
|
|
|
9
9
|
bin,
|
|
10
10
|
check: () => 'skill cli schema is missing',
|
|
11
11
|
annotation: () => 'write',
|
|
12
|
-
effect: () => 'write'
|
|
12
|
+
effect: () => 'write',
|
|
13
|
+
outputCap: () => undefined
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
const check = buildSchemaCheck(def.schema, def.resourcePaths);
|
|
16
17
|
const annotation = buildSchemaAnnotation(def.schema);
|
|
18
|
+
const outputCap = buildSchemaOutputCap(def.schema);
|
|
17
19
|
return {
|
|
18
20
|
bin,
|
|
19
21
|
check,
|
|
20
22
|
annotation,
|
|
21
|
-
effect: (argv) => (check(argv) === null ? annotation(argv) : 'write')
|
|
23
|
+
effect: (argv) => (check(argv) === null ? annotation(argv) : 'write'),
|
|
24
|
+
outputCap: (argv) => (check(argv) === null ? outputCap(argv) : undefined)
|
|
22
25
|
};
|
|
23
26
|
}
|
|
24
27
|
export function checkCliOps(argv, compiled) {
|
|
@@ -12,11 +12,23 @@ export function parseManifestBlock(block, key = 'metadata') {
|
|
|
12
12
|
requires,
|
|
13
13
|
install,
|
|
14
14
|
setup,
|
|
15
|
+
env: readEnv(oc.env),
|
|
15
16
|
stub: typeof oc.stub === 'boolean' ? oc.stub : undefined,
|
|
16
17
|
minCapabilityProfile: typeof oc.minCapabilityProfile === 'string' ? oc.minCapabilityProfile : undefined,
|
|
17
18
|
emoji: typeof oc.emoji === 'string' ? oc.emoji : undefined
|
|
18
19
|
};
|
|
19
20
|
}
|
|
21
|
+
function readEnv(v) {
|
|
22
|
+
if (!isObj(v))
|
|
23
|
+
return undefined;
|
|
24
|
+
const env = {};
|
|
25
|
+
for (const [name, value] of Object.entries(v)) {
|
|
26
|
+
if (typeof value === 'string' && /^[A-Z_][A-Z0-9_]*$/.test(name)) {
|
|
27
|
+
env[name] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return Object.keys(env).length > 0 ? env : undefined;
|
|
31
|
+
}
|
|
20
32
|
function extractJsonBlock(block, key) {
|
|
21
33
|
const at = block.indexOf(`${key}:`);
|
|
22
34
|
if (at === -1) {
|
|
@@ -3,4 +3,3 @@ import { skillMentionName } from './mention-name.ts';
|
|
|
3
3
|
export { skillMentionName };
|
|
4
4
|
export declare function resolveSkillMention(text: string, catalog: readonly SkillCatalogEntry[]): SkillCatalogEntry | null;
|
|
5
5
|
export declare function resolveImplicitSkill(text: string, activeSkills: readonly SkillCatalogEntry[]): SkillCatalogEntry | null;
|
|
6
|
-
export declare function skillPreloadMessage(content: string, name: string, body: string): string;
|
|
@@ -113,7 +113,8 @@ export function registerHarnessTypes(ns) {
|
|
|
113
113
|
{ name: 'captureInferenceCalls', type: 'bool' },
|
|
114
114
|
{ name: 'capability', type: 'string' },
|
|
115
115
|
{ name: 'arguments', type: 'json' },
|
|
116
|
-
{ name: 'enhance', type: 'string' }
|
|
116
|
+
{ name: 'enhance', type: 'string' },
|
|
117
|
+
{ name: 'session', type: 'string' }
|
|
117
118
|
]
|
|
118
119
|
});
|
|
119
120
|
ns.register({
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// This file is autogenerated by the hyperschema compiler
|
|
2
|
-
// Schema Version:
|
|
2
|
+
// Schema Version: 20
|
|
3
3
|
/* eslint-disable camelcase */
|
|
4
4
|
/* eslint-disable quotes */
|
|
5
5
|
/* eslint-disable space-before-function-paren */
|
|
6
6
|
|
|
7
7
|
import { c } from 'hyperschema/runtime'
|
|
8
8
|
|
|
9
|
-
const VERSION =
|
|
9
|
+
const VERSION = 20
|
|
10
10
|
|
|
11
11
|
// eslint-disable-next-line no-unused-vars
|
|
12
12
|
let version = VERSION
|
|
@@ -491,7 +491,8 @@ const encoding11 = {
|
|
|
491
491
|
((version >= 9 && m.captureInferenceCalls) ? 33554432 : 0) |
|
|
492
492
|
((version >= 11 && m.capability) ? 67108864 : 0) |
|
|
493
493
|
((version >= 11 && m.arguments) ? 134217728 : 0) |
|
|
494
|
-
((version >= 12 && m.enhance) ? 268435456 : 0)
|
|
494
|
+
((version >= 12 && m.enhance) ? 268435456 : 0) |
|
|
495
|
+
((version >= 20 && m.session) ? 536870912 : 0)
|
|
495
496
|
|
|
496
497
|
encoding10.preencode(state, m.type)
|
|
497
498
|
c.uint.preencode(state, flags)
|
|
@@ -522,6 +523,7 @@ const encoding11 = {
|
|
|
522
523
|
if (version >= 11 && m.capability) c.string.preencode(state, m.capability)
|
|
523
524
|
if (version >= 11 && m.arguments) c.json.preencode(state, m.arguments)
|
|
524
525
|
if (version >= 12 && m.enhance) c.string.preencode(state, m.enhance)
|
|
526
|
+
if (version >= 20 && m.session) c.string.preencode(state, m.session)
|
|
525
527
|
},
|
|
526
528
|
encode(state, m) {
|
|
527
529
|
const flags =
|
|
@@ -553,7 +555,8 @@ const encoding11 = {
|
|
|
553
555
|
((version >= 9 && m.captureInferenceCalls) ? 33554432 : 0) |
|
|
554
556
|
((version >= 11 && m.capability) ? 67108864 : 0) |
|
|
555
557
|
((version >= 11 && m.arguments) ? 134217728 : 0) |
|
|
556
|
-
((version >= 12 && m.enhance) ? 268435456 : 0)
|
|
558
|
+
((version >= 12 && m.enhance) ? 268435456 : 0) |
|
|
559
|
+
((version >= 20 && m.session) ? 536870912 : 0)
|
|
557
560
|
|
|
558
561
|
encoding10.encode(state, m.type)
|
|
559
562
|
c.uint.encode(state, flags)
|
|
@@ -584,6 +587,7 @@ const encoding11 = {
|
|
|
584
587
|
if (version >= 11 && m.capability) c.string.encode(state, m.capability)
|
|
585
588
|
if (version >= 11 && m.arguments) c.json.encode(state, m.arguments)
|
|
586
589
|
if (version >= 12 && m.enhance) c.string.encode(state, m.enhance)
|
|
590
|
+
if (version >= 20 && m.session) c.string.encode(state, m.session)
|
|
587
591
|
},
|
|
588
592
|
decode(state) {
|
|
589
593
|
const r0 = encoding10.decode(state)
|
|
@@ -619,7 +623,8 @@ const encoding11 = {
|
|
|
619
623
|
captureInferenceCalls: (version >= 9 && (flags & 33554432) !== 0),
|
|
620
624
|
capability: (version >= 11 && (flags & 67108864) !== 0) ? c.string.decode(state) : null,
|
|
621
625
|
arguments: (version >= 11 && (flags & 134217728) !== 0) ? c.json.decode(state) : null,
|
|
622
|
-
enhance: (version >= 12 && (flags & 268435456) !== 0) ? c.string.decode(state) : null
|
|
626
|
+
enhance: (version >= 12 && (flags & 268435456) !== 0) ? c.string.decode(state) : null,
|
|
627
|
+
session: (version >= 20 && (flags & 536870912) !== 0) ? c.string.decode(state) : null
|
|
623
628
|
}
|
|
624
629
|
}
|
|
625
630
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// This file is autogenerated by the hyperschema compiler
|
|
2
|
-
// Schema Version:
|
|
2
|
+
// Schema Version: 20
|
|
3
3
|
/* eslint-disable camelcase */
|
|
4
4
|
/* eslint-disable quotes */
|
|
5
5
|
/* eslint-disable space-before-function-paren */
|
|
6
6
|
|
|
7
7
|
import { c } from 'hyperschema/runtime'
|
|
8
8
|
|
|
9
|
-
const VERSION =
|
|
9
|
+
const VERSION = 20
|
|
10
10
|
|
|
11
11
|
// eslint-disable-next-line no-unused-vars
|
|
12
12
|
let version = VERSION
|
|
@@ -491,7 +491,8 @@ const encoding11 = {
|
|
|
491
491
|
((version >= 9 && m.captureInferenceCalls) ? 33554432 : 0) |
|
|
492
492
|
((version >= 11 && m.capability) ? 67108864 : 0) |
|
|
493
493
|
((version >= 11 && m.arguments) ? 134217728 : 0) |
|
|
494
|
-
((version >= 12 && m.enhance) ? 268435456 : 0)
|
|
494
|
+
((version >= 12 && m.enhance) ? 268435456 : 0) |
|
|
495
|
+
((version >= 20 && m.session) ? 536870912 : 0)
|
|
495
496
|
|
|
496
497
|
encoding10.preencode(state, m.type)
|
|
497
498
|
c.uint.preencode(state, flags)
|
|
@@ -522,6 +523,7 @@ const encoding11 = {
|
|
|
522
523
|
if (version >= 11 && m.capability) c.string.preencode(state, m.capability)
|
|
523
524
|
if (version >= 11 && m.arguments) c.json.preencode(state, m.arguments)
|
|
524
525
|
if (version >= 12 && m.enhance) c.string.preencode(state, m.enhance)
|
|
526
|
+
if (version >= 20 && m.session) c.string.preencode(state, m.session)
|
|
525
527
|
},
|
|
526
528
|
encode(state, m) {
|
|
527
529
|
const flags =
|
|
@@ -553,7 +555,8 @@ const encoding11 = {
|
|
|
553
555
|
((version >= 9 && m.captureInferenceCalls) ? 33554432 : 0) |
|
|
554
556
|
((version >= 11 && m.capability) ? 67108864 : 0) |
|
|
555
557
|
((version >= 11 && m.arguments) ? 134217728 : 0) |
|
|
556
|
-
((version >= 12 && m.enhance) ? 268435456 : 0)
|
|
558
|
+
((version >= 12 && m.enhance) ? 268435456 : 0) |
|
|
559
|
+
((version >= 20 && m.session) ? 536870912 : 0)
|
|
557
560
|
|
|
558
561
|
encoding10.encode(state, m.type)
|
|
559
562
|
c.uint.encode(state, flags)
|
|
@@ -584,6 +587,7 @@ const encoding11 = {
|
|
|
584
587
|
if (version >= 11 && m.capability) c.string.encode(state, m.capability)
|
|
585
588
|
if (version >= 11 && m.arguments) c.json.encode(state, m.arguments)
|
|
586
589
|
if (version >= 12 && m.enhance) c.string.encode(state, m.enhance)
|
|
590
|
+
if (version >= 20 && m.session) c.string.encode(state, m.session)
|
|
587
591
|
},
|
|
588
592
|
decode(state) {
|
|
589
593
|
const r0 = encoding10.decode(state)
|
|
@@ -619,7 +623,8 @@ const encoding11 = {
|
|
|
619
623
|
captureInferenceCalls: (version >= 9 && (flags & 33554432) !== 0),
|
|
620
624
|
capability: (version >= 11 && (flags & 67108864) !== 0) ? c.string.decode(state) : null,
|
|
621
625
|
arguments: (version >= 11 && (flags & 134217728) !== 0) ? c.json.decode(state) : null,
|
|
622
|
-
enhance: (version >= 12 && (flags & 268435456) !== 0) ? c.string.decode(state) : null
|
|
626
|
+
enhance: (version >= 12 && (flags & 268435456) !== 0) ? c.string.decode(state) : null,
|
|
627
|
+
session: (version >= 20 && (flags & 536870912) !== 0) ? c.string.decode(state) : null
|
|
623
628
|
}
|
|
624
629
|
}
|
|
625
630
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version":
|
|
2
|
+
"version": 20,
|
|
3
3
|
"schema": [
|
|
4
4
|
{
|
|
5
5
|
"name": "empty",
|
|
@@ -475,6 +475,11 @@
|
|
|
475
475
|
"name": "enhance",
|
|
476
476
|
"type": "string",
|
|
477
477
|
"version": 12
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
"name": "session",
|
|
481
|
+
"type": "string",
|
|
482
|
+
"version": 20
|
|
478
483
|
}
|
|
479
484
|
]
|
|
480
485
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// This file is autogenerated by hyperschema-ts
|
|
2
|
-
// Schema version:
|
|
2
|
+
// Schema version: 20
|
|
3
3
|
// Do not edit manually.
|
|
4
4
|
|
|
5
5
|
export interface HarnessEmpty {
|
|
@@ -91,6 +91,7 @@ export interface HarnessCompletionFrame {
|
|
|
91
91
|
capability?: string | null
|
|
92
92
|
arguments?: unknown | null
|
|
93
93
|
enhance?: string | null
|
|
94
|
+
session?: string | null
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
export type HarnessCompletionEventType = "content" | "thinking" | "tool-call" | "tool-result" | "approval-ask" | "metrics" | "error" | "aborted" | "attach-ask" | "fetch-ask" | "progress" | "rag-ask" | "question-ask" | "done" | "tool-activity" | "drive-ask" | "inference-call" | "inference-sent"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qvac/harness",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"bare": ">=1.34.0"
|
|
@@ -747,7 +747,7 @@
|
|
|
747
747
|
"@qvac/inference": "0.21.0",
|
|
748
748
|
"@qvac/langdetect-text": "0.1.2",
|
|
749
749
|
"@qvac/sdk": "0.21.0",
|
|
750
|
-
"@qvac/skills": "0.1.
|
|
750
|
+
"@qvac/skills": "0.1.14",
|
|
751
751
|
"@xmldom/xmldom": "^0.9.12",
|
|
752
752
|
"asenion": "^0.1.0",
|
|
753
753
|
"b4a": "^1.9.0",
|
|
@@ -773,7 +773,7 @@
|
|
|
773
773
|
"bare-process": "4.4.1",
|
|
774
774
|
"bare-sidecar": "0.5.7",
|
|
775
775
|
"bare-storage": "^1.1.0",
|
|
776
|
-
"bare-stow": "^0.1.
|
|
776
|
+
"bare-stow": "^0.1.7",
|
|
777
777
|
"bare-stream": "^2.13.3",
|
|
778
778
|
"bare-subprocess": "^6.1.0",
|
|
779
779
|
"bare-supervisor": "^0.1.1",
|
|
@@ -809,8 +809,8 @@
|
|
|
809
809
|
"holepunch-types": "^0.2.1",
|
|
810
810
|
"hyperdrive": "^13.3.3",
|
|
811
811
|
"jsdom": "~30.0.1",
|
|
812
|
-
"lunte": "^1.8.
|
|
813
|
-
"lunte-plugin-qvac": "^0.
|
|
812
|
+
"lunte": "^1.8.6",
|
|
813
|
+
"lunte-plugin-qvac": "^0.2.0",
|
|
814
814
|
"mermaid": "~11.16.1",
|
|
815
815
|
"prettier": "^3.6.2",
|
|
816
816
|
"prettier-config-holepunch": "^2.0.0",
|
|
@@ -819,8 +819,6 @@
|
|
|
819
819
|
"patchedDependencies": {
|
|
820
820
|
"pyodide@314.0.2": "patches/pyodide@314.0.2.patch",
|
|
821
821
|
"localwatch@1.4.3": "patches/localwatch@1.4.3.patch",
|
|
822
|
-
"hypercore-blob-server@1.15.0": "patches/hypercore-blob-server@1.15.0.patch",
|
|
823
|
-
"bare-stow@0.1.5": "patches/bare-stow@0.1.5.patch",
|
|
824
822
|
"@qvac/inference@0.21.0": "patches/@qvac%2Finference@0.21.0.patch"
|
|
825
823
|
},
|
|
826
824
|
"repository": {
|
|
@@ -124,7 +124,8 @@ export default async function start(ipc, ready) {
|
|
|
124
124
|
...credentialed,
|
|
125
125
|
deniedRoots,
|
|
126
126
|
skillsDir,
|
|
127
|
-
skillSettings: (skill, agentId) => harness?.skillSettings(skill, agentId) ?? {}
|
|
127
|
+
skillSettings: (skill, agentId) => harness?.skillSettings(skill, agentId) ?? {},
|
|
128
|
+
skillEnv: (skill) => skillCatalog?.find((entry) => entry.name === skill)?.env
|
|
128
129
|
}),
|
|
129
130
|
mcpCallTool,
|
|
130
131
|
createWebSearchTool(),
|