viveworker 0.8.4 → 0.8.6

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.
@@ -65,12 +65,21 @@ const REMOTE_PAIRING_RELAY_TOKEN_ROTATION_MS = 30 * 24 * 60 * 60 * 1000;
65
65
  const DEFAULT_COMPLETION_REPLY_IMAGE_MAX_BYTES = 15 * 1024 * 1024;
66
66
  const DEFAULT_COMPLETION_REPLY_UPLOAD_TTL_MS = 24 * 60 * 60 * 1000;
67
67
  const DEFAULT_COMPLETION_REPLY_ACK_TIMEOUT_MS = 1200;
68
+ const DEFAULT_COMPLETION_REPLY_THREAD_REOPEN_WAIT_MS = 1800;
68
69
  const MAX_COMPLETION_REPLY_IMAGE_COUNT = 4;
69
70
  const COMPLETION_PUSH_CONTENT_DEDUPE_WINDOW_MS = 2 * 60 * 1000;
70
71
  const HAZBASE_METADATA_TIMEOUT_MS = 1500;
71
72
  const NPM_VERSION_CHECK_TIMEOUT_MS = 2500;
72
73
  const NPM_VERSION_CHECK_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
73
74
  const TIMELINE_ACTIVITY_TTL_MS = 2 * 60 * 1000;
75
+ const PAYMENT_NETWORKS = {
76
+ base: { family: "evm", chainId: 8453, asset: "usdc", assets: ["usdc"], scheme: "exact", label: "Base", releaseStatus: "comingSoon" },
77
+ "base-sepolia": { family: "evm", chainId: 84532, asset: "usdc", assets: ["usdc"], scheme: "exact", label: "Base Sepolia" },
78
+ polygon: { family: "evm", chainId: 137, asset: "usdc", assets: ["usdc", "jpyc"], scheme: "exact", label: "Polygon", releaseStatus: "comingSoon" },
79
+ "polygon-amoy": { family: "evm", chainId: 80002, asset: "usdc", assets: ["usdc", "jpyc"], scheme: "exact", label: "Polygon Amoy" },
80
+ liquidtestnet: { family: "liquid", chainId: null, asset: "usdt", assets: ["usdt", "lbtc"], scheme: "exact-liquid-pset", label: "Liquid Testnet" },
81
+ liquidv1: { family: "liquid", chainId: null, asset: "usdt", assets: ["usdt", "lbtc"], scheme: "exact-liquid-pset", label: "Liquid", releaseStatus: "comingSoon" },
82
+ };
74
83
 
75
84
  // Shared memo for buildDiffThreadGroups. Each call spawns 3 git subprocesses
76
85
  // per tracked repo (`git diff --name-status`, `git status --porcelain`,
