@qvac/harness 0.2.8 → 0.2.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.
- package/README.md +15 -7
- package/dist/harness.bundle +13 -12
- package/dist-lib/index.d.ts +2 -1
- package/dist-lib/index.js +2 -1
- package/dist-lib/lib/capabilities/completion.d.ts +36 -2
- package/dist-lib/lib/capabilities/completion.js +150 -27
- package/dist-lib/lib/connect.js +3 -1
- package/dist-lib/lib/contract-types.d.ts +2 -0
- package/dist-lib/lib/docx/index.js +4 -27
- package/dist-lib/lib/hooks/built-in/index.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/index.js +1 -0
- package/dist-lib/lib/hooks/built-in/sandbox.d.ts +5 -0
- package/dist-lib/lib/hooks/built-in/sandbox.js +43 -0
- package/dist-lib/lib/hooks/built-in/skill-catalog.js +7 -2
- package/dist-lib/lib/hooks/registry.js +2 -1
- package/dist-lib/lib/hooks/types.d.ts +1 -0
- package/dist-lib/lib/office/index.d.ts +6 -0
- package/dist-lib/lib/office/index.js +38 -0
- package/dist-lib/lib/office/pptx.d.ts +1 -0
- package/dist-lib/lib/office/pptx.js +26 -0
- package/dist-lib/lib/office/python.d.ts +7 -0
- package/dist-lib/lib/office/python.js +32 -0
- package/dist-lib/lib/office/xlsx.d.ts +1 -0
- package/dist-lib/lib/office/xlsx.js +26 -0
- package/dist-lib/lib/serve.js +1 -0
- package/dist-lib/lib/tools/edit-image.js +1 -1
- package/dist-lib/lib/tools/generate-image.js +1 -1
- package/dist-lib/lib/tools/generate-music.js +1 -1
- package/dist-lib/lib/tools/http-request.js +3 -3
- package/dist-lib/lib/tools/mcp-call.js +3 -3
- package/dist-lib/lib/tools/skill.js +4 -2
- package/dist-lib/lib/tools/skills/auth-resolver.d.ts +1 -0
- package/dist-lib/lib/tools/skills/auth-resolver.js +59 -29
- package/dist-lib/lib/tools/toolsets.d.ts +1 -0
- package/dist-lib/lib/tools/toolsets.js +1 -0
- package/dist-lib/lib/tools/weather-lookup.js +9 -3
- package/dist-lib/lib/tools/web-fetch.js +6 -3
- package/dist-lib/lib/tools/web-search.js +5 -2
- package/dist-lib/lib/wire.js +28 -0
- package/dist-lib/schema/harness.js +7 -3
- package/dist-lib/spec/hrpc/messages.js +28 -10
- package/dist-lib/spec/hyperschema/index.js +28 -10
- package/dist-lib/spec/hyperschema/schema.json +19 -1
- package/dist-lib/spec/hyperschema/types.d.ts +4 -2
- package/package.json +10 -6
- package/.claude/skills/asana/SKILL.md +0 -86
- package/.claude/skills/gh-stack/SKILL.md +0 -181
- package/.claude/skills/gh-stack/references/commands.md +0 -177
- package/.claude/skills/gh-stack/references/stack-design.md +0 -97
- package/.claude/skills/gh-stack/references/troubleshooting.md +0 -159
- package/.claude/skills/js-house-style/SKILL.md +0 -671
- package/.claude/skills/kvcache/SKILL.md +0 -171
- package/.claude/skills/pr-create/SKILL.md +0 -180
- package/.claude/skills/pr-review/SKILL.md +0 -414
- package/CLAUDE.md +0 -39
- package/docs/decisions/2026-05-29-kvcache-tool-turns.md +0 -75
- package/docs/decisions/2026-06-02-kvcache-tool-turns-reenabled.md +0 -127
- package/docs/decisions/2026-06-23-android-cpu-llm-defaults.md +0 -126
- package/docs/decisions/2026-07-29-assistant-owned-storage-layout.md +0 -88
- package/docs/decisions/2026-08-04-sidecar-orphan-reaping.md +0 -79
- package/docs/decisions/2026-08-11-pyodide-worker-thread.md +0 -68
- package/docs/decisions/2026-08-17-harness-hook-pipeline.md +0 -84
- package/docs/decisions/2026-08-17-harness-outbound-egress-guard.md +0 -176
- package/docs/decisions/2026-08-20-chat-prompt-prefix-is-stable.md +0 -95
- package/docs/decisions/2026-08-20-kvcache-static-placement.md +0 -109
- package/docs/decisions/2026-08-21-vendored-python-wheels.md +0 -85
- package/docs/decisions/2026-08-26-exec-is-not-gated-by-skills.md +0 -64
- package/docs/decisions/2026-08-27-writable-skills-overlay.md +0 -56
- package/docs/decisions/2026-09-01-skill-operation-manifests.md +0 -53
- package/docs/decisions/2026-09-01-usage-breakdown-is-measured.md +0 -78
- package/docs/decisions/2026-09-03-usage-breakdown-reported-by-the-harness.md +0 -136
- package/docs/decisions/2026-09-10-skill-wording-is-measured-offline.md +0 -63
- package/docs/decisions/2026-09-16-a-lookup-reports-the-place-it-resolved.md +0 -70
- package/docs/decisions/INDEX.md +0 -40
- package/docs/decisions/TEMPLATE.md +0 -32
- package/docs/kvcache-reference.md +0 -655
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { extractDocxText } from '../docx/index.js';
|
|
3
|
+
import { extractWithPython } from './python.js';
|
|
4
|
+
import { XLSX_EXTRACT } from './xlsx.js';
|
|
5
|
+
import { PPTX_EXTRACT } from './pptx.js';
|
|
6
|
+
export { closePythonWorker as closeOfficeWorker } from '../tools/exec/adapters/python/worker/client.js';
|
|
7
|
+
const OFFICE_MIME_EXTENSIONS = {
|
|
8
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx',
|
|
9
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
|
|
10
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation': '.pptx'
|
|
11
|
+
};
|
|
12
|
+
export async function extractOfficeText(bytes, fileName, options = {}) {
|
|
13
|
+
const mimeType = options.mimeType?.split(';')[0]?.trim().toLowerCase();
|
|
14
|
+
const suffix = path.extname(fileName).toLowerCase();
|
|
15
|
+
const extension = ['.docx', '.xlsx', '.pptx'].includes(suffix)
|
|
16
|
+
? suffix
|
|
17
|
+
: OFFICE_MIME_EXTENSIONS[mimeType ?? ''];
|
|
18
|
+
if (extension === '.docx') {
|
|
19
|
+
return extractDocxText(bytes, fileName, options);
|
|
20
|
+
}
|
|
21
|
+
if (extension === '.xlsx') {
|
|
22
|
+
return extractWithPython(bytes, fileName, {
|
|
23
|
+
format: 'XLSX',
|
|
24
|
+
packages: ['openpyxl==3.1.5'],
|
|
25
|
+
source: XLSX_EXTRACT,
|
|
26
|
+
signal: options.signal
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (extension === '.pptx') {
|
|
30
|
+
return extractWithPython(bytes, fileName, {
|
|
31
|
+
format: 'PPTX',
|
|
32
|
+
packages: ['python-pptx==1.0.2'],
|
|
33
|
+
source: PPTX_EXTRACT,
|
|
34
|
+
signal: options.signal
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`Unsupported Office document: ${fileName}`);
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PPTX_EXTRACT: string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const PPTX_EXTRACT = `from pptx import Presentation
|
|
2
|
+
from pptx.enum.shapes import MSO_SHAPE_TYPE
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
def text(shapes):
|
|
6
|
+
for shape in shapes:
|
|
7
|
+
if shape.shape_type == MSO_SHAPE_TYPE.GROUP:
|
|
8
|
+
yield from text(shape.shapes)
|
|
9
|
+
elif shape.has_text_frame:
|
|
10
|
+
if shape.text.strip():
|
|
11
|
+
yield shape.text
|
|
12
|
+
elif shape.has_table:
|
|
13
|
+
for row in shape.table.rows:
|
|
14
|
+
cells = ["" if cell.is_spanned else cell.text.replace("\\n", " ") for cell in row.cells]
|
|
15
|
+
if any(cell.strip() for cell in cells):
|
|
16
|
+
yield " | ".join(cells)
|
|
17
|
+
|
|
18
|
+
with Path("extracted.txt").open("w", encoding="utf-8") as output:
|
|
19
|
+
for number, slide in enumerate(Presentation("input.pptx").slides, 1):
|
|
20
|
+
heading = False
|
|
21
|
+
for block in text(slide.shapes):
|
|
22
|
+
if not heading:
|
|
23
|
+
output.write("Slide " + str(number) + "\\n")
|
|
24
|
+
heading = True
|
|
25
|
+
output.write(block + "\\n")
|
|
26
|
+
`;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AbortSignal } from 'bare-abort-controller';
|
|
2
|
+
export declare function extractWithPython(bytes: Uint8Array, fileName: string, { format, packages, source, signal }: {
|
|
3
|
+
readonly format: string;
|
|
4
|
+
readonly packages: readonly string[];
|
|
5
|
+
readonly source: string;
|
|
6
|
+
readonly signal?: AbortSignal | undefined;
|
|
7
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import b4a from 'b4a';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { runPython } from '../tools/exec/adapters/python/pyodide.js';
|
|
4
|
+
import { planVendoredInstall, vendoredManifest, wheelsDir } from '../tools/exec/adapters/python/wheels.js';
|
|
5
|
+
const MAX_OFFICE_BYTES = 20 * 1024 * 1024;
|
|
6
|
+
export async function extractWithPython(bytes, fileName, { format, packages, source, signal }) {
|
|
7
|
+
if (bytes.byteLength > MAX_OFFICE_BYTES) {
|
|
8
|
+
throw new Error(`document is too large to extract: ${bytes.byteLength} bytes, limit ${MAX_OFFICE_BYTES}`);
|
|
9
|
+
}
|
|
10
|
+
const dir = wheelsDir();
|
|
11
|
+
const manifest = vendoredManifest(dir);
|
|
12
|
+
const install = planVendoredInstall(packages, { dir, manifest });
|
|
13
|
+
if (!dir ||
|
|
14
|
+
!manifest ||
|
|
15
|
+
!install ||
|
|
16
|
+
!['micropip', ...install.packages].every((name) => Object.keys(manifest.files).some((file) => file.replaceAll('_', '-').startsWith(`${name}-`) && fs.existsSync(`${dir}/${file}`)))) {
|
|
17
|
+
throw new Error(`bundled ${format} extraction packages are unavailable`);
|
|
18
|
+
}
|
|
19
|
+
const result = await runPython(source, {
|
|
20
|
+
packages,
|
|
21
|
+
inputs: [{ path: `input.${format.toLowerCase()}`, bytes }],
|
|
22
|
+
outputs: ['extracted.txt'],
|
|
23
|
+
signal
|
|
24
|
+
});
|
|
25
|
+
if (result.exitCode !== 0)
|
|
26
|
+
throw new Error(`${fileName}: ${result.stderr.trim()}`);
|
|
27
|
+
const output = result.files?.get('extracted.txt');
|
|
28
|
+
if (!output) {
|
|
29
|
+
throw new Error(result.rejected?.[0]?.reason ?? `${format} extraction produced no text file`);
|
|
30
|
+
}
|
|
31
|
+
return b4a.toString(output, 'utf8');
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const XLSX_EXTRACT: string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const XLSX_EXTRACT = `from openpyxl import load_workbook
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
formulas = load_workbook("input.xlsx", read_only=True, data_only=False, keep_links=False)
|
|
5
|
+
values = load_workbook("input.xlsx", read_only=True, data_only=True, keep_links=False)
|
|
6
|
+
try:
|
|
7
|
+
with Path("extracted.txt").open("w", encoding="utf-8") as output:
|
|
8
|
+
for sheet in formulas.worksheets:
|
|
9
|
+
heading = False
|
|
10
|
+
cached = values[sheet.title]
|
|
11
|
+
sheet.reset_dimensions()
|
|
12
|
+
cached.reset_dimensions()
|
|
13
|
+
for cells, results in zip(sheet.iter_rows(), cached.iter_rows()):
|
|
14
|
+
row = [result.value if cell.data_type == "f" and result.value is not None else cell.value
|
|
15
|
+
for cell, result in zip(cells, results)]
|
|
16
|
+
if not any(value is not None and str(value).strip() for value in row):
|
|
17
|
+
continue
|
|
18
|
+
if not heading:
|
|
19
|
+
output.write("Sheet: " + sheet.title + "\\n")
|
|
20
|
+
heading = True
|
|
21
|
+
output.write(" | ".join("" if value is None else str(value).replace("\\n", " ")
|
|
22
|
+
for value in row) + "\\n")
|
|
23
|
+
finally:
|
|
24
|
+
formulas.close()
|
|
25
|
+
values.close()
|
|
26
|
+
`;
|
package/dist-lib/lib/serve.js
CHANGED
|
@@ -414,6 +414,7 @@ function completion(harness) {
|
|
|
414
414
|
thinkingDefault: frame.thinkingDefault ?? undefined,
|
|
415
415
|
generationParams: frame.generationParams ?? undefined,
|
|
416
416
|
responseFormat: frame.responseFormat ?? undefined,
|
|
417
|
+
captureInferenceCalls: frame.captureInferenceCalls ?? undefined,
|
|
417
418
|
messages: (frame.messages ?? []).map(({ role, content, attachments }) => ({
|
|
418
419
|
role,
|
|
419
420
|
content,
|
|
@@ -170,7 +170,7 @@ export function createEditImageTool(runtime, { loadMedia = defaultLoadMedia, res
|
|
|
170
170
|
if (sources.length > 1 && !inContext)
|
|
171
171
|
return { error: MULTI_SOURCE_UNSUPPORTED };
|
|
172
172
|
if (!runtime.acquire())
|
|
173
|
-
return { error: BUSY_ERROR };
|
|
173
|
+
return { error: BUSY_ERROR, code: 'busy' };
|
|
174
174
|
const report = ctx.progress;
|
|
175
175
|
const onTick = report && generationPercentTick((percentage) => report({ percentage }), sources.length);
|
|
176
176
|
try {
|
|
@@ -323,7 +323,7 @@ export function createGenerateImageTool(runtime, clamps) {
|
|
|
323
323
|
if (!ctx?.attach)
|
|
324
324
|
return { error: 'this device cannot persist generated images' };
|
|
325
325
|
if (!runtime.acquire())
|
|
326
|
-
return { error: BUSY_ERROR };
|
|
326
|
+
return { error: BUSY_ERROR, code: 'busy' };
|
|
327
327
|
const report = ctx.progress;
|
|
328
328
|
const onTick = report && generationPercentTick((percentage) => report({ percentage }));
|
|
329
329
|
try {
|
|
@@ -443,7 +443,7 @@ export function createGenerateMusicTool(runtime, clamps) {
|
|
|
443
443
|
if (!ctx?.attach)
|
|
444
444
|
return { error: 'this device cannot persist generated audio' };
|
|
445
445
|
if (!runtime.acquire())
|
|
446
|
-
return { error: BUSY_ERROR };
|
|
446
|
+
return { error: BUSY_ERROR, code: 'busy' };
|
|
447
447
|
const report = ctx.progress;
|
|
448
448
|
const onTick = report && musicPercentTick((percentage) => report({ percentage }));
|
|
449
449
|
const capMax = stepsCap ?? MAX_STEPS;
|
|
@@ -163,10 +163,10 @@ export function createHttpRequestTool({ fetch: fetchImpl, credentialStore, crede
|
|
|
163
163
|
}
|
|
164
164
|
catch (err) {
|
|
165
165
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
166
|
+
const timedOut = error.name === 'AbortError' || error instanceof AbortedError;
|
|
166
167
|
return {
|
|
167
|
-
error:
|
|
168
|
-
|
|
169
|
-
: `request failed: ${error.message}`
|
|
168
|
+
error: timedOut ? 'request timed out' : `request failed: ${error.message}`,
|
|
169
|
+
code: timedOut ? 'timeout' : 'transport'
|
|
170
170
|
};
|
|
171
171
|
}
|
|
172
172
|
finally {
|
|
@@ -141,10 +141,10 @@ export function createMcpCallTool({ fetch: fetchImpl, credentialStore, credentia
|
|
|
141
141
|
}
|
|
142
142
|
catch (err) {
|
|
143
143
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
144
|
+
const timedOut = error.name === 'AbortError' || error instanceof AbortedError;
|
|
144
145
|
return {
|
|
145
|
-
error:
|
|
146
|
-
|
|
147
|
-
: `request failed: ${error.message}`
|
|
146
|
+
error: timedOut ? 'request timed out' : `request failed: ${error.message}`,
|
|
147
|
+
code: timedOut ? 'timeout' : 'transport'
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
finally {
|
|
@@ -131,8 +131,10 @@ export function createSkillTool({ skillsDir, overlayDir = resolveSkillsOverlayDi
|
|
|
131
131
|
if (file === MAIN_FILE)
|
|
132
132
|
return { error: `skill not found: ${name}` };
|
|
133
133
|
const available = await listSkillFiles([overlayDrive, baseDrive], name);
|
|
134
|
-
const
|
|
135
|
-
|
|
134
|
+
const rest = available.length
|
|
135
|
+
? ` — files this skill has: ${available.join(', ')}`
|
|
136
|
+
: '. This skill has no other files; you already have its full instructions — act now.';
|
|
137
|
+
return { error: `file not found in skill ${name}: ${file}${rest}` };
|
|
136
138
|
}
|
|
137
139
|
}
|
|
138
140
|
};
|
|
@@ -19,6 +19,7 @@ export declare function isCredentialUrlAllowed(target: string, patterns: readonl
|
|
|
19
19
|
export type CredentialAllowListFn = (ctx?: ToolExecuteContext) => CredentialAllowList;
|
|
20
20
|
export type AuthOutcome = {
|
|
21
21
|
readonly token: string;
|
|
22
|
+
readonly key: string;
|
|
22
23
|
} | {
|
|
23
24
|
readonly error: string;
|
|
24
25
|
};
|
|
@@ -60,49 +60,79 @@ export function parseAuth(v) {
|
|
|
60
60
|
}
|
|
61
61
|
return { ok: true, value: { type: 'bearer', tokenCredentialKey: key } };
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
function grantedKeys(url, credentialAllowList) {
|
|
64
|
+
return Object.entries(credentialAllowList)
|
|
65
|
+
.filter(([, entry]) => urlAllowed(url, Array.isArray(entry) ? entry : [entry]))
|
|
66
|
+
.map(([key]) => key);
|
|
67
|
+
}
|
|
68
|
+
async function connectedCredentials(keys, credentialStore, ctx) {
|
|
69
|
+
const connected = [];
|
|
70
|
+
for (const key of keys) {
|
|
71
|
+
const record = credentialRecord(await credentialStore(key, ctx));
|
|
72
|
+
if (record)
|
|
73
|
+
connected.push({ token: record.token, key });
|
|
74
|
+
}
|
|
75
|
+
return connected;
|
|
76
|
+
}
|
|
77
|
+
function ambiguous(keys) {
|
|
78
|
+
return {
|
|
79
|
+
error: `multiple credentials match this URL; set auth.tokenCredentialKey to one of ${keys.join(', ')}`
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function resolveGuessedKey(key, url, credentialStore, credentialAllowList, ctx) {
|
|
83
|
+
const granted = grantedKeys(url, credentialAllowList);
|
|
84
|
+
if (granted.length === 0) {
|
|
69
85
|
return {
|
|
70
|
-
error: `credential ${key}
|
|
86
|
+
error: `unknown credential key ${key}; no loaded skill grants a credential for ${url.host} — omit auth, or load the skill that declares it`
|
|
71
87
|
};
|
|
72
88
|
}
|
|
89
|
+
const [first, ...rest] = await connectedCredentials(granted, credentialStore, ctx);
|
|
90
|
+
if (!first) {
|
|
91
|
+
return {
|
|
92
|
+
error: `unknown credential key ${key}; no connected credential for ${url.host} (${granted.join(', ')}). Tell the user to connect it in Skills; do not retry`
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (rest.length > 0)
|
|
96
|
+
return ambiguous([first, ...rest].map((c) => c.key));
|
|
97
|
+
return first;
|
|
98
|
+
}
|
|
99
|
+
async function resolveDeclaredKey(key, url, credentialStore, credentialAllowList, ctx) {
|
|
100
|
+
const entry = credentialAllowList?.[key];
|
|
101
|
+
if (!entry)
|
|
102
|
+
return null;
|
|
73
103
|
const patterns = Array.isArray(entry) ? entry : [entry];
|
|
74
104
|
if (!urlAllowed(url, patterns)) {
|
|
75
105
|
return { error: `credential ${key} is not allowed for URL ${url.href}` };
|
|
76
106
|
}
|
|
77
107
|
const record = credentialRecord(await credentialStore(key, ctx));
|
|
78
|
-
if (!record)
|
|
79
|
-
return {
|
|
80
|
-
|
|
108
|
+
if (!record) {
|
|
109
|
+
return {
|
|
110
|
+
error: `credential ${key} is not connected for ${url.host}. Tell the user to connect it in Skills; do not retry`
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return { token: record.token, key };
|
|
114
|
+
}
|
|
115
|
+
export async function resolveAuth(auth, url, credentialStore, credentialAllowList, ctx) {
|
|
116
|
+
const key = auth.tokenCredentialKey;
|
|
117
|
+
if (!credentialStore)
|
|
118
|
+
return { error: 'credentials not configured' };
|
|
119
|
+
return ((await resolveDeclaredKey(key, url, credentialStore, credentialAllowList, ctx)) ??
|
|
120
|
+
resolveGuessedKey(key, url, credentialStore, credentialAllowList ?? {}, ctx));
|
|
81
121
|
}
|
|
82
122
|
export async function resolveAutoAuth(url, credentialStore, credentialAllowList, ctx) {
|
|
83
123
|
if (!credentialStore || !credentialAllowList)
|
|
84
124
|
return null;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
continue;
|
|
90
|
-
const record = credentialRecord(await credentialStore(key, ctx));
|
|
91
|
-
if (!record)
|
|
92
|
-
continue;
|
|
93
|
-
if (selected) {
|
|
94
|
-
return { error: 'multiple credentials match this URL; set auth.tokenCredentialKey' };
|
|
95
|
-
}
|
|
96
|
-
selected = { token: record.token, key };
|
|
97
|
-
}
|
|
98
|
-
return selected;
|
|
125
|
+
const connected = await connectedCredentials(grantedKeys(url, credentialAllowList), credentialStore, ctx);
|
|
126
|
+
if (connected.length > 1)
|
|
127
|
+
return ambiguous(connected.map((c) => c.key));
|
|
128
|
+
return connected[0] ?? null;
|
|
99
129
|
}
|
|
100
130
|
export async function resolveBearerHeader(auth, url, credentialStore, credentialAllowList, ctx, signal) {
|
|
101
131
|
if (auth) {
|
|
102
132
|
const resolved = await raceAbort(resolveAuth(auth, url, credentialStore, credentialAllowList, ctx), signal);
|
|
103
133
|
if ('error' in resolved)
|
|
104
134
|
return { error: resolved.error };
|
|
105
|
-
return { header: `Bearer ${resolved.token}`, credentialKey:
|
|
135
|
+
return { header: `Bearer ${resolved.token}`, credentialKey: resolved.key };
|
|
106
136
|
}
|
|
107
137
|
const resolved = await raceAbort(resolveAutoAuth(url, credentialStore, credentialAllowList, ctx), signal);
|
|
108
138
|
if (resolved && 'error' in resolved)
|
|
@@ -112,10 +142,10 @@ export async function resolveBearerHeader(auth, url, credentialStore, credential
|
|
|
112
142
|
: { header: null };
|
|
113
143
|
}
|
|
114
144
|
export async function resolveRedirectBearerHeader(auth, url, credentialStore, credentialAllowList, ctx, signal) {
|
|
115
|
-
if (!auth)
|
|
145
|
+
if (!auth || !credentialStore)
|
|
116
146
|
return { header: null };
|
|
117
|
-
const resolved = await raceAbort(
|
|
118
|
-
return 'error' in resolved
|
|
147
|
+
const resolved = await raceAbort(resolveDeclaredKey(auth.tokenCredentialKey, url, credentialStore, credentialAllowList, ctx), signal);
|
|
148
|
+
return !resolved || 'error' in resolved
|
|
119
149
|
? { header: null }
|
|
120
|
-
: { header: `Bearer ${resolved.token}`, credentialKey:
|
|
150
|
+
: { header: `Bearer ${resolved.token}`, credentialKey: resolved.key };
|
|
121
151
|
}
|
|
@@ -4,5 +4,6 @@ export declare function asToolset(value?: string | null): Toolset | null;
|
|
|
4
4
|
export interface ToolsetScoped {
|
|
5
5
|
toolset?: string | null;
|
|
6
6
|
}
|
|
7
|
+
export declare const NETWORK_TOOLSETS: ReadonlyArray<Toolset>;
|
|
7
8
|
export declare function permittedTools<T extends ToolsetScoped>(tools: ReadonlyArray<T>, disabled?: ReadonlyArray<string> | null): T[];
|
|
8
9
|
export declare function toolsetEnabled(toolset: Toolset, disabled?: ReadonlyArray<string> | null): boolean;
|
|
@@ -9,6 +9,7 @@ export const TOOLSETS = [
|
|
|
9
9
|
export function asToolset(value) {
|
|
10
10
|
return TOOLSETS.find((toolset) => toolset === value) ?? null;
|
|
11
11
|
}
|
|
12
|
+
export const NETWORK_TOOLSETS = ['web-search', 'http-request', 'mcp-call'];
|
|
12
13
|
export function permittedTools(tools, disabled) {
|
|
13
14
|
if (!disabled?.length)
|
|
14
15
|
return [...tools];
|
|
@@ -63,7 +63,7 @@ export function createWeatherLookupTool({ fetch: fetchImpl, resolveHost = lookup
|
|
|
63
63
|
if (/location not found/i.test(body))
|
|
64
64
|
return { content: notFound(location) };
|
|
65
65
|
if (response.status !== 200) {
|
|
66
|
-
return { error: unavailable(`it answered HTTP ${response.status}`) };
|
|
66
|
+
return { error: unavailable(`it answered HTTP ${response.status}`), code: 'transport' };
|
|
67
67
|
}
|
|
68
68
|
let json;
|
|
69
69
|
try {
|
|
@@ -91,9 +91,15 @@ export function createWeatherLookupTool({ fetch: fetchImpl, resolveHost = lookup
|
|
|
91
91
|
if (ctx?.signal?.aborted)
|
|
92
92
|
return { error: 'weather_lookup cancelled' };
|
|
93
93
|
if (controller.signal.aborted) {
|
|
94
|
-
return {
|
|
94
|
+
return {
|
|
95
|
+
error: `weather_lookup timed out after ${LOOKUP_TIMEOUT_MS} ms`,
|
|
96
|
+
code: 'timeout'
|
|
97
|
+
};
|
|
95
98
|
}
|
|
96
|
-
return {
|
|
99
|
+
return {
|
|
100
|
+
error: unavailable(err instanceof Error ? err.message : String(err)),
|
|
101
|
+
code: 'transport'
|
|
102
|
+
};
|
|
97
103
|
}
|
|
98
104
|
finally {
|
|
99
105
|
clearTimeout(timeout);
|
|
@@ -79,7 +79,10 @@ export function createWebFetchTool({ fetch: fetchImpl, resolveHost = lookupHost
|
|
|
79
79
|
return { error: got.error };
|
|
80
80
|
const { response, finalUrl: currentUrl } = got;
|
|
81
81
|
if (response.status < 200 || response.status >= 300) {
|
|
82
|
-
return {
|
|
82
|
+
return {
|
|
83
|
+
error: `HTTP ${response.status} fetching ${currentUrl.toString()}`,
|
|
84
|
+
status: response.status
|
|
85
|
+
};
|
|
83
86
|
}
|
|
84
87
|
const rawBody = await response.text();
|
|
85
88
|
const body = rawBody.length > MAX_HTML_CHARS ? rawBody.slice(0, MAX_HTML_CHARS) : rawBody;
|
|
@@ -140,9 +143,9 @@ export function createWebFetchTool({ fetch: fetchImpl, resolveHost = lookupHost
|
|
|
140
143
|
return { error: 'web_fetch cancelled' };
|
|
141
144
|
}
|
|
142
145
|
if (controller.signal.aborted) {
|
|
143
|
-
return { error: `${url.hostname} timed out after ${PAGE_TIMEOUT_MS}ms
|
|
146
|
+
return { error: `${url.hostname} timed out after ${PAGE_TIMEOUT_MS}ms`, code: 'timeout' };
|
|
144
147
|
}
|
|
145
|
-
return { error: err instanceof Error ? err.message : String(err) };
|
|
148
|
+
return { error: err instanceof Error ? err.message : String(err), code: 'transport' };
|
|
146
149
|
}
|
|
147
150
|
finally {
|
|
148
151
|
clearTimeout(timeout);
|
|
@@ -43,12 +43,15 @@ export function createWebSearchTool({ fetch } = {}) {
|
|
|
43
43
|
}
|
|
44
44
|
const failed = engineSummary.filter((e) => e.status !== 'ok').length;
|
|
45
45
|
if (engineSummary.length > 0 && failed === engineSummary.length) {
|
|
46
|
-
return {
|
|
46
|
+
return {
|
|
47
|
+
error: `web_search unavailable: all ${failed} engines failed (timeout/error)`,
|
|
48
|
+
code: 'transport'
|
|
49
|
+
};
|
|
47
50
|
}
|
|
48
51
|
return { content: `No web results found for "${query}".` };
|
|
49
52
|
}
|
|
50
53
|
catch (err) {
|
|
51
|
-
return { error: err instanceof Error ? err.message : String(err) };
|
|
54
|
+
return { error: err instanceof Error ? err.message : String(err), code: 'transport' };
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
};
|
package/dist-lib/lib/wire.js
CHANGED
|
@@ -32,6 +32,20 @@ export function toWire(event) {
|
|
|
32
32
|
const { type, ...metrics } = event;
|
|
33
33
|
return { type, metrics };
|
|
34
34
|
}
|
|
35
|
+
case 'inference-call':
|
|
36
|
+
return { type: 'inference-call', payload: event.call };
|
|
37
|
+
case 'inference-sent':
|
|
38
|
+
return {
|
|
39
|
+
type: 'inference-sent',
|
|
40
|
+
payload: {
|
|
41
|
+
round: event.round,
|
|
42
|
+
droppedTurns: event.droppedTurns,
|
|
43
|
+
messages: event.messages,
|
|
44
|
+
tokens: event.tokens,
|
|
45
|
+
outcome: event.outcome,
|
|
46
|
+
reason: event.reason
|
|
47
|
+
}
|
|
48
|
+
};
|
|
35
49
|
case 'error':
|
|
36
50
|
return { type: 'error', message: event.message };
|
|
37
51
|
case 'aborted':
|
|
@@ -73,6 +87,20 @@ export function fromWire(event) {
|
|
|
73
87
|
};
|
|
74
88
|
case 'metrics':
|
|
75
89
|
return { type: 'metrics', ...event.metrics };
|
|
90
|
+
case 'inference-call':
|
|
91
|
+
return { type: 'inference-call', call: event.payload };
|
|
92
|
+
case 'inference-sent': {
|
|
93
|
+
const sent = (event.payload ?? {});
|
|
94
|
+
return {
|
|
95
|
+
type: 'inference-sent',
|
|
96
|
+
messages: sent.messages,
|
|
97
|
+
round: Number(sent.round ?? 0),
|
|
98
|
+
droppedTurns: Number(sent.droppedTurns ?? 0),
|
|
99
|
+
tokens: sent.tokens,
|
|
100
|
+
outcome: sent.outcome || 'completed',
|
|
101
|
+
reason: sent.reason || undefined
|
|
102
|
+
};
|
|
103
|
+
}
|
|
76
104
|
case 'error':
|
|
77
105
|
return { type: 'error', message: event.message ?? 'harness error' };
|
|
78
106
|
case 'aborted':
|
|
@@ -23,7 +23,8 @@ export function registerHarnessTypes(ns) {
|
|
|
23
23
|
{ name: 'imageWidth', type: 'uint' },
|
|
24
24
|
{ name: 'imageHeight', type: 'uint' },
|
|
25
25
|
{ name: 'approvalMode', type: '@harness/approval-mode' },
|
|
26
|
-
{ name: 'disabledToolsets', type: 'string', array: true }
|
|
26
|
+
{ name: 'disabledToolsets', type: 'string', array: true },
|
|
27
|
+
{ name: 'sandboxMode', type: 'bool' }
|
|
27
28
|
]
|
|
28
29
|
});
|
|
29
30
|
ns.register({
|
|
@@ -107,7 +108,8 @@ export function registerHarnessTypes(ns) {
|
|
|
107
108
|
{ name: 'grantedDirs', type: 'string', array: true },
|
|
108
109
|
{ name: 'grantedDrives', type: '@harness/granted-drive', array: true },
|
|
109
110
|
{ name: 'payload', type: 'json' },
|
|
110
|
-
{ name: 'grantedWriteDirs', type: 'string', array: true }
|
|
111
|
+
{ name: 'grantedWriteDirs', type: 'string', array: true },
|
|
112
|
+
{ name: 'captureInferenceCalls', type: 'bool' }
|
|
111
113
|
]
|
|
112
114
|
});
|
|
113
115
|
ns.register({
|
|
@@ -128,7 +130,9 @@ export function registerHarnessTypes(ns) {
|
|
|
128
130
|
'question-ask',
|
|
129
131
|
'done',
|
|
130
132
|
'tool-activity',
|
|
131
|
-
'drive-ask'
|
|
133
|
+
'drive-ask',
|
|
134
|
+
'inference-call',
|
|
135
|
+
'inference-sent'
|
|
132
136
|
],
|
|
133
137
|
strings: true
|
|
134
138
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// This file is autogenerated by the hyperschema compiler
|
|
2
|
-
// Schema Version:
|
|
2
|
+
// Schema Version: 10
|
|
3
3
|
/* eslint-disable camelcase */
|
|
4
4
|
/* eslint-disable quotes */
|
|
5
5
|
/* eslint-disable space-before-function-paren */
|
|
6
6
|
|
|
7
7
|
import { c } from 'hyperschema/runtime'
|
|
8
8
|
|
|
9
|
-
const VERSION =
|
|
9
|
+
const VERSION = 10
|
|
10
10
|
|
|
11
11
|
// eslint-disable-next-line no-unused-vars
|
|
12
12
|
let version = VERSION
|
|
@@ -143,7 +143,8 @@ const encoding4 = {
|
|
|
143
143
|
(m.imageWidth ? 8192 : 0) |
|
|
144
144
|
(m.imageHeight ? 16384 : 0) |
|
|
145
145
|
((version >= 5 && m.approvalMode) ? 32768 : 0) |
|
|
146
|
-
((version >= 5 && m.disabledToolsets) ? 65536 : 0)
|
|
146
|
+
((version >= 5 && m.disabledToolsets) ? 65536 : 0) |
|
|
147
|
+
((version >= 10 && m.sandboxMode) ? 131072 : 0)
|
|
147
148
|
|
|
148
149
|
c.uint.preencode(state, flags)
|
|
149
150
|
|
|
@@ -183,7 +184,8 @@ const encoding4 = {
|
|
|
183
184
|
(m.imageWidth ? 8192 : 0) |
|
|
184
185
|
(m.imageHeight ? 16384 : 0) |
|
|
185
186
|
((version >= 5 && m.approvalMode) ? 32768 : 0) |
|
|
186
|
-
((version >= 5 && m.disabledToolsets) ? 65536 : 0)
|
|
187
|
+
((version >= 5 && m.disabledToolsets) ? 65536 : 0) |
|
|
188
|
+
((version >= 10 && m.sandboxMode) ? 131072 : 0)
|
|
187
189
|
|
|
188
190
|
c.uint.encode(state, flags)
|
|
189
191
|
|
|
@@ -225,7 +227,8 @@ const encoding4 = {
|
|
|
225
227
|
imageWidth: (flags & 8192) !== 0 ? c.uint.decode(state) : 0,
|
|
226
228
|
imageHeight: (flags & 16384) !== 0 ? c.uint.decode(state) : 0,
|
|
227
229
|
approvalMode: (version >= 5 && (flags & 32768) !== 0) ? encoding59.decode(state) : null,
|
|
228
|
-
disabledToolsets: (version >= 5 && (flags & 65536) !== 0) ? encoding4_16.decode(state) : null
|
|
230
|
+
disabledToolsets: (version >= 5 && (flags & 65536) !== 0) ? encoding4_16.decode(state) : null,
|
|
231
|
+
sandboxMode: (version >= 10 && (flags & 131072) !== 0)
|
|
229
232
|
}
|
|
230
233
|
}
|
|
231
234
|
}
|
|
@@ -478,7 +481,8 @@ const encoding11 = {
|
|
|
478
481
|
((version >= 5 && m.grantedDirs) ? 2097152 : 0) |
|
|
479
482
|
((version >= 5 && m.grantedDrives) ? 4194304 : 0) |
|
|
480
483
|
((version >= 5 && m.payload) ? 8388608 : 0) |
|
|
481
|
-
((version >= 5 && m.grantedWriteDirs) ? 16777216 : 0)
|
|
484
|
+
((version >= 5 && m.grantedWriteDirs) ? 16777216 : 0) |
|
|
485
|
+
((version >= 9 && m.captureInferenceCalls) ? 33554432 : 0)
|
|
482
486
|
|
|
483
487
|
encoding10.preencode(state, m.type)
|
|
484
488
|
c.uint.preencode(state, flags)
|
|
@@ -533,7 +537,8 @@ const encoding11 = {
|
|
|
533
537
|
((version >= 5 && m.grantedDirs) ? 2097152 : 0) |
|
|
534
538
|
((version >= 5 && m.grantedDrives) ? 4194304 : 0) |
|
|
535
539
|
((version >= 5 && m.payload) ? 8388608 : 0) |
|
|
536
|
-
((version >= 5 && m.grantedWriteDirs) ? 16777216 : 0)
|
|
540
|
+
((version >= 5 && m.grantedWriteDirs) ? 16777216 : 0) |
|
|
541
|
+
((version >= 9 && m.captureInferenceCalls) ? 33554432 : 0)
|
|
537
542
|
|
|
538
543
|
encoding10.encode(state, m.type)
|
|
539
544
|
c.uint.encode(state, flags)
|
|
@@ -592,7 +597,8 @@ const encoding11 = {
|
|
|
592
597
|
grantedDirs: (version >= 5 && (flags & 2097152) !== 0) ? encoding11_22.decode(state) : null,
|
|
593
598
|
grantedDrives: (version >= 5 && (flags & 4194304) !== 0) ? encoding11_23.decode(state) : null,
|
|
594
599
|
payload: (version >= 5 && (flags & 8388608) !== 0) ? c.json.decode(state) : null,
|
|
595
|
-
grantedWriteDirs: (version >= 5 && (flags & 16777216) !== 0) ? encoding11_25.decode(state) : null
|
|
600
|
+
grantedWriteDirs: (version >= 5 && (flags & 16777216) !== 0) ? encoding11_25.decode(state) : null,
|
|
601
|
+
captureInferenceCalls: (version >= 9 && (flags & 33554432) !== 0)
|
|
596
602
|
}
|
|
597
603
|
}
|
|
598
604
|
}
|
|
@@ -613,13 +619,15 @@ const encoding12_enum = {
|
|
|
613
619
|
"question-ask": 'question-ask',
|
|
614
620
|
done: 'done',
|
|
615
621
|
"tool-activity": 'tool-activity',
|
|
616
|
-
"drive-ask": 'drive-ask'
|
|
622
|
+
"drive-ask": 'drive-ask',
|
|
623
|
+
"inference-call": 'inference-call',
|
|
624
|
+
"inference-sent": 'inference-sent'
|
|
617
625
|
}
|
|
618
626
|
|
|
619
627
|
// @harness/completion-event-type enum
|
|
620
628
|
const encoding12 = {
|
|
621
629
|
preencode (state, m) {
|
|
622
|
-
state.end++ // max enum is
|
|
630
|
+
state.end++ // max enum is 18 so always one byte
|
|
623
631
|
},
|
|
624
632
|
encode (state, m) {
|
|
625
633
|
switch (m) {
|
|
@@ -671,6 +679,12 @@ const encoding12 = {
|
|
|
671
679
|
case 'drive-ask':
|
|
672
680
|
c.uint.encode(state, 16)
|
|
673
681
|
break
|
|
682
|
+
case 'inference-call':
|
|
683
|
+
c.uint.encode(state, 17)
|
|
684
|
+
break
|
|
685
|
+
case 'inference-sent':
|
|
686
|
+
c.uint.encode(state, 18)
|
|
687
|
+
break
|
|
674
688
|
default:
|
|
675
689
|
throw new Error('Unknown enum')
|
|
676
690
|
}
|
|
@@ -709,6 +723,10 @@ const encoding12 = {
|
|
|
709
723
|
return 'tool-activity'
|
|
710
724
|
case 16:
|
|
711
725
|
return 'drive-ask'
|
|
726
|
+
case 17:
|
|
727
|
+
return 'inference-call'
|
|
728
|
+
case 18:
|
|
729
|
+
return 'inference-sent'
|
|
712
730
|
default: return null
|
|
713
731
|
}
|
|
714
732
|
}
|