@developerz.ai/aitm 0.0.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 -0
- package/README.md +30 -0
- package/dist/agent-config/agent-config-detector.d.ts +15 -0
- package/dist/agent-config/agent-config-detector.js +56 -0
- package/dist/agent-config/agent-config-detector.js.map +1 -0
- package/dist/cli/args.d.ts +37 -0
- package/dist/cli/args.js +238 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/cli.d.ts +15 -0
- package/dist/cli/cli.js +113 -0
- package/dist/cli/cli.js.map +1 -0
- package/dist/cli/commands.d.ts +83 -0
- package/dist/cli/commands.js +521 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/compaction/compactor.d.ts +20 -0
- package/dist/compaction/compactor.js +75 -0
- package/dist/compaction/compactor.js.map +1 -0
- package/dist/config/config-loader.d.ts +25 -0
- package/dist/config/config-loader.js +275 -0
- package/dist/config/config-loader.js.map +1 -0
- package/dist/config/config-writer.d.ts +14 -0
- package/dist/config/config-writer.js +178 -0
- package/dist/config/config-writer.js.map +1 -0
- package/dist/config/schema.d.ts +85 -0
- package/dist/config/schema.js +38 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/credentials/credentials.d.ts +15 -0
- package/dist/credentials/credentials.js +58 -0
- package/dist/credentials/credentials.js.map +1 -0
- package/dist/credentials/defaults.d.ts +2 -0
- package/dist/credentials/defaults.js +21 -0
- package/dist/credentials/defaults.js.map +1 -0
- package/dist/fs/atomic-write.d.ts +1 -0
- package/dist/fs/atomic-write.js +27 -0
- package/dist/fs/atomic-write.js.map +1 -0
- package/dist/github/errors.d.ts +18 -0
- package/dist/github/errors.js +20 -0
- package/dist/github/errors.js.map +1 -0
- package/dist/github/github-client.d.ts +47 -0
- package/dist/github/github-client.js +417 -0
- package/dist/github/github-client.js.map +1 -0
- package/dist/github/schema.d.ts +44 -0
- package/dist/github/schema.js +23 -0
- package/dist/github/schema.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/logger.d.ts +36 -0
- package/dist/logger/logger.js +123 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/loop/run-loop-adapter.d.ts +46 -0
- package/dist/loop/run-loop-adapter.js +270 -0
- package/dist/loop/run-loop-adapter.js.map +1 -0
- package/dist/loop/take-over-flow.d.ts +57 -0
- package/dist/loop/take-over-flow.js +183 -0
- package/dist/loop/take-over-flow.js.map +1 -0
- package/dist/loop/work-loop.d.ts +95 -0
- package/dist/loop/work-loop.js +211 -0
- package/dist/loop/work-loop.js.map +1 -0
- package/dist/mcp/mcp-client.d.ts +27 -0
- package/dist/mcp/mcp-client.js +123 -0
- package/dist/mcp/mcp-client.js.map +1 -0
- package/dist/mcp/schema.d.ts +53 -0
- package/dist/mcp/schema.js +39 -0
- package/dist/mcp/schema.js.map +1 -0
- package/dist/openrouter/client.d.ts +28 -0
- package/dist/openrouter/client.js +40 -0
- package/dist/openrouter/client.js.map +1 -0
- package/dist/openrouter/model-limits.d.ts +21 -0
- package/dist/openrouter/model-limits.js +39 -0
- package/dist/openrouter/model-limits.js.map +1 -0
- package/dist/openrouter/server-tools.d.ts +35 -0
- package/dist/openrouter/server-tools.js +25 -0
- package/dist/openrouter/server-tools.js.map +1 -0
- package/dist/orchestrator/orchestrator.d.ts +60 -0
- package/dist/orchestrator/orchestrator.js +180 -0
- package/dist/orchestrator/orchestrator.js.map +1 -0
- package/dist/orchestrator/subagent-tools.d.ts +44 -0
- package/dist/orchestrator/subagent-tools.js +133 -0
- package/dist/orchestrator/subagent-tools.js.map +1 -0
- package/dist/orchestrator/system-prompts.d.ts +4 -0
- package/dist/orchestrator/system-prompts.js +78 -0
- package/dist/orchestrator/system-prompts.js.map +1 -0
- package/dist/plan/plan-graph.d.ts +11 -0
- package/dist/plan/plan-graph.js +69 -0
- package/dist/plan/plan-graph.js.map +1 -0
- package/dist/plan/schema.d.ts +30 -0
- package/dist/plan/schema.js +24 -0
- package/dist/plan/schema.js.map +1 -0
- package/dist/state/schema.d.ts +88 -0
- package/dist/state/schema.js +53 -0
- package/dist/state/schema.js.map +1 -0
- package/dist/state/state-store.d.ts +16 -0
- package/dist/state/state-store.js +129 -0
- package/dist/state/state-store.js.map +1 -0
- package/dist/subagents/factory.d.ts +8 -0
- package/dist/subagents/factory.js +10 -0
- package/dist/subagents/factory.js.map +1 -0
- package/dist/subagents/planner.d.ts +31 -0
- package/dist/subagents/planner.js +83 -0
- package/dist/subagents/planner.js.map +1 -0
- package/dist/subagents/reviewer.d.ts +60 -0
- package/dist/subagents/reviewer.js +159 -0
- package/dist/subagents/reviewer.js.map +1 -0
- package/dist/subagents/worker.d.ts +71 -0
- package/dist/subagents/worker.js +180 -0
- package/dist/subagents/worker.js.map +1 -0
- package/dist/testing/temp-repo.d.ts +7 -0
- package/dist/testing/temp-repo.js +21 -0
- package/dist/testing/temp-repo.js.map +1 -0
- package/dist/tools/datetime.d.ts +12 -0
- package/dist/tools/datetime.js +42 -0
- package/dist/tools/datetime.js.map +1 -0
- package/dist/tools/fetch-html.d.ts +32 -0
- package/dist/tools/fetch-html.js +139 -0
- package/dist/tools/fetch-html.js.map +1 -0
- package/dist/tools/github-thread-tool.d.ts +10 -0
- package/dist/tools/github-thread-tool.js +36 -0
- package/dist/tools/github-thread-tool.js.map +1 -0
- package/dist/tools/web-fetch.d.ts +31 -0
- package/dist/tools/web-fetch.js +223 -0
- package/dist/tools/web-fetch.js.map +1 -0
- package/dist/workspace/worktree-pool.d.ts +21 -0
- package/dist/workspace/worktree-pool.js +104 -0
- package/dist/workspace/worktree-pool.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// fetch_html — a heavier-stealth sibling of web-fetch for sites that fingerprint TLS (JA3/JA4)
|
|
2
|
+
// or HTTP/2 frame ordering (Cloudflare JS challenge, Akamai Bot Manager). Node's stock `fetch`
|
|
3
|
+
// can't fake a browser's TLS handshake, so this shells out to a `curl-impersonate` binary
|
|
4
|
+
// (https://github.com/lwthiker/curl-impersonate), which speaks a real Chrome/Firefox/Safari
|
|
5
|
+
// handshake. Same WebFetchOutput shape as web-fetch so a subagent can swap tools without prompt
|
|
6
|
+
// changes — the model should reach for this only when web-fetch returns a 403/challenge.
|
|
7
|
+
//
|
|
8
|
+
// curl-impersonate is an OPTIONAL system binary. The tool never hard-fails at startup: when the
|
|
9
|
+
// binary is absent the call returns a status-0 result whose body explains how to install it
|
|
10
|
+
// (use isFetchHtmlAvailable() at the wiring site to skip registration entirely). No npm dep.
|
|
11
|
+
import { tool } from 'ai';
|
|
12
|
+
import { ExecaError, execa } from 'execa';
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { assertSafeUrl, defaultLookup } from "./web-fetch.js";
|
|
15
|
+
const fetchHtmlInputSchema = z.object({
|
|
16
|
+
url: z.string().url(),
|
|
17
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
18
|
+
maxChars: z.number().int().positive().optional(),
|
|
19
|
+
impersonate: z.enum(['chrome', 'firefox', 'safari']).optional(),
|
|
20
|
+
});
|
|
21
|
+
// Default curl-impersonate target per browser hint. These version strings depend on the
|
|
22
|
+
// installed curl-impersonate build — override via FetchHtmlInit.targets to match your version.
|
|
23
|
+
export const DEFAULT_IMPERSONATE_TARGETS = Object.freeze({ chrome: 'chrome116', firefox: 'firefox117', safari: 'safari15_5' });
|
|
24
|
+
// Default binary — the chrome build ships in most curl-impersonate installs. Point at a
|
|
25
|
+
// different binary (e.g. `curl-impersonate-ff`, an absolute path) via FetchHtmlInit.binary.
|
|
26
|
+
const DEFAULT_BINARY = 'curl-impersonate-chrome';
|
|
27
|
+
const DEFAULT_MAX_CHARS = 200_000;
|
|
28
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
29
|
+
// Hard ceilings on model-provided inputs so one tool call can't exhaust resources: cap the
|
|
30
|
+
// request runtime, and cap maxChars (which also bounds the subprocess maxBuffer below).
|
|
31
|
+
const MAX_TIMEOUT_MS = 120_000;
|
|
32
|
+
const MAX_OUTPUT_CHARS = 5_000_000;
|
|
33
|
+
// Marker separating curl's `--write-out` metadata (redirected to stderr via %{stderr}) from any
|
|
34
|
+
// real stderr noise. Chosen to be vanishingly unlikely to occur in normal output.
|
|
35
|
+
const META = '__AITM_FETCH_HTML_META__';
|
|
36
|
+
function resolveExec(init) {
|
|
37
|
+
return init.exec ?? ((file, args, options) => execa(file, args, options));
|
|
38
|
+
}
|
|
39
|
+
// True when the curl-impersonate binary is callable. Use at the wiring site to decide whether to
|
|
40
|
+
// register fetch_html at all (per CLAUDE.md: gate on "binary present", don't hard-fail).
|
|
41
|
+
export async function isFetchHtmlAvailable(init = {}) {
|
|
42
|
+
const exec = resolveExec(init);
|
|
43
|
+
const binary = init.binary ?? DEFAULT_BINARY;
|
|
44
|
+
try {
|
|
45
|
+
await exec(binary, ['--version']);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function fetchHtmlTool(init = {}) {
|
|
53
|
+
const exec = resolveExec(init);
|
|
54
|
+
const binary = init.binary ?? DEFAULT_BINARY;
|
|
55
|
+
const targets = init.targets ?? DEFAULT_IMPERSONATE_TARGETS;
|
|
56
|
+
const lookup = init.lookup ?? defaultLookup;
|
|
57
|
+
return tool({
|
|
58
|
+
description: 'Fetch a URL using a real browser TLS fingerprint (via curl-impersonate), for sites that block stock fetch with a JS challenge / 403 (Cloudflare, Akamai). Same output shape as web-fetch. Use this only when web-fetch returns a challenge or 403.',
|
|
59
|
+
inputSchema: fetchHtmlInputSchema,
|
|
60
|
+
execute: async (input) => {
|
|
61
|
+
const maxChars = Math.min(input.maxChars ?? DEFAULT_MAX_CHARS, MAX_OUTPUT_CHARS);
|
|
62
|
+
const timeoutMs = Math.min(input.timeoutMs ?? DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
|
|
63
|
+
const safeUrl = await assertSafeUrl(input.url, lookup);
|
|
64
|
+
const target = targets[input.impersonate ?? 'chrome'];
|
|
65
|
+
// -sS: quiet but show errors; -L: follow redirects; --max-time bounds the request.
|
|
66
|
+
// -w '%{stderr}…': write the metadata line to STDERR (so STDOUT is purely the body).
|
|
67
|
+
const args = [
|
|
68
|
+
'--impersonate',
|
|
69
|
+
target,
|
|
70
|
+
'-sSL',
|
|
71
|
+
'--max-time',
|
|
72
|
+
String(Math.max(1, Math.ceil(timeoutMs / 1000))),
|
|
73
|
+
'-w',
|
|
74
|
+
`%{stderr}\n${META}\t%{http_code}\t%{url_effective}\t%{content_type}`,
|
|
75
|
+
safeUrl.toString(),
|
|
76
|
+
];
|
|
77
|
+
try {
|
|
78
|
+
const r = await exec(binary, args, {
|
|
79
|
+
timeout: timeoutMs + 5_000,
|
|
80
|
+
maxBuffer: maxChars * 4 + 2_000_000,
|
|
81
|
+
});
|
|
82
|
+
return buildOutput(input.url, asString(r.stdout), asString(r.stderr), maxChars);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
// curl ran but returned non-zero (e.g. still blocked, timeout): salvage metadata if present.
|
|
86
|
+
if (err instanceof ExecaError) {
|
|
87
|
+
const parsed = parseMeta(asString(err.stderr));
|
|
88
|
+
if (parsed)
|
|
89
|
+
return buildOutput(input.url, asString(err.stdout), asString(err.stderr), maxChars);
|
|
90
|
+
return errorOutput(input.url, err.shortMessage || err.message);
|
|
91
|
+
}
|
|
92
|
+
return errorOutput(input.url, err instanceof Error ? err.message : String(err));
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function buildOutput(requestedUrl, stdout, stderr, maxChars) {
|
|
98
|
+
const meta = parseMeta(stderr);
|
|
99
|
+
const truncated = stdout.length > maxChars;
|
|
100
|
+
return {
|
|
101
|
+
url: requestedUrl,
|
|
102
|
+
finalUrl: meta?.finalUrl || requestedUrl,
|
|
103
|
+
status: meta?.status ?? 0,
|
|
104
|
+
contentType: meta?.contentType ?? null,
|
|
105
|
+
body: truncated ? stdout.slice(0, maxChars) : stdout,
|
|
106
|
+
truncated,
|
|
107
|
+
retrievedAt: new Date().toISOString(),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function parseMeta(stderr) {
|
|
111
|
+
const line = stderr
|
|
112
|
+
.split('\n')
|
|
113
|
+
.reverse()
|
|
114
|
+
.find((l) => l.startsWith(META));
|
|
115
|
+
if (!line)
|
|
116
|
+
return null;
|
|
117
|
+
const [, statusRaw, finalUrl, contentType] = line.split('\t');
|
|
118
|
+
const status = Number.parseInt(statusRaw ?? '', 10);
|
|
119
|
+
return {
|
|
120
|
+
status: Number.isFinite(status) ? status : 0,
|
|
121
|
+
finalUrl: finalUrl ?? '',
|
|
122
|
+
contentType: contentType && contentType.length > 0 ? contentType : null,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function errorOutput(requestedUrl, message) {
|
|
126
|
+
return {
|
|
127
|
+
url: requestedUrl,
|
|
128
|
+
finalUrl: requestedUrl,
|
|
129
|
+
status: 0,
|
|
130
|
+
contentType: null,
|
|
131
|
+
body: `fetch_html failed: ${message}. Is curl-impersonate installed and on PATH? See https://github.com/lwthiker/curl-impersonate`,
|
|
132
|
+
truncated: false,
|
|
133
|
+
retrievedAt: new Date().toISOString(),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function asString(v) {
|
|
137
|
+
return typeof v === 'string' ? v : '';
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=fetch-html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-html.js","sourceRoot":"","sources":["../../src/tools/fetch-html.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,+FAA+F;AAC/F,0FAA0F;AAC1F,4FAA4F;AAC5F,gGAAgG;AAChG,yFAAyF;AACzF,EAAE;AACF,gGAAgG;AAChG,4FAA4F;AAC5F,6FAA6F;AAG7F,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,aAAa,EAAsC,MAAM,gBAAgB,CAAC;AAElG,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IACrB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACjD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAChD,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE;CAChE,CAAC,CAAC;AAKH,wFAAwF;AACxF,+FAA+F;AAC/F,MAAM,CAAC,MAAM,2BAA2B,GAEpC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;AAExF,wFAAwF;AACxF,4FAA4F;AAC5F,MAAM,cAAc,GAAG,yBAAyB,CAAC;AACjD,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAClC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,2FAA2F;AAC3F,wFAAwF;AACxF,MAAM,cAAc,GAAG,OAAO,CAAC;AAC/B,MAAM,gBAAgB,GAAG,SAAS,CAAC;AACnC,gGAAgG;AAChG,kFAAkF;AAClF,MAAM,IAAI,GAAG,0BAA0B,CAAC;AAqBxC,SAAS,WAAW,CAAC,IAAmB;IACtC,OAAO,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED,iGAAiG;AACjG,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAAsB,EAAE;IACjE,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,cAAc,CAAC;IAC7C,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAsB,EAAE;IACpD,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,cAAc,CAAC;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,2BAA2B,CAAC;IAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,aAAa,CAAC;IAE5C,OAAO,IAAI,CAAC;QACV,WAAW,EACT,oPAAoP;QACtP,WAAW,EAAE,oBAAoB;QACjC,OAAO,EAAE,KAAK,EAAE,KAAqB,EAA2B,EAAE;YAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,IAAI,iBAAiB,EAAE,gBAAgB,CAAC,CAAC;YACjF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,kBAAkB,EAAE,cAAc,CAAC,CAAC;YAClF,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACvD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,IAAI,QAAQ,CAAC,CAAC;YACtD,mFAAmF;YACnF,qFAAqF;YACrF,MAAM,IAAI,GAAG;gBACX,eAAe;gBACf,MAAM;gBACN,MAAM;gBACN,YAAY;gBACZ,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC;gBAChD,IAAI;gBACJ,cAAc,IAAI,mDAAmD;gBACrE,OAAO,CAAC,QAAQ,EAAE;aACnB,CAAC;YACF,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE;oBACjC,OAAO,EAAE,SAAS,GAAG,KAAK;oBAC1B,SAAS,EAAE,QAAQ,GAAG,CAAC,GAAG,SAAS;iBACpC,CAAC,CAAC;gBACH,OAAO,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC;YAClF,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,6FAA6F;gBAC7F,IAAI,GAAG,YAAY,UAAU,EAAE,CAAC;oBAC9B,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC/C,IAAI,MAAM;wBACR,OAAO,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC;oBACtF,OAAO,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;gBACjE,CAAC;gBACD,OAAO,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAClF,CAAC;QACH,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,SAAS,WAAW,CAClB,YAAoB,EACpB,MAAc,EACd,MAAc,EACd,QAAgB;IAEhB,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAC/B,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAC;IAC3C,OAAO;QACL,GAAG,EAAE,YAAY;QACjB,QAAQ,EAAE,IAAI,EAAE,QAAQ,IAAI,YAAY;QACxC,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;QACzB,WAAW,EAAE,IAAI,EAAE,WAAW,IAAI,IAAI;QACtC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM;QACpD,SAAS;QACT,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACtC,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAChB,MAAc;IAEd,MAAM,IAAI,GAAG,MAAM;SAChB,KAAK,CAAC,IAAI,CAAC;SACX,OAAO,EAAE;SACT,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,MAAM,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACpD,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC5C,QAAQ,EAAE,QAAQ,IAAI,EAAE;QACxB,WAAW,EAAE,WAAW,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI;KACxE,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,YAAoB,EAAE,OAAe;IACxD,OAAO;QACL,GAAG,EAAE,YAAY;QACjB,QAAQ,EAAE,YAAY;QACtB,MAAM,EAAE,CAAC;QACT,WAAW,EAAE,IAAI;QACjB,IAAI,EAAE,sBAAsB,OAAO,+FAA+F;QAClI,SAAS,EAAE,KAAK;QAChB,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACtC,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,CAAU;IAC1B,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Tool } from 'ai';
|
|
2
|
+
import type { GithubToolInput, GithubToolOutput } from '../subagents/reviewer.ts';
|
|
3
|
+
export type GithubThreadClient = {
|
|
4
|
+
replyToThread(threadId: string, body: string): Promise<void>;
|
|
5
|
+
resolveThread(threadId: string): Promise<void>;
|
|
6
|
+
};
|
|
7
|
+
export type GithubThreadToolInit = {
|
|
8
|
+
github: GithubThreadClient;
|
|
9
|
+
};
|
|
10
|
+
export declare function githubThreadTool(init: GithubThreadToolInit): Tool<GithubToolInput, GithubToolOutput>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Reviewer subagent's github tool — a thin LLM-facing wrapper around the two GitHubClient
|
|
2
|
+
// methods Reviewer needs: replyToThread + resolveThread. Kept as one discriminated-union
|
|
3
|
+
// tool so the SDK only registers a single `github` slot (matches the contract in
|
|
4
|
+
// src/subagents/reviewer.ts §ReviewerTools.github).
|
|
5
|
+
//
|
|
6
|
+
// Lifecycle parallels the claude-task-master `post_comment_replies()` flow (fix_pr.py):
|
|
7
|
+
// 1. agent decides per thread: fixed | replied | wontfix
|
|
8
|
+
// 2. agent calls github tool → replyToThread to post the reply
|
|
9
|
+
// 3. agent calls github tool → resolveThread to mark it resolved (skipped for "replied")
|
|
10
|
+
//
|
|
11
|
+
// `resolveThread` follows the same pattern: optional, only fired when the thread is "done".
|
|
12
|
+
import { tool } from 'ai';
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
// Flat object, not a discriminatedUnion: a union compiles to JSON-Schema `oneOf` in the tool's
|
|
15
|
+
// parameters, which some OpenRouter-routed providers reject ("Invalid arguments passed to the
|
|
16
|
+
// model"). `body` is required only for replyToThread (enforced in execute).
|
|
17
|
+
const githubInputSchema = z.object({
|
|
18
|
+
action: z.enum(['replyToThread', 'resolveThread']),
|
|
19
|
+
threadId: z.string().min(1),
|
|
20
|
+
body: z.string().optional(),
|
|
21
|
+
});
|
|
22
|
+
export function githubThreadTool(init) {
|
|
23
|
+
return tool({
|
|
24
|
+
description: 'Act on a single PR review thread. action="replyToThread" posts a reply; action="resolveThread" marks it resolved. Use replyToThread before resolveThread so the resolution carries an explanation.',
|
|
25
|
+
inputSchema: githubInputSchema,
|
|
26
|
+
execute: async (input) => {
|
|
27
|
+
if (input.action === 'replyToThread') {
|
|
28
|
+
await init.github.replyToThread(input.threadId, input.body ?? '');
|
|
29
|
+
return { ok: true };
|
|
30
|
+
}
|
|
31
|
+
await init.github.resolveThread(input.threadId);
|
|
32
|
+
return { ok: true };
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=github-thread-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-thread-tool.js","sourceRoot":"","sources":["../../src/tools/github-thread-tool.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,yFAAyF;AACzF,iFAAiF;AACjF,oDAAoD;AACpD,EAAE;AACF,wFAAwF;AACxF,2DAA2D;AAC3D,iEAAiE;AACjE,2FAA2F;AAC3F,EAAE;AACF,4FAA4F;AAE5F,OAAO,EAAa,IAAI,EAAE,MAAM,IAAI,CAAC;AACrC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,+FAA+F;AAC/F,8FAA8F;AAC9F,4EAA4E;AAC5E,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;IAClD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAMH,MAAM,UAAU,gBAAgB,CAC9B,IAA0B;IAE1B,OAAO,IAAI,CAAC;QACV,WAAW,EACT,oMAAoM;QACtM,WAAW,EAAE,iBAAiB;QAC9B,OAAO,EAAE,KAAK,EAAE,KAAK,EAA6B,EAAE;YAClD,IAAI,KAAK,CAAC,MAAM,KAAK,eAAe,EAAE,CAAC;gBACrC,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;gBAClE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;YACtB,CAAC;YACD,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAChD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QACtB,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Tool } from 'ai';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
declare const webFetchInputSchema: z.ZodObject<{
|
|
4
|
+
url: z.ZodString;
|
|
5
|
+
maxChars: z.ZodOptional<z.ZodNumber>;
|
|
6
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
referrer: z.ZodOptional<z.ZodString>;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
export type WebFetchInput = z.infer<typeof webFetchInputSchema>;
|
|
10
|
+
export type WebFetchOutput = {
|
|
11
|
+
url: string;
|
|
12
|
+
finalUrl: string;
|
|
13
|
+
status: number;
|
|
14
|
+
contentType: string | null;
|
|
15
|
+
body: string;
|
|
16
|
+
truncated: boolean;
|
|
17
|
+
retrievedAt: string;
|
|
18
|
+
};
|
|
19
|
+
export type LookupFn = (hostname: string) => Promise<Array<{
|
|
20
|
+
address: string;
|
|
21
|
+
}>>;
|
|
22
|
+
export type WebFetchInit = {
|
|
23
|
+
local?: boolean;
|
|
24
|
+
headers?: Record<string, string>;
|
|
25
|
+
lookup?: LookupFn;
|
|
26
|
+
};
|
|
27
|
+
export declare const defaultLookup: LookupFn;
|
|
28
|
+
export declare function assertSafeUrl(rawUrl: string, lookup: LookupFn): Promise<URL>;
|
|
29
|
+
export declare const DEFAULT_STEALTH_HEADERS: Readonly<Record<string, string>>;
|
|
30
|
+
export declare function webFetchTool(init?: WebFetchInit): Tool;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// Local web-fetch tool — default. Runs a stealthed `fetch` from the agent host
|
|
2
|
+
// (Chrome-like User-Agent + Sec-Ch-Ua + Accept-* headers), so the model can pull
|
|
3
|
+
// public docs without paying for OpenRouter's server-tool round-trip.
|
|
4
|
+
//
|
|
5
|
+
// For the OpenRouter server-tool variant (model-decides, billable via Exa/Firecrawl/etc.),
|
|
6
|
+
// see src/openrouter/server-tools.ts §webFetchServerTool. Pick local for cost / speed,
|
|
7
|
+
// server for sites that block scrapers harder than headers fix.
|
|
8
|
+
//
|
|
9
|
+
// SDK ref: docs/vendor/ai-sdk/chunk-02.md §"Tool Calling" — tool({ description, inputSchema, execute }).
|
|
10
|
+
import * as dns from 'node:dns/promises';
|
|
11
|
+
import { tool } from 'ai';
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
const webFetchInputSchema = z.object({
|
|
14
|
+
url: z.string().url(),
|
|
15
|
+
maxChars: z.number().int().positive().optional(),
|
|
16
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
17
|
+
referrer: z.string().optional(),
|
|
18
|
+
});
|
|
19
|
+
export const defaultLookup = async (hostname) => {
|
|
20
|
+
return await dns.lookup(hostname, { all: true });
|
|
21
|
+
};
|
|
22
|
+
// SSRF guard. Rejects non-http(s), private/loopback/link-local literals, and hostnames
|
|
23
|
+
// that resolve to private/loopback IPs. NOT a full DNS-rebinding fix: we don't pin the
|
|
24
|
+
// resolved address at connect time, so a hostile resolver could return a public IP here
|
|
25
|
+
// and a private one when `fetch` re-resolves. Portable connect-time pinning across
|
|
26
|
+
// Bun/Node/Deno isn't possible via standard fetch — that needs a runtime-specific
|
|
27
|
+
// dispatcher (e.g. undici `lookup` on Node) and is out of scope here.
|
|
28
|
+
export async function assertSafeUrl(rawUrl, lookup) {
|
|
29
|
+
let u;
|
|
30
|
+
try {
|
|
31
|
+
u = new URL(rawUrl);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new Error(`Invalid URL: ${rawUrl}`);
|
|
35
|
+
}
|
|
36
|
+
if (u.protocol !== 'http:' && u.protocol !== 'https:') {
|
|
37
|
+
throw new Error(`Only http/https URLs are allowed: ${u.protocol}`);
|
|
38
|
+
}
|
|
39
|
+
const h = u.hostname.toLowerCase();
|
|
40
|
+
if (isPrivateOrLoopbackHost(h)) {
|
|
41
|
+
throw new Error(`Refusing to fetch private/loopback address: ${h}`);
|
|
42
|
+
}
|
|
43
|
+
// For non-literal hostnames, resolve and re-check every returned IP. Closes the
|
|
44
|
+
// "public-looking domain resolves to private IP" bypass on the literal-hostname check.
|
|
45
|
+
if (!isIpLiteral(h)) {
|
|
46
|
+
let addrs;
|
|
47
|
+
try {
|
|
48
|
+
addrs = await lookup(h);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
52
|
+
throw new Error(`DNS lookup failed for ${h}: ${message}`);
|
|
53
|
+
}
|
|
54
|
+
if (addrs.length === 0) {
|
|
55
|
+
throw new Error(`Hostname did not resolve to any address: ${h}`);
|
|
56
|
+
}
|
|
57
|
+
for (const { address } of addrs) {
|
|
58
|
+
if (isPrivateOrLoopbackHost(address.toLowerCase())) {
|
|
59
|
+
throw new Error(`Refusing to fetch hostname resolving to private/loopback: ${h} → ${address}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return u;
|
|
64
|
+
}
|
|
65
|
+
function isIpLiteral(h) {
|
|
66
|
+
// Brackets are already stripped by URL.hostname for IPv6, but be defensive.
|
|
67
|
+
const s = h.startsWith('[') && h.endsWith(']') ? h.slice(1, -1) : h;
|
|
68
|
+
// IPv6 literals always contain a colon. IPv4 literals are pure dotted quads.
|
|
69
|
+
if (s.includes(':'))
|
|
70
|
+
return true;
|
|
71
|
+
return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(s);
|
|
72
|
+
}
|
|
73
|
+
function isPrivateOrLoopbackHost(h) {
|
|
74
|
+
// WHATWG URL preserves brackets on IPv6 hostnames — strip them so the rest can pattern-match.
|
|
75
|
+
if (h.startsWith('[') && h.endsWith(']'))
|
|
76
|
+
h = h.slice(1, -1);
|
|
77
|
+
if (h === 'localhost' || h === '::1' || h === '::' || h.endsWith('.localhost'))
|
|
78
|
+
return true;
|
|
79
|
+
// IPv4-mapped IPv6 (::ffff:0:0/96). URL normalizes ::ffff:127.0.0.1 → ::ffff:7f00:1
|
|
80
|
+
// (compressed hex), so handle both dotted and hex tails. Extract embedded IPv4 and re-check.
|
|
81
|
+
if (h.startsWith('::ffff:')) {
|
|
82
|
+
const tail = h.slice(7);
|
|
83
|
+
const dotted = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(tail);
|
|
84
|
+
if (dotted?.[1])
|
|
85
|
+
return isPrivateOrLoopbackHost(dotted[1]);
|
|
86
|
+
const hex = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(tail);
|
|
87
|
+
if (hex?.[1] && hex[2]) {
|
|
88
|
+
const high = parseInt(hex[1], 16);
|
|
89
|
+
const low = parseInt(hex[2], 16);
|
|
90
|
+
const ipv4 = `${(high >> 8) & 0xff}.${high & 0xff}.${(low >> 8) & 0xff}.${low & 0xff}`;
|
|
91
|
+
return isPrivateOrLoopbackHost(ipv4);
|
|
92
|
+
}
|
|
93
|
+
// Unrecognized ::ffff: form — block as a safety net rather than fall through.
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
const ipv4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(h);
|
|
97
|
+
if (ipv4) {
|
|
98
|
+
const a = Number(ipv4[1]);
|
|
99
|
+
const b = Number(ipv4[2]);
|
|
100
|
+
if (a === 0 || a === 10 || a === 127)
|
|
101
|
+
return true;
|
|
102
|
+
if (a === 169 && b === 254)
|
|
103
|
+
return true;
|
|
104
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
105
|
+
return true;
|
|
106
|
+
if (a === 192 && b === 168)
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
// IPv6 unique-local fc00::/7, link-local fe80::/10 — only meaningful on IPv6 literals,
|
|
110
|
+
// otherwise we'd block valid public domains like `fc-example.com`.
|
|
111
|
+
if (h.includes(':')) {
|
|
112
|
+
if (h.startsWith('fc') || h.startsWith('fd'))
|
|
113
|
+
return true;
|
|
114
|
+
if (h.startsWith('fe8') || h.startsWith('fe9') || h.startsWith('fea') || h.startsWith('feb')) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
// Stream the body until `maxChars` UTF-8 characters have been collected, then cancel
|
|
121
|
+
// the reader. Avoids buffering an entire huge response into memory just to slice it.
|
|
122
|
+
async function readBodyCapped(response, maxChars) {
|
|
123
|
+
if (!response.body)
|
|
124
|
+
return { body: '', truncated: false };
|
|
125
|
+
const reader = response.body.getReader();
|
|
126
|
+
const decoder = new TextDecoder();
|
|
127
|
+
let body = '';
|
|
128
|
+
let truncated = false;
|
|
129
|
+
try {
|
|
130
|
+
while (true) {
|
|
131
|
+
const { done, value } = await reader.read();
|
|
132
|
+
if (done) {
|
|
133
|
+
body += decoder.decode();
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
body += decoder.decode(value, { stream: true });
|
|
137
|
+
if (body.length >= maxChars) {
|
|
138
|
+
truncated = body.length > maxChars;
|
|
139
|
+
body = body.slice(0, maxChars);
|
|
140
|
+
await reader.cancel();
|
|
141
|
+
return { body, truncated: true };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
// Best-effort release; cancel after `done` is a no-op but throws on some streams.
|
|
147
|
+
try {
|
|
148
|
+
reader.releaseLock();
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// ignore
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return { body, truncated };
|
|
155
|
+
}
|
|
156
|
+
// Default stealth headers — Chrome on macOS. Override via WebFetchInit.headers if a site
|
|
157
|
+
// is blocking this exact fingerprint. Keep them in one place so it's easy to bump versions.
|
|
158
|
+
export const DEFAULT_STEALTH_HEADERS = Object.freeze({
|
|
159
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
|
160
|
+
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
|
161
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
162
|
+
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
163
|
+
'Sec-Ch-Ua': '"Chromium";v="131", "Not_A Brand";v="24", "Google Chrome";v="131"',
|
|
164
|
+
'Sec-Ch-Ua-Mobile': '?0',
|
|
165
|
+
'Sec-Ch-Ua-Platform': '"macOS"',
|
|
166
|
+
'Sec-Fetch-Dest': 'document',
|
|
167
|
+
'Sec-Fetch-Mode': 'navigate',
|
|
168
|
+
'Sec-Fetch-Site': 'none',
|
|
169
|
+
'Sec-Fetch-User': '?1',
|
|
170
|
+
'Upgrade-Insecure-Requests': '1',
|
|
171
|
+
});
|
|
172
|
+
const DEFAULT_MAX_CHARS = 200_000;
|
|
173
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
174
|
+
const SERVER_TOOL_STUB = 'Local web-fetch is disabled. Use the openrouter:web_fetch server tool instead (see webFetchServerTool in src/openrouter/server-tools.ts).';
|
|
175
|
+
export function webFetchTool(init = {}) {
|
|
176
|
+
const local = init.local ?? true;
|
|
177
|
+
const headers = { ...DEFAULT_STEALTH_HEADERS, ...init.headers };
|
|
178
|
+
const lookup = init.lookup ?? defaultLookup;
|
|
179
|
+
if (!local) {
|
|
180
|
+
return tool({
|
|
181
|
+
description: 'Fetch a URL via stealthed local HTTP. Stubbed: this instance delegates to OpenRouter web_fetch server tool.',
|
|
182
|
+
inputSchema: webFetchInputSchema,
|
|
183
|
+
execute: async (input) => ({
|
|
184
|
+
url: input.url,
|
|
185
|
+
finalUrl: input.url,
|
|
186
|
+
status: 0,
|
|
187
|
+
contentType: null,
|
|
188
|
+
body: SERVER_TOOL_STUB,
|
|
189
|
+
truncated: false,
|
|
190
|
+
retrievedAt: new Date().toISOString(),
|
|
191
|
+
}),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return tool({
|
|
195
|
+
description: 'Fetch a URL with browser-like headers and return the response body. Body is truncated to maxChars (default 200_000).',
|
|
196
|
+
inputSchema: webFetchInputSchema,
|
|
197
|
+
execute: async (input) => {
|
|
198
|
+
const maxChars = input.maxChars ?? DEFAULT_MAX_CHARS;
|
|
199
|
+
const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
200
|
+
const safeUrl = await assertSafeUrl(input.url, lookup);
|
|
201
|
+
const requestHeaders = { ...headers };
|
|
202
|
+
if (input.referrer !== undefined) {
|
|
203
|
+
requestHeaders.Referer = input.referrer;
|
|
204
|
+
}
|
|
205
|
+
const response = await fetch(safeUrl, {
|
|
206
|
+
headers: requestHeaders,
|
|
207
|
+
redirect: 'follow',
|
|
208
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
209
|
+
});
|
|
210
|
+
const { body, truncated } = await readBodyCapped(response, maxChars);
|
|
211
|
+
return {
|
|
212
|
+
url: input.url,
|
|
213
|
+
finalUrl: response.url || input.url,
|
|
214
|
+
status: response.status,
|
|
215
|
+
contentType: response.headers.get('content-type'),
|
|
216
|
+
body,
|
|
217
|
+
truncated,
|
|
218
|
+
retrievedAt: new Date().toISOString(),
|
|
219
|
+
};
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=web-fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-fetch.js","sourceRoot":"","sources":["../../src/tools/web-fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type Worktree = {
|
|
2
|
+
groupId: string;
|
|
3
|
+
branch: string;
|
|
4
|
+
path: string;
|
|
5
|
+
};
|
|
6
|
+
export declare class WorktreePool {
|
|
7
|
+
private readonly repoRoot;
|
|
8
|
+
private readonly stateDir;
|
|
9
|
+
private readonly maxConcurrent;
|
|
10
|
+
private readonly worktrees;
|
|
11
|
+
private readonly pendingGroupIds;
|
|
12
|
+
private readonly waiters;
|
|
13
|
+
private reserved;
|
|
14
|
+
constructor(repoRoot: string, stateDir: string, maxConcurrent: number);
|
|
15
|
+
acquire(groupId: string, branch: string, baseBranch: string): Promise<Worktree>;
|
|
16
|
+
release(groupId: string): Promise<void>;
|
|
17
|
+
releaseAll(): Promise<void>;
|
|
18
|
+
active(): ReadonlyArray<Worktree>;
|
|
19
|
+
private reserveSlot;
|
|
20
|
+
private freeSlot;
|
|
21
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Concurrent Workers cannot share the same git working tree without trampling each other.
|
|
2
|
+
// WorktreePool issues an isolated `git worktree` per active group under
|
|
3
|
+
// .ai-task-master/worktrees/<group-id>/. On release, the worktree is removed.
|
|
4
|
+
//
|
|
5
|
+
// docs/state.md (state dir layout — `worktrees/` is a new subtree added for concurrent runs)
|
|
6
|
+
// docs/task-groups.md §Branching (branch naming: aitm/<runId>/<group.id>)
|
|
7
|
+
// docs/runtime.md (uses execa, never Bun.$)
|
|
8
|
+
import { mkdir } from 'node:fs/promises';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { execa } from 'execa';
|
|
11
|
+
// Restrict groupId to characters safe as a single path segment so `join(worktreesDir,
|
|
12
|
+
// groupId)` cannot escape into a parent directory and `release()`'s force-remove of
|
|
13
|
+
// the computed path cannot reach outside `worktrees/`.
|
|
14
|
+
const SAFE_GROUP_ID = /^[A-Za-z0-9._-]+$/;
|
|
15
|
+
function assertSafeGroupId(groupId) {
|
|
16
|
+
if (!SAFE_GROUP_ID.test(groupId) || groupId === '.' || groupId === '..') {
|
|
17
|
+
throw new Error(`invalid groupId: ${groupId}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class WorktreePool {
|
|
21
|
+
repoRoot;
|
|
22
|
+
stateDir;
|
|
23
|
+
maxConcurrent;
|
|
24
|
+
worktrees = new Map();
|
|
25
|
+
// `pendingGroupIds` covers acquires that have passed the duplicate check but not yet
|
|
26
|
+
// completed `git worktree add`. Without it, two concurrent `acquire(g1, ...)` calls
|
|
27
|
+
// could both pass the `worktrees.has` gate and race in `git worktree add`.
|
|
28
|
+
pendingGroupIds = new Set();
|
|
29
|
+
waiters = [];
|
|
30
|
+
// `reserved` covers both checked-out worktrees AND acquires that have passed the
|
|
31
|
+
// gate but not yet completed `git worktree add`. Without this, two concurrent
|
|
32
|
+
// acquire() calls could both pass `worktrees.size < maxConcurrent` and exceed the cap.
|
|
33
|
+
reserved = 0;
|
|
34
|
+
constructor(repoRoot, stateDir, maxConcurrent) {
|
|
35
|
+
this.repoRoot = repoRoot;
|
|
36
|
+
this.stateDir = stateDir;
|
|
37
|
+
this.maxConcurrent = maxConcurrent;
|
|
38
|
+
}
|
|
39
|
+
async acquire(groupId, branch, baseBranch) {
|
|
40
|
+
assertSafeGroupId(groupId);
|
|
41
|
+
if (this.worktrees.has(groupId) || this.pendingGroupIds.has(groupId)) {
|
|
42
|
+
throw new Error(`worktree already acquired for group ${groupId}`);
|
|
43
|
+
}
|
|
44
|
+
this.pendingGroupIds.add(groupId);
|
|
45
|
+
await this.reserveSlot();
|
|
46
|
+
const worktreesDir = join(this.stateDir, 'worktrees');
|
|
47
|
+
const path = join(worktreesDir, groupId);
|
|
48
|
+
try {
|
|
49
|
+
await mkdir(worktreesDir, { recursive: true });
|
|
50
|
+
await execa('git', ['worktree', 'add', path, '-b', branch, baseBranch], {
|
|
51
|
+
cwd: this.repoRoot,
|
|
52
|
+
});
|
|
53
|
+
const wt = { groupId, branch, path };
|
|
54
|
+
this.worktrees.set(groupId, wt);
|
|
55
|
+
return wt;
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
this.freeSlot();
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
this.pendingGroupIds.delete(groupId);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async release(groupId) {
|
|
66
|
+
const wt = this.worktrees.get(groupId);
|
|
67
|
+
if (!wt)
|
|
68
|
+
return;
|
|
69
|
+
// Don't drop tracking until `git worktree remove` succeeds: a failed cleanup must
|
|
70
|
+
// remain re-tryable via `release(groupId)`, and the slot must stay reserved so the
|
|
71
|
+
// pool doesn't overbook after an untracked worktree is left on disk.
|
|
72
|
+
await execa('git', ['worktree', 'remove', '--force', wt.path], { cwd: this.repoRoot });
|
|
73
|
+
this.worktrees.delete(groupId);
|
|
74
|
+
this.freeSlot();
|
|
75
|
+
}
|
|
76
|
+
async releaseAll() {
|
|
77
|
+
const ids = Array.from(this.worktrees.keys());
|
|
78
|
+
for (const id of ids) {
|
|
79
|
+
await this.release(id);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
active() {
|
|
83
|
+
return Array.from(this.worktrees.values());
|
|
84
|
+
}
|
|
85
|
+
async reserveSlot() {
|
|
86
|
+
if (this.reserved < this.maxConcurrent) {
|
|
87
|
+
this.reserved++;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
await new Promise((resolve) => {
|
|
91
|
+
this.waiters.push(resolve);
|
|
92
|
+
});
|
|
93
|
+
// Slot was transferred from a releaser via freeSlot(); `reserved` is unchanged.
|
|
94
|
+
}
|
|
95
|
+
freeSlot() {
|
|
96
|
+
const next = this.waiters.shift();
|
|
97
|
+
if (next) {
|
|
98
|
+
next();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this.reserved--;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=worktree-pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree-pool.js","sourceRoot":"","sources":["../../src/workspace/worktree-pool.ts"],"names":[],"mappings":"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"}
|