@qvac/harness 0.8.6 → 0.8.8

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 (61) hide show
  1. package/CHANGELOG.md +1298 -507
  2. package/dist-lib/index.d.ts +11 -0
  3. package/dist-lib/index.js +102 -25
  4. package/dist-lib/lib/capabilities/completion.d.ts +5 -4
  5. package/dist-lib/lib/capabilities/completion.js +132 -64
  6. package/dist-lib/lib/capabilities/tool-selection.d.ts +1 -3
  7. package/dist-lib/lib/capabilities/tool-selection.js +2 -4
  8. package/dist-lib/lib/capabilities/tool-turns.d.ts +0 -1
  9. package/dist-lib/lib/capabilities/tool-turns.js +0 -6
  10. package/dist-lib/lib/connect.js +3 -0
  11. package/dist-lib/lib/contract-types.d.ts +2 -0
  12. package/dist-lib/lib/helpers/tool-call-markup.d.ts +1 -0
  13. package/dist-lib/lib/helpers/tool-call-markup.js +3 -0
  14. package/dist-lib/lib/models/context-choice.d.ts +15 -0
  15. package/dist-lib/lib/models/context-choice.js +55 -0
  16. package/dist-lib/lib/models/model-pool.d.ts +3 -1
  17. package/dist-lib/lib/models/model-pool.js +8 -11
  18. package/dist-lib/lib/models/sdk-faults.d.ts +1 -0
  19. package/dist-lib/lib/models/sdk-faults.js +6 -0
  20. package/dist-lib/lib/office/engine/pptx/picture-area.d.ts +23 -0
  21. package/dist-lib/lib/office/engine/pptx/picture-area.js +84 -0
  22. package/dist-lib/lib/office/engine/pptx/presentation.d.ts +2 -0
  23. package/dist-lib/lib/office/engine/pptx/presentation.js +55 -17
  24. package/dist-lib/lib/office/engine/pptx/types.d.ts +1 -0
  25. package/dist-lib/lib/office/engine/xlsx/workbook.d.ts +1 -0
  26. package/dist-lib/lib/office/engine/xlsx/workbook.js +34 -3
  27. package/dist-lib/lib/serve.js +6 -1
  28. package/dist-lib/lib/spawn.js +4 -10
  29. package/dist-lib/lib/tools/exec.d.ts +4 -1
  30. package/dist-lib/lib/tools/exec.js +55 -11
  31. package/dist-lib/lib/tools/file-glob.js +1 -0
  32. package/dist-lib/lib/tools/grep.js +1 -0
  33. package/dist-lib/lib/tools/office/ops.js +1 -1
  34. package/dist-lib/lib/tools/office.js +29 -6
  35. package/dist-lib/lib/tools/read-files.js +1 -0
  36. package/dist-lib/lib/tools/skill.js +6 -4
  37. package/dist-lib/lib/tools/skills/catalog.d.ts +2 -0
  38. package/dist-lib/lib/tools/skills/catalog.js +4 -0
  39. package/dist-lib/lib/tools/skills/cli-schema-check.d.ts +1 -0
  40. package/dist-lib/lib/tools/skills/cli-schema-check.js +3 -0
  41. package/dist-lib/lib/tools/skills/cli-schema-load.js +10 -0
  42. package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +2 -0
  43. package/dist-lib/lib/tools/skills/cli-schema.js +6 -3
  44. package/dist-lib/lib/tools/skills/frontmatter-parser.d.ts +2 -0
  45. package/dist-lib/lib/tools/skills/frontmatter-parser.js +4 -0
  46. package/dist-lib/lib/tools/skills/manifest-parse.d.ts +1 -0
  47. package/dist-lib/lib/tools/skills/manifest-parse.js +12 -0
  48. package/dist-lib/lib/tools/skills/mention-name.d.ts +6 -0
  49. package/dist-lib/lib/tools/skills/mention-name.js +6 -0
  50. package/dist-lib/lib/tools/skills/mention.d.ts +2 -3
  51. package/dist-lib/lib/tools/skills/mention.js +5 -6
  52. package/dist-lib/lib/tools/weather-lookup.js +1 -0
  53. package/dist-lib/lib/tools/web-fetch.js +1 -0
  54. package/dist-lib/lib/tools/web-search.js +1 -0
  55. package/dist-lib/schema/harness.js +4 -2
  56. package/dist-lib/spec/hrpc/messages.js +18 -8
  57. package/dist-lib/spec/hyperschema/index.js +18 -8
  58. package/dist-lib/spec/hyperschema/schema.json +11 -1
  59. package/dist-lib/spec/hyperschema/types.d.ts +3 -1
  60. package/package.json +5 -7
  61. package/worker/harness-sidecar.mjs +2 -1
