@asterxsk/kiln 0.1.0 → 0.2.0
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/LICENSE +21 -21
- package/README.md +170 -170
- package/agent/AGENTS.md +67 -67
- package/agent/README.md +5 -5
- package/agent/extensions/AGENTS.md +68 -68
- package/agent/extensions/ask-user/index.ts +418 -418
- package/agent/extensions/ask-user/package-lock.json +769 -769
- package/agent/extensions/ask-user/package.json +19 -19
- package/agent/extensions/ask-user/prompt.ts +45 -45
- package/agent/extensions/ask-user/tsconfig.json +7 -7
- package/agent/extensions/background-terminals/docs/implementation-guide.md +942 -942
- package/agent/extensions/background-terminals/index.ts +627 -627
- package/agent/extensions/background-terminals/manager.test.ts +735 -735
- package/agent/extensions/background-terminals/output.test.ts +109 -109
- package/agent/extensions/background-terminals/package-lock.json +769 -769
- package/agent/extensions/background-terminals/package.json +17 -17
- package/agent/extensions/background-terminals/prompt.test.ts +125 -125
- package/agent/extensions/background-terminals/ps.test.ts +82 -82
- package/agent/extensions/background-terminals/result-delivery.test.ts +44 -44
- package/agent/extensions/background-terminals/src/domain.ts +87 -87
- package/agent/extensions/background-terminals/src/manager.ts +907 -907
- package/agent/extensions/background-terminals/src/output.ts +84 -84
- package/agent/extensions/background-terminals/src/prompt.ts +142 -142
- package/agent/extensions/background-terminals/src/result-delivery.ts +27 -27
- package/agent/extensions/background-terminals/src/runtime.ts +36 -36
- package/agent/extensions/background-terminals/src/ui/output-view.ts +79 -79
- package/agent/extensions/background-terminals/src/ui/ps.ts +621 -621
- package/agent/extensions/background-terminals/tsconfig.json +7 -7
- package/agent/extensions/file-search/index.spec.ts +443 -443
- package/agent/extensions/file-search/index.ts +459 -459
- package/agent/extensions/file-search/package-lock.json +2253 -2253
- package/agent/extensions/file-search/package.json +23 -23
- package/agent/extensions/file-search/src/args.ts +122 -122
- package/agent/extensions/file-search/src/binaries.ts +422 -422
- package/agent/extensions/file-search/src/output.ts +126 -126
- package/agent/extensions/file-search/src/process.ts +146 -146
- package/agent/extensions/file-search/src/prompt.ts +52 -52
- package/agent/extensions/file-search/tsconfig.json +7 -7
- package/agent/extensions/modelconf/PLAN.md +915 -915
- package/agent/extensions/modelconf/index.ts +296 -296
- package/agent/extensions/modelconf/src/ui/ModelConfView.ts +1101 -1101
- package/agent/extensions/pi-web-access/CHANGELOG.md +690 -690
- package/agent/extensions/pi-web-access/LICENSE +21 -21
- package/agent/extensions/pi-web-access/README.md +470 -470
- package/agent/extensions/pi-web-access/SECURITY.md +5 -5
- package/agent/extensions/pi-web-access/activity.ts +101 -101
- package/agent/extensions/pi-web-access/auth-fetch.ts +148 -148
- package/agent/extensions/pi-web-access/brightdata-unlocker.ts +272 -272
- package/agent/extensions/pi-web-access/chrome-cookies.ts +669 -669
- package/agent/extensions/pi-web-access/content-find.ts +139 -139
- package/agent/extensions/pi-web-access/credential-source.ts +191 -191
- package/agent/extensions/pi-web-access/data-uri-sanitize.ts +406 -406
- package/agent/extensions/pi-web-access/datalab-pdf-extract.ts +568 -568
- package/agent/extensions/pi-web-access/declared-web-links.ts +173 -173
- package/agent/extensions/pi-web-access/evidence/CONTRACT-EVIDENCE.md +496 -496
- package/agent/extensions/pi-web-access/evidence/contract-probe.mjs +140 -140
- package/agent/extensions/pi-web-access/exa.ts +526 -526
- package/agent/extensions/pi-web-access/extract.ts +1196 -1196
- package/agent/extensions/pi-web-access/feature-config.ts +29 -29
- package/agent/extensions/pi-web-access/fetch-params.ts +111 -111
- package/agent/extensions/pi-web-access/gemini-adc.ts +298 -298
- package/agent/extensions/pi-web-access/gemini-api.ts +353 -353
- package/agent/extensions/pi-web-access/gemini-pdf-extract.ts +108 -108
- package/agent/extensions/pi-web-access/gemini-url-context.ts +128 -128
- package/agent/extensions/pi-web-access/gemini-web-config.ts +101 -101
- package/agent/extensions/pi-web-access/gemini-web.ts +487 -487
- package/agent/extensions/pi-web-access/github-api.ts +197 -197
- package/agent/extensions/pi-web-access/github-extract.ts +746 -746
- package/agent/extensions/pi-web-access/github-issue-pr.ts +700 -700
- package/agent/extensions/pi-web-access/index.ts +1737 -1737
- package/agent/extensions/pi-web-access/package-lock.json +5808 -5808
- package/agent/extensions/pi-web-access/package.json +64 -64
- package/agent/extensions/pi-web-access/page-query.ts +96 -96
- package/agent/extensions/pi-web-access/pdf-extract.ts +409 -409
- package/agent/extensions/pi-web-access/promise-try.d.ts +7 -7
- package/agent/extensions/pi-web-access/query-rewrite.ts +51 -51
- package/agent/extensions/pi-web-access/render-search-error.ts +170 -170
- package/agent/extensions/pi-web-access/rsc-extract.ts +338 -338
- package/agent/extensions/pi-web-access/source-check.ts +282 -282
- package/agent/extensions/pi-web-access/ssrf-protection.ts +526 -526
- package/agent/extensions/pi-web-access/storage.ts +521 -521
- package/agent/extensions/pi-web-access/summary-model-scope.ts +125 -125
- package/agent/extensions/pi-web-access/test/auth-fetch.test.mjs +208 -208
- package/agent/extensions/pi-web-access/test/brightdata-unlocker.test.mjs +840 -840
- package/agent/extensions/pi-web-access/test/chrome-cookie-extraction.test.mjs +441 -441
- package/agent/extensions/pi-web-access/test/config-path.test.mjs +283 -283
- package/agent/extensions/pi-web-access/test/content-find.test.mjs +25 -25
- package/agent/extensions/pi-web-access/test/credential-source.test.mjs +118 -118
- package/agent/extensions/pi-web-access/test/data-uri-sanitize.test.mjs +210 -210
- package/agent/extensions/pi-web-access/test/datalab-pdf-extract.test.mjs +552 -552
- package/agent/extensions/pi-web-access/test/declared-web-links.test.mjs +212 -212
- package/agent/extensions/pi-web-access/test/fetch-answer-storage.test.mjs +40 -40
- package/agent/extensions/pi-web-access/test/fetch-cache-storage.test.mjs +334 -334
- package/agent/extensions/pi-web-access/test/fetch-content-domain-policy.test.mjs +95 -95
- package/agent/extensions/pi-web-access/test/fetch-modes.test.mjs +53 -53
- package/agent/extensions/pi-web-access/test/fetch-not-found-guidance.test.mjs +92 -92
- package/agent/extensions/pi-web-access/test/fetch-params.test.mjs +86 -86
- package/agent/extensions/pi-web-access/test/fetch-render-call.test.mjs +34 -34
- package/agent/extensions/pi-web-access/test/fetch-routing.test.mjs +173 -173
- package/agent/extensions/pi-web-access/test/gemini-adc-auth.test.mjs +257 -257
- package/agent/extensions/pi-web-access/test/gemini-api-transport.test.mjs +170 -170
- package/agent/extensions/pi-web-access/test/gemini-pdf-extract.test.mjs +133 -133
- package/agent/extensions/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +178 -178
- package/agent/extensions/pi-web-access/test/gemini-web-header-overflow.test.mjs +148 -148
- package/agent/extensions/pi-web-access/test/get-search-content.test.mjs +223 -223
- package/agent/extensions/pi-web-access/test/github-extract.test.mjs +378 -378
- package/agent/extensions/pi-web-access/test/github-issue-pr.test.mjs +565 -565
- package/agent/extensions/pi-web-access/test/inline-content-config.test.mjs +99 -99
- package/agent/extensions/pi-web-access/test/lazy-extract-load.test.mjs +118 -118
- package/agent/extensions/pi-web-access/test/local-video-oversize.test.mjs +52 -52
- package/agent/extensions/pi-web-access/test/package-typebox-dependency.test.mjs +50 -50
- package/agent/extensions/pi-web-access/test/page-query.test.mjs +51 -51
- package/agent/extensions/pi-web-access/test/pdf-config.test.mjs +140 -140
- package/agent/extensions/pi-web-access/test/pdf-extract.test.mjs +500 -500
- package/agent/extensions/pi-web-access/test/proxy-transport.test.mjs +286 -286
- package/agent/extensions/pi-web-access/test/query-rewrite.test.mjs +52 -52
- package/agent/extensions/pi-web-access/test/rsc-fallback.test.mjs +102 -102
- package/agent/extensions/pi-web-access/test/search-error-render.test.mjs +152 -152
- package/agent/extensions/pi-web-access/test/search-providers.test.mjs +274 -274
- package/agent/extensions/pi-web-access/test/source-check.test.mjs +179 -179
- package/agent/extensions/pi-web-access/test/ssrf-allow-ranges-config.test.mjs +205 -205
- package/agent/extensions/pi-web-access/test/ssrf-protection.test.mjs +456 -456
- package/agent/extensions/pi-web-access/test/tool-registration-config.test.mjs +182 -182
- package/agent/extensions/pi-web-access/test/youtube-extract-errors.test.mjs +64 -64
- package/agent/extensions/pi-web-access/tsconfig.json +11 -11
- package/agent/extensions/pi-web-access/utils.ts +451 -451
- package/agent/extensions/pi-web-access/video-extract.ts +392 -392
- package/agent/extensions/pi-web-access/youtube-extract.ts +328 -328
- package/agent/extensions/shared/activity-status.ts +31 -31
- package/agent/extensions/shared/child-session.test.ts +270 -270
- package/agent/extensions/shared/child-session.ts +148 -148
- package/agent/extensions/shared/context-utilization.test.ts +48 -48
- package/agent/extensions/shared/context-utilization.ts +47 -47
- package/agent/extensions/shared/dashboard-state.ts +99 -99
- package/agent/extensions/shared/tool-call-timeout.test.ts +117 -117
- package/agent/extensions/shared/tool-call-timeout.ts +104 -104
- package/agent/extensions/subagents/by-the-way.test.ts +29 -29
- package/agent/extensions/subagents/claude.test.ts +119 -119
- package/agent/extensions/subagents/codex.test.ts +102 -102
- package/agent/extensions/subagents/context-usage.test.ts +107 -107
- package/agent/extensions/subagents/docs/design-plan.md +568 -568
- package/agent/extensions/subagents/docs/effect-v4-extension-guide.md +354 -354
- package/agent/extensions/subagents/docs/effect-v4-notes.md +571 -571
- package/agent/extensions/subagents/index.ts +779 -779
- package/agent/extensions/subagents/manager.test.ts +276 -276
- package/agent/extensions/subagents/package-lock.json +2244 -2244
- package/agent/extensions/subagents/package.json +19 -19
- package/agent/extensions/subagents/result-delivery.test.ts +27 -27
- package/agent/extensions/subagents/src/backend.ts +73 -73
- package/agent/extensions/subagents/src/backends/claude.ts +701 -701
- package/agent/extensions/subagents/src/backends/codex.ts +1060 -1060
- package/agent/extensions/subagents/src/backends/pi.ts +575 -575
- package/agent/extensions/subagents/src/backends/stub.ts +300 -300
- package/agent/extensions/subagents/src/by-the-way.ts +21 -21
- package/agent/extensions/subagents/src/domain.ts +253 -253
- package/agent/extensions/subagents/src/format.ts +74 -74
- package/agent/extensions/subagents/src/manager.ts +736 -736
- package/agent/extensions/subagents/src/prompt.ts +92 -92
- package/agent/extensions/subagents/src/result-delivery.ts +20 -20
- package/agent/extensions/subagents/src/runtime.ts +53 -53
- package/agent/extensions/subagents/src/ui/takeover.ts +583 -583
- package/agent/extensions/subagents/src/ui/transcript.ts +201 -201
- package/agent/extensions/subagents/takeover.test.ts +29 -29
- package/agent/extensions/subagents/tsconfig.json +7 -7
- package/agent/extensions/todo/AGENTS.md +38 -38
- package/agent/extensions/todo/LICENSE +21 -21
- package/agent/extensions/todo/config.ts +55 -55
- package/agent/extensions/todo/index.ts +151 -151
- package/agent/extensions/todo/locales/de.json +17 -17
- package/agent/extensions/todo/locales/en.json +15 -15
- package/agent/extensions/todo/locales/es.json +17 -17
- package/agent/extensions/todo/locales/fr.json +17 -17
- package/agent/extensions/todo/locales/pt-BR.json +17 -17
- package/agent/extensions/todo/locales/pt.json +17 -17
- package/agent/extensions/todo/locales/ru.json +17 -17
- package/agent/extensions/todo/locales/uk.json +17 -17
- package/agent/extensions/todo/locales/zh.json +17 -17
- package/agent/extensions/todo/package-lock.json +3358 -3358
- package/agent/extensions/todo/package.json +67 -67
- package/agent/extensions/todo/state/i18n-bridge.ts +64 -64
- package/agent/extensions/todo/state/invariants.ts +20 -20
- package/agent/extensions/todo/state/replay.ts +38 -38
- package/agent/extensions/todo/state/selectors.ts +107 -107
- package/agent/extensions/todo/state/state-reducer.ts +326 -326
- package/agent/extensions/todo/state/state.ts +18 -18
- package/agent/extensions/todo/state/store.ts +82 -82
- package/agent/extensions/todo/state/task-graph.ts +57 -57
- package/agent/extensions/todo/todo-overlay.ts +200 -200
- package/agent/extensions/todo/todo.ts +155 -155
- package/agent/extensions/todo/tool/response-envelope.ts +109 -109
- package/agent/extensions/todo/tool/types.ts +206 -206
- package/agent/extensions/todo/view/format.ts +177 -177
- package/agent/install.ps1 +637 -527
- package/agent/install.sh +620 -511
- package/agent/keybindings.json +7 -7
- package/bin/kiln.js +124 -11
- package/package.json +8 -2
- package/agent/extensions/taste/index.ts +0 -443
- package/agent/extensions/taste/install.ps1 +0 -23
- package/agent/extensions/taste/install.sh +0 -21
- /package/agent/extensions/{status line → statusline}/index.ts +0 -0
- /package/agent/extensions/{status line → statusline}/install.ps1 +0 -0
- /package/agent/extensions/{status line → statusline}/install.sh +0 -0
|
@@ -1,223 +1,223 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { test } from "node:test";
|
|
3
|
-
import { Value } from "typebox/value";
|
|
4
|
-
|
|
5
|
-
import initializeExtension from "../index.ts";
|
|
6
|
-
import { buildResearchArtifact, storeResearchArtifact } from "../source-check.ts";
|
|
7
|
-
import { clearResults, storeResult } from "../storage.ts";
|
|
8
|
-
|
|
9
|
-
function getContentTool() {
|
|
10
|
-
clearResults();
|
|
11
|
-
const tools = [];
|
|
12
|
-
initializeExtension({
|
|
13
|
-
registerTool(tool) { tools.push(tool); },
|
|
14
|
-
registerCommand() {},
|
|
15
|
-
registerShortcut() {},
|
|
16
|
-
on() {},
|
|
17
|
-
});
|
|
18
|
-
const tool = tools.find((registered) => registered.name === "get_search_content");
|
|
19
|
-
assert.ok(tool, "get_search_content tool was not registered");
|
|
20
|
-
return tool;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function storeFetchedContent(content) {
|
|
24
|
-
storeResult("large-fetch", {
|
|
25
|
-
id: "large-fetch",
|
|
26
|
-
type: "fetch",
|
|
27
|
-
timestamp: Date.now(),
|
|
28
|
-
urls: [{
|
|
29
|
-
url: "https://example.com/large",
|
|
30
|
-
title: "Large Page",
|
|
31
|
-
content,
|
|
32
|
-
error: null,
|
|
33
|
-
}],
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function storeSearchContent() {
|
|
38
|
-
storeResult("search-result", {
|
|
39
|
-
id: "search-result",
|
|
40
|
-
type: "search",
|
|
41
|
-
timestamp: Date.now(),
|
|
42
|
-
queries: [{
|
|
43
|
-
query: "CotEditor scripts",
|
|
44
|
-
answer: "",
|
|
45
|
-
results: [
|
|
46
|
-
{ title: "ScriptManager.swift", url: "https://example.com/script-manager", snippet: "UNIX script support" },
|
|
47
|
-
{ title: "UNIX script", url: "https://example.com/unix-script", snippet: "Script support" },
|
|
48
|
-
{ title: "ScriptMenu", url: "https://example.com/script-menu", snippet: "Menu integration" },
|
|
49
|
-
],
|
|
50
|
-
error: null,
|
|
51
|
-
}],
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
test("get_search_content schemas constrain numeric parameters", () => {
|
|
56
|
-
const properties = getContentTool().parameters.properties;
|
|
57
|
-
|
|
58
|
-
for (const name of ["queryIndex", "urlIndex", "offset"]) {
|
|
59
|
-
assert.equal(properties[name].type, "integer");
|
|
60
|
-
assert.equal(properties[name].minimum, 0);
|
|
61
|
-
assert.equal(properties[name].maximum, undefined);
|
|
62
|
-
for (const value of [-1, 1.5, Number.NaN, Number.POSITIVE_INFINITY]) {
|
|
63
|
-
assert.equal(Value.Check(properties[name], value), false, `${name} accepts ${value}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
assert.equal(properties.limit.type, "integer");
|
|
68
|
-
assert.equal(properties.limit.minimum, 1);
|
|
69
|
-
assert.equal(properties.limit.maximum, 30_000);
|
|
70
|
-
for (const value of [0, 1.5, 30_001, Number.NaN, Number.POSITIVE_INFINITY]) {
|
|
71
|
-
assert.equal(Value.Check(properties.limit, value), false, `limit accepts ${value}`);
|
|
72
|
-
}
|
|
73
|
-
for (const value of [1, 30_000]) {
|
|
74
|
-
assert.equal(Value.Check(properties.limit, value), true, `limit rejects ${value}`);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test("get_search_content returns a bounded first slice for large fetched content", async () => {
|
|
79
|
-
const tool = getContentTool();
|
|
80
|
-
storeFetchedContent("A".repeat(30_000) + "TAIL");
|
|
81
|
-
|
|
82
|
-
const result = await tool.execute("call", { responseId: "large-fetch", urlIndex: 0 });
|
|
83
|
-
const text = result.content[0].text;
|
|
84
|
-
|
|
85
|
-
assert.equal(result.details.contentLength, 30_004);
|
|
86
|
-
assert.equal(result.details.offset, 0);
|
|
87
|
-
assert.equal(result.details.returnedChars, 30_000);
|
|
88
|
-
assert.equal(result.details.nextOffset, 30_000);
|
|
89
|
-
assert.equal(result.details.truncated, true);
|
|
90
|
-
assert.match(text, /Showing chars 0-30000 of 30004/);
|
|
91
|
-
assert.match(text, /offset: 30000/);
|
|
92
|
-
assert.doesNotMatch(text, /TAIL/);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test("get_search_content returns requested fetched content slices", async () => {
|
|
96
|
-
const tool = getContentTool();
|
|
97
|
-
storeFetchedContent("A".repeat(30_000) + "BCDEFGHIJ");
|
|
98
|
-
|
|
99
|
-
const result = await tool.execute("call", {
|
|
100
|
-
responseId: "large-fetch",
|
|
101
|
-
url: "https://example.com/large",
|
|
102
|
-
offset: 30_000,
|
|
103
|
-
limit: 5,
|
|
104
|
-
});
|
|
105
|
-
const text = result.content[0].text;
|
|
106
|
-
|
|
107
|
-
assert.equal(result.details.offset, 30_000);
|
|
108
|
-
assert.equal(result.details.limit, 5);
|
|
109
|
-
assert.equal(result.details.returnedChars, 5);
|
|
110
|
-
assert.equal(result.details.nextOffset, 30_005);
|
|
111
|
-
assert.match(text, /BCDEF/);
|
|
112
|
-
assert.doesNotMatch(text, /GHIJ/);
|
|
113
|
-
assert.match(text, /urlIndex: 0, offset: 30005, limit: 5/);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test("get_search_content rejects unsafe fetched content ranges", async () => {
|
|
117
|
-
const tool = getContentTool();
|
|
118
|
-
storeFetchedContent("short content");
|
|
119
|
-
|
|
120
|
-
const tooLarge = await tool.execute("call", { responseId: "large-fetch", urlIndex: 0, limit: 30_001 });
|
|
121
|
-
assert.equal(tooLarge.details.error, "Invalid limit");
|
|
122
|
-
assert.match(tooLarge.content[0].text, /received 30001/);
|
|
123
|
-
assert.match(tooLarge.content[0].text, /limit must be an integer from 1 to 30000/);
|
|
124
|
-
|
|
125
|
-
const invalidOffset = await tool.execute("call", { responseId: "large-fetch", urlIndex: 0, offset: 1.5 });
|
|
126
|
-
assert.equal(invalidOffset.details.error, "Invalid offset");
|
|
127
|
-
assert.match(invalidOffset.content[0].text, /received 1\.5/);
|
|
128
|
-
assert.match(invalidOffset.content[0].text, /Use 0 or a larger integer/);
|
|
129
|
-
|
|
130
|
-
const outOfRange = await tool.execute("call", { responseId: "large-fetch", urlIndex: 0, offset: 99 });
|
|
131
|
-
assert.equal(outOfRange.details.error, "Offset out of range");
|
|
132
|
-
assert.match(outOfRange.content[0].text, /Received offset 99/);
|
|
133
|
-
assert.match(outOfRange.content[0].text, /valid range is 0-13/);
|
|
134
|
-
|
|
135
|
-
const missingFindText = await tool.execute("call", { responseId: "large-fetch", urlIndex: 0, findMode: "fuzzy" });
|
|
136
|
-
assert.equal(missingFindText.details.error, "findMode requires findText");
|
|
137
|
-
assert.match(missingFindText.content[0].text, /findMode "fuzzy" requires findText/);
|
|
138
|
-
|
|
139
|
-
const artifact = buildResearchArtifact({
|
|
140
|
-
query: "stored claim",
|
|
141
|
-
results: [{ url: "https://example.com/research", title: "Research source", snippet: "The bridge defaults match this research passage.", rank: 1 }],
|
|
142
|
-
summary: "Unique bridge research summary marker.",
|
|
143
|
-
});
|
|
144
|
-
artifact.id = "stored-research";
|
|
145
|
-
storeResearchArtifact(artifact);
|
|
146
|
-
const researchInvalidLimit = await tool.execute("call", { responseId: "stored-research", limit: 30_001 });
|
|
147
|
-
assert.equal(researchInvalidLimit.details.error, "Invalid limit");
|
|
148
|
-
assert.match(researchInvalidLimit.content[0].text, /received 30001/);
|
|
149
|
-
assert.match(researchInvalidLimit.content[0].text, /limit must be an integer from 1 to 30000/);
|
|
150
|
-
|
|
151
|
-
const researchOutOfRange = await tool.execute("call", { responseId: "stored-research", offset: 99_999 });
|
|
152
|
-
assert.equal(researchOutOfRange.details.error, "Offset out of range");
|
|
153
|
-
assert.match(researchOutOfRange.content[0].text, /responseId "stored-research"/);
|
|
154
|
-
assert.match(researchOutOfRange.content[0].text, /valid range is 0-/);
|
|
155
|
-
|
|
156
|
-
const researchFind = await tool.execute("call", {
|
|
157
|
-
responseId: "stored-research",
|
|
158
|
-
offset: 0,
|
|
159
|
-
limit: 10_000,
|
|
160
|
-
findText: "Unique bridge research summary marker",
|
|
161
|
-
});
|
|
162
|
-
assert.equal(researchFind.details.type, "research");
|
|
163
|
-
assert.equal(researchFind.details.findMode, "case-insensitive");
|
|
164
|
-
assert.equal(researchFind.details.matchCount, 1);
|
|
165
|
-
assert.match(researchFind.content[0].text, /^Text matches \(case-insensitive\)/);
|
|
166
|
-
assert.match(researchFind.content[0].text, /Unique bridge research summary marker/);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test("get_search_content normalizes bridge defaults for search matches", async () => {
|
|
170
|
-
const tool = getContentTool();
|
|
171
|
-
storeSearchContent();
|
|
172
|
-
|
|
173
|
-
const result = await tool.execute("call", {
|
|
174
|
-
responseId: "search-result",
|
|
175
|
-
query: "",
|
|
176
|
-
queryIndex: 0,
|
|
177
|
-
url: "",
|
|
178
|
-
urlIndex: 0,
|
|
179
|
-
offset: 0,
|
|
180
|
-
limit: 10_000,
|
|
181
|
-
findText: ["ScriptManager.swift", "UNIX script", "ScriptMenu"],
|
|
182
|
-
findMode: "case-insensitive",
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
assert.equal(result.details.findMode, "case-insensitive");
|
|
186
|
-
assert.equal(result.details.matchCount, 3);
|
|
187
|
-
assert.match(result.content[0].text, /ScriptManager\.swift/);
|
|
188
|
-
assert.match(result.content[0].text, /ScriptMenu/);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
test("get_search_content returns small fetched content without continuation noise", async () => {
|
|
192
|
-
const tool = getContentTool();
|
|
193
|
-
storeFetchedContent("small content");
|
|
194
|
-
|
|
195
|
-
const result = await tool.execute("call", { responseId: "large-fetch", urlIndex: 0 });
|
|
196
|
-
const text = result.content[0].text;
|
|
197
|
-
|
|
198
|
-
assert.equal(result.details.returnedChars, "small content".length);
|
|
199
|
-
assert.equal(result.details.nextOffset, null);
|
|
200
|
-
assert.match(text, /small content/);
|
|
201
|
-
assert.doesNotMatch(text, /Showing chars/);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test("get_search_content finds bounded passages in stored fetched content", async () => {
|
|
205
|
-
const tool = getContentTool();
|
|
206
|
-
storeFetchedContent(`prefix ${"A".repeat(2_000)} Installation requires Node 22. ${"B".repeat(2_000)} suffix`);
|
|
207
|
-
|
|
208
|
-
const result = await tool.execute("call", {
|
|
209
|
-
responseId: "large-fetch",
|
|
210
|
-
query: "",
|
|
211
|
-
queryIndex: 0,
|
|
212
|
-
url: "",
|
|
213
|
-
urlIndex: 0,
|
|
214
|
-
offset: 0,
|
|
215
|
-
limit: 10_000,
|
|
216
|
-
findText: "installation",
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
assert.equal(result.details.matchCount, 1);
|
|
220
|
-
assert.equal(result.details.findMode, "case-insensitive");
|
|
221
|
-
assert.match(result.content[0].text, /Installation requires Node 22/);
|
|
222
|
-
assert.ok(result.content[0].text.length < 1_000);
|
|
223
|
-
});
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import { Value } from "typebox/value";
|
|
4
|
+
|
|
5
|
+
import initializeExtension from "../index.ts";
|
|
6
|
+
import { buildResearchArtifact, storeResearchArtifact } from "../source-check.ts";
|
|
7
|
+
import { clearResults, storeResult } from "../storage.ts";
|
|
8
|
+
|
|
9
|
+
function getContentTool() {
|
|
10
|
+
clearResults();
|
|
11
|
+
const tools = [];
|
|
12
|
+
initializeExtension({
|
|
13
|
+
registerTool(tool) { tools.push(tool); },
|
|
14
|
+
registerCommand() {},
|
|
15
|
+
registerShortcut() {},
|
|
16
|
+
on() {},
|
|
17
|
+
});
|
|
18
|
+
const tool = tools.find((registered) => registered.name === "get_search_content");
|
|
19
|
+
assert.ok(tool, "get_search_content tool was not registered");
|
|
20
|
+
return tool;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function storeFetchedContent(content) {
|
|
24
|
+
storeResult("large-fetch", {
|
|
25
|
+
id: "large-fetch",
|
|
26
|
+
type: "fetch",
|
|
27
|
+
timestamp: Date.now(),
|
|
28
|
+
urls: [{
|
|
29
|
+
url: "https://example.com/large",
|
|
30
|
+
title: "Large Page",
|
|
31
|
+
content,
|
|
32
|
+
error: null,
|
|
33
|
+
}],
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function storeSearchContent() {
|
|
38
|
+
storeResult("search-result", {
|
|
39
|
+
id: "search-result",
|
|
40
|
+
type: "search",
|
|
41
|
+
timestamp: Date.now(),
|
|
42
|
+
queries: [{
|
|
43
|
+
query: "CotEditor scripts",
|
|
44
|
+
answer: "",
|
|
45
|
+
results: [
|
|
46
|
+
{ title: "ScriptManager.swift", url: "https://example.com/script-manager", snippet: "UNIX script support" },
|
|
47
|
+
{ title: "UNIX script", url: "https://example.com/unix-script", snippet: "Script support" },
|
|
48
|
+
{ title: "ScriptMenu", url: "https://example.com/script-menu", snippet: "Menu integration" },
|
|
49
|
+
],
|
|
50
|
+
error: null,
|
|
51
|
+
}],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
test("get_search_content schemas constrain numeric parameters", () => {
|
|
56
|
+
const properties = getContentTool().parameters.properties;
|
|
57
|
+
|
|
58
|
+
for (const name of ["queryIndex", "urlIndex", "offset"]) {
|
|
59
|
+
assert.equal(properties[name].type, "integer");
|
|
60
|
+
assert.equal(properties[name].minimum, 0);
|
|
61
|
+
assert.equal(properties[name].maximum, undefined);
|
|
62
|
+
for (const value of [-1, 1.5, Number.NaN, Number.POSITIVE_INFINITY]) {
|
|
63
|
+
assert.equal(Value.Check(properties[name], value), false, `${name} accepts ${value}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
assert.equal(properties.limit.type, "integer");
|
|
68
|
+
assert.equal(properties.limit.minimum, 1);
|
|
69
|
+
assert.equal(properties.limit.maximum, 30_000);
|
|
70
|
+
for (const value of [0, 1.5, 30_001, Number.NaN, Number.POSITIVE_INFINITY]) {
|
|
71
|
+
assert.equal(Value.Check(properties.limit, value), false, `limit accepts ${value}`);
|
|
72
|
+
}
|
|
73
|
+
for (const value of [1, 30_000]) {
|
|
74
|
+
assert.equal(Value.Check(properties.limit, value), true, `limit rejects ${value}`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("get_search_content returns a bounded first slice for large fetched content", async () => {
|
|
79
|
+
const tool = getContentTool();
|
|
80
|
+
storeFetchedContent("A".repeat(30_000) + "TAIL");
|
|
81
|
+
|
|
82
|
+
const result = await tool.execute("call", { responseId: "large-fetch", urlIndex: 0 });
|
|
83
|
+
const text = result.content[0].text;
|
|
84
|
+
|
|
85
|
+
assert.equal(result.details.contentLength, 30_004);
|
|
86
|
+
assert.equal(result.details.offset, 0);
|
|
87
|
+
assert.equal(result.details.returnedChars, 30_000);
|
|
88
|
+
assert.equal(result.details.nextOffset, 30_000);
|
|
89
|
+
assert.equal(result.details.truncated, true);
|
|
90
|
+
assert.match(text, /Showing chars 0-30000 of 30004/);
|
|
91
|
+
assert.match(text, /offset: 30000/);
|
|
92
|
+
assert.doesNotMatch(text, /TAIL/);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("get_search_content returns requested fetched content slices", async () => {
|
|
96
|
+
const tool = getContentTool();
|
|
97
|
+
storeFetchedContent("A".repeat(30_000) + "BCDEFGHIJ");
|
|
98
|
+
|
|
99
|
+
const result = await tool.execute("call", {
|
|
100
|
+
responseId: "large-fetch",
|
|
101
|
+
url: "https://example.com/large",
|
|
102
|
+
offset: 30_000,
|
|
103
|
+
limit: 5,
|
|
104
|
+
});
|
|
105
|
+
const text = result.content[0].text;
|
|
106
|
+
|
|
107
|
+
assert.equal(result.details.offset, 30_000);
|
|
108
|
+
assert.equal(result.details.limit, 5);
|
|
109
|
+
assert.equal(result.details.returnedChars, 5);
|
|
110
|
+
assert.equal(result.details.nextOffset, 30_005);
|
|
111
|
+
assert.match(text, /BCDEF/);
|
|
112
|
+
assert.doesNotMatch(text, /GHIJ/);
|
|
113
|
+
assert.match(text, /urlIndex: 0, offset: 30005, limit: 5/);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("get_search_content rejects unsafe fetched content ranges", async () => {
|
|
117
|
+
const tool = getContentTool();
|
|
118
|
+
storeFetchedContent("short content");
|
|
119
|
+
|
|
120
|
+
const tooLarge = await tool.execute("call", { responseId: "large-fetch", urlIndex: 0, limit: 30_001 });
|
|
121
|
+
assert.equal(tooLarge.details.error, "Invalid limit");
|
|
122
|
+
assert.match(tooLarge.content[0].text, /received 30001/);
|
|
123
|
+
assert.match(tooLarge.content[0].text, /limit must be an integer from 1 to 30000/);
|
|
124
|
+
|
|
125
|
+
const invalidOffset = await tool.execute("call", { responseId: "large-fetch", urlIndex: 0, offset: 1.5 });
|
|
126
|
+
assert.equal(invalidOffset.details.error, "Invalid offset");
|
|
127
|
+
assert.match(invalidOffset.content[0].text, /received 1\.5/);
|
|
128
|
+
assert.match(invalidOffset.content[0].text, /Use 0 or a larger integer/);
|
|
129
|
+
|
|
130
|
+
const outOfRange = await tool.execute("call", { responseId: "large-fetch", urlIndex: 0, offset: 99 });
|
|
131
|
+
assert.equal(outOfRange.details.error, "Offset out of range");
|
|
132
|
+
assert.match(outOfRange.content[0].text, /Received offset 99/);
|
|
133
|
+
assert.match(outOfRange.content[0].text, /valid range is 0-13/);
|
|
134
|
+
|
|
135
|
+
const missingFindText = await tool.execute("call", { responseId: "large-fetch", urlIndex: 0, findMode: "fuzzy" });
|
|
136
|
+
assert.equal(missingFindText.details.error, "findMode requires findText");
|
|
137
|
+
assert.match(missingFindText.content[0].text, /findMode "fuzzy" requires findText/);
|
|
138
|
+
|
|
139
|
+
const artifact = buildResearchArtifact({
|
|
140
|
+
query: "stored claim",
|
|
141
|
+
results: [{ url: "https://example.com/research", title: "Research source", snippet: "The bridge defaults match this research passage.", rank: 1 }],
|
|
142
|
+
summary: "Unique bridge research summary marker.",
|
|
143
|
+
});
|
|
144
|
+
artifact.id = "stored-research";
|
|
145
|
+
storeResearchArtifact(artifact);
|
|
146
|
+
const researchInvalidLimit = await tool.execute("call", { responseId: "stored-research", limit: 30_001 });
|
|
147
|
+
assert.equal(researchInvalidLimit.details.error, "Invalid limit");
|
|
148
|
+
assert.match(researchInvalidLimit.content[0].text, /received 30001/);
|
|
149
|
+
assert.match(researchInvalidLimit.content[0].text, /limit must be an integer from 1 to 30000/);
|
|
150
|
+
|
|
151
|
+
const researchOutOfRange = await tool.execute("call", { responseId: "stored-research", offset: 99_999 });
|
|
152
|
+
assert.equal(researchOutOfRange.details.error, "Offset out of range");
|
|
153
|
+
assert.match(researchOutOfRange.content[0].text, /responseId "stored-research"/);
|
|
154
|
+
assert.match(researchOutOfRange.content[0].text, /valid range is 0-/);
|
|
155
|
+
|
|
156
|
+
const researchFind = await tool.execute("call", {
|
|
157
|
+
responseId: "stored-research",
|
|
158
|
+
offset: 0,
|
|
159
|
+
limit: 10_000,
|
|
160
|
+
findText: "Unique bridge research summary marker",
|
|
161
|
+
});
|
|
162
|
+
assert.equal(researchFind.details.type, "research");
|
|
163
|
+
assert.equal(researchFind.details.findMode, "case-insensitive");
|
|
164
|
+
assert.equal(researchFind.details.matchCount, 1);
|
|
165
|
+
assert.match(researchFind.content[0].text, /^Text matches \(case-insensitive\)/);
|
|
166
|
+
assert.match(researchFind.content[0].text, /Unique bridge research summary marker/);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("get_search_content normalizes bridge defaults for search matches", async () => {
|
|
170
|
+
const tool = getContentTool();
|
|
171
|
+
storeSearchContent();
|
|
172
|
+
|
|
173
|
+
const result = await tool.execute("call", {
|
|
174
|
+
responseId: "search-result",
|
|
175
|
+
query: "",
|
|
176
|
+
queryIndex: 0,
|
|
177
|
+
url: "",
|
|
178
|
+
urlIndex: 0,
|
|
179
|
+
offset: 0,
|
|
180
|
+
limit: 10_000,
|
|
181
|
+
findText: ["ScriptManager.swift", "UNIX script", "ScriptMenu"],
|
|
182
|
+
findMode: "case-insensitive",
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
assert.equal(result.details.findMode, "case-insensitive");
|
|
186
|
+
assert.equal(result.details.matchCount, 3);
|
|
187
|
+
assert.match(result.content[0].text, /ScriptManager\.swift/);
|
|
188
|
+
assert.match(result.content[0].text, /ScriptMenu/);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("get_search_content returns small fetched content without continuation noise", async () => {
|
|
192
|
+
const tool = getContentTool();
|
|
193
|
+
storeFetchedContent("small content");
|
|
194
|
+
|
|
195
|
+
const result = await tool.execute("call", { responseId: "large-fetch", urlIndex: 0 });
|
|
196
|
+
const text = result.content[0].text;
|
|
197
|
+
|
|
198
|
+
assert.equal(result.details.returnedChars, "small content".length);
|
|
199
|
+
assert.equal(result.details.nextOffset, null);
|
|
200
|
+
assert.match(text, /small content/);
|
|
201
|
+
assert.doesNotMatch(text, /Showing chars/);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("get_search_content finds bounded passages in stored fetched content", async () => {
|
|
205
|
+
const tool = getContentTool();
|
|
206
|
+
storeFetchedContent(`prefix ${"A".repeat(2_000)} Installation requires Node 22. ${"B".repeat(2_000)} suffix`);
|
|
207
|
+
|
|
208
|
+
const result = await tool.execute("call", {
|
|
209
|
+
responseId: "large-fetch",
|
|
210
|
+
query: "",
|
|
211
|
+
queryIndex: 0,
|
|
212
|
+
url: "",
|
|
213
|
+
urlIndex: 0,
|
|
214
|
+
offset: 0,
|
|
215
|
+
limit: 10_000,
|
|
216
|
+
findText: "installation",
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
assert.equal(result.details.matchCount, 1);
|
|
220
|
+
assert.equal(result.details.findMode, "case-insensitive");
|
|
221
|
+
assert.match(result.content[0].text, /Installation requires Node 22/);
|
|
222
|
+
assert.ok(result.content[0].text.length < 1_000);
|
|
223
|
+
});
|