unbrowse 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +455 -96
- package/dist/index.js +2 -6
- package/dist/mcp.js +695 -46
- package/dist/server.js +25811 -0
- package/package.json +1 -2
- package/vendor/kuri/darwin-arm64/kuri +0 -0
- package/vendor/kuri/darwin-x64/kuri +0 -0
- package/vendor/kuri/linux-arm64/kuri +0 -0
- package/vendor/kuri/linux-x64/kuri +0 -0
- package/vendor/kuri/manifest.json +7 -10
- package/runtime-src/agent-outcome.ts +0 -166
- package/runtime-src/analytics-session.ts +0 -55
- package/runtime-src/api/browse-index.ts +0 -317
- package/runtime-src/api/browse-session.ts +0 -572
- package/runtime-src/api/browse-submit-prereqs.ts +0 -48
- package/runtime-src/api/browse-submit.ts +0 -1184
- package/runtime-src/api/routes.ts +0 -1823
- package/runtime-src/auth/browser-cookies.ts +0 -423
- package/runtime-src/auth/index.ts +0 -535
- package/runtime-src/auth/runtime.ts +0 -116
- package/runtime-src/browser/index.ts +0 -659
- package/runtime-src/browser/types.ts +0 -41
- package/runtime-src/build-info.generated.ts +0 -6
- package/runtime-src/capture/index.ts +0 -1794
- package/runtime-src/capture/prefetch.ts +0 -95
- package/runtime-src/capture/rsc.ts +0 -45
- package/runtime-src/cli/shortcuts.ts +0 -273
- package/runtime-src/cli.ts +0 -1572
- package/runtime-src/client/graph-client.ts +0 -100
- package/runtime-src/client/index.ts +0 -1425
- package/runtime-src/debug-trace.ts +0 -18
- package/runtime-src/domain.ts +0 -38
- package/runtime-src/execution/index.ts +0 -3397
- package/runtime-src/execution/retry.ts +0 -46
- package/runtime-src/execution/robots.ts +0 -167
- package/runtime-src/execution/search-forms.ts +0 -188
- package/runtime-src/extraction/index.ts +0 -1507
- package/runtime-src/foundry/publish-bundle.ts +0 -392
- package/runtime-src/graph/agent-augment.ts +0 -315
- package/runtime-src/graph/index.ts +0 -1524
- package/runtime-src/graph/local-fixtures.ts +0 -393
- package/runtime-src/graph/local-harness.ts +0 -646
- package/runtime-src/graph/planner.ts +0 -411
- package/runtime-src/graph/session.ts +0 -294
- package/runtime-src/graph/trace-store.ts +0 -136
- package/runtime-src/index.ts +0 -24
- package/runtime-src/indexer/index.ts +0 -465
- package/runtime-src/intent-match.ts +0 -1515
- package/runtime-src/kuri/client.ts +0 -1839
- package/runtime-src/logger.ts +0 -30
- package/runtime-src/marketplace/index.ts +0 -103
- package/runtime-src/mcp.ts +0 -1747
- package/runtime-src/orchestrator/browser-agent.ts +0 -374
- package/runtime-src/orchestrator/dag-advisor.ts +0 -59
- package/runtime-src/orchestrator/dag-feedback.ts +0 -257
- package/runtime-src/orchestrator/first-pass-action.ts +0 -403
- package/runtime-src/orchestrator/index.ts +0 -4480
- package/runtime-src/orchestrator/passive-publish.ts +0 -187
- package/runtime-src/orchestrator/timing-economics.ts +0 -80
- package/runtime-src/payments/cascade.ts +0 -137
- package/runtime-src/payments/index.ts +0 -270
- package/runtime-src/payments/lobster-pay.ts +0 -182
- package/runtime-src/payments/wallet.ts +0 -98
- package/runtime-src/publish/review-context.ts +0 -93
- package/runtime-src/publish/sanitize.ts +0 -197
- package/runtime-src/publish/schema-review.ts +0 -192
- package/runtime-src/publish-admission.ts +0 -388
- package/runtime-src/ratelimit/index.ts +0 -23
- package/runtime-src/reverse-engineer/bundle-scanner.ts +0 -127
- package/runtime-src/reverse-engineer/description-prompt.ts +0 -213
- package/runtime-src/reverse-engineer/index.ts +0 -1551
- package/runtime-src/router.ts +0 -17
- package/runtime-src/routing-telemetry.ts +0 -395
- package/runtime-src/runtime/browser-access.ts +0 -11
- package/runtime-src/runtime/browser-auth.ts +0 -12
- package/runtime-src/runtime/browser-host.ts +0 -48
- package/runtime-src/runtime/lifecycle.ts +0 -17
- package/runtime-src/runtime/local-server.ts +0 -311
- package/runtime-src/runtime/paths.ts +0 -99
- package/runtime-src/runtime/setup.ts +0 -251
- package/runtime-src/runtime/supervisor.ts +0 -69
- package/runtime-src/runtime/update-hints.ts +0 -351
- package/runtime-src/server.ts +0 -100
- package/runtime-src/session-logs.ts +0 -142
- package/runtime-src/settings.ts +0 -221
- package/runtime-src/single-binary.ts +0 -143
- package/runtime-src/site-policy.ts +0 -54
- package/runtime-src/stale-cleanup-runner.ts +0 -144
- package/runtime-src/stale-cleanup.ts +0 -133
- package/runtime-src/telemetry-attribution.ts +0 -120
- package/runtime-src/telemetry.ts +0 -253
- package/runtime-src/template-params.ts +0 -141
- package/runtime-src/transform/drift.ts +0 -60
- package/runtime-src/transform/index.ts +0 -277
- package/runtime-src/types/index.ts +0 -1
- package/runtime-src/types/skill.ts +0 -912
- package/runtime-src/vault/index.ts +0 -196
- package/runtime-src/verification/auth-gate.ts +0 -8
- package/runtime-src/verification/candidates.ts +0 -27
- package/runtime-src/verification/index.ts +0 -120
- package/runtime-src/verification/matrix.ts +0 -30
- package/runtime-src/version.ts +0 -148
- package/runtime-src/workflow/artifact.ts +0 -161
- package/runtime-src/workflow/compile.ts +0 -808
- package/runtime-src/workflow/publish.ts +0 -225
- package/runtime-src/workflow/runtime.ts +0 -213
- package/vendor/kuri/win-x64/kuri.exe +0 -0
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { log } from "../logger.js";
|
|
5
|
-
import { sanitizeForPublish } from "../publish/sanitize.js";
|
|
6
|
-
import type {
|
|
7
|
-
EndpointDescriptor,
|
|
8
|
-
SkillManifest,
|
|
9
|
-
WorkflowArtifact,
|
|
10
|
-
WorkflowPublishArtifact,
|
|
11
|
-
WorkflowPublishRecipe,
|
|
12
|
-
WorkflowPublishStatus,
|
|
13
|
-
} from "../types/index.js";
|
|
14
|
-
|
|
15
|
-
function sanitizeProfileName(value: string): string {
|
|
16
|
-
return value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function getConfigDir(): string {
|
|
20
|
-
if (process.env.UNBROWSE_CONFIG_DIR) return process.env.UNBROWSE_CONFIG_DIR;
|
|
21
|
-
const profile = sanitizeProfileName(process.env.UNBROWSE_PROFILE ?? "");
|
|
22
|
-
return profile
|
|
23
|
-
? join(homedir(), ".unbrowse", "profiles", profile)
|
|
24
|
-
: join(homedir(), ".unbrowse");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function getWorkflowExportDir(): string {
|
|
28
|
-
return process.env.UNBROWSE_WORKFLOW_EXPORT_DIR
|
|
29
|
-
?? join(getConfigDir(), "workflow-exports");
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function workflowPublishArtifactPathForSkill(skillId: string): string {
|
|
33
|
-
return join(getWorkflowExportDir(), `${skillId}.json`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function readWorkflowPublishArtifact(skillId: string): WorkflowPublishArtifact | null {
|
|
37
|
-
const target = workflowPublishArtifactPathForSkill(skillId);
|
|
38
|
-
if (!existsSync(target)) return null;
|
|
39
|
-
try {
|
|
40
|
-
return JSON.parse(readFileSync(target, "utf-8")) as WorkflowPublishArtifact;
|
|
41
|
-
} catch {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function listWorkflowPublishArtifacts(): string[] {
|
|
47
|
-
const dir = getWorkflowExportDir();
|
|
48
|
-
if (!existsSync(dir)) return [];
|
|
49
|
-
return readdirSync(dir)
|
|
50
|
-
.filter((entry) => entry.endsWith(".json"))
|
|
51
|
-
.map((entry) => join(dir, entry));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function stripQuery(url?: string): string | undefined {
|
|
55
|
-
if (!url) return undefined;
|
|
56
|
-
try {
|
|
57
|
-
const parsed = new URL(url);
|
|
58
|
-
return `${parsed.origin}${parsed.pathname}`;
|
|
59
|
-
} catch {
|
|
60
|
-
return url;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function docBullets(
|
|
65
|
-
skill: SkillManifest,
|
|
66
|
-
endpoints: EndpointDescriptor[],
|
|
67
|
-
workflowArtifact: WorkflowArtifact | null,
|
|
68
|
-
rootEndpointIds: string[],
|
|
69
|
-
): string[] {
|
|
70
|
-
const bullets = [
|
|
71
|
-
`${endpoints.length} publishable endpoint${endpoints.length === 1 ? "" : "s"} for ${skill.intent_signature}`,
|
|
72
|
-
];
|
|
73
|
-
if (rootEndpointIds.length > 0) {
|
|
74
|
-
const closureAdded = Math.max(0, endpoints.length - rootEndpointIds.length);
|
|
75
|
-
bullets.push(
|
|
76
|
-
closureAdded > 0
|
|
77
|
-
? `publish closure: ${rootEndpointIds.length} root step${rootEndpointIds.length === 1 ? "" : "s"} + ${closureAdded} DAG-linked dependent step${closureAdded === 1 ? "" : "s"}`
|
|
78
|
-
: `publish closure: ${rootEndpointIds.length} root step${rootEndpointIds.length === 1 ? "" : "s"}`,
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
if (workflowArtifact) {
|
|
82
|
-
bullets.push(`${workflowArtifact.recipes.length} workflow recipe${workflowArtifact.recipes.length === 1 ? "" : "s"} captured`);
|
|
83
|
-
const preferred = workflowArtifact.recipes.filter((recipe) => recipe.preferred).map((recipe) => recipe.endpoint_id);
|
|
84
|
-
if (preferred.length > 0) bullets.push(`preferred routes: ${preferred.join(", ")}`);
|
|
85
|
-
const guarded = workflowArtifact.recipes.filter((recipe) => recipe.mutation_guard.confirm_unsafe_required).length;
|
|
86
|
-
if (guarded > 0) bullets.push(`${guarded} mutation recipe${guarded === 1 ? "" : "s"} require confirm_unsafe`);
|
|
87
|
-
const preferredRecipe = workflowArtifact.recipes.find((recipe) => recipe.preferred) ?? workflowArtifact.recipes[0];
|
|
88
|
-
const preferredUsage = preferredRecipe ? usageNotesForRecipe(preferredRecipe)[0] : undefined;
|
|
89
|
-
if (preferredUsage) bullets.push(preferredUsage);
|
|
90
|
-
} else {
|
|
91
|
-
bullets.push("no workflow artifact captured yet");
|
|
92
|
-
}
|
|
93
|
-
return bullets;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function usageNotesForRecipe(recipe: WorkflowArtifact["recipes"][number]): string[] {
|
|
97
|
-
const notes: string[] = [];
|
|
98
|
-
const requiredUserParams = recipe.replay_contract.parameter_specs
|
|
99
|
-
.filter((param) => param.user_supplied && param.required)
|
|
100
|
-
.map((param) => `${param.name}:${param.type}${param.enum_values?.length ? `=${param.enum_values.join("|")}` : ""}`);
|
|
101
|
-
if (requiredUserParams.length > 0) {
|
|
102
|
-
notes.push(`params: ${requiredUserParams.join(", ")}`);
|
|
103
|
-
}
|
|
104
|
-
const derivedParams = recipe.replay_contract.parameter_specs
|
|
105
|
-
.filter((param) => !param.user_supplied)
|
|
106
|
-
.map((param) => `${param.name}<=${param.derived_from?.[0] ?? "observed state"}`);
|
|
107
|
-
if (derivedParams.length > 0) {
|
|
108
|
-
notes.push(`derived: ${derivedParams.slice(0, 4).join(", ")}`);
|
|
109
|
-
}
|
|
110
|
-
const prereqs = recipe.replay_contract.prerequisite_specs
|
|
111
|
-
.filter((entry) => entry.required)
|
|
112
|
-
.map((entry) => entry.name);
|
|
113
|
-
if (prereqs.length > 0) {
|
|
114
|
-
notes.push(`prereqs: ${prereqs.slice(0, 4).join(", ")}`);
|
|
115
|
-
}
|
|
116
|
-
const payment = recipe.replay_contract.payment_requirement;
|
|
117
|
-
if (payment.status === "x402_required") {
|
|
118
|
-
notes.push(`payment: x402 ${payment.price_usd ?? "dynamic"} ${payment.currency ?? "USDC"}`);
|
|
119
|
-
} else if (payment.status === "x402_optional") {
|
|
120
|
-
notes.push("payment: x402 may apply when marketplace pricing is configured");
|
|
121
|
-
} else {
|
|
122
|
-
notes.push("payment: free");
|
|
123
|
-
}
|
|
124
|
-
notes.push("replay: explicit only; traversal stays browser-native");
|
|
125
|
-
return notes;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function buildPublishedRecipes(workflowArtifact: WorkflowArtifact | null): WorkflowPublishRecipe[] {
|
|
129
|
-
if (!workflowArtifact) return [];
|
|
130
|
-
return workflowArtifact.recipes.map((recipe) => ({
|
|
131
|
-
endpoint_id: recipe.endpoint_id,
|
|
132
|
-
operation_id: recipe.operation_id,
|
|
133
|
-
preferred: recipe.preferred,
|
|
134
|
-
provenance_backed: recipe.provenance_backed,
|
|
135
|
-
last_successful_strategy: recipe.last_successful_strategy,
|
|
136
|
-
steps: recipe.steps.map((step) => ({
|
|
137
|
-
strategy: step.strategy,
|
|
138
|
-
provenance: step.provenance,
|
|
139
|
-
trigger_url: stripQuery(step.trigger_url),
|
|
140
|
-
action_count: step.action_sequence?.length ?? 0,
|
|
141
|
-
})),
|
|
142
|
-
mutation_guard: recipe.mutation_guard,
|
|
143
|
-
token_bindings: recipe.token_bindings.map((binding) => ({
|
|
144
|
-
target_location: binding.target_location,
|
|
145
|
-
target_name: binding.target_name,
|
|
146
|
-
refresh_on_statuses: binding.refresh_on_statuses,
|
|
147
|
-
selected_source_kind: binding.selected_source_kind,
|
|
148
|
-
selected_source_name: binding.selected_source_name,
|
|
149
|
-
candidates: binding.candidates.map((candidate) => ({
|
|
150
|
-
source_kind: candidate.source_kind,
|
|
151
|
-
source_name: candidate.source_name,
|
|
152
|
-
source_path: candidate.source_path,
|
|
153
|
-
confidence: candidate.confidence,
|
|
154
|
-
})),
|
|
155
|
-
})),
|
|
156
|
-
replay_contract: recipe.replay_contract,
|
|
157
|
-
usage_notes: usageNotesForRecipe(recipe),
|
|
158
|
-
}));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function buildWorkflowPublishArtifact(
|
|
162
|
-
skill: SkillManifest,
|
|
163
|
-
workflowArtifact: WorkflowArtifact | null,
|
|
164
|
-
options?: {
|
|
165
|
-
publishStatus?: WorkflowPublishStatus;
|
|
166
|
-
publishedAt?: string;
|
|
167
|
-
validationErrors?: string[];
|
|
168
|
-
endpointIds?: string[];
|
|
169
|
-
rootEndpointIds?: string[];
|
|
170
|
-
},
|
|
171
|
-
): WorkflowPublishArtifact {
|
|
172
|
-
const includeEndpointIds = new Set(options?.endpointIds ?? skill.endpoints.map((endpoint) => endpoint.endpoint_id));
|
|
173
|
-
const rootEndpointIds = (options?.rootEndpointIds ?? []).filter((endpointId) => includeEndpointIds.has(endpointId));
|
|
174
|
-
const includedEndpoints = skill.endpoints.filter((endpoint) => includeEndpointIds.has(endpoint.endpoint_id));
|
|
175
|
-
const sanitizedEndpoints = sanitizeForPublish(includedEndpoints.filter((endpoint) => endpoint.method !== "WS"));
|
|
176
|
-
const recipes = buildPublishedRecipes(workflowArtifact).filter((recipe) => includeEndpointIds.has(recipe.endpoint_id));
|
|
177
|
-
const tokenBindingCount = recipes.reduce((sum, recipe) => sum + recipe.token_bindings.length, 0);
|
|
178
|
-
const closureAddedCount = rootEndpointIds.length > 0
|
|
179
|
-
? Math.max(0, sanitizedEndpoints.length - rootEndpointIds.length)
|
|
180
|
-
: 0;
|
|
181
|
-
return {
|
|
182
|
-
export_version: "1",
|
|
183
|
-
generated_at: new Date().toISOString(),
|
|
184
|
-
publish_status: options?.publishStatus ?? "captured",
|
|
185
|
-
...(options?.publishedAt ? { published_at: options.publishedAt } : {}),
|
|
186
|
-
...(options?.validationErrors?.length ? { validation_errors: options.validationErrors } : {}),
|
|
187
|
-
skill_id: skill.skill_id,
|
|
188
|
-
domain: skill.domain,
|
|
189
|
-
intent_signature: skill.intent_signature,
|
|
190
|
-
sanitized_endpoints: sanitizedEndpoints,
|
|
191
|
-
workflow_summary: {
|
|
192
|
-
recipe_count: recipes.length,
|
|
193
|
-
preferred_endpoint_ids: recipes.filter((recipe) => recipe.preferred).map((recipe) => recipe.endpoint_id),
|
|
194
|
-
authenticated_capture: workflowArtifact?.auth_state.authenticated ?? false,
|
|
195
|
-
token_binding_count: tokenBindingCount,
|
|
196
|
-
trigger_url_count: workflowArtifact?.evidence.trigger_urls.length ?? 0,
|
|
197
|
-
included_endpoint_count: sanitizedEndpoints.length,
|
|
198
|
-
root_endpoint_ids: rootEndpointIds,
|
|
199
|
-
closure_added_count: closureAddedCount,
|
|
200
|
-
},
|
|
201
|
-
auth_summary: workflowArtifact?.auth_state ?? {
|
|
202
|
-
cookie_names: [],
|
|
203
|
-
header_names: [],
|
|
204
|
-
authenticated: false,
|
|
205
|
-
},
|
|
206
|
-
recipes,
|
|
207
|
-
docs: {
|
|
208
|
-
headline: `${skill.domain} workflow export`,
|
|
209
|
-
bullets: docBullets(skill, sanitizedEndpoints, workflowArtifact, rootEndpointIds),
|
|
210
|
-
},
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export function writeWorkflowPublishArtifact(artifact: WorkflowPublishArtifact): string | undefined {
|
|
215
|
-
try {
|
|
216
|
-
const dir = getWorkflowExportDir();
|
|
217
|
-
mkdirSync(dir, { recursive: true });
|
|
218
|
-
const target = workflowPublishArtifactPathForSkill(artifact.skill_id);
|
|
219
|
-
writeFileSync(target, JSON.stringify(artifact, null, 2), "utf-8");
|
|
220
|
-
return target;
|
|
221
|
-
} catch (error) {
|
|
222
|
-
log("workflow", `publish export write failed for ${artifact.skill_id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
223
|
-
return undefined;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
TokenBinding,
|
|
3
|
-
WorkflowArtifact,
|
|
4
|
-
WorkflowParameterSpec,
|
|
5
|
-
WorkflowRecipe,
|
|
6
|
-
WorkflowStep,
|
|
7
|
-
WorkflowStepStrategy,
|
|
8
|
-
} from "../types/index.js";
|
|
9
|
-
|
|
10
|
-
export interface WorkflowRuntimeContext {
|
|
11
|
-
cookies: Array<{ name: string; value: string; domain: string }>;
|
|
12
|
-
authHeaders: Record<string, string>;
|
|
13
|
-
body?: unknown;
|
|
14
|
-
artifact: WorkflowArtifact;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface ResolvedWorkflowBindings {
|
|
18
|
-
extraHeaders: Record<string, string>;
|
|
19
|
-
bodyOverride?: unknown;
|
|
20
|
-
selectedBindings: Array<{ target_name: string; source_kind: string; source_name: string }>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface WorkflowParamValidationError {
|
|
24
|
-
name: string;
|
|
25
|
-
reason: string;
|
|
26
|
-
allowed_values?: Array<string | number | boolean>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function cloneBody(body: unknown): unknown {
|
|
30
|
-
if (!body || typeof body !== "object") return body;
|
|
31
|
-
if (Array.isArray(body)) return body.map((entry) => cloneBody(entry));
|
|
32
|
-
return { ...(body as Record<string, unknown>) };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function resolveBindingValue(
|
|
36
|
-
binding: TokenBinding,
|
|
37
|
-
context: WorkflowRuntimeContext,
|
|
38
|
-
): { value?: string; source_kind?: string; source_name?: string } {
|
|
39
|
-
for (const candidate of binding.candidates) {
|
|
40
|
-
if (candidate.source_kind === "cookie") {
|
|
41
|
-
const cookie = context.cookies.find((entry) => entry.name.toLowerCase() === candidate.source_name.toLowerCase());
|
|
42
|
-
if (cookie?.value != null) return { value: cookie.value, source_kind: candidate.source_kind, source_name: candidate.source_name };
|
|
43
|
-
}
|
|
44
|
-
if (candidate.source_kind === "request_header" || candidate.source_kind === "response_header") {
|
|
45
|
-
const header = Object.entries(context.authHeaders).find(([key]) => key.toLowerCase() === candidate.source_name.toLowerCase());
|
|
46
|
-
if (header?.[1] != null) return { value: header[1], source_kind: candidate.source_kind, source_name: candidate.source_name };
|
|
47
|
-
}
|
|
48
|
-
if (candidate.source_kind === "hidden_input") {
|
|
49
|
-
const hint = context.artifact.evidence.dom_form_hints.find((entry) => entry.field_name.toLowerCase() === candidate.source_name.toLowerCase());
|
|
50
|
-
if (hint?.value != null) return { value: hint.value, source_kind: candidate.source_kind, source_name: candidate.source_name };
|
|
51
|
-
}
|
|
52
|
-
if (candidate.source_kind === "meta") {
|
|
53
|
-
const hint = context.artifact.evidence.meta_hints.find((entry) => entry.key.toLowerCase() === candidate.source_name.toLowerCase());
|
|
54
|
-
if (hint?.value != null) return { value: hint.value, source_kind: candidate.source_kind, source_name: candidate.source_name };
|
|
55
|
-
}
|
|
56
|
-
if (candidate.source_kind === "bootstrap_json") {
|
|
57
|
-
const hint = context.artifact.evidence.bootstrap_hints.find((entry) => entry.path === candidate.source_path || entry.path.toLowerCase().endsWith(candidate.source_name.toLowerCase()));
|
|
58
|
-
if (hint?.value != null) return { value: hint.value, source_kind: candidate.source_kind, source_name: candidate.source_name };
|
|
59
|
-
}
|
|
60
|
-
if (candidate.source_kind === "request_body" && candidate.observed_value != null) {
|
|
61
|
-
return { value: candidate.observed_value, source_kind: candidate.source_kind, source_name: candidate.source_name };
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return {};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function resolveWorkflowBindings(
|
|
68
|
-
recipe: WorkflowRecipe,
|
|
69
|
-
context: WorkflowRuntimeContext,
|
|
70
|
-
): ResolvedWorkflowBindings {
|
|
71
|
-
const extraHeaders: Record<string, string> = {};
|
|
72
|
-
let bodyOverride = cloneBody(context.body);
|
|
73
|
-
const selectedBindings: ResolvedWorkflowBindings["selectedBindings"] = [];
|
|
74
|
-
|
|
75
|
-
for (const binding of recipe.token_bindings) {
|
|
76
|
-
const resolved = resolveBindingValue(binding, context);
|
|
77
|
-
if (resolved.value == null || !resolved.source_kind || !resolved.source_name) continue;
|
|
78
|
-
selectedBindings.push({
|
|
79
|
-
target_name: binding.target_name,
|
|
80
|
-
source_kind: resolved.source_kind,
|
|
81
|
-
source_name: resolved.source_name,
|
|
82
|
-
});
|
|
83
|
-
if (binding.target_location === "header") {
|
|
84
|
-
extraHeaders[binding.target_name.toLowerCase()] = resolved.value;
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (!bodyOverride || typeof bodyOverride !== "object" || Array.isArray(bodyOverride)) {
|
|
88
|
-
bodyOverride = {};
|
|
89
|
-
}
|
|
90
|
-
(bodyOverride as Record<string, unknown>)[binding.target_name] = resolved.value;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return { extraHeaders, bodyOverride, selectedBindings };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function valueForSpec(
|
|
97
|
-
spec: WorkflowParameterSpec,
|
|
98
|
-
params: Record<string, unknown>,
|
|
99
|
-
): unknown {
|
|
100
|
-
const value = params[spec.name];
|
|
101
|
-
return value === undefined ? spec.default_value : value;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function matchesSpecType(spec: WorkflowParameterSpec, value: unknown): boolean {
|
|
105
|
-
if (value == null) return true;
|
|
106
|
-
switch (spec.type) {
|
|
107
|
-
case "boolean":
|
|
108
|
-
return typeof value === "boolean" || value === "true" || value === "false";
|
|
109
|
-
case "integer":
|
|
110
|
-
return typeof value === "number"
|
|
111
|
-
? Number.isInteger(value)
|
|
112
|
-
: typeof value === "string" && /^-?\d+$/.test(value);
|
|
113
|
-
case "number":
|
|
114
|
-
return typeof value === "number"
|
|
115
|
-
? Number.isFinite(value)
|
|
116
|
-
: typeof value === "string" && /^-?\d+(\.\d+)?$/.test(value);
|
|
117
|
-
case "array":
|
|
118
|
-
return Array.isArray(value);
|
|
119
|
-
case "object":
|
|
120
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
121
|
-
case "string":
|
|
122
|
-
default:
|
|
123
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function matchesSpecFormat(spec: WorkflowParameterSpec, value: unknown): boolean {
|
|
128
|
-
if (value == null || spec.format == null) return true;
|
|
129
|
-
const sample = String(value);
|
|
130
|
-
switch (spec.format) {
|
|
131
|
-
case "date":
|
|
132
|
-
return /^\d{4}-\d{2}-\d{2}$/.test(sample);
|
|
133
|
-
case "url":
|
|
134
|
-
return /^https?:\/\//i.test(sample);
|
|
135
|
-
case "email":
|
|
136
|
-
return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(sample);
|
|
137
|
-
default:
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function validateWorkflowReplayParams(
|
|
143
|
-
recipe: WorkflowRecipe,
|
|
144
|
-
params: Record<string, unknown>,
|
|
145
|
-
): WorkflowParamValidationError[] {
|
|
146
|
-
const errors: WorkflowParamValidationError[] = [];
|
|
147
|
-
for (const spec of recipe.replay_contract.parameter_specs) {
|
|
148
|
-
if (!spec.user_supplied) continue;
|
|
149
|
-
const value = valueForSpec(spec, params);
|
|
150
|
-
if (spec.required && (value == null || value === "")) {
|
|
151
|
-
errors.push({ name: spec.name, reason: "required" });
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
if (value == null || value === "") continue;
|
|
155
|
-
if (spec.enum_values?.length && !spec.enum_values.some((candidate) => String(candidate) === String(value))) {
|
|
156
|
-
errors.push({
|
|
157
|
-
name: spec.name,
|
|
158
|
-
reason: "invalid_enum",
|
|
159
|
-
allowed_values: spec.enum_values,
|
|
160
|
-
});
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
if (!matchesSpecType(spec, value)) {
|
|
164
|
-
errors.push({ name: spec.name, reason: `expected_${spec.type}` });
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
if (!matchesSpecFormat(spec, value)) {
|
|
168
|
-
errors.push({ name: spec.name, reason: `expected_format_${spec.format}` });
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return errors;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export function needsWorkflowTokenRefresh(status: number): boolean {
|
|
175
|
-
return status === 401 || status === 403 || status === 419 || status === 422;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export function pickWorkflowRecipe(
|
|
179
|
-
artifact: WorkflowArtifact | null,
|
|
180
|
-
endpointId: string,
|
|
181
|
-
): WorkflowRecipe | null {
|
|
182
|
-
if (!artifact) return null;
|
|
183
|
-
const exact = artifact.recipes.find((recipe) => recipe.endpoint_id === endpointId);
|
|
184
|
-
if (!exact) return null;
|
|
185
|
-
const preferredStrategy = exact.last_successful_strategy;
|
|
186
|
-
const steps = preferredStrategy
|
|
187
|
-
? [...exact.steps].sort((lhs, rhs) => {
|
|
188
|
-
if (lhs.strategy === preferredStrategy) return -1;
|
|
189
|
-
if (rhs.strategy === preferredStrategy) return 1;
|
|
190
|
-
return 0;
|
|
191
|
-
})
|
|
192
|
-
: exact.steps;
|
|
193
|
-
return { ...exact, steps };
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export function translateWorkflowStrategy(
|
|
197
|
-
strategy: WorkflowStepStrategy,
|
|
198
|
-
): "server" | "trigger-intercept" | "browser" {
|
|
199
|
-
switch (strategy) {
|
|
200
|
-
case "server":
|
|
201
|
-
return "server";
|
|
202
|
-
case "trigger-intercept":
|
|
203
|
-
return "trigger-intercept";
|
|
204
|
-
case "browser-action":
|
|
205
|
-
case "browser-fetch":
|
|
206
|
-
default:
|
|
207
|
-
return "browser";
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export function strategyLabel(step: WorkflowStep): string {
|
|
212
|
-
return step.strategy;
|
|
213
|
-
}
|
|
Binary file
|