@qvac/harness 0.8.9 → 0.8.10

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 (114) hide show
  1. package/CHANGELOG.md +62 -1
  2. package/dist-lib/index.d.ts +1 -1
  3. package/dist-lib/index.js +51 -50
  4. package/dist-lib/lib/audio-transcode-bare.js +2 -2
  5. package/dist-lib/lib/capabilities/completion.d.ts +1 -2
  6. package/dist-lib/lib/capabilities/completion.js +29 -25
  7. package/dist-lib/lib/capabilities/tool-prompt-log.js +3 -3
  8. package/dist-lib/lib/capabilities/voice.js +7 -6
  9. package/dist-lib/lib/connect.js +18 -17
  10. package/dist-lib/lib/errors.d.ts +266 -0
  11. package/dist-lib/lib/errors.js +189 -0
  12. package/dist-lib/lib/helpers/load-stall.d.ts +1 -1
  13. package/dist-lib/lib/helpers/load-stall.js +2 -2
  14. package/dist-lib/lib/helpers/race-abort.d.ts +2 -1
  15. package/dist-lib/lib/helpers/race-abort.js +3 -4
  16. package/dist-lib/lib/hooks/built-in/arg-repair.js +2 -2
  17. package/dist-lib/lib/hooks/pipeline.js +4 -4
  18. package/dist-lib/lib/hooks/run.js +2 -2
  19. package/dist-lib/lib/log.d.ts +26 -2
  20. package/dist-lib/lib/log.js +66 -16
  21. package/dist-lib/lib/models/model-pool.js +11 -6
  22. package/dist-lib/lib/models/model-source.js +3 -2
  23. package/dist-lib/lib/no-redirect-fetch-bare.js +2 -1
  24. package/dist-lib/lib/no-redirect-fetch-impl.js +2 -1
  25. package/dist-lib/lib/office/extract.js +4 -3
  26. package/dist-lib/lib/office/index.js +2 -1
  27. package/dist-lib/lib/resolution-detector.js +3 -3
  28. package/dist-lib/lib/rpc-session.js +2 -1
  29. package/dist-lib/lib/sdk-loader.js +2 -2
  30. package/dist-lib/lib/serve.js +33 -29
  31. package/dist-lib/lib/spawn.d.ts +2 -4
  32. package/dist-lib/lib/spawn.js +14 -7
  33. package/dist-lib/lib/tools/edit-image.js +2 -1
  34. package/dist-lib/lib/tools/exec/adapters/python/attachments.js +8 -8
  35. package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.js +7 -6
  36. package/dist-lib/lib/tools/exec/adapters/python/interrupt-timer.js +2 -2
  37. package/dist-lib/lib/tools/exec/adapters/python/memfs.js +2 -2
  38. package/dist-lib/lib/tools/exec/adapters/python/pyodide.js +8 -8
  39. package/dist-lib/lib/tools/exec/adapters/python/run-session.js +11 -7
  40. package/dist-lib/lib/tools/exec/adapters/python/wheels.js +5 -5
  41. package/dist-lib/lib/tools/exec/adapters/python/worker/active.js +2 -2
  42. package/dist-lib/lib/tools/exec/adapters/python/worker/client.js +9 -8
  43. package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.d.ts +2 -0
  44. package/dist-lib/lib/tools/exec/adapters/python/worker/serve.js +4 -4
  45. package/dist-lib/lib/tools/exec/adapters/python/worker/spawn.d.ts +2 -1
  46. package/dist-lib/lib/tools/exec/adapters/python/worker/spawn.js +2 -1
  47. package/dist-lib/lib/tools/exec/obsidian/note-name.js +2 -1
  48. package/dist-lib/lib/tools/exec/obsidian/safe-path.js +5 -4
  49. package/dist-lib/lib/tools/exec/obsidian/vault-create.js +3 -2
  50. package/dist-lib/lib/tools/exec/obsidian/vault-fs.js +2 -2
  51. package/dist-lib/lib/tools/exec/obsidian/vault-paths.js +3 -2
  52. package/dist-lib/lib/tools/exec/obsidian/vault-read.js +6 -5
  53. package/dist-lib/lib/tools/exec/obsidian/vault-write.js +13 -12
  54. package/dist-lib/lib/tools/exec/runner.js +2 -1
  55. package/dist-lib/lib/tools/files/edit-file.d.ts +3 -0
  56. package/dist-lib/lib/tools/files/edit-file.js +73 -0
  57. package/dist-lib/lib/tools/files/glob.d.ts +3 -0
  58. package/dist-lib/lib/tools/files/glob.js +63 -0
  59. package/dist-lib/lib/tools/files/grep.d.ts +3 -0
  60. package/dist-lib/lib/tools/files/grep.js +114 -0
  61. package/dist-lib/lib/tools/files/index.d.ts +3 -0
  62. package/dist-lib/lib/tools/files/index.js +23 -0
  63. package/dist-lib/lib/tools/files/list-dir.d.ts +5 -0
  64. package/dist-lib/lib/tools/files/list-dir.js +130 -0
  65. package/dist-lib/lib/tools/files/make-dir.d.ts +3 -0
  66. package/dist-lib/lib/tools/files/make-dir.js +47 -0
  67. package/dist-lib/lib/tools/files/read-file.d.ts +3 -0
  68. package/dist-lib/lib/tools/files/read-file.js +103 -0
  69. package/dist-lib/lib/tools/files/transfer.d.ts +4 -0
  70. package/dist-lib/lib/tools/files/transfer.js +82 -0
  71. package/dist-lib/lib/tools/files/trash-file.d.ts +4 -0
  72. package/dist-lib/lib/tools/files/trash-file.js +61 -0
  73. package/dist-lib/lib/tools/files/write-file.d.ts +3 -0
  74. package/dist-lib/lib/tools/files/write-file.js +55 -0
  75. package/dist-lib/lib/tools/fs/document-text.d.ts +2 -0
  76. package/dist-lib/lib/tools/fs/document-text.js +38 -0
  77. package/dist-lib/lib/tools/fs/edit-match.d.ts +11 -0
  78. package/dist-lib/lib/tools/fs/edit-match.js +85 -0
  79. package/dist-lib/lib/tools/fs/remote.d.ts +2 -1
  80. package/dist-lib/lib/tools/fs/remote.js +4 -4
  81. package/dist-lib/lib/tools/fs/scope.d.ts +1 -1
  82. package/dist-lib/lib/tools/fs/scope.js +3 -2
  83. package/dist-lib/lib/tools/fs/targets.d.ts +10 -0
  84. package/dist-lib/lib/tools/fs/targets.js +32 -0
  85. package/dist-lib/lib/tools/fs/tool-path.d.ts +24 -0
  86. package/dist-lib/lib/tools/fs/tool-path.js +58 -0
  87. package/dist-lib/lib/tools/fs/walk.js +3 -3
  88. package/dist-lib/lib/tools/generate-image.d.ts +2 -2
  89. package/dist-lib/lib/tools/generate-image.js +5 -6
  90. package/dist-lib/lib/tools/office.js +2 -1
  91. package/dist-lib/lib/tools/skill.js +2 -1
  92. package/dist-lib/lib/tools/skills/bundled-dir.js +4 -4
  93. package/dist-lib/lib/tools/skills/catalog.js +2 -2
  94. package/dist-lib/lib/tools/skills/credential-health.js +3 -3
  95. package/dist-lib/lib/tools/skills/overlay-crud.js +3 -2
  96. package/dist-lib/lib/tools/translate.d.ts +2 -2
  97. package/dist-lib/lib/tools/translate.js +5 -6
  98. package/dist-lib/lib/tools/weather-lookup/geocode.js +5 -4
  99. package/dist-lib/lib/web-search/engines/bing.js +2 -1
  100. package/dist-lib/lib/web-search/engines/google.js +2 -1
  101. package/dist-lib/lib/web-search/fetch-html.d.ts +5 -5
  102. package/dist-lib/lib/web-search/fetch-html.js +12 -15
  103. package/package.json +66 -24
  104. package/worker/harness-sidecar.mjs +12 -13
  105. package/dist-lib/lib/tools/create-documents.d.ts +0 -3
  106. package/dist-lib/lib/tools/create-documents.js +0 -95
  107. package/dist-lib/lib/tools/edit-documents.d.ts +0 -3
  108. package/dist-lib/lib/tools/edit-documents.js +0 -132
  109. package/dist-lib/lib/tools/file-glob.d.ts +0 -14
  110. package/dist-lib/lib/tools/file-glob.js +0 -118
  111. package/dist-lib/lib/tools/grep.d.ts +0 -5
  112. package/dist-lib/lib/tools/grep.js +0 -184
  113. package/dist-lib/lib/tools/read-files.d.ts +0 -5
  114. package/dist-lib/lib/tools/read-files.js +0 -157
