@c4roa0/agentpay-user 0.3.10 → 0.3.12

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/doc/TODO.md CHANGED
@@ -1,8 +1,19 @@
1
1
  # User TODO
2
2
 
3
+ - [x] Participant 0.3.11 update resumed original installation; unlock/status confirmed original Policy, Market connection and two settled Orders after restoring Anvil forwarding.
4
+ - [x] Prepare resumed switch without premature startup; preserve repair checkpoint across current-version interruption.
5
+ - [x] Show setup guidance for a fresh unfinished installation, without treating missing established Wallet files as new setup.
6
+ - [x] Classify chain RPC transport failure in Wallet and show actionable User recovery guidance; controlled RPC/IPC tests passed.
7
+ - [ ] Publish Wallet RPC guidance, pin its verified package in User, then publish and verify combined cleanup.
8
+
9
+ - [x] Allow update of an exact confirmed stopped switch target, then resume using new code; preserve retry checkpoint and reject other targets/live services (controlled verification).
10
+ - [x] Publish User 0.3.11 update recovery and document one-time bootstrap for older blocked updaters; tag/package integrity verified.
11
+ - [x] Participant update recovery after bootstrap preserved original Wallet/Policy/Orders (2026-11-12).
12
+
3
13
  - [x] Fix switching back from setup cancelled before Wallet creation; retain stop/listener and established-data checks.
4
- - [ ] Deliver the fix and verify participant switch-back to the original installation.
5
- - [ ] Improve unconfigured installation status/info: direct to setup, not service start or missing-key recovery.
14
+ - [x] Publish User 0.3.10 switch-back fix; matching tag and npm integrity verified.
15
+ - [ ] Verify participant switch-back to the original installation after installing 0.3.10.
16
+ - [x] Improve unconfigured installation status/info: direct to setup, not service start or missing-key recovery (unreleased).
6
17
 
7
18
  - [x] Verify local official Skill files before downloading and stream update repair progress (2026-11-12; targeted checks).
8
19
  - [x] Publish local official-Skill verification and streaming update progress in User 0.3.9; tag and npm integrity verified (2026-11-12).
package/doc/usage.md CHANGED
@@ -60,3 +60,29 @@ then compare its installed files. Matching local files need no download. Missing
60
60
  archives trigger a download; changed or incomplete extracted files are kept and
61
61
  reported for review. These checks do not start a payment or select a newer Skill
62
62
  from Market. Software updates display repair progress while it runs.
63
+
64
+ ### Updating after an interrupted installation switch
65
+
66
+ From the installation you previously selected, run `agentpay update`. If its
67
+ background services have stopped, the updater can install the new version and
68
+ continue that same switch. It preserves Wallet data and the confirmed target. If
69
+ recovery fails, run the same command again; do not delete the switch records.
70
+
71
+ User 0.3.9 and 0.3.10 stop before downloading when unit replacement is unfinished.
72
+ For those versions only, install the intended fixed User version with npm in the
73
+ same program folder, then run `agentpay service start` there. Do not uninstall or
74
+ create a replacement Wallet to recover an interrupted switch.
75
+
76
+ ### Setup and connection warnings
77
+
78
+ An installation whose setup was cancelled before Wallet creation displays
79
+ “Setup not finished”; continue with `agentpay setup`. Existing Wallet records with
80
+ missing files instead require review; do not create a replacement Wallet.
81
+
82
+ “Cannot connect to the chain RPC” refers to the blockchain connection, not the
83
+ Market website. Restore the RPC connection and any SSH forwarding used by your
84
+ deployment. Keep Wallet unlocked; Runtime retries the original Order checks.
85
+ Manual `agentpay orders status ORDER_ID` also retries without making a payment.
86
+
87
+ Update recovery prepares the selected installation first; repair performs the
88
+ final service start. If interrupted between these steps, rerun `agentpay update`.
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@c4roa0/agentpay-user",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "@c4roa0/agentpay-protocol": "0.14.3",
7
- "@c4roa0/agentpay-wallet": "0.3.3",
7
+ "@c4roa0/agentpay-wallet": "0.3.4",
8
8
  "ethers": "^6.17.0",
