shinjuku-shielded 0.3.183 → 0.3.185

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 CHANGED
@@ -6,13 +6,13 @@ answers privately, in a terminal chat or for your agent app. Its `mcp`
6
6
  command runs a local MCP server (stdio) for your agent. Your keys never
7
7
  leave your machine.
8
8
 
9
- This is version 0.3.183. The npm packages `shinjuku-shield` and
9
+ This is version 0.3.185. The npm packages `shinjuku-shield` and
10
10
  `shinjuku-shielded` hold the same files. The command is `shinjuku`.
11
11
 
12
12
  ## Set up
13
13
 
14
14
  ```sh
15
- npx -y shinjuku@0.3.183 setup
15
+ npx -y shinjuku@0.3.185 setup
16
16
  ```
17
17
 
18
18
  Setup offers to install the `shinjuku` command and, on Windows, adds npm's
@@ -35,13 +35,13 @@ Every wallet command works after `shinjuku`, for example `shinjuku balance`,
35
35
  Claude Code:
36
36
 
37
37
  ```sh
38
- claude mcp add --scope user shinjuku -- npx -y --no-audit --no-fund shinjuku-shield@0.3.183 mcp
38
+ claude mcp add --scope user shinjuku -- npx -y --no-audit --no-fund shinjuku-shield@0.3.185 mcp
39
39
  ```
40
40
 
41
41
  Claude Desktop (`claude_desktop_config.json`) or Cursor (`~/.cursor/mcp.json`):
42
42
 
43
43
  ```json
44
- { "mcpServers": { "shinjuku": { "command": "npx", "args": ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.183", "mcp"] } } }
44
+ { "mcpServers": { "shinjuku": { "command": "npx", "args": ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.185", "mcp"] } } }
45
45
  ```
46
46
 
47
47
  On native Windows, if the host cannot find `npx`, use `"command": "cmd"` and
@@ -54,7 +54,7 @@ Codex (the Codex app and Codex CLI read the same file, `~/.codex/config.toml`):
54
54
  ```toml
55
55
  [mcp_servers.shinjuku]
56
56
  command = "npx"
57
- args = ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.183", "mcp"]
57
+ args = ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.185", "mcp"]
58
58
  startup_timeout_sec = 120
59
59
  tool_timeout_sec = 300
60
60
  ```
package/manifest.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schema": "carbon-shielded-wallet/bundle-manifest/v1",
3
3
  "file": "shinjuku-wallet.mjs",
4
- "sha256": "d67bcc6c4e3910c2c10af366bc4f0dfab14e7023805cd9b15ebe92f68ba41098",
5
- "bytes": 9344406,
6
- "sourceCommit": "d57192d0800307fede3af52d006b6fb758fd0362",
4
+ "sha256": "cab378d1d911f907711de448e5c6525febcd0e73ff8988efcb4ae7d1fa82e961",
5
+ "bytes": 9351549,
6
+ "sourceCommit": "0e77401ed1e90cd6fa9035028ac5098ae6abbb6e",
7
7
  "sourceDirty": false,
8
8
  "installRecordMatchesLockfile": true,
9
9
  "releaseRule": "valid only when two fresh npm ci --ignore-scripts builds on different OSes give the same sha256; installRecordMatchesLockfile true means every entry npm installed equals its package-lock.json entry and every entry not marked optional, devOptional included, is installed: an optional entry (a package for another platform) may be absent",
@@ -1038,5 +1038,5 @@
1038
1038
  "version": "1.8.0"
1039
1039
  }
1040
1040
  ],
1041
- "publishedAt": "2026-11-06T15:11:02.328Z"
1041
+ "publishedAt": "2026-11-06T17:17:10.158Z"
1042
1042
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "shinjuku-shielded",
3
- "version": "0.3.183",
3
+ "version": "0.3.185",
4
4
  "mcpName": "io.github.ShinjukuStaition/shinjuku-mcp",
5
5
  "description": "Shinjuku Shielded, the private x402 facilitator on Solana: the self-custody wallet CLI and its MCP server. Shield, pay, and unshield USDC from any MCP agent, under caps you set.",
6
- "shinjukuRelease": "d67bcc6c",
7
- "shinjukuSha256": "d67bcc6c4e3910c2c10af366bc4f0dfab14e7023805cd9b15ebe92f68ba41098",
6
+ "shinjukuRelease": "cab378d1",
7
+ "shinjukuSha256": "cab378d1d911f907711de448e5c6525febcd0e73ff8988efcb4ae7d1fa82e961",
8
8
  "license": "SEE LICENSE IN LICENSE.txt",
9
9
  "author": "Shinjuku Shielded",
10
10
  "homepage": "https://github.com/ShinjukuStaition/shinjuku-shield#readme",
@@ -588,6 +588,16 @@ function processLockAddress(name, platform = process.platform) {
588
588
  if (platform === "linux") return `\0shinjuku-lock-${hash12}`;
589
589
  return join2(tmpdir2(), `shinjuku-lock-${hash12.slice(0, 32)}.sock`);
590
590
  }