@@ -806,7 +815,7 @@ function localizedMcpApprovalPushContent(locale, approval) {
806
815
 
807
816
  function localizedPaymentApprovalPushContent(locale, approval) {
808
817
  const payment = isPlainObject(approval?.rawParams) ? approval.rawParams : {};
809
- const amount = cleanText(payment.amountUsdc || payment.amountAtomic || "");
818
+ const amount = cleanText(`${payment.amountUsdc || payment.amountAtomic || ""} ${payment.assetLabel || ""}`.trim());
810
819
  const network = cleanText(payment.network || "");
811
820
  const resource = cleanText(payment.resource || payment.url || "");
812
821
  const titleKey = cleanText(approval?.kind || "") === "hazbase_wallet_payment"
@@ -7962,7 +7971,7 @@ function createX402PaymentApproval({ config, body, now = Date.now() }) {
7962
7971
  const requestId = cleanText(body.paymentRequestId || body.requestId || crypto.randomUUID());
7963
7972
  const requestKey = `payment:${requestId}`;
7964
7973
  const title = payment.amountUsdc
7965
- ? `Payment approval — ${payment.amountUsdc} USDC`
7974
+ ? `Payment approval — ${payment.amountUsdc} ${payment.assetLabel || "USDC"}`
7966
7975
  : "Payment approval";
7967
7976
  return {
7968
7977
  token,
@@ -8001,7 +8010,7 @@ function createHazbaseWalletPaymentApproval({ config, body, now = Date.now() })
8001
8010
  const token = crypto.randomBytes(18).toString("hex");
8002
8011
  const requestKey = `hazbase_wallet_payment:${paymentRequestId}`;
8003
8012
  const title = payment.amountUsdc
8004
- ? `Hazbase wallet payment — ${payment.amountUsdc} USDC`
8013
+ ? `Hazbase wallet payment — ${payment.amountUsdc} ${payment.assetLabel || "USDC"}`
8005
8014
  : "Hazbase wallet payment";
8006
8015
  return {
8007
8016
  token,
@@ -8036,7 +8045,7 @@ function formatHazbaseWalletPaymentApprovalMessage(payment) {
8036
8045
  const lines = [
8037
8046
  "Hazbase Smart Wallet payment requested.",
8038
8047
  "",
8039
- `Amount: ${payment.amountUsdc || payment.amountAtomic} USDC`,
8048
+ `Amount: ${payment.amountUsdc || payment.amountAtomic} ${payment.assetLabel || "USDC"}`,
8040
8049
  `Network: ${payment.network} (chainId ${payment.chainId})`,
8041
8050
  `Pay to: ${payment.payTo}`,
8042
8051
  `Asset: ${payment.asset}`,
@@ -8053,15 +8062,16 @@ function normalizeX402PaymentApprovalBody(body) {
8053
8062
  if (!isPlainObject(body)) return null;
8054
8063
  const payment = isPlainObject(body.payment) ? body.payment : {};
8055
8064
  const network = cleanText(payment.network || "");
8056
- const chainId = Number(payment.chainId) || 0;
8065
+ const chainId = payment.chainId === null ? null : Number(payment.chainId) || 0;
8057
8066
  const amountAtomic = cleanText(payment.amountAtomic || "");
8058
- const amountUsdc = cleanText(payment.amountUsdc || "");
8067
+ const amountUsdc = cleanText(payment.amountUsdc || payment.amount || "");
8068
+ const assetLabel = cleanText(payment.assetLabel || "");
8059
8069
  const payTo = cleanText(payment.payTo || "");
8060
8070
  const asset = cleanText(payment.asset || "");
8061
8071
  const resource = cleanText(payment.resource || body.url || "");
8062
8072
  const description = cleanText(payment.description || "");
8063
8073
  const url = cleanText(body.url || resource);
8064
- if (!network || !chainId || !amountAtomic || !payTo || !asset || !resource) {
8074
+ if (!network || (chainId !== null && !chainId) || !amountAtomic || !payTo || !asset || !resource) {
8065
8075
  return null;
8066
8076
  }
8067
8077
  return {
@@ -8070,6 +8080,7 @@ function normalizeX402PaymentApprovalBody(body) {
8070
8080
  chainId,
8071
8081
  amountAtomic,
8072
8082
  amountUsdc,
8083
+ assetLabel,
8073
8084
  payTo,
8074
8085
  asset,
8075
8086
  resource,
@@ -8081,8 +8092,8 @@ function formatX402PaymentApprovalMessage(payment) {
8081
8092
  const lines = [
8082
8093
  "x402 payment approval requested.",
8083
8094
  "",
8084
- `Amount: ${payment.amountUsdc || payment.amountAtomic} USDC`,
8085
- `Network: ${payment.network} (chainId ${payment.chainId})`,
8095
+ `Amount: ${payment.amountUsdc || payment.amountAtomic} ${payment.assetLabel || "USDC"}`,
8096
+ `Network: ${payment.network}${payment.chainId === null ? "" : ` (chainId ${payment.chainId})`}`,
8086
8097
  `Pay to: ${payment.payTo}`,
8087
8098
  `Asset: ${payment.asset}`,
8088
8099
  `Resource: ${payment.resource}`,
@@ -9640,6 +9651,50 @@ function isIpcNoClientFoundError(errorValue) {
9640
9651
  message.includes("client not found");
9641
9652
  }
9642
9653
 
9654
+ function codexThreadDeepLink(conversationId) {
9655
+ const id = cleanText(conversationId || "");
9656
+ if (!id || !/^[0-9a-z-]{16,}$/iu.test(id)) {
9657
+ return "";
9658
+ }
9659
+ return `codex://local/${encodeURIComponent(id)}`;
9660
+ }
9661
+
9662
+ function openCodexThreadBestEffort(conversationId) {
9663
+ if (process.platform !== "darwin") {
9664
+ return false;
9665
+ }
9666
+ const url = codexThreadDeepLink(conversationId);
9667
+ if (!url) {
9668
+ return false;
9669
+ }
9670
+ try {
9671
+ const child = spawn("open", [url], {
9672
+ detached: true,
9673
+ stdio: "ignore",
9674
+ });
9675
+ child.unref?.();
9676
+ console.log(`[completion-reply] reopened Codex thread via ${url}`);
9677
+ return true;
9678
+ } catch (error) {
9679
+ console.warn(
9680
+ `[completion-reply] failed to reopen Codex thread=${cleanText(conversationId || "") || "unknown"} ` +
9681
+ `error=${cleanText(error?.message || error || "") || "unknown"}`
9682
+ );
9683
+ return false;
9684
+ }
9685
+ }
9686
+
9687
+ async function waitForCodexThreadOwner(runtime, conversationId, timeoutMs) {
9688
+ const deadline = Date.now() + Math.max(0, Number(timeoutMs) || 0);
9689
+ while (Date.now() <= deadline) {
9690
+ if (runtime.threadOwnerClientIds.has(conversationId)) {
9691
+ return true;
9692
+ }
9693
+ await sleep(100);
9694
+ }
9695
+ return false;
9696
+ }
9697
+
9643
9698
  function buildDefaultCollaborationMode(threadState) {
9644
9699
  // Fallback turns must leave Plan mode unless the caller explicitly opts in.
9645
9700
  return buildRequestedCollaborationMode(threadState, "default");
@@ -14344,7 +14399,6 @@ async function handleCompletionReply({
14344
14399
  stagedWorkspaceImagePaths
14345
14400
  );
14346
14401
  let lastError = null;
14347
- const ownerClientId = runtime.threadOwnerClientIds.get(conversationId) ?? null;
14348
14402
  const finalizeReplyAccepted = async () => {
14349
14403
  if (timelineImageAliases.length > 0) {
14350
14404
  const aliases = isPlainObject(state.timelineImagePathAliases)
@@ -14357,46 +14411,76 @@ async function handleCompletionReply({
14357
14411
  }
14358
14412
  scheduleBestEffortFileCleanup(stagedWorkspaceImagePaths);
14359
14413
  };
14414
+ const sendReplyCandidate = async (candidate, attemptLabel = "initial") => {
14415
+ const currentOwnerClientId = runtime.threadOwnerClientIds.get(conversationId) ?? null;
14416
+ console.log(
14417
+ `[completion-reply] try candidate=${candidate.name} attempt=${attemptLabel} transport=${cleanText(candidate.transport || "thread-follower")} owner=${cleanText(currentOwnerClientId || "") || "none"} images=${normalizedLocalImagePaths.length} workspaceImages=${stagedWorkspaceImagePaths.length} cwd=${cleanText(resolvedCwd || "") || "none"}`
14418
+ );
14419
+ if (candidate.transport === "direct-turn-start" && currentOwnerClientId) {
14420
+ return runtime.ipcClient.startTurnDirect(
14421
+ conversationId,
14422
+ candidate.turnStartParams,
14423
+ currentOwnerClientId,
14424
+ { timeoutMs: config.completionReplyAckTimeoutMs }
14425
+ );
14426
+ }
14427
+ return runtime.ipcClient.startTurn(
14428
+ conversationId,
14429
+ candidate.turnStartParams,
14430
+ currentOwnerClientId,
14431
+ { timeoutMs: config.completionReplyAckTimeoutMs }
14432
+ );
14433
+ };
14434
+ let reopenedCodexThread = false;
14360
14435
 
14361
14436
  for (const candidate of turnCandidates) {
14362
14437
  try {
14363
- console.log(
14364
- `[completion-reply] try candidate=${candidate.name} transport=${cleanText(candidate.transport || "thread-follower")} owner=${cleanText(ownerClientId || "") || "none"} images=${normalizedLocalImagePaths.length} workspaceImages=${stagedWorkspaceImagePaths.length} cwd=${cleanText(resolvedCwd || "") || "none"}`
14365
- );
14366
- if (candidate.transport === "direct-turn-start" && ownerClientId) {
14367
- await runtime.ipcClient.startTurnDirect(
14368
- conversationId,
14369
- candidate.turnStartParams,
14370
- ownerClientId,
14371
- { timeoutMs: config.completionReplyAckTimeoutMs }
14372
- );
14373
- } else {
14374
- await runtime.ipcClient.startTurn(
14375
- conversationId,
14376
- candidate.turnStartParams,
14377
- ownerClientId,
14378
- { timeoutMs: config.completionReplyAckTimeoutMs }
14379
- );
14380
- }
14438
+ await sendReplyCandidate(candidate);
14381
14439
  console.log(
14382
14440
  `[completion-reply] success at=${new Date().toISOString()} candidate=${candidate.name} transport=${cleanText(candidate.transport || "thread-follower")}`
14383
14441
  );
14384
14442
  await finalizeReplyAccepted();
14385
14443
  return { alreadyAccepted: false };
14386
14444
  } catch (error) {
14387
- if (isStartTurnAckTimeout(error)) {
14445
+ let candidateError = error;
14446
+ if (isIpcNoClientFoundError(candidateError) && !reopenedCodexThread) {
14447
+ reopenedCodexThread = true;
14448
+ runtime.threadOwnerClientIds.delete(conversationId);
14449
+ const reopened = openCodexThreadBestEffort(conversationId);
14450
+ if (reopened) {
14451
+ const ownerReady = await waitForCodexThreadOwner(
14452
+ runtime,
14453
+ conversationId,
14454
+ config.completionReplyThreadReopenWaitMs
14455
+ );
14456
+ console.log(
14457
+ `[completion-reply] retry after Codex thread reopen candidate=${candidate.name} ownerReady=${ownerReady ? 1 : 0}`
14458
+ );
14459
+ try {
14460
+ await sendReplyCandidate(candidate, "after-reopen");
14461
+ console.log(
14462
+ `[completion-reply] success at=${new Date().toISOString()} candidate=${candidate.name} transport=${cleanText(candidate.transport || "thread-follower")} reopened=1`
14463
+ );
14464
+ await finalizeReplyAccepted();
14465
+ return { alreadyAccepted: false, reopenedCodexThread: true };
14466
+ } catch (retryError) {
14467
+ candidateError = retryError;
14468
+ }
14469
+ }
14470
+ }
14471
+ if (isStartTurnAckTimeout(candidateError)) {
14388
14472
  // Codex can create the turn but fail to send the bridge an ACK before
14389
14473
  // its internal follower timeout fires. Retrying risks duplicate user
14390
14474
  // turns, so treat this as accepted and let the timeline catch up.
14391
14475
  console.log(
14392
- `[completion-reply] accepted at=${new Date().toISOString()} candidate=${candidate.name} transport=${cleanText(candidate.transport || "thread-follower")} ack-timeout=${normalizeIpcErrorMessage(error)}`
14476
+ `[completion-reply] accepted at=${new Date().toISOString()} candidate=${candidate.name} transport=${cleanText(candidate.transport || "thread-follower")} ack-timeout=${normalizeIpcErrorMessage(candidateError)}`
14393
14477
  );
14394
14478
  await finalizeReplyAccepted();
14395
14479
  return { alreadyAccepted: false, ackTimeout: true };
14396
14480
  }
14397
- lastError = error;
14481
+ lastError = candidateError;
14398
14482
  console.log(
14399
- `[completion-reply] failed candidate=${candidate.name} transport=${cleanText(candidate.transport || "thread-follower")} error=${normalizeIpcErrorMessage(error)} raw=${inspect(error?.ipcError ?? error, { depth: 6, breakLength: 160 })}`
14483
+ `[completion-reply] failed candidate=${candidate.name} transport=${cleanText(candidate.transport || "thread-follower")} error=${normalizeIpcErrorMessage(candidateError)} raw=${inspect(candidateError?.ipcError ?? candidateError, { depth: 6, breakLength: 160 })}`
14400
14484
  );
14401
14485
  }
14402
14486
  }
@@ -16641,12 +16725,26 @@ if (url.pathname === "/api/hazbase/status" && req.method === "GET") {
16641
16725
  errors.push(chainsResult.reason?.message || String(chainsResult.reason || "chains metadata unavailable"));
16642
16726
  }
16643
16727
  const supportedChains = Array.isArray(chains?.chains)
16644
- ? chains.chains.filter((entry) => Number(entry.chainId) === 8453 || Number(entry.chainId) === 84532)
16728
+ ? chains.chains.filter((entry) => Boolean(paymentNetworkForChainId(Number(entry.chainId))))
16729
+ .filter((entry) => isPaymentNetworkAvailable(paymentNetworkForChainId(Number(entry.chainId))))
16645
16730
  : [];
16646
16731
  const payoutAddresses = {
16647
- 8453: resolveHazbaseAccountForChain(hazbase.accounts, 8453)?.smartAccountAddress || "",
16648
16732
  84532: resolveHazbaseAccountForChain(hazbase.accounts, 84532)?.smartAccountAddress || "",
16733
+ 80002: resolveHazbaseAccountForChain(hazbase.accounts, 80002)?.smartAccountAddress || "",
16649
16734
  };
16735
+ const rawPaymentCapabilities = buildPaymentCapabilities({ hazbase, payments });
16736
+ const paymentCapabilities = applyAgentPaymentDefaults(rawPaymentCapabilities, hazbase.agentPaymentDefaults);
16737
+ const agentPaymentDefaults = agentPaymentDefaultsStatus(hazbase.agentPaymentDefaults, rawPaymentCapabilities);
16738
+ const supportedPayments = (Array.isArray(payments?.networks) ? payments.networks : [])
16739
+ .filter((entry) => isPaymentNetworkAvailable(normalizePaymentNetworkKey(entry?.network || entry?.id || "")));
16740
+ const defaultPaymentNetwork = isPaymentNetworkAvailable(normalizePaymentNetworkKey(payments?.defaultNetwork || ""))
16741
+ ? payments.defaultNetwork
16742
+ : "base-sepolia";
16743
+ for (const capability of paymentCapabilities) {
16744
+ if (capability.payTo) {
16745
+ payoutAddresses[capability.network] = capability.payTo;
16746
+ }
16747
+ }
16650
16748
  const signedIn = Boolean(hazbase.accessToken) && !hazbase.sessionInvalid;
16651
16749
  return writeJson(res, 200, {
16652
16750
  enabled: true,
@@ -16663,9 +16761,11 @@ if (url.pathname === "/api/hazbase/status" && req.method === "GET") {
16663
16761
  credentialId: hazbase.credentialId,
16664
16762
  highTrustExpiresAt: hazbase.highTrustExpiresAt,
16665
16763
  accounts: hazbase.accounts,
16666
- supportedPayments: payments?.networks || [],
16764
+ paymentCapabilities,
16765
+ agentPaymentDefaults,
16766
+ supportedPayments,
16667
16767
  supportedChains,
16668
- defaultPaymentNetwork: payments?.defaultNetwork || "base-sepolia",
16768
+ defaultPaymentNetwork,
16669
16769
  payoutAddresses,
16670
16770
  error: errors.join(" | "),
16671
16771
  });
@@ -16680,16 +16780,48 @@ if (url.pathname === "/api/hazbase/payout-address" && req.method === "GET") {
16680
16780
  }
16681
16781
  const hazbase = normalizeHazbaseState(state.hazbase);
16682
16782
  if (!hazbase.accessToken) return writeJson(res, 401, { error: "hazbase-auth-required" });
16683
- const chainId = Number(url.searchParams.get("chainId") || 0);
16684
- if (chainId !== 8453 && chainId !== 84532) {
16783
+ const networkParam = normalizePaymentNetworkKey(url.searchParams.get("network") || "");
16784
+ if (networkParam && PAYMENT_NETWORKS[networkParam]?.family === "liquid") {
16785
+ if (!isPaymentNetworkAvailable(networkParam)) {
16786
+ return writeJson(res, 409, { error: "payment-network-coming-soon" });
16787
+ }
16788
+ const capability = resolvePaymentCapability(
16789
+ buildPaymentCapabilities({ hazbase, payments: null }),
16790
+ networkParam,
16791
+ url.searchParams.get("asset") || "usdt",
16792
+ );
16793
+ if (!capability?.payTo) return writeJson(res, 409, { error: "hazbase-wallet-account-missing" });
16794
+ return writeJson(res, 200, {
16795
+ chainId: null,
16796
+ network: networkParam,
16797
+ asset: capability.asset,
16798
+ scheme: capability.scheme,
16799
+ payoutMethod: "external_liquid",
16800
+ payoutAddress: capability.payTo,
16801
+ payTo: capability.payTo,
16802
+ issuer: capability.issuer || "external",
16803
+ });
16804
+ }
16805
+ const chainId = networkParam && PAYMENT_NETWORKS[networkParam]?.family === "evm"
16806
+ ? Number(PAYMENT_NETWORKS[networkParam].chainId || 0)
16807
+ : Number(url.searchParams.get("chainId") || 0);
16808
+ if (!paymentNetworkForChainId(chainId)) {
16685
16809
  return writeJson(res, 400, { error: "unsupported-chain" });
16686
16810
  }
16811
+ if (!isPaymentNetworkAvailable(paymentNetworkForChainId(chainId))) {
16812
+ return writeJson(res, 409, { error: "payment-network-coming-soon" });
16813
+ }
16814
+ const evmNetwork = paymentNetworkForChainId(chainId);
16815
+ const asset = normalizePaymentAssetKey(url.searchParams.get("asset") || PAYMENT_NETWORKS[evmNetwork]?.asset, evmNetwork);
16816
+ if (!asset) return writeJson(res, 400, { error: "unsupported-payment-asset" });
16687
16817
  const account = resolveHazbaseAccountForChain(hazbase.accounts, chainId);
16688
16818
  if (!account?.smartAccountAddress) {
16689
16819
  return writeJson(res, 409, { error: "hazbase-wallet-account-missing" });
16690
16820
  }
16691
16821
  return writeJson(res, 200, {
16692
16822
  chainId,
16823
+ network: evmNetwork,
16824
+ asset,
16693
16825
  payoutMethod: "hazbase_wallet",
16694
16826
  payoutAddress: account.smartAccountAddress,
16695
16827
  smartAccountAddress: account.smartAccountAddress,
@@ -16867,9 +16999,12 @@ if (url.pathname === "/api/hazbase/account/bootstrap" && req.method === "POST")
16867
16999
  }
16868
17000
  const body = await parseJsonBody(req);
16869
17001
  const chainId = Number(body?.chainId || 0);
16870
- if (chainId !== 8453 && chainId !== 84532) {
17002
+ if (!paymentNetworkForChainId(chainId)) {
16871
17003
  return writeJson(res, 400, { error: "unsupported-chain" });
16872
17004
  }
17005
+ if (!isPaymentNetworkAvailable(paymentNetworkForChainId(chainId))) {
17006
+ return writeJson(res, 409, { error: "payment-network-coming-soon" });
17007
+ }
16873
17008
  let result;
16874
17009
  try {
16875
17010
  result = await bootstrapPasskeyAccount({
@@ -16900,6 +17035,88 @@ if (url.pathname === "/api/hazbase/account/bootstrap" && req.method === "POST")
16900
17035
  return writeJson(res, 200, result);
16901
17036
  }
16902
17037
 
17038
+ if (url.pathname === "/api/hazbase/payment-capability" && req.method === "POST") {
17039
+ const session = requireMutatingApiSession(req, res, config, state);
17040
+ if (!session) return;
17041
+ const hazbase = normalizeHazbaseState(state.hazbase);
17042
+ if (!hazbase.accessToken) return writeJson(res, 401, { error: "hazbase-auth-required" });
17043
+ const body = await parseJsonBody(req);
17044
+ const network = normalizePaymentNetworkKey(body?.network);
17045
+ if (!network || PAYMENT_NETWORKS[network]?.family !== "liquid") {
17046
+ return writeJson(res, 400, { error: "unsupported-payment-network" });
17047
+ }
17048
+ if (!isPaymentNetworkAvailable(network)) {
17049
+ return writeJson(res, 409, { error: "payment-network-coming-soon" });
17050
+ }
17051
+ const asset = normalizePaymentAssetKey(body?.asset || "usdt", network);
17052
+ if (!asset) return writeJson(res, 400, { error: "unsupported-payment-asset" });
17053
+ const payTo = cleanText(body?.payTo || body?.payoutAddress || "").toLowerCase();
17054
+ if (!isValidLiquidAddressForNetwork(payTo, network)) {
17055
+ return writeJson(res, 400, { error: "invalid-liquid-address" });
17056
+ }
17057
+ const nextCapability = {
17058
+ network,
17059
+ asset,
17060
+ scheme: PAYMENT_NETWORKS[network].scheme,
17061
+ payoutMethod: "external_liquid",
17062
+ payTo,
17063
+ configured: true,
17064
+ issuer: "external",
17065
+ };
17066
+ const existing = normalizeStoredPaymentCapabilities(hazbase.paymentCapabilities)
17067
+ .filter((entry) => !(entry.network === network && entry.asset === asset));
17068
+ state.hazbase = {
17069
+ ...hazbase,
17070
+ paymentCapabilities: [...existing, nextCapability],
17071
+ sessionInvalid: false,
17072
+ sessionInvalidReason: "",
17073
+ sessionInvalidAt: "",
17074
+ };
17075
+ await saveState(config.stateFile, state);
17076
+ return writeJson(res, 200, { ok: true, capability: nextCapability });
17077
+ }
17078
+
17079
+ if (url.pathname === "/api/hazbase/agent-payment-defaults" && req.method === "POST") {
17080
+ const session = requireMutatingApiSession(req, res, config, state);
17081
+ if (!session) return;
17082
+ const hazbase = normalizeHazbaseState(state.hazbase);
17083
+ const body = await parseJsonBody(req);
17084
+ const mode = cleanText(body?.mode || "").toLowerCase() === "configured" ? "configured" : "custom";
17085
+ let nextDefaults;
17086
+ if (mode === "configured") {
17087
+ nextDefaults = { mode: "configured", accepts: [] };
17088
+ } else {
17089
+ const capabilities = buildPaymentCapabilities({ hazbase, payments: null });
17090
+ const configuredKeys = new Set(
17091
+ capabilities
17092
+ .filter((entry) => entry?.configured && entry?.enabled !== false && isPaymentNetworkAvailable(entry.network))
17093
+ .map((entry) => paymentCapabilityKey(entry))
17094
+ .filter(Boolean)
17095
+ );
17096
+ const rawAccepts = Array.isArray(body?.accepts) ? body.accepts : [];
17097
+ const accepts = [];
17098
+ const seen = new Set();
17099
+ for (const entry of rawAccepts) {
17100
+ const ref = paymentCapabilityRef(entry);
17101
+ if (!ref) return writeJson(res, 400, { error: "unsupported-payment-capability" });
17102
+ const key = `${ref.network}:${ref.asset}`;
17103
+ if (!configuredKeys.has(key)) {
17104
+ return writeJson(res, 409, { error: "payment-capability-not-configured", network: ref.network, asset: ref.asset });
17105
+ }
17106
+ if (seen.has(key)) continue;
17107
+ seen.add(key);
17108
+ accepts.push(ref);
17109
+ }
17110
+ nextDefaults = { mode: "custom", accepts };
17111
+ }
17112
+ state.hazbase = {
17113
+ ...hazbase,
17114
+ agentPaymentDefaults: nextDefaults,
17115
+ };
17116
+ await saveState(config.stateFile, state);
17117
+ return writeJson(res, 200, { ok: true, agentPaymentDefaults: nextDefaults });
17118
+ }
17119
+
16903
17120
  if (url.pathname === "/api/hazbase/session/refresh" && req.method === "POST") {
16904
17121
  const hookSecret = req.headers["x-viveworker-hook-secret"] || "";
16905
17122
  const hookAuth = config.sessionSecret && hookSecret === config.sessionSecret;
@@ -21246,6 +21463,10 @@ function buildConfig(cli) {
21246
21463
  "COMPLETION_REPLY_ACK_TIMEOUT_MS",
21247
21464
  DEFAULT_COMPLETION_REPLY_ACK_TIMEOUT_MS
21248
21465
  ),
21466
+ completionReplyThreadReopenWaitMs: numberEnv(
21467
+ "COMPLETION_REPLY_THREAD_REOPEN_WAIT_MS",
21468
+ DEFAULT_COMPLETION_REPLY_THREAD_REOPEN_WAIT_MS
21469
+ ),
21249
21470
  choicePageSize: numberEnv("CHOICE_PAGE_SIZE", 5),
21250
21471
  completionReplyImageMaxBytes: numberEnv(
21251
21472
  "COMPLETION_REPLY_IMAGE_MAX_BYTES",
@@ -21291,6 +21512,8 @@ function normalizeHazbaseState(raw) {
21291
21512
  highTrustToken: cleanText(value.highTrustToken ?? ""),
21292
21513
  highTrustExpiresAt: cleanText(value.highTrustExpiresAt ?? ""),
21293
21514
  accounts,
21515
+ paymentCapabilities: normalizeStoredPaymentCapabilities(value.paymentCapabilities),
21516
+ agentPaymentDefaults: normalizeAgentPaymentDefaults(value.agentPaymentDefaults),
21294
21517
  sessionInvalid: value.sessionInvalid === true,
21295
21518
  sessionInvalidReason: cleanText(value.sessionInvalidReason ?? ""),
21296
21519
  sessionInvalidAt: cleanText(value.sessionInvalidAt ?? ""),
@@ -21551,6 +21774,202 @@ function resolveHazbaseAccountForChain(accounts, chainId) {
21551
21774
  return list.find((entry) => Number(entry?.chainId) === normalizedChainId && cleanText(entry?.smartAccountAddress || "")) || null;
21552
21775
  }
21553
21776
 
21777
+ function normalizePaymentNetworkKey(raw) {
21778
+ const value = cleanText(raw || "").toLowerCase();
21779
+ if (value === "basesepolia") return "base-sepolia";
21780
+ if (value === "polygonamoy" || value === "amoy") return "polygon-amoy";
21781
+ if (value === "matic") return "polygon";
21782
+ if (value === "liquid" || value === "liquid-mainnet") return "liquidv1";
21783
+ return PAYMENT_NETWORKS[value] ? value : "";
21784
+ }
21785
+
21786
+ function isPaymentNetworkAvailable(network) {
21787
+ return PAYMENT_NETWORKS[network]?.releaseStatus !== "comingSoon";
21788
+ }
21789
+
21790
+ function paymentNetworkForChainId(chainId) {
21791
+ const id = Number(chainId || 0);
21792
+ return Object.entries(PAYMENT_NETWORKS).find(([, entry]) => entry.chainId === id)?.[0] || "";
21793
+ }
21794
+
21795
+ function normalizePaymentAssetKey(raw, network) {
21796
+ const networkInfo = PAYMENT_NETWORKS[network] || null;
21797
+ const value = cleanText(raw || networkInfo?.asset || "").toLowerCase();
21798
+ if (value === "usd-t" || value === "tether") return "usdt";
21799
+ if (value === "jpy" || value === "jpy-coin") return "jpyc";
21800
+ if (value === "bitcoin" || value === "btc") return "lbtc";
21801
+ const allowed = Array.isArray(networkInfo?.assets) ? networkInfo.assets : [networkInfo?.asset].filter(Boolean);
21802
+ if ((value === "usdc" || value === "jpyc" || value === "usdt" || value === "lbtc") && allowed.includes(value)) return value;
21803
+ return "";
21804
+ }
21805
+
21806
+ function isValidLiquidAddressForNetwork(address, network) {
21807
+ const value = cleanText(address || "").toLowerCase();
21808
+ if (network === "liquidtestnet") return /^(tlq1|tex1)[0-9a-z]{20,}$/u.test(value);
21809
+ if (network === "liquidv1") return /^(lq1|ex1)[0-9a-z]{20,}$/u.test(value);
21810
+ return false;
21811
+ }
21812
+
21813
+ function normalizeStoredPaymentCapabilities(value) {
21814
+ const list = Array.isArray(value) ? value : [];
21815
+ const out = [];
21816
+ for (const entry of list) {
21817
+ const network = normalizePaymentNetworkKey(entry?.network);
21818
+ if (!network || PAYMENT_NETWORKS[network]?.family !== "liquid") continue;
21819
+ const asset = normalizePaymentAssetKey(entry?.asset, network);
21820
+ const payTo = cleanText(entry?.payTo || entry?.payoutAddress || "").toLowerCase();
21821
+ if (!asset || !isValidLiquidAddressForNetwork(payTo, network)) continue;
21822
+ out.push({
21823
+ network,
21824
+ asset,
21825
+ scheme: PAYMENT_NETWORKS[network].scheme,
21826
+ payoutMethod: "external_liquid",
21827
+ payTo,
21828
+ configured: true,
21829
+ issuer: cleanText(entry?.issuer || "external") || "external",
21830
+ });
21831
+ }
21832
+ return out;
21833
+ }
21834
+
21835
+ function paymentCapabilityRef(entry) {
21836
+ const network = normalizePaymentNetworkKey(entry?.network);
21837
+ const asset = normalizePaymentAssetKey(entry?.asset, network);
21838
+ if (!network || !asset) return null;
21839
+ return { network, asset };
21840
+ }
21841
+
21842
+ function paymentCapabilityKey(entry) {
21843
+ const ref = paymentCapabilityRef(entry);
21844
+ return ref ? `${ref.network}:${ref.asset}` : "";
21845
+ }
21846
+
21847
+ function normalizeAgentPaymentDefaults(value) {
21848
+ const raw = value && typeof value === "object" ? value : {};
21849
+ const mode = cleanText(raw.mode || "").toLowerCase() === "custom" ? "custom" : "configured";
21850
+ const list = Array.isArray(raw.accepts) ? raw.accepts : Array.isArray(value) ? value : [];
21851
+ const accepts = [];
21852
+ const seen = new Set();
21853
+ for (const entry of list) {
21854
+ const ref = paymentCapabilityRef(entry);
21855
+ if (!ref || !isPaymentNetworkAvailable(ref.network)) continue;
21856
+ const key = `${ref.network}:${ref.asset}`;
21857
+ if (seen.has(key)) continue;
21858
+ seen.add(key);
21859
+ accepts.push(ref);
21860
+ }
21861
+ return { mode, accepts };
21862
+ }
21863
+
21864
+ function effectiveAgentPaymentDefaultRefs(defaults, capabilities) {
21865
+ const normalized = normalizeAgentPaymentDefaults(defaults);
21866
+ const configured = (Array.isArray(capabilities) ? capabilities : [])
21867
+ .filter((entry) => entry?.configured && entry?.enabled !== false && isPaymentNetworkAvailable(entry.network))
21868
+ .map((entry) => paymentCapabilityRef(entry))
21869
+ .filter(Boolean);
21870
+ if (normalized.mode !== "custom") {
21871
+ return configured;
21872
+ }
21873
+ const configuredKeys = new Set(configured.map((entry) => `${entry.network}:${entry.asset}`));
21874
+ return normalized.accepts.filter((entry) => configuredKeys.has(`${entry.network}:${entry.asset}`));
21875
+ }
21876
+
21877
+ function applyAgentPaymentDefaults(capabilities, defaults) {
21878
+ const effective = effectiveAgentPaymentDefaultRefs(defaults, capabilities);
21879
+ const enabledKeys = new Set(effective.map((entry) => `${entry.network}:${entry.asset}`));
21880
+ return (Array.isArray(capabilities) ? capabilities : []).map((entry) => ({
21881
+ ...entry,
21882
+ agentEnabled: enabledKeys.has(paymentCapabilityKey(entry)),
21883
+ }));
21884
+ }
21885
+
21886
+ function agentPaymentDefaultsStatus(defaults, capabilities) {
21887
+ const normalized = normalizeAgentPaymentDefaults(defaults);
21888
+ return {
21889
+ mode: normalized.mode,
21890
+ accepts: normalized.accepts,
21891
+ effectiveAccepts: effectiveAgentPaymentDefaultRefs(normalized, capabilities),
21892
+ };
21893
+ }
21894
+
21895
+ function liquidCapabilityFromEnv(network) {
21896
+ if (!isPaymentNetworkAvailable(network)) return null;
21897
+ const suffix = network === "liquidtestnet" ? "LIQUIDTESTNET" : "LIQUIDV1";
21898
+ const payTo = cleanText(process.env[`VIVEWORKER_${suffix}_PAYOUT_ADDRESS`] || process.env[`VIVEWORKER_${suffix}_PAY_TO`] || "").toLowerCase();
21899
+ if (!isValidLiquidAddressForNetwork(payTo, network)) return null;
21900
+ return {
21901
+ network,
21902
+ asset: "usdt",
21903
+ scheme: PAYMENT_NETWORKS[network].scheme,
21904
+ payoutMethod: "external_liquid",
21905
+ payTo,
21906
+ configured: true,
21907
+ issuer: "env",
21908
+ };
21909
+ }
21910
+
21911
+ function buildPaymentCapabilities({ hazbase, payments }) {
21912
+ const supported = Array.isArray(payments?.networks) ? payments.networks : [];
21913
+ const supportedByNetwork = new Map(supported.map((entry) => [cleanText(entry?.network || entry?.id || ""), entry]));
21914
+ const capabilities = [];
21915
+ for (const network of ["base-sepolia", "base", "polygon-amoy", "polygon"]) {
21916
+ const meta = PAYMENT_NETWORKS[network];
21917
+ const paymentMeta = supportedByNetwork.get(network) || null;
21918
+ const supportedAssets = Array.isArray(paymentMeta?.assets) && paymentMeta.assets.length > 0
21919
+ ? paymentMeta.assets.map((entry) => normalizePaymentAssetKey(entry?.asset, network)).filter(Boolean)
21920
+ : meta.assets || [meta.asset];
21921
+ const account = resolveHazbaseAccountForChain(hazbase.accounts, meta.chainId);
21922
+ const available = isPaymentNetworkAvailable(network);
21923
+ for (const asset of [...new Set(supportedAssets)]) {
21924
+ capabilities.push({
21925
+ network,
21926
+ family: "evm",
21927
+ chainId: meta.chainId,
21928
+ asset,
21929
+ scheme: "exact",
21930
+ payoutMethod: "hazbase_wallet",
21931
+ payTo: account?.smartAccountAddress || "",
21932
+ smartAccountAddress: account?.smartAccountAddress || "",
21933
+ configured: Boolean(account?.smartAccountAddress),
21934
+ enabled: available && (supportedByNetwork.has(network) || supported.length === 0),
21935
+ label: meta.label,
21936
+ issuer: "hazbase",
21937
+ releaseStatus: meta.releaseStatus || "available",
21938
+ });
21939
+ }
21940
+ }
21941
+ const stored = normalizeStoredPaymentCapabilities(hazbase.paymentCapabilities);
21942
+ for (const network of ["liquidtestnet", "liquidv1"]) {
21943
+ const meta = PAYMENT_NETWORKS[network];
21944
+ const existing = stored.find((entry) => entry.network === network) || liquidCapabilityFromEnv(network);
21945
+ const available = isPaymentNetworkAvailable(network);
21946
+ capabilities.push({
21947
+ network,
21948
+ family: "liquid",
21949
+ chainId: null,
21950
+ asset: existing?.asset || meta.asset,
21951
+ scheme: meta.scheme,
21952
+ payoutMethod: "external_liquid",
21953
+ payTo: existing?.payTo || "",
21954
+ configured: Boolean(existing?.payTo),
21955
+ enabled: available && (supportedByNetwork.has(network) || supported.length === 0),
21956
+ label: meta.label,
21957
+ issuer: existing?.issuer || "external",
21958
+ releaseStatus: meta.releaseStatus || "available",
21959
+ });
21960
+ }
21961
+ return capabilities;
21962
+ }
21963
+
21964
+ function resolvePaymentCapability(capabilities, network, asset = "") {
21965
+ const normalizedNetwork = normalizePaymentNetworkKey(network);
21966
+ const normalizedAsset = normalizePaymentAssetKey(asset, normalizedNetwork);
21967
+ return capabilities.find((entry) => (
21968
+ normalizePaymentNetworkKey(entry.network) === normalizedNetwork &&
21969
+ (!normalizedAsset || normalizePaymentAssetKey(entry.asset, normalizedNetwork) === normalizedAsset)
21970
+ )) || null;
21971
+ }
21972
+
21554
21973
 
21555
21974
  async function finalizeHazbaseWalletPaymentFailure({ config, runtime, state, approval, error }) {
21556
21975
  const errorCode = cleanText(error?.code || error?.message || "hazbase-wallet-payment-failed") || "hazbase-wallet-payment-failed";