9
9
  "fs-native-extensions": "1.5.3"
10
10
  },
package/progress.md CHANGED
@@ -1,9 +1,61 @@
1
1
  # AgentPay User progress
2
2
 
3
+ ## User 0.3.12 release candidate — 2026-11-12
4
+
5
+ Pins published Wallet 0.3.4 for actionable RPC errors. Includes single-start update
6
+ recovery and incomplete-setup guidance, including missing-Policy and mixed-error
7
+ review fixes. All 66 targeted User checks passed; tag publication and participant
8
+ acceptance remain pending.
9
+
10
+ ## Participant recovery and guidance cleanup — 2026-11-12
11
+
12
+ Participant installed User 0.3.11 and resumed the original installation through
13
+ update. After unlock and restoring the Anvil tunnel, status showed Wallet 0.3.3 /
14
+ Runtime 0.3.11, original Policy/Market connection and two settled Orders. The old
15
+ missing-result warning remains separate. This verifies return from incomplete B;
16
+ B has not run as a configured independent Wallet/Runtime pair.
17
+
18
+ Unreleased cleanup prepares the switch without starting services, then repair
19
+ starts them once. Current-version recovery now saves a repair checkpoint before
20
+ selecting units. Status/info distinguish fresh incomplete setup from lost existing
21
+ Wallet files. Wallet classifies RPC transport failure without exposing raw errors;
22
+ User preserves the code through recovery, status and Order guidance. Targeted tests
23
+ use temporary files, controlled service management and real local RPC/IPC failure;
24
+ they do not establish participant acceptance of these new changes. Publish Wallet
25
+ first, then pin the verified release in User before publishing the combined change.
26
+
27
+ Review follow-up: missing Policy with saved Order history or a Market connection
28
+ now requires file recovery, while key-only first setup can continue. Mixed RPC
29
+ and manual-review Order issues lead to the Order list rather than network-only
30
+ guidance. All 24 targeted info/status checks passed using temporary files and
31
+ controlled status observations; no participant data or services changed.
32
+
33
+ ## Update recovery during an unfinished switch — 2026-11-12
34
+
35
+ Published as `v0.3.11` at `6cf9f0d`. Full CI behavior and standalone package checks
36
+ passed; npm archive integrity independently verified. Wallet remains 0.3.3.
37
+ [Release workflow](https://github.com/c4rO-0/agentpay-user/actions/runs/43178613605).
38
+
39
+ User 0.3.11: the updater may replace the exact previously confirmed switch target
40
+ only after managed services and known listeners are stopped. It retains the switch
41
+ journal, runs the new service command after package installation, then repairs the
42
+ installation. A failed resume keeps the update checkpoint for retry; another target
43
+ cannot adopt the transition. An existing confirmed switch no longer needs duplicate
44
+ consent during resume. 35 update/selection checks passed with temporary storage,
45
+ real local sockets and controlled npm/systemctl; participant acceptance remains open.
46
+ Already-installed 0.3.9/0.3.10 updaters still reject this state before fetching code,
47
+ so the current stranded installation needs a one-time npm bootstrap. This fix cannot
48
+ retroactively change an installed updater.
49
+
3
50
  ## Participant incomplete-setup switch — 2026-11-12
4
51
 
52
+ Published as `v0.3.10` at `6e89266`; 20 targeted selection checks, full CI behavior
53
+ and standalone package checks passed. npm latest and archive integrity independently
54
+ verified. Wallet stays 0.3.3; participant switch-back remains to be verified.
55
+ [Release workflow](https://github.com/c4rO-0/agentpay-user/actions/runs/43173745431).
56
+
5
57
  Participant A-to-B setup cancellation exposed a switch-back failure: B had no
6
- Wallet directory, but selection required complete service settings. User 0.3.10 candidate
58
+ Wallet directory, but selection required complete service settings. User 0.3.10 fix
7
59
  allows demonstrably unconfigured B to release selection after confirmed systemd
8
60
  stop and socket checks. Existing key/Policy/connection or invalid directories still
9
61
  block missing-configuration bypass. Original participant A remains stopped until
@@ -1,3 +1,4 @@
1
+ import { inspectLocalSetup } from "./local-setup-state.mjs";
1
2
  // SPDX-License-Identifier: MPL-2.0
2
3
  // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
3
4
  // If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
@@ -89,7 +90,20 @@ export async function showLocalInfo({
89
90
  );
90
91
  return 1;
91
92
  }
92
- let incomplete = false;
93
+ const setup = await inspectLocalSetup(dataDir);
94
+ if (setup.state === "needed") {
95
+ write([
96
+ "------------------------------------------------------------",
97
+ "> Wallet information",
98
+ "------------------------------------------------------------",
99
+ "[WAIT] Setup not finished",
100
+ setup.hasKey ? "Your local key is saved. Continue setup to finish this installation." : "No Wallet has been created for this installation yet.",
101
+ "", "Data folder", " " + dataDir,
102
+ "", "Next", " " + setup.next,
103
+ ].join("\n"));
104
+ return 0;
105
+ }
106
+ let incomplete = setup.state === "attention";
93
107
  const read = async (name) => {
94
108
  try {
95
109
  return await privateJson(dataDir, name);
@@ -119,6 +133,11 @@ export async function showLocalInfo({
119
133
  "> Wallet information",
120
134
  divider,
121
135
  "Saved settings · live status: agentpay status",
136
+ ...(setup.state === "attention" ? [
137
+ "[!] Local Wallet files need attention",
138
+ "Keep this data folder. Check missing files against your backup before continuing.",
139
+ "Do not create a replacement Wallet to recover existing Orders.",
140
+ ] : []),
122
141
  ...section("Wallet & authorization"),
123
142
  ` Network: ${Number.isSafeInteger(policy?.chainId) && policy.chainId > 0 ? `eip155:${policy.chainId}` : "Not recorded"}`,
124
143
  ...field("Safe account", address(policy?.safe)),
@@ -0,0 +1,34 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+ // Distinguish an unfinished first setup from missing data in an existing Wallet.
3
+ // This is read-only guidance; it never creates keys or changes installation routing.
4
+ import {lstat} from 'node:fs/promises';
5
+ import {join} from 'node:path';
6
+
7
+ export async function inspectLocalSetup(dataDir) {
8
+ try {
9
+ for (const path of [dataDir,join(dataDir,'wallet'),join(dataDir,'runtime')]) {
10
+ const info=await lstat(path).catch(e=>{if(e.code==='ENOENT')return null;throw e;});
11
+ if(info && (!info.isDirectory() || info.isSymbolicLink() || info.uid!==process.getuid() || info.mode & 0o077))
12
+ return {state:'attention'};
13
+ }
14
+ async function present(name) {
15
+ const info=await lstat(join(dataDir,name)).catch(e=>{if(e.code==='ENOENT')return null;throw e;});
16
+ if(!info)return false;
17
+ if(!info.isFile() || info.isSymbolicLink() || info.uid!==process.getuid() || info.mode & 0o077)throw Error('Unsafe saved file');
18
+ return true;
19
+ }
20
+ const key=await present('wallet/session.keystore.json');
21
+ const policy=await present('wallet/policy.json');
22
+ const settings=await present('runtime/local-services.json');
23
+ if(key && policy && settings)return {state:'ready'};
24
+ // A saved connection or Order history indicates prior use. A key alone can
25
+ // belong to an interrupted first setup and must still allow setup to resume.
26
+ const priorUse=await present('market-connection.json') || await present('runtime/invocations.json');
27
+ if(!policy && priorUse)return {state:'attention'};
28
+ const prior=policy || priorUse || await present('runtime/connection.json');
29
+ if(!key && prior)return {state:'attention'};
30
+ if(policy && !settings)return {state:'attention'};
31
+ const deployment=await present('user-deployment.json') || await present('anvil-onboarding.json');
32
+ return {state:'needed',hasKey:key,next:deployment ? 'agentpay setup' : 'agentpay install --market YOUR_MARKET_URL'};
33
+ } catch {return {state:'attention'};}
34
+ }
@@ -106,7 +106,14 @@ async function updateUnderLock({
106
106
  } catch(error) { if(error.code !== 'ENOENT') throw error; }
107
107
  dataDir ??= await resolveUserDataDir({userDir});
108
108
  if (pending && pending.dataDir !== dataDir) throw Error('The interrupted update belongs to another data folder. Keep its recovery record for review.');
109
- const scope = await serviceScope({userDir,dataDir});
109
+ const scope = await serviceScope({userDir,dataDir,resumeUpdate:true});
110
+ const resumeSwitch = async options => {
111
+ if (!scope.resumeSwitch) return;
112
+ write('Continuing your previously confirmed installation switch…');
113
+ // Prepare the confirmed target without launching it; repair starts it once.
114
+ const output = await service('activate-setup',options);
115
+ if(output)write(output);
116
+ };
110
117
  if (pending && current.version === pending.targetVersion) {
111
118
  installing = true;
112
119
  if (current.dependencies?.['@c4roa0/agentpay-wallet'] !== pending.walletVersion ||
@@ -115,6 +122,7 @@ async function updateUnderLock({
115
122
  write('Continuing the interrupted update. Checking installed packages and service settings.');
116
123
  const configured = await lstat(join(dataDir,'runtime/local-services.json')).then(s=>s.isFile(),e=>{if(e.code==='ENOENT')return false;throw e;});
117
124
  if (configured) {
125
+ await resumeSwitch({env:{...process.env,AGENTPAY_USER_DATA_DIR:dataDir},cwd:target.prefix,timeout:600000});
118
126
  const output = await repair({env:{...process.env,AGENTPAY_USER_DATA_DIR:dataDir},cwd:target.prefix,timeout:600000});
119
127
  if(output)write(output);
120
128
  } else await officialTest({userDir,dataDir,write});
@@ -132,6 +140,24 @@ async function updateUnderLock({
132
140
  if (pending && (metadata.version !== pending.targetVersion || metadata.dependencies["@c4roa0/agentpay-wallet"] !== pending.walletVersion || metadata.dependencies["@c4roa0/agentpay-protocol"] !== pending.protocolVersion))
133
141
  throw Error("The published package differs from the interrupted update. No program files were changed.");
134
142
  if (!newer(metadata.version,current.version)) {
143
+ if (scope.resumeSwitch) {
144
+ if (!force && (await ask("Continue the previously confirmed installation switch? Type 'confirm', or 'q' to cancel")).trim().toLowerCase() !== 'confirm') {
145
+ write('Update cancelled. The saved switch was kept.'); return 0;
146
+ }
147
+ const options={env:{...process.env,AGENTPAY_USER_DATA_DIR:dataDir},cwd:target.prefix,timeout:600000};
148
+ // Even without a new package, keep repair resumable if the process exits
149
+ // after selecting the target units but before the one final service start.
150
+ if (!stable(current.dependencies?.['@c4roa0/agentpay-wallet']) || !stable(current.dependencies?.['@c4roa0/agentpay-protocol']))
151
+ throw Error('Installed package dependencies could not be verified.');
152
+ await atomicWritePrivate(progressPath, JSON.stringify({version:1,installation:userDir,dataDir,
153
+ targetVersion:current.version,walletVersion:current.dependencies['@c4roa0/agentpay-wallet'],
154
+ protocolVersion:current.dependencies['@c4roa0/agentpay-protocol']}) + '\n');
155
+ installing = true;
156
+ await resumeSwitch(options);
157
+ const output=await repair(options); if(output)write(output);
158
+ await unlink(progressPath);
159
+ return 0;
160
+ }
135
161
  write(`User ${current.version} is current. Checking the official test Skill.`);
136
162
  await officialTest({userDir,dataDir,write});
137
163
  return 0;
@@ -149,7 +175,8 @@ async function updateUnderLock({
149
175
  write(`User ${current.version} → ${metadata.version}`);
150
176
  write(`Wallet ${walletVersion === targetWallet ? `${walletVersion} (unchanged)` : `${walletVersion || 'not confirmed'} → ${targetWallet}`}`);
151
177
  write(`Installation folder: ${target.prefix}`);
152
- write(scope.active ? 'Background services will pause. Keys, permissions, Orders and cache are kept.' : 'This installation is inactive. The running Wallet and Runtime will not change.');
178
+ if (scope.resumeSwitch) write('After updating, AgentPay will finish your previously confirmed installation switch.');
179
+ write(scope.active ? 'Background services will pause. Keys, permissions, Orders and cache are kept.' : scope.resumeSwitch ? 'Services are stopped. Keys, permissions, Orders and cache are kept.' : 'This installation is inactive. The running Wallet and Runtime will not change.');
153
180
  if (!force && (await ask("Type 'confirm' to update, or 'q' to cancel")).trim().toLowerCase() !== 'confirm') {
154
181
  write('Update cancelled. Nothing was changed.'); return 0;
155
182
  }
@@ -202,7 +229,8 @@ async function updateUnderLock({
202
229
  installed.dependencies?.['@c4roa0/agentpay-protocol'] !== metadata.dependencies['@c4roa0/agentpay-protocol'])
203
230
  throw Error('The installed version does not match the confirmed update.');
204
231
  if (configured) {
205
- write(scope.active ? '3/3 Restore services and verify the official test Skill.' : '3/3 Verify the inactive installation and official test Skill.');
232
+ await resumeSwitch(options);
233
+ write(scope.active || scope.resumeSwitch ? '3/3 Restore services and verify the official test Skill.' : '3/3 Verify the inactive installation and official test Skill.');
206
234
  // A fresh Node process loads the NEW repair/client/official digest, not old imports.
207
235
  const output = await repair(options);
208
236
  if(output) write(output);
@@ -256,13 +256,15 @@ export function localReviewDetails(
256
256
  if (
257
257
  unknownOriginal ||
258
258
  unavailable ||
259
- (o?.errorCode && o.errorCode !== "ORDER_CHECK_UNAVAILABLE")
259
+ (o?.errorCode && !["ORDER_CHECK_UNAVAILABLE", "CHAIN_RPC_UNAVAILABLE"].includes(o.errorCode))
260
260
  ) {
261
261
  text(
262
262
  "Check the original Agent conversation and payment history. If still unclear, contact AgentPay support with the reference IDs below.",
263
263
  );
264
264
  text("Do not create another purchase to resolve this record.");
265
- } else if (o?.errorCode === "ORDER_CHECK_UNAVAILABLE")
265
+ } else if (o?.errorCode === "CHAIN_RPC_UNAVAILABLE")
266
+ text("Restore the chain RPC connection and any SSH tunnel. Keep Wallet unlocked; Runtime retries automatically. Do not pay again.");
267
+ else if (o?.errorCode === "ORDER_CHECK_UNAVAILABLE")
266
268
  text(
267
269
  "Runtime retries automatically. Keep Wallet unlocked and check your network if this continues.",
268
270
  );
@@ -358,6 +360,8 @@ export function localOrderSummary(entry, observation, review) {
358
360
  ? "SETTLED_RESULT_UNAVAILABLE"
359
361
  : null);
360
362
  const reasons = {
363
+ CHAIN_RPC_UNAVAILABLE:
364
+ "Cannot connect to the chain RPC. Check your network and any SSH tunnel. Runtime retries automatically; keep the original request.",
361
365
  RECOVERY_POLICY_UNAVAILABLE:
362
366
  "Original Policy cannot authorize this check (it may be revoked or belong to another key).",
363
367
  ORIGINAL_POLICY_UNKNOWN:
@@ -42,11 +42,13 @@ export async function showOrderStatus({
42
42
  return 0;
43
43
  } catch (error) {
44
44
  write("\n> Order status");
45
- write("[WAIT] Could not update from Settlement");
45
+ write(error.code === "CHAIN_RPC_UNAVAILABLE" ? "[WAIT] Cannot connect to the chain RPC" : "[WAIT] Could not update from Settlement");
46
46
  write(
47
47
  ["LOCKED", "WALLET_LOCKED"].includes(error.code)
48
48
  ? "Unlock Wallet: agentpay wallet unlock"
49
- : "Check agentpay status and your network, then retry this query.",
49
+ : error.code === "CHAIN_RPC_UNAVAILABLE"
50
+ ? "Check your network and any SSH port forwarding, then retry this query. No new payment is needed."
51
+ : "Check agentpay status and your network, then retry this query.",
50
52
  );
51
53
  const summary = localOrderSummary(entry, observation, review);
52
54
  write("\n> Last local record · not refreshed");
@@ -32,6 +32,7 @@ const resultIssue = (o) =>
32
32
  ? "SETTLED_RESULT_UNAVAILABLE"
33
33
  : null;
34
34
  const knownCodes = new Set([
35
+ "CHAIN_RPC_UNAVAILABLE",
35
36
  "LOCKED",
36
37
  "POLICY_UNBOUND",
37
38
  "POLICY_MIGRATION_PENDING",
@@ -6,7 +6,7 @@ import { join, resolve } from 'node:path';
6
6
  import { homedir } from 'node:os';
7
7
  import { autostartFiles, units } from './autostart-files.mjs';
8
8
  import { installationDirectory } from './installation-paths.mjs';
9
- import { withInstallationOperation } from './installation-operation.mjs';
9
+ import { withInstallationOperation, runInstallationCommand } from './installation-operation.mjs';
10
10
  import { readServiceSettings, listenerPresent } from './local-services.mjs';
11
11
  import { atomicWritePrivate } from './private-journal-files.mjs';
12
12
  import { ensureRuntimeConnection } from './runtime-connection-settings.mjs';
@@ -104,7 +104,7 @@ export async function withServiceSelection({target, action, ask, write = console
104
104
  write('Current data: ' + previous.dataDir);
105
105
  write('Selected data: ' + target.dataDir);
106
106
  write('Wallet and Runtime will stop. Keys, permissions and Order records stay in their own folders.');
107
- if (!ask || String(await ask("Type 'confirm' to switch, or 'q' to cancel")).trim().toLowerCase() !== 'confirm') {
107
+ if (!recovery && (!ask || String(await ask("Type 'confirm' to switch, or 'q' to cancel")).trim().toLowerCase() !== 'confirm')) {
108
108
  write('Switch cancelled. Existing services were kept.');
109
109
  return {cancelled:true};
110
110
  }
@@ -188,7 +188,8 @@ export async function withServiceLifecycleLock(operation) {
188
188
 
189
189
  // Called while holding the installation-operation lock. Maintenance of an inactive
190
190
  // program tree must never claim the fixed service pair or rewrite active data.
191
- export async function maintenanceServiceScope({userDir,dataDir,unitDir}) {
191
+ export async function maintenanceServiceScope({userDir,dataDir,unitDir, resumeUpdate = false,
192
+ ctl = (...args) => runInstallationCommand('systemctl',['--user',...args],{timeout:30000})}) {
192
193
  const canonical = async path => realpath(path).catch(error=>{if(error.code==='ENOENT')return resolve(path);throw error;});
193
194
  userDir = await canonical(userDir); dataDir = await canonical(dataDir);
194
195
  let saved;
@@ -197,8 +198,26 @@ export async function maintenanceServiceScope({userDir,dataDir,unitDir}) {
197
198
  const {assertPrivateFile}=await import('./private-journal-files.mjs');
198
199
  await assertPrivateFile(path); saved=JSON.parse(await readFile(path,'utf8'));
199
200
  if(saved.version!==1)throw Error('Unrecognized service selection. Keep it for review.');
200
- if(['stopping','preparing'].includes(saved.phase) && !await targetUnitsPrepared(unitDir,saved.target))
201
- throw Error('A service switch is unfinished. Retry service start from its selected installation before maintenance.');
201
+ if(['stopping','preparing'].includes(saved.phase) && !await targetUnitsPrepared(unitDir,saved.target)) {
202
+ if (!resumeUpdate || !saved.target ||
203
+ await canonical(saved.target.userDir)!==userDir || await canonical(saved.target.dataDir)!==dataDir)
204
+ throw Error('A service switch is unfinished. Run agentpay update from its originally selected installation.');
205
+ // Read-only preflight: the prior confirmation remains bound to this exact
206
+ // target. Do not replace code while any managed process could still use it.
207
+ await installedServiceSelection(unitDir,saved);
208
+ for (const name of units) {
209
+ const result=await ctl('show',name,'--property=ActiveState','--value');
210
+ if (!['inactive','failed'].includes(String(result.stdout||'').trim()))
211
+ throw Error('The interrupted switch still has running services. Stop them before updating.');
212
+ }
213
+ for (const path of new Set([saved.previous?.dataDir,dataDir].filter(Boolean))) {
214
+ const settings=await previousServiceSettings(path);
215
+ if (await listenerPresent(join(path,'wallet/wallet.sock')) ||
216
+ (settings && await listenerPresent({host:'127.0.0.1',port:Number(new URL(settings.runtimeUrl).port)})))
217
+ throw Error('An installation listener is still running. Stop it before updating.');
218
+ }
219
+ return {active:false,resumeSwitch:true};
220
+ }
202
221
  } catch(error) {if(error.code!=='ENOENT')throw error;}
203
222
  const owner=await installedServiceSelection(unitDir) || saved?.target;
204
223
  if(!owner)return {active:true}; // Existing single-install setup/repair behavior.
@@ -1,3 +1,4 @@
1
+ import { inspectLocalSetup } from "./local-setup-state.mjs";
1
2
  import { assessRunningSoftware } from "./software-versions.mjs";
2
3
  import { walletCliPath } from "./wallet-package.mjs";
3
4
  // SPDX-License-Identifier: MPL-2.0
@@ -179,10 +180,12 @@ export async function collectLocalStatus() {
179
180
  readAuthorizationStatus(dataDir),
180
181
  readMarketStatus(dataDir),
181
182
  inspectDeploymentStatus(dataDir),
183
+ inspectLocalSetup(dataDir),
182
184
  ]);
183
185
  const value = (i) =>
184
186
  results[i].status === "fulfilled" ? results[i].value : null;
185
187
  return {
188
+ setup: value(7),
186
189
  software: assessRunningSoftware(value(0), value(3)),
187
190
  deployment: value(6) || { state: "unknown" },
188
191
  market: value(5) || { state: "unknown" },
@@ -279,7 +282,7 @@ export function renderStatusPanel(
279
282
  const onlyRetryable =
280
283
  r?.orders.review > 0 &&
281
284
  issues.length === r.orders.review &&
282
- issues.every((issue) => issue.code === "ORDER_CHECK_UNAVAILABLE");
285
+ issues.every((issue) => ["ORDER_CHECK_UNAVAILABLE", "CHAIN_RPC_UNAVAILABLE"].includes(issue.code));
283
286
  const stopped = (s) =>
284
287
  s && ["failed", "inactive", "deactivating"].includes(s.ActiveState);
285
288
  let title = "Local checks passed",
@@ -292,6 +295,16 @@ export function renderStatusPanel(
292
295
  detail =
293
296
  "Your saved installation could not be read. Keep your existing Wallet data.";
294
297
  next = "agentpay service logs runtime";
298
+ } else if (model.setup?.state === "needed") {
299
+ title = "Setup not finished";
300
+ level = "wait";
301
+ detail = "Complete setup for this installation before starting its services.";
302
+ next = model.setup.next;
303
+ } else if (model.setup?.state === "attention") {
304
+ title = "Local Wallet files need attention";
305
+ level = "error";
306
+ detail = "Existing files are missing or could not be safely checked. Keep this data folder; do not create a replacement Wallet.";
307
+ next = "agentpay wallet info";
295
308
  } else if (model.deployment?.state === "changed") {
296
309
  title = "Spending permission needs updating";
297
310
  level = "error";
@@ -384,6 +397,11 @@ export function renderStatusPanel(
384
397
  detail =
385
398
  "Platform deployment check is unavailable. Check your network and retry.";
386
399
  next = "agentpay status";
400
+ } else if (onlyRetryable && issues.some(issue => issue.code === "CHAIN_RPC_UNAVAILABLE")) {
401
+ title = "Cannot connect to the chain RPC";
402
+ level = "wait";
403
+ detail = "Check your network and any SSH port forwarding. Order checks retry automatically; do not repeat purchases.";
404
+ next = "Restore the connection, then run agentpay status.";
387
405
  } else if (onlyRetryable) {
388
406
  title = "Order checks temporarily unavailable";
389
407
  level = "wait";
@@ -447,7 +465,7 @@ export function renderStatusPanel(
447
465
  "",
448
466
  ];
449
467
  if (!model.configurationError) {
450
- if (model.software) {
468
+ if (model.software && model.setup?.state !== "needed") {
451
469
  lines.push("> Software");
452
470
  for (const item of model.software) lines.push(` ${badge(item.state === "matched" ? "ok" : "wait", 6)} ${item.label.padEnd(8)} ${item.state === "matched" ? item.running : item.state === "unreported" ? "Running version not reported" : item.running === item.expected ? "Package interface mismatch" : `Running ${item.running} · installed ${item.expected}`}`);
453
471
  lines.push("");
@@ -461,15 +479,17 @@ export function renderStatusPanel(
461
479
  ? w.locked
462
480
  ? "Running · locked"
463
481
  : "Running · unlocked"
482
+ : model.setup?.state === "needed"
483
+ ? "Not started · setup needed"
464
484
  : stopped(ws)
465
485
  ? "Stopped"
466
486
  : "Not checked",
467
- w ? (w.locked ? "wait" : "ok") : "error",
487
+ w ? (w.locked ? "wait" : "ok") : model.setup?.state === "needed" ? "wait" : "error",
468
488
  );
469
489
  row(
470
490
  "Runtime",
471
- r ? "Running" : stopped(rs) ? "Stopped" : "Not checked",
472
- r ? "ok" : "error",
491
+ r ? "Running" : model.setup?.state === "needed" ? "Not started · setup needed" : stopped(rs) ? "Stopped" : "Not checked",
492
+ r ? "ok" : model.setup?.state === "needed" ? "wait" : "error",
473
493
  );
474
494
  if (r)
475
495
  row(
@@ -489,7 +509,9 @@ export function renderStatusPanel(
489
509
  );
490
510
  lines.push("", "> Policy");
491
511
  const policyState =
492
- model.deployment?.state === "changed"
512
+ model.setup?.state === "needed"
513
+ ? ["wait", "Setup not finished"]
514
+ : model.deployment?.state === "changed"
493
515
  ? ["error", "Update required · deployment changed"]
494
516
  : w?.policyRenewalPending
495
517
  ? ["wait", "Renewal incomplete"]
@@ -509,7 +531,7 @@ export function renderStatusPanel(
509
531
  lines.push(
510
532
  ` Valid until: ${new Date(authorization.validUntil * 1000).toLocaleString()}`,
511
533
  );
512
- if (model.deployment?.state === "unknown")
534
+ if (model.deployment?.state === "unknown" && model.setup?.state !== "needed")
513
535
  lines.push(` ${badge("wait")} Platform compatibility not confirmed`);
514
536
  if (model.deployment?.state === "downloaded")
515
537
  lines.push(
package/update-pair.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "format": "agentpay.local-update-pair.v1",
3
- "userVersion": "0.3.10",
4
- "walletVersion": "0.3.3",
3
+ "userVersion": "0.3.12",
4
+ "walletVersion": "0.3.4",
5
5
  "typesDependency": "0.14.3"
6
6
  }