@@ -1,7 +1,7 @@
1
1
  import { errorMessage } from '../../guards.js';
2
- import { hlog } from '../../log.js';
2
+ import { log } from '../../log.js';
3
3
  import { displayPath } from './scope.js';
4
- export const WALK_SKIP_DIRS = new Set(['.git', 'node_modules']);
4
+ export const WALK_SKIP_DIRS = new Set(['.git', 'node_modules', '.trash']);
5
5
  export const MAX_WALK_ENTRIES = 10_000;
6
6
  export const MAX_WALK_MS = 30_000;
7
7
  export async function walkFiles(root, folder = '/') {
@@ -29,7 +29,7 @@ export async function walkFiles(root, folder = '/') {
29
29
  }
30
30
  }
31
31
  catch (err) {
32
- hlog('warn: walk aborted mid-listing', {
32
+ log.warn('warn: walk aborted mid-listing', {
33
33
  root: displayPath(root, '/'),
34
34
  folder,
35
35
  visited,
@@ -1,4 +1,5 @@
1
1
  import type { AbortSignal as BareAbortSignal } from 'bare-abort-controller';
2
+ import { IMAGE_LOAD_STALL_ERROR } from '../errors.ts';
2
3
  import type { ToolDefinition } from '../capabilities/completion.ts';
3
4
  import type { ModelSrc } from '../models/context.ts';
4
5
  import { type ImageClamps } from '../models/model-catalog.ts';
@@ -88,7 +89,7 @@ export interface DiffusionRuntime {
88
89
  release(): void;
89
90
  }
90
91
  export declare function generationPercentTick(onPercent: (percentage: number) => void, encodePhases?: number): (tick: DiffusionTick) => void;
91
- export declare const IMAGE_LOAD_STALL_ERROR = "image model download stalled";
92
+ export { IMAGE_LOAD_STALL_ERROR };
92
93
  export declare function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, hold, loads, stallMs, ...base }: DiffusionRuntimeOptions): DiffusionRuntime;
93
94
  export declare const BUSY_ERROR = "an image generation is already running; try again when it finishes";
94
95
  export interface ImageToolError {
@@ -113,4 +114,3 @@ export declare function settingsDimension(value: number | undefined, maxDim?: nu
113
114
  export declare function settingsSteps(value: number | undefined, maxSteps?: number): number | undefined;
114
115
  export declare function effectiveSteps(parsedSteps: number | undefined, settingsValue: number | undefined, stepsCap: number | undefined): number | undefined;
115
116
  export declare function createGenerateImageTool(runtime: DiffusionRuntime, clamps?: ImageClamps): ToolDefinition;
116
- export {};
@@ -1,4 +1,5 @@
1
1
  import ScopeLock from 'scope-lock';
2
+ import { HarnessError, IMAGE_LOAD_STALL_ERROR, isHarnessError } from '../errors.js';
2
3
  import { AbortedError, raceAbort } from '../helpers/race-abort.js';
3
4
  import { DIFFUSION_CATALOG, imageModelFor } from '../models/model-catalog.js';
4
5
  import { createLoadStall } from '../helpers/load-stall.js';
@@ -38,7 +39,7 @@ function diffusionProgressName(set) {
38
39
  const entry = DIFFUSION_CATALOG.find((candidate) => candidate.model.src === set.model);
39
40
  return entry?.name ?? set.model;
40
41
  }
41
- export const IMAGE_LOAD_STALL_ERROR = 'image model download stalled';
42
+ export { IMAGE_LOAD_STALL_ERROR };
42
43
  export function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, hold, loads = createLoadTracker(), stallMs = IMAGE_LOAD_STALL_MS, ...base }) {
43
44
  let loading = null;
44
45
  let residentFingerprint = null;
@@ -102,7 +103,7 @@ export function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBefore
102
103
  config.vaeModelSrc = await resolveModelSrc(sdk, set.vaeModel, 'diffusion');
103
104
  }
104
105
  const modelConfig = Object.keys(config).length ? config : undefined;
105
- const stall = createLoadStall(stallMs, IMAGE_LOAD_STALL_ERROR);
106
+ const stall = createLoadStall(stallMs, () => new HarnessError('IMAGE_LOAD_STALLED'));
106
107
  const pending = sdk.loadModel({
107
108
  modelSrc,
108
109
  modelType: 'diffusion',
@@ -118,9 +119,7 @@ export function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBefore
118
119
  return { modelId, prediction: set.prediction };
119
120
  }
120
121
  catch (err) {
121
- if (err instanceof Error &&
122
- err.message === IMAGE_LOAD_STALL_ERROR &&
123
- pending.requestId) {
122
+ if (isHarnessError(err, 'IMAGE_LOAD_STALLED') && pending.requestId) {
124
123
  void sdk.cancel?.({ requestId: pending.requestId }).catch(noop);
125
124
  }
126
125
  throw err;
@@ -371,7 +370,7 @@ export function createGenerateImageTool(runtime, clamps) {
371
370
  if (err instanceof AbortedError) {
372
371
  return { error: 'image generation canceled' };
373
372
  }
374
- if (err instanceof Error && err.message === IMAGE_LOAD_STALL_ERROR) {
373
+ if (isHarnessError(err, 'IMAGE_LOAD_STALLED')) {
375
374
  return { error: IMAGE_LOAD_STALL_ERROR };
376
375
  }
377
376
  throw err;
@@ -1,4 +1,5 @@
1
1
  import b4a from 'b4a';
2
+ import { HarnessError } from '../errors.js';
2
3
  import { errorMessage, isRecord } from '../guards.js';
3
4
  import { DocsError, docx, pptx, units, xlsx } from '../office/engine/index.js';
4
5
  import { stageInputs } from './exec/adapters/python/attachments.js';
@@ -356,7 +357,7 @@ async function attachOutput(job, ctx) {
356
357
  function runOp(document, op, input) {
357
358
  const method = Reflect.get(document, op.name);
358
359
  if (typeof method !== 'function') {
359
- throw new Error(`${op.name} is not available on this file`);
360
+ throw new HarnessError('OFFICE_OP_UNAVAILABLE', [op.name]);
360
361
  }
361
362
  return Reflect.apply(method, document, opArguments(op.spec, input, units.inches));
362
363
  }
@@ -1,6 +1,7 @@
1
1
  import path from 'path';
2
2
  import b4a from 'b4a';
3
3
  import Localdrive from 'localdrive';
4
+ import { HarnessError } from '../errors.js';
4
5
  import { OBSIDIAN_VAULT_MODE_NOTE } from './exec/obsidian/guidance.js';
5
6
  import { unquoteArg } from './model-args.js';
6
7
  import { credentialRecord } from './skills/auth-resolver.js';
@@ -101,7 +102,7 @@ export function createSkillTool({ skillsDir, overlayDir = resolveSkillsOverlayDi
101
102
  return { raw: b4a.toString(buf, 'utf8'), root: root ?? undefined };
102
103
  }
103
104
  }
104
- throw new Error(`ENOENT: ${name}/${file}`);
105
+ throw new HarnessError('SKILL_FILE_NOT_FOUND', [name, file]);
105
106
  }
106
107
  return {
107
108
  name: 'skill',
@@ -2,6 +2,7 @@ import fs from 'fs';
2
2
  import os from 'os';
3
3
  import path from 'path';
4
4
  import process from 'process';
5
+ import { HarnessError } from '../../errors.js';
5
6
  import { userTmpPath } from '../../tmp-path.js';
6
7
  import { hashBundledSkills } from './bundled-hash.js';
7
8
  import { SKILLS as BUNDLED_SKILLS } from '@qvac/skills/bundled';
@@ -26,7 +27,7 @@ async function materialize() {
26
27
  try {
27
28
  await writeTree(staging);
28
29
  if (!(await validTree(staging))) {
29
- throw new Error(`bundled skills staging dir ${staging} failed validation`);
30
+ throw new HarnessError('BUNDLED_SKILLS_INVALID', [staging]);
30
31
  }
31
32
  if (await tryRename(staging, dir)) {
32
33
  return dir;
@@ -58,12 +59,11 @@ async function materialize() {
58
59
  }
59
60
  }
60
61
  function tamperError(dir) {
61
- return new Error(`bundled skills dir ${dir} failed validation after a rewrite — ` +
62
- 'another process is tampering with it; remove it and restart');
62
+ return new HarnessError('BUNDLED_SKILLS_TAMPERED', [dir]);
63
63
  }
64
64
  function occupiedError(dir, cause) {
65
65
  const why = cause instanceof Error ? ` (${cause.message})` : '';
66
- return new Error(`bundled skills dir ${dir} could not be read or replaced${why} — remove it and restart`);
66
+ return new HarnessError('BUNDLED_SKILLS_OCCUPIED', [dir, why]);
67
67
  }
68
68
  export function randomSuffix() {
69
69
  return Math.random().toString(36).slice(2, 10);
@@ -10,7 +10,7 @@ import { parseToolGrant } from './tool-grants.js';
10
10
  import { deriveSetup } from './setup-routes.js';
11
11
  import { resolveSkillsOverlayDir } from './overlay-dir.js';
12
12
  import { parseOperationsManifest } from '../operations/manifest.js';
13
- import { hlog } from '../../log.js';
13
+ import { log } from '../../log.js';
14
14
  export function skillSupportsPlatform(entry, platform) {
15
15
  return supportsPlatform(entry, platform);
16
16
  }
@@ -193,7 +193,7 @@ async function loadOperations(skillDir, name) {
193
193
  }
194
194
  const operations = parseOperationsManifest(raw);
195
195
  if (!operations) {
196
- hlog('skill operations manifest rejected', { skill: name });
196
+ log.warn('skill operations manifest rejected', { skill: name });
197
197
  return [];
198
198
  }
199
199
  return operations;
@@ -1,4 +1,4 @@
1
- import { hlog } from '../../log.js';
1
+ import { log } from '../../log.js';
2
2
  import { scopeKey } from './scope-key.js';
3
3
  const rejected = new Set();
4
4
  const lastProbedAt = new Map();
@@ -16,12 +16,12 @@ export function markCredentialValid(key, agentId, skill) {
16
16
  }
17
17
  export function recordCredentialHealth(key, status, agentId) {
18
18
  if (status === 401 || status === 403) {
19
- hlog('credential rejected by provider', { key, status });
19
+ log.warn('credential rejected by provider', { key, status });
20
20
  markCredentialRejected(key, agentId);
21
21
  }
22
22
  else if (status < 400) {
23
23
  if (isCredentialRejected(key, agentId)) {
24
- hlog('credential recovered', { key });
24
+ log.info('credential recovered', { key });
25
25
  }
26
26
  markCredentialValid(key, agentId);
27
27
  }
@@ -1,10 +1,11 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import { HarnessError } from '../../errors.js';
3
4
  import { randomSuffix } from './bundled-dir.js';
4
5
  const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
5
6
  function assertSafeName(name) {
6
7
  if (!SAFE_NAME.test(name)) {
7
- throw new Error(`invalid skill name: ${name}`);
8
+ throw new HarnessError('INVALID_SKILL_NAME', [name]);
8
9
  }
9
10
  }
10
11
  async function dirNames(root) {
@@ -66,7 +67,7 @@ export async function copyBaseForEdit(baseDir, overlayDir, name) {
66
67
  }
67
68
  const src = path.join(baseDir, name);
68
69
  if (!(await exists(src))) {
69
- throw new Error(`skill not found: ${name}`);
70
+ throw new HarnessError('SKILL_NOT_FOUND', [name]);
70
71
  }
71
72
  await copyDir(src, dest);
72
73
  return { copied: true };
@@ -1,4 +1,5 @@
1
1
  import type { AbortSignal as BareAbortSignal } from 'bare-abort-controller';
2
+ import { TRANSLATION_LOAD_STALL_ERROR } from '../errors.ts';
2
3
  import type { ToolDefinition } from '../capabilities/completion.ts';
3
4
  import type { ModelSrc } from '../models/context.ts';
4
5
  import { type NmtPlan } from '../models/nmt-catalog.ts';
@@ -38,7 +39,7 @@ export type TranslationSdk = ModelRegistrySdk & {
38
39
  };
39
40
  };
40
41
  export declare const TRANSLATION_LOAD_STALL_MS: number;
41
- export declare const TRANSLATION_LOAD_STALL_ERROR = "translation model download stalled";
42
+ export { TRANSLATION_LOAD_STALL_ERROR };
42
43
  export interface TranslationRuntimeOptions {
43
44
  loadSdk: () => Promise<TranslationSdk>;
44
45
  loads?: LoadTracker;
@@ -76,4 +77,3 @@ export interface TranslateToolOptions {
76
77
  slm?: SlmTranslateFn;
77
78
  }
78
79
  export declare function createTranslateTool(runtime: TranslationRuntime, { slm }?: TranslateToolOptions): ToolDefinition;
79
- export {};
@@ -1,5 +1,6 @@
1
1
  import ScopeLock from 'scope-lock';
2
2
  import { getLangName } from '@qvac/langdetect-text';
3
+ import { HarnessError, isHarnessError, TRANSLATION_LOAD_STALL_ERROR } from '../errors.js';
3
4
  import { detectLanguage, UNDETERMINED } from '../capabilities/detect-language.js';
4
5
  import { AbortedError, raceAbort } from '../helpers/race-abort.js';
5
6
  import { ENGLISH, NMT_FROM_ENGLISH, NMT_TO_ENGLISH, languageLabel, SLM_LANGUAGE_CODES, sameLanguage, translationPlanFor } from '../models/nmt-catalog.js';
@@ -7,7 +8,7 @@ import { createLoadStall } from '../helpers/load-stall.js';
7
8
  import { createLoadTracker } from '../models/model-pool.js';
8
9
  import { resolveModelSrc } from '../models/model-source.js';
9
10
  export const TRANSLATION_LOAD_STALL_MS = 15 * 60_000;
10
- export const TRANSLATION_LOAD_STALL_ERROR = 'translation model download stalled';
11
+ export { TRANSLATION_LOAD_STALL_ERROR };
11
12
  function packName(plan) {
12
13
  const pair = plan.model.match(/model\.([a-z]+)\.intgemm/)?.[1];
13
14
  if (pair) {
@@ -38,7 +39,7 @@ export function createTranslationRuntime({ loadSdk, loads = createLoadTracker(),
38
39
  modelConfig.pivotModel = { modelSrc: await resolveModelSrc(sdk, plan.pivot, 'nmt') };
39
40
  }
40
41
  const name = packName(plan);
41
- const stall = createLoadStall(stallMs, TRANSLATION_LOAD_STALL_ERROR);
42
+ const stall = createLoadStall(stallMs, () => new HarnessError('TRANSLATION_LOAD_STALLED'));
42
43
  onProgress?.(name, {});
43
44
  const pending = sdk.loadModel({
44
45
  modelSrc,
@@ -54,9 +55,7 @@ export function createTranslationRuntime({ loadSdk, loads = createLoadTracker(),
54
55
  return { modelId };
55
56
  }
56
57
  catch (err) {
57
- if (err instanceof Error &&
58
- err.message === TRANSLATION_LOAD_STALL_ERROR &&
59
- pending.requestId) {
58
+ if (isHarnessError(err, 'TRANSLATION_LOAD_STALLED') && pending.requestId) {
60
59
  void sdk.cancel?.({ requestId: pending.requestId }).catch(noop);
61
60
  }
62
61
  throw err;
@@ -216,7 +215,7 @@ export function createTranslateTool(runtime, { slm } = {}) {
216
215
  entered = true;
217
216
  if (plan.route === 'slm') {
218
217
  if (!slm) {
219
- throw new Error('slm translation is unavailable');
218
+ throw new HarnessError('SLM_TRANSLATION_UNAVAILABLE');
220
219
  }
221
220
  const text = await slm(plan.model, { text: parsed.text, from: plan.from, to: plan.to }, ctx?.signal);
222
221
  return {
@@ -1,7 +1,8 @@
1
1
  import { AbortController } from 'bare-abort-controller';
2
2
  import { z } from 'zod';
3
+ import { HarnessError } from '../../errors.js';
3
4
  import { errorMessage } from '../../guards.js';
4
- import { hlog } from '../../log.js';
5
+ import { log } from '../../log.js';
5
6
  import { resolveOutboundUrl } from '../url-guard.js';
6
7
  import { checkQualifier, describePlace, fold } from './place.js';
7
8
  export const GEOCODER_HOST = 'geocoding-api.open-meteo.com';
@@ -33,7 +34,7 @@ export async function geocode(city, lookup) {
33
34
  if (lookup.signal.aborted) {
34
35
  throw err;
35
36
  }
36
- hlog('warn: weather geocoder failed, asking wttr.in by name', { err: errorMessage(err) });
37
+ log.warn('warn: weather geocoder failed, asking wttr.in by name', { err: errorMessage(err) });
37
38
  return [];
38
39
  }
39
40
  finally {
@@ -46,7 +47,7 @@ async function search(city, { fetch, resolveHost }, signal) {
46
47
  const url = new URL(`https://${GEOCODER_HOST}/v1/search?${query}`);
47
48
  const check = await resolveOutboundUrl(url, resolveHost, signal);
48
49
  if (check.blocked) {
49
- throw new Error(check.blocked);
50
+ throw new HarnessError('EGRESS_BLOCKED', [check.blocked]);
50
51
  }
51
52
  const response = await fetch(url, {
52
53
  method: 'GET',
@@ -57,7 +58,7 @@ async function search(city, { fetch, resolveHost }, signal) {
57
58
  maxBytes: MAX_BODY_BYTES
58
59
  });
59
60
  if (response.status !== 200) {
60
- throw new Error(`geocoder answered HTTP ${response.status}`);
61
+ throw new HarnessError('GEOCODER_HTTP_STATUS', [response.status]);
61
62
  }
62
63
  const { results = [] } = GeocodeSchema.parse(JSON.parse(await response.text()));
63
64
  return results.map((result) => ({
@@ -1,3 +1,4 @@
1
+ import { HarnessError } from '../../errors.js';
1
2
  import { fetchHtml } from '../fetch-html.js';
2
3
  import { clean } from '../html-helpers.js';
3
4
  import { urlMatchesHost } from '../url-helpers.js';
@@ -101,7 +102,7 @@ export async function searchBing(query, fetcher, signal) {
101
102
  const url = `https://www.bing.com/search?q=${encodeURIComponent(query)}`;
102
103
  const html = await fetchHtml(fetcher, url, { headers: { 'User-Agent': SAFARI_UA }, signal });
103
104
  if (html.includes('captcha') || html.includes('challenge/verify') || html.includes('Turnstile')) {
104
- throw new Error('Bing CAPTCHA detected');
105
+ throw new HarnessError('SEARCH_CAPTCHA', ['Bing']);
105
106
  }
106
107
  const primary = parseBAlgoBlocks(html);
107
108
  if (primary.length > 0) {
@@ -1,3 +1,4 @@
1
+ import { HarnessError } from '../../errors.js';
1
2
  import { fetchHtml } from '../fetch-html.js';
2
3
  import { clean } from '../html-helpers.js';
3
4
  import { urlMatchesHost } from '../url-helpers.js';
@@ -86,7 +87,7 @@ export async function searchGoogle(query, fetcher, signal) {
86
87
  signal
87
88
  });
88
89
  if (html.includes('detected unusual traffic') || html.includes('g-recaptcha')) {
89
- throw new Error('Google CAPTCHA detected');
90
+ throw new HarnessError('SEARCH_CAPTCHA', ['Google']);
90
91
  }
91
92
  const primary = parseDataVedAnchors(html);
92
93
  if (primary.length > 0) {
@@ -1,11 +1,12 @@
1
1
  import { type AbortSignal as BareAbortSignal } from 'bare-abort-controller';
2
+ import { HarnessError } from '../errors.ts';
2
3
  import type { Fetcher } from './types.ts';
3
4
  export declare const ENGINE_TIMEOUT_MS = 8000;
4
5
  export declare const MAX_HTML_CHARS = 2000000;
5
- export declare class TimeoutError extends Error {
6
+ export declare class TimeoutError extends HarnessError<'FETCH_TIMED_OUT'> {
6
7
  constructor(ms: number);
7
8
  }
8
- export declare class AbortError extends Error {
9
+ export declare class AbortError extends HarnessError<'FETCH_CANCELLED'> {
9
10
  constructor();
10
11
  }
11
12
  interface FetchOptions {
@@ -18,9 +19,8 @@ interface FetchOptions {
18
19
  readonly signal?: BareAbortSignal;
19
20
  }
20
21
  export declare function fetchHtml(fetcher: Fetcher, url: string, options?: FetchOptions): Promise<string>;
21
- export declare class JsonParseError extends Error {
22
- readonly cause?: unknown;
23
- constructor(message: string, cause?: unknown);
22
+ export declare class JsonParseError extends HarnessError<'RESPONSE_NOT_JSON'> {
23
+ constructor(chars: number, cause: unknown);
24
24
  }
25
25
  export declare function fetchJson(fetcher: Fetcher, url: string, options?: FetchOptions): Promise<unknown>;
26
26
  export {};
@@ -1,17 +1,17 @@
1
1
  import { AbortController } from 'bare-abort-controller';
2
+ import { errorMessage } from '../guards.js';
3
+ import { HarnessError } from '../errors.js';
2
4
  import { BROWSER_UA } from './user-agents.js';
3
5
  export const ENGINE_TIMEOUT_MS = 8000;
4
6
  export const MAX_HTML_CHARS = 2_000_000;
5
- export class TimeoutError extends Error {
7
+ export class TimeoutError extends HarnessError {
6
8
  constructor(ms) {
7
- super(`Request timed out after ${ms}ms`);
8
- this.name = 'TimeoutError';
9
+ super('FETCH_TIMED_OUT', [ms]);
9
10
  }
10
11
  }
11
- export class AbortError extends Error {
12
+ export class AbortError extends HarnessError {
12
13
  constructor() {
13
- super('Request cancelled');
14
- this.name = 'AbortError';
14
+ super('FETCH_CANCELLED');
15
15
  }
16
16
  }
17
17
  function buildInit(options, signal) {
@@ -74,16 +74,13 @@ async function fetchWithTimeout(fetcher, url, options) {
74
74
  export async function fetchHtml(fetcher, url, options) {
75
75
  const { ok, status, text } = await fetchWithTimeout(fetcher, url, options);
76
76
  if (!ok) {
77
- throw new Error(`HTTP ${status}`);
77
+ throw new HarnessError('HTTP_STATUS', [status]);
78
78
  }
79
79
  return text;
80
80
  }
81
- export class JsonParseError extends Error {
82
- cause;
83
- constructor(message, cause) {
84
- super(message);
85
- this.cause = cause;
86
- this.name = 'JsonParseError';
81
+ export class JsonParseError extends HarnessError {
82
+ constructor(chars, cause) {
83
+ super('RESPONSE_NOT_JSON', [chars, errorMessage(cause)], { cause });
87
84
  }
88
85
  }
89
86
  export async function fetchJson(fetcher, url, options) {
@@ -92,12 +89,12 @@ export async function fetchJson(fetcher, url, options) {
92
89
  accept: options?.accept ?? 'application/json'
93
90
  });
94
91
  if (!ok) {
95
- throw new Error(`HTTP ${status}`);
92
+ throw new HarnessError('HTTP_STATUS', [status]);
96
93
  }
97
94
  try {
98
95
  return JSON.parse(text);
99
96
  }
100
97
  catch (err) {
101
- throw new JsonParseError(`Response was not valid JSON (${text.length} chars): ${(err instanceof Error ? err : new Error(String(err))).message}`, err);
98
+ throw new JsonParseError(text.length, err);
102
99
  }
103
100
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qvac/harness",
3
- "version": "0.8.9",
3
+ "version": "0.8.10",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "bare": ">=1.34.0"
@@ -163,6 +163,10 @@
163
163
  "types": "./dist-lib/lib/helpers/race-abort.d.ts",
164
164
  "default": "./dist-lib/lib/helpers/race-abort.js"
165
165
  },
166
+ "./errors": {
167
+ "types": "./dist-lib/lib/errors.d.ts",
168
+ "default": "./dist-lib/lib/errors.js"
169
+ },
166
170
  "./sdk-config-loader": {
167
171
  "bare": {
168
172
  "types": "./dist-lib/lib/sdk-config-loader.bare.d.ts",
@@ -190,14 +194,6 @@
190
194
  "types": "./dist-lib/lib/tools/catalog.d.ts",
191
195
  "default": "./dist-lib/lib/tools/catalog.js"
192
196
  },
193
- "./lib/tools/create-documents.ts": {
194
- "types": "./dist-lib/lib/tools/create-documents.d.ts",
195
- "default": "./dist-lib/lib/tools/create-documents.js"
196
- },
197
- "./lib/tools/edit-documents.ts": {
198
- "types": "./dist-lib/lib/tools/edit-documents.d.ts",
199
- "default": "./dist-lib/lib/tools/edit-documents.js"
200
- },
201
197
  "./lib/tools/edit-image.ts": {
202
198
  "types": "./dist-lib/lib/tools/edit-image.d.ts",
203
199
  "default": "./dist-lib/lib/tools/edit-image.js"
@@ -374,14 +370,58 @@
374
370
  "types": "./dist-lib/lib/tools/exec/video-tools.d.ts",
375
371
  "default": "./dist-lib/lib/tools/exec/video-tools.js"
376
372
  },
377
- "./lib/tools/file-glob.ts": {
378
- "types": "./dist-lib/lib/tools/file-glob.d.ts",
379
- "default": "./dist-lib/lib/tools/file-glob.js"
373
+ "./lib/tools/files/edit-file.ts": {
374
+ "types": "./dist-lib/lib/tools/files/edit-file.d.ts",
375
+ "default": "./dist-lib/lib/tools/files/edit-file.js"
376
+ },
377
+ "./lib/tools/files/glob.ts": {
378
+ "types": "./dist-lib/lib/tools/files/glob.d.ts",
379
+ "default": "./dist-lib/lib/tools/files/glob.js"
380
+ },
381
+ "./lib/tools/files/grep.ts": {
382
+ "types": "./dist-lib/lib/tools/files/grep.d.ts",
383
+ "default": "./dist-lib/lib/tools/files/grep.js"
384
+ },
385
+ "./lib/tools/files/index.ts": {
386
+ "types": "./dist-lib/lib/tools/files/index.d.ts",
387
+ "default": "./dist-lib/lib/tools/files/index.js"
388
+ },
389
+ "./lib/tools/files/list-dir.ts": {
390
+ "types": "./dist-lib/lib/tools/files/list-dir.d.ts",
391
+ "default": "./dist-lib/lib/tools/files/list-dir.js"
392
+ },
393
+ "./lib/tools/files/make-dir.ts": {
394
+ "types": "./dist-lib/lib/tools/files/make-dir.d.ts",
395
+ "default": "./dist-lib/lib/tools/files/make-dir.js"
396
+ },
397
+ "./lib/tools/files/read-file.ts": {
398
+ "types": "./dist-lib/lib/tools/files/read-file.d.ts",
399
+ "default": "./dist-lib/lib/tools/files/read-file.js"
400
+ },
401
+ "./lib/tools/files/transfer.ts": {
402
+ "types": "./dist-lib/lib/tools/files/transfer.d.ts",
403
+ "default": "./dist-lib/lib/tools/files/transfer.js"
404
+ },
405
+ "./lib/tools/files/trash-file.ts": {
406
+ "types": "./dist-lib/lib/tools/files/trash-file.d.ts",
407
+ "default": "./dist-lib/lib/tools/files/trash-file.js"
408
+ },
409
+ "./lib/tools/files/write-file.ts": {
410
+ "types": "./dist-lib/lib/tools/files/write-file.d.ts",
411
+ "default": "./dist-lib/lib/tools/files/write-file.js"
412
+ },
413
+ "./lib/tools/fs/document-text.ts": {
414
+ "types": "./dist-lib/lib/tools/fs/document-text.d.ts",
415
+ "default": "./dist-lib/lib/tools/fs/document-text.js"
380
416
  },
381
417
  "./lib/tools/fs/drive.ts": {
382
418
  "types": "./dist-lib/lib/tools/fs/drive.d.ts",
383
419
  "default": "./dist-lib/lib/tools/fs/drive.js"
384
420
  },
421
+ "./lib/tools/fs/edit-match.ts": {
422
+ "types": "./dist-lib/lib/tools/fs/edit-match.d.ts",
423
+ "default": "./dist-lib/lib/tools/fs/edit-match.js"
424
+ },
385
425
  "./lib/tools/fs/glob.ts": {
386
426
  "types": "./dist-lib/lib/tools/fs/glob.d.ts",
387
427
  "default": "./dist-lib/lib/tools/fs/glob.js"
@@ -406,6 +446,14 @@
406
446
  "types": "./dist-lib/lib/tools/fs/scope.d.ts",
407
447
  "default": "./dist-lib/lib/tools/fs/scope.js"
408
448
  },
449
+ "./lib/tools/fs/targets.ts": {
450
+ "types": "./dist-lib/lib/tools/fs/targets.d.ts",
451
+ "default": "./dist-lib/lib/tools/fs/targets.js"
452
+ },
453
+ "./lib/tools/fs/tool-path.ts": {
454
+ "types": "./dist-lib/lib/tools/fs/tool-path.d.ts",
455
+ "default": "./dist-lib/lib/tools/fs/tool-path.js"
456
+ },
409
457
  "./lib/tools/fs/walk.ts": {
410
458
  "types": "./dist-lib/lib/tools/fs/walk.d.ts",
411
459
  "default": "./dist-lib/lib/tools/fs/walk.js"
@@ -422,10 +470,6 @@
422
470
  "types": "./dist-lib/lib/tools/generate-music.d.ts",
423
471
  "default": "./dist-lib/lib/tools/generate-music.js"
424
472
  },
425
- "./lib/tools/grep.ts": {
426
- "types": "./dist-lib/lib/tools/grep.d.ts",
427
- "default": "./dist-lib/lib/tools/grep.js"
428
- },
429
473
  "./lib/tools/http-methods.ts": {
430
474
  "types": "./dist-lib/lib/tools/http-methods.d.ts",
431
475
  "default": "./dist-lib/lib/tools/http-methods.js"
@@ -502,10 +546,6 @@
502
546
  "types": "./dist-lib/lib/tools/private-ip.d.ts",
503
547
  "default": "./dist-lib/lib/tools/private-ip.js"
504
548
  },
505
- "./lib/tools/read-files.ts": {
506
- "types": "./dist-lib/lib/tools/read-files.d.ts",
507
- "default": "./dist-lib/lib/tools/read-files.js"
508
- },
509
549
  "./lib/tools/skills/auth-resolver.ts": {
510
550
  "types": "./dist-lib/lib/tools/skills/auth-resolver.d.ts",
511
551
  "default": "./dist-lib/lib/tools/skills/auth-resolver.js"
@@ -642,7 +682,7 @@
642
682
  "scripts": {
643
683
  "build:schema": "bare schema/build.ts",
644
684
  "test": "npm run vendor:wheels && npm run test:bare && npm run test:addons",
645
- "test:bare": "QVAC_NO_SKILL_BIN_PROBE=1 brittle-jobs 'test/{,!(helpers)/}*.ts'",
685
+ "test:bare": "QVAC_NO_SKILL_BIN_PROBE=1 brittle-jobs 'test/{,!(helpers)/,!(helpers|fixtures)/*/}*.ts'",
646
686
  "format": "prettier . --write",
647
687
  "lint": "prettier . --check && lunte && tsc --noEmit",
648
688
  "build:bundle": "node scripts/build-bundle.mjs",
@@ -744,9 +784,11 @@
744
784
  }
745
785
  },
746
786
  "dependencies": {
747
- "@qvac/inference": "0.21.0",
748
- "@qvac/langdetect-text": "0.1.2",
749
- "@qvac/sdk": "0.21.0",
787
+ "@qvac/error": "^0.1.1",
788
+ "@qvac/inference": "^0.21.0",
789
+ "@qvac/langdetect-text": "^0.1.2",
790
+ "@qvac/logging": "0.1.1",
791
+ "@qvac/sdk": "^0.21.0",
750
792
  "@qvac/skills": "0.1.15",
751
793
  "@xmldom/xmldom": "^0.9.12",
752
794
  "asenion": "^0.1.0",