591
+ async function processLockFree(name) {
592
+ const address4 = processLockAddress(name);
593
+ try {
594
+ (await listen(address4)).close();
595
+ return true;
596
+ } catch (error) {
597
+ if (!["EADDRINUSE", "EACCES"].includes(error?.code)) throw error;
598
+ }
599
+ return process.platform === "darwin" ? deadSocketFile(address4) : false;
600
+ }
591
601
  async function takeProcessLock(name, {
592
602
  onWait = () => {
593
603
  },
@@ -43143,9 +43153,19 @@ function facilitatorRpcUrl(origin) {
43143
43153
  url = void 0;
43144
43154
  }
43145
43155
  const base = url && (url.protocol === "https:" || url.protocol === "http:") ? payHostOf(url.origin) : DEFAULT_FACILITATOR_ORIGIN;
43146
- relayOrigins.add(base);
43156
+ if (!relayOrigins.has(base)) {
43157
+ relayOrigins.add(base);
43158
+ builtRelayOrigins.add(base);
43159
+ try {
43160
+ process.env[RELAY_ORIGINS_ENV] = [...builtRelayOrigins].join(" ");
43161
+ } catch {
43162
+ }
43163
+ }
43147
43164
  return `${base}${FACILITATOR_RPC_PATH}`;
43148
43165
  }
43166
+ function relayOriginsEnv() {
43167
+ return builtRelayOrigins.size ? { [RELAY_ORIGINS_ENV]: [...builtRelayOrigins].join(" ") } : {};
43168
+ }
43149
43169
  function isShinjukuRelayUrl(input) {
43150
43170
  try {
43151
43171
  const url = new URL(typeof input === "string" || input instanceof URL ? input : input?.url);
@@ -43189,7 +43209,7 @@ function resolveRpcUrl2({ rpcFile, rpc, env = process.env, origin, readFile: rea
43189
43209
  }
43190
43210
  return { url, source: "relay" };
43191
43211
  }
43192
- var DEFAULT_FACILITATOR_ORIGIN, PAY_HOSTS, FACILITATOR_RPC_PATH, RPC_ENV, relayOrigins;
43212
+ var DEFAULT_FACILITATOR_ORIGIN, PAY_HOSTS, FACILITATOR_RPC_PATH, RPC_ENV, RELAY_ORIGINS_ENV, builtRelayOrigins, relayOrigins;
43193
43213
  var init_rpcDefault = __esm({
43194
43214
  "wallet/src/rpcDefault.mjs"() {
43195
43215
  "use strict";
@@ -43201,7 +43221,16 @@ var init_rpcDefault = __esm({
43201
43221
  });
43202
43222
  FACILITATOR_RPC_PATH = "/api/solana-rpc";
43203
43223
  RPC_ENV = "UNDERMARKET_SOLANA_RPC_URL";
43204
- relayOrigins = /* @__PURE__ */ new Set([DEFAULT_FACILITATOR_ORIGIN, ...Object.keys(PAY_HOSTS), ...Object.values(PAY_HOSTS)]);
43224
+ RELAY_ORIGINS_ENV = "SHINJUKU_RELAY_ORIGINS";
43225
+ builtRelayOrigins = new Set(String(process.env[RELAY_ORIGINS_ENV] ?? "").split(/\s+/).flatMap((value2) => {
43226
+ try {
43227
+ const url = new URL(value2);
43228
+ return (url.protocol === "https:" || url.protocol === "http:") && url.origin === value2 ? [value2] : [];
43229
+ } catch {
43230
+ return [];
43231
+ }
43232
+ }));
43233
+ relayOrigins = /* @__PURE__ */ new Set([DEFAULT_FACILITATOR_ORIGIN, ...Object.keys(PAY_HOSTS), ...Object.values(PAY_HOSTS), ...builtRelayOrigins]);
43205
43234
  }
43206
43235
  });
43207
43236
 
@@ -43832,6 +43861,16 @@ async function onOwnTagDispatcher(dispatcher, task) {
43832
43861
  }
43833
43862
  }
43834
43863
  }
43864
+ function routeDispatcherOf(url) {
43865
+ let origin;
43866
+ try {
43867
+ origin = new URL(String(url)).origin;
43868
+ } catch {
43869
+ origin = void 0;
43870
+ }
43871
+ if (origin !== void 0 && routedOrigins.has(origin)) return onionInit({}).dispatcher;
43872
+ return ownTag.getStore()?.dispatcher;
43873
+ }
43835
43874
  function onionInit(init = {}) {
43836
43875
  if (runEnded()) return init;
43837
43876
  const own = ownTagInit(init);
@@ -48313,6 +48352,40 @@ var init_relayLimit = __esm({
48313
48352
  }
48314
48353
  });
48315
48354
 
48355
+ // wallet/src/cli/homeJob.mjs
48356
+ function stepWords(lead, words2, line, { env = process.env, first, inline = false } = {}) {
48357
+ const key = homeKeyOf(words2);
48358
+ const comma = /\bor$/.test(lead) ? "" : ",";
48359
+ if (inHomeJob(env) && key) return `${lead}${comma} ${first ? `${first}, then ` : ""}press [${key}] on the home screen.`;
48360
+ return inline ? `${lead}: ${line}` : `${lead}${comma} run:
48361
+ ${line}`;
48362
+ }
48363
+ function stepName(words2, line, { env = process.env } = {}) {
48364
+ const key = homeKeyOf(words2);
48365
+ return inHomeJob(env) && key ? `press [${key}] on the home screen` : line;
48366
+ }
48367
+ var HOME_JOB_ENV, LAUNCHER_PID_ENV, inHomeJob, HOME_KEY, JOB_NAME, jobLabel, homeKeyOf;
48368
+ var init_homeJob = __esm({
48369
+ "wallet/src/cli/homeJob.mjs"() {
48370
+ "use strict";
48371
+ HOME_JOB_ENV = "SHINJUKU_WALLET_HOME_JOB";
48372
+ LAUNCHER_PID_ENV = "SHINJUKU_WALLET_LAUNCHER_PID";
48373
+ inHomeJob = (env = process.env) => env?.[HOME_JOB_ENV] === "1";
48374
+ HOME_KEY = { "add-funds": "a", unshield: "u", curl: "p", chat: "t", setup: "s", restore: "r", limits: "l" };
48375
+ JOB_NAME = {
48376
+ "add-funds": "add funds",
48377
+ unshield: "send money out",
48378
+ curl: "pay a link",
48379
+ chat: "private AI chat",
48380
+ setup: "setup",
48381
+ restore: "restore",
48382
+ limits: "spending limits"
48383
+ };
48384
+ jobLabel = (name, typed, env = process.env) => inHomeJob(env) && JOB_NAME[name] ? JOB_NAME[name] : typed;
48385
+ homeKeyOf = (words2) => HOME_KEY[String(words2 ?? "").trim().split(/\s+/)[0]];
48386
+ }
48387
+ });
48388
+
48316
48389
  // wallet/src/net/proxy.mjs
48317
48390
  import { spawn } from "node:child_process";
48318
48391
  import { randomBytes as randomBytes5 } from "node:crypto";
@@ -48825,7 +48898,8 @@ function proxiedChild(args, plainProxy, stdio) {
48825
48898
  NODE_USE_ENV_PROXY: "1",
48826
48899
  NODE_OPTIONS: proxiedNodeOptions(process.env.NODE_OPTIONS),
48827
48900
  ...proxyEnv(proxy),
48828
- [MARK]: `tor ${proxy}`
48901
+ [MARK]: `tor ${proxy}`,
48902
+ [LAUNCHER_PID_ENV]: launcherPid()
48829
48903
  };
48830
48904
  delete env[QUIET_RUN_MARK];
48831
48905
  const quiet = quietRun(args);
@@ -48836,7 +48910,7 @@ function reexec(args, plainProxy) {
48836
48910
  return waitForChild(proxiedChild(args, plainProxy, "inherit"));
48837
48911
  }
48838
48912
  function directChild(args) {
48839
- const env = { ...process.env, [DIRECT_RUN_MARK]: "1" };
48913
+ const env = { ...process.env, [DIRECT_RUN_MARK]: "1", [LAUNCHER_PID_ENV]: launcherPid() };
48840
48914
  delete env.NODE_USE_ENV_PROXY;
48841
48915
  if (env.NODE_OPTIONS !== void 0) env.NODE_OPTIONS = withoutEnvProxyFlag(env.NODE_OPTIONS);
48842
48916
  return spawn(
@@ -48981,7 +49055,7 @@ function torChild(argv2, stdio) {
48981
49055
  if (!options.mode) throw new ProxyUsageError("torChild needs --tor");
48982
49056
  return proxiedChild(options.args, options.proxy, stdio);
48983
49057
  }
48984
- var relayKeyOf, READ_ONLY_METHOD, readOnlyPost, TOR_HTTP_TUNNEL, TOR_CHECK, PROOF_TIMEOUT_MS, LOCAL_PROOF_TIMEOUT_MS, LOCAL_PROOF_MAX_BYTES, torProvenAt, proxyProvenAt, MARK, TOR_RUN_STARTED_ENV, ownProxyRefused, ONION_V3, RETRY_AFTER_CAP_MS, RETRY_AFTER_FLOOR_MS, RETRY_AFTER_DEFAULT_MS, RPC_NO_ANSWER_LIMIT_MS, RPC_PACING_CAP_MS, PACING_NOTICE_EVERY_MS, PACING_RATE_WINDOW_MS, TOR_MAX_ROTATIONS, runStartedAt, rpcNotBefore, pacingWaiters, pacingSince, pacedMs, rpcAnswered, lastAnswerAt, readSteps, pacingNoticeAt, pacingSamples, torMoves, torCircuitId, torMovesRefused, pendingMove, inTorRunOf, inTorRun, runNetworkMode, isOnionOrigin, ProxyUsageError, isOnionHost, PROXY_REMOVED, ENV_PROXY_NAMES, OUTER_PROXY_ENV, DIRECT_RUN_MARK, USE_ENV_PROXY_FLAG, withoutEnvProxyFlag, TOR_MIN_VERSION, localTorProof, TOR_EXPERT_BUNDLE, TOR_EXPERT_BUNDLE_SHA256, TOR_EXPERT_BUNDLE_URL, TUNNEL_LINE, TOR_DEB_KEY, TOR_DEB_KEYRING, TOR_INSTALL, torStart, LOCAL_TUNNEL, torStartText, TOR_WINDOW, torNotRunningMessage, torUpdateHow, TOR_STOP_OLD_WINDOWS, torTooOldWhy, torTooOldMessage, NOTICE, TOR_NOTICE_LINE, ONION_DETAIL_LINE, withoutOnionDetail, QUIET_RUN_MARK, QUIET_RUNS, QUIET_TERMINAL_COMMANDS, INFER_SUBCOMMANDS, quietRun, TOR_TROUBLE_LINE, TOR_WAIT_PLAIN, TOR_TROUBLE_PLAIN, promptRedraw, TOR_TROUBLE_SAID, quietTorErr, withRunTag, proxyEnv, pacedSoFar, RESENDABLE_METHOD, torCircuit, GLOBAL_DISPATCHER4, laneDispatchers, laneOfDispatcher, PROXY_WARNING_FLAG, proxiedExecArgv, proxiedNodeOptions, TOR_FROM_CONFIG_ENV, TOR_FROM_CONFIG_HINT, TOR_FROM_CONFIG_SENTENCE, torStarter;
49058
+ var relayKeyOf, READ_ONLY_METHOD, readOnlyPost, TOR_HTTP_TUNNEL, TOR_CHECK, PROOF_TIMEOUT_MS, LOCAL_PROOF_TIMEOUT_MS, LOCAL_PROOF_MAX_BYTES, torProvenAt, proxyProvenAt, MARK, TOR_RUN_STARTED_ENV, ownProxyRefused, ONION_V3, RETRY_AFTER_CAP_MS, RETRY_AFTER_FLOOR_MS, RETRY_AFTER_DEFAULT_MS, RPC_NO_ANSWER_LIMIT_MS, RPC_PACING_CAP_MS, PACING_NOTICE_EVERY_MS, PACING_RATE_WINDOW_MS, TOR_MAX_ROTATIONS, runStartedAt, rpcNotBefore, pacingWaiters, pacingSince, pacedMs, rpcAnswered, lastAnswerAt, readSteps, pacingNoticeAt, pacingSamples, torMoves, torCircuitId, torMovesRefused, pendingMove, inTorRunOf, inTorRun, runNetworkMode, isOnionOrigin, ProxyUsageError, isOnionHost, PROXY_REMOVED, ENV_PROXY_NAMES, OUTER_PROXY_ENV, DIRECT_RUN_MARK, USE_ENV_PROXY_FLAG, withoutEnvProxyFlag, TOR_MIN_VERSION, localTorProof, TOR_EXPERT_BUNDLE, TOR_EXPERT_BUNDLE_SHA256, TOR_EXPERT_BUNDLE_URL, TUNNEL_LINE, TOR_DEB_KEY, TOR_DEB_KEYRING, TOR_INSTALL, torStart, LOCAL_TUNNEL, torStartText, TOR_WINDOW, torNotRunningMessage, torUpdateHow, TOR_STOP_OLD_WINDOWS, torTooOldWhy, torTooOldMessage, NOTICE, TOR_NOTICE_LINE, ONION_DETAIL_LINE, withoutOnionDetail, QUIET_RUN_MARK, QUIET_RUNS, QUIET_TERMINAL_COMMANDS, INFER_SUBCOMMANDS, quietRun, TOR_TROUBLE_LINE, TOR_WAIT_PLAIN, TOR_TROUBLE_PLAIN, promptRedraw, TOR_TROUBLE_SAID, quietTorErr, withRunTag, proxyEnv, pacedSoFar, RESENDABLE_METHOD, torCircuit, GLOBAL_DISPATCHER4, laneDispatchers, laneOfDispatcher, PROXY_WARNING_FLAG, proxiedExecArgv, proxiedNodeOptions, TOR_FROM_CONFIG_ENV, TOR_FROM_CONFIG_HINT, TOR_FROM_CONFIG_SENTENCE, launcherPid, torStarter;
48985
49059
  var init_proxy = __esm({
48986
49060
  "wallet/src/net/proxy.mjs"() {
48987
49061
  "use strict";
@@ -48993,6 +49067,7 @@ var init_proxy = __esm({
48993
49067
  init_relayLimit();
48994
49068
  init_rpcDefault();
48995
49069
  init_phaseTiming();
49070
+ init_homeJob();
48996
49071
  relayKeyOf = (input) => {
48997
49072
  if (!isShinjukuRelayUrl(input)) return void 0;
48998
49073
  return `${new URL(typeof input === "string" || input instanceof URL ? input : input.url).origin}${FACILITATOR_RPC_PATH}`;
@@ -49181,6 +49256,7 @@ ${command}`;
49181
49256
  TOR_FROM_CONFIG_ENV = "SHINJUKU_WALLET_TOR_FROM_CONFIG";
49182
49257
  TOR_FROM_CONFIG_HINT = '. Tor is on for every command because setup recorded it (config.json "tor": true); to run this one command without Tor, add --no-tor (the servers then see your IP address)';
49183
49258
  TOR_FROM_CONFIG_SENTENCE = " Tor is on for every command because you chose it in setup.";
49259
+ launcherPid = () => process.env[LAUNCHER_PID_ENV] ?? String(process.ppid);
49184
49260
  }
49185
49261
  });
49186
49262
 
@@ -83575,7 +83651,7 @@ var init_p03LoopLag = __esm({
83575
83651
  });
83576
83652
 
83577
83653
  // src/server/payments/shielded/p03IndexedReplay.ts
83578
- var import_web3110, ERROR_KINDS, P03_E1_MOVE_INPUTS, MOVE_VALUE_INDICES, MOVE_HEAD_BYTES3, UPGRADEABLE_LOADER, TOKEN_PROGRAM, E1_CHANGELOG_BYTES2, P03_E1_TREE_READ_BYTES, rpcHttpFetch;
83654
+ var import_web3110, P03_E1_BEHIND_RETRY_MS, P03_E1_BEHIND_RETRIES, P03_E1_BEHIND_POLL_MS, P03_E1_BEHIND_KEEPS, ERROR_KINDS, P03_E1_MOVE_INPUTS, MOVE_VALUE_INDICES, MOVE_HEAD_BYTES3, UPGRADEABLE_LOADER, TOKEN_PROGRAM, E1_CHANGELOG_BYTES2, P03_E1_TREE_READ_BYTES, rpcHttpFetch;
83579
83655
  var init_p03IndexedReplay = __esm({
83580
83656
  "src/server/payments/shielded/p03IndexedReplay.ts"() {
83581
83657
  "use strict";
@@ -83587,6 +83663,10 @@ var init_p03IndexedReplay = __esm({
83587
83663
  init_p03MinContextSlotRetry();
83588
83664
  init_p03LoopLag();
83589
83665
  init_ShieldedSolanaP03Types();
83666
+ P03_E1_BEHIND_RETRY_MS = 1e3;
83667
+ P03_E1_BEHIND_RETRIES = 2;
83668
+ P03_E1_BEHIND_POLL_MS = 200;
83669
+ P03_E1_BEHIND_KEEPS = P03_E1_BEHIND_RETRIES * (1 + Math.floor((P03_E1_BEHIND_RETRY_MS - 1) / P03_E1_BEHIND_POLL_MS));
83590
83670
  ERROR_KINDS = /* @__PURE__ */ new Map([
83591
83671
  [E1_ERROR_CODES2.E1NotInGap, "not-in-gap"],
83592
83672
  [E1_ERROR_CODES2.E1LeafModified, "leaf-modified"],
@@ -106022,6 +106102,16 @@ function refundEtaLeft(dueAtMs, now = Date.now()) {
106022
106102
  function floatRefundReadDue(record, now = Date.now()) {
106023
106103
  return Number.isSafeInteger(record?.refundDueAtMs) && now >= record.refundDueAtMs - REFUND_READ_EARLY_MS;
106024
106104
  }
106105
+ function sellerWaitMs(record) {
106106
+ const seconds2 = (record?.sellerRequirement ?? record?.requirement)?.maxTimeoutSeconds;
106107
+ if (!Number.isSafeInteger(seconds2) || seconds2 <= 0 || seconds2 > SELLER_TIMEOUT_ABSURD_SECONDS) return SELLER_TIMEOUT_MS;
106108
+ return Math.min(Math.max(seconds2 * 1e3, SELLER_TIMEOUT_MS), FLOAT_KEEP_BYTES_MS);
106109
+ }
106110
+ function sellerAnswerInit(url, init, ms) {
106111
+ const target = () => init.dispatcher ?? routeDispatcherOf(url) ?? globalThis[GLOBAL_DISPATCHER5];
106112
+ if (target() === void 0) return init;
106113
+ return { ...init, dispatcher: { dispatch: (options, handler) => target().dispatch({ ...options, headersTimeout: ms, bodyTimeout: ms }, handler) } };
106114
+ }
106025
106115
  async function sellerRefusalText(response) {
106026
106116
  let text3 = "";
106027
106117
  let reader;
@@ -107488,7 +107578,8 @@ async function payViaFloat({
107488
107578
  let resendUntilMs;
107489
107579
  let answeredAtMs;
107490
107580
  let dispatcher;
107491
- const sellerTimeout = AbortSignal.timeout(SELLER_TIMEOUT_MS);
107581
+ const waitMs = sellerWaitMs(record);
107582
+ const sellerTimeout = AbortSignal.timeout(waitMs);
107492
107583
  const stopResends = () => Date.now() > resendUntilMs || ["landed", "dead", "final", "cancelled"].includes(watch.verdict()?.state);
107493
107584
  for (let resend = 0; ; resend += 1) {
107494
107585
  const init = {
@@ -107500,7 +107591,7 @@ async function payViaFloat({
107500
107591
  };
107501
107592
  const socketWatch = resend === 0 ? watchRequestSocket(record.url) : void 0;
107502
107593
  try {
107503
- response = await fetchImpl(record.url, dispatcher === void 0 ? keepAliveInit(init) : { ...init, dispatcher });
107594
+ response = await fetchImpl(record.url, sellerAnswerInit(record.url, dispatcher === void 0 ? keepAliveInit(init) : { ...init, dispatcher }, waitMs));
107504
107595
  } catch (error) {
107505
107596
  unanswered2(error);
107506
107597
  } finally {
@@ -107802,13 +107893,13 @@ async function selfBroadcastLeg({ store, walName: walName2, record, sendRequest,
107802
107893
  mark("seller-sent");
107803
107894
  let response;
107804
107895
  try {
107805
- response = await fetchImpl(record.url, keepAliveInit({
107896
+ response = await fetchImpl(record.url, sellerAnswerInit(record.url, keepAliveInit({
107806
107897
  method: sendRequest.method,
107807
107898
  headers: { ...Object.fromEntries(sendRequest.headers), "PAYMENT-SIGNATURE": header },
107808
107899
  ...sendRequest.method === "POST" ? { body: Buffer.from(sendRequest.bodyBase64, "base64") } : {},
107809
107900
  redirect: "error",
107810
- signal: AbortSignal.timeout(SELLER_TIMEOUT_MS)
107811
- }));
107901
+ signal: AbortSignal.timeout(sellerWaitMs(record))
107902
+ }), sellerWaitMs(record)));
107812
107903
  } catch (error) {
107813
107904
  const timedOut = error?.name === "TimeoutError" || error?.cause?.name === "TimeoutError";
107814
107905
  if (!timedOut && !proxyRefusedConnection(error) && await resendUnanswered()) {
@@ -107939,7 +108030,7 @@ function paidResult(record, replayed) {
107939
108030
  float: { floatId: record.floatId }
107940
108031
  };
107941
108032
  }
107942
- var import_web3115, FLOAT_PAYMENT_SCHEMA2, REGENERATE_CODES, FLOAT_PAYMENT_WAL_PREFIX2, FLOAT_CONFIG_SCHEMA, FLOAT_PAY_PATH, FLOAT_MEMO_PATH, FLOAT_STATUS_PATH, MAX_BODY_BYTES, FLOAT_REFUND_ETA_SECONDS, REFUND_READ_EARLY_MS, REFUND_ETA_MAX_SECONDS, refundDueOf, REFUND_SETTLED_AFTER_MS, REFUND_OUTCOME_NEWS_MS, FINISHING_POLL_MS, FINISHING_LIMIT_MS, SELLER_TIMEOUT_MS, FLOAT_STATUS_START_MS, FLOAT_STATUS_POLL_MS, FLOAT_STATUS_TIMEOUT_MS, FLOAT_TRANSFER_DEAD, FLOAT_PAYMENT_REVERTED, VERDICT_POLL_MS, SELLER_ANSWER_VERDICT_MS, TRANSFER_MIN_BLOCKS_LEFT, TRANSFER_FRESH_MS, BLOCK_HEIGHT_TIMEOUT_MS, CONFIG_TIMEOUT_MS, MEMO_UPLOAD_TIMEOUT_MS, CONFIG_CACHE_MS, FLOAT_POOL_WARM_MS, DECIMAL3, POSITIVE, PENDING_CODES, FLOAT_FRONT_HEADER, hash7, fail22, GENERIC_REASON, SELLER_REASON_MAX, SELLER_REASON_READ_BYTES, FEE_PAYER_RESENDS, FEE_PAYER_RESEND_MS, GLOBAL_DISPATCHER5, sortedJson, MODEL_ID, floatPaymentWalName, floatRecordName, FLOAT_KEEP_BYTES_MS, memoryOnlyNeedsScrub, FLOAT_KEEP_OPEN_REQUEST_MS, configCache, configGeneration, configReadSequence, configReads, configRefreshes, SPECULATED_MAX_AGE_MS, speculatedQuotes, poolDomains, FLOAT_FACTS_SCHEMA, floatFacts, floatFactsKey, floatFactsMac, poolDomainReads, poolDomainWarmFailedAt, WARM_RETRY_MS, payerGenerator, etaEntry, loadPayerGenerator, plainText, BROADCAST_REFUSED, BROADCAST_REFUSED_STANDARD, BROADCAST_DEAD, TRANSFER_LANDING_WINDOW_MS, PROOF_DELIVERY_MS, SELF_BROADCAST_LEG1_LEFT_MS, BROADCAST_UNSIGNED, BROADCAST_NO_LOOKUP, BROADCAST_LANDED, BROADCAST_LIMIT_MS, BROADCAST_QUOTE_MARGIN_MS, SELF_BROADCAST_RESEND_MS, SELF_BROADCAST_UNANSWERED_RESEND_MS, SELLER_HEADERS_MAX_COUNT, SELLER_HEADERS_MAX_BYTES, FINAL_AFTER_202_MS, SELLER_HEADERS_KEPT;
108033
+ var import_web3115, FLOAT_PAYMENT_SCHEMA2, REGENERATE_CODES, FLOAT_PAYMENT_WAL_PREFIX2, FLOAT_CONFIG_SCHEMA, FLOAT_PAY_PATH, FLOAT_MEMO_PATH, FLOAT_STATUS_PATH, MAX_BODY_BYTES, FLOAT_REFUND_ETA_SECONDS, REFUND_READ_EARLY_MS, REFUND_ETA_MAX_SECONDS, refundDueOf, REFUND_SETTLED_AFTER_MS, REFUND_OUTCOME_NEWS_MS, FINISHING_POLL_MS, FINISHING_LIMIT_MS, SELLER_TIMEOUT_MS, SELLER_TIMEOUT_ABSURD_SECONDS, FLOAT_STATUS_START_MS, FLOAT_STATUS_POLL_MS, FLOAT_STATUS_TIMEOUT_MS, FLOAT_TRANSFER_DEAD, FLOAT_PAYMENT_REVERTED, VERDICT_POLL_MS, SELLER_ANSWER_VERDICT_MS, TRANSFER_MIN_BLOCKS_LEFT, TRANSFER_FRESH_MS, BLOCK_HEIGHT_TIMEOUT_MS, CONFIG_TIMEOUT_MS, MEMO_UPLOAD_TIMEOUT_MS, CONFIG_CACHE_MS, FLOAT_POOL_WARM_MS, DECIMAL3, POSITIVE, PENDING_CODES, FLOAT_FRONT_HEADER, hash7, fail22, GENERIC_REASON, SELLER_REASON_MAX, SELLER_REASON_READ_BYTES, FEE_PAYER_RESENDS, FEE_PAYER_RESEND_MS, GLOBAL_DISPATCHER5, sortedJson, MODEL_ID, floatPaymentWalName, floatRecordName, FLOAT_KEEP_BYTES_MS, memoryOnlyNeedsScrub, FLOAT_KEEP_OPEN_REQUEST_MS, configCache, configGeneration, configReadSequence, configReads, configRefreshes, SPECULATED_MAX_AGE_MS, speculatedQuotes, poolDomains, FLOAT_FACTS_SCHEMA, floatFacts, floatFactsKey, floatFactsMac, poolDomainReads, poolDomainWarmFailedAt, WARM_RETRY_MS, payerGenerator, etaEntry, loadPayerGenerator, plainText, BROADCAST_REFUSED, BROADCAST_REFUSED_STANDARD, BROADCAST_DEAD, TRANSFER_LANDING_WINDOW_MS, PROOF_DELIVERY_MS, SELF_BROADCAST_LEG1_LEFT_MS, BROADCAST_UNSIGNED, BROADCAST_NO_LOOKUP, BROADCAST_LANDED, BROADCAST_LIMIT_MS, BROADCAST_QUOTE_MARGIN_MS, SELF_BROADCAST_RESEND_MS, SELF_BROADCAST_UNANSWERED_RESEND_MS, SELLER_HEADERS_MAX_COUNT, SELLER_HEADERS_MAX_BYTES, FINAL_AFTER_202_MS, SELLER_HEADERS_KEPT;
107943
108034
  var init_floatPay = __esm({
107944
108035
  "wallet/src/pay/floatPay.mjs"() {
107945
108036
  "use strict";
@@ -107986,6 +108077,7 @@ var init_floatPay = __esm({
107986
108077
  FINISHING_POLL_MS = 2e3;
107987
108078
  FINISHING_LIMIT_MS = 6e4;
107988
108079
  SELLER_TIMEOUT_MS = 12e4;
108080
+ SELLER_TIMEOUT_ABSURD_SECONDS = 86400;
107989
108081
  FLOAT_STATUS_START_MS = 45e3;
107990
108082
  FLOAT_STATUS_POLL_MS = 5e3;
107991
108083
  FLOAT_STATUS_TIMEOUT_MS = 5e3;
@@ -135533,7 +135625,7 @@ ${canonical5}`;
135533
135625
  const balance = {
135534
135626
  name: "wallet_balance",
135535
135627
  title: "Balance",
135536
- description: `Use this when the user asks "what is my balance" or "how much USDC do I have": it answers ONE balance (balanceAtomic, what a payment can spend) and the public (unshielded) USDC. Always safe: it ${BALANCE_READ_DOES}. On "show my wallet", print \`card\` as it is (at most 8 lines). largestPaymentAtomic: the most ONE payment can spend now; a payment above it is not refused: pay_for_service gets the balance ready first (a tell-first answer, then doneInMinutesLow to doneInMinutesHigh minutes) (changeSettling: 0 only while the last payment's change settles, about changeReadyInSeconds; then one payment can use it all, or up to changeUpToAtomic). balanceParts (above 1): never tell the user about parts, accounts or a merge; say only that a large payment or send first gets the balance ready. balanceAtomic is one number; breakdown names the rest (shieldedAtomic is the same value; privateSendPendingAtomic is what a running private send has not sent yet, already taken off; refundsInFlightAtomic is a refund Shinjuku proved, on its way, not in balanceAtomic; refundsNotCertainAtomic a refund the seller may have kept, in no number; openPaymentsAtomic: paid, waiting on its seller (same request_id finishes it, else refunded); setAsideAtomic (when not 0): value an older version set aside; totalAtomic adds these; during a pool move, pools lists each pool's share and movingAtomic is value on its way to the new pool; the move field shows the move, poolSwitches each switch). unshieldedAtomic is the public USDC on the wallet's own Solana key (ownAddress), which wallet_shield makes private; unshieldedSolLamports is that key's SOL (Shinjuku pays every fee and rent). A stale balance (a new deposit, a received payment) reads the chain first; past about 20 s it answers the last balance with refreshing: true and finishes in the background (call again in 30 s); with a refreshReason that balance is exact and counts what the reason names (payment_settling: a payment that settles; account_join: all of it, while the wallet gets it ready to send; pool_move: value that arrives from the old pool; refund_check: a refund counted from an old read, which the wallet checks now (never tell the user it is still on its way); first_check: a new wallet's own landed deposits (its first history check)). status reading (balanceAtomic null): the first read of a wallet that may hold money is not done; call again in 30 s. If the public read fails, unshieldedAtomic is null with unshieldedReason. ownKeyTokens: the other tokens on ownAddress (not its USDC; [] when none; null when not read now: say nothing of other tokens), each { mint, amountAtomic, decimals, amount }, at most 10 (ownKeyTokensMore counts the rest); a token is its mint, never a name or symbol; this wallet has no tool that sells, swaps, or sends them: they stay on ownAddress. earlierDeposits (when any): USDC at an earlier pool's one-time deposit address, each with a notice line to tell the user; the wallet moves it itself. addFunds (when any): each wallet_add_funds address still open, with its state and the USDC known to wait there (heldAtomic; null with heldNote when not read in time). startsAtNextRequest true (old name): its deposit starts by itself once USDC is seen there (startsInMinutes; 0: at once). landingAtomic (when not 0): added USDC whose deposit landed on chain but is not in balanceAtomic yet; this call finishes it (up to about 20 s), else call again when next says. session: the spending limits (null or "none": no limit), what is left, and how to change them (session.summary, only when a limit is set). With no limit set, say nothing about limits or the session counters. To add money, use wallet_add_funds. version: this wallet's version. Amounts are atomic (1000000 = 1 USDC); say and usdc give USDC.`,
135628
+ description: `Use this for "what is my balance" or "how much USDC do I have": it answers ONE balance (balanceAtomic, what a payment can spend) and the public (unshielded) USDC. Always safe: it ${BALANCE_READ_DOES}. On "show my wallet", print \`card\` as it is (at most 8 lines). largestPaymentAtomic: the most ONE payment can spend now; a payment above it is not refused: pay_for_service gets the balance ready first (a tell-first answer, then doneInMinutesLow to doneInMinutesHigh minutes) (changeSettling: 0 only while the last payment's change settles, about changeReadyInSeconds; then one payment can use it all, or up to changeUpToAtomic). balanceParts (above 1): never mention parts, accounts or a merge; say only that a large payment or send first gets the balance ready. balanceAtomic is one number; breakdown names the rest (shieldedAtomic is the same value; privateSendPendingAtomic is what a running private send has not sent yet, already taken off; refundsInFlightAtomic is a refund Shinjuku proved, on its way, not in balanceAtomic; refundsNotCertainAtomic a refund the seller may have kept, in no number; openPaymentsAtomic: paid, waiting on its seller (same request_id finishes it, else refunded); setAsideAtomic (when not 0): value an older version set aside; totalAtomic adds these; during a pool move, pools lists each pool's share, movingAtomic is value on its way to the new pool, and move and poolSwitches show the move). unshieldedAtomic is the public USDC on the wallet's own Solana key (ownAddress), which wallet_shield makes private; unshieldedSolLamports is that key's SOL (Shinjuku pays every fee and rent). A stale balance (a new deposit, a received payment) reads the chain first; past about 20 s it answers the last balance with refreshing: true and finishes in the background (call again in 30 s); with a refreshReason that balance is exact and counts what the reason names (payment_settling: a payment that settles; account_join: all of it, while the wallet gets it ready to send; pool_move: value that arrives from the old pool; refund_check: a refund counted from an old read, which the wallet checks now (never say it is still on its way); first_check: a new wallet's own landed deposits (its first history check)). status reading (balanceAtomic null): the first read of a wallet that may hold money is not done; call again in 30 s. If the public read fails, unshieldedAtomic is null with unshieldedReason. ownKeyTokens: the other tokens on ownAddress (not its USDC; [] when none; null when not read now: say nothing of other tokens), each { mint, amountAtomic, decimals, amount }, at most 10 (ownKeyTokensMore counts the rest); a token is its mint, never a name or symbol; no wallet tool sells, swaps or sends them: they stay on ownAddress. earlierDeposits (when any): USDC at an earlier pool's one-time deposit address, each with a notice line to tell the user; the wallet moves it itself. addFunds (when any): each open wallet_add_funds address, its state and the USDC known to wait there (heldAtomic; null with heldNote when not read in time). startsAtNextRequest true (old name): it starts by itself once USDC is seen there (startsInMinutes; 0: at once). landingAtomic (when not 0): added USDC that landed on chain, not in balanceAtomic yet; this call finishes it (up to about 20 s), else call again when next says. session: the spending limits (null or "none": no limit), what is left, and how to change them (session.summary, only when a limit is set). With no limit set, say nothing about limits or counters. To add money, use wallet_add_funds. version: this wallet's version. Amounts are atomic (1000000 = 1 USDC); say and usdc give USDC.`,
135537
135629
  inputSchema: { type: "object", properties: {
135538
135630
  verbose: { type: "boolean", description: "Optional. true adds `internal`: the engineering numbers (funded balance records, notes, payments without waiting, a 0 set-aside value). Default false." }
135539
135631
  }, additionalProperties: false },
@@ -140459,7 +140551,7 @@ ${canonical5}`;
140459
140551
  name: "wallet_unshield",
140460
140552
  // UX130-13: a host that defers tools may show only the title: it says where a send goes by default.
140461
140553
  title: "Send USDC privately",
140462
- description: `Use this when the user says "send 2 USDC to <address>" or "withdraw to my wallet" (for "my wallet" leave \`to\` out: it uses the address saved at setup, or says how to set one; never ask the user first): it sends amount (USDC, like "2"; or amount_atomic) from the PRIVATE balance as plain USDC to \`to\`, a public Solana address. BEFORE you call it to send all of the balance when wallet_balance showed balanceParts above 1, write to the user that the wallet first gets the balance ready to send (do not tell the user how the wallet stores the balance; some apps show nothing until this call ends). Privately: the chain shows the pool paying the address, not which wallet paid. This wallet's own public address (ownAddress in wallet_balance: it holds the public USDC, wallet_shield deposits from it) is refused as \`to\`: an exit there would show on chain that the private balance is this wallet's, even if that address never deposited (wallet_add_funds deposits come from one-time addresses, not from it; never tell the user they came from it). Shinjuku pays every network fee; no SOL is needed. The user's saved own addresses (setup or this app's wallet entry) send at once. Any other address needs the user's yes in a dialog of this app; a client that cannot ask is refused (mcp_unshield_to_not_allowed) and a decline sends nothing (mcp_unshield_declined). \`to\` must already be able to receive USDC: an address that cannot is refused before any dialog (mcp_unshield_to_invalid). It runs in steps and may wait for the pool's next anchor. Each call answers within about 20 s (at once when the send ends; a dialog may take longer) with ok: true and stage: confirming, queued, refreshing, self-pay = preparing, exit = sending, done; joining = the wallet first gets the balance ready to send, then sends, and each call goes on with it (joinSteps: its steps, in order; doneInMinutesLow to doneInMinutesHigh: the time to say). For the status, call wallet_unshield again with the SAME request_id, amount and to. When the wallet must first get the balance ready to send, the FIRST call answers at once with stage joining and joinTellFirst: true; write its say to the user, then call again with the same request_id at once. One exception: when this app sends a progress token with that next call, the call gets the balance ready and then sends, with a progress line every ${JOIN_STEP_MS / 1e3} s ("getting your balance ready to send"; some apps show none, so the line is said before the call, as above). It answers when the send is done, or after about ${joinAnswerSeconds(JOIN_CALL_MAX_MS)} s with how far it got (joinsDone; sendState sending: the USDC is leaving, never say nothing moved); then call again with the same request_id. Without a progress token it answers within about 20 s with stage joining: call again until it sends. The same request_id never sends twice, also after a restart. request_id is optional: without it the answer names the id it made, and the same call again within 10 minutes after it ends reads that same send. An optional unshield limit the user sets may bound each call and what this wallet sends in any 24 hours, from every app (mcp_unshield_cap_exceeded: tell the user). Other refusals: mcp_argument_invalid, mcp_request_id_reused, wallet_locked, mcp_tool_disabled, mcp_unshield_failed, and the unshield's own p03_* codes. A call the app stopped or calls rejected or interrupted, or one that failed, may still send: call wallet_cancel (no request_id) before you tell the user anything about that send; never say nothing was sent without it. A send is one payment to \`to\`. ${HOST_BLOCKED_LINE}`,
140554
+ description: `Use this for "send 2 USDC to <address>" or "withdraw to my wallet" (for "my wallet" leave \`to\` out: it uses the address saved at setup, or says how to set one; never ask the user first): it sends amount (USDC, like "2"; or amount_atomic) from the PRIVATE balance as plain USDC to \`to\`, a public Solana address. BEFORE you call it to send all of the balance when wallet_balance showed balanceParts above 1, write to the user that the wallet first gets the balance ready to send (never say how the wallet stores the balance; some apps show nothing until this call ends). Privately: the chain shows the pool paying the address, not which wallet paid. This wallet's own public address (ownAddress in wallet_balance: it holds the public USDC, wallet_shield deposits from it) is refused as \`to\`: an exit there would show on chain that the private balance is this wallet's, even if that address never deposited (wallet_add_funds deposits come from one-time addresses, not from it; never tell the user they came from it). Shinjuku pays every network fee; no SOL is needed. The user's saved own addresses (setup or this app's wallet entry) send at once. Any other address needs the user's yes in a dialog of this app; a client that cannot ask is refused (mcp_unshield_to_not_allowed) and a decline sends nothing (mcp_unshield_declined). \`to\` must already be able to receive USDC: an address that cannot is refused before any dialog (mcp_unshield_to_invalid). It runs in steps and may wait for the pool's next anchor. Each call answers within about 20 s (at once when the send ends; a dialog may take longer) with ok: true and stage: confirming, queued, refreshing, self-pay = preparing, exit = sending, done; joining = the wallet first gets the balance ready, then sends; each call goes on with it (joinSteps: its steps, in order; doneInMinutesLow to doneInMinutesHigh: the time to say). For the status, call wallet_unshield again with the SAME request_id, amount and to. When the balance must first be made ready, the FIRST call answers at once with stage joining and joinTellFirst: true; write its say to the user, then call again with the same request_id at once. One exception: when this app sends a progress token with that next call, the call gets the balance ready and then sends, with a progress line every ${JOIN_STEP_MS / 1e3} s ("getting your balance ready to send"; some apps show none, so the line is said before the call, as above). It answers when the send is done, or after about ${joinAnswerSeconds(JOIN_CALL_MAX_MS)} s with how far it got (joinsDone; sendState sending: the USDC is leaving, never say nothing moved); then call again with the same request_id. Without a progress token it answers within about 20 s with stage joining: call again until it sends. The same request_id never sends twice, also after a restart. request_id is optional: without it the answer names the id it made, and the same call again within 10 minutes after it ends reads that same send. An optional unshield limit the user sets may bound each call and what this wallet sends in any 24 hours, from every app (mcp_unshield_cap_exceeded: tell the user). Other refusals: mcp_argument_invalid, mcp_request_id_reused, wallet_locked, mcp_tool_disabled, mcp_unshield_failed, and the unshield's own p03_* codes. A call the app stopped, rejected or interrupted, or one that failed, may still send: call wallet_cancel (no request_id) before you tell the user anything about that send; never say nothing was sent without it. A send is one payment to \`to\`. ${HOST_BLOCKED_LINE}`,
140463
140555
  inputSchema: {
140464
140556
  type: "object",
140465
140557
  properties: {
@@ -146898,39 +146990,6 @@ var init_consolePaste = __esm({
146898
146990
  }
146899
146991
  });
146900
146992
 
146901
- // wallet/src/cli/homeJob.mjs
146902
- function stepWords(lead, words2, line, { env = process.env, first, inline = false } = {}) {
146903
- const key = homeKeyOf(words2);
146904
- const comma = /\bor$/.test(lead) ? "" : ",";
146905
- if (inHomeJob(env) && key) return `${lead}${comma} ${first ? `${first}, then ` : ""}press [${key}] on the home screen.`;
146906
- return inline ? `${lead}: ${line}` : `${lead}${comma} run:
146907
- ${line}`;
146908
- }
146909
- function stepName(words2, line, { env = process.env } = {}) {
146910
- const key = homeKeyOf(words2);
146911
- return inHomeJob(env) && key ? `press [${key}] on the home screen` : line;
146912
- }
146913
- var HOME_JOB_ENV, inHomeJob, HOME_KEY, JOB_NAME, jobLabel, homeKeyOf;
146914
- var init_homeJob = __esm({
146915
- "wallet/src/cli/homeJob.mjs"() {
146916
- "use strict";
146917
- HOME_JOB_ENV = "SHINJUKU_WALLET_HOME_JOB";
146918
- inHomeJob = (env = process.env) => env?.[HOME_JOB_ENV] === "1";
146919
- HOME_KEY = { "add-funds": "a", unshield: "u", curl: "p", chat: "t", setup: "s", restore: "r", limits: "l" };
146920
- JOB_NAME = {
146921
- "add-funds": "add funds",
146922
- unshield: "send money out",
146923
- curl: "pay a link",
146924
- chat: "private AI chat",
146925
- setup: "setup",
146926
- restore: "restore",
146927
- limits: "spending limits"
146928
- };
146929
- jobLabel = (name, typed, env = process.env) => inHomeJob(env) && JOB_NAME[name] ? JOB_NAME[name] : typed;
146930
- homeKeyOf = (words2) => HOME_KEY[String(words2 ?? "").trim().split(/\s+/)[0]];
146931
- }
146932
- });
146933
-
146934
146993
  // wallet/src/cli/homeMark.mjs
146935
146994
  var MARK_WIDTH, MARK2, MARK_CLASS, EYE, COMPACT_MARK;
146936
146995
  var init_homeMark = __esm({
@@ -147789,7 +147848,15 @@ function homeScreenNeeds() {
147789
147848
  }
147790
147849
  return { columns: MIN_COLUMNS, rows: 80 };
147791
147850
  }
147792
- async function runJobView({ title, run: run2, stdout = process.stdout, stdin = process.stdin, env = process.env } = {}) {
147851
+ async function runJobView({
147852
+ title,
147853
+ run: run2,
147854
+ stdout = process.stdout,
147855
+ stdin = process.stdin,
147856
+ env = process.env,
147857
+ launcherGone = launcherWatch(),
147858
+ platform = process.platform
147859
+ } = {}) {
147793
147860
  const codes = paletteFor(screenColor(stdout, env));
147794
147861
  const paint2 = (parts) => parts.map(([text3, cls]) => codes && cls ? `${codes[cls]}${text3}${RESET2}` : text3).join("");
147795
147862
  const write = (text3) => {
@@ -147811,6 +147878,22 @@ ${rule}
147811
147878
  code2 = 1;
147812
147879
  }
147813
147880
  const end2 = code2 === 0 ? [["\u25C6 ", "crim"], [`${clean4(title)}: done`, "bone"]] : code2 === 130 ? [["\u25C6 ", "crim"], [`${clean4(title)}: stopped (Ctrl+C)`, "bone"]] : [["\u25CB ", "mute"], [`${clean4(title)}: it stopped; its message is above`, "bone"]];
147881
+ const gone = () => {
147882
+ try {
147883
+ return launcherGone();
147884
+ } catch {
147885
+ return false;
147886
+ }
147887
+ };
147888
+ const shellAgain = (first) => write(`${first ? `
147889
+ ${"\u2500".repeat(width)}
147890
+ ${end2.map(([text3]) => text3).join("")}
147891
+ ` : ""}The wallet screen has closed: the command that started it ended with the Ctrl+C.
147892
+ ` + (platform === "win32" ? 'If this window asks "Terminate batch job (Y/N)?", type Y and press Enter. ' : "") + "To open the wallet again, run the same command.\n");
147893
+ if (gone()) {
147894
+ shellAgain(true);
147895
+ return { code: code2, next: "quit", launcherGone: true };
147896
+ }
147814
147897
  write(`
147815
147898
  ${rule}
147816
147899
  ${paint2(end2)}
@@ -147820,15 +147903,24 @@ ${paint2([["any other key, [q] too, goes back to the home screen", "mute"]])}
147820
147903
  if (!stdin?.isTTY || typeof stdin.setRawMode !== "function") return { code: code2, next: "quit" };
147821
147904
  const rawBefore = Boolean(stdin.isRaw);
147822
147905
  let key;
147906
+ let ended2 = false;
147907
+ let poll;
147823
147908
  try {
147824
147909
  stdin.setRawMode(true);
147825
147910
  key = await new Promise((done) => {
147826
147911
  stdin.once("data", (chunk) => done(String(chunk)));
147912
+ poll = setInterval(() => {
147913
+ if (gone()) {
147914
+ ended2 = true;
147915
+ done(void 0);
147916
+ }
147917
+ }, LAUNCHER_POLL_MS);
147827
147918
  stdin.resume();
147828
147919
  });
147829
147920
  } catch {
147830
147921
  key = void 0;
147831
147922
  } finally {
147923
+ clearInterval(poll);
147832
147924
  stdin.pause();
147833
147925
  await stopReading(stdin);
147834
147926
  try {
@@ -147836,8 +147928,24 @@ ${paint2([["any other key, [q] too, goes back to the home screen", "mute"]])}
147836
147928
  } catch {
147837
147929
  }
147838
147930
  }
147931
+ if (ended2) {
147932
+ shellAgain(false);
147933
+ return { code: code2, next: "quit", launcherGone: true };
147934
+ }
147839
147935
  return key === void 0 ? { code: code2, next: "quit" } : { code: code2, next: "back", key };
147840
147936
  }
147937
+ function launcherWatch(launcher = LAUNCHER_AT_LOAD, parent = PARENT_AT_LOAD) {
147938
+ return () => {
147939
+ if (!Number.isSafeInteger(launcher) || launcher <= 1) return false;
147940
+ if (process.ppid !== parent) return true;
147941
+ try {
147942
+ process.kill(launcher, 0);
147943
+ return false;
147944
+ } catch (error) {
147945
+ return error?.code !== "EPERM";
147946
+ }
147947
+ };
147948
+ }
147841
147949
  async function waitForRoom({ stdout = process.stdout, stdin = process.stdin, env = process.env } = {}) {
147842
147950
  if (!stdin?.isTTY || typeof stdin.setRawMode !== "function") return "off";
147843
147951
  const needs = homeScreenNeeds();
@@ -147892,11 +148000,12 @@ async function waitForRoom({ stdout = process.stdout, stdin = process.stdin, env
147892
148000
  }
147893
148001
  }
147894
148002
  }
147895
- var MIN_COLUMNS, WIDE_COLUMNS, WIDE_WIDTH, LEFT_X, LEFT_WIDTH, PANEL_X, PANEL_WIDTH, TOP, BAND_MS, BAND_CHANCE, KATAKANA, FONT, AGENT_KEY, HELP_KEY, QUIT_KEY, KEY_HELP, lettersOf, PANELS, ALL_PIECES, KEYS_LINE, FIRST_LINES, SETUP_KEY, RESTORE_KEY, END_KEYS, RESTORE_STEP, HINT, tooSmallLines, COLORS, COLORS_16, RESET2, DATA_SYMBOLS, HOME_SHAPE, FIRST_SHAPE, WIDE, WORD_TOP, WORD_BOTTOM, WORD_WIDTH, ROMAJI, BIG_MARK, SMALL_MARK, LOCKUP_GAP, LOCKUP_WIDTH, Screen, setupLine, randomKatakana, MARK_TONES, PUBLIC_STEP, stackHeight;
148003
+ var MIN_COLUMNS, WIDE_COLUMNS, WIDE_WIDTH, LEFT_X, LEFT_WIDTH, PANEL_X, PANEL_WIDTH, TOP, BAND_MS, BAND_CHANCE, KATAKANA, FONT, AGENT_KEY, HELP_KEY, QUIT_KEY, KEY_HELP, lettersOf, PANELS, ALL_PIECES, KEYS_LINE, FIRST_LINES, SETUP_KEY, RESTORE_KEY, END_KEYS, RESTORE_STEP, HINT, tooSmallLines, COLORS, COLORS_16, RESET2, DATA_SYMBOLS, HOME_SHAPE, FIRST_SHAPE, WIDE, WORD_TOP, WORD_BOTTOM, WORD_WIDTH, ROMAJI, BIG_MARK, SMALL_MARK, LOCKUP_GAP, LOCKUP_WIDTH, Screen, setupLine, randomKatakana, MARK_TONES, PUBLIC_STEP, stackHeight, LAUNCHER_POLL_MS, LAUNCHER_AT_LOAD, PARENT_AT_LOAD;
147896
148004
  var init_homeScreen = __esm({
147897
148005
  "wallet/src/cli/homeScreen.mjs"() {
147898
148006
  "use strict";
147899
148007
  init_homeMark();
148008
+ init_homeJob();
147900
148009
  MIN_COLUMNS = 80;
147901
148010
  WIDE_COLUMNS = 120;
147902
148011
  WIDE_WIDTH = 119;
@@ -148057,6 +148166,9 @@ var init_homeScreen = __esm({
148057
148166
  "at-most": "your private balance is at its most (5 USDC)."
148058
148167
  };
148059
148168
  stackHeight = (blocks) => blocks.reduce((sum2, block) => sum2 + block.height, 0) + blocks.length - 1;
148169
+ LAUNCHER_POLL_MS = 250;
148170
+ LAUNCHER_AT_LOAD = /^[0-9]+$/.test(process.env[LAUNCHER_PID_ENV] ?? "") ? Number(process.env[LAUNCHER_PID_ENV]) : process.ppid;
148171
+ PARENT_AT_LOAD = process.ppid;
148060
148172
  }
148061
148173
  });
148062
148174
 
@@ -148767,9 +148879,11 @@ async function runChat({ io, values, deps = {} }) {
148767
148879
  return typeof deps.balanceLine === "function" ? deps.balanceLine(parts) : `${balanceWords(parts)}.`;
148768
148880
  };
148769
148881
  const entryOf = (id) => catalog.models.find((entry) => entry.id === id) ?? models.find((entry) => entry.id === id);
148882
+ const fullTokensOf = (id) => defaultMaxTokens(id, liveOf(id) ?? models.find((entry) => entry.id === id));
148770
148883
  const tokensFor = (id, messages, limit) => {
148771
148884
  try {
148772
- return BigInt(inference.ceilingAtomic({ model: id, messages, maxTokens: CHAT_MAX_TOKENS, liveEntry: liveOf(id) })) <= limit ? CHAT_MAX_TOKENS : CHAT_SHORT_TOKENS;
148885
+ const full = fullTokensOf(id);
148886
+ return BigInt(inference.ceilingAtomic({ model: id, messages, maxTokens: full, liveEntry: liveOf(id) })) <= limit ? full : CHAT_SHORT_TOKENS;
148773
148887
  } catch {
148774
148888
  return CHAT_SHORT_TOKENS;
148775
148889
  }
@@ -148778,7 +148892,7 @@ async function runChat({ io, values, deps = {} }) {
148778
148892
  let maxTokens = tokensFor(id, [{ role: "user", content: "x" }], turnLimit());
148779
148893
  const messages = SHORT_QUESTION;
148780
148894
  const ceilingAtomic2 = BigInt(inference.ceilingAtomic({ model: id, messages, maxTokens, liveEntry: liveOf(id) }));
148781
- if (ceilingAtomic2 > caps.answer && maxTokens !== CHAT_MAX_TOKENS) maxTokens = CHAT_MAX_TOKENS;
148895
+ if (ceilingAtomic2 > caps.answer && maxTokens !== fullTokensOf(id)) maxTokens = fullTokensOf(id);
148782
148896
  return priceOf2({ model: id, messages, maxTokens });
148783
148897
  };
148784
148898
  const priceText = answerPriceWords;
@@ -148874,7 +148988,7 @@ async function runChat({ io, values, deps = {} }) {
148874
148988
  "Private: each answer is paid from your private balance, and the chain does not show which wallet paid. Your words go out only through Tor, so the AI provider and the company that runs the model never see your IP address. They do see your words and the answers.",
148875
148989
  // payAndFetch opts.memoryOnly (floatPay.mjs MEMORY ONLY): the payment record holds amounts, ids and hashes only.
148876
148990
  "Memory only: the chat writes no chat file and no log, and the wallet writes no copy of the conversation, not even encrypted: its payment records keep only the amounts. The conversation is gone when the chat ends. Let an answer arrive before you end the chat: if the chat ends while an answer is on its way, that answer is gone, and a payment already sent may not come back.",
148877
- `Cost: each answer pays the AI provider's price for up to ${CHAT_MAX_TOKENS} tokens (${CHAT_SHORT_TOKENS} for a model whose longer answer is above your limit) plus the 0.001 USDC Shinjuku fee, up front, so a short answer costs as much as a long one; what an answer does not use is not refunded. With ${state2.model}, ${lowFirst(costLine2(state2.model))}. ` + (state2.mode === "talk" ? "Each question sends the whole conversation, so a long chat costs more (/new starts again)." : "In this mode each question goes alone, so each costs about the same."),
148991
+ `Cost: each answer pays the AI provider's price for up to ${CHAT_MAX_TOKENS} tokens (${CHAT_SHORT_TOKENS} for a costly model, or one whose longer answer is above your limit) plus the 0.001 USDC Shinjuku fee, up front, so a short answer costs as much as a long one; what an answer does not use is not refunded. With ${state2.model}, ${lowFirst(costLine2(state2.model))}. ` + (state2.mode === "talk" ? "Each question sends the whole conversation, so a long chat costs more (/new starts again)." : "In this mode each question goes alone, so each costs about the same."),
148878
148992
  `Mode: ${modeName()}. One question at a time sends each question alone and names each payment; a conversation lets the AI remember the chat and shows the running total. /mode 1 or /mode 2 changes it.`,
148879
148993
  caps.answer >= NO_CAP && caps.chat >= NO_CAP ? `Limits: none${capsSourceText(caps)}` : `Limits: ${capWords(caps.answer)} an answer and ${capWords(caps.chat)} for this chat${capsSourceText(caps)}` + (caps.limits.maxSessionAtomic === void 0 ? "" : ` Your limit of ${usdcWords(caps.limits.maxSessionAtomic)} in any 24 hours counts every payment from this wallet.`),
148880
148994
  `Commands: /new starts a new conversation, /mode changes the mode, /cost shows what this chat paid, /balance shows your private balance, /model picks another model of the same AI company (/model <id> names one), /provider picks another AI company and then its model (${now.models.length ? `${now.models.length} models served now` : "none served right now"}), /help shows this, /exit (or /quit, Ctrl+D) ends the chat${state2.pending ? ", /retry finishes the unfinished answer" : ""}.`
@@ -156037,7 +156151,7 @@ async function addFunds(values, deps) {
156037
156151
  plan,
156038
156152
  save: save2,
156039
156153
  proxyArgs: proxy.args,
156040
- childEnv: { ...proxy.env, [PASSPHRASE_ENV]: passphrase2, [RPC_ENV2]: rpcUrl, [STEP_VERBOSE_ENV]: ctx.verbose ? "1" : "0" },
156154
+ childEnv: { ...proxy.env, [PASSPHRASE_ENV]: passphrase2, [RPC_ENV2]: rpcUrl, ...relayOriginsEnv(), [STEP_VERBOSE_ENV]: ctx.verbose ? "1" : "0" },
156041
156155
  // A1: a quiet terminal run names no plan id and no stage (funds-status and --verbose do).
156042
156156
  say: (stage, text3) => io.err(ctx.quiet ? `add-funds: ${text3.replace(STEP_WORDS, "")}
156043
156157
  ` : `add-funds ${plan.fundsId} [${stage}] ${text3}
@@ -156363,15 +156477,18 @@ function freePort() {
156363
156477
  var failure2 = (plain, detail, extra = {}) => Object.assign(new Error(detail ?? plain), { plain, ...extra });
156364
156478
  var sleep4 = (ms) => new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
156365
156479
  var tunnelOf = (port) => `http://127.0.0.1:${port}`;
156366
- function takeLock(home, say2) {
156367
- mkdirSync(managedTorDir(home), { recursive: true, mode: 448 });
156480
+ function lockName(home) {
156368
156481
  let real = home;
156369
156482
  try {
156370
156483
  real = realpathSync.native(home);
156371
156484
  } catch {
156372
156485
  }
156373
156486
  if (process.platform === "win32" || process.platform === "darwin") real = real.toLowerCase();
156374
- return takeProcessLock(`tor-start ${real}`, {
156487
+ return `tor-start ${real}`;
156488
+ }
156489
+ function takeLock(home, say2) {
156490
+ mkdirSync(managedTorDir(home), { recursive: true, mode: 448 });
156491
+ return takeProcessLock(lockName(home), {
156375
156492
  waitEveryMs: Infinity,
156376
156493
  onWait: () => say2("Waiting for Tor, which is starting in the background."),
156377
156494
  lostError: () => failure2("another start took over. Ask again", "the start lock was taken over")
@@ -156432,6 +156549,13 @@ async function installBundle(home, say2) {
156432
156549
  `);
156433
156550
  renameSync(unpacking, target);
156434
156551
  }
156552
+ function bootstrappedSince(home, from) {
156553
+ try {
156554
+ return /Bootstrapped 100%/.test(readFileSync(logFile(home), "utf8").slice(Number.isInteger(from) ? from : 0));
156555
+ } catch {
156556
+ return false;
156557
+ }
156558
+ }
156435
156559
  async function waitBootstrapped(home, alive2, from) {
156436
156560
  const until = Date.now() + BOOTSTRAP_TIMEOUT_MS;
156437
156561
  for (; ; ) {
@@ -156511,9 +156635,27 @@ async function startTor(home, port, say2, assertHeld = () => {
156511
156635
  child.once("exit", () => {
156512
156636
  ended2 = true;
156513
156637
  });
156638
+ let stopped = false;
156639
+ const endStart = () => {
156640
+ if (!ended2) child.kill();
156641
+ try {
156642
+ rmSync(stateFile(home), { force: true });
156643
+ } catch {
156644
+ }
156645
+ };
156646
+ const onStop = (signal) => {
156647
+ stopped = true;
156648
+ if (!bootstrappedSince(home, from)) endStart();
156649
+ if (process.listenerCount(signal) === 1) process.exit(signal === "SIGINT" ? 130 : 143);
156650
+ };
156651
+ const stopSignals = ["SIGINT", "SIGTERM"];
156652
+ for (const signal of stopSignals) process.on(signal, onStop);
156514
156653
  await new Promise((resolvePromise, reject) => {
156515
156654
  child.once("error", (error) => reject(blocked(error)));
156516
156655
  child.once("spawn", resolvePromise);
156656
+ }).catch((error) => {
156657
+ for (const signal of stopSignals) process.off(signal, onStop);
156658
+ throw error;
156517
156659
  });
156518
156660
  child.unref();
156519
156661
  try {
@@ -156528,14 +156670,13 @@ async function startTor(home, port, say2, assertHeld = () => {
156528
156670
  );
156529
156671
  }
156530
156672
  say2("Starting Tor in the background (a few seconds; up to a minute the first time).");
156531
- await waitBootstrapped(home, () => !ended2, from);
156673
+ await waitBootstrapped(home, () => !ended2 && !stopped, from);
156532
156674
  } catch (error) {
156533
- if (!ended2) child.kill();
156534
- try {
156535
- rmSync(stateFile(home), { force: true });
156536
- } catch {
156537
- }
156675
+ if (!stopped || !bootstrappedSince(home, from)) endStart();
156676
+ if (stopped) throw failure2("you stopped the start with Ctrl+C, so Tor was stopped too. Ask again to start it", "the Tor start was stopped (Ctrl+C)");
156538
156677
  throw error;
156678
+ } finally {
156679
+ for (const signal of stopSignals) process.off(signal, onStop);
156539
156680
  }
156540
156681
  }
156541
156682
  async function recordTunnel(home, tunnel) {
@@ -156544,7 +156685,7 @@ async function recordTunnel(home, tunnel) {
156544
156685
  } catch {
156545
156686
  }
156546
156687
  }
156547
- async function ownTorReady(home, state2) {
156688
+ async function ownTorReady(home, state2, { wait: wait2 = true } = {}) {
156548
156689
  if (!state2 || !pidAlive4(state2.pid)) return void 0;
156549
156690
  let now;
156550
156691
  try {
@@ -156556,6 +156697,7 @@ async function ownTorReady(home, state2) {
156556
156697
  let own = await controlCheck(home, state2);
156557
156698
  if (own?.tunnelPort !== state2.port) return void 0;
156558
156699
  if (own.progress < 100) {
156700
+ if (!wait2) return void 0;
156559
156701
  await waitBootstrapped(home, () => pidAlive4(state2.pid), Number.isInteger(state2.logFrom) ? state2.logFrom : 0);
156560
156702
  own = await controlCheck(home, state2);
156561
156703
  if (own?.tunnelPort !== state2.port || own.progress < 100) return void 0;
@@ -156619,8 +156761,9 @@ async function ensureWalletTor(proxy, { env = process.env, platform = process.pl
156619
156761
  const ownPort = ownPortOf(home);
156620
156762
  if (explicit && Number(url.port) !== ownPort) return proxy;
156621
156763
  try {
156622
- const ready = await ownTorReady(home, managedTorState(home));
156623
- if (ready) {
156764
+ const known = managedTorState(home);
156765
+ const ready = await ownTorReady(home, known, { wait: false });
156766
+ if (ready && await processLockFree(lockName(home)) && pidAlive4(known.pid)) {
156624
156767
  await recordTunnel(home, ready);
156625
156768
  return ready;
156626
156769
  }
@@ -158434,16 +158577,21 @@ var CURL_SHORT_FLAGS = Object.freeze({ "-X": "--method", "-H": "--header", "-d":
158434
158577
  function curlArgs(command, args) {
158435
158578
  const known = new Map(command.flags.map((entry) => [entry.name, entry]));
158436
158579
  const out = [];
158580
+ let afterJsonData = false;
158437
158581
  for (let index = 0; index < args.length; index += 1) {
158438
158582
  const token = args[index];
158439
158583
  const name = CURL_SHORT_FLAGS[token] ?? token;
158440
158584
  if (!name.startsWith("-")) {
158585
+ if (afterJsonData && out.includes("--url")) throw new PublicFailure(CURL_JSON_ARGUMENT_REFUSAL);
158586
+ afterJsonData = false;
158441
158587
  out.push("--url", token);
158442
158588
  continue;
158443
158589
  }
158590
+ afterJsonData = false;
158444
158591
  out.push(name);
158445
158592
  if (known.has(name) && !known.get(name).boolean && index + 1 < args.length) {
158446
158593
  out.push(args[index + 1]);
158594
+ afterJsonData = name === "--data" && /^\s*[[{]/.test(args[index + 1]);
158447
158595
  index += 1;
158448
158596
  }
158449
158597
  }
@@ -158481,13 +158629,34 @@ function validateCurl(values, env) {
158481
158629
  }
158482
158630
  function curlBody(values) {
158483
158631
  const file = values.get("--data-file") ?? (values.get("--data")?.startsWith("@") ? values.get("--data").slice(1) : void 0);
158484
- if (file === void 0) return values.get("--data");
158632
+ let body;
158633
+ if (file === void 0) body = values.get("--data");
158634
+ else {
158635
+ try {
158636
+ body = readFileSync(resolve41(file));
158637
+ } catch {
158638
+ throw new PublicFailure("fetch_options_invalid: the body file (--data-file, or -d @file) could not be read; pass a file you can read. Nothing was sent");
158639
+ }
158640
+ }
158641
+ curlJsonBody(values, body, file !== void 0);
158642
+ return body;
158643
+ }
158644
+ function curlJsonBody(values, body, fromFile) {
158645
+ if (body === void 0) return;
158646
+ const type = (values.get("--header") ?? []).find((line) => line.slice(0, line.indexOf(":")).trim().toLowerCase() === "content-type");
158647
+ const typeValue = type === void 0 ? void 0 : type.slice(type.indexOf(":") + 1).trim().toLowerCase();
158648
+ const jsonType = typeValue !== void 0 && /^application\/(?:[\w.+-]+\+)?json\b/.test(typeValue);
158649
+ const text3 = Buffer.isBuffer(body) ? body.toString("utf8") : String(body);
158650
+ const asJson = fromFile ? jsonType : typeValue === void 0 ? /^\s*[[{]/.test(text3) : jsonType;
158651
+ if (!asJson) return;
158485
158652
  try {
158486
- return readFileSync(resolve41(file));
158653
+ JSON.parse(text3.replace(/^/, ""));
158654
+ return;
158487
158655
  } catch {
158488
- throw new PublicFailure("fetch_options_invalid: the body file (--data-file, or -d @file) could not be read; pass a file you can read. Nothing was sent");
158489
158656
  }
158657
+ throw new PublicFailure(fromFile ? "fetch_options_invalid: the body file is not valid JSON, so it was not sent; nothing was paid. Fix the file (every name and text in double quotes) and run the same command again" : CURL_JSON_ARGUMENT_REFUSAL);
158490
158658
  }
158659
+ var CURL_JSON_ARGUMENT_REFUSAL = "fetch_options_invalid: the body is not valid JSON, so it was not sent; nothing was paid. PowerShell removes the double quotes inside a JSON argument: save the JSON in a file and pass it as -d @<file> (or --data-file <file>), then run the command again";
158491
158660
  function curlFailure(error) {
158492
158661
  if (error instanceof SpendRefusalError && knownErrorCode(error.error)) {
158493
158662
  return new PublicFailure(`${error.error}: ${error.detail}`, { cause: error });
@@ -160264,7 +160433,7 @@ function startKeepWarm(options, env = process.env) {
160264
160433
  ...proxy.env,
160265
160434
  [PASSPHRASE_ENV3]: options.passphrase,
160266
160435
  [KEEP_WARM_PARENT_ENV]: "1",
160267
- ...options.rpcUrl ? { [RPC_ENV3]: options.rpcUrl } : {}
160436
+ ...options.rpcUrl ? { [RPC_ENV3]: options.rpcUrl, ...relayOriginsEnv() } : {}
160268
160437
  },
160269
160438
  stdio: ["pipe", "ignore", "pipe"],
160270
160439
  windowsHide: true