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,187 +0,0 @@
|
|
|
1
|
-
import { cachePublishedSkill, validateManifest, publishGraphEdges } from "../client/index.js";
|
|
2
|
-
import {
|
|
3
|
-
formatMarketplacePublishSelection,
|
|
4
|
-
selectMarketplacePublishClosure,
|
|
5
|
-
} from "../publish-admission.js";
|
|
6
|
-
import { attributeLifecycle, type LifecycleEvent } from "../runtime/lifecycle.js";
|
|
7
|
-
import { publishSkill } from "../marketplace/index.js";
|
|
8
|
-
import type { SkillManifest } from "../types/index.js";
|
|
9
|
-
import { readWorkflowArtifact } from "../workflow/artifact.js";
|
|
10
|
-
import { buildWorkflowPublishArtifact, writeWorkflowPublishArtifact } from "../workflow/publish.js";
|
|
11
|
-
|
|
12
|
-
type PassivePublishDeps = {
|
|
13
|
-
cachePublishedSkill: typeof cachePublishedSkill;
|
|
14
|
-
publishSkill: typeof publishSkill;
|
|
15
|
-
validateManifest: typeof validateManifest;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export type PassiveParityVerdict = "pass" | "fail" | "skip";
|
|
19
|
-
|
|
20
|
-
type PassivePublishOptions = {
|
|
21
|
-
deps?: PassivePublishDeps;
|
|
22
|
-
parity?: PassiveParityVerdict | Promise<PassiveParityVerdict> | (() => Promise<PassiveParityVerdict>);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const defaultDeps: PassivePublishDeps = {
|
|
26
|
-
cachePublishedSkill,
|
|
27
|
-
publishSkill,
|
|
28
|
-
validateManifest,
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const passivePublishInFlight = new Map<string, Promise<void>>();
|
|
32
|
-
|
|
33
|
-
function mergeBackendDescriptions(
|
|
34
|
-
localSkill: SkillManifest,
|
|
35
|
-
publishedSkill: SkillManifest,
|
|
36
|
-
): SkillManifest["endpoints"] {
|
|
37
|
-
return localSkill.endpoints.map((endpoint) => {
|
|
38
|
-
const backendEndpoint = publishedSkill.endpoints.find(
|
|
39
|
-
(candidate) =>
|
|
40
|
-
candidate.endpoint_id === endpoint.endpoint_id ||
|
|
41
|
-
(candidate.method === endpoint.method && candidate.url_template === endpoint.url_template),
|
|
42
|
-
);
|
|
43
|
-
if (!backendEndpoint?.description) return endpoint;
|
|
44
|
-
return {
|
|
45
|
-
...endpoint,
|
|
46
|
-
description: backendEndpoint.description,
|
|
47
|
-
};
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function queuePassiveSkillPublish(
|
|
52
|
-
skill: SkillManifest,
|
|
53
|
-
options: PassivePublishOptions = {},
|
|
54
|
-
): Promise<void> {
|
|
55
|
-
const deps = options.deps ?? defaultDeps;
|
|
56
|
-
const existing = passivePublishInFlight.get(skill.skill_id);
|
|
57
|
-
if (existing) return existing;
|
|
58
|
-
|
|
59
|
-
const job = (async () => {
|
|
60
|
-
if (skill.execution_type !== "http") return;
|
|
61
|
-
|
|
62
|
-
const parityVerdict =
|
|
63
|
-
typeof options.parity === "function"
|
|
64
|
-
? await options.parity()
|
|
65
|
-
: options.parity instanceof Promise
|
|
66
|
-
? await options.parity
|
|
67
|
-
: options.parity;
|
|
68
|
-
if (parityVerdict === "fail") {
|
|
69
|
-
console.warn(`[publish] passive publish skipped for ${skill.skill_id}: parity_failed`);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const selection = selectMarketplacePublishClosure(skill);
|
|
74
|
-
if (selection.endpoints.length === 0) {
|
|
75
|
-
console.warn(
|
|
76
|
-
`[publish] passive publish skipped for ${skill.skill_id}: no admitted endpoints (${formatMarketplacePublishSelection(selection)})`,
|
|
77
|
-
);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const { operation_graph: _graph, ...publishBase } = skill;
|
|
82
|
-
const publishDraft: SkillManifest = {
|
|
83
|
-
...publishBase,
|
|
84
|
-
endpoints: selection.endpoints,
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const validation = await deps.validateManifest({ ...publishDraft, skill_id: "__validate__" });
|
|
88
|
-
if (!validation.valid) {
|
|
89
|
-
writeWorkflowPublishArtifact(buildWorkflowPublishArtifact(
|
|
90
|
-
skill,
|
|
91
|
-
readWorkflowArtifact(skill.skill_id),
|
|
92
|
-
{
|
|
93
|
-
publishStatus: "blocked-validation",
|
|
94
|
-
validationErrors: validation.hardErrors,
|
|
95
|
-
endpointIds: selection.closure_endpoint_ids,
|
|
96
|
-
rootEndpointIds: selection.root_endpoint_ids,
|
|
97
|
-
},
|
|
98
|
-
));
|
|
99
|
-
console.warn(
|
|
100
|
-
`[publish] passive publish skipped for ${skill.skill_id}: ${validation.hardErrors.join("; ") || "validation failed"}`,
|
|
101
|
-
);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const publishStart = Date.now();
|
|
106
|
-
const published = await deps.publishSkill(publishDraft);
|
|
107
|
-
const publishMs = Date.now() - publishStart;
|
|
108
|
-
console.log(
|
|
109
|
-
`[publish] passive publish admitted ${selection.endpoints.length}/${selection.stats.total} endpoint(s) for ${skill.skill_id} (${selection.root_endpoint_ids.length} roots, ${selection.endpoints.length - selection.root_endpoint_ids.length} closure) (${formatMarketplacePublishSelection(selection)})`,
|
|
110
|
-
);
|
|
111
|
-
deps.cachePublishedSkill({
|
|
112
|
-
...skill,
|
|
113
|
-
...published,
|
|
114
|
-
endpoints: mergeBackendDescriptions(skill, published),
|
|
115
|
-
operation_graph: skill.operation_graph,
|
|
116
|
-
...(skill.auth_profile_ref ? { auth_profile_ref: skill.auth_profile_ref } : {}),
|
|
117
|
-
});
|
|
118
|
-
writeWorkflowPublishArtifact(buildWorkflowPublishArtifact(
|
|
119
|
-
skill,
|
|
120
|
-
readWorkflowArtifact(skill.skill_id),
|
|
121
|
-
{
|
|
122
|
-
publishStatus: "published",
|
|
123
|
-
publishedAt: new Date().toISOString(),
|
|
124
|
-
endpointIds: selection.closure_endpoint_ids,
|
|
125
|
-
rootEndpointIds: selection.root_endpoint_ids,
|
|
126
|
-
},
|
|
127
|
-
));
|
|
128
|
-
|
|
129
|
-
// Publish graph edges via dedicated endpoint (fire-and-forget)
|
|
130
|
-
if (skill.operation_graph?.operations) {
|
|
131
|
-
for (const op of skill.operation_graph.operations) {
|
|
132
|
-
if (!selection.closure_operation_ids.includes(op.operation_id)) continue;
|
|
133
|
-
const opEdges = (skill.operation_graph.edges ?? [])
|
|
134
|
-
.filter(e => e.from_operation_id === op.operation_id && selection.closure_operation_ids.includes(e.to_operation_id))
|
|
135
|
-
.map(e => ({
|
|
136
|
-
target_endpoint_id: skill.operation_graph!.operations.find(
|
|
137
|
-
t => t.operation_id === e.to_operation_id
|
|
138
|
-
)?.endpoint_id ?? e.to_operation_id,
|
|
139
|
-
kind: e.kind,
|
|
140
|
-
confidence: e.confidence,
|
|
141
|
-
}));
|
|
142
|
-
if (opEdges.length > 0) {
|
|
143
|
-
publishGraphEdges(skill.domain, {
|
|
144
|
-
endpoint_id: op.endpoint_id,
|
|
145
|
-
method: op.method,
|
|
146
|
-
url_template: op.url_template,
|
|
147
|
-
}, opEdges).catch(() => {});
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const publishEvent: LifecycleEvent = {
|
|
153
|
-
phase: "publish",
|
|
154
|
-
skill_id: skill.skill_id,
|
|
155
|
-
timestamp: new Date().toISOString(),
|
|
156
|
-
duration_ms: publishMs,
|
|
157
|
-
source: "marketplace",
|
|
158
|
-
};
|
|
159
|
-
const totals = attributeLifecycle([publishEvent]);
|
|
160
|
-
console.log(`[lifecycle] publish=${totals.get("publish")}ms for ${skill.skill_id}`);
|
|
161
|
-
console.log(`[publish] passive publish succeeded for ${skill.skill_id}`);
|
|
162
|
-
})()
|
|
163
|
-
.catch((err) => {
|
|
164
|
-
console.error(
|
|
165
|
-
`[publish] passive publish failed for ${skill.skill_id}: ${err instanceof Error ? err.message : String(err)}`,
|
|
166
|
-
);
|
|
167
|
-
})
|
|
168
|
-
.finally(() => {
|
|
169
|
-
passivePublishInFlight.delete(skill.skill_id);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
passivePublishInFlight.set(skill.skill_id, job);
|
|
173
|
-
return job;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/** Await all in-flight passive publish jobs. Call before process exit. */
|
|
177
|
-
export async function drainPendingPassivePublishes(): Promise<void> {
|
|
178
|
-
const pending = [...passivePublishInFlight.values()];
|
|
179
|
-
if (pending.length === 0) return;
|
|
180
|
-
console.log(`[publish] draining ${pending.length} pending passive publish(es)...`);
|
|
181
|
-
await Promise.allSettled(pending);
|
|
182
|
-
console.log(`[publish] all passive publishes drained`);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export function resetPassivePublishQueueForTests(): void {
|
|
186
|
-
passivePublishInFlight.clear();
|
|
187
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import type { SkillManifest } from "../types/index.js";
|
|
2
|
-
|
|
3
|
-
export const DEFAULT_CAPTURE_MS = 22_000;
|
|
4
|
-
export const DEFAULT_CAPTURE_TOKENS = 30_000;
|
|
5
|
-
export const CHARS_PER_TOKEN = 4;
|
|
6
|
-
export const TOKEN_COST_PER_MILLION_USD = 3;
|
|
7
|
-
export const TOKEN_COST_UC = Math.round(TOKEN_COST_PER_MILLION_USD);
|
|
8
|
-
|
|
9
|
-
const SAVINGS_SOURCES = new Set([
|
|
10
|
-
"marketplace",
|
|
11
|
-
"route-cache",
|
|
12
|
-
"first-pass",
|
|
13
|
-
"direct-fetch",
|
|
14
|
-
"browser-action",
|
|
15
|
-
]);
|
|
16
|
-
|
|
17
|
-
export interface TimingEconomics {
|
|
18
|
-
response_bytes: number;
|
|
19
|
-
response_tokens: number;
|
|
20
|
-
tokens_saved: number;
|
|
21
|
-
tokens_saved_pct: number;
|
|
22
|
-
time_saved_pct: number;
|
|
23
|
-
baseline_total_ms?: number;
|
|
24
|
-
time_saved_ms?: number;
|
|
25
|
-
baseline_cost_uc?: number;
|
|
26
|
-
actual_cost_uc: number;
|
|
27
|
-
cost_saved_uc?: number;
|
|
28
|
-
baseline_source?: "real" | "estimated";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function computeTimingEconomics({
|
|
32
|
-
source,
|
|
33
|
-
totalMs,
|
|
34
|
-
result,
|
|
35
|
-
skill,
|
|
36
|
-
paidSearchUc = 0,
|
|
37
|
-
paidExecutionUc = 0,
|
|
38
|
-
}: {
|
|
39
|
-
source: string;
|
|
40
|
-
totalMs: number;
|
|
41
|
-
result: unknown;
|
|
42
|
-
skill?: Pick<SkillManifest, "discovery_cost">;
|
|
43
|
-
paidSearchUc?: number;
|
|
44
|
-
paidExecutionUc?: number;
|
|
45
|
-
}): TimingEconomics {
|
|
46
|
-
const resultStr = typeof result === "string" ? result : JSON.stringify(result ?? "");
|
|
47
|
-
const responseBytes = resultStr.length;
|
|
48
|
-
const responseTokens = Math.ceil(responseBytes / CHARS_PER_TOKEN);
|
|
49
|
-
const actualCostUc = (responseTokens * TOKEN_COST_UC) + paidSearchUc + paidExecutionUc;
|
|
50
|
-
|
|
51
|
-
const economics: TimingEconomics = {
|
|
52
|
-
response_bytes: responseBytes,
|
|
53
|
-
response_tokens: responseTokens,
|
|
54
|
-
tokens_saved: 0,
|
|
55
|
-
tokens_saved_pct: 0,
|
|
56
|
-
time_saved_pct: 0,
|
|
57
|
-
actual_cost_uc: actualCostUc,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
if (!SAVINGS_SOURCES.has(source)) return economics;
|
|
61
|
-
|
|
62
|
-
const baselineTokens = skill?.discovery_cost?.capture_tokens ?? DEFAULT_CAPTURE_TOKENS;
|
|
63
|
-
const baselineMs = skill?.discovery_cost?.capture_ms ?? DEFAULT_CAPTURE_MS;
|
|
64
|
-
const baselineCostUc = baselineTokens * TOKEN_COST_UC;
|
|
65
|
-
|
|
66
|
-
economics.tokens_saved = Math.max(0, baselineTokens - responseTokens);
|
|
67
|
-
economics.tokens_saved_pct = baselineTokens > 0
|
|
68
|
-
? Math.round((economics.tokens_saved / baselineTokens) * 100)
|
|
69
|
-
: 0;
|
|
70
|
-
economics.time_saved_pct = baselineMs > 0
|
|
71
|
-
? Math.round((Math.max(0, baselineMs - totalMs) / baselineMs) * 100)
|
|
72
|
-
: 0;
|
|
73
|
-
economics.baseline_total_ms = baselineMs;
|
|
74
|
-
economics.time_saved_ms = Math.max(0, baselineMs - totalMs);
|
|
75
|
-
economics.baseline_cost_uc = baselineCostUc;
|
|
76
|
-
economics.cost_saved_uc = Math.max(0, baselineCostUc - actualCostUc);
|
|
77
|
-
economics.baseline_source = skill?.discovery_cost ? "real" : "estimated";
|
|
78
|
-
|
|
79
|
-
return economics;
|
|
80
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { createHash } from "crypto";
|
|
2
|
-
import bs58 from "bs58";
|
|
3
|
-
import type { SkillManifest } from "../types/index.js";
|
|
4
|
-
|
|
5
|
-
type Contributor = NonNullable<SkillManifest["contributors"]>[number];
|
|
6
|
-
|
|
7
|
-
type CascadeSdk = {
|
|
8
|
-
createSplitsClient(args: {
|
|
9
|
-
rpc: unknown;
|
|
10
|
-
rpcSubscriptions: unknown;
|
|
11
|
-
signer: unknown;
|
|
12
|
-
}): {
|
|
13
|
-
ensureSplit(args: {
|
|
14
|
-
recipients: Array<{ address: string; share: number }>;
|
|
15
|
-
uniqueId?: unknown;
|
|
16
|
-
}): Promise<{
|
|
17
|
-
status: "created" | "updated" | "no_change" | "blocked" | "failed";
|
|
18
|
-
splitConfig?: string;
|
|
19
|
-
message?: string;
|
|
20
|
-
reason?: string;
|
|
21
|
-
}>;
|
|
22
|
-
};
|
|
23
|
-
labelToSeed(label: string): unknown;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
type SolanaKit = {
|
|
27
|
-
createSolanaRpc(url: string): unknown;
|
|
28
|
-
createSolanaRpcSubscriptions(url: string): unknown;
|
|
29
|
-
createKeyPairSignerFromBytes(secretKey: Uint8Array): Promise<unknown>;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
type CascadeDeps = {
|
|
33
|
-
loadSdk?: () => Promise<CascadeSdk>;
|
|
34
|
-
loadKit?: () => Promise<SolanaKit>;
|
|
35
|
-
env?: Record<string, string | undefined>;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export type CascadeProvisionResult = {
|
|
39
|
-
split_config?: string;
|
|
40
|
-
warning?: string;
|
|
41
|
-
source?: "env" | "sdk" | "existing";
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
function payableContributors(skill: Pick<SkillManifest, "contributors">): Contributor[] {
|
|
45
|
-
return (skill.contributors ?? []).filter((c): c is Contributor => !!c.wallet_address?.trim());
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function cascadeLabel(skillId: string): string {
|
|
49
|
-
const digest = createHash("sha256").update(skillId).digest("hex");
|
|
50
|
-
return `ubr-${digest.slice(0, 23)}`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function decodeSecretKey(raw: string): Uint8Array {
|
|
54
|
-
const trimmed = raw.trim();
|
|
55
|
-
if (!trimmed) throw new Error("empty signer secret");
|
|
56
|
-
|
|
57
|
-
if (trimmed.startsWith("[")) {
|
|
58
|
-
return Uint8Array.from(JSON.parse(trimmed) as number[]);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (/^[1-9A-HJ-NP-Za-km-z]+$/.test(trimmed)) {
|
|
62
|
-
return Uint8Array.from(bs58.decode(trimmed));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return Uint8Array.from(Buffer.from(trimmed, "base64"));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function recipientsForSkill(
|
|
69
|
-
skill: Pick<SkillManifest, "contributors">,
|
|
70
|
-
platformWallet: string,
|
|
71
|
-
): Array<{ address: string; share: number }> {
|
|
72
|
-
return [
|
|
73
|
-
{ address: platformWallet, share: 10 },
|
|
74
|
-
...payableContributors(skill).map((c) => ({
|
|
75
|
-
address: c.wallet_address!.trim(),
|
|
76
|
-
share: c.share,
|
|
77
|
-
})),
|
|
78
|
-
];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export async function ensureCascadeSplitForSkill(
|
|
82
|
-
skill: Pick<SkillManifest, "skill_id" | "contributors" | "split_config">,
|
|
83
|
-
deps: CascadeDeps = {},
|
|
84
|
-
): Promise<CascadeProvisionResult> {
|
|
85
|
-
const env = deps.env ?? process.env;
|
|
86
|
-
if (skill.split_config?.trim()) {
|
|
87
|
-
return { split_config: skill.split_config.trim(), source: "existing" };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const contributors = payableContributors(skill);
|
|
91
|
-
if (contributors.length <= 1) return {};
|
|
92
|
-
|
|
93
|
-
const explicitSplitConfig = env.UNBROWSE_CASCADE_SPLIT_ADDRESS?.trim()
|
|
94
|
-
|| env.UNBROWSE_CASCADE_SPLIT_CONFIG?.trim();
|
|
95
|
-
if (explicitSplitConfig) {
|
|
96
|
-
return { split_config: explicitSplitConfig, source: "env" };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const platformWallet = env.UNBROWSE_CASCADE_PLATFORM_WALLET?.trim()
|
|
100
|
-
|| env.PAYMENT_RECIPIENT?.trim();
|
|
101
|
-
const secretKey = env.UNBROWSE_CASCADE_SIGNER_SECRET_KEY?.trim();
|
|
102
|
-
const rpcUrl = env.UNBROWSE_CASCADE_RPC_URL?.trim();
|
|
103
|
-
const rpcWsUrl = env.UNBROWSE_CASCADE_RPC_WS_URL?.trim();
|
|
104
|
-
|
|
105
|
-
if (!platformWallet || !secretKey || !rpcUrl || !rpcWsUrl) {
|
|
106
|
-
return {
|
|
107
|
-
warning: "cascade_split_not_configured",
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const loadSdk = deps.loadSdk ?? (async () => await import("@cascade-fyi/splits-sdk") as unknown as CascadeSdk);
|
|
112
|
-
const loadKit = deps.loadKit ?? (async () => await import("@solana/kit") as unknown as SolanaKit);
|
|
113
|
-
const [sdk, kit] = await Promise.all([loadSdk(), loadKit()]);
|
|
114
|
-
|
|
115
|
-
const signer = await kit.createKeyPairSignerFromBytes(decodeSecretKey(secretKey));
|
|
116
|
-
const splits = sdk.createSplitsClient({
|
|
117
|
-
rpc: kit.createSolanaRpc(rpcUrl),
|
|
118
|
-
rpcSubscriptions: kit.createSolanaRpcSubscriptions(rpcWsUrl),
|
|
119
|
-
signer,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const result = await splits.ensureSplit({
|
|
123
|
-
recipients: recipientsForSkill(skill, platformWallet),
|
|
124
|
-
uniqueId: sdk.labelToSeed(cascadeLabel(skill.skill_id)),
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
if (result.status === "created" || result.status === "updated" || result.status === "no_change") {
|
|
128
|
-
return {
|
|
129
|
-
split_config: result.splitConfig,
|
|
130
|
-
source: "sdk",
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
warning: result.message || `cascade_split_${result.status}`,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_BACKEND_URL } from "../version.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Payment integration — lobster.cash compatible.
|
|
5
|
-
*
|
|
6
|
-
* This module describes payment INTENT and requirements.
|
|
7
|
-
* It does NOT implement wallet internals, generate wallets,
|
|
8
|
-
* or hardcode wallet provider action names.
|
|
9
|
-
*
|
|
10
|
-
* Delegation boundary:
|
|
11
|
-
* - Unbrowse owns: use-case intent, amount, recipient, memo
|
|
12
|
-
* - Wallet provider (lobster.cash) owns: provisioning, signing, broadcast, status
|
|
13
|
-
*
|
|
14
|
-
* @see https://lobster.cash/docs/skill-compatibility-guide
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// Payment requirement — what unbrowse tells the agent/wallet
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
export interface PaymentRequirement {
|
|
22
|
-
required: boolean;
|
|
23
|
-
amount: string;
|
|
24
|
-
currency: string;
|
|
25
|
-
reason: string;
|
|
26
|
-
recipient?: string;
|
|
27
|
-
memo?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type PaymentStatus =
|
|
31
|
-
| "paid"
|
|
32
|
-
| "payment_required"
|
|
33
|
-
| "wallet_not_configured"
|
|
34
|
-
| "insufficient_balance"
|
|
35
|
-
| "payment_failed"
|
|
36
|
-
| "awaiting_confirmation"
|
|
37
|
-
| "indexing_fallback"
|
|
38
|
-
| "free";
|
|
39
|
-
|
|
40
|
-
export interface PaymentGateResult {
|
|
41
|
-
status: PaymentStatus;
|
|
42
|
-
requirement?: PaymentRequirement;
|
|
43
|
-
message: string;
|
|
44
|
-
next_step?: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
// X402 configuration — Solana + Base, USDC via corbits.dev
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
|
|
51
|
-
export const X402_CONFIG = {
|
|
52
|
-
chains: {
|
|
53
|
-
solana: {
|
|
54
|
-
network: "solana",
|
|
55
|
-
currency: "USDC",
|
|
56
|
-
testnet: "solana-devnet",
|
|
57
|
-
mainnet: "solana-mainnet",
|
|
58
|
-
},
|
|
59
|
-
base: {
|
|
60
|
-
network: "base",
|
|
61
|
-
currency: "USDC",
|
|
62
|
-
testnet: "base-sepolia",
|
|
63
|
-
mainnet: "base-mainnet",
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
facilitator: "https://facilitator.corbits.dev",
|
|
67
|
-
supports_pda_wallets: true,
|
|
68
|
-
} as const;
|
|
69
|
-
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
// Dynamic pricing — fetch real route price from the backend
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
|
|
74
|
-
/** Backend API base URL for pricing queries. */
|
|
75
|
-
const PRICING_API_URL = process.env.UNBROWSE_BACKEND_URL ?? DEFAULT_BACKEND_URL;
|
|
76
|
-
|
|
77
|
-
/** Maximum time (ms) to wait for dynamic price before falling back. */
|
|
78
|
-
const PRICING_TIMEOUT_MS = 2_000;
|
|
79
|
-
|
|
80
|
-
/** Default fallback price when backend is unreachable or slow (USD). */
|
|
81
|
-
const DEFAULT_PRICE_USD = "0.001";
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Fetch the dynamic route price for a skill from the backend.
|
|
85
|
-
*
|
|
86
|
-
* Calls GET /v1/skills/:id/price with a 2 s timeout.
|
|
87
|
-
* Returns the price as a USD string, or null if the backend is
|
|
88
|
-
* unavailable, slow, or returns an unexpected shape.
|
|
89
|
-
*
|
|
90
|
-
* Non-blocking: callers should fall back to DEFAULT_PRICE_USD on null.
|
|
91
|
-
*/
|
|
92
|
-
export async function fetchDynamicPrice(skillId: string): Promise<string | null> {
|
|
93
|
-
const controller = new AbortController();
|
|
94
|
-
const timer = setTimeout(() => controller.abort(), PRICING_TIMEOUT_MS);
|
|
95
|
-
try {
|
|
96
|
-
const res = await fetch(
|
|
97
|
-
`${PRICING_API_URL}/v1/skills/${encodeURIComponent(skillId)}/price`,
|
|
98
|
-
{
|
|
99
|
-
method: "GET",
|
|
100
|
-
headers: { Accept: "application/json" },
|
|
101
|
-
signal: controller.signal,
|
|
102
|
-
},
|
|
103
|
-
);
|
|
104
|
-
if (!res.ok) return null;
|
|
105
|
-
const body = (await res.json()) as Record<string, unknown>;
|
|
106
|
-
const price = body?.price_usd;
|
|
107
|
-
if (typeof price === "number" && price > 0) return String(price);
|
|
108
|
-
if (typeof price === "string" && parseFloat(price) > 0) return price;
|
|
109
|
-
return null;
|
|
110
|
-
} catch {
|
|
111
|
-
// Network error, timeout, or JSON parse failure — non-fatal.
|
|
112
|
-
return null;
|
|
113
|
-
} finally {
|
|
114
|
-
clearTimeout(timer);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
|
-
// Payment gate — determines if execution requires payment
|
|
120
|
-
// ---------------------------------------------------------------------------
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Check if a skill execution requires payment.
|
|
124
|
-
* Returns a PaymentGateResult describing what the agent needs to do.
|
|
125
|
-
*
|
|
126
|
-
* Attempts to fetch the real dynamic price from the backend via
|
|
127
|
-
* GET /v1/skills/:id/price. Falls back to DEFAULT_PRICE_USD ("0.001")
|
|
128
|
-
* when the backend is unreachable, slow (>2 s), or returns an error.
|
|
129
|
-
*
|
|
130
|
-
* This function does NOT execute payments — it describes requirements.
|
|
131
|
-
* The agent's wallet plugin (lobster.cash) handles actual payment.
|
|
132
|
-
*/
|
|
133
|
-
export async function checkPaymentRequirement(
|
|
134
|
-
skillId: string,
|
|
135
|
-
endpointId: string,
|
|
136
|
-
options?: {
|
|
137
|
-
price_usd?: string;
|
|
138
|
-
skip_payment?: boolean;
|
|
139
|
-
wallet_configured?: boolean;
|
|
140
|
-
},
|
|
141
|
-
): Promise<PaymentGateResult> {
|
|
142
|
-
if (options?.skip_payment || process.env.UNBROWSE_SKIP_PAYMENT === "1") {
|
|
143
|
-
return { status: "free", message: "Payment skipped." };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (skillId.startsWith("local:") || process.env.UNBROWSE_FREE_TIER === "1") {
|
|
147
|
-
return { status: "free", message: "No payment required for local skills." };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Resolve price: explicit override > dynamic backend price > hardcoded default
|
|
151
|
-
let amount = options?.price_usd ?? null;
|
|
152
|
-
if (amount === null) {
|
|
153
|
-
const dynamic = await fetchDynamicPrice(skillId).catch(() => null);
|
|
154
|
-
amount = dynamic ?? DEFAULT_PRICE_USD;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (parseFloat(amount) <= 0) {
|
|
158
|
-
return { status: "free", message: "No payment required." };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const requirement: PaymentRequirement = {
|
|
162
|
-
required: true,
|
|
163
|
-
amount,
|
|
164
|
-
currency: "USDC",
|
|
165
|
-
reason: `Per-query fee for ${skillId}/${endpointId}`,
|
|
166
|
-
recipient: X402_CONFIG.facilitator,
|
|
167
|
-
memo: `unbrowse:${skillId}:${endpointId}`,
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
if (options?.wallet_configured === false) {
|
|
171
|
-
return {
|
|
172
|
-
status: "wallet_not_configured",
|
|
173
|
-
requirement,
|
|
174
|
-
message: "No agent wallet configured. Set up a wallet like lobster.cash to use paid skills.",
|
|
175
|
-
next_step: "Complete wallet setup before proceeding with this skill execution.",
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return {
|
|
180
|
-
status: "payment_required",
|
|
181
|
-
requirement,
|
|
182
|
-
message: `This execution requires ${amount} USDC. Transaction execution and final status are handled by your wallet provider.`,
|
|
183
|
-
next_step: "If a wallet step is required and wallet context is missing, complete wallet setup first.",
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Interpret a payment result from the agent's wallet.
|
|
189
|
-
* Maps wallet outcomes to gate decisions.
|
|
190
|
-
*/
|
|
191
|
-
/**
|
|
192
|
-
* Interpret a payment result from the agent's wallet.
|
|
193
|
-
* Maps wallet outcomes to gate decisions.
|
|
194
|
-
*
|
|
195
|
-
* When the wallet reports insufficient balance or no wallet,
|
|
196
|
-
* the caller should use resolveUnpaidAccess() to determine
|
|
197
|
-
* whether indexing fallback is available.
|
|
198
|
-
*/
|
|
199
|
-
export function interpretPaymentResult(
|
|
200
|
-
walletStatus: string,
|
|
201
|
-
requirement: PaymentRequirement,
|
|
202
|
-
): PaymentGateResult {
|
|
203
|
-
switch (walletStatus) {
|
|
204
|
-
case "confirmed":
|
|
205
|
-
case "success":
|
|
206
|
-
case "paid":
|
|
207
|
-
return { status: "paid", requirement, message: "Payment confirmed. Proceeding with execution." };
|
|
208
|
-
|
|
209
|
-
case "pending":
|
|
210
|
-
case "processing":
|
|
211
|
-
return {
|
|
212
|
-
status: "awaiting_confirmation",
|
|
213
|
-
requirement,
|
|
214
|
-
message: "Payment is being processed. Wait for your wallet to report the final transaction status before continuing.",
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
case "insufficient_balance":
|
|
218
|
-
case "insufficient_funds":
|
|
219
|
-
return {
|
|
220
|
-
status: "insufficient_balance",
|
|
221
|
-
requirement,
|
|
222
|
-
message: `Insufficient balance. Fund your wallet with at least ${requirement.amount} ${requirement.currency}, or fall back to indexing mode.`,
|
|
223
|
-
next_step: "Fund your agent wallet and retry, or use resolveUnpaidAccess() to contribute indexing instead.",
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
case "wallet_not_configured":
|
|
227
|
-
case "no_wallet":
|
|
228
|
-
return {
|
|
229
|
-
status: "wallet_not_configured",
|
|
230
|
-
requirement,
|
|
231
|
-
message: "No agent wallet configured. Falling back to indexing mode — you can contribute route indexing instead of paying.",
|
|
232
|
-
next_step: "Set up lobster.cash to unlock paid execution, or continue in indexing mode.",
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
default:
|
|
236
|
-
return {
|
|
237
|
-
status: "payment_failed",
|
|
238
|
-
requirement,
|
|
239
|
-
message: `Payment failed: ${walletStatus}. You may retry or fall back to indexing mode.`,
|
|
240
|
-
next_step: "Retry the payment, or use resolveUnpaidAccess() to contribute indexing instead.",
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Determine what an unpaid user can do.
|
|
247
|
-
*
|
|
248
|
-
* Users who can't pay (no wallet, insufficient balance, payment failed)
|
|
249
|
-
* fall back to indexing mode: they can capture, reverse-engineer, and
|
|
250
|
-
* index routes into the marketplace — contributing to the network
|
|
251
|
-
* instead of consuming paid executions.
|
|
252
|
-
*
|
|
253
|
-
* This is the default behavior when payment is required but not available.
|
|
254
|
-
*/
|
|
255
|
-
export function resolveUnpaidAccess(
|
|
256
|
-
gateResult: PaymentGateResult,
|
|
257
|
-
): PaymentGateResult {
|
|
258
|
-
const cantPay = gateResult.status === "wallet_not_configured"
|
|
259
|
-
|| gateResult.status === "insufficient_balance"
|
|
260
|
-
|| gateResult.status === "payment_failed";
|
|
261
|
-
|
|
262
|
-
if (!cantPay) return gateResult;
|
|
263
|
-
|
|
264
|
-
return {
|
|
265
|
-
status: "indexing_fallback",
|
|
266
|
-
requirement: gateResult.requirement,
|
|
267
|
-
message: "Indexing mode: you can capture and index routes for the marketplace. Paid execution requires a funded wallet.",
|
|
268
|
-
next_step: "Use resolve with --force-capture to contribute new routes. Set up lobster.cash to unlock paid execution.",
|
|
269
|
-
};
|
|
270
|
-
}
|