veefore 1.4.1 → 1.5.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/dist/veefore.mjs +1167 -224
- package/package.json +1 -1
package/dist/veefore.mjs
CHANGED
|
@@ -236,29 +236,29 @@ var ApiClient = class {
|
|
|
236
236
|
setToken(token) {
|
|
237
237
|
this.token = token;
|
|
238
238
|
}
|
|
239
|
-
async get(
|
|
239
|
+
async get(path14, query = {}) {
|
|
240
240
|
const search = new URLSearchParams();
|
|
241
241
|
for (const [key, value] of Object.entries(query)) {
|
|
242
242
|
if (value !== void 0) search.set(key, String(value));
|
|
243
243
|
}
|
|
244
244
|
const suffix = search.toString() === "" ? "" : `?${search.toString()}`;
|
|
245
|
-
return this.send("GET", `${
|
|
245
|
+
return this.send("GET", `${path14}${suffix}`);
|
|
246
246
|
}
|
|
247
|
-
async post(
|
|
248
|
-
return this.send("POST",
|
|
247
|
+
async post(path14, body) {
|
|
248
|
+
return this.send("POST", path14, body);
|
|
249
249
|
}
|
|
250
|
-
async patch(
|
|
251
|
-
return this.send("PATCH",
|
|
250
|
+
async patch(path14, body) {
|
|
251
|
+
return this.send("PATCH", path14, body);
|
|
252
252
|
}
|
|
253
|
-
async delete(
|
|
254
|
-
return this.send("DELETE",
|
|
253
|
+
async delete(path14) {
|
|
254
|
+
return this.send("DELETE", path14);
|
|
255
255
|
}
|
|
256
256
|
/** Raw request for endpoints that return a non-JSON body (exports). */
|
|
257
|
-
async raw(method,
|
|
258
|
-
return this.fetchWithTimeout(method,
|
|
257
|
+
async raw(method, path14, body) {
|
|
258
|
+
return this.fetchWithTimeout(method, path14, body);
|
|
259
259
|
}
|
|
260
|
-
async send(method,
|
|
261
|
-
const response = await this.fetchWithTimeout(method,
|
|
260
|
+
async send(method, path14, body) {
|
|
261
|
+
const response = await this.fetchWithTimeout(method, path14, body);
|
|
262
262
|
const text = await response.text();
|
|
263
263
|
const parsed = parseJson(text);
|
|
264
264
|
if (response.ok) {
|
|
@@ -266,14 +266,14 @@ var ApiClient = class {
|
|
|
266
266
|
if (parsed && "data" in parsed) return parsed.data;
|
|
267
267
|
throw new CliApiError(
|
|
268
268
|
"UNEXPECTED_RESPONSE",
|
|
269
|
-
`The control plane returned an unexpected body for ${method} ${
|
|
269
|
+
`The control plane returned an unexpected body for ${method} ${path14}.`,
|
|
270
270
|
response.status,
|
|
271
271
|
response.headers.get("x-request-id")
|
|
272
272
|
);
|
|
273
273
|
}
|
|
274
274
|
const error = new CliApiError(
|
|
275
275
|
parsed?.error?.code ?? `HTTP_${response.status}`,
|
|
276
|
-
parsed?.error?.message ?? `${method} ${
|
|
276
|
+
parsed?.error?.message ?? `${method} ${path14} failed with ${response.status} ${response.statusText}.`,
|
|
277
277
|
response.status,
|
|
278
278
|
parsed?.error?.request_id ?? response.headers.get("x-request-id"),
|
|
279
279
|
parsed?.error?.details ?? {}
|
|
@@ -291,7 +291,7 @@ var ApiClient = class {
|
|
|
291
291
|
}
|
|
292
292
|
throw error;
|
|
293
293
|
}
|
|
294
|
-
async fetchWithTimeout(method,
|
|
294
|
+
async fetchWithTimeout(method, path14, body) {
|
|
295
295
|
const controller = new AbortController();
|
|
296
296
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
297
297
|
const headers = { accept: "application/json" };
|
|
@@ -300,7 +300,7 @@ var ApiClient = class {
|
|
|
300
300
|
if (this.workspaceId) headers["x-veefore-workspace"] = this.workspaceId;
|
|
301
301
|
if (this.environmentId) headers["x-veefore-environment"] = this.environmentId;
|
|
302
302
|
try {
|
|
303
|
-
return await fetch(`${this.controlPlaneUrl}${
|
|
303
|
+
return await fetch(`${this.controlPlaneUrl}${path14}`, {
|
|
304
304
|
method,
|
|
305
305
|
headers,
|
|
306
306
|
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
@@ -1946,11 +1946,11 @@ function renderRevokeConfirmation(agent, environment, preview) {
|
|
|
1946
1946
|
out(` ${style.red("This action is irreversible for this identity.")}`);
|
|
1947
1947
|
out();
|
|
1948
1948
|
}
|
|
1949
|
-
async function lifecycle(args, flags,
|
|
1949
|
+
async function lifecycle(args, flags, path14, resultingStatus) {
|
|
1950
1950
|
const context = await resolveContext(flags);
|
|
1951
1951
|
const agentId = positional(args) ?? context.config.agentId;
|
|
1952
1952
|
const agent = await context.api.get(`/v1/agents/${agentId}`);
|
|
1953
|
-
const reasonRequired =
|
|
1953
|
+
const reasonRequired = path14 === "suspend";
|
|
1954
1954
|
const reason = valueFor(args, "--reason");
|
|
1955
1955
|
if (reasonRequired && (!reason || reason.trim().length < 10)) {
|
|
1956
1956
|
throw new CliError(
|
|
@@ -1960,24 +1960,24 @@ async function lifecycle(args, flags, path13, resultingStatus) {
|
|
|
1960
1960
|
}
|
|
1961
1961
|
out();
|
|
1962
1962
|
out(
|
|
1963
|
-
`${symbol.info} ${
|
|
1963
|
+
`${symbol.info} ${path14} ${style.bold(agent.name)} ${style.grey(`(${agent.status} \u2192 ${resultingStatus})`)}`
|
|
1964
1964
|
);
|
|
1965
|
-
if (
|
|
1965
|
+
if (path14 !== "resume") {
|
|
1966
1966
|
const confirmed = await confirm("Continue?", { yes: flags.yes });
|
|
1967
1967
|
if (!confirmed) {
|
|
1968
1968
|
out(style.grey("Cancelled."));
|
|
1969
1969
|
return 1;
|
|
1970
1970
|
}
|
|
1971
1971
|
}
|
|
1972
|
-
const actionSpinner = spinner(`Applying ${
|
|
1972
|
+
const actionSpinner = spinner(`Applying ${path14}`);
|
|
1973
1973
|
try {
|
|
1974
1974
|
await withStepUp(
|
|
1975
1975
|
context.api,
|
|
1976
|
-
() => context.api.post(`/v1/agents/${agentId}/${
|
|
1976
|
+
() => context.api.post(`/v1/agents/${agentId}/${path14}`, reason ? { reason } : {})
|
|
1977
1977
|
);
|
|
1978
1978
|
actionSpinner.succeed(`${agent.name} is ${resultingStatus}`);
|
|
1979
1979
|
} catch (error) {
|
|
1980
|
-
actionSpinner.fail(`Could not ${
|
|
1980
|
+
actionSpinner.fail(`Could not ${path14} the agent`);
|
|
1981
1981
|
throw error;
|
|
1982
1982
|
}
|
|
1983
1983
|
out();
|
|
@@ -2289,6 +2289,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2289
2289
|
import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync } from "node:fs";
|
|
2290
2290
|
import path3 from "node:path";
|
|
2291
2291
|
var MIN_APPROVAL_WAIT_SDK = "1.1.0";
|
|
2292
|
+
var MIN_CHECKPOINT_SDK = "1.2.0";
|
|
2292
2293
|
function versionLessThan(a, b) {
|
|
2293
2294
|
if (!a) return true;
|
|
2294
2295
|
const left = a.split(/[.+-]/).slice(0, 3).map(Number);
|
|
@@ -2335,7 +2336,8 @@ function installedSdks(root, options = {}) {
|
|
|
2335
2336
|
language: "js",
|
|
2336
2337
|
location: path3.relative(top, path3.dirname(manifest)) || ".",
|
|
2337
2338
|
version,
|
|
2338
|
-
waitsForApproval: !versionLessThan(version, MIN_APPROVAL_WAIT_SDK)
|
|
2339
|
+
waitsForApproval: !versionLessThan(version, MIN_APPROVAL_WAIT_SDK),
|
|
2340
|
+
hasCheckpoint: !versionLessThan(version, MIN_CHECKPOINT_SDK)
|
|
2339
2341
|
});
|
|
2340
2342
|
}
|
|
2341
2343
|
if (options.python) {
|
|
@@ -2352,7 +2354,8 @@ function installedSdks(root, options = {}) {
|
|
|
2352
2354
|
language: "py",
|
|
2353
2355
|
location: "python: veefore-py",
|
|
2354
2356
|
version,
|
|
2355
|
-
waitsForApproval: !versionLessThan(version, MIN_APPROVAL_WAIT_SDK)
|
|
2357
|
+
waitsForApproval: !versionLessThan(version, MIN_APPROVAL_WAIT_SDK),
|
|
2358
|
+
hasCheckpoint: !versionLessThan(version, MIN_CHECKPOINT_SDK)
|
|
2356
2359
|
});
|
|
2357
2360
|
}
|
|
2358
2361
|
}
|
|
@@ -3300,12 +3303,12 @@ function isLoosening(from, to) {
|
|
|
3300
3303
|
}
|
|
3301
3304
|
|
|
3302
3305
|
// src/commands/init.ts
|
|
3303
|
-
import { readFileSync as
|
|
3304
|
-
import
|
|
3306
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
3307
|
+
import path13 from "node:path";
|
|
3305
3308
|
|
|
3306
3309
|
// src/integrate.ts
|
|
3307
|
-
import { readFileSync as
|
|
3308
|
-
import
|
|
3310
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
3311
|
+
import path10 from "node:path";
|
|
3309
3312
|
|
|
3310
3313
|
// ../types/src/permissions.ts
|
|
3311
3314
|
var PERMISSIONS = [
|
|
@@ -4189,10 +4192,33 @@ function printDispatchInstructions(items, where) {
|
|
|
4189
4192
|
out(
|
|
4190
4193
|
` ${items.length} tool${items.length === 1 ? " is" : "s are"} defined as data, so no one function performs each tool.`
|
|
4191
4194
|
);
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4195
|
+
const keys = [...new Set(items.map((item) => item.key))];
|
|
4196
|
+
const sites = new Map(
|
|
4197
|
+
keys.flatMap((key) => {
|
|
4198
|
+
const site = where?.sites?.get(key);
|
|
4199
|
+
return site ? [[key, site]] : [];
|
|
4200
|
+
})
|
|
4201
|
+
);
|
|
4202
|
+
const places = new Set([...sites.values()].map((site) => `${site.file}:${site.line}`));
|
|
4203
|
+
const only = places.size === 1 && sites.size === keys.length ? [...sites.values()][0] : null;
|
|
4204
|
+
if (sites.size > 0 && !only) {
|
|
4205
|
+
out(" Wrap the call that performs each one, where your code runs it:");
|
|
4206
|
+
out();
|
|
4207
|
+
for (const key of keys) {
|
|
4208
|
+
const site = sites.get(key);
|
|
4209
|
+
out(
|
|
4210
|
+
` ${style.bold(key)} ${style.grey(site ? `${site.file}:${site.line}` : "where it is run; this could not be found")}`
|
|
4211
|
+
);
|
|
4212
|
+
if (site) out(` ${style.grey(site.code)}`);
|
|
4213
|
+
}
|
|
4214
|
+
out();
|
|
4215
|
+
out(" The change is the same shape in each place:");
|
|
4216
|
+
} else if (only || where) {
|
|
4217
|
+
const file = only?.file ?? where?.file;
|
|
4218
|
+
const line = only?.line ?? where?.line ?? 1;
|
|
4219
|
+
const named = where?.functionName && file === where.file ? ` (${where.functionName})` : "";
|
|
4220
|
+
out(` Your code runs them in ${style.bold(`${file}:${line}`)}${named}.`);
|
|
4221
|
+
if (only) out(` ${style.grey(only.code)}`);
|
|
4196
4222
|
out(" Wrap the call there that performs the chosen tool, once:");
|
|
4197
4223
|
} else {
|
|
4198
4224
|
out(" Wrap the code that runs whichever tool the model picked, once:");
|
|
@@ -4210,7 +4236,9 @@ function printDispatchInstructions(items, where) {
|
|
|
4210
4236
|
out(` ${style.cyan("import { guard } from '@veefore/sdk';")}`);
|
|
4211
4237
|
out();
|
|
4212
4238
|
out(` ${style.grey("// before: const result = await runTool(call);")}`);
|
|
4213
|
-
out(
|
|
4239
|
+
out(
|
|
4240
|
+
` ${style.cyan("const result = await guard(call.name, (_args) => runTool(call))(call.args);")}`
|
|
4241
|
+
);
|
|
4214
4242
|
}
|
|
4215
4243
|
out();
|
|
4216
4244
|
out(
|
|
@@ -4238,7 +4266,7 @@ function toCamel(key) {
|
|
|
4238
4266
|
}
|
|
4239
4267
|
function findGuardedKeys(sources) {
|
|
4240
4268
|
const found = /* @__PURE__ */ new Map();
|
|
4241
|
-
const pattern = /(?:\b|@)[A-Za-z_$]*[gG]uard\(\s*['"]([a-z][a-z0-9_]*)['"]/g;
|
|
4269
|
+
const pattern = /(?:\b|@)(?:[A-Za-z_$]*[gG]uard|[A-Za-z_$]*[cC]heckpoint)\(\s*['"]([a-z][a-z0-9_]*)['"]/g;
|
|
4242
4270
|
for (const { file, text } of sources) {
|
|
4243
4271
|
for (const match of text.matchAll(pattern)) {
|
|
4244
4272
|
const key = match[1];
|
|
@@ -4651,12 +4679,95 @@ function restoreBackup(root, backup, options = {}) {
|
|
|
4651
4679
|
}
|
|
4652
4680
|
|
|
4653
4681
|
// src/rewrite.ts
|
|
4654
|
-
import { readFileSync as
|
|
4655
|
-
import
|
|
4682
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
4683
|
+
import path9 from "node:path";
|
|
4656
4684
|
|
|
4657
4685
|
// src/scan.ts
|
|
4658
|
-
import { existsSync as existsSync6, readFileSync as
|
|
4659
|
-
import
|
|
4686
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
|
|
4687
|
+
import path8 from "node:path";
|
|
4688
|
+
|
|
4689
|
+
// src/anchors.ts
|
|
4690
|
+
var TOOL_ANCHORS = [
|
|
4691
|
+
// Schema-defined tools: findToolSchemas requires one of these keys.
|
|
4692
|
+
"parameters",
|
|
4693
|
+
"input_schema",
|
|
4694
|
+
"inputSchema",
|
|
4695
|
+
"parametersJsonSchema",
|
|
4696
|
+
// The OpenAI function shape, which may carry no description at all.
|
|
4697
|
+
"'function'",
|
|
4698
|
+
'"function"',
|
|
4699
|
+
"`function`",
|
|
4700
|
+
// Framework calls and decorators.
|
|
4701
|
+
"tool(",
|
|
4702
|
+
"Tool(",
|
|
4703
|
+
"tool (",
|
|
4704
|
+
"Tool (",
|
|
4705
|
+
"registerTool",
|
|
4706
|
+
// Python decorators, including the `@mcp.tool` / `@agent.tool` forms, which
|
|
4707
|
+
// carry neither `@tool` nor `tool(`.
|
|
4708
|
+
"@tool",
|
|
4709
|
+
".tool",
|
|
4710
|
+
"function_tool",
|
|
4711
|
+
"StructuredTool",
|
|
4712
|
+
"DynamicTool"
|
|
4713
|
+
];
|
|
4714
|
+
var AGENT_ANCHORS = [
|
|
4715
|
+
// Model SDKs and agent frameworks.
|
|
4716
|
+
"openai",
|
|
4717
|
+
"OpenAI",
|
|
4718
|
+
"@anthropic-ai/sdk",
|
|
4719
|
+
"anthropic",
|
|
4720
|
+
"Anthropic",
|
|
4721
|
+
"@google/genai",
|
|
4722
|
+
"@google/generative-ai",
|
|
4723
|
+
"google.generativeai",
|
|
4724
|
+
"google-generativeai",
|
|
4725
|
+
"langchain",
|
|
4726
|
+
"langgraph",
|
|
4727
|
+
"crewai",
|
|
4728
|
+
"llama_index",
|
|
4729
|
+
"llamaindex",
|
|
4730
|
+
"autogen",
|
|
4731
|
+
"litellm",
|
|
4732
|
+
"mistralai",
|
|
4733
|
+
"ollama",
|
|
4734
|
+
"bedrock-runtime",
|
|
4735
|
+
"bedrock_runtime",
|
|
4736
|
+
"@modelcontextprotocol",
|
|
4737
|
+
"mcp.server",
|
|
4738
|
+
// The vocabulary of a tool call, whatever produced it.
|
|
4739
|
+
"tool_calls",
|
|
4740
|
+
"toolCalls",
|
|
4741
|
+
"tool_call",
|
|
4742
|
+
"toolCall",
|
|
4743
|
+
"function_call",
|
|
4744
|
+
"functionCall",
|
|
4745
|
+
"tool_choice",
|
|
4746
|
+
"toolChoice",
|
|
4747
|
+
"toolUse",
|
|
4748
|
+
"tool_use"
|
|
4749
|
+
];
|
|
4750
|
+
var GUARD_ANCHORS = ["uard(", "uard (", "checkpoint("];
|
|
4751
|
+
var RISK_ANCHORS = [
|
|
4752
|
+
"://",
|
|
4753
|
+
"process.env",
|
|
4754
|
+
"import.meta.env",
|
|
4755
|
+
"os.environ",
|
|
4756
|
+
"getenv",
|
|
4757
|
+
"Deno.env"
|
|
4758
|
+
];
|
|
4759
|
+
function toolKeyAnchors(toolKeys) {
|
|
4760
|
+
const anchors = /* @__PURE__ */ new Set();
|
|
4761
|
+
for (const key of toolKeys) {
|
|
4762
|
+
anchors.add(key);
|
|
4763
|
+
const camel = key.replace(/_([a-z0-9])/g, (_match, char) => char.toUpperCase());
|
|
4764
|
+
if (camel !== key) anchors.add(camel);
|
|
4765
|
+
}
|
|
4766
|
+
return [...anchors];
|
|
4767
|
+
}
|
|
4768
|
+
var SCAN_ANCHORS = [
|
|
4769
|
+
.../* @__PURE__ */ new Set([...TOOL_ANCHORS, ...AGENT_ANCHORS, ...GUARD_ANCHORS, ...RISK_ANCHORS])
|
|
4770
|
+
];
|
|
4660
4771
|
|
|
4661
4772
|
// src/framework.ts
|
|
4662
4773
|
import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync2 } from "node:fs";
|
|
@@ -4852,19 +4963,11 @@ function integrationSnippet(framework) {
|
|
|
4852
4963
|
].join("\n");
|
|
4853
4964
|
}
|
|
4854
4965
|
|
|
4855
|
-
// src/
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
const currency = CURRENCY_PARAMETER.exec(definition)?.[1] ?? null;
|
|
4861
|
-
if (!amount && !currency) return null;
|
|
4862
|
-
return {
|
|
4863
|
-
amountParameter: amount,
|
|
4864
|
-
amountUnit: amount ? "MINOR" : null,
|
|
4865
|
-
currencyParameter: currency
|
|
4866
|
-
};
|
|
4867
|
-
}
|
|
4966
|
+
// src/search.ts
|
|
4967
|
+
import { execFileSync } from "node:child_process";
|
|
4968
|
+
import { mkdtempSync, readFileSync as readFileSync6, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4969
|
+
import { tmpdir } from "node:os";
|
|
4970
|
+
import path7 from "node:path";
|
|
4868
4971
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4869
4972
|
".ts",
|
|
4870
4973
|
".tsx",
|
|
@@ -4900,8 +5003,247 @@ var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
|
4900
5003
|
"tests",
|
|
4901
5004
|
"test"
|
|
4902
5005
|
]);
|
|
4903
|
-
var
|
|
4904
|
-
var
|
|
5006
|
+
var SKIP_FILES = /\.(test|spec)\.[cm]?[jt]sx?$|^test_.*\.py$|_test\.py$|\.d\.ts$|\.min\.[cm]?js$/;
|
|
5007
|
+
var DEFAULT_BUDGET_MS = 6e4;
|
|
5008
|
+
var MAX_PARSE_BYTES = 4 * 1024 * 1024;
|
|
5009
|
+
function indexFiles(root, options = {}) {
|
|
5010
|
+
const deadline2 = Date.now() + (options.budgetMs ?? DEFAULT_BUDGET_MS);
|
|
5011
|
+
const fromGit = gitFiles(root, deadline2);
|
|
5012
|
+
if (fromGit) {
|
|
5013
|
+
return {
|
|
5014
|
+
files: fromGit.tracked.filter(isCandidate),
|
|
5015
|
+
method: "git",
|
|
5016
|
+
truncated: false,
|
|
5017
|
+
unsearchable: fromGit.unsearchable.filter(isCandidate)
|
|
5018
|
+
};
|
|
5019
|
+
}
|
|
5020
|
+
const files = [];
|
|
5021
|
+
const truncated = walk(root, root, files, deadline2);
|
|
5022
|
+
return { files, method: "walk", truncated, unsearchable: [] };
|
|
5023
|
+
}
|
|
5024
|
+
function gitFiles(root, deadline2) {
|
|
5025
|
+
const output = git(
|
|
5026
|
+
root,
|
|
5027
|
+
["ls-files", "-z", "--cached", "--others", "--exclude-standard"],
|
|
5028
|
+
deadline2
|
|
5029
|
+
);
|
|
5030
|
+
if (output === null) return null;
|
|
5031
|
+
const tracked = new Set(output.split("\0").filter((entry) => entry !== ""));
|
|
5032
|
+
const unsearchable = /* @__PURE__ */ new Set();
|
|
5033
|
+
const submodules = git(root, ["ls-files", "-z", "--cached", "--recurse-submodules"], deadline2);
|
|
5034
|
+
if (submodules !== null) {
|
|
5035
|
+
for (const entry of submodules.split("\0")) {
|
|
5036
|
+
if (entry !== "" && !tracked.has(entry)) unsearchable.add(entry);
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5039
|
+
for (const file of ignoredSources(root, deadline2)) {
|
|
5040
|
+
if (!tracked.has(file)) unsearchable.add(file);
|
|
5041
|
+
}
|
|
5042
|
+
return { tracked: [...tracked, ...unsearchable], unsearchable: [...unsearchable] };
|
|
5043
|
+
}
|
|
5044
|
+
function ignoredSources(root, deadline2) {
|
|
5045
|
+
const output = git(
|
|
5046
|
+
root,
|
|
5047
|
+
["ls-files", "-z", "--others", "--ignored", "--exclude-standard", "--directory"],
|
|
5048
|
+
deadline2
|
|
5049
|
+
);
|
|
5050
|
+
if (output === null) return [];
|
|
5051
|
+
const files = [];
|
|
5052
|
+
for (const entry of output.split("\0")) {
|
|
5053
|
+
if (entry === "") continue;
|
|
5054
|
+
if (entry.endsWith("/")) {
|
|
5055
|
+
const name = entry.replace(/\/$/, "").split("/").pop() ?? "";
|
|
5056
|
+
if (SKIP_DIRECTORIES.has(name) || name.startsWith(".")) continue;
|
|
5057
|
+
const nested = [];
|
|
5058
|
+
walk(root, path7.join(root, entry), nested, deadline2);
|
|
5059
|
+
files.push(...nested);
|
|
5060
|
+
continue;
|
|
5061
|
+
}
|
|
5062
|
+
if (isCandidate(entry)) files.push(entry);
|
|
5063
|
+
}
|
|
5064
|
+
return files;
|
|
5065
|
+
}
|
|
5066
|
+
function git(root, args, deadline2) {
|
|
5067
|
+
const timeout = deadline2 - Date.now();
|
|
5068
|
+
if (timeout <= 0) return null;
|
|
5069
|
+
try {
|
|
5070
|
+
return execFileSync("git", ["-c", "core.quotepath=off", ...args], {
|
|
5071
|
+
cwd: root,
|
|
5072
|
+
encoding: "utf8",
|
|
5073
|
+
timeout,
|
|
5074
|
+
maxBuffer: 512 * 1024 * 1024,
|
|
5075
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5076
|
+
});
|
|
5077
|
+
} catch {
|
|
5078
|
+
return null;
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
function isCandidate(file) {
|
|
5082
|
+
if (!SOURCE_EXTENSIONS.has(path7.extname(file))) return false;
|
|
5083
|
+
const segments = file.split(/[/\\]/);
|
|
5084
|
+
const name = segments[segments.length - 1] ?? "";
|
|
5085
|
+
if (SKIP_FILES.test(name)) return false;
|
|
5086
|
+
return !segments.slice(0, -1).some((segment) => SKIP_DIRECTORIES.has(segment) || segment.startsWith("."));
|
|
5087
|
+
}
|
|
5088
|
+
function walk(root, dir, out2, deadline2) {
|
|
5089
|
+
let entries;
|
|
5090
|
+
try {
|
|
5091
|
+
entries = readdirSyncSafe(dir);
|
|
5092
|
+
} catch {
|
|
5093
|
+
return false;
|
|
5094
|
+
}
|
|
5095
|
+
for (const entry of entries) {
|
|
5096
|
+
if (Date.now() > deadline2) return true;
|
|
5097
|
+
if (entry.startsWith(".")) continue;
|
|
5098
|
+
const full = path7.join(dir, entry);
|
|
5099
|
+
let directory;
|
|
5100
|
+
try {
|
|
5101
|
+
directory = statSync4(full).isDirectory();
|
|
5102
|
+
} catch {
|
|
5103
|
+
continue;
|
|
5104
|
+
}
|
|
5105
|
+
if (directory) {
|
|
5106
|
+
if (SKIP_DIRECTORIES.has(entry)) continue;
|
|
5107
|
+
if (walk(root, full, out2, deadline2)) return true;
|
|
5108
|
+
continue;
|
|
5109
|
+
}
|
|
5110
|
+
const relative = path7.relative(root, full);
|
|
5111
|
+
if (isCandidate(relative)) out2.push(relative);
|
|
5112
|
+
}
|
|
5113
|
+
return false;
|
|
5114
|
+
}
|
|
5115
|
+
function readdirSyncSafe(dir) {
|
|
5116
|
+
return readdirSync3(dir).sort();
|
|
5117
|
+
}
|
|
5118
|
+
function shortlist(root, index, anchors, options = {}) {
|
|
5119
|
+
const considered = index.files.length;
|
|
5120
|
+
if (anchors.length === 0 || considered === 0) {
|
|
5121
|
+
return { files: [], method: "in-process", considered, truncated: false };
|
|
5122
|
+
}
|
|
5123
|
+
const deadline2 = Date.now() + (options.budgetMs ?? DEFAULT_BUDGET_MS);
|
|
5124
|
+
const allowed = new Set(index.files);
|
|
5125
|
+
const reached = index.unsearchable.filter((file) => allowed.has(file));
|
|
5126
|
+
const extra = reached.length > 0 ? inProcessSearch(root, reached, anchors, deadline2, reached.length) : null;
|
|
5127
|
+
const combine = (files, method) => ({
|
|
5128
|
+
files: [.../* @__PURE__ */ new Set([...files, ...extra?.files ?? []])],
|
|
5129
|
+
method,
|
|
5130
|
+
considered,
|
|
5131
|
+
truncated: extra?.truncated ?? false
|
|
5132
|
+
});
|
|
5133
|
+
if (index.method === "git") {
|
|
5134
|
+
const matched = searchWith(
|
|
5135
|
+
"git",
|
|
5136
|
+
root,
|
|
5137
|
+
["-c", "core.quotepath=off", "grep", "--no-color", "-l", "-I", "-z", "-F", "--untracked"],
|
|
5138
|
+
anchors,
|
|
5139
|
+
deadline2
|
|
5140
|
+
);
|
|
5141
|
+
if (matched) {
|
|
5142
|
+
return combine(
|
|
5143
|
+
matched.filter((file) => allowed.has(file)),
|
|
5144
|
+
"git-grep"
|
|
5145
|
+
);
|
|
5146
|
+
}
|
|
5147
|
+
}
|
|
5148
|
+
const ripgrep = searchWith(
|
|
5149
|
+
"rg",
|
|
5150
|
+
root,
|
|
5151
|
+
["--files-with-matches", "--fixed-strings", "--null", "--no-messages"],
|
|
5152
|
+
anchors,
|
|
5153
|
+
deadline2
|
|
5154
|
+
);
|
|
5155
|
+
if (ripgrep) {
|
|
5156
|
+
return combine(
|
|
5157
|
+
ripgrep.map(normalise).filter((file) => allowed.has(file)),
|
|
5158
|
+
"ripgrep"
|
|
5159
|
+
);
|
|
5160
|
+
}
|
|
5161
|
+
return inProcessSearch(root, index.files, anchors, deadline2, considered);
|
|
5162
|
+
}
|
|
5163
|
+
function searchWith(command, root, args, anchors, deadline2) {
|
|
5164
|
+
const timeout = deadline2 - Date.now();
|
|
5165
|
+
if (timeout <= 0) return null;
|
|
5166
|
+
let directory = null;
|
|
5167
|
+
try {
|
|
5168
|
+
directory = mkdtempSync(path7.join(tmpdir(), "veefore-search-"));
|
|
5169
|
+
const patterns = path7.join(directory, "anchors.txt");
|
|
5170
|
+
writeFileSync4(patterns, `${anchors.join("\n")}
|
|
5171
|
+
`);
|
|
5172
|
+
const output = execFileSync(command, [...args, "-f", patterns], {
|
|
5173
|
+
cwd: root,
|
|
5174
|
+
encoding: "utf8",
|
|
5175
|
+
timeout,
|
|
5176
|
+
maxBuffer: 512 * 1024 * 1024,
|
|
5177
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5178
|
+
});
|
|
5179
|
+
return output.split("\0").filter((entry) => entry !== "");
|
|
5180
|
+
} catch (error) {
|
|
5181
|
+
const status = error.status;
|
|
5182
|
+
if (status === 1) return [];
|
|
5183
|
+
return null;
|
|
5184
|
+
} finally {
|
|
5185
|
+
if (directory) rmSync3(directory, { recursive: true, force: true });
|
|
5186
|
+
}
|
|
5187
|
+
}
|
|
5188
|
+
function inProcessSearch(root, files, anchors, deadline2, considered) {
|
|
5189
|
+
const needles = anchors.map((anchor) => Buffer.from(anchor, "utf8"));
|
|
5190
|
+
const matched = [];
|
|
5191
|
+
let truncated = false;
|
|
5192
|
+
for (const [position, file] of files.entries()) {
|
|
5193
|
+
if ((position & 63) === 0 && Date.now() > deadline2) {
|
|
5194
|
+
truncated = true;
|
|
5195
|
+
break;
|
|
5196
|
+
}
|
|
5197
|
+
let buffer;
|
|
5198
|
+
try {
|
|
5199
|
+
buffer = readFileSync6(path7.join(root, file));
|
|
5200
|
+
} catch {
|
|
5201
|
+
continue;
|
|
5202
|
+
}
|
|
5203
|
+
const nul = buffer.indexOf(0);
|
|
5204
|
+
if (nul >= 0 && nul < 8192) continue;
|
|
5205
|
+
if (needles.some((needle) => buffer.includes(needle))) matched.push(file);
|
|
5206
|
+
}
|
|
5207
|
+
return { files: matched, method: "in-process", considered, truncated };
|
|
5208
|
+
}
|
|
5209
|
+
function normalise(file) {
|
|
5210
|
+
return file.startsWith(`.${path7.sep}`) || file.startsWith("./") ? file.slice(2) : file;
|
|
5211
|
+
}
|
|
5212
|
+
function tooBigToParse(text) {
|
|
5213
|
+
if (text.length > MAX_PARSE_BYTES) return true;
|
|
5214
|
+
if (text.length < 128 * 1024) return false;
|
|
5215
|
+
const head = text.slice(0, 128 * 1024);
|
|
5216
|
+
const lines = head.split("\n").length;
|
|
5217
|
+
return head.length / lines > 500;
|
|
5218
|
+
}
|
|
5219
|
+
function readFiles(root, files) {
|
|
5220
|
+
const out2 = [];
|
|
5221
|
+
for (const file of files) {
|
|
5222
|
+
let text;
|
|
5223
|
+
try {
|
|
5224
|
+
text = readFileSync6(path7.join(root, file), "utf8");
|
|
5225
|
+
} catch {
|
|
5226
|
+
continue;
|
|
5227
|
+
}
|
|
5228
|
+
if (tooBigToParse(text)) continue;
|
|
5229
|
+
out2.push({ file, text });
|
|
5230
|
+
}
|
|
5231
|
+
return out2;
|
|
5232
|
+
}
|
|
5233
|
+
|
|
5234
|
+
// src/scan.ts
|
|
5235
|
+
var MINOR_AMOUNT_PARAMETER = /["']?\b(amount_minor|amountMinor|amount_cents|amountCents|amount_in_cents|amountInCents|amount_minor_units|amountMinorUnits)\b["']?\s*[:=(]/;
|
|
5236
|
+
var CURRENCY_PARAMETER = /["']?\b(currency_code|currencyCode|currency)\b["']?\s*[:=(]/;
|
|
5237
|
+
function inferMoneyMapping(definition) {
|
|
5238
|
+
const amount = MINOR_AMOUNT_PARAMETER.exec(definition)?.[1] ?? null;
|
|
5239
|
+
const currency = CURRENCY_PARAMETER.exec(definition)?.[1] ?? null;
|
|
5240
|
+
if (!amount && !currency) return null;
|
|
5241
|
+
return {
|
|
5242
|
+
amountParameter: amount,
|
|
5243
|
+
amountUnit: amount ? "MINOR" : null,
|
|
5244
|
+
currencyParameter: currency
|
|
5245
|
+
};
|
|
5246
|
+
}
|
|
4905
5247
|
var MODEL_HOSTS = [
|
|
4906
5248
|
"api.openai.com",
|
|
4907
5249
|
"api.anthropic.com",
|
|
@@ -4918,24 +5260,74 @@ var MODEL_SECRET = /^(OPENAI|ANTHROPIC|GOOGLE|GEMINI|AZURE_OPENAI|MISTRAL|GROQ|C
|
|
|
4918
5260
|
var SECRET_NAME = /(KEY|TOKEN|SECRET|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|AUTH|DSN)(_|$)|^DATABASE_URL$|_CONNECTION_STRING$/;
|
|
4919
5261
|
var NOT_SECRET = /^(VEEFORE_|NODE_|NEXT_PUBLIC_|VITE_|PUBLIC_|NPM_)/;
|
|
4920
5262
|
function listSourceFiles(root = process.cwd()) {
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
5263
|
+
return listSources(root).files;
|
|
5264
|
+
}
|
|
5265
|
+
function listSources(root = process.cwd()) {
|
|
5266
|
+
const index = indexFiles(root);
|
|
5267
|
+
return { files: index.files, truncated: index.truncated, method: index.method };
|
|
5268
|
+
}
|
|
5269
|
+
function discoverAgents(root = process.cwd()) {
|
|
5270
|
+
const scan = scanProject(root);
|
|
5271
|
+
const index = indexFiles(root);
|
|
5272
|
+
const byDirectory = /* @__PURE__ */ new Map();
|
|
5273
|
+
for (const tool of scan.tools) {
|
|
5274
|
+
const directory = nearestManifest(root, tool.location.file);
|
|
5275
|
+
const existing = byDirectory.get(directory);
|
|
5276
|
+
const agent = existing ?? {
|
|
5277
|
+
directory,
|
|
5278
|
+
name: readProjectName(path8.join(root, directory)),
|
|
5279
|
+
files: [],
|
|
5280
|
+
tools: []
|
|
5281
|
+
};
|
|
5282
|
+
if (!agent.files.includes(tool.location.file)) agent.files.push(tool.location.file);
|
|
5283
|
+
agent.tools.push(tool);
|
|
5284
|
+
byDirectory.set(directory, agent);
|
|
5285
|
+
}
|
|
5286
|
+
const known = new Set(scan.tools.map((tool) => tool.location.file));
|
|
5287
|
+
const agentFiles = shortlist(root, index, [...AGENT_ANCHORS]);
|
|
5288
|
+
const unreadable = [];
|
|
5289
|
+
for (const { file, text } of readFiles(root, agentFiles.files)) {
|
|
5290
|
+
if (known.has(file)) continue;
|
|
5291
|
+
const python = file.endsWith(".py");
|
|
5292
|
+
if ((python ? findPythonTools(text) : findJsTools(text)).length > 0) continue;
|
|
5293
|
+
const hint = /\btools?\s*[:=]|\btool_calls\b|\btoolCalls\b|\bfunction_call\b/.exec(text);
|
|
5294
|
+
if (!hint) continue;
|
|
5295
|
+
unreadable.push({
|
|
5296
|
+
file,
|
|
5297
|
+
line: lineLocator(text)(hint.index),
|
|
5298
|
+
reason: `passes tools to a model, but none could be read from this file`
|
|
5299
|
+
});
|
|
5300
|
+
}
|
|
5301
|
+
return {
|
|
5302
|
+
agents: [...byDirectory.values()].sort((a, b) => a.directory.localeCompare(b.directory)),
|
|
5303
|
+
unreadable,
|
|
5304
|
+
filesScanned: scan.filesScanned,
|
|
5305
|
+
truncated: scan.truncated || agentFiles.truncated
|
|
5306
|
+
};
|
|
5307
|
+
}
|
|
5308
|
+
function nearestManifest(root, file) {
|
|
5309
|
+
const MANIFESTS = ["package.json", "pyproject.toml", "go.mod", "Cargo.toml", "setup.py"];
|
|
5310
|
+
let dir = path8.dirname(file);
|
|
5311
|
+
for (let depth = 0; depth < 32; depth += 1) {
|
|
5312
|
+
const absolute = path8.join(root, dir);
|
|
5313
|
+
if (MANIFESTS.some((manifest) => existsSync6(path8.join(absolute, manifest)))) {
|
|
5314
|
+
return dir === "" ? "." : dir;
|
|
5315
|
+
}
|
|
5316
|
+
const parent = path8.dirname(dir);
|
|
5317
|
+
if (parent === dir || dir === "." || dir === "") break;
|
|
5318
|
+
dir = parent;
|
|
5319
|
+
}
|
|
5320
|
+
return ".";
|
|
4924
5321
|
}
|
|
4925
5322
|
function scanProject(root = process.cwd()) {
|
|
4926
5323
|
const framework = detectFramework(root);
|
|
4927
|
-
const
|
|
4928
|
-
const
|
|
5324
|
+
const index = indexFiles(root);
|
|
5325
|
+
const candidates = shortlist(root, index, [...SCAN_ANCHORS]);
|
|
5326
|
+
const truncated = index.truncated || candidates.truncated;
|
|
4929
5327
|
const tools = [];
|
|
4930
5328
|
const hosts = /* @__PURE__ */ new Map();
|
|
4931
5329
|
const secrets = /* @__PURE__ */ new Map();
|
|
4932
|
-
for (const relative of files) {
|
|
4933
|
-
let text;
|
|
4934
|
-
try {
|
|
4935
|
-
text = readFileSync6(path7.join(root, relative), "utf8");
|
|
4936
|
-
} catch {
|
|
4937
|
-
continue;
|
|
4938
|
-
}
|
|
5330
|
+
for (const { file: relative, text } of readFiles(root, candidates.files)) {
|
|
4939
5331
|
const python = relative.endsWith(".py");
|
|
4940
5332
|
const lineOf = lineLocator(text);
|
|
4941
5333
|
const fileHosts = findHosts(text).map((hit) => ({ ...hit, line: lineOf(hit.index) }));
|
|
@@ -4951,7 +5343,7 @@ function scanProject(root = process.cwd()) {
|
|
|
4951
5343
|
found.forEach((hit, position) => {
|
|
4952
5344
|
const end = found[position + 1]?.index ?? text.length;
|
|
4953
5345
|
const start = hit.index;
|
|
4954
|
-
const inRegion = (
|
|
5346
|
+
const inRegion = (index2) => index2 >= start && index2 < end;
|
|
4955
5347
|
const ownHosts = unique(fileHosts.filter((h) => inRegion(h.index)).map((h) => h.host));
|
|
4956
5348
|
const ownSecrets = unique(
|
|
4957
5349
|
fileSecrets.filter((s) => inRegion(s.index) && classifySecret(s.name) === "service").map((s) => s.name)
|
|
@@ -4979,7 +5371,9 @@ function scanProject(root = process.cwd()) {
|
|
|
4979
5371
|
projectName: readProjectName(root),
|
|
4980
5372
|
framework,
|
|
4981
5373
|
entryPoints: findEntryPoints(root),
|
|
4982
|
-
filesScanned: files.length,
|
|
5374
|
+
filesScanned: index.files.length,
|
|
5375
|
+
filesParsed: candidates.files.length,
|
|
5376
|
+
searchMethod: candidates.method,
|
|
4983
5377
|
truncated,
|
|
4984
5378
|
tools: dedupeTools(tools),
|
|
4985
5379
|
hosts: [...hosts.entries()].map(([host, locations]) => ({ host, locations })).sort((a, b) => a.host.localeCompare(b.host)),
|
|
@@ -5053,18 +5447,8 @@ function findJsTools(text) {
|
|
|
5053
5447
|
}
|
|
5054
5448
|
}
|
|
5055
5449
|
}
|
|
5056
|
-
for (const
|
|
5057
|
-
|
|
5058
|
-
const body = text.slice(index, index + 400);
|
|
5059
|
-
const name = stringField(body, "name");
|
|
5060
|
-
if (name) {
|
|
5061
|
-
hits.push({
|
|
5062
|
-
name,
|
|
5063
|
-
source: "openai-function",
|
|
5064
|
-
index,
|
|
5065
|
-
description: stringField(body, "description")
|
|
5066
|
-
});
|
|
5067
|
-
}
|
|
5450
|
+
for (const hit of findFunctionToolSchemas(text)) {
|
|
5451
|
+
hits.push({ ...hit, source: "openai-function" });
|
|
5068
5452
|
}
|
|
5069
5453
|
addSchemaTools(hits, text, false);
|
|
5070
5454
|
if (mcp) {
|
|
@@ -5128,6 +5512,71 @@ function findToolSchemas(text, python) {
|
|
|
5128
5512
|
}
|
|
5129
5513
|
return hits;
|
|
5130
5514
|
}
|
|
5515
|
+
function findFunctionToolSchemas(text) {
|
|
5516
|
+
const hits = [];
|
|
5517
|
+
const NAME = /\bname\s*:\s*(['"`])([\w.-]{1,80})\1/;
|
|
5518
|
+
for (const match of text.matchAll(/\btype\s*:\s*(['"`])function\1/g)) {
|
|
5519
|
+
const at = match.index ?? 0;
|
|
5520
|
+
const from = at + match[0].length;
|
|
5521
|
+
const sibling = /^[^{}]{0,400}?(?<![\w$.])function\s*:\s*\{/.exec(text.slice(from));
|
|
5522
|
+
if (sibling) {
|
|
5523
|
+
const open = from + sibling[0].length - 1;
|
|
5524
|
+
const close = matchingBrace(text, open);
|
|
5525
|
+
if (close > open) {
|
|
5526
|
+
const inner = text.slice(open, close + 1);
|
|
5527
|
+
const found = NAME.exec(inner);
|
|
5528
|
+
if (found) {
|
|
5529
|
+
hits.push({
|
|
5530
|
+
name: found[2],
|
|
5531
|
+
index: open + found.index,
|
|
5532
|
+
description: stringField(inner, "description")
|
|
5533
|
+
});
|
|
5534
|
+
continue;
|
|
5535
|
+
}
|
|
5536
|
+
}
|
|
5537
|
+
}
|
|
5538
|
+
const opener = text.lastIndexOf("{", at);
|
|
5539
|
+
const windows = [
|
|
5540
|
+
[
|
|
5541
|
+
Math.max(opener + 1, at - 400),
|
|
5542
|
+
opener < 0 ? "" : text.slice(Math.max(opener + 1, at - 400), at)
|
|
5543
|
+
],
|
|
5544
|
+
[from, text.slice(from, from + 400).split(/[{}]/)[0] ?? ""]
|
|
5545
|
+
];
|
|
5546
|
+
for (const [offset, window] of windows) {
|
|
5547
|
+
const found = NAME.exec(window);
|
|
5548
|
+
if (!found) continue;
|
|
5549
|
+
hits.push({
|
|
5550
|
+
name: found[2],
|
|
5551
|
+
index: offset + found.index,
|
|
5552
|
+
description: stringField(window, "description")
|
|
5553
|
+
});
|
|
5554
|
+
break;
|
|
5555
|
+
}
|
|
5556
|
+
}
|
|
5557
|
+
return hits;
|
|
5558
|
+
}
|
|
5559
|
+
function matchingBrace(text, open) {
|
|
5560
|
+
let depth = 0;
|
|
5561
|
+
for (let index = open; index < text.length; index += 1) {
|
|
5562
|
+
const char = text[index];
|
|
5563
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
5564
|
+
const quote2 = char;
|
|
5565
|
+
index += 1;
|
|
5566
|
+
while (index < text.length && text[index] !== quote2) {
|
|
5567
|
+
if (text[index] === "\\") index += 1;
|
|
5568
|
+
index += 1;
|
|
5569
|
+
}
|
|
5570
|
+
continue;
|
|
5571
|
+
}
|
|
5572
|
+
if (char === "{") depth += 1;
|
|
5573
|
+
else if (char === "}") {
|
|
5574
|
+
depth -= 1;
|
|
5575
|
+
if (depth === 0) return index;
|
|
5576
|
+
}
|
|
5577
|
+
}
|
|
5578
|
+
return -1;
|
|
5579
|
+
}
|
|
5131
5580
|
function addSchemaTools(hits, text, python) {
|
|
5132
5581
|
const known = new Set(hits.map((hit) => toToolKey(hit.name)));
|
|
5133
5582
|
for (const hit of findToolSchemas(text, python)) {
|
|
@@ -5419,52 +5868,21 @@ function toToolKey(name) {
|
|
|
5419
5868
|
const safe = /^[a-z]/.test(key) ? key : `tool_${key}`;
|
|
5420
5869
|
return safe.slice(0, 80) || "tool";
|
|
5421
5870
|
}
|
|
5422
|
-
function walk(root, dir, out2) {
|
|
5423
|
-
let entries;
|
|
5424
|
-
try {
|
|
5425
|
-
entries = readdirSync3(dir).sort();
|
|
5426
|
-
} catch {
|
|
5427
|
-
return false;
|
|
5428
|
-
}
|
|
5429
|
-
for (const entry of entries) {
|
|
5430
|
-
if (out2.length >= MAX_FILES) return true;
|
|
5431
|
-
if (entry.startsWith(".") && entry !== ".") {
|
|
5432
|
-
continue;
|
|
5433
|
-
}
|
|
5434
|
-
const full = path7.join(dir, entry);
|
|
5435
|
-
let stats;
|
|
5436
|
-
try {
|
|
5437
|
-
stats = statSync4(full);
|
|
5438
|
-
} catch {
|
|
5439
|
-
continue;
|
|
5440
|
-
}
|
|
5441
|
-
if (stats.isDirectory()) {
|
|
5442
|
-
if (SKIP_DIRECTORIES.has(entry)) continue;
|
|
5443
|
-
if (walk(root, full, out2)) return true;
|
|
5444
|
-
continue;
|
|
5445
|
-
}
|
|
5446
|
-
if (!stats.isFile() || stats.size > MAX_FILE_BYTES) continue;
|
|
5447
|
-
if (!SOURCE_EXTENSIONS.has(path7.extname(entry))) continue;
|
|
5448
|
-
if (/\.(test|spec)\.[cm]?[jt]sx?$|^test_.*\.py$|_test\.py$|\.d\.ts$/.test(entry)) continue;
|
|
5449
|
-
out2.push(path7.relative(root, full));
|
|
5450
|
-
}
|
|
5451
|
-
return false;
|
|
5452
|
-
}
|
|
5453
5871
|
function readProjectName(root) {
|
|
5454
|
-
const pkg =
|
|
5872
|
+
const pkg = path8.join(root, "package.json");
|
|
5455
5873
|
if (existsSync6(pkg)) {
|
|
5456
5874
|
try {
|
|
5457
|
-
const name = JSON.parse(
|
|
5875
|
+
const name = JSON.parse(readFileSync7(pkg, "utf8")).name;
|
|
5458
5876
|
if (typeof name === "string" && name.trim()) return name.replace(/^@[^/]+\//, "").trim();
|
|
5459
5877
|
} catch {
|
|
5460
5878
|
}
|
|
5461
5879
|
}
|
|
5462
|
-
const pyproject =
|
|
5880
|
+
const pyproject = path8.join(root, "pyproject.toml");
|
|
5463
5881
|
if (existsSync6(pyproject)) {
|
|
5464
|
-
const name = /^\s*name\s*=\s*["']([^"']+)["']/m.exec(
|
|
5882
|
+
const name = /^\s*name\s*=\s*["']([^"']+)["']/m.exec(readFileSync7(pyproject, "utf8"))?.[1];
|
|
5465
5883
|
if (name) return name.trim();
|
|
5466
5884
|
}
|
|
5467
|
-
return
|
|
5885
|
+
return path8.basename(root) || null;
|
|
5468
5886
|
}
|
|
5469
5887
|
function stringField(body, field) {
|
|
5470
5888
|
const match = new RegExp(`\\b${field}\\s*:\\s*(['"\`])([^]{1,300}?)\\1`).exec(body);
|
|
@@ -5519,7 +5937,7 @@ function planRewrite(root, files, toolKeys) {
|
|
|
5519
5937
|
for (const file of files) {
|
|
5520
5938
|
let before;
|
|
5521
5939
|
try {
|
|
5522
|
-
before =
|
|
5940
|
+
before = readFileSync8(path9.join(root, file), "utf8");
|
|
5523
5941
|
} catch {
|
|
5524
5942
|
continue;
|
|
5525
5943
|
}
|
|
@@ -5624,27 +6042,20 @@ function rewriteJs(source, wanted) {
|
|
|
5624
6042
|
wrap3(key, match.index ?? 0, start, trimEnd(source, start, handler.end));
|
|
5625
6043
|
}
|
|
5626
6044
|
}
|
|
5627
|
-
|
|
5628
|
-
const name = /\bname\s*:\s*['"]([\w.-]{1,80})['"]/.exec(
|
|
5629
|
-
source.slice(match.index ?? 0, (match.index ?? 0) + 400)
|
|
5630
|
-
)?.[1];
|
|
5631
|
-
if (!name || !wanted.has(toToolKey(name))) continue;
|
|
5632
|
-
changes.push({
|
|
5633
|
-
key: toToolKey(name),
|
|
5634
|
-
line: lineOf(match.index ?? 0),
|
|
5635
|
-
status: "manual",
|
|
5636
|
-
reason: `this is only the schema; wrap the function your code calls for it, e.g. \`guard(${quote(toToolKey(name))}, yourFunction)\``
|
|
5637
|
-
});
|
|
5638
|
-
}
|
|
5639
|
-
changes.push(...schemaOnly(source, false, wanted));
|
|
6045
|
+
changes.push(...schemaOnly(source, false, wanted, new Set(changes.map((c) => c.key))));
|
|
5640
6046
|
const wrapped = edits.length > 0;
|
|
5641
6047
|
let after = applyEdits(source, edits);
|
|
5642
6048
|
if (wrapped) after = addJsImport(after, alias);
|
|
5643
6049
|
return { after, changes: dedupeChanges(changes) };
|
|
5644
6050
|
}
|
|
5645
6051
|
function addJsImport(source, alias) {
|
|
5646
|
-
|
|
5647
|
-
|
|
6052
|
+
return addJsSdkImport(source, "guard", alias);
|
|
6053
|
+
}
|
|
6054
|
+
function addJsSdkImport(source, name, alias) {
|
|
6055
|
+
if (existingImport(source, name, "js")) return source;
|
|
6056
|
+
const specifier = alias === name ? name : `${name} as ${alias}`;
|
|
6057
|
+
const joined = joinExistingSdkImport(source, specifier, alias, name);
|
|
6058
|
+
if (joined) return joined;
|
|
5648
6059
|
const imports = [
|
|
5649
6060
|
...source.matchAll(
|
|
5650
6061
|
/^import\s+(?:type\s+)?(?:[\s\S]*?\sfrom\s*)?['"][^'"\n]+['"]\s*;?[^\S\n]*$/gm
|
|
@@ -5657,11 +6068,33 @@ function addJsImport(source, alias) {
|
|
|
5657
6068
|
import { ${specifier} } from '@veefore/sdk';${source.slice(at)}`;
|
|
5658
6069
|
}
|
|
5659
6070
|
const cjs = /\brequire\(\s*['"][^'"]+['"]\s*\)/.test(source) && !/^\s*export\s/m.test(source);
|
|
5660
|
-
const line = cjs ? `const { ${alias ===
|
|
6071
|
+
const line = cjs ? `const { ${alias === name ? name : `${name}: ${alias}`} } = require('@veefore/sdk');` : `import { ${specifier} } from '@veefore/sdk';`;
|
|
5661
6072
|
const prologue = /^(?:#![^\n]*\n)?(?:\s*(['"])use [a-z ]+\1;?[^\S\n]*\n)*/.exec(source)?.[0] ?? "";
|
|
5662
6073
|
return `${source.slice(0, prologue.length)}${line}
|
|
5663
6074
|
${source.slice(prologue.length)}`;
|
|
5664
6075
|
}
|
|
6076
|
+
function joinExistingSdkImport(source, specifier, alias, name) {
|
|
6077
|
+
const esm = /import\s*\{([^}]*)\}\s*from\s*(['"])@veefore\/sdk\2/.exec(source);
|
|
6078
|
+
if (esm) {
|
|
6079
|
+
const names = (esm[1] ?? "").trim();
|
|
6080
|
+
const replacement = esm[0].replace(
|
|
6081
|
+
`{${esm[1] ?? ""}}`,
|
|
6082
|
+
names === "" ? `{ ${specifier} }` : `{ ${names}, ${specifier} }`
|
|
6083
|
+
);
|
|
6084
|
+
return source.replace(esm[0], replacement);
|
|
6085
|
+
}
|
|
6086
|
+
const cjs = /const\s*\{([^}]*)\}\s*=\s*require\(\s*(['"])@veefore\/sdk\2\s*\)/.exec(source);
|
|
6087
|
+
if (cjs) {
|
|
6088
|
+
const names = (cjs[1] ?? "").trim();
|
|
6089
|
+
const entry = alias === name ? name : `${name}: ${alias}`;
|
|
6090
|
+
const replacement = cjs[0].replace(
|
|
6091
|
+
`{${cjs[1] ?? ""}}`,
|
|
6092
|
+
names === "" ? `{ ${entry} }` : `{ ${names}, ${entry} }`
|
|
6093
|
+
);
|
|
6094
|
+
return source.replace(cjs[0], replacement);
|
|
6095
|
+
}
|
|
6096
|
+
return null;
|
|
6097
|
+
}
|
|
5665
6098
|
function rewritePython(source, wanted) {
|
|
5666
6099
|
const changes = [];
|
|
5667
6100
|
const lines = source.split("\n");
|
|
@@ -5704,7 +6137,7 @@ function rewritePython(source, wanted) {
|
|
|
5704
6137
|
reason: `decorate the function passed as func= with @guard(${JSON.stringify(toToolKey(name))})`
|
|
5705
6138
|
});
|
|
5706
6139
|
}
|
|
5707
|
-
changes.push(...schemaOnly(source, true, wanted));
|
|
6140
|
+
changes.push(...schemaOnly(source, true, wanted, new Set(changes.map((c) => c.key))));
|
|
5708
6141
|
if (inserts.length === 0) return { after: source, changes: dedupeChanges(changes) };
|
|
5709
6142
|
const out2 = [...lines];
|
|
5710
6143
|
for (const insert of [...inserts].sort((a, b) => b.before - a.before)) {
|
|
@@ -5914,9 +6347,63 @@ function topLevelString(text, open, close, name) {
|
|
|
5914
6347
|
const literal = /^(['"`])([\w .-]{1,80})\1$/.exec(text.slice(value.start, value.end));
|
|
5915
6348
|
return literal?.[2] ?? null;
|
|
5916
6349
|
}
|
|
5917
|
-
var DISPATCH_NAME = /^(?:execute|run|call|dispatch|handle|invoke|perform)_?(?:tool|tool_?call|function_?call)s?$/i;
|
|
6350
|
+
var DISPATCH_NAME = /^(?:execute|run|call|dispatch|handle|invoke|perform)[A-Za-z0-9_]*?_?(?:tool|tool_?call|function_?call)s?$/i;
|
|
5918
6351
|
var KEY_PROPERTIES = ["tool", "name", "toolName", "tool_name"];
|
|
5919
6352
|
var ARG_PROPERTIES = ["params", "args", "arguments", "input", "parameters"];
|
|
6353
|
+
function findRunSites(files, read, toolNames) {
|
|
6354
|
+
const sites = /* @__PURE__ */ new Map();
|
|
6355
|
+
if (toolNames.length === 0) return sites;
|
|
6356
|
+
const patterns = (name) => {
|
|
6357
|
+
const quoted = `['"\`]${escapeForRegex(name)}['"\`]`;
|
|
6358
|
+
return [
|
|
6359
|
+
// `switch (call.tool) { case 'x':` — unmistakable.
|
|
6360
|
+
new RegExp(`case\\s+${quoted}`),
|
|
6361
|
+
// `call.name === 'x'`: the key read off the call being run, which is
|
|
6362
|
+
// what a dispatch does and what a view's own state does not.
|
|
6363
|
+
new RegExp(
|
|
6364
|
+
`\\.\\s*[A-Za-z_$][\\w$]*\\s*={2,3}\\s*${quoted}|${quoted}\\s*={2,3}\\s*[A-Za-z_$][\\w$]*\\s*\\.`
|
|
6365
|
+
),
|
|
6366
|
+
// `handlers['x']`, `executors['x']`
|
|
6367
|
+
new RegExp(`\\[\\s*${quoted}\\s*\\]`),
|
|
6368
|
+
// `'x': async (args) => …`, a map of handlers by key.
|
|
6369
|
+
new RegExp(`${quoted}\\s*:\\s*(?:async\\b|function\\b|\\(|[\\w$]+\\s*=>)`),
|
|
6370
|
+
// Any other comparison against the key. `!==` is deliberately not one:
|
|
6371
|
+
// a tool is not run where its name is ruled out, and taking the first
|
|
6372
|
+
// of those sent the instruction to a view that merely checks which
|
|
6373
|
+
// tool is selected.
|
|
6374
|
+
new RegExp(`(?<![!<>=])={2,3}\\s*${quoted}|${quoted}\\s*={2,3}`)
|
|
6375
|
+
];
|
|
6376
|
+
};
|
|
6377
|
+
const best = /* @__PURE__ */ new Map();
|
|
6378
|
+
for (const file of files) {
|
|
6379
|
+
const source = read(file);
|
|
6380
|
+
if (source === null) continue;
|
|
6381
|
+
const lineOf = lineLocator2(source);
|
|
6382
|
+
for (const name of toolNames) {
|
|
6383
|
+
if (!source.includes(name)) continue;
|
|
6384
|
+
for (const [rank, pattern] of patterns(name).entries()) {
|
|
6385
|
+
const found = pattern.exec(source);
|
|
6386
|
+
if (!found) continue;
|
|
6387
|
+
const current = best.get(name);
|
|
6388
|
+
if (current && current.rank <= rank) break;
|
|
6389
|
+
const at = found.index;
|
|
6390
|
+
const start = source.lastIndexOf("\n", at) + 1;
|
|
6391
|
+
const end = source.indexOf("\n", at);
|
|
6392
|
+
best.set(name, {
|
|
6393
|
+
rank,
|
|
6394
|
+
site: {
|
|
6395
|
+
file,
|
|
6396
|
+
line: lineOf(at),
|
|
6397
|
+
code: source.slice(start, end < 0 ? source.length : end).trim().slice(0, 160)
|
|
6398
|
+
}
|
|
6399
|
+
});
|
|
6400
|
+
break;
|
|
6401
|
+
}
|
|
6402
|
+
}
|
|
6403
|
+
}
|
|
6404
|
+
for (const [name, entry] of best) sites.set(name, entry.site);
|
|
6405
|
+
return sites;
|
|
6406
|
+
}
|
|
5920
6407
|
function planDispatch(files, read, toolNames) {
|
|
5921
6408
|
let best = null;
|
|
5922
6409
|
for (const file of files) {
|
|
@@ -5938,27 +6425,36 @@ function planDispatch(files, read, toolNames) {
|
|
|
5938
6425
|
};
|
|
5939
6426
|
}
|
|
5940
6427
|
}
|
|
5941
|
-
|
|
5942
|
-
best = { file, line: lineOf(found.index), score: Number.MAX_SAFE_INTEGER, fn: found.name };
|
|
5943
|
-
}
|
|
6428
|
+
best ??= { file, line: lineOf(found.index), fn: found.name };
|
|
5944
6429
|
}
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
)
|
|
5950
|
-
|
|
5951
|
-
const first = toolNames.map((name) => source.search(new RegExp(`case\\s+['"\`]${escapeForRegex(name)}['"\`]`))).filter((index) => index >= 0).sort((a, b) => a - b)[0];
|
|
5952
|
-
best = { file, line: first === void 0 ? 1 : lineOf(first), score: named, fn: null };
|
|
6430
|
+
}
|
|
6431
|
+
const sites = findRunSites(files, read, toolNames);
|
|
6432
|
+
if (!best) {
|
|
6433
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
6434
|
+
for (const site of sites.values()) {
|
|
6435
|
+
byFile.set(site.file, [...byFile.get(site.file) ?? [], site]);
|
|
5953
6436
|
}
|
|
6437
|
+
const chosen = [...byFile.entries()].sort(
|
|
6438
|
+
(a, b) => b[1].length - a[1].length || a[0].localeCompare(b[0])
|
|
6439
|
+
)[0];
|
|
6440
|
+
if (!chosen) return null;
|
|
6441
|
+
const line = Math.min(...chosen[1].map((site) => site.line));
|
|
6442
|
+
return {
|
|
6443
|
+
file: chosen[0],
|
|
6444
|
+
line,
|
|
6445
|
+
functionName: null,
|
|
6446
|
+
after: null,
|
|
6447
|
+
reason: "this file runs the tools by name",
|
|
6448
|
+
sites
|
|
6449
|
+
};
|
|
5954
6450
|
}
|
|
5955
|
-
if (!best) return null;
|
|
5956
6451
|
return {
|
|
5957
6452
|
file: best.file,
|
|
5958
6453
|
line: best.line,
|
|
5959
6454
|
functionName: best.fn,
|
|
5960
6455
|
after: null,
|
|
5961
|
-
reason:
|
|
6456
|
+
reason: `${best.fn}() runs the tools, but where to put the guard inside it is not certain`,
|
|
6457
|
+
sites
|
|
5962
6458
|
};
|
|
5963
6459
|
}
|
|
5964
6460
|
function dispatchFunctions(source, python) {
|
|
@@ -6031,13 +6527,220 @@ function guardDispatch(source, fn, typescript) {
|
|
|
6031
6527
|
);
|
|
6032
6528
|
return { after, at: target.start };
|
|
6033
6529
|
}
|
|
6530
|
+
var CHECKPOINT_ARG_PROPERTIES = ["args", "arguments", "params", "parameters", "input", "payload"];
|
|
6531
|
+
function planCheckpoints(files, read, toolKeys) {
|
|
6532
|
+
const plans = [];
|
|
6533
|
+
const outstanding = new Set(toolKeys);
|
|
6534
|
+
if (outstanding.size === 0) return plans;
|
|
6535
|
+
for (const file of files) {
|
|
6536
|
+
if (outstanding.size === 0) break;
|
|
6537
|
+
if (file.endsWith(".py") || !JS_EXTENSIONS.test(file)) continue;
|
|
6538
|
+
const source = read(file);
|
|
6539
|
+
if (source === null) continue;
|
|
6540
|
+
const present = [...outstanding].filter(
|
|
6541
|
+
(key) => source.includes(key) || source.includes(camelOf(key))
|
|
6542
|
+
);
|
|
6543
|
+
if (present.length === 0) continue;
|
|
6544
|
+
const scopes = functionScopes(source);
|
|
6545
|
+
const lineOf = lineLocator2(source);
|
|
6546
|
+
const alias = chooseAliasFor(source, "checkpoint", "js");
|
|
6547
|
+
const edits = [];
|
|
6548
|
+
const placed = [];
|
|
6549
|
+
for (const key of present) {
|
|
6550
|
+
const site = checkpointSite(source, key, scopes);
|
|
6551
|
+
if (!site) continue;
|
|
6552
|
+
const indent = blockIndent(source, site.at);
|
|
6553
|
+
const args = site.argsExpression ? `, ${site.argsExpression}` : "";
|
|
6554
|
+
edits.push({
|
|
6555
|
+
start: site.at,
|
|
6556
|
+
end: site.at,
|
|
6557
|
+
text: `
|
|
6558
|
+
${indent}await ${alias}(${quote(key)}${args});`
|
|
6559
|
+
});
|
|
6560
|
+
placed.push({ key, line: lineOf(site.at), shape: site.shape });
|
|
6561
|
+
outstanding.delete(key);
|
|
6562
|
+
}
|
|
6563
|
+
if (edits.length === 0) continue;
|
|
6564
|
+
const after = addJsSdkImport(applyEdits(source, edits), "checkpoint", alias);
|
|
6565
|
+
plans.push({ file, before: source, after, placed });
|
|
6566
|
+
}
|
|
6567
|
+
return plans;
|
|
6568
|
+
}
|
|
6569
|
+
function checkpointSite(source, key, scopes) {
|
|
6570
|
+
const quoted = `['"\`]${escapeForRegex(key)}['"\`]`;
|
|
6571
|
+
const usable = (at) => enclosingAsync(scopes, at) === true;
|
|
6572
|
+
for (const match of source.matchAll(new RegExp(`\\bcase\\s+${quoted}\\s*:`, "g"))) {
|
|
6573
|
+
const colon = (match.index ?? 0) + match[0].length;
|
|
6574
|
+
const braced = /^\s*\{/.exec(source.slice(colon));
|
|
6575
|
+
const at = braced ? colon + braced[0].length : colon;
|
|
6576
|
+
if (!usable(at)) continue;
|
|
6577
|
+
return { at, shape: "case", argsExpression: switchArguments(source, match.index ?? 0) };
|
|
6578
|
+
}
|
|
6579
|
+
const comparison = new RegExp(
|
|
6580
|
+
`(?:if|else\\s+if)\\s*\\(([^()]{0,200}?)${quoted}([^()]{0,200}?)\\)\\s*\\{`,
|
|
6581
|
+
"g"
|
|
6582
|
+
);
|
|
6583
|
+
for (const match of source.matchAll(comparison)) {
|
|
6584
|
+
const left = match[1] ?? "";
|
|
6585
|
+
if (!/(?<![!<>])={2,3}\s*$/.test(left)) continue;
|
|
6586
|
+
const at = (match.index ?? 0) + match[0].length;
|
|
6587
|
+
if (!usable(at)) continue;
|
|
6588
|
+
const object = /([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*[A-Za-z_$][\w$]*\s*={2,3}\s*$/.exec(left);
|
|
6589
|
+
return {
|
|
6590
|
+
at,
|
|
6591
|
+
shape: "branch",
|
|
6592
|
+
argsExpression: object ? argumentsOf(source, object[1]) : null
|
|
6593
|
+
};
|
|
6594
|
+
}
|
|
6595
|
+
const declaration = new RegExp(
|
|
6596
|
+
`\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*(?::[^=\\n]{0,120})?=[^;\\n]{0,400}?${quoted}[^;]{0,200}?;`,
|
|
6597
|
+
"g"
|
|
6598
|
+
);
|
|
6599
|
+
for (const match of source.matchAll(declaration)) {
|
|
6600
|
+
const name = match[1];
|
|
6601
|
+
const end = (match.index ?? 0) + match[0].length;
|
|
6602
|
+
const head = new RegExp(`^\\s*if\\s*\\(\\s*${escapeForRegex(name)}\\b`).exec(
|
|
6603
|
+
source.slice(end, end + 200)
|
|
6604
|
+
);
|
|
6605
|
+
if (!head) continue;
|
|
6606
|
+
const parenOpen = end + head[0].lastIndexOf("(", head[0].length - name.length);
|
|
6607
|
+
const parenClose = matchBracket(source, parenOpen);
|
|
6608
|
+
if (parenClose < 0) continue;
|
|
6609
|
+
const brace = /^\s*\{/.exec(source.slice(parenClose + 1));
|
|
6610
|
+
if (!brace) continue;
|
|
6611
|
+
const at = parenClose + 1 + brace[0].length;
|
|
6612
|
+
if (!usable(at)) continue;
|
|
6613
|
+
return { at, shape: "found", argsExpression: argumentsOf(source, name) };
|
|
6614
|
+
}
|
|
6615
|
+
const handler = new RegExp(
|
|
6616
|
+
`${quoted}\\s*:\\s*(?:async\\s*)?\\(([^)]{0,200})\\)\\s*(?::[^=>{]{0,120})?=>\\s*\\{`,
|
|
6617
|
+
"g"
|
|
6618
|
+
);
|
|
6619
|
+
for (const match of source.matchAll(handler)) {
|
|
6620
|
+
const at = (match.index ?? 0) + match[0].length;
|
|
6621
|
+
if (!usable(at)) continue;
|
|
6622
|
+
return { at, shape: "handler", argsExpression: parameterExpression(match[1] ?? "") };
|
|
6623
|
+
}
|
|
6624
|
+
const camel = camelOf(key);
|
|
6625
|
+
const named = new RegExp(
|
|
6626
|
+
`\\basync\\s+function\\s+(?:${escapeForRegex(camel)}|${escapeForRegex(key)})\\s*\\(`,
|
|
6627
|
+
"g"
|
|
6628
|
+
);
|
|
6629
|
+
for (const match of source.matchAll(named)) {
|
|
6630
|
+
const parenOpen = (match.index ?? 0) + match[0].length - 1;
|
|
6631
|
+
const parenClose = matchBracket(source, parenOpen);
|
|
6632
|
+
if (parenClose < 0) continue;
|
|
6633
|
+
const bodyOpen = source.indexOf("{", parenClose);
|
|
6634
|
+
if (bodyOpen < 0) continue;
|
|
6635
|
+
if (!/^\s*(?::[^{;]*)?$/.test(source.slice(parenClose + 1, bodyOpen))) continue;
|
|
6636
|
+
return {
|
|
6637
|
+
at: bodyOpen + 1,
|
|
6638
|
+
shape: "function",
|
|
6639
|
+
argsExpression: parameterExpression(source.slice(parenOpen + 1, parenClose))
|
|
6640
|
+
};
|
|
6641
|
+
}
|
|
6642
|
+
return null;
|
|
6643
|
+
}
|
|
6644
|
+
function parameterExpression(parameters) {
|
|
6645
|
+
const names = parameters.split(",").map((part) => /^\s*(?:\.\.\.)?([A-Za-z_$][\w$]*)/.exec(part)?.[1]).filter((name) => name !== void 0);
|
|
6646
|
+
const named = names.find((name) => CHECKPOINT_ARG_PROPERTIES.includes(name));
|
|
6647
|
+
if (named) return named;
|
|
6648
|
+
return names.length === 1 ? names[0] : null;
|
|
6649
|
+
}
|
|
6650
|
+
function camelOf(key) {
|
|
6651
|
+
return key.replace(/_([a-z0-9])/g, (_match, char) => char.toUpperCase());
|
|
6652
|
+
}
|
|
6653
|
+
function switchArguments(source, at) {
|
|
6654
|
+
const before = source.slice(Math.max(0, at - 4e3), at);
|
|
6655
|
+
const found = [
|
|
6656
|
+
...before.matchAll(/\bswitch\s*\(\s*([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*[A-Za-z_$][\w$]*/g)
|
|
6657
|
+
];
|
|
6658
|
+
const last = found[found.length - 1];
|
|
6659
|
+
return last ? argumentsOf(source, last[1]) : null;
|
|
6660
|
+
}
|
|
6661
|
+
function argumentsOf(source, object) {
|
|
6662
|
+
for (const property of CHECKPOINT_ARG_PROPERTIES) {
|
|
6663
|
+
const pattern = new RegExp(`\\b${escapeForRegex(object)}\\s*(?:\\?\\.|\\.)\\s*${property}\\b`);
|
|
6664
|
+
if (pattern.test(source)) return `${object}.${property}`;
|
|
6665
|
+
}
|
|
6666
|
+
return null;
|
|
6667
|
+
}
|
|
6668
|
+
function blockIndent(source, at) {
|
|
6669
|
+
const lineEnd = source.indexOf("\n", at);
|
|
6670
|
+
if (lineEnd >= 0) {
|
|
6671
|
+
const next = /^([ \t]*)\S/.exec(source.slice(lineEnd + 1, lineEnd + 200));
|
|
6672
|
+
if (next && !source.slice(lineEnd + 1).trimStart().startsWith("}"))
|
|
6673
|
+
return next[1];
|
|
6674
|
+
}
|
|
6675
|
+
const lineStart = source.lastIndexOf("\n", at) + 1;
|
|
6676
|
+
return `${/^[ \t]*/.exec(source.slice(lineStart, at))?.[0] ?? ""} `;
|
|
6677
|
+
}
|
|
6678
|
+
function functionScopes(source) {
|
|
6679
|
+
const scopes = [];
|
|
6680
|
+
const add = (isAsync, parenOpen, arrow) => {
|
|
6681
|
+
const parenClose = matchBracket(source, parenOpen);
|
|
6682
|
+
if (parenClose < 0) return;
|
|
6683
|
+
const bodyOpen = source.indexOf("{", parenClose);
|
|
6684
|
+
if (bodyOpen < 0) return;
|
|
6685
|
+
const between = source.slice(parenClose + 1, bodyOpen);
|
|
6686
|
+
if (arrow !== /=>/.test(between)) return;
|
|
6687
|
+
if (!/^\s*(?::[^{;=]*)?(?:=>\s*)?$/.test(between)) return;
|
|
6688
|
+
const bodyClose = matchBracket(source, bodyOpen);
|
|
6689
|
+
if (bodyClose < 0) return;
|
|
6690
|
+
scopes.push({ bodyOpen, bodyClose, async: isAsync });
|
|
6691
|
+
};
|
|
6692
|
+
for (const match of source.matchAll(/\b(async\s+)?function\s*\*?\s*[A-Za-z_$][\w$]*\s*\(/g)) {
|
|
6693
|
+
add(match[1] !== void 0, (match.index ?? 0) + match[0].length - 1, false);
|
|
6694
|
+
}
|
|
6695
|
+
for (const match of source.matchAll(/(\basync\s*)?\(/g)) {
|
|
6696
|
+
add(match[1] !== void 0, (match.index ?? 0) + match[0].length - 1, true);
|
|
6697
|
+
}
|
|
6698
|
+
const method = /^[ \t]*((?:(?:public|private|protected|static|override|async|get|set)\s+)*)\*?\s*([A-Za-z_$][\w$]*)\s*\(/gm;
|
|
6699
|
+
for (const match of source.matchAll(method)) {
|
|
6700
|
+
if (CONTROL_KEYWORDS.has(match[2])) continue;
|
|
6701
|
+
add(/\basync\b/.test(match[1] ?? ""), (match.index ?? 0) + match[0].length - 1, false);
|
|
6702
|
+
}
|
|
6703
|
+
for (const match of source.matchAll(/\b(async\s+)?([A-Za-z_$][\w$]*)\s*=>\s*\{/g)) {
|
|
6704
|
+
const bodyOpen = (match.index ?? 0) + match[0].length - 1;
|
|
6705
|
+
const bodyClose = matchBracket(source, bodyOpen);
|
|
6706
|
+
if (bodyClose > 0) scopes.push({ bodyOpen, bodyClose, async: match[1] !== void 0 });
|
|
6707
|
+
}
|
|
6708
|
+
return scopes;
|
|
6709
|
+
}
|
|
6710
|
+
var CONTROL_KEYWORDS = /* @__PURE__ */ new Set([
|
|
6711
|
+
"if",
|
|
6712
|
+
"else",
|
|
6713
|
+
"for",
|
|
6714
|
+
"while",
|
|
6715
|
+
"do",
|
|
6716
|
+
"switch",
|
|
6717
|
+
"catch",
|
|
6718
|
+
"with",
|
|
6719
|
+
"return",
|
|
6720
|
+
"typeof",
|
|
6721
|
+
"new",
|
|
6722
|
+
"await",
|
|
6723
|
+
"function",
|
|
6724
|
+
"throw",
|
|
6725
|
+
"case"
|
|
6726
|
+
]);
|
|
6727
|
+
function enclosingAsync(scopes, at) {
|
|
6728
|
+
let best = null;
|
|
6729
|
+
for (const scope of scopes) {
|
|
6730
|
+
if (at <= scope.bodyOpen || at > scope.bodyClose) continue;
|
|
6731
|
+
if (!best || scope.bodyOpen > best.bodyOpen) best = scope;
|
|
6732
|
+
}
|
|
6733
|
+
return best ? best.async : null;
|
|
6734
|
+
}
|
|
6034
6735
|
function unguardJs(source) {
|
|
6035
|
-
const
|
|
6036
|
-
|
|
6736
|
+
const checkpoints = uncheckpointJs(source);
|
|
6737
|
+
const imported = jsGuardImport(checkpoints.after);
|
|
6738
|
+
if (!imported) return checkpoints;
|
|
6739
|
+
source = checkpoints.after;
|
|
6037
6740
|
const { alias } = imported;
|
|
6038
6741
|
const lineOf = lineLocator2(source);
|
|
6039
6742
|
const edits = [];
|
|
6040
|
-
const leftovers = [];
|
|
6743
|
+
const leftovers = [...checkpoints.leftovers];
|
|
6041
6744
|
const calls = new RegExp(`(?<![\\w$.])${escapeForRegex(alias)}\\s*\\(`, "g");
|
|
6042
6745
|
for (const match of source.matchAll(calls)) {
|
|
6043
6746
|
const start = match.index ?? 0;
|
|
@@ -6076,8 +6779,35 @@ function unguardJs(source) {
|
|
|
6076
6779
|
}
|
|
6077
6780
|
let after = applyEdits(source, edits);
|
|
6078
6781
|
const stillUsed = new RegExp(`(?<![\\w$.])${escapeForRegex(alias)}\\s*\\(`).test(after);
|
|
6079
|
-
if (!stillUsed) after =
|
|
6080
|
-
return { after, removed: edits.length, leftovers };
|
|
6782
|
+
if (!stillUsed) after = removeJsSdkImport(after, "guard");
|
|
6783
|
+
return { after, removed: edits.length + checkpoints.removed, leftovers };
|
|
6784
|
+
}
|
|
6785
|
+
function uncheckpointJs(source) {
|
|
6786
|
+
const alias = existingImport(source, "checkpoint", "js");
|
|
6787
|
+
if (!alias) return { after: source, removed: 0, leftovers: [] };
|
|
6788
|
+
const lineOf = lineLocator2(source);
|
|
6789
|
+
const leftovers = [];
|
|
6790
|
+
let removed = 0;
|
|
6791
|
+
let after = source.replace(
|
|
6792
|
+
new RegExp(
|
|
6793
|
+
`^[ \\t]*(?:await[ \\t]+)?${escapeForRegex(alias)}\\(\\s*(['"\`])[a-z][a-z0-9_]*\\1[^\\n]*\\)[ \\t]*;?[ \\t]*\\r?\\n`,
|
|
6794
|
+
"gm"
|
|
6795
|
+
),
|
|
6796
|
+
() => {
|
|
6797
|
+
removed += 1;
|
|
6798
|
+
return "";
|
|
6799
|
+
}
|
|
6800
|
+
);
|
|
6801
|
+
for (const match of after.matchAll(
|
|
6802
|
+
new RegExp(`(?<![\\w$.])${escapeForRegex(alias)}\\s*\\(`, "g")
|
|
6803
|
+
)) {
|
|
6804
|
+
leftovers.push({
|
|
6805
|
+
line: lineOf(match.index ?? 0),
|
|
6806
|
+
text: after.slice(match.index ?? 0, (match.index ?? 0) + 80).split("\n")[0] ?? ""
|
|
6807
|
+
});
|
|
6808
|
+
}
|
|
6809
|
+
if (leftovers.length === 0) after = removeJsSdkImport(after, "checkpoint");
|
|
6810
|
+
return { after, removed, leftovers };
|
|
6081
6811
|
}
|
|
6082
6812
|
function unguardPython(source) {
|
|
6083
6813
|
const imported = /^from[ \t]+veefore[ \t]+import[ \t]+([^\n]+)$/m.exec(source);
|
|
@@ -6168,29 +6898,28 @@ function jsGuardImport(source) {
|
|
|
6168
6898
|
}
|
|
6169
6899
|
return null;
|
|
6170
6900
|
}
|
|
6171
|
-
function
|
|
6172
|
-
const
|
|
6173
|
-
|
|
6901
|
+
function removeJsSdkImport(source, name) {
|
|
6902
|
+
const escaped = escapeForRegex(name);
|
|
6903
|
+
const drop = (names, rebuild, target) => {
|
|
6904
|
+
const rest = names.split(",").map((part) => part.trim()).filter((part) => part && !target.test(part));
|
|
6174
6905
|
return rest.length === 0 ? "" : rebuild(rest);
|
|
6175
6906
|
};
|
|
6176
6907
|
let after = source.replace(
|
|
6177
6908
|
/^[ \t]*import\s*\{([^}]*)\}\s*from\s*(['"])@veefore\/sdk\2\s*;?[ \t]*\r?\n?/m,
|
|
6178
|
-
(
|
|
6179
|
-
full,
|
|
6909
|
+
(_full, names, quote2) => drop(
|
|
6180
6910
|
names,
|
|
6181
6911
|
(rest) => `import { ${rest.join(", ")} } from ${quote2}@veefore/sdk${quote2};
|
|
6182
6912
|
`,
|
|
6183
|
-
|
|
6913
|
+
new RegExp(`^${escaped}(\\s+as\\s+[\\w$]+)?$`)
|
|
6184
6914
|
)
|
|
6185
6915
|
);
|
|
6186
6916
|
after = after.replace(
|
|
6187
6917
|
/^[ \t]*const\s*\{([^}]*)\}\s*=\s*require\(\s*(['"])@veefore\/sdk\2\s*\)\s*;?[ \t]*\r?\n?/m,
|
|
6188
|
-
(
|
|
6189
|
-
full,
|
|
6918
|
+
(_full, names, quote2) => drop(
|
|
6190
6919
|
names,
|
|
6191
6920
|
(rest) => `const { ${rest.join(", ")} } = require(${quote2}@veefore/sdk${quote2});
|
|
6192
6921
|
`,
|
|
6193
|
-
|
|
6922
|
+
new RegExp(`^${escaped}(\\s*:\\s*[\\w$]+)?$`)
|
|
6194
6923
|
)
|
|
6195
6924
|
);
|
|
6196
6925
|
return after;
|
|
@@ -6201,22 +6930,35 @@ function escapeForRegex(value) {
|
|
|
6201
6930
|
function isGuarded(value, alias) {
|
|
6202
6931
|
return new RegExp(`^\\s*(${alias}|guard|veeforeGuard)\\(`).test(value);
|
|
6203
6932
|
}
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
var PY_GUARD_IMPORT = /^from\s+veefore\s+import\s+[^\n]*\bguard\b(?:\s+as\s+(\w+))?/m;
|
|
6207
|
-
function existingGuardImport(source, language) {
|
|
6933
|
+
function existingImport(source, name, language) {
|
|
6934
|
+
const escaped = escapeForRegex(name);
|
|
6208
6935
|
if (language === "py") {
|
|
6209
|
-
const match2 =
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6936
|
+
const match2 = new RegExp(
|
|
6937
|
+
`^from\\s+veefore\\s+import\\s+[^\\n]*\\b${escaped}\\b(?:\\s+as\\s+(\\w+))?`,
|
|
6938
|
+
"m"
|
|
6939
|
+
).exec(source);
|
|
6940
|
+
return match2 ? match2[1] ?? name : null;
|
|
6941
|
+
}
|
|
6942
|
+
const esm = new RegExp(
|
|
6943
|
+
`import\\s*\\{[^}]*\\b${escaped}\\b(?:\\s+as\\s+([A-Za-z_$][\\w$]*))?[^}]*\\}\\s*from\\s*['"]@veefore/sdk['"]`
|
|
6944
|
+
).exec(source);
|
|
6945
|
+
const cjs = new RegExp(
|
|
6946
|
+
`\\{[^}]*\\b${escaped}\\b(?:\\s*:\\s*([A-Za-z_$][\\w$]*))?[^}]*\\}\\s*=\\s*require\\(\\s*['"]@veefore/sdk['"]\\s*\\)`
|
|
6947
|
+
).exec(source);
|
|
6948
|
+
const match = esm ?? cjs;
|
|
6949
|
+
return match ? match[1] ?? name : null;
|
|
6214
6950
|
}
|
|
6215
|
-
function
|
|
6216
|
-
|
|
6951
|
+
function existingGuardImport(source, language) {
|
|
6952
|
+
return existingImport(source, "guard", language);
|
|
6953
|
+
}
|
|
6954
|
+
function chooseAliasFor(source, name, language) {
|
|
6955
|
+
const existing = existingImport(source, name, language);
|
|
6217
6956
|
if (existing) return existing;
|
|
6218
|
-
if (
|
|
6219
|
-
return language === "py" ?
|
|
6957
|
+
if (!new RegExp(`\\b${escapeForRegex(name)}\\b`).test(source)) return name;
|
|
6958
|
+
return language === "py" ? `veefore_${name}` : `veefore${name[0]?.toUpperCase() ?? ""}${name.slice(1)}`;
|
|
6959
|
+
}
|
|
6960
|
+
function chooseAlias(source, language) {
|
|
6961
|
+
return chooseAliasFor(source, "guard", language);
|
|
6220
6962
|
}
|
|
6221
6963
|
function applyEdits(source, edits) {
|
|
6222
6964
|
let out2 = source;
|
|
@@ -6225,14 +6967,22 @@ function applyEdits(source, edits) {
|
|
|
6225
6967
|
}
|
|
6226
6968
|
return out2;
|
|
6227
6969
|
}
|
|
6228
|
-
|
|
6970
|
+
var SCHEMA_ONLY_REASON = "this is only the schema; guard the code that runs the tool the model picked (often one dispatch function for every tool)";
|
|
6971
|
+
function schemaOnly(source, python, wanted, placed = /* @__PURE__ */ new Set()) {
|
|
6229
6972
|
const lineOf = lineLocator2(source);
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6973
|
+
const hits = [
|
|
6974
|
+
...findToolSchemas(source, python).map((hit) => ({ name: hit.name, index: hit.index })),
|
|
6975
|
+
...python ? [] : findFunctionToolSchemas(source).map((hit) => ({ name: hit.name, index: hit.index }))
|
|
6976
|
+
].sort((a, b) => a.index - b.index);
|
|
6977
|
+
const changes = [];
|
|
6978
|
+
const seen = new Set(placed);
|
|
6979
|
+
for (const hit of hits) {
|
|
6980
|
+
const key = toToolKey(hit.name);
|
|
6981
|
+
if (!wanted.has(key) || seen.has(key)) continue;
|
|
6982
|
+
seen.add(key);
|
|
6983
|
+
changes.push({ key, line: lineOf(hit.index), status: "manual", reason: SCHEMA_ONLY_REASON });
|
|
6984
|
+
}
|
|
6985
|
+
return changes;
|
|
6236
6986
|
}
|
|
6237
6987
|
function dedupeChanges(changes) {
|
|
6238
6988
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6352,10 +7102,30 @@ function myers(a, b) {
|
|
|
6352
7102
|
|
|
6353
7103
|
// src/integrate.ts
|
|
6354
7104
|
async function integrate(root, toolKeys, options) {
|
|
6355
|
-
const
|
|
7105
|
+
const index = indexFiles(root);
|
|
7106
|
+
const anchors = [...toolKeyAnchors(toolKeys), ...TOOL_ANCHORS, ...GUARD_ANCHORS];
|
|
7107
|
+
const candidates = shortlist(root, index, anchors);
|
|
7108
|
+
const sourceFiles = candidates.files;
|
|
7109
|
+
const truncated = index.truncated || candidates.truncated;
|
|
7110
|
+
const plan = planRewrite(root, sourceFiles, toolKeys);
|
|
6356
7111
|
const interactive = options.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true && !options.yes);
|
|
6357
|
-
|
|
7112
|
+
if (truncated && !options.quiet) {
|
|
7113
|
+
out(
|
|
7114
|
+
`${symbol.warn} The search ran out of time before it finished, so a tool may be reported as not found, or the place it runs may be missed.`
|
|
7115
|
+
);
|
|
7116
|
+
}
|
|
7117
|
+
const sources = readSources(root, sourceFiles);
|
|
7118
|
+
const dispatchGuardFiles = findDispatchGuards(sources);
|
|
6358
7119
|
const dispatchGuarded = dispatchGuardFiles.length > 0;
|
|
7120
|
+
const alreadyGuarded = guardedToolKeys(sources, toolKeys);
|
|
7121
|
+
for (const file of plan.files) {
|
|
7122
|
+
for (const change of file.changes) {
|
|
7123
|
+
if (change.status === "manual" && alreadyGuarded.has(change.key)) {
|
|
7124
|
+
change.status = "already";
|
|
7125
|
+
delete change.reason;
|
|
7126
|
+
}
|
|
7127
|
+
}
|
|
7128
|
+
}
|
|
6359
7129
|
let manual = plan.files.flatMap(
|
|
6360
7130
|
(file) => file.changes.filter((change) => change.status === "manual").map((change) => ({
|
|
6361
7131
|
file: file.file,
|
|
@@ -6379,7 +7149,7 @@ async function integrate(root, toolKeys, options) {
|
|
|
6379
7149
|
}
|
|
6380
7150
|
dispatch = [];
|
|
6381
7151
|
} else if (dispatch.length > 0) {
|
|
6382
|
-
dispatchPlan = planDispatch(
|
|
7152
|
+
dispatchPlan = planDispatch(sourceFiles, (file) => currentText(root, plan, file), [
|
|
6383
7153
|
...new Set(dispatch.map((item) => item.key))
|
|
6384
7154
|
]);
|
|
6385
7155
|
if (dispatchPlan?.after) {
|
|
@@ -6387,7 +7157,22 @@ async function integrate(root, toolKeys, options) {
|
|
|
6387
7157
|
dispatch = [];
|
|
6388
7158
|
}
|
|
6389
7159
|
}
|
|
6390
|
-
|
|
7160
|
+
if (dispatch.length > 0 && checkpointUsable(root, options.quiet === true)) {
|
|
7161
|
+
const placed = applyCheckpoints(
|
|
7162
|
+
plan,
|
|
7163
|
+
planCheckpoints(sourceFiles, (file) => currentText(root, plan, file), [
|
|
7164
|
+
...new Set(dispatch.map((item) => item.key))
|
|
7165
|
+
])
|
|
7166
|
+
);
|
|
7167
|
+
dispatch = dispatch.filter((item) => !placed.has(item.key));
|
|
7168
|
+
}
|
|
7169
|
+
const runsElsewhere = (key) => {
|
|
7170
|
+
const site = dispatchPlan?.sites?.get(key);
|
|
7171
|
+
return site !== void 0 && site.file !== dispatchPlan?.file;
|
|
7172
|
+
};
|
|
7173
|
+
const dispatchKeys = [
|
|
7174
|
+
...new Set(dispatch.filter((item) => !runsElsewhere(item.key)).map((item) => item.key))
|
|
7175
|
+
];
|
|
6391
7176
|
const dispatchItems = dispatchPlan ? dispatchKeys.map((key) => ({
|
|
6392
7177
|
file: dispatchPlan?.file,
|
|
6393
7178
|
key,
|
|
@@ -6395,7 +7180,7 @@ async function integrate(root, toolKeys, options) {
|
|
|
6395
7180
|
reason: `guard the code that runs the tool: ${dispatchPlan?.reason ?? ""}`,
|
|
6396
7181
|
language: languageOf(dispatchPlan?.file)
|
|
6397
7182
|
})) : [];
|
|
6398
|
-
const unplaced = dispatchPlan ?
|
|
7183
|
+
const unplaced = dispatchPlan ? dispatch.filter((item) => runsElsewhere(item.key)) : dispatch;
|
|
6399
7184
|
const pending = [...manual, ...dispatchItems];
|
|
6400
7185
|
if (pending.length > 0 && !options.dryRun) {
|
|
6401
7186
|
const hostedAvailable = await hostedEnabled(options.api ?? null);
|
|
@@ -6450,8 +7235,9 @@ async function integrate(root, toolKeys, options) {
|
|
|
6450
7235
|
const entry = plan.files.find((file) => file.file === proposal.file);
|
|
6451
7236
|
if (!entry) continue;
|
|
6452
7237
|
entry.after = proposal.proposal;
|
|
7238
|
+
const guarded = findGuardedKeys([{ file: proposal.file, text: proposal.proposal }]);
|
|
6453
7239
|
for (const change of entry.changes) {
|
|
6454
|
-
if (change.status === "manual" &&
|
|
7240
|
+
if (change.status === "manual" && guarded.has(change.key)) {
|
|
6455
7241
|
change.status = "wrapped";
|
|
6456
7242
|
change.via = "ai";
|
|
6457
7243
|
delete change.reason;
|
|
@@ -6465,29 +7251,37 @@ async function integrate(root, toolKeys, options) {
|
|
|
6465
7251
|
dispatch = remaining.filter(isDispatchItem);
|
|
6466
7252
|
if (method !== "skip" && !options.quiet) {
|
|
6467
7253
|
if (manual.length > 0) printManualInstructions(manual);
|
|
6468
|
-
|
|
7254
|
+
printDispatchInstructions([...dispatch, ...unplaced], dispatchPlan);
|
|
6469
7255
|
}
|
|
6470
7256
|
} else if (pending.length > 0 && !options.quiet) {
|
|
6471
|
-
|
|
7257
|
+
printDispatchInstructions([...dispatchItems, ...unplaced], dispatchPlan);
|
|
6472
7258
|
dispatch = dispatchItems;
|
|
7259
|
+
} else if (unplaced.length > 0 && !options.quiet && options.assist !== "skip") {
|
|
7260
|
+
printDispatchInstructions(unplaced, dispatchPlan);
|
|
6473
7261
|
}
|
|
6474
|
-
|
|
6475
|
-
printDispatchInstructions(unplaced, null);
|
|
6476
|
-
}
|
|
6477
|
-
manual = [...manual, ...dispatch, ...unplaced];
|
|
7262
|
+
manual = stillManual([...manual, ...dispatch, ...unplaced], plan);
|
|
6478
7263
|
const toWrite = plan.files.filter((file) => file.before !== file.after);
|
|
6479
7264
|
const languages = new Set(toWrite.map((file) => file.file.endsWith(".py") ? "py" : "js"));
|
|
6480
7265
|
if (!options.quiet) printPlan(plan);
|
|
6481
7266
|
if (toWrite.length === 0) {
|
|
6482
|
-
|
|
7267
|
+
const quiet = options.quiet === true || options.dryRun === true;
|
|
7268
|
+
if (!quiet) {
|
|
6483
7269
|
const guardedFiles = plan.files.filter((file) => file.changes.some((change) => change.status !== "manual")).map((file) => file.file);
|
|
6484
7270
|
reportSdkReadiness(root, [.../* @__PURE__ */ new Set([...guardedFiles, ...dispatchGuardFiles])]);
|
|
6485
7271
|
}
|
|
6486
|
-
|
|
7272
|
+
const unguarded2 = reportProtection(root, sourceFiles, toolKeys, quiet);
|
|
7273
|
+
return { status: "nothing-to-do", plan, checks: [], backupId: null, manual, unguarded: unguarded2 };
|
|
6487
7274
|
}
|
|
6488
7275
|
if (options.dryRun) {
|
|
6489
7276
|
out(style.grey("Dry run: nothing was written."));
|
|
6490
|
-
return {
|
|
7277
|
+
return {
|
|
7278
|
+
status: "dry-run",
|
|
7279
|
+
plan,
|
|
7280
|
+
checks: [],
|
|
7281
|
+
backupId: null,
|
|
7282
|
+
manual,
|
|
7283
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true)
|
|
7284
|
+
};
|
|
6491
7285
|
}
|
|
6492
7286
|
if (!options.quiet) {
|
|
6493
7287
|
reportSdkReadiness(
|
|
@@ -6501,7 +7295,14 @@ async function integrate(root, toolKeys, options) {
|
|
|
6501
7295
|
);
|
|
6502
7296
|
if (!proceed) {
|
|
6503
7297
|
out(style.grey("No files were changed. Run `veefore integrate` when you are ready."));
|
|
6504
|
-
return {
|
|
7298
|
+
return {
|
|
7299
|
+
status: "declined",
|
|
7300
|
+
plan,
|
|
7301
|
+
checks: [],
|
|
7302
|
+
backupId: null,
|
|
7303
|
+
manual,
|
|
7304
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true)
|
|
7305
|
+
};
|
|
6505
7306
|
}
|
|
6506
7307
|
const jsReady = languages.has("js") && sdkInstalled(root, "js");
|
|
6507
7308
|
const baseline = jsReady ? typecheck(
|
|
@@ -6509,13 +7310,20 @@ async function integrate(root, toolKeys, options) {
|
|
|
6509
7310
|
toWrite.map((file) => file.file)
|
|
6510
7311
|
) : null;
|
|
6511
7312
|
for (const file of toWrite) {
|
|
6512
|
-
if (
|
|
7313
|
+
if (readFileSync9(path10.join(root, file.file), "utf8") !== file.before) {
|
|
6513
7314
|
out(`${symbol.fail} ${file.file} changed while setup was running. Nothing was written.`);
|
|
6514
|
-
return {
|
|
7315
|
+
return {
|
|
7316
|
+
status: "declined",
|
|
7317
|
+
plan,
|
|
7318
|
+
checks: [],
|
|
7319
|
+
backupId: null,
|
|
7320
|
+
manual,
|
|
7321
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true)
|
|
7322
|
+
};
|
|
6515
7323
|
}
|
|
6516
7324
|
}
|
|
6517
7325
|
const backup = createBackup(root, toWrite);
|
|
6518
|
-
for (const file of toWrite) writeFilePreservingMode(
|
|
7326
|
+
for (const file of toWrite) writeFilePreservingMode(path10.join(root, file.file), file.after);
|
|
6519
7327
|
out(
|
|
6520
7328
|
`${symbol.pass} Updated ${toWrite.length} file${toWrite.length === 1 ? "" : "s"} ${style.grey(`(backup ${backup.id})`)}`
|
|
6521
7329
|
);
|
|
@@ -6571,7 +7379,14 @@ async function integrate(root, toolKeys, options) {
|
|
|
6571
7379
|
`${symbol.warn} A check failed after the change, so every file was put back as it was ${style.grey(`(${restored.restored.length} restored)`)}.`
|
|
6572
7380
|
);
|
|
6573
7381
|
out(style.grey(" Please report this: the rewrite should never break a project."));
|
|
6574
|
-
return {
|
|
7382
|
+
return {
|
|
7383
|
+
status: "rolled-back",
|
|
7384
|
+
plan,
|
|
7385
|
+
checks,
|
|
7386
|
+
backupId: null,
|
|
7387
|
+
manual,
|
|
7388
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true)
|
|
7389
|
+
};
|
|
6575
7390
|
}
|
|
6576
7391
|
for (const language of languages) {
|
|
6577
7392
|
if (!sdkInstalled(root, language)) {
|
|
@@ -6580,13 +7395,34 @@ async function integrate(root, toolKeys, options) {
|
|
|
6580
7395
|
);
|
|
6581
7396
|
}
|
|
6582
7397
|
}
|
|
7398
|
+
const unguarded = reportProtection(root, sourceFiles, toolKeys, options.quiet === true);
|
|
6583
7399
|
out(style.grey(" Undo at any time with `veefore undo`."));
|
|
6584
|
-
return { status: "applied", plan, checks, backupId: backup.id, manual };
|
|
7400
|
+
return { status: "applied", plan, checks, backupId: backup.id, manual, unguarded };
|
|
7401
|
+
}
|
|
7402
|
+
function reportProtection(root, files, toolKeys, quiet) {
|
|
7403
|
+
const guarded = guardedToolKeys(readSources(root, files), toolKeys);
|
|
7404
|
+
const unguarded = toolKeys.filter((key) => !guarded.has(key));
|
|
7405
|
+
if (quiet) return unguarded;
|
|
7406
|
+
if (unguarded.length === 0) {
|
|
7407
|
+
out(
|
|
7408
|
+
`${symbol.pass} Every registered tool goes through Veefore ${style.grey(`(${toolKeys.length})`)}`
|
|
7409
|
+
);
|
|
7410
|
+
return unguarded;
|
|
7411
|
+
}
|
|
7412
|
+
out(
|
|
7413
|
+
`${symbol.warn} ${toolKeys.length - unguarded.length} of ${toolKeys.length} registered tools go through Veefore. Still unprotected: ${style.bold(unguarded.join(", "))}`
|
|
7414
|
+
);
|
|
7415
|
+
out(
|
|
7416
|
+
style.grey(
|
|
7417
|
+
" The instructions above say what each one needs. Then `veefore integrate --check` to confirm."
|
|
7418
|
+
)
|
|
7419
|
+
);
|
|
7420
|
+
return unguarded;
|
|
6585
7421
|
}
|
|
6586
7422
|
function reportSdkReadiness(root, files) {
|
|
6587
7423
|
const problems = [];
|
|
6588
7424
|
if (files.length === 0) return problems;
|
|
6589
|
-
const dirs = [...new Set(files.map((file) =>
|
|
7425
|
+
const dirs = [...new Set(files.map((file) => path10.dirname(file)))];
|
|
6590
7426
|
const python = files.some((file) => file.endsWith(".py"));
|
|
6591
7427
|
const sdks = installedSdks(root, { dirs, python });
|
|
6592
7428
|
for (const sdk of sdks) {
|
|
@@ -6663,7 +7499,7 @@ async function removeGuards(root, options) {
|
|
|
6663
7499
|
const tsFiles = changed.filter((file) => /\.(m|c)?tsx?$/.test(file));
|
|
6664
7500
|
const baseline = tsFiles.length > 0 ? typecheck(root, tsFiles) : null;
|
|
6665
7501
|
const backup = createBackup(root, toWrite);
|
|
6666
|
-
for (const entry of toWrite) writeFilePreservingMode(
|
|
7502
|
+
for (const entry of toWrite) writeFilePreservingMode(path10.join(root, entry.file), entry.after);
|
|
6667
7503
|
const checks = [];
|
|
6668
7504
|
const py = changed.filter((file) => file.endsWith(".py"));
|
|
6669
7505
|
if (py.length > 0) checks.push(pythonSyntax(root, py));
|
|
@@ -6687,7 +7523,7 @@ function currentText(root, plan, file) {
|
|
|
6687
7523
|
const entry = plan.files.find((candidate) => candidate.file === file);
|
|
6688
7524
|
if (entry) return entry.after;
|
|
6689
7525
|
try {
|
|
6690
|
-
return
|
|
7526
|
+
return readFileSync9(path10.join(root, file), "utf8");
|
|
6691
7527
|
} catch {
|
|
6692
7528
|
return null;
|
|
6693
7529
|
}
|
|
@@ -6709,17 +7545,76 @@ function applyDispatch(root, plan, file, after, line, items, via) {
|
|
|
6709
7545
|
entry.changes.push({ key, line, status: "wrapped", ...via ? { via } : {} });
|
|
6710
7546
|
}
|
|
6711
7547
|
}
|
|
6712
|
-
function
|
|
6713
|
-
|
|
7548
|
+
function stillManual(items, plan) {
|
|
7549
|
+
const open = new Set(
|
|
7550
|
+
plan.files.flatMap(
|
|
7551
|
+
(file) => file.changes.filter((change) => change.status === "manual").map((change) => change.key)
|
|
7552
|
+
)
|
|
7553
|
+
);
|
|
7554
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7555
|
+
return items.filter((item) => {
|
|
7556
|
+
if (!open.has(item.key)) return false;
|
|
7557
|
+
const id = isDispatchItem(item) ? item.key : `${item.key}\0${item.file}`;
|
|
7558
|
+
if (seen.has(id)) return false;
|
|
7559
|
+
seen.add(id);
|
|
7560
|
+
return true;
|
|
7561
|
+
});
|
|
7562
|
+
}
|
|
7563
|
+
function checkpointUsable(root, quiet) {
|
|
7564
|
+
const sdks = installedSdks(root).filter((sdk) => sdk.language === "js");
|
|
7565
|
+
const stale = sdks.filter((sdk) => !sdk.hasCheckpoint);
|
|
7566
|
+
if (sdks.length === 0 || stale.length === 0) return true;
|
|
7567
|
+
if (!quiet) {
|
|
7568
|
+
out(
|
|
7569
|
+
`${symbol.warn} Tools your code runs in a branch rather than a function are guarded with ${style.bold("checkpoint()")}, added in @veefore/sdk ${MIN_CHECKPOINT_SDK}. ${stale[0]?.version ?? "The installed version"} does not have it, so they are left for you.`
|
|
7570
|
+
);
|
|
7571
|
+
out(
|
|
7572
|
+
` Upgrade and re-run: ${style.bold(`${installCommand(root, "js")}@^${MIN_CHECKPOINT_SDK}`)}`
|
|
7573
|
+
);
|
|
7574
|
+
}
|
|
7575
|
+
return false;
|
|
7576
|
+
}
|
|
7577
|
+
function applyCheckpoints(plan, plans) {
|
|
7578
|
+
const placed = /* @__PURE__ */ new Set();
|
|
7579
|
+
for (const checkpoint of plans) {
|
|
7580
|
+
let entry = plan.files.find((candidate) => candidate.file === checkpoint.file);
|
|
7581
|
+
if (!entry) {
|
|
7582
|
+
entry = {
|
|
7583
|
+
file: checkpoint.file,
|
|
7584
|
+
before: checkpoint.before,
|
|
7585
|
+
after: checkpoint.after,
|
|
7586
|
+
changes: []
|
|
7587
|
+
};
|
|
7588
|
+
plan.files.push(entry);
|
|
7589
|
+
}
|
|
7590
|
+
entry.after = checkpoint.after;
|
|
7591
|
+
for (const place of checkpoint.placed) {
|
|
7592
|
+
placed.add(place.key);
|
|
7593
|
+
entry.changes.push({
|
|
7594
|
+
key: place.key,
|
|
7595
|
+
line: place.line,
|
|
7596
|
+
status: "wrapped",
|
|
7597
|
+
via: "checkpoint"
|
|
7598
|
+
});
|
|
7599
|
+
}
|
|
7600
|
+
}
|
|
7601
|
+
for (const file of plan.files) {
|
|
7602
|
+
file.changes = file.changes.filter(
|
|
7603
|
+
(change) => !(change.status === "manual" && placed.has(change.key) && isDispatchItem({ reason: change.reason ?? "" }))
|
|
7604
|
+
);
|
|
7605
|
+
}
|
|
7606
|
+
return placed;
|
|
7607
|
+
}
|
|
7608
|
+
function readSources(root, files = listSourceFiles(root)) {
|
|
7609
|
+
return files.flatMap((file) => {
|
|
6714
7610
|
try {
|
|
6715
|
-
return [{ file, text:
|
|
7611
|
+
return [{ file, text: readFileSync9(path10.join(root, file), "utf8") }];
|
|
6716
7612
|
} catch {
|
|
6717
7613
|
return [];
|
|
6718
7614
|
}
|
|
6719
7615
|
});
|
|
6720
7616
|
}
|
|
6721
|
-
function
|
|
6722
|
-
const sources = readSources(root);
|
|
7617
|
+
function guardedToolKeys(sources, toolKeys) {
|
|
6723
7618
|
const guarded = findGuardedKeys(sources);
|
|
6724
7619
|
const dispatchFiles = findDispatchGuards(sources);
|
|
6725
7620
|
if (dispatchFiles.length > 0) {
|
|
@@ -6734,6 +7629,10 @@ function checkIntegration(root, toolKeys) {
|
|
|
6734
7629
|
}
|
|
6735
7630
|
}
|
|
6736
7631
|
}
|
|
7632
|
+
return new Map([...guarded].filter(([key]) => toolKeys.includes(key)));
|
|
7633
|
+
}
|
|
7634
|
+
function checkIntegration(root, toolKeys) {
|
|
7635
|
+
const guarded = guardedToolKeys(readSources(root), toolKeys);
|
|
6737
7636
|
heading("Protection check");
|
|
6738
7637
|
for (const key of toolKeys) {
|
|
6739
7638
|
const file = guarded.get(key);
|
|
@@ -6796,22 +7695,31 @@ function printPlan(plan) {
|
|
|
6796
7695
|
const wrapped = all.filter((entry) => entry.change.status === "wrapped");
|
|
6797
7696
|
const already = all.filter((entry) => entry.change.status === "already");
|
|
6798
7697
|
const manual = all.filter((entry) => entry.change.status === "manual");
|
|
7698
|
+
const tools = (entries) => new Set(entries.map((e) => e.change.key)).size;
|
|
7699
|
+
const keys = (entries) => [...new Set(entries.map((e) => e.change.key))];
|
|
6799
7700
|
if (wrapped.length > 0) {
|
|
7701
|
+
const count = tools(wrapped);
|
|
7702
|
+
const only = new Set(wrapped.map((entry) => entry.change.via ?? "guard"));
|
|
7703
|
+
const how = only.size === 1 && only.has("checkpoint") ? `gated with ${style.bold("checkpoint()")} where your code runs them` : `wrapped with ${style.bold("guard()")}`;
|
|
6800
7704
|
out(
|
|
6801
|
-
`${symbol.pass} ${
|
|
7705
|
+
`${symbol.pass} ${count} tool${count === 1 ? "" : "s"} will be ${how}, so Veefore decides each call:`
|
|
6802
7706
|
);
|
|
7707
|
+
const note = {
|
|
7708
|
+
ai: style.amber("proposed by AI \u2014 review the diff below"),
|
|
7709
|
+
checkpoint: style.grey("guarded where your code runs it")
|
|
7710
|
+
};
|
|
6803
7711
|
for (const { file, change } of wrapped) {
|
|
6804
7712
|
out(
|
|
6805
|
-
` ${style.bold(change.key.padEnd(24))} ${style.grey(`${file}:${change.line}`)}` + (change.via
|
|
7713
|
+
` ${style.bold(change.key.padEnd(24))} ${style.grey(`${file}:${change.line}`)}` + (change.via ? ` ${note[change.via] ?? ""}` : "")
|
|
6806
7714
|
);
|
|
6807
7715
|
}
|
|
6808
7716
|
}
|
|
6809
7717
|
if (already.length > 0) {
|
|
6810
|
-
out(`${symbol.pass} Already protected: ${already
|
|
7718
|
+
out(`${symbol.pass} Already protected: ${keys(already).join(", ")}`);
|
|
6811
7719
|
}
|
|
6812
7720
|
if (manual.length > 0) {
|
|
6813
7721
|
out(
|
|
6814
|
-
`${symbol.warn} Not changed automatically: ${manual
|
|
7722
|
+
`${symbol.warn} Not changed automatically: ${keys(manual).join(", ")} ${style.grey("(see instructions above)")}`
|
|
6815
7723
|
);
|
|
6816
7724
|
}
|
|
6817
7725
|
if (plan.notFound.length > 0) {
|
|
@@ -6833,8 +7741,8 @@ function printPlan(plan) {
|
|
|
6833
7741
|
}
|
|
6834
7742
|
|
|
6835
7743
|
// src/commands/remove.ts
|
|
6836
|
-
import { rmSync as
|
|
6837
|
-
import
|
|
7744
|
+
import { rmSync as rmSync4 } from "node:fs";
|
|
7745
|
+
import path11 from "node:path";
|
|
6838
7746
|
|
|
6839
7747
|
// src/discover.ts
|
|
6840
7748
|
var LOCAL_CANDIDATE_PORTS = [4e3, 4400, 4100, 4200, 4300, 4500, 8e3, 8080, 3e3];
|
|
@@ -6948,7 +7856,7 @@ async function removeCommand(args, flags, known) {
|
|
|
6948
7856
|
if (!file || !config) {
|
|
6949
7857
|
throw new CliError("This project is not connected to Veefore.", "There is nothing to remove.");
|
|
6950
7858
|
}
|
|
6951
|
-
const root =
|
|
7859
|
+
const root = path11.dirname(file);
|
|
6952
7860
|
const state = known ?? await agentState(config);
|
|
6953
7861
|
heading("Remove Veefore from this project");
|
|
6954
7862
|
details([
|
|
@@ -6999,7 +7907,7 @@ async function removeCommand(args, flags, known) {
|
|
|
6999
7907
|
out(style.grey("The project stays connected."));
|
|
7000
7908
|
return 0;
|
|
7001
7909
|
}
|
|
7002
|
-
|
|
7910
|
+
rmSync4(file);
|
|
7003
7911
|
const envRemoved = removeLocalEnvBlock(root);
|
|
7004
7912
|
out(
|
|
7005
7913
|
`${symbol.pass} Disconnected: removed veefore.config.json${envRemoved ? " and the Veefore block in .env.local" : ""}.`
|
|
@@ -7083,6 +7991,7 @@ async function integrateCommand(args, flags) {
|
|
|
7083
7991
|
files: outcome.plan.files.map((file) => ({ file: file.file, changes: file.changes })),
|
|
7084
7992
|
not_found: outcome.plan.notFound,
|
|
7085
7993
|
manual: outcome.manual,
|
|
7994
|
+
unguarded: outcome.unguarded,
|
|
7086
7995
|
checks: outcome.checks
|
|
7087
7996
|
},
|
|
7088
7997
|
null,
|
|
@@ -7482,10 +8391,10 @@ async function resolveAuthorizedTools(context, filter) {
|
|
|
7482
8391
|
|
|
7483
8392
|
// src/panel.ts
|
|
7484
8393
|
import { homedir as homedir2 } from "node:os";
|
|
7485
|
-
import
|
|
8394
|
+
import path12 from "node:path";
|
|
7486
8395
|
|
|
7487
8396
|
// src/version.ts
|
|
7488
|
-
var CLI_VERSION = "1.
|
|
8397
|
+
var CLI_VERSION = "1.5.0";
|
|
7489
8398
|
|
|
7490
8399
|
// src/panel.ts
|
|
7491
8400
|
function welcomeSource(flags = {}) {
|
|
@@ -7509,7 +8418,7 @@ function welcomeSource(flags = {}) {
|
|
|
7509
8418
|
}
|
|
7510
8419
|
function tilde(dir) {
|
|
7511
8420
|
const home = homedir2();
|
|
7512
|
-
return dir === home || dir.startsWith(`${home}${
|
|
8421
|
+
return dir === home || dir.startsWith(`${home}${path12.sep}`) ? `~${dir.slice(home.length)}` : dir;
|
|
7513
8422
|
}
|
|
7514
8423
|
function hostOf(url) {
|
|
7515
8424
|
try {
|
|
@@ -7576,7 +8485,7 @@ function planTool(tool) {
|
|
|
7576
8485
|
}
|
|
7577
8486
|
function printScan(scan) {
|
|
7578
8487
|
out(
|
|
7579
|
-
`${symbol.pass}
|
|
8488
|
+
`${symbol.pass} Searched ${scan.filesScanned.toLocaleString("en-US")} file${scan.filesScanned === 1 ? "" : "s"}, read ${scan.filesParsed.toLocaleString("en-US")}` + (scan.truncated ? style.amber(" (ran out of time; findings may be incomplete)") : "") + style.grey(" \u2014 locally, nothing was uploaded")
|
|
7580
8489
|
);
|
|
7581
8490
|
if (scan.tools.length === 0) {
|
|
7582
8491
|
out(`${symbol.warn} No tools found, so there is nothing for Veefore to decide on yet.`);
|
|
@@ -7609,6 +8518,40 @@ function printScan(scan) {
|
|
|
7609
8518
|
`${symbol.warn} Your code reads ${service.length} credential${service.length === 1 ? "" : "s"}: ${service.map((s) => s.name).join(", ")} ${style.grey("(names only; values were not read)")}`
|
|
7610
8519
|
);
|
|
7611
8520
|
}
|
|
8521
|
+
printGaps(scan.root);
|
|
8522
|
+
}
|
|
8523
|
+
function printGaps(root) {
|
|
8524
|
+
let discovery;
|
|
8525
|
+
try {
|
|
8526
|
+
discovery = discoverAgents(root);
|
|
8527
|
+
} catch {
|
|
8528
|
+
return;
|
|
8529
|
+
}
|
|
8530
|
+
if (discovery.agents.length > 1) {
|
|
8531
|
+
out();
|
|
8532
|
+
out(`${symbol.info} ${discovery.agents.length} agents found in this project:`);
|
|
8533
|
+
for (const agent of discovery.agents) {
|
|
8534
|
+
out(
|
|
8535
|
+
` ${style.bold(agent.name ?? agent.directory)} ${style.grey(`${agent.directory} \u2014 ${agent.tools.length} tool${agent.tools.length === 1 ? "" : "s"}`)}`
|
|
8536
|
+
);
|
|
8537
|
+
}
|
|
8538
|
+
out(style.grey(" Set each one up from its own directory, so its tools stay its own."));
|
|
8539
|
+
}
|
|
8540
|
+
if (discovery.unreadable.length === 0) return;
|
|
8541
|
+
const shown = discovery.unreadable.slice(0, 5);
|
|
8542
|
+
out();
|
|
8543
|
+
out(
|
|
8544
|
+
`${symbol.warn} ${discovery.unreadable.length} file${discovery.unreadable.length === 1 ? "" : "s"} look${discovery.unreadable.length === 1 ? "s" : ""} like agent code whose tools could not be read:`
|
|
8545
|
+
);
|
|
8546
|
+
for (const gap of shown) out(` ${style.grey(`${gap.file}:${gap.line}`)}`);
|
|
8547
|
+
if (discovery.unreadable.length > shown.length) {
|
|
8548
|
+
out(style.grey(` \u2026and ${discovery.unreadable.length - shown.length} more`));
|
|
8549
|
+
}
|
|
8550
|
+
out(
|
|
8551
|
+
style.grey(
|
|
8552
|
+
" Tools defined in a shape this scan does not know are not protected. Register them\n in the console by hand, or report the shape so it can be detected."
|
|
8553
|
+
)
|
|
8554
|
+
);
|
|
7612
8555
|
}
|
|
7613
8556
|
function printPlan2(plan, environment) {
|
|
7614
8557
|
out();
|
|
@@ -8570,7 +9513,7 @@ async function initCommand(args, flags) {
|
|
|
8570
9513
|
}
|
|
8571
9514
|
function previousApprovalMode(dir = process.cwd()) {
|
|
8572
9515
|
try {
|
|
8573
|
-
const text =
|
|
9516
|
+
const text = readFileSync10(path13.join(dir, ".env.local"), "utf8");
|
|
8574
9517
|
const value = /^\s*VEEFORE_APPROVAL_MODE\s*=\s*['"]?(\w+)/m.exec(text)?.[1]?.toLowerCase();
|
|
8575
9518
|
return value === "wait" || value === "throw" ? value : null;
|
|
8576
9519
|
} catch {
|