@kynver-app/runtime 0.1.103 → 0.1.105
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/cleanup-completion-blocker.d.ts +10 -0
- package/dist/cleanup-guards.d.ts +1 -6
- package/dist/cleanup-worktree-salvage.d.ts +7 -0
- package/dist/cli.js +166 -59
- package/dist/cli.js.map +4 -4
- package/dist/index.js +166 -59
- package/dist/index.js.map +4 -4
- package/dist/server/cleanup.d.ts +3 -0
- package/dist/server/cleanup.js +3511 -0
- package/dist/server/cleanup.js.map +7 -0
- package/dist/server/default-repo.d.ts +1 -0
- package/dist/server/default-repo.js +228 -0
- package/dist/server/default-repo.js.map +7 -0
- package/dist/server/harness-notice.d.ts +2 -0
- package/dist/server/harness-notice.js +287 -0
- package/dist/server/harness-notice.js.map +7 -0
- package/dist/server/heavy-verification.d.ts +2 -0
- package/dist/server/heavy-verification.js +223 -0
- package/dist/server/heavy-verification.js.map +7 -0
- package/dist/server/landing.d.ts +1 -0
- package/dist/server/landing.js +44 -0
- package/dist/server/landing.js.map +7 -0
- package/dist/server/memory-cost-enforce.d.ts +1 -0
- package/dist/server/memory-cost-enforce.js +470 -0
- package/dist/server/memory-cost-enforce.js.map +7 -0
- package/dist/server/memory-cost.d.ts +1 -0
- package/dist/server/memory-cost.js +184 -0
- package/dist/server/memory-cost.js.map +7 -0
- package/dist/server/monitor.d.ts +3 -0
- package/dist/server/monitor.js +1577 -0
- package/dist/server/monitor.js.map +7 -0
- package/dist/server/orchestration.d.ts +10 -0
- package/dist/server/orchestration.js +444 -0
- package/dist/server/orchestration.js.map +7 -0
- package/dist/server/pr-evidence.d.ts +2 -0
- package/dist/server/pr-evidence.js +163 -0
- package/dist/server/pr-evidence.js.map +7 -0
- package/dist/server/repo-search.d.ts +1 -0
- package/dist/server/repo-search.js +224 -0
- package/dist/server/repo-search.js.map +7 -0
- package/dist/server/worker-policy.d.ts +2 -0
- package/dist/server/worker-policy.js +177 -0
- package/dist/server/worker-policy.js.map +7 -0
- package/dist/status.d.ts +4 -0
- package/package.json +63 -3
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// src/vercel/vercel-url.ts
|
|
2
|
+
var VERCEL_HOST_RE = /(^|\.)vercel\.app$/i;
|
|
3
|
+
var DPL_ID_RE = /^dpl_[a-z0-9]+$/i;
|
|
4
|
+
function tryParseUrl(raw) {
|
|
5
|
+
const trimmed = raw.trim();
|
|
6
|
+
if (!trimmed) return null;
|
|
7
|
+
try {
|
|
8
|
+
return new URL(trimmed);
|
|
9
|
+
} catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function parseDashboardDeployment(url) {
|
|
14
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
15
|
+
if (parts.length < 3) return null;
|
|
16
|
+
const deploymentId = parts[parts.length - 1]?.trim();
|
|
17
|
+
if (!deploymentId || deploymentId === "deployments") return null;
|
|
18
|
+
return deploymentId;
|
|
19
|
+
}
|
|
20
|
+
function parseDeploymentsSegment(url) {
|
|
21
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
22
|
+
const idx = parts.indexOf("deployments");
|
|
23
|
+
if (idx < 0 || idx >= parts.length - 1) return null;
|
|
24
|
+
const deploymentId = parts[idx + 1]?.trim();
|
|
25
|
+
return deploymentId || null;
|
|
26
|
+
}
|
|
27
|
+
function classifyVercelUrl(raw) {
|
|
28
|
+
const empty = {
|
|
29
|
+
kind: "unknown",
|
|
30
|
+
previewUrl: null,
|
|
31
|
+
inspectTarget: null,
|
|
32
|
+
deploymentId: null
|
|
33
|
+
};
|
|
34
|
+
const trimmed = raw.trim();
|
|
35
|
+
if (!trimmed) return empty;
|
|
36
|
+
if (/^dpl_[a-z0-9]+$/i.test(trimmed)) {
|
|
37
|
+
return {
|
|
38
|
+
kind: "deployment_id",
|
|
39
|
+
previewUrl: null,
|
|
40
|
+
inspectTarget: trimmed,
|
|
41
|
+
deploymentId: trimmed
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const url = tryParseUrl(trimmed);
|
|
45
|
+
if (!url) return empty;
|
|
46
|
+
if (VERCEL_HOST_RE.test(url.hostname)) {
|
|
47
|
+
const hostUrl = url.origin;
|
|
48
|
+
return {
|
|
49
|
+
kind: "deployment_host",
|
|
50
|
+
previewUrl: hostUrl,
|
|
51
|
+
inspectTarget: hostUrl,
|
|
52
|
+
deploymentId: null
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (url.hostname === "vercel.com" || url.hostname.endsWith(".vercel.com")) {
|
|
56
|
+
const deploymentId = parseDeploymentsSegment(url) ?? parseDashboardDeployment(url);
|
|
57
|
+
const inspectTarget = deploymentId && DPL_ID_RE.test(deploymentId) ? deploymentId : null;
|
|
58
|
+
return {
|
|
59
|
+
kind: "dashboard",
|
|
60
|
+
previewUrl: null,
|
|
61
|
+
inspectTarget,
|
|
62
|
+
deploymentId
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return empty;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/vercel/vercel-github-status.ts
|
|
69
|
+
var VERCEL_CONTEXT_RE = /vercel/i;
|
|
70
|
+
function normalizeGitHubStatusState(state) {
|
|
71
|
+
const value = typeof state === "string" ? state.trim().toLowerCase() : "";
|
|
72
|
+
if (value === "success") return "success";
|
|
73
|
+
if (value === "pending") return "pending";
|
|
74
|
+
if (value === "failure") return "failure";
|
|
75
|
+
if (value === "error") return "error";
|
|
76
|
+
return "unknown";
|
|
77
|
+
}
|
|
78
|
+
function isVercelStatusContext(context) {
|
|
79
|
+
const label = typeof context === "string" ? context.trim() : "";
|
|
80
|
+
return Boolean(label && VERCEL_CONTEXT_RE.test(label));
|
|
81
|
+
}
|
|
82
|
+
function pickVercelStatusContext(statuses) {
|
|
83
|
+
const rows = Array.isArray(statuses) ? statuses : [];
|
|
84
|
+
const vercelRows = rows.filter((row) => isVercelStatusContext(row.context));
|
|
85
|
+
if (vercelRows.length === 0) return null;
|
|
86
|
+
const score = (state) => {
|
|
87
|
+
if (state === "failure" || state === "error") return 0;
|
|
88
|
+
if (state === "pending") return 1;
|
|
89
|
+
if (state === "success") return 2;
|
|
90
|
+
return 1;
|
|
91
|
+
};
|
|
92
|
+
let best = null;
|
|
93
|
+
let bestScore = Infinity;
|
|
94
|
+
for (const row of vercelRows) {
|
|
95
|
+
const rowScore = score(normalizeGitHubStatusState(row.state));
|
|
96
|
+
if (rowScore < bestScore) {
|
|
97
|
+
best = row;
|
|
98
|
+
bestScore = rowScore;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!best) return null;
|
|
102
|
+
const targetUrl = typeof best.target_url === "string" && best.target_url.trim() ? best.target_url.trim() : null;
|
|
103
|
+
const classified = targetUrl ? classifyVercelUrl(targetUrl) : null;
|
|
104
|
+
return {
|
|
105
|
+
context: String(best.context ?? "Vercel").trim(),
|
|
106
|
+
state: normalizeGitHubStatusState(best.state),
|
|
107
|
+
targetUrl,
|
|
108
|
+
description: typeof best.description === "string" && best.description.trim() ? best.description.trim() : null,
|
|
109
|
+
deploymentId: classified?.deploymentId ?? null,
|
|
110
|
+
previewUrl: classified?.previewUrl ?? null,
|
|
111
|
+
dashboardUrl: classified?.kind === "dashboard" ? targetUrl : null
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/vercel/vercel-evidence.ts
|
|
116
|
+
function mapGitHubStateToEvidence(state) {
|
|
117
|
+
if (state === "success") return "ready";
|
|
118
|
+
if (state === "pending") return "building";
|
|
119
|
+
if (state === "failure" || state === "error") return "error";
|
|
120
|
+
return "unavailable";
|
|
121
|
+
}
|
|
122
|
+
function evidenceSummary(parts) {
|
|
123
|
+
return parts.filter(Boolean).join("; ");
|
|
124
|
+
}
|
|
125
|
+
function evidenceFromGitHubVercelStatus(statuses, options = {}) {
|
|
126
|
+
const observedAt = options.observedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
127
|
+
const row = pickVercelStatusContext(statuses);
|
|
128
|
+
if (!row) {
|
|
129
|
+
return {
|
|
130
|
+
status: "not_run",
|
|
131
|
+
previewUrl: null,
|
|
132
|
+
deploymentUrl: null,
|
|
133
|
+
summary: "No Vercel GitHub status context on commit",
|
|
134
|
+
observedAt,
|
|
135
|
+
inspectSkipped: true,
|
|
136
|
+
inspectReason: "no_vercel_status_context",
|
|
137
|
+
githubState: null,
|
|
138
|
+
vercelContext: null
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const status = mapGitHubStateToEvidence(row.state);
|
|
142
|
+
const previewUrl = row.previewUrl ?? row.dashboardUrl;
|
|
143
|
+
const deploymentUrl = row.previewUrl ?? row.dashboardUrl;
|
|
144
|
+
return {
|
|
145
|
+
status,
|
|
146
|
+
previewUrl,
|
|
147
|
+
deploymentUrl,
|
|
148
|
+
summary: evidenceSummary([
|
|
149
|
+
`GitHub ${row.context}=${row.state}`,
|
|
150
|
+
row.description ?? "",
|
|
151
|
+
row.dashboardUrl ? "dashboard target_url (API lookup skipped)" : ""
|
|
152
|
+
]),
|
|
153
|
+
observedAt,
|
|
154
|
+
inspectSkipped: true,
|
|
155
|
+
inspectReason: row.dashboardUrl && row.state === "success" ? "trusted_github_status_dashboard_url" : row.dashboardUrl ? "dashboard_url_not_api_inspectable" : "github_status_only",
|
|
156
|
+
githubState: row.state,
|
|
157
|
+
vercelContext: row.context
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
export {
|
|
161
|
+
evidenceFromGitHubVercelStatus
|
|
162
|
+
};
|
|
163
|
+
//# sourceMappingURL=pr-evidence.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/vercel/vercel-url.ts", "../../src/vercel/vercel-github-status.ts", "../../src/vercel/vercel-evidence.ts"],
|
|
4
|
+
"sourcesContent": ["// Classify Vercel URLs from GitHub StatusContext.target_url for REST API lookup vs trust-github.\r\n\r\nexport type VercelUrlKind =\r\n | \"deployment_host\"\r\n | \"dashboard\"\r\n | \"deployment_id\"\r\n | \"unknown\";\r\n\r\nexport interface ClassifiedVercelUrl {\r\n kind: VercelUrlKind;\r\n /** Original URL when it is a usable preview/deployment host. */\r\n previewUrl: string | null;\r\n /** Deployment id or host URL suitable for Vercel REST API lookup \u2014 never a dashboard page URL. */\r\n inspectTarget: string | null;\r\n /** Deployment id extracted from dashboard paths when present. */\r\n deploymentId: string | null;\r\n}\r\n\r\nconst VERCEL_HOST_RE = /(^|\\.)vercel\\.app$/i;\r\nconst DPL_ID_RE = /^dpl_[a-z0-9]+$/i;\r\n\r\n/** True when the string is safe for Vercel REST API deployment lookup (never a dashboard page URL). */\r\nexport function isInspectableVercelTarget(target: string): boolean {\r\n const trimmed = target.trim();\r\n if (!trimmed) return false;\r\n if (/vercel\\.com/i.test(trimmed)) return false;\r\n if (DPL_ID_RE.test(trimmed)) return true;\r\n try {\r\n const url = new URL(trimmed.startsWith(\"http\") ? trimmed : `https://${trimmed}`);\r\n return VERCEL_HOST_RE.test(url.hostname);\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\nfunction tryParseUrl(raw: string): URL | null {\r\n const trimmed = raw.trim();\r\n if (!trimmed) return null;\r\n try {\r\n return new URL(trimmed);\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/** Dashboard deployment pages: /{team}/{project}/{deploymentId} */\r\nfunction parseDashboardDeployment(url: URL): string | null {\r\n const parts = url.pathname.split(\"/\").filter(Boolean);\r\n if (parts.length < 3) return null;\r\n const deploymentId = parts[parts.length - 1]?.trim();\r\n if (!deploymentId || deploymentId === \"deployments\") return null;\r\n return deploymentId;\r\n}\r\n\r\n/** Alternate dashboard shape: .../deployments/{id} */\r\nfunction parseDeploymentsSegment(url: URL): string | null {\r\n const parts = url.pathname.split(\"/\").filter(Boolean);\r\n const idx = parts.indexOf(\"deployments\");\r\n if (idx < 0 || idx >= parts.length - 1) return null;\r\n const deploymentId = parts[idx + 1]?.trim();\r\n return deploymentId || null;\r\n}\r\n\r\n/**\r\n * Classify a Vercel-related URL from GitHub commit status target_url.\r\n * Vercel REST API accepts deployment ids and `*.vercel.app` hosts \u2014 not vercel.com dashboard links.\r\n */\r\nexport function classifyVercelUrl(raw: string): ClassifiedVercelUrl {\r\n const empty: ClassifiedVercelUrl = {\r\n kind: \"unknown\",\r\n previewUrl: null,\r\n inspectTarget: null,\r\n deploymentId: null,\r\n };\r\n\r\n const trimmed = raw.trim();\r\n if (!trimmed) return empty;\r\n\r\n if (/^dpl_[a-z0-9]+$/i.test(trimmed)) {\r\n return {\r\n kind: \"deployment_id\",\r\n previewUrl: null,\r\n inspectTarget: trimmed,\r\n deploymentId: trimmed,\r\n };\r\n }\r\n\r\n const url = tryParseUrl(trimmed);\r\n if (!url) return empty;\r\n\r\n if (VERCEL_HOST_RE.test(url.hostname)) {\r\n const hostUrl = url.origin;\r\n return {\r\n kind: \"deployment_host\",\r\n previewUrl: hostUrl,\r\n inspectTarget: hostUrl,\r\n deploymentId: null,\r\n };\r\n }\r\n\r\n if (url.hostname === \"vercel.com\" || url.hostname.endsWith(\".vercel.com\")) {\r\n const deploymentId =\r\n parseDeploymentsSegment(url) ?? parseDashboardDeployment(url);\r\n const inspectTarget =\r\n deploymentId && DPL_ID_RE.test(deploymentId) ? deploymentId : null;\r\n return {\r\n kind: \"dashboard\",\r\n previewUrl: null,\r\n inspectTarget,\r\n deploymentId,\r\n };\r\n }\r\n\r\n return empty;\r\n}\r\n\r\nexport function isDashboardVercelUrl(raw: string): boolean {\r\n return classifyVercelUrl(raw).kind === \"dashboard\";\r\n}\r\n", "// GitHub commit StatusContext rows for the Vercel GitHub app integration.\r\n\r\nimport { classifyVercelUrl } from \"./vercel-url.js\";\r\n\r\nexport type GitHubStatusState = \"success\" | \"pending\" | \"failure\" | \"error\" | \"unknown\";\r\n\r\nexport interface GitHubCommitStatusRow {\r\n context?: string | null;\r\n state?: string | null;\r\n target_url?: string | null;\r\n description?: string | null;\r\n}\r\n\r\nexport interface GitHubVercelStatusContext {\r\n context: string;\r\n state: GitHubStatusState;\r\n targetUrl: string | null;\r\n description: string | null;\r\n deploymentId: string | null;\r\n previewUrl: string | null;\r\n dashboardUrl: string | null;\r\n}\r\n\r\nconst VERCEL_CONTEXT_RE = /vercel/i;\r\n\r\nexport function normalizeGitHubStatusState(state: unknown): GitHubStatusState {\r\n const value = typeof state === \"string\" ? state.trim().toLowerCase() : \"\";\r\n if (value === \"success\") return \"success\";\r\n if (value === \"pending\") return \"pending\";\r\n if (value === \"failure\") return \"failure\";\r\n if (value === \"error\") return \"error\";\r\n return \"unknown\";\r\n}\r\n\r\nexport function isVercelStatusContext(context: unknown): boolean {\r\n const label = typeof context === \"string\" ? context.trim() : \"\";\r\n return Boolean(label && VERCEL_CONTEXT_RE.test(label));\r\n}\r\n\r\nexport function pickVercelStatusContext(\r\n statuses: GitHubCommitStatusRow[] | null | undefined,\r\n): GitHubVercelStatusContext | null {\r\n const rows = Array.isArray(statuses) ? statuses : [];\r\n const vercelRows = rows.filter((row) => isVercelStatusContext(row.context));\r\n if (vercelRows.length === 0) return null;\r\n\r\n const score = (state: GitHubStatusState): number => {\r\n if (state === \"failure\" || state === \"error\") return 0;\r\n if (state === \"pending\") return 1;\r\n if (state === \"success\") return 2;\r\n return 1;\r\n };\r\n\r\n let best: GitHubCommitStatusRow | null = null;\r\n let bestScore = Infinity;\r\n for (const row of vercelRows) {\r\n const rowScore = score(normalizeGitHubStatusState(row.state));\r\n if (rowScore < bestScore) {\r\n best = row;\r\n bestScore = rowScore;\r\n }\r\n }\r\n if (!best) return null;\r\n\r\n const targetUrl =\r\n typeof best.target_url === \"string\" && best.target_url.trim()\r\n ? best.target_url.trim()\r\n : null;\r\n const classified = targetUrl ? classifyVercelUrl(targetUrl) : null;\r\n\r\n return {\r\n context: String(best.context ?? \"Vercel\").trim(),\r\n state: normalizeGitHubStatusState(best.state),\r\n targetUrl,\r\n description:\r\n typeof best.description === \"string\" && best.description.trim()\r\n ? best.description.trim()\r\n : null,\r\n deploymentId: classified?.deploymentId ?? null,\r\n previewUrl: classified?.previewUrl ?? null,\r\n dashboardUrl: classified?.kind === \"dashboard\" ? targetUrl : null,\r\n };\r\n}\r\n", "// Build merge-gate Vercel evidence from GitHub status and optional Vercel REST API.\r\n\r\nimport {\r\n fetchVercelDeploymentStatus,\r\n mapVercelReadyStateToEvidenceStatus,\r\n type VercelDeploymentLookupResult,\r\n} from \"./vercel-api.js\";\r\nimport {\r\n pickVercelStatusContext,\r\n type GitHubCommitStatusRow,\r\n type GitHubStatusState,\r\n type GitHubVercelStatusContext,\r\n} from \"./vercel-github-status.js\";\r\nimport {\r\n classifyVercelUrl,\r\n isInspectableVercelTarget,\r\n type ClassifiedVercelUrl,\r\n} from \"./vercel-url.js\";\r\n\r\nexport type VercelEvidenceStatus =\r\n | \"ready\"\r\n | \"building\"\r\n | \"error\"\r\n | \"unavailable\"\r\n | \"not_run\";\r\n\r\nexport interface VercelEvidenceResult {\r\n status: VercelEvidenceStatus;\r\n previewUrl: string | null;\r\n deploymentUrl: string | null;\r\n summary: string | null;\r\n observedAt: string | null;\r\n /** True when Vercel API lookup was not run (GitHub-only or missing token). */\r\n inspectSkipped: boolean;\r\n inspectReason: string | null;\r\n githubState: GitHubStatusState | null;\r\n vercelContext: string | null;\r\n}\r\n\r\n/** @deprecated Renamed \u2014 use VercelDeploymentLookupResult from vercel-api. */\r\nexport type VercelInspectCommandResult = VercelDeploymentLookupResult;\r\n\r\nexport type RunVercelDeploymentLookup = (\r\n target: string,\r\n) => Promise<VercelDeploymentLookupResult>;\r\n\r\n/** @deprecated Renamed \u2014 use RunVercelDeploymentLookup. */\r\nexport type RunVercelInspect = RunVercelDeploymentLookup;\r\n\r\nfunction mapGitHubStateToEvidence(state: GitHubStatusState): VercelEvidenceStatus {\r\n if (state === \"success\") return \"ready\";\r\n if (state === \"pending\") return \"building\";\r\n if (state === \"failure\" || state === \"error\") return \"error\";\r\n return \"unavailable\";\r\n}\r\n\r\nfunction evidenceSummary(parts: string[]): string {\r\n return parts.filter(Boolean).join(\"; \");\r\n}\r\n\r\n/**\r\n * Resolve the deployment lookup target. Never returns a vercel.com dashboard page URL.\r\n */\r\nexport function resolveVercelInspectTarget(rawUrl: string | null | undefined): {\r\n target: string | null;\r\n classified: ClassifiedVercelUrl | null;\r\n reason: string | null;\r\n} {\r\n const trimmed = typeof rawUrl === \"string\" ? rawUrl.trim() : \"\";\r\n if (!trimmed) {\r\n return { target: null, classified: null, reason: \"missing target_url\" };\r\n }\r\n\r\n const classified = classifyVercelUrl(trimmed);\r\n if (classified.inspectTarget && isInspectableVercelTarget(classified.inspectTarget)) {\r\n return { target: classified.inspectTarget, classified, reason: null };\r\n }\r\n\r\n if (classified.kind === \"dashboard\") {\r\n return {\r\n target: null,\r\n classified,\r\n reason: classified.deploymentId\r\n ? \"dashboard deployment id is not API-inspectable; trust GitHub Vercel status\"\r\n : \"dashboard URL is not valid for Vercel API lookup\",\r\n };\r\n }\r\n\r\n return { target: null, classified, reason: \"unrecognized Vercel URL\" };\r\n}\r\n\r\nexport function evidenceFromGitHubVercelStatus(\r\n statuses: GitHubCommitStatusRow[] | null | undefined,\r\n options: { observedAt?: string } = {},\r\n): VercelEvidenceResult {\r\n const observedAt = options.observedAt ?? new Date().toISOString();\r\n const row = pickVercelStatusContext(statuses);\r\n\r\n if (!row) {\r\n return {\r\n status: \"not_run\",\r\n previewUrl: null,\r\n deploymentUrl: null,\r\n summary: \"No Vercel GitHub status context on commit\",\r\n observedAt,\r\n inspectSkipped: true,\r\n inspectReason: \"no_vercel_status_context\",\r\n githubState: null,\r\n vercelContext: null,\r\n };\r\n }\r\n\r\n const status = mapGitHubStateToEvidence(row.state);\r\n const previewUrl = row.previewUrl ?? row.dashboardUrl;\r\n const deploymentUrl = row.previewUrl ?? row.dashboardUrl;\r\n\r\n return {\r\n status,\r\n previewUrl,\r\n deploymentUrl,\r\n summary: evidenceSummary([\r\n `GitHub ${row.context}=${row.state}`,\r\n row.description ?? \"\",\r\n row.dashboardUrl ? \"dashboard target_url (API lookup skipped)\" : \"\",\r\n ]),\r\n observedAt,\r\n inspectSkipped: true,\r\n inspectReason:\r\n row.dashboardUrl && row.state === \"success\"\r\n ? \"trusted_github_status_dashboard_url\"\r\n : row.dashboardUrl\r\n ? \"dashboard_url_not_api_inspectable\"\r\n : \"github_status_only\",\r\n githubState: row.state,\r\n vercelContext: row.context,\r\n };\r\n}\r\n\r\nexport interface CollectVercelEvidenceInput {\r\n statuses?: GitHubCommitStatusRow[] | null;\r\n /** When false, never call Vercel REST API (GitHub status only). Default path. */\r\n allowInspect?: boolean;\r\n /** @deprecated waitSeconds was for CLI inspect; ignored for API lookup. */\r\n waitSeconds?: number;\r\n runInspect?: RunVercelDeploymentLookup;\r\n observedAt?: string;\r\n}\r\n\r\n/**\r\n * Prefer GitHub Vercel StatusContext.state for gates. Call Vercel REST API only when\r\n * inspectable (deployment host or id), GitHub state is not terminal success, and\r\n * `allowInspect` is true with VERCEL_TOKEN configured.\r\n */\r\nexport async function collectVercelEvidence(\r\n input: CollectVercelEvidenceInput,\r\n): Promise<VercelEvidenceResult> {\r\n const observedAt = input.observedAt ?? new Date().toISOString();\r\n const base = evidenceFromGitHubVercelStatus(input.statuses ?? [], { observedAt });\r\n const row = pickVercelStatusContext(input.statuses ?? []);\r\n\r\n if (!row) return base;\r\n\r\n if (row.state === \"success\") {\r\n return {\r\n ...base,\r\n inspectSkipped: true,\r\n inspectReason: row.dashboardUrl\r\n ? \"trusted_github_status_dashboard_url\"\r\n : \"trusted_github_status\",\r\n };\r\n }\r\n\r\n if (!input.allowInspect) {\r\n return {\r\n ...base,\r\n inspectSkipped: true,\r\n inspectReason: \"api_lookup_disabled\",\r\n };\r\n }\r\n\r\n const { target, reason } = resolveVercelInspectTarget(row.targetUrl);\r\n if (!target) {\r\n return {\r\n ...base,\r\n inspectSkipped: true,\r\n inspectReason: reason ?? \"not_api_inspectable\",\r\n summary: evidenceSummary([\r\n base.summary ?? \"\",\r\n reason ?? \"skipped Vercel API lookup\",\r\n ]),\r\n };\r\n }\r\n\r\n const runLookup = input.runInspect ?? fetchVercelDeploymentStatus;\r\n const lookedUp = await runLookup(target);\r\n const observed = new Date().toISOString();\r\n\r\n if (!lookedUp.ok && lookedUp.error?.includes(\"VERCEL_TOKEN\")) {\r\n return {\r\n status: \"unavailable\",\r\n previewUrl: base.previewUrl,\r\n deploymentUrl: base.deploymentUrl,\r\n summary: \"VERCEL_TOKEN not configured for API fallback\",\r\n observedAt: observed,\r\n inspectSkipped: true,\r\n inspectReason: \"vercel_token_missing\",\r\n githubState: row.state,\r\n vercelContext: row.context,\r\n };\r\n }\r\n\r\n if (!lookedUp.ok) {\r\n return {\r\n status: row.state === \"pending\" ? \"building\" : \"error\",\r\n previewUrl: base.previewUrl,\r\n deploymentUrl: target.startsWith(\"http\") ? target : base.deploymentUrl,\r\n summary: evidenceSummary([\r\n `Vercel API lookup ${target} failed`,\r\n lookedUp.error ?? \"\",\r\n ]),\r\n observedAt: observed,\r\n inspectSkipped: false,\r\n inspectReason: null,\r\n githubState: row.state,\r\n vercelContext: row.context,\r\n };\r\n }\r\n\r\n const mapped = mapVercelReadyStateToEvidenceStatus(\r\n lookedUp.readyState,\r\n row.state === \"pending\",\r\n );\r\n const previewUrl =\r\n lookedUp.previewUrl ??\r\n (target.startsWith(\"http\") ? target : base.previewUrl);\r\n\r\n return {\r\n status: mapped,\r\n previewUrl,\r\n deploymentUrl: previewUrl ?? base.deploymentUrl,\r\n summary: evidenceSummary([\r\n `Vercel API ${target}=${lookedUp.readyState ?? \"unknown\"}`,\r\n mapped === \"ready\" ? \"preview ready\" : \"\",\r\n ]),\r\n observedAt: observed,\r\n inspectSkipped: false,\r\n inspectReason: null,\r\n githubState: row.state,\r\n vercelContext: row.context,\r\n };\r\n}\r\n\r\nexport type { GitHubVercelStatusContext };\r\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { classifyRepoSearchMeta, diagnoseRepoSearchFailure, extractSearchMetaFromToolLine, formatRepoSearchGuidance, } from "../repo-search.js";
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// src/repo-search.ts
|
|
2
|
+
var RG_BINARIES = /* @__PURE__ */ new Set(["rg", "ripgrep", "grep"]);
|
|
3
|
+
var RG_OPTS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
4
|
+
"-e",
|
|
5
|
+
"--regexp",
|
|
6
|
+
"-f",
|
|
7
|
+
"--file",
|
|
8
|
+
"-m",
|
|
9
|
+
"--max-count",
|
|
10
|
+
"-A",
|
|
11
|
+
"--after-context",
|
|
12
|
+
"-B",
|
|
13
|
+
"--before-context",
|
|
14
|
+
"-C",
|
|
15
|
+
"--context",
|
|
16
|
+
"-g",
|
|
17
|
+
"--glob",
|
|
18
|
+
"--iglob"
|
|
19
|
+
]);
|
|
20
|
+
function binaryName(token) {
|
|
21
|
+
if (!token) return null;
|
|
22
|
+
const base = token.split("/").pop() ?? token;
|
|
23
|
+
return base.replace(/\.exe$/i, "").toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
function splitShellWords(input) {
|
|
26
|
+
if (!input) return [];
|
|
27
|
+
const words = [];
|
|
28
|
+
let current = "";
|
|
29
|
+
let quote;
|
|
30
|
+
let escaped = false;
|
|
31
|
+
for (const char of input) {
|
|
32
|
+
if (escaped) {
|
|
33
|
+
current += char;
|
|
34
|
+
escaped = false;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (char === "\\") {
|
|
38
|
+
escaped = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (quote) {
|
|
42
|
+
if (char === quote) quote = void 0;
|
|
43
|
+
else current += char;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (char === '"' || char === "'") {
|
|
47
|
+
quote = char;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (/\s/.test(char)) {
|
|
51
|
+
if (current) {
|
|
52
|
+
words.push(current);
|
|
53
|
+
current = "";
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
current += char;
|
|
58
|
+
}
|
|
59
|
+
if (current) words.push(current);
|
|
60
|
+
return words;
|
|
61
|
+
}
|
|
62
|
+
function isRgArgv(argv) {
|
|
63
|
+
const bin = binaryName(argv[0]);
|
|
64
|
+
return Boolean(bin && RG_BINARIES.has(bin));
|
|
65
|
+
}
|
|
66
|
+
function isSingleFileSearchTarget(target) {
|
|
67
|
+
if (!target || target.includes("/") || target.includes("*")) return false;
|
|
68
|
+
return /\.(?:json|md|mjs|cjs|js|ts|tsx|yaml|yml)$/iu.test(target);
|
|
69
|
+
}
|
|
70
|
+
function isRgExcludeScopeTarget(target) {
|
|
71
|
+
if (!target) return false;
|
|
72
|
+
const t = target.trim();
|
|
73
|
+
return t.startsWith("!") && !t.includes("/") && !t.endsWith("/**");
|
|
74
|
+
}
|
|
75
|
+
function fixRgGlobToken(token) {
|
|
76
|
+
if (!token.startsWith("--glob=")) return token;
|
|
77
|
+
const value = token.slice("--glob=".length);
|
|
78
|
+
if (value.startsWith("!") && !value.includes("/") && !value.endsWith("/**")) {
|
|
79
|
+
return `--glob=${value}/**`;
|
|
80
|
+
}
|
|
81
|
+
return token;
|
|
82
|
+
}
|
|
83
|
+
function collectRgPositional(argv) {
|
|
84
|
+
const positional = [];
|
|
85
|
+
for (let i = 1; i < argv.length; i += 1) {
|
|
86
|
+
const token = argv[i];
|
|
87
|
+
if (!token) continue;
|
|
88
|
+
if (token === "--") {
|
|
89
|
+
positional.push(...argv.slice(i + 1));
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
if (token.startsWith("-")) {
|
|
93
|
+
if (token.includes("=")) continue;
|
|
94
|
+
if (RG_OPTS_WITH_VALUE.has(token)) i += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
positional.push(token);
|
|
98
|
+
}
|
|
99
|
+
return positional;
|
|
100
|
+
}
|
|
101
|
+
function normalizeRgArgv(argv) {
|
|
102
|
+
let changed = false;
|
|
103
|
+
const out = argv.map((token) => {
|
|
104
|
+
const fixed = fixRgGlobToken(token);
|
|
105
|
+
if (fixed !== token) changed = true;
|
|
106
|
+
return fixed;
|
|
107
|
+
});
|
|
108
|
+
const positional = collectRgPositional(out);
|
|
109
|
+
if (positional.length === 2) {
|
|
110
|
+
const [pattern, target] = positional;
|
|
111
|
+
if (isSingleFileSearchTarget(target)) {
|
|
112
|
+
return { argv: [out[0] ?? "rg", "-g", target, pattern, "."], changed: true };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { argv: out, changed };
|
|
116
|
+
}
|
|
117
|
+
function normalizeRepoSearchCommand(command) {
|
|
118
|
+
const trimmed = command.trim();
|
|
119
|
+
if (!trimmed) return { command: trimmed, changed: false };
|
|
120
|
+
const joiner = trimmed.includes("&&") ? " && " : trimmed.includes("||") ? " || " : "; ";
|
|
121
|
+
const stages = trimmed.split(/\s*(?:&&|\|\||;)\s*/u);
|
|
122
|
+
let changed = false;
|
|
123
|
+
const normalizedStages = stages.map((stage) => {
|
|
124
|
+
const argv = splitShellWords(stage.trim());
|
|
125
|
+
if (!argv.length || !isRgArgv(argv)) return stage;
|
|
126
|
+
const normalized = normalizeRgArgv(argv);
|
|
127
|
+
if (normalized.changed) {
|
|
128
|
+
changed = true;
|
|
129
|
+
return normalized.argv.join(" ");
|
|
130
|
+
}
|
|
131
|
+
return stage;
|
|
132
|
+
});
|
|
133
|
+
if (!changed) return { command: trimmed, changed: false };
|
|
134
|
+
return { command: normalizedStages.join(joiner), changed: true };
|
|
135
|
+
}
|
|
136
|
+
function extractSearchMeta(meta) {
|
|
137
|
+
if (!meta) return {};
|
|
138
|
+
const pipelineMatch = meta.match(/search\s+"(.+)"\s+in\s+([^()]+?)(?:\s*\(|$)/iu);
|
|
139
|
+
if (pipelineMatch) {
|
|
140
|
+
return { pattern: pipelineMatch[1], target: pipelineMatch[2]?.trim() };
|
|
141
|
+
}
|
|
142
|
+
const match = meta.match(/^search\s+"(.+)"\s+in\s+(.+)$/iu);
|
|
143
|
+
if (!match) return {};
|
|
144
|
+
return { pattern: match[1], target: match[2]?.trim() };
|
|
145
|
+
}
|
|
146
|
+
function classifyRepoSearchMeta(meta) {
|
|
147
|
+
const { pattern, target } = extractSearchMeta(meta);
|
|
148
|
+
if (!pattern) return { kind: "not_repo_search" };
|
|
149
|
+
if (isRgExcludeScopeTarget(target)) {
|
|
150
|
+
return { kind: "rg_exclude_syntax", pattern, target };
|
|
151
|
+
}
|
|
152
|
+
if (isSingleFileSearchTarget(target)) {
|
|
153
|
+
return { kind: "bad_scope", pattern, target };
|
|
154
|
+
}
|
|
155
|
+
return { kind: "not_repo_search", pattern, target };
|
|
156
|
+
}
|
|
157
|
+
function metaToNormalizedRgCommand(meta) {
|
|
158
|
+
const { pattern, target } = extractSearchMeta(meta);
|
|
159
|
+
if (!pattern) return null;
|
|
160
|
+
if (isRgExcludeScopeTarget(target)) {
|
|
161
|
+
const glob = `${target.trim()}/**`;
|
|
162
|
+
return {
|
|
163
|
+
command: `rg "${pattern}" -g '${glob}' .`,
|
|
164
|
+
changed: true
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (target && isSingleFileSearchTarget(target)) {
|
|
168
|
+
return {
|
|
169
|
+
command: `rg -g ${target} "${pattern}" .`,
|
|
170
|
+
changed: true
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
function formatRepoSearchGuidance(ctx) {
|
|
176
|
+
if (ctx.kind === "bad_scope" && ctx.pattern?.includes("agent-os-land-pr") && ctx.target === "package.json") {
|
|
177
|
+
return "Search package.json with a glob from the repo root: `rg -g package.json agent-os-land-pr .` \u2014 or run `node scripts/agent-os-land-pr.mjs <pr-url>` directly.";
|
|
178
|
+
}
|
|
179
|
+
if (ctx.kind === "bad_scope" && ctx.pattern && ctx.target) {
|
|
180
|
+
return `Use \`rg -g '${ctx.target}' ${ctx.pattern} .\` from the repo root instead of treating ${ctx.target} as a folder.`;
|
|
181
|
+
}
|
|
182
|
+
if (ctx.kind === "rg_exclude_syntax" && ctx.pattern) {
|
|
183
|
+
const glob = ctx.target ? `${ctx.target.trim()}/**` : "!node_modules/**";
|
|
184
|
+
return `Repo search scope \`${ctx.target ?? "!node_modules"}\` is not a valid ripgrep path. Use \`rg "${ctx.pattern}" -g '${glob}' .\` from the repo root (exclude globs need a \`/**\` suffix).`;
|
|
185
|
+
}
|
|
186
|
+
if (ctx.kind === "no_matches" && ctx.pattern) {
|
|
187
|
+
return `No matches for "${ctx.pattern}". Try a broader pattern, drop overly short tokens, or search from the repo root with \`rg "${ctx.pattern}" .\`.`;
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
function extractSearchMetaFromToolLine(line) {
|
|
192
|
+
const match = line.match(/search\s+"(.+)"\s+in\s+([^()]+?)(?:\s*\(agent\)|\s*failed|$)/iu);
|
|
193
|
+
if (!match) return null;
|
|
194
|
+
return `search "${match[1]}" in ${match[2]?.trim()}`;
|
|
195
|
+
}
|
|
196
|
+
function diagnoseRepoSearchFailure(input) {
|
|
197
|
+
const meta = input.meta?.trim() || (input.command ? extractSearchMetaFromToolLine(input.command) : null) || null;
|
|
198
|
+
if (meta) {
|
|
199
|
+
const ctx = classifyRepoSearchMeta(meta);
|
|
200
|
+
const guidance = formatRepoSearchGuidance(ctx);
|
|
201
|
+
if (guidance) return guidance;
|
|
202
|
+
const normalized = metaToNormalizedRgCommand(meta);
|
|
203
|
+
if (normalized?.changed) {
|
|
204
|
+
return `Repo search used an invalid scope. Retry with: \`${normalized.command}\`.`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (input.command && /\b(rg|ripgrep)\b/i.test(input.command)) {
|
|
208
|
+
const normalized = normalizeRepoSearchCommand(input.command);
|
|
209
|
+
if (normalized.changed) {
|
|
210
|
+
return `Ripgrep scope may be invalid. Retry with: \`${normalized.command}\`.`;
|
|
211
|
+
}
|
|
212
|
+
if (input.exitCode === 1) {
|
|
213
|
+
return "Ripgrep returned no matches (exit 1). Try a broader pattern or search from the repo root.";
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
export {
|
|
219
|
+
classifyRepoSearchMeta,
|
|
220
|
+
diagnoseRepoSearchFailure,
|
|
221
|
+
extractSearchMetaFromToolLine,
|
|
222
|
+
formatRepoSearchGuidance
|
|
223
|
+
};
|
|
224
|
+
//# sourceMappingURL=repo-search.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/repo-search.ts"],
|
|
4
|
+
"sourcesContent": ["/**\r\n * Normalize and classify repo text-search commands for harness operators and chat agents.\r\n *\r\n * Codex/OpenClaw often emit `search \"pattern\" in <scope>` meta that maps poorly to ripgrep\r\n * (e.g. `in !node_modules` instead of `-g '!node_modules/**'`, or `in package.json` as a path).\r\n */\r\n\r\nconst RG_BINARIES = new Set([\"rg\", \"ripgrep\", \"grep\"]);\r\nconst RG_OPTS_WITH_VALUE = new Set([\r\n \"-e\",\r\n \"--regexp\",\r\n \"-f\",\r\n \"--file\",\r\n \"-m\",\r\n \"--max-count\",\r\n \"-A\",\r\n \"--after-context\",\r\n \"-B\",\r\n \"--before-context\",\r\n \"-C\",\r\n \"--context\",\r\n \"-g\",\r\n \"--glob\",\r\n \"--iglob\",\r\n]);\r\n\r\nexport type RepoSearchKind =\r\n | \"not_repo_search\"\r\n | \"bad_scope\"\r\n | \"rg_exclude_syntax\"\r\n | \"no_matches\";\r\n\r\nexport interface RepoSearchContext {\r\n kind: RepoSearchKind;\r\n pattern?: string;\r\n target?: string;\r\n}\r\n\r\nfunction binaryName(token: string | undefined): string | null {\r\n if (!token) return null;\r\n const base = token.split(\"/\").pop() ?? token;\r\n return base.replace(/\\.exe$/i, \"\").toLowerCase();\r\n}\r\n\r\nfunction splitShellWords(input: string): string[] {\r\n if (!input) return [];\r\n const words: string[] = [];\r\n let current = \"\";\r\n let quote: string | undefined;\r\n let escaped = false;\r\n for (const char of input) {\r\n if (escaped) {\r\n current += char;\r\n escaped = false;\r\n continue;\r\n }\r\n if (char === \"\\\\\") {\r\n escaped = true;\r\n continue;\r\n }\r\n if (quote) {\r\n if (char === quote) quote = undefined;\r\n else current += char;\r\n continue;\r\n }\r\n if (char === '\"' || char === \"'\") {\r\n quote = char;\r\n continue;\r\n }\r\n if (/\\s/.test(char)) {\r\n if (current) {\r\n words.push(current);\r\n current = \"\";\r\n }\r\n continue;\r\n }\r\n current += char;\r\n }\r\n if (current) words.push(current);\r\n return words;\r\n}\r\n\r\nfunction isRgArgv(argv: string[]): boolean {\r\n const bin = binaryName(argv[0]);\r\n return Boolean(bin && RG_BINARIES.has(bin));\r\n}\r\n\r\nexport function isSingleFileSearchTarget(target: string | undefined): boolean {\r\n if (!target || target.includes(\"/\") || target.includes(\"*\")) return false;\r\n return /\\.(?:json|md|mjs|cjs|js|ts|tsx|yaml|yml)$/iu.test(target);\r\n}\r\n\r\n/** `!node_modules` as a path is invalid \u2014 ripgrep needs `-g '!node_modules/**'`. */\r\nexport function isRgExcludeScopeTarget(target: string | undefined): boolean {\r\n if (!target) return false;\r\n const t = target.trim();\r\n return t.startsWith(\"!\") && !t.includes(\"/\") && !t.endsWith(\"/**\");\r\n}\r\n\r\nfunction fixRgGlobToken(token: string): string {\r\n if (!token.startsWith(\"--glob=\")) return token;\r\n const value = token.slice(\"--glob=\".length);\r\n if (value.startsWith(\"!\") && !value.includes(\"/\") && !value.endsWith(\"/**\")) {\r\n return `--glob=${value}/**`;\r\n }\r\n return token;\r\n}\r\n\r\nfunction collectRgPositional(argv: string[]): string[] {\r\n const positional: string[] = [];\r\n for (let i = 1; i < argv.length; i += 1) {\r\n const token = argv[i];\r\n if (!token) continue;\r\n if (token === \"--\") {\r\n positional.push(...argv.slice(i + 1));\r\n break;\r\n }\r\n if (token.startsWith(\"-\")) {\r\n if (token.includes(\"=\")) continue;\r\n if (RG_OPTS_WITH_VALUE.has(token)) i += 1;\r\n continue;\r\n }\r\n positional.push(token);\r\n }\r\n return positional;\r\n}\r\n\r\nexport function normalizeRgArgv(argv: string[]): { argv: string[]; changed: boolean } {\r\n let changed = false;\r\n const out = argv.map((token) => {\r\n const fixed = fixRgGlobToken(token);\r\n if (fixed !== token) changed = true;\r\n return fixed;\r\n });\r\n const positional = collectRgPositional(out);\r\n if (positional.length === 2) {\r\n const [pattern, target] = positional;\r\n if (isSingleFileSearchTarget(target)) {\r\n return { argv: [out[0] ?? \"rg\", \"-g\", target, pattern, \".\"], changed: true };\r\n }\r\n }\r\n return { argv: out, changed };\r\n}\r\n\r\nexport function normalizeRepoSearchCommand(command: string): { command: string; changed: boolean } {\r\n const trimmed = command.trim();\r\n if (!trimmed) return { command: trimmed, changed: false };\r\n const joiner = trimmed.includes(\"&&\") ? \" && \" : trimmed.includes(\"||\") ? \" || \" : \"; \";\r\n const stages = trimmed.split(/\\s*(?:&&|\\|\\||;)\\s*/u);\r\n let changed = false;\r\n const normalizedStages = stages.map((stage) => {\r\n const argv = splitShellWords(stage.trim());\r\n if (!argv.length || !isRgArgv(argv)) return stage;\r\n const normalized = normalizeRgArgv(argv);\r\n if (normalized.changed) {\r\n changed = true;\r\n return normalized.argv.join(\" \");\r\n }\r\n return stage;\r\n });\r\n if (!changed) return { command: trimmed, changed: false };\r\n return { command: normalizedStages.join(joiner), changed: true };\r\n}\r\n\r\nexport function extractSearchMeta(meta: string): { pattern?: string; target?: string } {\r\n if (!meta) return {};\r\n const pipelineMatch = meta.match(/search\\s+\"(.+)\"\\s+in\\s+([^()]+?)(?:\\s*\\(|$)/iu);\r\n if (pipelineMatch) {\r\n return { pattern: pipelineMatch[1], target: pipelineMatch[2]?.trim() };\r\n }\r\n const match = meta.match(/^search\\s+\"(.+)\"\\s+in\\s+(.+)$/iu);\r\n if (!match) return {};\r\n return { pattern: match[1], target: match[2]?.trim() };\r\n}\r\n\r\nexport function classifyRepoSearchMeta(meta: string): RepoSearchContext {\r\n const { pattern, target } = extractSearchMeta(meta);\r\n if (!pattern) return { kind: \"not_repo_search\" };\r\n if (isRgExcludeScopeTarget(target)) {\r\n return { kind: \"rg_exclude_syntax\", pattern, target };\r\n }\r\n if (isSingleFileSearchTarget(target)) {\r\n return { kind: \"bad_scope\", pattern, target };\r\n }\r\n return { kind: \"not_repo_search\", pattern, target };\r\n}\r\n\r\nexport function metaToNormalizedRgCommand(meta: string): { command: string; changed: boolean } | null {\r\n const { pattern, target } = extractSearchMeta(meta);\r\n if (!pattern) return null;\r\n if (isRgExcludeScopeTarget(target)) {\r\n const glob = `${target!.trim()}/**`;\r\n return {\r\n command: `rg \"${pattern}\" -g '${glob}' .`,\r\n changed: true,\r\n };\r\n }\r\n if (target && isSingleFileSearchTarget(target)) {\r\n return {\r\n command: `rg -g ${target} \"${pattern}\" .`,\r\n changed: true,\r\n };\r\n }\r\n return null;\r\n}\r\n\r\nexport function formatRepoSearchGuidance(ctx: RepoSearchContext): string | null {\r\n if (\r\n ctx.kind === \"bad_scope\" &&\r\n ctx.pattern?.includes(\"agent-os-land-pr\") &&\r\n ctx.target === \"package.json\"\r\n ) {\r\n return (\r\n \"Search package.json with a glob from the repo root: \" +\r\n \"`rg -g package.json agent-os-land-pr .` \u2014 or run `node scripts/agent-os-land-pr.mjs <pr-url>` directly.\"\r\n );\r\n }\r\n if (ctx.kind === \"bad_scope\" && ctx.pattern && ctx.target) {\r\n return `Use \\`rg -g '${ctx.target}' ${ctx.pattern} .\\` from the repo root instead of treating ${ctx.target} as a folder.`;\r\n }\r\n if (ctx.kind === \"rg_exclude_syntax\" && ctx.pattern) {\r\n const glob = ctx.target ? `${ctx.target.trim()}/**` : \"!node_modules/**\";\r\n return (\r\n `Repo search scope \\`${ctx.target ?? \"!node_modules\"}\\` is not a valid ripgrep path. ` +\r\n `Use \\`rg \"${ctx.pattern}\" -g '${glob}' .\\` from the repo root (exclude globs need a \\`/**\\` suffix).`\r\n );\r\n }\r\n if (ctx.kind === \"no_matches\" && ctx.pattern) {\r\n return `No matches for \"${ctx.pattern}\". Try a broader pattern, drop overly short tokens, or search from the repo root with \\`rg \"${ctx.pattern}\" .\\`.`;\r\n }\r\n return null;\r\n}\r\n\r\n/** Extract `search \"\u2026\" in \u2026` from a tool-failure line or command string. */\r\nexport function extractSearchMetaFromToolLine(line: string): string | null {\r\n const match = line.match(/search\\s+\"(.+)\"\\s+in\\s+([^()]+?)(?:\\s*\\(agent\\)|\\s*failed|$)/iu);\r\n if (!match) return null;\r\n return `search \"${match[1]}\" in ${match[2]?.trim()}`;\r\n}\r\n\r\nexport function diagnoseRepoSearchFailure(input: {\r\n meta?: string;\r\n command?: string;\r\n exitCode?: number;\r\n}): string | null {\r\n const meta =\r\n input.meta?.trim() ||\r\n (input.command ? extractSearchMetaFromToolLine(input.command) : null) ||\r\n null;\r\n if (meta) {\r\n const ctx = classifyRepoSearchMeta(meta);\r\n const guidance = formatRepoSearchGuidance(ctx);\r\n if (guidance) return guidance;\r\n const normalized = metaToNormalizedRgCommand(meta);\r\n if (normalized?.changed) {\r\n return `Repo search used an invalid scope. Retry with: \\`${normalized.command}\\`.`;\r\n }\r\n }\r\n if (input.command && /\\b(rg|ripgrep)\\b/i.test(input.command)) {\r\n const normalized = normalizeRepoSearchCommand(input.command);\r\n if (normalized.changed) {\r\n return `Ripgrep scope may be invalid. Retry with: \\`${normalized.command}\\`.`;\r\n }\r\n if (input.exitCode === 1) {\r\n return \"Ripgrep returned no matches (exit 1). Try a broader pattern or search from the repo root.\";\r\n }\r\n }\r\n return null;\r\n}\r\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { auditWorkerEnv, scrubWorkerEnv, listForbiddenWorkerEnvKeys, isForbiddenWorkerEnvKey, } from "../worker-env.js";
|
|
2
|
+
export { DEFAULT_WORKER_PROVIDER, resolveConfiguredWorkerProvider, isClaudeFamilyProvider, taskAllowsClaudeWorker, enforceCursorWorkerProvider, preferCursorExecutor, type EnforceCursorWorkerProviderInput, } from "../worker-provider-policy.js";
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// src/worker-env.ts
|
|
2
|
+
var FORBIDDEN_WORKER_ENV_KEYS = [
|
|
3
|
+
"ANTHROPIC_API_KEY",
|
|
4
|
+
"ANALYST_API_KEY",
|
|
5
|
+
"RECRUITER_API_KEY",
|
|
6
|
+
"AUTH_SECRET",
|
|
7
|
+
"NEXTAUTH_SECRET",
|
|
8
|
+
"DATABASE_URL",
|
|
9
|
+
"PRODUCTION_DATABASE_URL",
|
|
10
|
+
"KYNVER_PRODUCTION_DATABASE_URL",
|
|
11
|
+
"REDIS_URL",
|
|
12
|
+
"GOOGLE_CLIENT_SECRET",
|
|
13
|
+
"GITHUB_CLIENT_SECRET",
|
|
14
|
+
"KYNVER_API_KEY",
|
|
15
|
+
"KYNVER_SERVICE_SECRET",
|
|
16
|
+
"KYNVER_RUNTIME_SECRET",
|
|
17
|
+
"KYNVER_CRON_SECRET",
|
|
18
|
+
"OPENCLAW_CRON_SECRET",
|
|
19
|
+
"QSTASH_TOKEN",
|
|
20
|
+
"QSTASH_CURRENT_SIGNING_KEY",
|
|
21
|
+
"QSTASH_NEXT_SIGNING_KEY",
|
|
22
|
+
"TOOL_SECRETS_KEK",
|
|
23
|
+
"TOOL_EXECUTOR_DISPATCH_SECRET",
|
|
24
|
+
"CLOUDFLARE_API_TOKEN",
|
|
25
|
+
"STRIPE_SECRET_KEY",
|
|
26
|
+
"STRIPE_WEBHOOK_SECRET",
|
|
27
|
+
"STRIPE_IDENTITY_WEBHOOK_SECRET",
|
|
28
|
+
"VOYAGE_API_KEY",
|
|
29
|
+
"PERPLEXITY_API_KEY",
|
|
30
|
+
"FRED_API_KEY",
|
|
31
|
+
"FMP_API_KEY",
|
|
32
|
+
"CURSOR_API_KEY"
|
|
33
|
+
];
|
|
34
|
+
var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
|
|
35
|
+
var FORBIDDEN_SUFFIXES = ["_SECRET", "_API_KEY"];
|
|
36
|
+
function isForbiddenWorkerEnvKey(key) {
|
|
37
|
+
if (FORBIDDEN_KEY_SET.has(key)) return true;
|
|
38
|
+
return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));
|
|
39
|
+
}
|
|
40
|
+
function listForbiddenWorkerEnvKeys(env) {
|
|
41
|
+
return Object.keys(env).filter(isForbiddenWorkerEnvKey).sort();
|
|
42
|
+
}
|
|
43
|
+
function scrubWorkerEnv(env) {
|
|
44
|
+
const next = { ...env };
|
|
45
|
+
for (const key of Object.keys(next)) {
|
|
46
|
+
if (isForbiddenWorkerEnvKey(key)) delete next[key];
|
|
47
|
+
}
|
|
48
|
+
return next;
|
|
49
|
+
}
|
|
50
|
+
function auditWorkerEnv(env) {
|
|
51
|
+
const forbiddenPresent = listForbiddenWorkerEnvKeys(env);
|
|
52
|
+
return { forbiddenPresent, safe: forbiddenPresent.length === 0 };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/config.ts
|
|
56
|
+
import { homedir as homedir2, totalmem } from "node:os";
|
|
57
|
+
import path2 from "node:path";
|
|
58
|
+
|
|
59
|
+
// src/wsl-host.ts
|
|
60
|
+
var DEFAULT_WSL_HOST_WARN_FREE_BYTES = 25 * 1024 * 1024 * 1024;
|
|
61
|
+
var DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES = 12 * 1024 * 1024 * 1024;
|
|
62
|
+
|
|
63
|
+
// src/disk-gate.ts
|
|
64
|
+
var DEFAULT_WARN_FREE_BYTES = 30 * 1024 * 1024 * 1024;
|
|
65
|
+
var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
66
|
+
|
|
67
|
+
// src/paths.ts
|
|
68
|
+
import { homedir } from "node:os";
|
|
69
|
+
import path from "node:path";
|
|
70
|
+
var LEGACY_ROOT = path.join(homedir(), ".openclaw", "harness");
|
|
71
|
+
|
|
72
|
+
// src/resource-gate.ts
|
|
73
|
+
var DEFAULT_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;
|
|
74
|
+
var DEFAULT_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;
|
|
75
|
+
|
|
76
|
+
// src/config.ts
|
|
77
|
+
var CONFIG_DIR = path2.join(homedir2(), ".kynver");
|
|
78
|
+
var CONFIG_FILE = path2.join(CONFIG_DIR, "config.json");
|
|
79
|
+
var CREDENTIALS_FILE = path2.join(CONFIG_DIR, "credentials");
|
|
80
|
+
var SETUP_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;
|
|
81
|
+
var SETUP_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;
|
|
82
|
+
|
|
83
|
+
// src/model-routing.ts
|
|
84
|
+
var CURSOR_DEFAULT_MODEL = "composer-2.5";
|
|
85
|
+
|
|
86
|
+
// src/worker-provider-policy.ts
|
|
87
|
+
var DEFAULT_WORKER_PROVIDER = "cursor";
|
|
88
|
+
var CLAUDE_FAMILY = /* @__PURE__ */ new Set(["claude", "opus", "anthropic"]);
|
|
89
|
+
var TASK_OVERRIDE_MARKERS = [
|
|
90
|
+
/\[worker-provider:\s*claude\]/i,
|
|
91
|
+
/\[use-claude-worker\]/i,
|
|
92
|
+
/\[operator-worker-provider:\s*claude\]/i
|
|
93
|
+
];
|
|
94
|
+
function taskString(task, key) {
|
|
95
|
+
const v = task[key];
|
|
96
|
+
return typeof v === "string" ? v.trim() : "";
|
|
97
|
+
}
|
|
98
|
+
function isClaudeFamilyProvider(provider) {
|
|
99
|
+
if (!provider?.trim()) return false;
|
|
100
|
+
const normalized = provider.trim().toLowerCase();
|
|
101
|
+
if (CLAUDE_FAMILY.has(normalized)) return true;
|
|
102
|
+
return normalized.includes("claude") || normalized.includes("opus");
|
|
103
|
+
}
|
|
104
|
+
function taskAllowsClaudeWorker(task) {
|
|
105
|
+
if (!task) return false;
|
|
106
|
+
const override = task.workerProviderOverride;
|
|
107
|
+
if (typeof override === "string" && isClaudeFamilyProvider(override)) return true;
|
|
108
|
+
const ref = taskString(task, "executorRef").toLowerCase();
|
|
109
|
+
if (ref === "provider:claude" || ref.startsWith("provider:claude:")) return true;
|
|
110
|
+
if (ref.includes("claude-worker-override") || ref.includes("operator-claude")) return true;
|
|
111
|
+
const description = taskString(task, "description");
|
|
112
|
+
if (TASK_OVERRIDE_MARKERS.some((re) => re.test(description))) return true;
|
|
113
|
+
const title = taskString(task, "title");
|
|
114
|
+
if (/\[use-claude-worker\]/i.test(title)) return true;
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
function coerceCursorModel(model, ruleSuffix) {
|
|
118
|
+
const coerced = {
|
|
119
|
+
provider: DEFAULT_WORKER_PROVIDER,
|
|
120
|
+
model: CURSOR_DEFAULT_MODEL,
|
|
121
|
+
rule: `policy:cursor_default${ruleSuffix}`,
|
|
122
|
+
requestedModel: model
|
|
123
|
+
};
|
|
124
|
+
return coerced;
|
|
125
|
+
}
|
|
126
|
+
function enforceCursorWorkerProvider(input) {
|
|
127
|
+
const { routing, task } = input;
|
|
128
|
+
const explicit = input.explicitProvider?.trim().toLowerCase();
|
|
129
|
+
if (input.explicitProviderIsOperatorOverride && isClaudeFamilyProvider(explicit)) {
|
|
130
|
+
return {
|
|
131
|
+
...routing,
|
|
132
|
+
provider: "claude",
|
|
133
|
+
rule: routing.rule.startsWith("explicit:") ? routing.rule : "explicit:operator_provider"
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (taskAllowsClaudeWorker(task)) {
|
|
137
|
+
return routing;
|
|
138
|
+
}
|
|
139
|
+
if (routing.rule === "explicit:cli" && isClaudeFamilyProvider(routing.provider)) {
|
|
140
|
+
return routing;
|
|
141
|
+
}
|
|
142
|
+
if (!isClaudeFamilyProvider(routing.provider)) {
|
|
143
|
+
return routing;
|
|
144
|
+
}
|
|
145
|
+
const suffix = routing.rule && routing.rule !== "default:global" ? `:${routing.rule.replace(/:/g, "_")}` : "";
|
|
146
|
+
return coerceCursorModel(routing.model, suffix);
|
|
147
|
+
}
|
|
148
|
+
function resolveConfiguredWorkerProvider(configured, fallback = DEFAULT_WORKER_PROVIDER) {
|
|
149
|
+
const trimmed = configured?.trim();
|
|
150
|
+
if (!trimmed) return fallback;
|
|
151
|
+
if (isClaudeFamilyProvider(trimmed)) return DEFAULT_WORKER_PROVIDER;
|
|
152
|
+
if (trimmed === "codex") return "codex";
|
|
153
|
+
return trimmed;
|
|
154
|
+
}
|
|
155
|
+
function preferCursorExecutor(executors) {
|
|
156
|
+
const unique = [...new Set(executors.map((e) => e.trim().toLowerCase()).filter(Boolean))];
|
|
157
|
+
if (unique.includes(DEFAULT_WORKER_PROVIDER)) {
|
|
158
|
+
return [...new Set(unique.map((e) => isClaudeFamilyProvider(e) ? DEFAULT_WORKER_PROVIDER : e))];
|
|
159
|
+
}
|
|
160
|
+
if (unique.every((e) => isClaudeFamilyProvider(e))) {
|
|
161
|
+
return [DEFAULT_WORKER_PROVIDER];
|
|
162
|
+
}
|
|
163
|
+
return unique;
|
|
164
|
+
}
|
|
165
|
+
export {
|
|
166
|
+
DEFAULT_WORKER_PROVIDER,
|
|
167
|
+
auditWorkerEnv,
|
|
168
|
+
enforceCursorWorkerProvider,
|
|
169
|
+
isClaudeFamilyProvider,
|
|
170
|
+
isForbiddenWorkerEnvKey,
|
|
171
|
+
listForbiddenWorkerEnvKeys,
|
|
172
|
+
preferCursorExecutor,
|
|
173
|
+
resolveConfiguredWorkerProvider,
|
|
174
|
+
scrubWorkerEnv,
|
|
175
|
+
taskAllowsClaudeWorker
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=worker-policy.js.map
|