@qvac/harness 0.8.8 → 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 (115) hide show
  1. package/CHANGELOG.md +80 -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/mention.js +7 -2
  96. package/dist-lib/lib/tools/skills/overlay-crud.js +3 -2
  97. package/dist-lib/lib/tools/translate.d.ts +2 -2
  98. package/dist-lib/lib/tools/translate.js +5 -6
  99. package/dist-lib/lib/tools/weather-lookup/geocode.js +5 -4
  100. package/dist-lib/lib/web-search/engines/bing.js +2 -1
  101. package/dist-lib/lib/web-search/engines/google.js +2 -1
  102. package/dist-lib/lib/web-search/fetch-html.d.ts +5 -5
  103. package/dist-lib/lib/web-search/fetch-html.js +12 -15
  104. package/package.json +67 -25
  105. package/worker/harness-sidecar.mjs +12 -13
  106. package/dist-lib/lib/tools/create-documents.d.ts +0 -3
  107. package/dist-lib/lib/tools/create-documents.js +0 -95
  108. package/dist-lib/lib/tools/edit-documents.d.ts +0 -3
  109. package/dist-lib/lib/tools/edit-documents.js +0 -132
  110. package/dist-lib/lib/tools/file-glob.d.ts +0 -14
  111. package/dist-lib/lib/tools/file-glob.js +0 -118
  112. package/dist-lib/lib/tools/grep.d.ts +0 -5
  113. package/dist-lib/lib/tools/grep.js +0 -184
  114. package/dist-lib/lib/tools/read-files.d.ts +0 -5
  115. package/dist-lib/lib/tools/read-files.js +0 -157
@@ -1,4 +1,5 @@
1
1
  import process from 'process';
2
+ import { HarnessError } from '../errors.js';
2
3
  import { contextCandidates } from '../context-window.js';
3
4
  import { errorCode, errorMessage, isRecord } from '../guards.js';
4
5
  import { bufferBurst } from '../helpers/buffer-burst.js';