@@ -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
- return `ops[${i}] ${op.op} does not take ${unknown.join(', ')}; ${takes}`;
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) => (opKeys(op.spec).includes('image') ? [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
- return { error: `ops[${i}] ${op.name}: ${errorMessage(err)}` };
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
- `Do not call ${format.name} again for it. Your answer is the reply value alone, word for word.`
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,
@@ -91,6 +91,7 @@ export function createReadFilesTool({ realpath = defaultRealpath, deniedRoots }
91
91
  required: ['files']
92
92
  },
93
93
  effect: () => 'read',
94
+ stableRead: true,
94
95
  autoApprove: true,
95
96
  rootsRequired: true,
96
97
  validate(rawArgs) {
@@ -185,12 +185,14 @@ export function createSkillTool({ skillsDir, overlayDir = resolveSkillsOverlayDi
185
185
  try {
186
186
  return await mainInstead(file, available);
187
187
  }
188
- catch { }
188
+ catch {
189
+ return { error: `skill not found: ${name}` };
190
+ }
189
191
  }
190
192
  const rest = available.length
191
- ? ` — files this skill has: ${available.join(', ')}`
192
- : '. This skill has no other files; you already have its full instructions — act now.';
193
- return { error: `file not found in skill ${name}: ${file}${rest}` };
193
+ ? `Its other files: ${available.join(', ')}.`
194
+ : 'It has no other files; you already have its full instructions, so act now.';
195
+ return { name, content: `There is no file ${file} in this skill. ${rest}` };
194
196
  }
195
197
  }
196
198
  };
@@ -9,6 +9,7 @@ import { type OperationSpec } from '../operations/manifest.ts';
9
9
  import type { CapabilityProfile } from '../../models/model-catalog.ts';
10
10
  export interface SkillCatalogEntry {
11
11
  name: string;
12
+ displayName?: string;
12
13
  description: string;
13
14
  aliases?: string[];
14
15
  preloadOnName?: boolean;
@@ -20,6 +21,7 @@ export interface SkillCatalogEntry {
20
21
  requires?: SkillRequires;
21
22
  install?: InstallStep[];
22
23
  setup?: SkillSetup;
24
+ env?: Readonly<Record<string, string>>;
23
25
  cliValidator?: CliValidator;
24
26
  stub?: boolean;
25
27
  minCapabilityProfile?: string;
@@ -79,9 +79,11 @@ function mergeOverlay(base, overlay) {
79
79
  const { origin: _origin, mcpReads: _mcpReads, operations: _operations, ...rest } = overlay;
80
80
  return {
81
81
  ...rest,
82
+ displayName: base.displayName,
82
83
  credentials: base.credentials,
83
84
  allowList: base.allowList,
84
85
  platform: base.platform,
86
+ env: base.env,
85
87
  operations: base.operations,
86
88
  mcpReads: base.mcpReads
87
89
  };
@@ -155,6 +157,7 @@ async function loadEntry(skillsDir, dir, origin) {
155
157
  const platform = platformsOf(meta.platform, origin);
156
158
  return {
157
159
  name: dir,
160
+ displayName: origin === 'overlay' ? undefined : meta.displayName || undefined,
158
161
  description: meta.description ?? '',
159
162
  aliases: meta.aliases,
160
163
  preloadOnName: meta.preloadOnName === false ? false : undefined,
@@ -166,6 +169,7 @@ async function loadEntry(skillsDir, dir, origin) {
166
169
  requires: meta.requires,
167
170
  install: meta.install,
168
171
  setup: meta.setup,
172
+ env: origin === 'overlay' ? undefined : meta.env,
169
173
  cliValidator: cli ? buildCliValidator(cli) : undefined,
170
174
  stub: meta.stub,
171
175
  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) {
@@ -1,6 +1,7 @@
1
1
  import type { InstallStep, SkillRequires, SkillSetup } from './manifest.ts';
2
2
  export interface SkillFrontMatter {
3
3
  name?: string;
4
+ displayName?: string;
4
5
  description?: string;
5
6
  aliases?: string[];
6
7
  preloadOnName?: boolean;
@@ -12,6 +13,7 @@ export interface SkillFrontMatter {
12
13
  requires?: SkillRequires;
13
14
  install?: InstallStep[];
14
15
  setup?: SkillSetup;
16
+ env?: Readonly<Record<string, string>>;
15
17
  stub?: boolean;
16
18
  minCapabilityProfile?: string;
17
19
  emoji?: string;
@@ -1,4 +1,5 @@
1
1
  import { parseManifestBlock } from './manifest-parse.js';
2
+ import { isMentionHandle } from './mention-name.js';
2
3
  const FRONT_MATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
3
4
  const FIELD_RE = /^([A-Za-z_][\w-]*):(.*)$/;
4
5
  export function parseSkillFrontMatter(raw) {
@@ -54,6 +55,9 @@ function parseFields(block) {
54
55
  else if (key === 'name' || key === 'description') {
55
56
  meta[key] = stripQuotes(value);
56
57
  }
58
+ else if (key === 'display_name' && isMentionHandle(stripQuotes(value))) {
59
+ meta.displayName = stripQuotes(value);
60
+ }
57
61
  else if (key === 'preload_on_name') {
58
62
  meta.preloadOnName = stripQuotes(value).toLowerCase() !== 'false';
59
63
  }
@@ -3,6 +3,7 @@ export interface ParsedManifest {
3
3
  requires?: SkillRequires;
4
4
  install?: InstallStep[];
5
5
  setup?: SkillSetup;
6
+ env?: Readonly<Record<string, string>>;
6
7
  stub?: boolean;
7
8
  minCapabilityProfile?: string;
8
9
  emoji?: string;
@@ -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) {
@@ -1 +1,7 @@
1
1
  export declare function skillMentionName(text: string): string | null;
2
+ export declare function isMentionHandle(value: string): boolean;
3
+ export interface MentionableSkill {
4
+ readonly name: string;
5
+ readonly displayName?: string | undefined;
6
+ }
7
+ export declare function skillMentionHandle(skill: MentionableSkill): string;
@@ -2,3 +2,9 @@ const MENTION_PATTERN = /^@([a-z0-9][a-z0-9_-]*)(?=\s|$)/;
2
2
  export function skillMentionName(text) {
3
3
  return MENTION_PATTERN.exec(text.trimStart().toLowerCase())?.[1] ?? null;
4
4
  }
5
+ export function isMentionHandle(value) {
6
+ return skillMentionName(`@${value}`) === value.toLowerCase();
7
+ }
8
+ export function skillMentionHandle(skill) {
9
+ return (skill.displayName ?? skill.name).toLowerCase();
10
+ }
@@ -1,6 +1,5 @@
1
1
  import type { SkillCatalogEntry } from './catalog.ts';
2
- import { skillMentionName } from './mention-name.ts';
3
- export { skillMentionName };
2
+ import { skillMentionHandle, skillMentionName } from './mention-name.ts';
3
+ export { skillMentionHandle, 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;
@@ -1,12 +1,14 @@
1
1
  import { has } from 'asenion';
2
- import { skillMentionName } from './mention-name.js';
3
- export { skillMentionName };
2
+ import { skillMentionHandle, skillMentionName } from './mention-name.js';
3
+ export { skillMentionHandle, skillMentionName };
4
4
  export function resolveSkillMention(text, catalog) {
5
5
  const name = skillMentionName(text);
6
6
  if (!name) {
7
7
  return null;
8
8
  }
9
- return catalog.find((skill) => skill.name.toLowerCase() === name) ?? null;
9
+ return (catalog.find((skill) => skill.displayName?.toLowerCase() === name) ??
10
+ catalog.find((skill) => skill.name.toLowerCase() === name) ??
11
+ null);
10
12
  }
11
13
  const MIN_IMPLICIT_NAME_LENGTH = 4;
12
14
  function matchesName(text, candidate) {
@@ -38,6 +40,3 @@ export function resolveImplicitSkill(text, activeSkills) {
38
40
  }
39
41
  return best;
40
42
  }
41
- export function skillPreloadMessage(content, name, body) {
42
- return `[Skill invoked by user: ${name}]\n${body}\n\nThe user's message:\n${content}`;
43
- }
@@ -32,6 +32,7 @@ export function createWeatherLookupTool({ fetch = noRedirectFetch, resolveHost =
32
32
  required: ['location']
33
33
  },
34
34
  effect: () => 'read',
35
+ stableRead: true,
35
36
  resource: () => ({ kind: 'host', label: `${GEOCODER_HOST}, ${WEATHER_HOST}` }),
36
37
  execute: (args, ctx) => execute(args.location, ctx?.signal, { fetch, resolveHost, timeoutMs: geocodeTimeoutMs })
37
38
  };
@@ -43,6 +43,7 @@ export function createWebFetchTool({ fetch: fetchImpl, resolveHost = lookupHost
43
43
  required: ['url']
44
44
  },
45
45
  effect: () => 'read',
46
+ stableRead: true,
46
47
  resource: (args) => {
47
48
  try {
48
49
  return { kind: 'host', label: new URL(String(args.url)).hostname };
@@ -30,6 +30,7 @@ export function createWebSearchTool({ fetch } = {}) {
30
30
  required: ['query']
31
31
  },
32
32
  effect: () => 'read',
33
+ stableRead: true,
33
34
  async execute(args, ctx) {
34
35
  const query = args.query;
35
36
  if (typeof query !== 'string' || query.trim().length === 0) {
@@ -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({
@@ -321,7 +322,8 @@ export function registerHarnessTypes(ns) {
321
322
  { name: 'surfaceWhenUnsupported', type: 'bool' },
322
323
  { name: 'minCapabilityProfile', type: 'string' },
323
324
  { name: 'emoji', type: 'string' },
324
- { name: 'setupV2', type: '@harness/skill-setup-v2' }
325
+ { name: 'setupV2', type: '@harness/skill-setup-v2' },
326
+ { name: 'displayName', type: 'string' }
325
327
  ]
326
328
  });
327
329
  ns.register({
@@ -1,12 +1,12 @@
1
1
  // This file is autogenerated by the hyperschema compiler
2
- // Schema Version: 19
2
+ // Schema Version: 21
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 = 19
9
+ const VERSION = 21
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
  }
@@ -1497,7 +1502,8 @@ const encoding28 = {
1497
1502
  (m.surfaceWhenUnsupported ? 256 : 0) |
1498
1503
  (m.minCapabilityProfile ? 512 : 0) |
1499
1504
  (m.emoji ? 1024 : 0) |
1500
- (m.setupV2 ? 2048 : 0)
1505
+ (m.setupV2 ? 2048 : 0) |
1506
+ ((version >= 21 && m.displayName) ? 4096 : 0)
1501
1507
 
1502
1508
  c.string.preencode(state, m.name)
1503
1509
  c.string.preencode(state, m.description)
@@ -1514,6 +1520,7 @@ const encoding28 = {
1514
1520
  if (m.minCapabilityProfile) c.string.preencode(state, m.minCapabilityProfile)
1515
1521
  if (m.emoji) c.string.preencode(state, m.emoji)
1516
1522
  if (m.setupV2) encoding28_14.preencode(state, m.setupV2)
1523
+ if (version >= 21 && m.displayName) c.string.preencode(state, m.displayName)
1517
1524
  },
1518
1525
  encode(state, m) {
1519
1526
  const flags =
@@ -1528,7 +1535,8 @@ const encoding28 = {
1528
1535
  (m.surfaceWhenUnsupported ? 256 : 0) |
1529
1536
  (m.minCapabilityProfile ? 512 : 0) |
1530
1537
  (m.emoji ? 1024 : 0) |
1531
- (m.setupV2 ? 2048 : 0)
1538
+ (m.setupV2 ? 2048 : 0) |
1539
+ ((version >= 21 && m.displayName) ? 4096 : 0)
1532
1540
 
1533
1541
  c.string.encode(state, m.name)
1534
1542
  c.string.encode(state, m.description)
@@ -1545,6 +1553,7 @@ const encoding28 = {
1545
1553
  if (m.minCapabilityProfile) c.string.encode(state, m.minCapabilityProfile)
1546
1554
  if (m.emoji) c.string.encode(state, m.emoji)
1547
1555
  if (m.setupV2) encoding28_14.encode(state, m.setupV2)
1556
+ if (version >= 21 && m.displayName) c.string.encode(state, m.displayName)
1548
1557
  },
1549
1558
  decode(state) {
1550
1559
  const r0 = c.string.decode(state)
@@ -1566,7 +1575,8 @@ const encoding28 = {
1566
1575
  surfaceWhenUnsupported: (flags & 256) !== 0,
1567
1576
  minCapabilityProfile: (flags & 512) !== 0 ? c.string.decode(state) : null,
1568
1577
  emoji: (flags & 1024) !== 0 ? c.string.decode(state) : null,
1569
- setupV2: (flags & 2048) !== 0 ? encoding28_14.decode(state) : null
1578
+ setupV2: (flags & 2048) !== 0 ? encoding28_14.decode(state) : null,
1579
+ displayName: (version >= 21 && (flags & 4096) !== 0) ? c.string.decode(state) : null
1570
1580
  }
1571
1581
  }
1572
1582
  }
@@ -1,12 +1,12 @@
1
1
  // This file is autogenerated by the hyperschema compiler
2
- // Schema Version: 19
2
+ // Schema Version: 21
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 = 19
9
+ const VERSION = 21
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
  }
@@ -1497,7 +1502,8 @@ const encoding28 = {
1497
1502
  (m.surfaceWhenUnsupported ? 256 : 0) |
1498
1503
  (m.minCapabilityProfile ? 512 : 0) |
1499
1504
  (m.emoji ? 1024 : 0) |
1500
- (m.setupV2 ? 2048 : 0)
1505
+ (m.setupV2 ? 2048 : 0) |
1506
+ ((version >= 21 && m.displayName) ? 4096 : 0)
1501
1507
 
1502
1508
  c.string.preencode(state, m.name)
1503
1509
  c.string.preencode(state, m.description)
@@ -1514,6 +1520,7 @@ const encoding28 = {
1514
1520
  if (m.minCapabilityProfile) c.string.preencode(state, m.minCapabilityProfile)
1515
1521
  if (m.emoji) c.string.preencode(state, m.emoji)
1516
1522
  if (m.setupV2) encoding28_14.preencode(state, m.setupV2)
1523
+ if (version >= 21 && m.displayName) c.string.preencode(state, m.displayName)
1517
1524
  },
1518
1525
  encode(state, m) {
1519
1526
  const flags =
@@ -1528,7 +1535,8 @@ const encoding28 = {
1528
1535
  (m.surfaceWhenUnsupported ? 256 : 0) |
1529
1536
  (m.minCapabilityProfile ? 512 : 0) |
1530
1537
  (m.emoji ? 1024 : 0) |
1531
- (m.setupV2 ? 2048 : 0)
1538
+ (m.setupV2 ? 2048 : 0) |
1539
+ ((version >= 21 && m.displayName) ? 4096 : 0)
1532
1540
 
1533
1541
  c.string.encode(state, m.name)
1534
1542
  c.string.encode(state, m.description)
@@ -1545,6 +1553,7 @@ const encoding28 = {
1545
1553
  if (m.minCapabilityProfile) c.string.encode(state, m.minCapabilityProfile)
1546
1554
  if (m.emoji) c.string.encode(state, m.emoji)
1547
1555
  if (m.setupV2) encoding28_14.encode(state, m.setupV2)
1556
+ if (version >= 21 && m.displayName) c.string.encode(state, m.displayName)
1548
1557
  },
1549
1558
  decode(state) {
1550
1559
  const r0 = c.string.decode(state)
@@ -1566,7 +1575,8 @@ const encoding28 = {
1566
1575
  surfaceWhenUnsupported: (flags & 256) !== 0,
1567
1576
  minCapabilityProfile: (flags & 512) !== 0 ? c.string.decode(state) : null,
1568
1577
  emoji: (flags & 1024) !== 0 ? c.string.decode(state) : null,
1569
- setupV2: (flags & 2048) !== 0 ? encoding28_14.decode(state) : null
1578
+ setupV2: (flags & 2048) !== 0 ? encoding28_14.decode(state) : null,
1579
+ displayName: (version >= 21 && (flags & 4096) !== 0) ? c.string.decode(state) : null
1570
1580
  }
1571
1581
  }
1572
1582
  }