@qvac/harness 0.3.1 → 0.3.3

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 (30) hide show
  1. package/CHANGELOG.md +79 -1
  2. package/dist/harness.bundle +359 -175
  3. package/dist/prebuilds-manifest.json +8 -0
  4. package/dist-lib/lib/capabilities/completion.d.ts +2 -1
  5. package/dist-lib/lib/capabilities/completion.js +5 -6
  6. package/dist-lib/lib/tools/exec/adapters/adapter.d.ts +3 -1
  7. package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.d.ts +5 -0
  8. package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.js +28 -2
  9. package/dist-lib/lib/tools/exec/adapters/python/interrupt-timer.d.ts +5 -0
  10. package/dist-lib/lib/tools/exec/adapters/python/interrupt-timer.js +29 -0
  11. package/dist-lib/lib/tools/exec/adapters/python/named-hosts.d.ts +2 -0
  12. package/dist-lib/lib/tools/exec/adapters/python/named-hosts.js +15 -0
  13. package/dist-lib/lib/tools/exec/adapters/python/pyodide.d.ts +3 -1
  14. package/dist-lib/lib/tools/exec/adapters/python/pyodide.js +41 -7
  15. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +9 -3
  16. package/dist-lib/lib/tools/exec/adapters/python/run-session.d.ts +2 -0
  17. package/dist-lib/lib/tools/exec/adapters/python/run-session.js +19 -11
  18. package/dist-lib/lib/tools/exec/adapters/python/worker/client.d.ts +2 -1
  19. package/dist-lib/lib/tools/exec/adapters/python/worker/client.js +16 -3
  20. package/dist-lib/lib/tools/exec/adapters/python/worker/entry.js +15 -2
  21. package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.d.ts +6 -0
  22. package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.js +6 -2
  23. package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-bare.d.ts +1 -0
  24. package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-bare.js +8 -0
  25. package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.d.ts +1 -0
  26. package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.js +8 -0
  27. package/dist-lib/lib/tools/exec.js +36 -3
  28. package/package.json +16 -7
  29. package/dist-lib/lib/capabilities/vision-history.d.ts +0 -3
  30. package/dist-lib/lib/capabilities/vision-history.js +0 -21
@@ -1,5 +1,13 @@
1
1
  import Channel from 'bare-channel';
2
2
  import safetyCatch from 'safety-catch';
3
+ export function spawnInlineThread(source, data) {
4
+ const Thread = Bare.Thread;
5
+ const thread = new Thread('inline-thread.js', {
6
+ data,
7
+ source: `const data = Bare.Thread.self.data\n${source}`
8
+ });
9
+ return () => thread.join();
10
+ }
3
11
  export function spawnPythonWorker(entry, data) {
4
12
  const channel = new Channel();
5
13
  const thread = new Bare.Thread(entry, { data: { handle: channel.handle, ...data } });
@@ -11,3 +11,4 @@ export type WorkerData = {
11
11
  readonly log: boolean;
12
12
  };
13
13
  export declare function spawnPythonWorker(entry: string, data: WorkerData): WorkerLink;
14
+ export declare function spawnInlineThread(source: string, data: object): () => void;
@@ -43,3 +43,11 @@ export function spawnPythonWorker(entry, data) {
43
43
  unref: () => worker.unref()
44
44
  };
45
45
  }
46
+ export function spawnInlineThread(source, data) {
47
+ const worker = new Worker(`const data = require('worker_threads').workerData\n${source}`, {
48
+ eval: true,
49
+ workerData: data
50
+ });
51
+ worker.unref();
52
+ return () => { };
53
+ }
@@ -4,6 +4,7 @@ import { AbortedError } from '../helpers/race-abort.js';
4
4
  import { spawnEnv } from '../helpers/spawn-path.js';
5
5
  import { checkPdfBuildSource, checkBareLengthArgs, checkPptxBuildSource, checkPythonSource, checkUnstagedRead, createPythonAdapter, isPythonLanguage, parseInputs, parseOutputs, parsePackages, parseWorkdir, pinned, runsThroughPyodide, unwrapPythonSource } from './exec/adapters/python/python-adapter.js';
6
6
  import { pythonCreateEffect } from './exec/adapters/python/create-effect.js';
7
+ import { namedHosts } from './exec/adapters/python/named-hosts.js';
7
8
  import { pythonReachesNetwork } from './exec/adapters/python/net-shim.js';
8
9
  import { createShellAdapter } from './exec/adapters/shell/shell-adapter.js';