@@ -12,7 +13,7 @@ import { clipRawCall, hasMarkupCall, normalizeToolArgs, recoverMarkupToolCalls,
12
13
  import { INTENT_PARAM, INTENT_PARAM_DESCRIPTION, takeToolIntent } from '../helpers/tool-intent.js';
13
14
  import { HookPipeline } from '../hooks/pipeline.js';
14
15
  import { fallbackToolLabel } from '../helpers/tool-label.js';
15
- import { hlog } from '../log.js';
16
+ import { log } from '../log.js';
16
17
  import { resolveLlmGenerationParams } from '../models/llm-presets.js';
17
18
  import { toolDialectFor } from '../models/model-catalog.js';
18
19
  import { isContextOverflowError, isGrammarOverrunError, isSdkModelWedgedError } from '../models/sdk-faults.js';
@@ -55,7 +56,7 @@ function runWallClockBudgetMs() {
55
56
  const ms = Number(process.env.QVAC_RUN_BUDGET_MS);
56
57
  return ms > 0 ? ms : RUN_WALL_CLOCK_BUDGET_MS;
57
58
  }
58
- export const CONTEXT_OVERFLOW_MESSAGE = 'This message is too large for the model context window.';
59
+ export { CONTEXT_OVERFLOW_MESSAGE } from '../errors.js';
59
60
  const MAX_MUTE_ROUNDS = 1;
60
61
  const NARRATED_INTENT = /\b(I['’]ll|I will|Now I|Let me(?! know)|Next,? I)\b[^.!?]*[.!?]?\s*$/i;
61
62
  const MAX_WEB_SEARCHES_PER_TURN = 3;
@@ -282,7 +283,7 @@ function deniedCall() {
282
283
  async function executeCall(tool, args, { ctx, approve, signal, denied, sources = [], effect: classified, skipApprovals, onToolActivity, intent, display }) {
283
284
  const validationError = tool.validate?.(args, ctx);
284
285
  if (validationError) {
285
- hlog('tool rejected: arguments failed validation', { tool: tool.name });
286
+ log.info('tool rejected: arguments failed validation', { tool: tool.name });
286
287
  return guidanceResult(validationError);
287
288
  }
288
289
  const refusal = guardToolCall(tool.name, args, {
@@ -290,7 +291,7 @@ async function executeCall(tool, args, { ctx, approve, signal, denied, sources =
290
291
  externalWrite: tool.externalWrite === true
291
292
  });
292
293
  if (refusal) {
293
- hlog('tool refused: unverifiable value in payload', { tool: tool.name });
294
+ log.info('tool refused: unverifiable value in payload', { tool: tool.name });
294
295
  onToolActivity?.({
295
296
  type: 'tool-activity',
296
297
  name: tool.name,
@@ -303,13 +304,13 @@ async function executeCall(tool, args, { ctx, approve, signal, denied, sources =
303
304
  }
304
305
  const key = callKey(tool.name, args);
305
306
  if (denied?.has(key)) {
306
- hlog('tool denied already, not asking again', { tool: tool.name });
307
+ log.info('tool denied already, not asking again', { tool: tool.name });
307
308
  return deniedCall();
308
309
  }
309
310
  const effect = classified ?? tool.effect(args, ctx);
310
311
  const resource = tool.resource?.(args, ctx) ?? null;
311
312
  const autoRun = effect === 'read' || tool.autoApprove === true || skipApprovals === true;
312
- hlog('tool decision', {
313
+ log.info('tool decision', {
313
314
  tool: tool.name,
314
315
  effect,
315
316
  auto: autoRun,
@@ -326,7 +327,7 @@ async function executeCall(tool, args, { ctx, approve, signal, denied, sources =
326
327
  display: display ?? null
327
328
  }), signal)
328
329
  : false);
329
- hlog(`tool ${approved ? 'approved' : 'denied'}`, { tool: tool.name });
330
+ log.info(`tool ${approved ? 'approved' : 'denied'}`, { tool: tool.name });
330
331
  const decision = toolDecision({
331
332
  auto: tool.autoApprove === true || effect === 'read',
332
333
  skipAll: skipApprovals === true,
@@ -348,11 +349,11 @@ async function executeCall(tool, args, { ctx, approve, signal, denied, sources =
348
349
  let outcome = { kind: 'success' };
349
350
  try {
350
351
  output = await tool.execute(args, { ...ctx, signal, approval: decision });
351
- hlog('tool done', { tool: tool.name });
352
+ log.debug('tool done', { tool: tool.name });
352
353
  }
353
354
  catch (err) {
354
355
  const message = errorMessage(err);
355
- hlog('tool failed', { tool: tool.name, err: message });
356
+ log.warn('tool failed', { tool: tool.name, err: message });
356
357
  output = { error: message, code: errorCode(err) };
357
358
  outcome = { kind: 'error', message, error: err };
358
359
  }
@@ -524,7 +525,7 @@ async function* runRoundTokens(result, state, { sdk, signal, startInThink = fals
524
525
  if (state.overrun) {
525
526
  throw state.overrun;
526
527
  }
527
- hlog('stream ended', { events: tokenEvents, stopToken: state.stopToken });
528
+ log.info('stream ended', { events: tokenEvents, stopToken: state.stopToken });
528
529
  if (state.stopToken) {
529
530
  cancelThisRound();
530
531
  }
@@ -642,7 +643,7 @@ async function* runRecoveredRoundWith(sdk, args, modelId) {
642
643
  };
643
644
  }
644
645
  const replay = unprovenReplay(state, final);
645
- hlog('final', {
646
+ log.debug('final', {
646
647
  stopReason: final.stopReason,
647
648
  toolCalls: final.toolCalls?.length ?? 0,
648
649
  stopToken: state.stopToken,
@@ -678,7 +679,7 @@ async function* runRecoveredRoundWith(sdk, args, modelId) {
678
679
  };
679
680
  }
680
681
  if (recovered) {
681
- hlog('grammar overrun recovered', { round: args.round });
682
+ log.info('grammar overrun recovered', { round: args.round });
682
683
  return {
683
684
  contentText,
684
685
  final: acceptedFinal(state.rawContentText, state.rawText),
@@ -692,13 +693,13 @@ async function* runRecoveredRoundWith(sdk, args, modelId) {
692
693
  throw err;
693
694
  }
694
695
  if (emitted) {
695
- throw new Error(CONTEXT_OVERFLOW_MESSAGE, { cause: err });
696
+ throw new HarnessError('CONTEXT_WINDOW_EXCEEDED', [], { cause: err });
696
697
  }
697
698
  lastOverflow = err;
698
699
  }
699
700
  }
700
701
  if (lastOverflow) {
701
- throw new Error(CONTEXT_OVERFLOW_MESSAGE, { cause: lastOverflow });
702
+ throw new HarnessError('CONTEXT_WINDOW_EXCEEDED', [], { cause: lastOverflow });
702
703
  }
703
704
  throw new Error('context recovery produced no completion candidate');
704
705
  }
@@ -997,7 +998,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
997
998
  promptParts = promptUsageParts({ system, toolSchemas: sdkTools });
998
999
  logSelection();
999
1000
  }
1000
- hlog('round start', {
1001
+ log.debug('round start', {
1001
1002
  round,
1002
1003
  turns: history.length,
1003
1004
  tools: sdkTools?.length ?? 0,
@@ -1025,7 +1026,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1025
1026
  }
1026
1027
  history = recovered.history;
1027
1028
  collectStats(stats, final.stats, final.stopReason);
1028
- hlog('round stats', { round, wallMs: Date.now() - roundStarted, ...final.stats });
1029
+ log.debug('round stats', { round, wallMs: Date.now() - roundStarted, ...final.stats });
1029
1030
  const markup = stripToolResponseBlocks(truncateAtControlToken(final.raw.fullText) || recovered.unproven || '');
1030
1031
  let toolCalls = final.toolCalls?.length
1031
1032
  ? final.toolCalls
@@ -1040,7 +1041,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1040
1041
  toolCalls = rescued;
1041
1042
  }
1042
1043
  if (toolCalls.length) {
1043
- hlog('recovered printed skill load', { round });
1044
+ log.info('recovered printed skill load', { round });
1044
1045
  }
1045
1046
  }
1046
1047
  if (!toolCalls.length && enabled.some((tool) => tool.name === SKILL_TOOL_NAME)) {
@@ -1051,7 +1052,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1051
1052
  nudgedSkills.add(unquoteArg(call.arguments.name));
1052
1053
  }
1053
1054
  if (toolCalls.length) {
1054
- hlog('recovered skill-named call', { round });
1055
+ log.info('recovered skill-named call', { round });
1055
1056
  }
1056
1057
  }
1057
1058
  if (!toolCalls.length) {
@@ -1061,7 +1062,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1061
1062
  }
1062
1063
  const spoken = recovered.contentText.trim();
1063
1064
  if (enabled.length && !narrated && !replay && NARRATED_INTENT.test(spoken)) {
1064
- hlog('round narrated a tool call instead of making it', { round });
1065
+ log.info('round narrated a tool call instead of making it', { round });
1065
1066
  narrated = true;
1066
1067
  history = [
1067
1068
  ...history,
@@ -1075,7 +1076,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1075
1076
  outcome = 'answered';
1076
1077
  break;
1077
1078
  }
1078
- hlog('round produced no answer', { round, mute });
1079
+ log.info('round produced no answer', { round, mute });
1079
1080
  history = [
1080
1081
  ...history,
1081
1082
  { role: 'assistant', content: spoken },
@@ -1090,7 +1091,10 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1090
1091
  mute = 0;
1091
1092
  narrated = false;
1092
1093
  if (toolCalls.length > MAX_CALLS_PER_ROUND) {
1093
- hlog('round over the call cap; the rest are dropped', { round, calls: toolCalls.length });
1094
+ log.info('round over the call cap; the rest are dropped', {
1095
+ round,
1096
+ calls: toolCalls.length
1097
+ });
1094
1098
  toolCalls = toolCalls.slice(0, MAX_CALLS_PER_ROUND);
1095
1099
  }
1096
1100
  const resolved = await resolveToolCalls(toolCalls, {
@@ -1161,7 +1165,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1161
1165
  let execution;
1162
1166
  if (priorFailure) {
1163
1167
  blockedRepeats++;
1164
- hlog('repeat of a call that already failed; answered without running it', {
1168
+ log.info('repeat of a call that already failed; answered without running it', {
1165
1169
  tool: call.name,
1166
1170
  blockedRepeats
1167
1171
  });
@@ -1169,7 +1173,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1169
1173
  }
1170
1174
  else if (priorSuccess) {
1171
1175
  blockedRepeats++;
1172
- hlog('repeat of a read that already answered; not run again', {
1176
+ log.info('repeat of a read that already answered; not run again', {
1173
1177
  tool: call.name,
1174
1178
  blockedRepeats
1175
1179
  });
@@ -1241,7 +1245,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1241
1245
  runOutcomes.record(output, executionOutcome);
1242
1246
  if (searchCapped) {
1243
1247
  cappedCalls++;
1244
- hlog('web_search capped', { searches: webSearches });
1248
+ log.info('web_search capped', { searches: webSearches });
1245
1249
  }
1246
1250
  else if (tool?.name === WEB_SEARCH_TOOL_NAME &&
1247
1251
  !rejection &&
@@ -1362,7 +1366,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1362
1366
  }
1363
1367
  said = `${recovered.contentText}\n${answer}`;
1364
1368
  if (!said.trim()) {
1365
- hlog('degenerate wrap-up', { outcome });
1369
+ log.info('degenerate wrap-up', { outcome });
1366
1370
  yield {
1367
1371
  type: 'content',
1368
1372
  text: degenerateAnswer(outcome, history, runOutcomes.facts())
@@ -1,8 +1,8 @@
1
- import { harnessLogEnabled, hlog } from '../log.js';
1
+ import { log, logEnabled } from '../log.js';
2
2
  export function logToolPrompt({ promptPrefix, modelId, cacheKey, agentId, messageCount, responseFormat, tools = [], definitions, activeSkills, skillsSelected, enabledSkills, mentionedSkill, loadedSkills, toolOps }) {
3
- if (!harnessLogEnabled())
3
+ if (!logEnabled('debug'))
4
4
  return;
5
- hlog('sdk tool definitions', {
5
+ log.debug('sdk tool definitions', {
6
6
  promptPrefix,
7
7
  modelId,
8
8
  cacheKey: cacheKey ?? null,
@@ -1,7 +1,8 @@
1
1
  import b4a from 'b4a';
2
+ import { HarnessError } from '../errors.js';
2
3
  import { errorMessage } from '../guards.js';
3
4
  import { AbortedError } from '../helpers/race-abort.js';
4
- import { hlog } from '../log.js';
5
+ import { log } from '../log.js';
5
6
  import { buildWavBuffer } from '../wav-encode.js';
6
7
  import { normalizeForSpeech } from './speech-text.js';
7
8
  export const SUPERTONIC_SAMPLE_RATE = 44100;
@@ -82,7 +83,7 @@ async function readHead(seconds, signal) {
82
83
  }
83
84
  }
84
85
  catch (err) {
85
- hlog('audio decode/resample failed', { error: errorMessage(err) });
86
+ log.warn('audio decode/resample failed', { error: errorMessage(err) });
86
87
  return null;
87
88
  }
88
89
  return head;
@@ -190,7 +191,7 @@ export async function* speakStreamOnce(sdk, modelId, text) {
190
191
  session.end();
191
192
  }
192
193
  catch (err) {
193
- hlog('tts stream input failed', { err: errorMessage(err) });
194
+ log.warn('tts stream input failed', { err: errorMessage(err) });
194
195
  inputError = err;
195
196
  destroyQuietly(session);
196
197
  }
@@ -208,7 +209,7 @@ export async function* speakStreamOnce(sdk, modelId, text) {
208
209
  throw inputError;
209
210
  }
210
211
  if (wrotePronounceable && !yieldedAudio) {
211
- throw new Error('Text-to-speech returned no audio.');
212
+ throw new HarnessError('TTS_NO_AUDIO');
212
213
  }
213
214
  }
214
215
  catch (err) {
@@ -236,7 +237,7 @@ function writeSpeakable(session, fragment) {
236
237
  export function speakable(text) {
237
238
  const { text: spoken, dropped } = normalizeForSpeech(text);
238
239
  if (dropped.length) {
239
- hlog('tts dropped unspeakable characters', { chars: [...new Set(dropped)].join('') });
240
+ log.info('tts dropped unspeakable characters', { chars: [...new Set(dropped)].join('') });
240
241
  }
241
242
  return spoken;
242
243
  }
@@ -251,7 +252,7 @@ export async function speakOnce(sdk, modelId, text) {
251
252
  samples.push(sample);
252
253
  }
253
254
  if (!(await result.done)) {
254
- throw new Error('tts synthesis did not complete');
255
+ throw new HarnessError('TTS_INCOMPLETE');
255
256
  }
256
257
  return samples;
257
258
  }
@@ -1,12 +1,13 @@
1
1
  import EventEmitter from 'events';
2
2
  import fs from 'fs';
3
3
  import { z } from 'zod';
4
+ import { HarnessError } from './errors.js';
4
5
  import { settleOnClose } from './rpc-session.js';
5
6
  import { harnessClient } from '../spec/calls.js';
6
7
  import HRPC from '../spec/hrpc/index.js';
7
8
  import { unsupportedKvCacheDeletion } from './capabilities/kv-cache.js';
8
9
  import { readModelSourcePreview } from './capabilities/preview-model-source.js';
9
- import { hlog } from './log.js';
10
+ import { log } from './log.js';
10
11
  import { assertEmbedDevice } from './models/embed-config.js';
11
12
  import { addonTaskType } from './models/model-task.js';
12
13
  import { errorCode, errorMessage, isRecord } from './guards.js';
@@ -91,7 +92,7 @@ export class RemoteHarness extends EventEmitter {
91
92
  await session.client.setCustomModels({ models: this._customModels });
92
93
  }
93
94
  catch (err) {
94
- hlog('custom model restore failed', { message: errorMessage(err) });
95
+ log.warn('custom model restore failed', { message: errorMessage(err) });
95
96
  }
96
97
  }
97
98
  return session;
@@ -159,11 +160,11 @@ export class RemoteHarness extends EventEmitter {
159
160
  return;
160
161
  }
161
162
  context.signal?.addEventListener?.('abort', onAbort, { once: true });
162
- hlog('client wire open', { model: context.model, capability: open.capability });
163
+ log.debug('client wire open', { model: context.model, capability: open.capability });
163
164
  for await (const event of frames(duplex)) {
164
165
  received++;
165
166
  if (event.type !== 'content' && event.type !== 'thinking') {
166
- hlog('client frame', { type: event.type, n: received });
167
+ log.debug('client frame', { type: event.type, n: received });
167
168
  }
168
169
  if (event.type === 'done') {
169
170
  break;
@@ -188,7 +189,7 @@ export class RemoteHarness extends EventEmitter {
188
189
  duplex.write({ type: 'decision', callId, approved: false });
189
190
  }
190
191
  })
191
- .catch((err) => hlog('approval reply failed', { callId, message: errorMessage(err) }));
192
+ .catch((err) => log.warn('approval reply failed', { callId, message: errorMessage(err) }));
192
193
  continue;
193
194
  }
194
195
  if (event.type === 'attach-ask') {
@@ -207,7 +208,7 @@ export class RemoteHarness extends EventEmitter {
207
208
  });
208
209
  }
209
210
  })
210
- .catch((err) => hlog('attach reply failed', { callId, message: errorMessage(err) }));
211
+ .catch((err) => log.warn('attach reply failed', { callId, message: errorMessage(err) }));
211
212
  continue;
212
213
  }
213
214
  if (event.type === 'fetch-ask') {
@@ -224,7 +225,7 @@ export class RemoteHarness extends EventEmitter {
224
225
  }
225
226
  duplex.write({ type: 'fetch-result', callId, message: errorMessage(err) });
226
227
  })
227
- .catch((err) => hlog('fetch reply failed', { callId, message: errorMessage(err) }));
228
+ .catch((err) => log.warn('fetch reply failed', { callId, message: errorMessage(err) }));
228
229
  continue;
229
230
  }
230
231
  if (event.type === 'drive-ask') {
@@ -247,7 +248,7 @@ export class RemoteHarness extends EventEmitter {
247
248
  });
248
249
  }
249
250
  })
250
- .catch((err) => hlog('drive reply failed', { callId, message: errorMessage(err) }));
251
+ .catch((err) => log.warn('drive reply failed', { callId, message: errorMessage(err) }));
251
252
  continue;
252
253
  }
253
254
  if (event.type === 'rag-ask') {
@@ -266,7 +267,7 @@ export class RemoteHarness extends EventEmitter {
266
267
  });
267
268
  }
268
269
  })
269
- .catch((err) => hlog('rag reply failed', { callId, message: errorMessage(err) }));
270
+ .catch((err) => log.warn('rag reply failed', { callId, message: errorMessage(err) }));
270
271
  continue;
271
272
  }
272
273
  if (event.type === 'question-ask') {
@@ -290,7 +291,7 @@ export class RemoteHarness extends EventEmitter {
290
291
  }
291
292
  yield wire;
292
293
  }
293
- hlog('client wire ended', { received });
294
+ log.info('client wire ended', { received });
294
295
  }
295
296
  catch (err) {
296
297
  if (context.signal?.aborted) {
@@ -302,7 +303,7 @@ export class RemoteHarness extends EventEmitter {
302
303
  throw died(`harness died mid-run: ${errorMessage(err)}`);
303
304
  }
304
305
  finally {
305
- hlog('client run end', { received, aborted: Boolean(context.signal?.aborted) });
306
+ log.debug('client run end', { received, aborted: Boolean(context.signal?.aborted) });
306
307
  context.signal?.removeEventListener?.('abort', onAbort);
307
308
  duplex?.destroy();
308
309
  closed = true;
@@ -705,10 +706,10 @@ function abortable(promise, signal) {
705
706
  return new Promise((resolve, reject) => {
706
707
  if (signal.aborted) {
707
708
  promise.then(noop, noop);
708
- reject(new Error('embed aborted'));
709
+ reject(new HarnessError('EMBED_ABORTED'));
709
710
  return;
710
711
  }
711
- const onAbort = () => reject(new Error('embed aborted'));
712
+ const onAbort = () => reject(new HarnessError('EMBED_ABORTED'));
712
713
  signal.addEventListener?.('abort', onAbort, { once: true });
713
714
  promise.then(resolve, reject).finally(() => signal.removeEventListener?.('abort', onAbort));
714
715
  });
@@ -853,7 +854,7 @@ function answerQuestion(event, { askUser, reply }) {
853
854
  options: Array.isArray(event.options) ? event.options.map(String) : undefined
854
855
  }))
855
856
  .then((answer) => reply({ type: 'question-result', callId, answer: answer ?? '' }), (err) => reply({ type: 'question-result', callId, message: errorMessage(err) }))
856
- .catch((err) => hlog('question reply failed', { callId, message: errorMessage(err) }));
857
+ .catch((err) => log.warn('question reply failed', { callId, message: errorMessage(err) }));
857
858
  }
858
859
  async function bridgeRag(event, context) {
859
860
  if (!context.ragSearch) {
@@ -878,15 +879,15 @@ async function* frames(stream) {
878
879
  let error = null;
879
880
  stream.on('data', (frame) => queue.push(frame));
880
881
  stream.on('end', () => {
881
- hlog('client stream event', { event: 'end' });
882
+ log.debug('client stream event', { event: 'end' });
882
883
  queue.end();
883
884
  });
884
885
  stream.on('close', () => {
885
- hlog('client stream event', { event: 'close' });
886
+ log.debug('client stream event', { event: 'close' });
886
887
  queue.end();
887
888
  });
888
889
  stream.on('error', (err) => {
889
- hlog('client stream event', { event: 'error', err: err.message });
890
+ log.warn('client stream event', { event: 'error', err: err.message });
890
891
  error = err;
891
892
  queue.end();
892
893
  });