viveworker 0.7.0-beta.2 → 0.7.0-beta.3
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/README.md +10 -0
- package/package.json +2 -1
- package/scripts/share-cli.mjs +553 -3
- package/scripts/viveworker-bridge.mjs +791 -44
- package/viveworker.env.example +4 -0
- package/web/app.css +29 -0
- package/web/app.js +127 -11
- package/web/i18n.js +22 -0
- package/web/sw.js +1 -1
|
@@ -56,6 +56,8 @@ const DEFAULT_COMPLETION_REPLY_IMAGE_MAX_BYTES = 15 * 1024 * 1024;
|
|
|
56
56
|
const DEFAULT_COMPLETION_REPLY_UPLOAD_TTL_MS = 24 * 60 * 60 * 1000;
|
|
57
57
|
const MAX_COMPLETION_REPLY_IMAGE_COUNT = 4;
|
|
58
58
|
const HAZBASE_METADATA_TIMEOUT_MS = 1500;
|
|
59
|
+
const NPM_VERSION_CHECK_TIMEOUT_MS = 2500;
|
|
60
|
+
const NPM_VERSION_CHECK_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
59
61
|
|
|
60
62
|
// Shared memo for buildDiffThreadGroups. Each call spawns 3 git subprocesses
|
|
61
63
|
// per tracked repo (`git diff --name-status`, `git status --porcelain`,
|
|
@@ -265,6 +267,11 @@ const runtime = {
|
|
|
265
267
|
// deliberately NOT persisted via `state` since the cache is worthless after
|
|
266
268
|
// a restart and would just bloat the state file.
|
|
267
269
|
a2aShareStatusCache: null,
|
|
270
|
+
// Latest npm version cache. Checked only from the technical settings page
|
|
271
|
+
// and cached here so a weak network or repeated settings renders never
|
|
272
|
+
// hammer registry.npmjs.org.
|
|
273
|
+
npmVersionStatusCache: null,
|
|
274
|
+
npmVersionStatusPending: null,
|
|
268
275
|
};
|
|
269
276
|
const state = await loadState(config.stateFile);
|
|
270
277
|
runtime.threadRegistry = await loadThreadRegistry(config.threadRegistryFile);
|
|
@@ -2809,6 +2816,7 @@ function providerDisplayName(locale, provider) {
|
|
|
2809
2816
|
if (p === "claude") return t(locale, "common.claude");
|
|
2810
2817
|
if (p === "moltbook") return "Moltbook";
|
|
2811
2818
|
if (p === "a2a") return "A2A";
|
|
2819
|
+
if (p === "viveworker") return t(locale, "common.appName");
|
|
2812
2820
|
return t(locale, "common.codex");
|
|
2813
2821
|
}
|
|
2814
2822
|
|
|
@@ -2848,7 +2856,7 @@ function normalizeHistoryItem(raw) {
|
|
|
2848
2856
|
if (!isPlainObject(raw)) {
|
|
2849
2857
|
return null;
|
|
2850
2858
|
}
|
|
2851
|
-
if (
|
|
2859
|
+
if (shouldHideInternalTimelineItem(raw)) {
|
|
2852
2860
|
return null;
|
|
2853
2861
|
}
|
|
2854
2862
|
|
|
@@ -2903,7 +2911,7 @@ function normalizeHistoryItem(raw) {
|
|
|
2903
2911
|
...(raw.instruction != null ? { instruction: cleanText(raw.instruction) } : {}),
|
|
2904
2912
|
...(raw.taskStatus != null ? { taskStatus: cleanText(raw.taskStatus) } : {}),
|
|
2905
2913
|
};
|
|
2906
|
-
return
|
|
2914
|
+
return shouldHideInternalTimelineItem(normalized) ? null : normalized;
|
|
2907
2915
|
}
|
|
2908
2916
|
|
|
2909
2917
|
function historyToken(stableId) {
|
|
@@ -3043,7 +3051,7 @@ function normalizeTimelineEntry(raw) {
|
|
|
3043
3051
|
if (!isPlainObject(raw)) {
|
|
3044
3052
|
return null;
|
|
3045
3053
|
}
|
|
3046
|
-
if (
|
|
3054
|
+
if (shouldHideInternalTimelineItem(raw)) {
|
|
3047
3055
|
return null;
|
|
3048
3056
|
}
|
|
3049
3057
|
|
|
@@ -3137,7 +3145,7 @@ function normalizeTimelineEntry(raw) {
|
|
|
3137
3145
|
...(raw.taskStatus != null ? { taskStatus: cleanText(raw.taskStatus) } : {}),
|
|
3138
3146
|
...(suggestions.length > 0 ? { suggestions } : {}),
|
|
3139
3147
|
};
|
|
3140
|
-
return
|
|
3148
|
+
return shouldHideInternalTimelineItem(normalized) ? null : normalized;
|
|
3141
3149
|
}
|
|
3142
3150
|
|
|
3143
3151
|
function recordTimelineEntry({ config, runtime, state, entry }) {
|
|
@@ -5669,6 +5677,12 @@ async function processScannedEvent({ config, runtime, state, event }) {
|
|
|
5669
5677
|
return false;
|
|
5670
5678
|
}
|
|
5671
5679
|
|
|
5680
|
+
if (shouldSuppressInternalScannedEvent(event)) {
|
|
5681
|
+
state.seenEvents[event.id] = event.timestampMs || Date.now();
|
|
5682
|
+
trimSeenEvents(state.seenEvents, config.maxSeenEvents);
|
|
5683
|
+
return true;
|
|
5684
|
+
}
|
|
5685
|
+
|
|
5672
5686
|
let dirty = false;
|
|
5673
5687
|
|
|
5674
5688
|
if (event.kind === "task_complete") {
|
|
@@ -6528,6 +6542,150 @@ async function createNativeApproval({ config, runtime, conversationId, request,
|
|
|
6528
6542
|
};
|
|
6529
6543
|
}
|
|
6530
6544
|
|
|
6545
|
+
function createX402PaymentApproval({ config, body, now = Date.now() }) {
|
|
6546
|
+
const payment = normalizeX402PaymentApprovalBody(body);
|
|
6547
|
+
if (!payment) {
|
|
6548
|
+
return null;
|
|
6549
|
+
}
|
|
6550
|
+
const token = crypto.randomBytes(18).toString("hex");
|
|
6551
|
+
const requestId = cleanText(body.paymentRequestId || body.requestId || crypto.randomUUID());
|
|
6552
|
+
const requestKey = `payment:${requestId}`;
|
|
6553
|
+
const title = payment.amountUsdc
|
|
6554
|
+
? `Payment approval — ${payment.amountUsdc} USDC`
|
|
6555
|
+
: "Payment approval";
|
|
6556
|
+
return {
|
|
6557
|
+
token,
|
|
6558
|
+
requestKey,
|
|
6559
|
+
conversationId: "payments",
|
|
6560
|
+
requestId,
|
|
6561
|
+
ownerClientId: null,
|
|
6562
|
+
kind: "payment",
|
|
6563
|
+
threadLabel: "x402 payment",
|
|
6564
|
+
title,
|
|
6565
|
+
messageText: formatX402PaymentApprovalMessage(payment),
|
|
6566
|
+
reviewUrl: `${config.nativeApprovalPublicBaseUrl}/native-approvals/${token}`,
|
|
6567
|
+
rawParams: payment,
|
|
6568
|
+
cwd: "",
|
|
6569
|
+
workspaceRoot: "",
|
|
6570
|
+
fileRefs: [],
|
|
6571
|
+
diffText: "",
|
|
6572
|
+
diffAvailable: false,
|
|
6573
|
+
diffSource: "",
|
|
6574
|
+
diffAddedLines: 0,
|
|
6575
|
+
diffRemovedLines: 0,
|
|
6576
|
+
createdAtMs: now,
|
|
6577
|
+
resolved: false,
|
|
6578
|
+
resolving: false,
|
|
6579
|
+
resolveClaudeWaiter: null,
|
|
6580
|
+
provider: "viveworker",
|
|
6581
|
+
};
|
|
6582
|
+
}
|
|
6583
|
+
|
|
6584
|
+
|
|
6585
|
+
function createHazbaseWalletPaymentApproval({ config, body, now = Date.now() }) {
|
|
6586
|
+
const payment = normalizeX402PaymentApprovalBody(body);
|
|
6587
|
+
if (!payment) return null;
|
|
6588
|
+
const paymentRequestId = cleanText(body.paymentRequestId || body.requestId || "");
|
|
6589
|
+
if (!/^[a-zA-Z0-9:_-]{8,160}$/u.test(paymentRequestId)) return null;
|
|
6590
|
+
const token = crypto.randomBytes(18).toString("hex");
|
|
6591
|
+
const requestKey = `hazbase_wallet_payment:${paymentRequestId}`;
|
|
6592
|
+
const title = payment.amountUsdc
|
|
6593
|
+
? `Hazbase wallet payment — ${payment.amountUsdc} USDC`
|
|
6594
|
+
: "Hazbase wallet payment";
|
|
6595
|
+
return {
|
|
6596
|
+
token,
|
|
6597
|
+
requestKey,
|
|
6598
|
+
conversationId: "payments",
|
|
6599
|
+
requestId: paymentRequestId,
|
|
6600
|
+
paymentRequestId,
|
|
6601
|
+
ownerClientId: null,
|
|
6602
|
+
kind: "hazbase_wallet_payment",
|
|
6603
|
+
threadLabel: "x402 payment",
|
|
6604
|
+
title,
|
|
6605
|
+
messageText: formatHazbaseWalletPaymentApprovalMessage(payment),
|
|
6606
|
+
reviewUrl: `${config.nativeApprovalPublicBaseUrl}/native-approvals/${token}`,
|
|
6607
|
+
rawParams: payment,
|
|
6608
|
+
cwd: "",
|
|
6609
|
+
workspaceRoot: "",
|
|
6610
|
+
fileRefs: [],
|
|
6611
|
+
diffText: "",
|
|
6612
|
+
diffAvailable: false,
|
|
6613
|
+
diffSource: "",
|
|
6614
|
+
diffAddedLines: 0,
|
|
6615
|
+
diffRemovedLines: 0,
|
|
6616
|
+
createdAtMs: now,
|
|
6617
|
+
resolved: false,
|
|
6618
|
+
resolving: false,
|
|
6619
|
+
resolveClaudeWaiter: null,
|
|
6620
|
+
provider: "viveworker",
|
|
6621
|
+
};
|
|
6622
|
+
}
|
|
6623
|
+
|
|
6624
|
+
function formatHazbaseWalletPaymentApprovalMessage(payment) {
|
|
6625
|
+
const lines = [
|
|
6626
|
+
"Hazbase Smart Wallet payment requested.",
|
|
6627
|
+
"",
|
|
6628
|
+
`Amount: ${payment.amountUsdc || payment.amountAtomic} USDC`,
|
|
6629
|
+
`Network: ${payment.network} (chainId ${payment.chainId})`,
|
|
6630
|
+
`Pay to: ${payment.payTo}`,
|
|
6631
|
+
`Asset: ${payment.asset}`,
|
|
6632
|
+
`Resource: ${payment.resource}`,
|
|
6633
|
+
"",
|
|
6634
|
+
"Approving will ask for your passkey, then hazBase will submit a gasless Smart Wallet payment.",
|
|
6635
|
+
];
|
|
6636
|
+
if (payment.description) lines.splice(7, 0, `Description: ${payment.description}`);
|
|
6637
|
+
if (payment.url && payment.url !== payment.resource) lines.splice(-1, 0, `URL: ${payment.url}`);
|
|
6638
|
+
return lines.join("\n");
|
|
6639
|
+
}
|
|
6640
|
+
|
|
6641
|
+
function normalizeX402PaymentApprovalBody(body) {
|
|
6642
|
+
if (!isPlainObject(body)) return null;
|
|
6643
|
+
const payment = isPlainObject(body.payment) ? body.payment : {};
|
|
6644
|
+
const network = cleanText(payment.network || "");
|
|
6645
|
+
const chainId = Number(payment.chainId) || 0;
|
|
6646
|
+
const amountAtomic = cleanText(payment.amountAtomic || "");
|
|
6647
|
+
const amountUsdc = cleanText(payment.amountUsdc || "");
|
|
6648
|
+
const payTo = cleanText(payment.payTo || "");
|
|
6649
|
+
const asset = cleanText(payment.asset || "");
|
|
6650
|
+
const resource = cleanText(payment.resource || body.url || "");
|
|
6651
|
+
const description = cleanText(payment.description || "");
|
|
6652
|
+
const url = cleanText(body.url || resource);
|
|
6653
|
+
if (!network || !chainId || !amountAtomic || !payTo || !asset || !resource) {
|
|
6654
|
+
return null;
|
|
6655
|
+
}
|
|
6656
|
+
return {
|
|
6657
|
+
url,
|
|
6658
|
+
network,
|
|
6659
|
+
chainId,
|
|
6660
|
+
amountAtomic,
|
|
6661
|
+
amountUsdc,
|
|
6662
|
+
payTo,
|
|
6663
|
+
asset,
|
|
6664
|
+
resource,
|
|
6665
|
+
description,
|
|
6666
|
+
};
|
|
6667
|
+
}
|
|
6668
|
+
|
|
6669
|
+
function formatX402PaymentApprovalMessage(payment) {
|
|
6670
|
+
const lines = [
|
|
6671
|
+
"x402 payment approval requested.",
|
|
6672
|
+
"",
|
|
6673
|
+
`Amount: ${payment.amountUsdc || payment.amountAtomic} USDC`,
|
|
6674
|
+
`Network: ${payment.network} (chainId ${payment.chainId})`,
|
|
6675
|
+
`Pay to: ${payment.payTo}`,
|
|
6676
|
+
`Asset: ${payment.asset}`,
|
|
6677
|
+
`Resource: ${payment.resource}`,
|
|
6678
|
+
];
|
|
6679
|
+
if (payment.description) {
|
|
6680
|
+
lines.push(`Description: ${payment.description}`);
|
|
6681
|
+
}
|
|
6682
|
+
if (payment.url && payment.url !== payment.resource) {
|
|
6683
|
+
lines.push(`URL: ${payment.url}`);
|
|
6684
|
+
}
|
|
6685
|
+
lines.push("", "Approve only if the amount, recipient, network, and resource match what you expect.");
|
|
6686
|
+
return lines.join("\n");
|
|
6687
|
+
}
|
|
6688
|
+
|
|
6531
6689
|
async function buildNativeApprovalPayload({ config, runtime, conversationId, request, token }) {
|
|
6532
6690
|
const kind = nativeApprovalKind(request.method);
|
|
6533
6691
|
if (!kind) {
|
|
@@ -8767,6 +8925,18 @@ function isHiddenClaudeInternalScoringText(text) {
|
|
|
8767
8925
|
return /^You are scoring Moltbook posts? for an AI agent\b/iu.test(single);
|
|
8768
8926
|
}
|
|
8769
8927
|
|
|
8928
|
+
function isHiddenCodexApprovalAssessmentText(text) {
|
|
8929
|
+
const single = cleanText(stripNotificationMarkup(stripEnvironmentContextBlocks(text || "")));
|
|
8930
|
+
if (!single) {
|
|
8931
|
+
return false;
|
|
8932
|
+
}
|
|
8933
|
+
return /\bThe following is the Codex agent history(?: added since your last approval assessment)?\b/iu.test(single);
|
|
8934
|
+
}
|
|
8935
|
+
|
|
8936
|
+
function shouldHideInternalTimelineItem(item) {
|
|
8937
|
+
return shouldHideClaudeInternalItem(item) || shouldHideCodexInternalApprovalItem(item);
|
|
8938
|
+
}
|
|
8939
|
+
|
|
8770
8940
|
function shouldHideClaudeInternalItem(item) {
|
|
8771
8941
|
if (!isPlainObject(item)) {
|
|
8772
8942
|
return false;
|
|
@@ -8786,6 +8956,48 @@ function shouldHideClaudeInternalItem(item) {
|
|
|
8786
8956
|
);
|
|
8787
8957
|
}
|
|
8788
8958
|
|
|
8959
|
+
function shouldSuppressInternalScannedEvent(event) {
|
|
8960
|
+
if (!isPlainObject(event)) {
|
|
8961
|
+
return false;
|
|
8962
|
+
}
|
|
8963
|
+
const provider = normalizeProvider(event.provider || "codex");
|
|
8964
|
+
if (provider !== "codex") {
|
|
8965
|
+
return false;
|
|
8966
|
+
}
|
|
8967
|
+
|
|
8968
|
+
return shouldHideCodexInternalApprovalItem({
|
|
8969
|
+
provider,
|
|
8970
|
+
kind: cleanText(event.kind || ""),
|
|
8971
|
+
title: event.title,
|
|
8972
|
+
threadLabel: event.threadLabel,
|
|
8973
|
+
summary: event.summary ?? event.message,
|
|
8974
|
+
messageText: event.messageText ?? event.detailText ?? event.message,
|
|
8975
|
+
detailText: event.detailText,
|
|
8976
|
+
message: event.message,
|
|
8977
|
+
});
|
|
8978
|
+
}
|
|
8979
|
+
|
|
8980
|
+
function shouldHideCodexInternalApprovalItem(item) {
|
|
8981
|
+
if (!isPlainObject(item)) {
|
|
8982
|
+
return false;
|
|
8983
|
+
}
|
|
8984
|
+
if (normalizeProvider(item.provider) !== "codex") {
|
|
8985
|
+
return false;
|
|
8986
|
+
}
|
|
8987
|
+
|
|
8988
|
+
const title = cleanText(item.title ?? "");
|
|
8989
|
+
const threadLabel = cleanText(item.threadLabel ?? "");
|
|
8990
|
+
if (isHiddenCodexApprovalAssessmentText(title) || isHiddenCodexApprovalAssessmentText(threadLabel)) {
|
|
8991
|
+
return true;
|
|
8992
|
+
}
|
|
8993
|
+
return (
|
|
8994
|
+
isHiddenCodexApprovalAssessmentText(item.messageText) ||
|
|
8995
|
+
isHiddenCodexApprovalAssessmentText(item.summary) ||
|
|
8996
|
+
isHiddenCodexApprovalAssessmentText(item.detailText) ||
|
|
8997
|
+
isHiddenCodexApprovalAssessmentText(item.message)
|
|
8998
|
+
);
|
|
8999
|
+
}
|
|
9000
|
+
|
|
8789
9001
|
function threadStateArchiveStatus(threadState) {
|
|
8790
9002
|
if (!isPlainObject(threadState)) {
|
|
8791
9003
|
return "";
|
|
@@ -10499,7 +10711,9 @@ function buildPendingInboxItems(runtime, state, config, locale) {
|
|
|
10499
10711
|
token: approval.token,
|
|
10500
10712
|
threadId: cleanText(approval.conversationId || ""),
|
|
10501
10713
|
threadLabel: approval.threadLabel || "",
|
|
10502
|
-
title:
|
|
10714
|
+
title: cleanText(approval.kind || "") === "payment"
|
|
10715
|
+
? cleanText(approval.title || "") || formatLocalizedTitle(locale, "server.title.approval", approval.threadLabel)
|
|
10716
|
+
: formatLocalizedTitle(locale, "server.title.approval", approval.threadLabel),
|
|
10503
10717
|
summary: formatNotificationBody(approval.messageText, 100) || approval.messageText,
|
|
10504
10718
|
primaryLabel: t(locale, "server.action.review"),
|
|
10505
10719
|
createdAtMs: Number(approval.createdAtMs) || now,
|
|
@@ -10851,7 +11065,9 @@ function buildOperationalTimelineEntries(runtime, state, config, locale) {
|
|
|
10851
11065
|
kind: "approval",
|
|
10852
11066
|
threadId: cleanText(approval.conversationId || ""),
|
|
10853
11067
|
threadLabel: approval.threadLabel,
|
|
10854
|
-
title:
|
|
11068
|
+
title: cleanText(approval.kind || "") === "payment"
|
|
11069
|
+
? cleanText(approval.title || "") || formatLocalizedTitle(locale, "server.title.approval", approval.threadLabel)
|
|
11070
|
+
: formatLocalizedTitle(locale, "server.title.approval", approval.threadLabel),
|
|
10855
11071
|
summary: formatNotificationBody(approval.messageText, 180) || approval.messageText,
|
|
10856
11072
|
messageText: approval.messageText,
|
|
10857
11073
|
outcome: "pending",
|
|
@@ -11058,7 +11274,9 @@ function buildPendingApprovalDetail(runtime, state, approval, locale) {
|
|
|
11058
11274
|
approvalKind,
|
|
11059
11275
|
provider: normalizeProvider(approval.provider),
|
|
11060
11276
|
token: approval.token,
|
|
11061
|
-
title:
|
|
11277
|
+
title: approvalKind === "payment"
|
|
11278
|
+
? cleanText(approval.title || "") || formatLocalizedTitle(locale, "server.title.approval", approval.threadLabel)
|
|
11279
|
+
: formatLocalizedTitle(locale, "server.title.approval", approval.threadLabel),
|
|
11062
11280
|
threadLabel: approval.threadLabel || "",
|
|
11063
11281
|
createdAtMs: Number(approval.createdAtMs) || 0,
|
|
11064
11282
|
messageHtml: renderMessageHtml(approval.messageText, `<p>${escapeHtml(t(locale, "detail.approvalRequested"))}</p>`),
|
|
@@ -11073,10 +11291,14 @@ function buildPendingApprovalDetail(runtime, state, approval, locale) {
|
|
|
11073
11291
|
readOnly: approval.readOnly === true,
|
|
11074
11292
|
actions: approval.readOnly === true
|
|
11075
11293
|
? []
|
|
11076
|
-
:
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
11294
|
+
: approvalKind === "hazbase_wallet_payment"
|
|
11295
|
+
? [
|
|
11296
|
+
{ label: t(locale, "server.action.payWithWallet"), tone: "primary", url: `/api/payments/x402/hazbase-wallet/${encodeURIComponent(approval.token)}/pay`, body: { hazbaseReauth: true } },
|
|
11297
|
+
]
|
|
11298
|
+
: [
|
|
11299
|
+
{ label: t(locale, "server.action.approve"), tone: "primary", url: `/api/items/approval/${encodeURIComponent(approval.token)}/accept`, body: {} },
|
|
11300
|
+
{ label: t(locale, "server.action.reject"), tone: "danger", url: `/api/items/approval/${encodeURIComponent(approval.token)}/decline`, body: {} },
|
|
11301
|
+
],
|
|
11080
11302
|
};
|
|
11081
11303
|
if (approvalKind === "plan") {
|
|
11082
11304
|
const planText = String(approval.planText || "");
|
|
@@ -13472,6 +13694,12 @@ function createNativeApprovalServer({ config, runtime, state }) {
|
|
|
13472
13694
|
return writeJson(res, 200, buildSessionPayload({ config, state, session }));
|
|
13473
13695
|
}
|
|
13474
13696
|
|
|
13697
|
+
if (url.pathname === "/api/version/status" && req.method === "GET") {
|
|
13698
|
+
const session = requireApiSession(req, res, config, state);
|
|
13699
|
+
if (!session) return;
|
|
13700
|
+
return writeJson(res, 200, await getNpmVersionStatus(runtime, config));
|
|
13701
|
+
}
|
|
13702
|
+
|
|
13475
13703
|
// Collapses the PWA's boot-time fan-out (session + inbox + timeline
|
|
13476
13704
|
// + devices) into a single HTTPS round-trip. iOS Safari tears down
|
|
13477
13705
|
// connections aggressively, so each parallel fetch pays its own
|
|
@@ -13929,10 +14157,15 @@ if (url.pathname === "/api/hazbase/status" && req.method === "GET") {
|
|
|
13929
14157
|
8453: resolveHazbaseAccountForChain(hazbase.accounts, 8453)?.smartAccountAddress || "",
|
|
13930
14158
|
84532: resolveHazbaseAccountForChain(hazbase.accounts, 84532)?.smartAccountAddress || "",
|
|
13931
14159
|
};
|
|
14160
|
+
const signedIn = Boolean(hazbase.accessToken) && !hazbase.sessionInvalid;
|
|
13932
14161
|
return writeJson(res, 200, {
|
|
13933
14162
|
enabled: true,
|
|
13934
14163
|
apiUrl: config.hazbaseApiUrl,
|
|
13935
|
-
signedIn
|
|
14164
|
+
signedIn,
|
|
14165
|
+
sessionStatus: hazbase.sessionInvalid ? "expired" : signedIn ? "signed_in" : "signed_out",
|
|
14166
|
+
sessionInvalid: hazbase.sessionInvalid,
|
|
14167
|
+
sessionInvalidReason: hazbase.sessionInvalidReason,
|
|
14168
|
+
sessionInvalidAt: hazbase.sessionInvalidAt,
|
|
13936
14169
|
email: hazbase.email,
|
|
13937
14170
|
userId: hazbase.userId,
|
|
13938
14171
|
sessionId: hazbase.sessionId,
|
|
@@ -13995,15 +14228,19 @@ if (url.pathname === "/api/hazbase/verify-otp" && req.method === "POST") {
|
|
|
13995
14228
|
const code = cleanText(body?.code || "");
|
|
13996
14229
|
if (!email || !code) return writeJson(res, 400, { error: "otp-required" });
|
|
13997
14230
|
const result = await verifyHazbaseEmailOtp({ email, code });
|
|
14231
|
+
const previousHazbase = normalizeHazbaseState(state.hazbase);
|
|
13998
14232
|
state.hazbase = {
|
|
13999
|
-
...
|
|
14233
|
+
...previousHazbase,
|
|
14000
14234
|
email: result.email || email,
|
|
14001
14235
|
accessToken: cleanText(result.accessToken || ""),
|
|
14002
14236
|
sessionId: cleanText(result.sessionId || ""),
|
|
14003
14237
|
userId: cleanText(result.userId || ""),
|
|
14004
|
-
accounts: Array.isArray(result.accounts) ? result.accounts : [],
|
|
14238
|
+
accounts: mergeHazbaseAccountSummaries(previousHazbase.accounts, Array.isArray(result.accounts) ? result.accounts : []),
|
|
14005
14239
|
highTrustToken: "",
|
|
14006
14240
|
highTrustExpiresAt: "",
|
|
14241
|
+
sessionInvalid: false,
|
|
14242
|
+
sessionInvalidReason: "",
|
|
14243
|
+
sessionInvalidAt: "",
|
|
14007
14244
|
};
|
|
14008
14245
|
await saveState(config.stateFile, state);
|
|
14009
14246
|
return writeJson(res, 200, result);
|
|
@@ -14024,11 +14261,17 @@ if (url.pathname === "/api/hazbase/passkey/register/challenge" && req.method ===
|
|
|
14024
14261
|
message: error?.message || "Open viveworker on its .local hostname to use hazBase passkeys.",
|
|
14025
14262
|
});
|
|
14026
14263
|
}
|
|
14027
|
-
|
|
14028
|
-
|
|
14029
|
-
|
|
14030
|
-
|
|
14031
|
-
|
|
14264
|
+
let result;
|
|
14265
|
+
try {
|
|
14266
|
+
result = await requestPasskeyRegistrationChallenge({
|
|
14267
|
+
emailSession: hazbase.accessToken,
|
|
14268
|
+
deviceLabel: cleanText(body?.deviceLabel || config.hazbaseDeviceLabel || "") || undefined,
|
|
14269
|
+
partnerOrigin,
|
|
14270
|
+
});
|
|
14271
|
+
} catch (error) {
|
|
14272
|
+
if (await maybeWriteHazbaseSessionExpiredResponse({ error, config, state, res })) return;
|
|
14273
|
+
throw error;
|
|
14274
|
+
}
|
|
14032
14275
|
return writeJson(res, 200, result);
|
|
14033
14276
|
}
|
|
14034
14277
|
|
|
@@ -14038,16 +14281,25 @@ if (url.pathname === "/api/hazbase/passkey/register/complete" && req.method ===
|
|
|
14038
14281
|
const hazbase = normalizeHazbaseState(state.hazbase);
|
|
14039
14282
|
if (!hazbase.accessToken) return writeJson(res, 401, { error: "hazbase-auth-required" });
|
|
14040
14283
|
const body = await parseJsonBody(req);
|
|
14041
|
-
|
|
14042
|
-
|
|
14043
|
-
|
|
14044
|
-
|
|
14045
|
-
|
|
14046
|
-
|
|
14284
|
+
let result;
|
|
14285
|
+
try {
|
|
14286
|
+
result = await completePasskeyRegistration({
|
|
14287
|
+
emailSession: hazbase.accessToken,
|
|
14288
|
+
challengeId: cleanText(body?.challengeId || ""),
|
|
14289
|
+
credential: body?.credential,
|
|
14290
|
+
deviceLabel: cleanText(body?.deviceLabel || config.hazbaseDeviceLabel || "") || undefined,
|
|
14291
|
+
});
|
|
14292
|
+
} catch (error) {
|
|
14293
|
+
if (await maybeWriteHazbaseSessionExpiredResponse({ error, config, state, res })) return;
|
|
14294
|
+
throw error;
|
|
14295
|
+
}
|
|
14047
14296
|
state.hazbase = {
|
|
14048
14297
|
...hazbase,
|
|
14049
14298
|
deviceBindingId: cleanText(result.deviceBindingId || hazbase.deviceBindingId || ""),
|
|
14050
14299
|
credentialId: cleanText(result.credentialId || hazbase.credentialId || ""),
|
|
14300
|
+
sessionInvalid: false,
|
|
14301
|
+
sessionInvalidReason: "",
|
|
14302
|
+
sessionInvalidAt: "",
|
|
14051
14303
|
};
|
|
14052
14304
|
await saveState(config.stateFile, state);
|
|
14053
14305
|
return writeJson(res, 200, result);
|
|
@@ -14068,12 +14320,18 @@ if (url.pathname === "/api/hazbase/passkey/assert/challenge" && req.method === "
|
|
|
14068
14320
|
message: error?.message || "Open viveworker on its .local hostname to use hazBase passkeys.",
|
|
14069
14321
|
});
|
|
14070
14322
|
}
|
|
14071
|
-
|
|
14072
|
-
|
|
14073
|
-
|
|
14074
|
-
|
|
14075
|
-
|
|
14076
|
-
|
|
14323
|
+
let result;
|
|
14324
|
+
try {
|
|
14325
|
+
result = await requestPasskeyAssertionChallenge({
|
|
14326
|
+
emailSession: hazbase.accessToken,
|
|
14327
|
+
purpose: cleanText(body?.purpose || "bootstrap") || "bootstrap",
|
|
14328
|
+
deviceBindingId: hazbase.deviceBindingId || undefined,
|
|
14329
|
+
partnerOrigin,
|
|
14330
|
+
});
|
|
14331
|
+
} catch (error) {
|
|
14332
|
+
if (await maybeWriteHazbaseSessionExpiredResponse({ error, config, state, res })) return;
|
|
14333
|
+
throw error;
|
|
14334
|
+
}
|
|
14077
14335
|
return writeJson(res, 200, result);
|
|
14078
14336
|
}
|
|
14079
14337
|
|
|
@@ -14083,19 +14341,28 @@ if (url.pathname === "/api/hazbase/passkey/assert/complete" && req.method === "P
|
|
|
14083
14341
|
const hazbase = normalizeHazbaseState(state.hazbase);
|
|
14084
14342
|
if (!hazbase.accessToken) return writeJson(res, 401, { error: "hazbase-auth-required" });
|
|
14085
14343
|
const body = await parseJsonBody(req);
|
|
14086
|
-
|
|
14087
|
-
|
|
14088
|
-
|
|
14089
|
-
|
|
14090
|
-
|
|
14091
|
-
|
|
14092
|
-
|
|
14344
|
+
let result;
|
|
14345
|
+
try {
|
|
14346
|
+
result = await completePasskeyAssertion({
|
|
14347
|
+
emailSession: hazbase.accessToken,
|
|
14348
|
+
challengeId: cleanText(body?.challengeId || ""),
|
|
14349
|
+
credential: body?.credential,
|
|
14350
|
+
purpose: cleanText(body?.purpose || "bootstrap") || "bootstrap",
|
|
14351
|
+
deviceBindingId: hazbase.deviceBindingId || undefined,
|
|
14352
|
+
});
|
|
14353
|
+
} catch (error) {
|
|
14354
|
+
if (await maybeWriteHazbaseSessionExpiredResponse({ error, config, state, res })) return;
|
|
14355
|
+
throw error;
|
|
14356
|
+
}
|
|
14093
14357
|
state.hazbase = {
|
|
14094
14358
|
...hazbase,
|
|
14095
14359
|
deviceBindingId: cleanText(result.deviceBindingId || hazbase.deviceBindingId || ""),
|
|
14096
14360
|
credentialId: cleanText(result.credentialId || hazbase.credentialId || ""),
|
|
14097
14361
|
highTrustToken: cleanText(result.highTrustToken || ""),
|
|
14098
14362
|
highTrustExpiresAt: cleanText(result.highTrustExpiresAt || ""),
|
|
14363
|
+
sessionInvalid: false,
|
|
14364
|
+
sessionInvalidReason: "",
|
|
14365
|
+
sessionInvalidAt: "",
|
|
14099
14366
|
};
|
|
14100
14367
|
await saveState(config.stateFile, state);
|
|
14101
14368
|
return writeJson(res, 200, result);
|
|
@@ -14113,12 +14380,18 @@ if (url.pathname === "/api/hazbase/account/bootstrap" && req.method === "POST")
|
|
|
14113
14380
|
if (chainId !== 8453 && chainId !== 84532) {
|
|
14114
14381
|
return writeJson(res, 400, { error: "unsupported-chain" });
|
|
14115
14382
|
}
|
|
14116
|
-
|
|
14117
|
-
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
|
|
14383
|
+
let result;
|
|
14384
|
+
try {
|
|
14385
|
+
result = await bootstrapPasskeyAccount({
|
|
14386
|
+
emailSession: hazbase.accessToken,
|
|
14387
|
+
deviceBindingId: hazbase.deviceBindingId,
|
|
14388
|
+
highTrustToken: hazbase.highTrustToken,
|
|
14389
|
+
chainId,
|
|
14390
|
+
});
|
|
14391
|
+
} catch (error) {
|
|
14392
|
+
if (await maybeWriteHazbaseSessionExpiredResponse({ error, config, state, res })) return;
|
|
14393
|
+
throw error;
|
|
14394
|
+
}
|
|
14122
14395
|
const nextAccounts = mergeHazbaseAccountSummaries(hazbase.accounts, [{
|
|
14123
14396
|
smartAccountAddress: result.smartAccountAddress,
|
|
14124
14397
|
chainId: result.chainId,
|
|
@@ -14129,11 +14402,29 @@ if (url.pathname === "/api/hazbase/account/bootstrap" && req.method === "POST")
|
|
|
14129
14402
|
state.hazbase = {
|
|
14130
14403
|
...hazbase,
|
|
14131
14404
|
accounts: nextAccounts,
|
|
14405
|
+
sessionInvalid: false,
|
|
14406
|
+
sessionInvalidReason: "",
|
|
14407
|
+
sessionInvalidAt: "",
|
|
14132
14408
|
};
|
|
14133
14409
|
await saveState(config.stateFile, state);
|
|
14134
14410
|
return writeJson(res, 200, result);
|
|
14135
14411
|
}
|
|
14136
14412
|
|
|
14413
|
+
if (url.pathname === "/api/hazbase/session/refresh" && req.method === "POST") {
|
|
14414
|
+
const hookSecret = req.headers["x-viveworker-hook-secret"] || "";
|
|
14415
|
+
const hookAuth = config.sessionSecret && hookSecret === config.sessionSecret;
|
|
14416
|
+
if (!hookAuth) {
|
|
14417
|
+
const session = requireMutatingApiSession(req, res, config, state);
|
|
14418
|
+
if (!session) return;
|
|
14419
|
+
}
|
|
14420
|
+
markHazbaseSessionInvalid(state, "manual_refresh_requested");
|
|
14421
|
+
await saveState(config.stateFile, state);
|
|
14422
|
+
return writeJson(res, 200, {
|
|
14423
|
+
ok: true,
|
|
14424
|
+
status: "session_refresh_required",
|
|
14425
|
+
});
|
|
14426
|
+
}
|
|
14427
|
+
|
|
14137
14428
|
if (url.pathname === "/api/hazbase/logout" && req.method === "POST") {
|
|
14138
14429
|
const session = requireMutatingApiSession(req, res, config, state);
|
|
14139
14430
|
if (!session) return;
|
|
@@ -14656,6 +14947,164 @@ if (url.pathname === "/api/hazbase/logout" && req.method === "POST") {
|
|
|
14656
14947
|
});
|
|
14657
14948
|
}
|
|
14658
14949
|
|
|
14950
|
+
|
|
14951
|
+
if (url.pathname === "/api/payments/x402/hazbase-wallet" && req.method === "POST") {
|
|
14952
|
+
const hookSecret = req.headers["x-viveworker-hook-secret"] || "";
|
|
14953
|
+
if (!config.sessionSecret || hookSecret !== config.sessionSecret) {
|
|
14954
|
+
return writeJson(res, 401, { error: "unauthorized" });
|
|
14955
|
+
}
|
|
14956
|
+
|
|
14957
|
+
let body;
|
|
14958
|
+
try {
|
|
14959
|
+
body = await parseJsonBody(req);
|
|
14960
|
+
} catch {
|
|
14961
|
+
return writeJson(res, 400, { error: "invalid-json-body" });
|
|
14962
|
+
}
|
|
14963
|
+
|
|
14964
|
+
const approval = createHazbaseWalletPaymentApproval({ config, body });
|
|
14965
|
+
if (!approval) {
|
|
14966
|
+
return writeJson(res, 400, { error: "invalid-hazbase-wallet-payment-request" });
|
|
14967
|
+
}
|
|
14968
|
+
if (runtime.nativeApprovalsByRequestKey.has(approval.requestKey)) {
|
|
14969
|
+
return writeJson(res, 409, { error: "hazbase-wallet-payment-already-pending" });
|
|
14970
|
+
}
|
|
14971
|
+
|
|
14972
|
+
runtime.nativeApprovalsByToken.set(approval.token, approval);
|
|
14973
|
+
runtime.nativeApprovalsByRequestKey.set(approval.requestKey, approval);
|
|
14974
|
+
|
|
14975
|
+
deliverWebPushItem({
|
|
14976
|
+
config,
|
|
14977
|
+
state,
|
|
14978
|
+
kind: "approval",
|
|
14979
|
+
tab: "inbox",
|
|
14980
|
+
subtab: "pending",
|
|
14981
|
+
token: approval.token,
|
|
14982
|
+
stableId: pendingApprovalStableId(approval),
|
|
14983
|
+
title: approval.title,
|
|
14984
|
+
body: approval.messageText,
|
|
14985
|
+
buildLocalizedContent: () => ({ title: approval.title, body: approval.messageText }),
|
|
14986
|
+
}).catch((error) => {
|
|
14987
|
+
console.error(`[hazbase-wallet-payment-push] ${approval.requestKey} | ${error.message}`);
|
|
14988
|
+
});
|
|
14989
|
+
|
|
14990
|
+
const waitMs = Math.max(10_000, Math.min(900_000, Number(body.timeoutMs) || 600_000));
|
|
14991
|
+
const decisionPromise = new Promise((resolve) => {
|
|
14992
|
+
approval.resolveClaudeWaiter = resolve;
|
|
14993
|
+
});
|
|
14994
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
14995
|
+
setTimeout(() => resolve({ paid: false, decision: "timeout", error: "hazbase-wallet-payment-timeout" }), waitMs);
|
|
14996
|
+
});
|
|
14997
|
+
const decision = await Promise.race([decisionPromise, timeoutPromise]);
|
|
14998
|
+
|
|
14999
|
+
if (!approval.resolved) {
|
|
15000
|
+
approval.resolved = true;
|
|
15001
|
+
approval.resolving = false;
|
|
15002
|
+
runtime.nativeApprovalsByToken.delete(approval.token);
|
|
15003
|
+
runtime.nativeApprovalsByRequestKey.delete(approval.requestKey);
|
|
15004
|
+
}
|
|
15005
|
+
|
|
15006
|
+
if (decision && typeof decision === "object" && decision.paid === true) {
|
|
15007
|
+
return writeJson(res, 200, decision);
|
|
15008
|
+
}
|
|
15009
|
+
const error = decision?.error || decision?.decision || "hazbase-wallet-payment-declined";
|
|
15010
|
+
const statusCode = error === "hazbase-wallet-payment-timeout" ? 408 : 403;
|
|
15011
|
+
return writeJson(res, statusCode, { paid: false, error, decision: decision?.decision || "decline" });
|
|
15012
|
+
}
|
|
15013
|
+
|
|
15014
|
+
const hazbaseWalletPayMatch = url.pathname.match(/^\/api\/payments\/x402\/hazbase-wallet\/([a-f0-9]+)\/pay$/u);
|
|
15015
|
+
if (hazbaseWalletPayMatch && req.method === "POST") {
|
|
15016
|
+
const session = requireMutatingApiSession(req, res, config, state);
|
|
15017
|
+
if (!session) return;
|
|
15018
|
+
const token = hazbaseWalletPayMatch[1];
|
|
15019
|
+
const approval = runtime.nativeApprovalsByToken.get(token);
|
|
15020
|
+
if (!approval || approval.kind !== "hazbase_wallet_payment") {
|
|
15021
|
+
return writeJson(res, 404, { error: "approval-not-found" });
|
|
15022
|
+
}
|
|
15023
|
+
if (approval.resolved || approval.resolving) {
|
|
15024
|
+
return writeJson(res, 409, { error: "approval-already-handled" });
|
|
15025
|
+
}
|
|
15026
|
+
approval.resolving = true;
|
|
15027
|
+
try {
|
|
15028
|
+
const result = await completeHazbaseWalletPaymentApproval({ config, runtime, state, approval });
|
|
15029
|
+
return writeJson(res, 200, result);
|
|
15030
|
+
} catch (error) {
|
|
15031
|
+
approval.resolving = false;
|
|
15032
|
+
console.error(`[hazbase-wallet-payment-error] ${approval.requestKey} | ${error?.stack || error?.message || error}`);
|
|
15033
|
+
return writeJson(res, Number(error?.statusCode) || 500, { error: error?.code || error?.message || "hazbase-wallet-payment-failed" });
|
|
15034
|
+
}
|
|
15035
|
+
}
|
|
15036
|
+
|
|
15037
|
+
if (url.pathname === "/api/payments/x402/approval" && req.method === "POST") {
|
|
15038
|
+
const hookSecret = req.headers["x-viveworker-hook-secret"] || "";
|
|
15039
|
+
if (!config.sessionSecret || hookSecret !== config.sessionSecret) {
|
|
15040
|
+
return writeJson(res, 401, { error: "unauthorized" });
|
|
15041
|
+
}
|
|
15042
|
+
|
|
15043
|
+
let body;
|
|
15044
|
+
try {
|
|
15045
|
+
body = await parseJsonBody(req);
|
|
15046
|
+
} catch {
|
|
15047
|
+
return writeJson(res, 400, { error: "invalid-json-body" });
|
|
15048
|
+
}
|
|
15049
|
+
|
|
15050
|
+
const approval = createX402PaymentApproval({ config, body });
|
|
15051
|
+
if (!approval) {
|
|
15052
|
+
return writeJson(res, 400, { error: "invalid-payment-approval-request" });
|
|
15053
|
+
}
|
|
15054
|
+
if (runtime.nativeApprovalsByRequestKey.has(approval.requestKey)) {
|
|
15055
|
+
return writeJson(res, 409, { error: "payment-approval-already-pending" });
|
|
15056
|
+
}
|
|
15057
|
+
|
|
15058
|
+
runtime.nativeApprovalsByToken.set(approval.token, approval);
|
|
15059
|
+
runtime.nativeApprovalsByRequestKey.set(approval.requestKey, approval);
|
|
15060
|
+
|
|
15061
|
+
deliverWebPushItem({
|
|
15062
|
+
config,
|
|
15063
|
+
state,
|
|
15064
|
+
kind: "approval",
|
|
15065
|
+
tab: "inbox",
|
|
15066
|
+
subtab: "pending",
|
|
15067
|
+
token: approval.token,
|
|
15068
|
+
stableId: pendingApprovalStableId(approval),
|
|
15069
|
+
title: approval.title,
|
|
15070
|
+
body: approval.messageText,
|
|
15071
|
+
buildLocalizedContent: () => ({
|
|
15072
|
+
title: approval.title,
|
|
15073
|
+
body: approval.messageText,
|
|
15074
|
+
}),
|
|
15075
|
+
}).catch((error) => {
|
|
15076
|
+
console.error(`[payment-approval-push] ${approval.requestKey} | ${error.message}`);
|
|
15077
|
+
});
|
|
15078
|
+
|
|
15079
|
+
const waitMs = Math.max(10_000, Math.min(900_000, Number(body.timeoutMs) || 600_000));
|
|
15080
|
+
const decisionPromise = new Promise((resolve) => {
|
|
15081
|
+
approval.resolveClaudeWaiter = resolve;
|
|
15082
|
+
});
|
|
15083
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
15084
|
+
setTimeout(() => resolve("timeout"), waitMs);
|
|
15085
|
+
});
|
|
15086
|
+
const decision = await Promise.race([decisionPromise, timeoutPromise]);
|
|
15087
|
+
|
|
15088
|
+
if (!approval.resolved) {
|
|
15089
|
+
approval.resolved = true;
|
|
15090
|
+
approval.resolving = false;
|
|
15091
|
+
runtime.nativeApprovalsByToken.delete(approval.token);
|
|
15092
|
+
runtime.nativeApprovalsByRequestKey.delete(approval.requestKey);
|
|
15093
|
+
}
|
|
15094
|
+
|
|
15095
|
+
if (decision === "accept") {
|
|
15096
|
+
return writeJson(res, 200, {
|
|
15097
|
+
approved: true,
|
|
15098
|
+
decision,
|
|
15099
|
+
approvedPayment: approval.rawParams,
|
|
15100
|
+
});
|
|
15101
|
+
}
|
|
15102
|
+
if (decision === "timeout") {
|
|
15103
|
+
return writeJson(res, 408, { approved: false, decision, error: "payment-approval-timeout" });
|
|
15104
|
+
}
|
|
15105
|
+
return writeJson(res, 403, { approved: false, decision: "decline", error: "payment-approval-declined" });
|
|
15106
|
+
}
|
|
15107
|
+
|
|
14659
15108
|
if (url.pathname === "/api/providers/claude/events" && req.method === "POST") {
|
|
14660
15109
|
const hookSecret = req.headers["x-viveworker-hook-secret"] || "";
|
|
14661
15110
|
if (!config.sessionSecret || hookSecret !== config.sessionSecret) {
|
|
@@ -15473,6 +15922,10 @@ if (url.pathname === "/api/hazbase/logout" && req.method === "POST") {
|
|
|
15473
15922
|
if (approval.resolved || approval.resolving) {
|
|
15474
15923
|
return writeJson(res, 409, { error: "approval-already-handled" });
|
|
15475
15924
|
}
|
|
15925
|
+
if (approval.kind === "hazbase_wallet_payment") {
|
|
15926
|
+
console.warn(`[hazbase-wallet-payment-generic-decision-blocked] ${approval.requestKey} | ${decision}`);
|
|
15927
|
+
return writeJson(res, 409, { error: "hazbase-wallet-payment-requires-wallet-pay-route" });
|
|
15928
|
+
}
|
|
15476
15929
|
|
|
15477
15930
|
approval.resolving = true;
|
|
15478
15931
|
try {
|
|
@@ -16067,6 +16520,21 @@ if (url.pathname === "/api/hazbase/logout" && req.method === "POST") {
|
|
|
16067
16520
|
if (approval.resolved || approval.resolving) {
|
|
16068
16521
|
return writeApprovalHandled(res, req, approval.title, 409, config.defaultLocale);
|
|
16069
16522
|
}
|
|
16523
|
+
if (approval.kind === "hazbase_wallet_payment") {
|
|
16524
|
+
console.warn(`[hazbase-wallet-payment-native-decision-blocked] ${approval.requestKey} | ${decision}`);
|
|
16525
|
+
if (requestWantsHtml(req)) {
|
|
16526
|
+
return writeHtml(
|
|
16527
|
+
res,
|
|
16528
|
+
409,
|
|
16529
|
+
renderStatusPage({
|
|
16530
|
+
title: approval.title,
|
|
16531
|
+
body: "This approval must be completed from the viveworker app with the wallet payment button.",
|
|
16532
|
+
tone: "warn",
|
|
16533
|
+
})
|
|
16534
|
+
);
|
|
16535
|
+
}
|
|
16536
|
+
return writeJson(res, 409, { error: "hazbase-wallet-payment-requires-wallet-pay-route" });
|
|
16537
|
+
}
|
|
16070
16538
|
|
|
16071
16539
|
approval.resolving = true;
|
|
16072
16540
|
try {
|
|
@@ -17453,6 +17921,9 @@ function buildConfig(cli) {
|
|
|
17453
17921
|
hazbaseApiUrl: stripTrailingSlash(process.env.HAZBASE_API_URL || "https://api.hazbase.com"),
|
|
17454
17922
|
hazbaseClientKey: cleanText(process.env.HAZBASE_CLIENT_KEY || "viveworker-internal"),
|
|
17455
17923
|
hazbaseDeviceLabel: cleanText(process.env.HAZBASE_DEVICE_LABEL || "viveworker"),
|
|
17924
|
+
npmPackageName: cleanText(process.env.VIVEWORKER_NPM_PACKAGE || "viveworker"),
|
|
17925
|
+
npmDistTag: cleanText(process.env.VIVEWORKER_NPM_DIST_TAG || "latest"),
|
|
17926
|
+
npmRegistryUrl: stripTrailingSlash(process.env.NPM_REGISTRY_URL || "https://registry.npmjs.org"),
|
|
17456
17927
|
webUiEnabled: boolEnv("WEB_UI_ENABLED", true),
|
|
17457
17928
|
authRequired: boolEnv("AUTH_REQUIRED", true),
|
|
17458
17929
|
webPushEnabled: boolEnv("WEB_PUSH_ENABLED", false),
|
|
@@ -17553,7 +18024,54 @@ function normalizeHazbaseState(raw) {
|
|
|
17553
18024
|
highTrustToken: cleanText(value.highTrustToken ?? ""),
|
|
17554
18025
|
highTrustExpiresAt: cleanText(value.highTrustExpiresAt ?? ""),
|
|
17555
18026
|
accounts: Array.isArray(value.accounts) ? value.accounts : [],
|
|
18027
|
+
sessionInvalid: value.sessionInvalid === true,
|
|
18028
|
+
sessionInvalidReason: cleanText(value.sessionInvalidReason ?? ""),
|
|
18029
|
+
sessionInvalidAt: cleanText(value.sessionInvalidAt ?? ""),
|
|
18030
|
+
};
|
|
18031
|
+
}
|
|
18032
|
+
|
|
18033
|
+
function isHazbaseInvalidAppSessionError(error) {
|
|
18034
|
+
const details = error?.details && typeof error.details === "object" ? error.details : {};
|
|
18035
|
+
const statusCode = Number(error?.statusCode || error?.status || details.statusCode || 0);
|
|
18036
|
+
const text = [
|
|
18037
|
+
error?.code,
|
|
18038
|
+
error?.message,
|
|
18039
|
+
details.errorCode,
|
|
18040
|
+
details.code,
|
|
18041
|
+
details.error,
|
|
18042
|
+
details.message,
|
|
18043
|
+
]
|
|
18044
|
+
.map((entry) => cleanText(entry || "").toLowerCase())
|
|
18045
|
+
.filter(Boolean)
|
|
18046
|
+
.join(" ");
|
|
18047
|
+
return statusCode === 401 && text.includes("invalid app session");
|
|
18048
|
+
}
|
|
18049
|
+
|
|
18050
|
+
function markHazbaseSessionInvalid(state, reason = "invalid_app_session") {
|
|
18051
|
+
const hazbase = normalizeHazbaseState(state.hazbase);
|
|
18052
|
+
state.hazbase = {
|
|
18053
|
+
...hazbase,
|
|
18054
|
+
accessToken: "",
|
|
18055
|
+
sessionId: "",
|
|
18056
|
+
highTrustToken: "",
|
|
18057
|
+
highTrustExpiresAt: "",
|
|
18058
|
+
sessionInvalid: true,
|
|
18059
|
+
sessionInvalidReason: cleanText(reason || "invalid_app_session"),
|
|
18060
|
+
sessionInvalidAt: new Date().toISOString(),
|
|
17556
18061
|
};
|
|
18062
|
+
return state.hazbase;
|
|
18063
|
+
}
|
|
18064
|
+
|
|
18065
|
+
async function maybeWriteHazbaseSessionExpiredResponse({ error, config, state, res }) {
|
|
18066
|
+
if (!isHazbaseInvalidAppSessionError(error)) {
|
|
18067
|
+
return false;
|
|
18068
|
+
}
|
|
18069
|
+
markHazbaseSessionInvalid(state);
|
|
18070
|
+
await saveState(config.stateFile, state);
|
|
18071
|
+
return writeJson(res, 401, {
|
|
18072
|
+
error: "hazbase-session-expired",
|
|
18073
|
+
message: "Hazbase wallet session expired. Sign in again from Wallet settings.",
|
|
18074
|
+
});
|
|
17557
18075
|
}
|
|
17558
18076
|
|
|
17559
18077
|
function hazbasePasskeyLocalHostError() {
|
|
@@ -17627,6 +18145,117 @@ async function fetchHazbaseMetadata(config, endpoint, timeoutMs = HAZBASE_METADA
|
|
|
17627
18145
|
}
|
|
17628
18146
|
}
|
|
17629
18147
|
|
|
18148
|
+
async function getNpmVersionStatus(runtime, config) {
|
|
18149
|
+
const now = Date.now();
|
|
18150
|
+
const cached = runtime.npmVersionStatusCache;
|
|
18151
|
+
if (cached && now - Number(cached.checkedAtMs || 0) < NPM_VERSION_CHECK_CACHE_TTL_MS) {
|
|
18152
|
+
return cached;
|
|
18153
|
+
}
|
|
18154
|
+
if (runtime.npmVersionStatusPending) {
|
|
18155
|
+
return runtime.npmVersionStatusPending;
|
|
18156
|
+
}
|
|
18157
|
+
runtime.npmVersionStatusPending = fetchNpmVersionStatus(config)
|
|
18158
|
+
.then((status) => {
|
|
18159
|
+
runtime.npmVersionStatusCache = status;
|
|
18160
|
+
return status;
|
|
18161
|
+
})
|
|
18162
|
+
.catch((error) => {
|
|
18163
|
+
const fallback = {
|
|
18164
|
+
packageName: config.npmPackageName || "viveworker",
|
|
18165
|
+
distTag: config.npmDistTag || "latest",
|
|
18166
|
+
currentVersion: appPackageVersion,
|
|
18167
|
+
latestVersion: "",
|
|
18168
|
+
updateAvailable: false,
|
|
18169
|
+
checkedAtMs: Date.now(),
|
|
18170
|
+
error: cleanText(error?.message || "npm version check failed"),
|
|
18171
|
+
};
|
|
18172
|
+
runtime.npmVersionStatusCache = fallback;
|
|
18173
|
+
return fallback;
|
|
18174
|
+
})
|
|
18175
|
+
.finally(() => {
|
|
18176
|
+
runtime.npmVersionStatusPending = null;
|
|
18177
|
+
});
|
|
18178
|
+
return runtime.npmVersionStatusPending;
|
|
18179
|
+
}
|
|
18180
|
+
|
|
18181
|
+
async function fetchNpmVersionStatus(config) {
|
|
18182
|
+
const packageName = cleanText(config.npmPackageName || "viveworker") || "viveworker";
|
|
18183
|
+
const distTag = cleanText(config.npmDistTag || "latest") || "latest";
|
|
18184
|
+
const registryBaseUrl = stripTrailingSlash(config.npmRegistryUrl || "https://registry.npmjs.org");
|
|
18185
|
+
const endpoint = `${registryBaseUrl}/${encodeURIComponent(packageName).replace(/^%40/u, "@")}/${encodeURIComponent(distTag)}`;
|
|
18186
|
+
const controller = new AbortController();
|
|
18187
|
+
const timer = setTimeout(() => controller.abort(), NPM_VERSION_CHECK_TIMEOUT_MS);
|
|
18188
|
+
timer.unref?.();
|
|
18189
|
+
try {
|
|
18190
|
+
const response = await fetch(endpoint, {
|
|
18191
|
+
method: "GET",
|
|
18192
|
+
headers: { Accept: "application/json" },
|
|
18193
|
+
signal: controller.signal,
|
|
18194
|
+
});
|
|
18195
|
+
if (!response.ok) {
|
|
18196
|
+
throw new Error(`npm registry returned ${response.status}`);
|
|
18197
|
+
}
|
|
18198
|
+
const payload = await response.json().catch(() => null);
|
|
18199
|
+
const latestVersion = cleanText(payload?.version || "");
|
|
18200
|
+
if (!latestVersion) {
|
|
18201
|
+
throw new Error("npm registry response did not include a version");
|
|
18202
|
+
}
|
|
18203
|
+
return {
|
|
18204
|
+
packageName,
|
|
18205
|
+
distTag,
|
|
18206
|
+
currentVersion: appPackageVersion,
|
|
18207
|
+
latestVersion,
|
|
18208
|
+
updateAvailable: comparePackageVersions(latestVersion, appPackageVersion) > 0,
|
|
18209
|
+
checkedAtMs: Date.now(),
|
|
18210
|
+
error: "",
|
|
18211
|
+
};
|
|
18212
|
+
} catch (error) {
|
|
18213
|
+
if (error?.name === "AbortError") {
|
|
18214
|
+
throw new Error("npm version check timed out");
|
|
18215
|
+
}
|
|
18216
|
+
throw error;
|
|
18217
|
+
} finally {
|
|
18218
|
+
clearTimeout(timer);
|
|
18219
|
+
}
|
|
18220
|
+
}
|
|
18221
|
+
|
|
18222
|
+
function comparePackageVersions(left, right) {
|
|
18223
|
+
const a = parsePackageVersion(left);
|
|
18224
|
+
const b = parsePackageVersion(right);
|
|
18225
|
+
for (let index = 0; index < 3; index += 1) {
|
|
18226
|
+
if (a.parts[index] !== b.parts[index]) return a.parts[index] > b.parts[index] ? 1 : -1;
|
|
18227
|
+
}
|
|
18228
|
+
if (!a.prerelease.length && b.prerelease.length) return 1;
|
|
18229
|
+
if (a.prerelease.length && !b.prerelease.length) return -1;
|
|
18230
|
+
const length = Math.max(a.prerelease.length, b.prerelease.length);
|
|
18231
|
+
for (let index = 0; index < length; index += 1) {
|
|
18232
|
+
const av = a.prerelease[index];
|
|
18233
|
+
const bv = b.prerelease[index];
|
|
18234
|
+
if (av == null && bv == null) return 0;
|
|
18235
|
+
if (av == null) return -1;
|
|
18236
|
+
if (bv == null) return 1;
|
|
18237
|
+
const an = /^\d+$/u.test(av) ? Number(av) : null;
|
|
18238
|
+
const bn = /^\d+$/u.test(bv) ? Number(bv) : null;
|
|
18239
|
+
if (an != null && bn != null && an !== bn) return an > bn ? 1 : -1;
|
|
18240
|
+
if (an != null && bn == null) return -1;
|
|
18241
|
+
if (an == null && bn != null) return 1;
|
|
18242
|
+
const cmp = av.localeCompare(bv);
|
|
18243
|
+
if (cmp !== 0) return cmp > 0 ? 1 : -1;
|
|
18244
|
+
}
|
|
18245
|
+
return 0;
|
|
18246
|
+
}
|
|
18247
|
+
|
|
18248
|
+
function parsePackageVersion(value) {
|
|
18249
|
+
const normalized = cleanText(value || "").replace(/^v/iu, "").split("+")[0];
|
|
18250
|
+
const [core, prerelease = ""] = normalized.split("-", 2);
|
|
18251
|
+
const parts = core.split(".").map((part) => Number(part)).map((part) => (Number.isFinite(part) && part >= 0 ? part : 0));
|
|
18252
|
+
while (parts.length < 3) parts.push(0);
|
|
18253
|
+
return {
|
|
18254
|
+
parts: parts.slice(0, 3),
|
|
18255
|
+
prerelease: prerelease ? prerelease.split(".").map((part) => cleanText(part)).filter(Boolean) : [],
|
|
18256
|
+
};
|
|
18257
|
+
}
|
|
18258
|
+
|
|
17630
18259
|
function mergeHazbaseAccountSummaries(existing, incoming) {
|
|
17631
18260
|
const merged = new Map();
|
|
17632
18261
|
for (const entry of [...(Array.isArray(existing) ? existing : []), ...(Array.isArray(incoming) ? incoming : [])]) {
|
|
@@ -17644,6 +18273,124 @@ function resolveHazbaseAccountForChain(accounts, chainId) {
|
|
|
17644
18273
|
return list.find((entry) => Number(entry?.chainId) === normalizedChainId && cleanText(entry?.smartAccountAddress || "")) || null;
|
|
17645
18274
|
}
|
|
17646
18275
|
|
|
18276
|
+
|
|
18277
|
+
async function completeHazbaseWalletPaymentApproval({ config, runtime, state, approval }) {
|
|
18278
|
+
const hazbase = normalizeHazbaseState(state.hazbase);
|
|
18279
|
+
if (!hazbase.accessToken) throw codedError("hazbase-auth-required", 401);
|
|
18280
|
+
if (!hazbase.deviceBindingId) throw codedError("hazbase-passkey-required", 409);
|
|
18281
|
+
if (!hazbase.highTrustToken) throw codedError("hazbase-reauth-required", 409);
|
|
18282
|
+
const chainId = Number(approval.rawParams?.chainId || 0);
|
|
18283
|
+
const account = resolveHazbaseAccountForChain(hazbase.accounts, chainId);
|
|
18284
|
+
if (!account?.smartAccountAddress) throw codedError("hazbase-wallet-account-missing", 409);
|
|
18285
|
+
|
|
18286
|
+
let result;
|
|
18287
|
+
try {
|
|
18288
|
+
result = await postHazbaseJson(config, "/api/payments/x402/hazbase-wallet/pay", {
|
|
18289
|
+
paymentRequestId: approval.paymentRequestId,
|
|
18290
|
+
deviceBindingId: hazbase.deviceBindingId,
|
|
18291
|
+
highTrustToken: hazbase.highTrustToken,
|
|
18292
|
+
smartAccountAddress: account.smartAccountAddress,
|
|
18293
|
+
waitForReceipt: true,
|
|
18294
|
+
}, hazbase.accessToken, 130_000);
|
|
18295
|
+
} catch (error) {
|
|
18296
|
+
if (isHazbaseInvalidAppSessionError(error)) {
|
|
18297
|
+
markHazbaseSessionInvalid(state);
|
|
18298
|
+
await saveState(config.stateFile, state);
|
|
18299
|
+
throw codedError("hazbase-session-expired", 401, {
|
|
18300
|
+
message: "Hazbase wallet session expired. Sign in again from Wallet settings.",
|
|
18301
|
+
});
|
|
18302
|
+
}
|
|
18303
|
+
throw error;
|
|
18304
|
+
}
|
|
18305
|
+
|
|
18306
|
+
if (!result?.xPayment) {
|
|
18307
|
+
throw codedError(result?.errorCode || result?.error || "hazbase-wallet-payment-failed", 502);
|
|
18308
|
+
}
|
|
18309
|
+
|
|
18310
|
+
const payload = {
|
|
18311
|
+
paid: true,
|
|
18312
|
+
decision: "accept",
|
|
18313
|
+
paymentRequestId: approval.paymentRequestId,
|
|
18314
|
+
xPayment: cleanText(result.xPayment || ""),
|
|
18315
|
+
payer: cleanText(result.payer || account.smartAccountAddress || ""),
|
|
18316
|
+
chainId: Number(result.chainId || chainId),
|
|
18317
|
+
network: cleanText(result.network || approval.rawParams?.network || ""),
|
|
18318
|
+
relayMode: cleanText(result.relayMode || ""),
|
|
18319
|
+
submittedUserOpHash: cleanText(result.submittedUserOpHash || ""),
|
|
18320
|
+
transactionHash: cleanText(result.transactionHash || ""),
|
|
18321
|
+
payment: result,
|
|
18322
|
+
};
|
|
18323
|
+
|
|
18324
|
+
approval.resolved = true;
|
|
18325
|
+
approval.resolving = false;
|
|
18326
|
+
runtime.nativeApprovalsByToken.delete(approval.token);
|
|
18327
|
+
runtime.nativeApprovalsByRequestKey.delete(approval.requestKey);
|
|
18328
|
+
approval.resolveClaudeWaiter?.(payload);
|
|
18329
|
+
const stateChanged = recordActionHistoryItem({
|
|
18330
|
+
config,
|
|
18331
|
+
runtime,
|
|
18332
|
+
state,
|
|
18333
|
+
kind: "approval",
|
|
18334
|
+
stableId: `approval:${approval.requestKey}:${Date.now()}`,
|
|
18335
|
+
token: approval.token,
|
|
18336
|
+
title: approval.title,
|
|
18337
|
+
threadLabel: approval.threadLabel || "",
|
|
18338
|
+
messageText: `${approvalDecisionMessage("accept", config.defaultLocale, approval.provider)}\n\n${approval.messageText}\n\nTransaction: ${payload.transactionHash || payload.submittedUserOpHash}`,
|
|
18339
|
+
summary: formatNotificationBody(approval.messageText, 160) || approvalDecisionMessage("accept", config.defaultLocale, approval.provider),
|
|
18340
|
+
fileRefs: [],
|
|
18341
|
+
diffText: "",
|
|
18342
|
+
diffSource: "",
|
|
18343
|
+
diffAvailable: false,
|
|
18344
|
+
diffAddedLines: 0,
|
|
18345
|
+
diffRemovedLines: 0,
|
|
18346
|
+
outcome: "approved",
|
|
18347
|
+
provider: approval.provider || "viveworker",
|
|
18348
|
+
});
|
|
18349
|
+
if (stateChanged) await saveState(config.stateFile, state);
|
|
18350
|
+
console.log(`[hazbase-wallet-payment] ${approval.requestKey} | ${payload.transactionHash || payload.submittedUserOpHash}`);
|
|
18351
|
+
return { ok: true, ...payload };
|
|
18352
|
+
}
|
|
18353
|
+
|
|
18354
|
+
async function postHazbaseJson(config, endpoint, body, bearerToken, timeoutMs = 30_000) {
|
|
18355
|
+
const baseUrl = stripTrailingSlash(config?.hazbaseApiUrl || "");
|
|
18356
|
+
if (!baseUrl) throw codedError("hazbase-api-not-configured", 503);
|
|
18357
|
+
const controller = new AbortController();
|
|
18358
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
18359
|
+
timer.unref?.();
|
|
18360
|
+
try {
|
|
18361
|
+
const response = await fetch(`${baseUrl}${endpoint}`, {
|
|
18362
|
+
method: "POST",
|
|
18363
|
+
headers: {
|
|
18364
|
+
"content-type": "application/json",
|
|
18365
|
+
accept: "application/json",
|
|
18366
|
+
authorization: `Bearer ${bearerToken}`,
|
|
18367
|
+
"x-request-id": `viveworker_${crypto.randomUUID()}`,
|
|
18368
|
+
},
|
|
18369
|
+
body: JSON.stringify(body),
|
|
18370
|
+
signal: controller.signal,
|
|
18371
|
+
});
|
|
18372
|
+
const payload = await response.json().catch(() => null);
|
|
18373
|
+
const data = payload?.data || payload || {};
|
|
18374
|
+
if (!response.ok) {
|
|
18375
|
+
throw codedError(data?.errorCode || data?.code || data?.error || `hazbase-api-${response.status}`, response.status, data);
|
|
18376
|
+
}
|
|
18377
|
+
return data;
|
|
18378
|
+
} catch (error) {
|
|
18379
|
+
if (error?.name === "AbortError") throw codedError("hazbase-api-timeout", 504);
|
|
18380
|
+
throw error;
|
|
18381
|
+
} finally {
|
|
18382
|
+
clearTimeout(timer);
|
|
18383
|
+
}
|
|
18384
|
+
}
|
|
18385
|
+
|
|
18386
|
+
function codedError(code, statusCode = 500, details = null) {
|
|
18387
|
+
const error = new Error(code);
|
|
18388
|
+
error.code = code;
|
|
18389
|
+
error.statusCode = statusCode;
|
|
18390
|
+
error.details = details;
|
|
18391
|
+
return error;
|
|
18392
|
+
}
|
|
18393
|
+
|
|
17647
18394
|
function validateConfig(config) {
|
|
17648
18395
|
let publicBaseUrl = null;
|
|
17649
18396
|
try {
|