9
10
  import { routeObsidianCommand } from './exec/obsidian/route.js';
@@ -82,6 +83,34 @@ function checkFileArgs(args, python) {
82
83
  function truncateLabel(text) {
83
84
  return text.length > 120 ? `${text.slice(0, 120)}…` : text;
84
85
  }
86
+ const CONNECT_ANSWER_MS = 60_000;
87
+ function connectAsker(askUser) {
88
+ return async (host) => {
89
+ let timer;
90
+ const late = new Promise((resolve) => {
91
+ timer = setTimeout(() => resolve(null), CONNECT_ANSWER_MS);
92
+ });
93
+ try {
94
+ const answer = await Promise.race([
95
+ askUser({
96
+ question: `This Python run wants to connect to ${host}, which its approved code does not name. Allow it for this run?`,
97
+ options: ['Allow', 'Deny']
98
+ }),
99
+ late
100
+ ]);
101
+ return /^allow/i.test((answer ?? '').trim());
102
+ }
103
+ finally {
104
+ clearTimeout(timer);
105
+ }
106
+ };
107
+ }
108
+ function networkPrefix(source) {
109
+ const hosts = namedHosts(source);
110
+ if (hosts.length === 0)
111
+ return ' · network: no host named in the code';
112
+ return ` · network: ${hosts.join(', ')}`;
113
+ }
85
114
  function packageSuffix(args) {
86
115
  if (!Array.isArray(args.packages) || args.packages.length === 0)
87
116
  return '';
@@ -428,7 +457,7 @@ export function createExecTool({ spawn, killProcess, loadPyodide, disablePyodide
428
457
  return PACKAGES_NEED_PYTHON_ERROR;
429
458
  return checkFileArgs(rawArgs, python);
430
459
  },
431
- resource(rawArgs) {
460
+ resource(rawArgs, ctx) {
432
461
  const args = withInferredLanguage(rawArgs, disablePyodide);
433
462
  const script = typeof args.script === 'string' ? args.script : '';
434
463
  if (script.trim() && !script.includes('\n')) {
@@ -438,8 +467,10 @@ export function createExecTool({ spawn, killProcess, loadPyodide, disablePyodide
438
467
  if (!command)
439
468
  return null;
440
469
  const workdir = typeof args.workdir === 'string' && args.workdir.trim() ? args.workdir.trim() : null;
470
+ const source = unwrapPythonSource(command) ?? command;
471
+ const online = pythonNetwork && ctx?.sandboxMode !== true;
441
472
  const body = runsThroughPyodide(args.language, disablePyodide)
442
- ? `python${packageSuffix(args)}${fileSuffix(args)}: ${(unwrapPythonSource(command) ?? command).slice(0, 120)}`
473
+ ? `python${online ? networkPrefix(source) : ''}${packageSuffix(args)}${fileSuffix(args)}: ${source.slice(0, 120)}`
443
474
  : command;
444
475
  return {
445
476
  kind: 'command',
@@ -515,7 +546,9 @@ export function createExecTool({ spawn, killProcess, loadPyodide, disablePyodide
515
546
  turnImages: ctx?.turnImages,
516
547
  runBudget: ctx?.runBudget,
517
548
  sandboxMode: ctx?.sandboxMode,
518
- toolNames: ctx?.toolNames
549
+ toolNames: ctx?.toolNames,
550
+ approval: ctx?.approval,
551
+ confirmConnect: ctx?.askUser ? connectAsker(ctx.askUser) : undefined
519
552
  });
520
553
  return await execution.run({
521
554
  cwd,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qvac/harness",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "description": "An AI harness implementing generate(context) via @qvac/sdk. Knows nothing about workbench, hyperdb, or chunks — just inference in, events out.",
6
6
  "exports": {
@@ -220,6 +220,10 @@
220
220
  "types": "./dist-lib/lib/tools/exec/adapters/python/guarded-fetch.d.ts",
221
221
  "default": "./dist-lib/lib/tools/exec/adapters/python/guarded-fetch.js"
222
222
  },
223
+ "./lib/tools/exec/adapters/python/interrupt-timer.ts": {
224
+ "types": "./dist-lib/lib/tools/exec/adapters/python/interrupt-timer.d.ts",
225
+ "default": "./dist-lib/lib/tools/exec/adapters/python/interrupt-timer.js"
226
+ },
223
227
  "./lib/tools/exec/adapters/python/loader.ts": {
224
228
  "types": "./dist-lib/lib/tools/exec/adapters/python/loader.d.ts",
225
229
  "default": "./dist-lib/lib/tools/exec/adapters/python/loader.js"
@@ -232,6 +236,10 @@
232
236
  "types": "./dist-lib/lib/tools/exec/adapters/python/module-distributions.d.ts",
233
237
  "default": "./dist-lib/lib/tools/exec/adapters/python/module-distributions.js"
234
238
  },
239
+ "./lib/tools/exec/adapters/python/named-hosts.ts": {
240
+ "types": "./dist-lib/lib/tools/exec/adapters/python/named-hosts.d.ts",
241
+ "default": "./dist-lib/lib/tools/exec/adapters/python/named-hosts.js"
242
+ },
235
243
  "./lib/tools/exec/adapters/python/net-shim.ts": {
236
244
  "types": "./dist-lib/lib/tools/exec/adapters/python/net-shim.d.ts",
237
245
  "default": "./dist-lib/lib/tools/exec/adapters/python/net-shim.js"
@@ -686,10 +694,10 @@
686
694
  }
687
695
  },
688
696
  "dependencies": {
689
- "@qvac/inference": "0.19.0",
697
+ "@qvac/inference": "0.20.0",
690
698
  "@qvac/langdetect-text": "0.1.2",
691
- "@qvac/sdk": "0.19.0",
692
- "@qvac/skills": "0.1.7",
699
+ "@qvac/sdk": "0.20.0",
700
+ "@qvac/skills": "0.1.9",
693
701
  "asenion": "^0.1.0",
694
702
  "b4a": "^1.8.1",
695
703
  "bare-abort-controller": "^1.1.2",
@@ -748,7 +756,9 @@
748
756
  "corestore": "^7.11.0",
749
757
  "holepunch-types": "^0.2.1",
750
758
  "hyperdrive": "^13.3.3",
759
+ "jsdom": "~30.0.1",
751
760
  "lunte": "^1.8.3",
761
+ "mermaid": "~11.16.1",
752
762
  "prettier": "^3.6.2",
753
763
  "prettier-config-holepunch": "^2.0.0",
754
764
  "prettytap": "^0.2.0",
@@ -761,8 +771,8 @@
761
771
  "localwatch@1.4.3": "patches/localwatch@1.4.3.patch",
762
772
  "hypercore-blob-server@1.15.0": "patches/hypercore-blob-server@1.15.0.patch",
763
773
  "bare-stow@0.1.5": "patches/bare-stow@0.1.5.patch",
764
- "@qvac/inference@0.19.0": "patches/@qvac%2Finference@0.19.0.patch",
765
- "brittle@4.1.0": "patches/brittle@4.1.0.patch"
774
+ "brittle@4.1.0": "patches/brittle@4.1.0.patch",
775
+ "@qvac/inference@0.20.0": "patches/@qvac%2Finference@0.20.0.patch"
766
776
  },
767
777
  "repository": {
768
778
  "type": "git",
@@ -775,7 +785,6 @@
775
785
  },
776
786
  "homepage": "https://github.com/tetherto/qvac-harness",
777
787
  "overrides": {
778
- "@qvac/asr-ggml": "0.4.2",
779
788
  "bare-runtime": "^1.30.0",
780
789
  "bare-module": "6.4.0",
781
790
  "bare-process": "4.4.1",
@@ -1,3 +0,0 @@
1
- import type { Message } from './completion.ts';
2
- export declare function projectVisionHistory(history: ReadonlyArray<Message>): Message[];
3
- export declare function visionHistoryCacheKey(cacheKey: string | undefined, history: ReadonlyArray<Message>): string | undefined;
@@ -1,21 +0,0 @@
1
- const CACHE_VERSION = 'vision-history-v1';
2
- export function projectVisionHistory(history) {
3
- return history.flatMap((message) => {
4
- const attachments = message.attachments;
5
- if (message.role !== 'user' || !attachments || attachments.length < 2)
6
- return [message];
7
- return attachments.map((attachment, index) => ({
8
- ...message,
9
- content: index === attachments.length - 1 ? message.content : '\n',
10
- attachments: [attachment]
11
- }));
12
- });
13
- }
14
- export function visionHistoryCacheKey(cacheKey, history) {
15
- if (!cacheKey || !history.some(hasMultipleImages))
16
- return cacheKey;
17
- return `${cacheKey}-${CACHE_VERSION}`;
18
- }
19
- function hasMultipleImages(message) {
20
- return message.role === 'user' && (message.attachments?.length ?? 0) > 1;
21
- }