@c4roa0/agentpay-user 0.3.8 → 0.3.9
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 +7 -2
- package/doc/usage.md +8 -0
- package/package.json +1 -1
- package/progress.md +21 -0
- package/src/installation-operation.mjs +2 -2
- package/src/npm-update.mjs +11 -4
- package/src/official-test-update.mjs +13 -0
- package/src/skill-bundle.mjs +38 -0
- package/update-pair.json +1 -1
package/doc/TODO.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# User TODO
|
|
2
2
|
|
|
3
|
+
- [x] Verify local official Skill files before downloading and stream update repair progress (2026-11-12; targeted checks).
|
|
4
|
+
- [ ] Publish and verify the local official-Skill check optimization in the next User patch.
|
|
5
|
+
|
|
6
|
+
- [x] Publish User 0.3.8 with matching v0.3.8 tag; full CI, standalone package and public registry integrity passed (2026-11-12).
|
|
7
|
+
|
|
3
8
|
- [x] Recover failed service start/stop without blocking repair; incomplete target unit replacement remains protected (2026-11-12, controlled service-manager verification).
|
|
4
9
|
|
|
5
10
|
- [x] Fix shared-data switch client relocation and retry without a repair deadlock; isolated selection/connection checks pass (2026-11-11).
|
|
@@ -18,13 +23,13 @@
|
|
|
18
23
|
- [x] Implement and check storage-choice module (reuse/new/cancel); public installer selection now connects through a service-switch gate (2026-11-11).
|
|
19
24
|
- [x] Connect installer selection and service-start switching with confirmation, stopped-state/listener checks and Runtime lifetime exclusion; focused checks passed (2026-11-11; systemd results simulated).
|
|
20
25
|
- [x] Correct shared-directory purge, mixed-unit retry, manual setup regression, first-start preparation and startup-preference inheritance; 37 targeted checks pass (2026-11-11).
|
|
21
|
-
- [ ]
|
|
26
|
+
- [ ] Complete remaining switch-crash recovery and desktop login acceptance; real A/B/A systemd switching passed; preserve historical request scope across switches. Inactive maintenance isolation is implemented with scoped verification.
|
|
22
27
|
|
|
23
28
|
|
|
24
29
|
|
|
25
30
|
- [x] Isolated public npm fresh install, registry failure/retry and disposable-data uninstall (2026-11-11; no real systemd or financial acceptance).
|
|
26
31
|
- [x] Replace existence-only locking for new operations: inherited native lock survives caller death while writers live, then permits retry. Old unknown locks require review; they are not automatically removed.
|
|
27
|
-
- [
|
|
32
|
+
- [x] Publish reviewed folder-local npm uninstall completion guidance in User 0.3.8.
|
|
28
33
|
|
|
29
34
|
|
|
30
35
|
- [x] Participant directory-local update to User 0.3.6 / Wallet 0.3.3: running versions, original Orders in CLI/Market and available result cache verified (2026-11-11).
|
package/doc/usage.md
CHANGED
|
@@ -52,3 +52,11 @@ to that business example. An interrupted test retains its original request.
|
|
|
52
52
|
[Background service guide](runbooks/local-autostart.md) explains Linux user services,
|
|
53
53
|
login reminders and manual startup. A local lock or process stop does not revoke
|
|
54
54
|
permission; follow [emergency stop](runbooks/emergency-payment-stop.md) when needed.
|
|
55
|
+
|
|
56
|
+
### Official test Skill checks during updates
|
|
57
|
+
|
|
58
|
+
Update and repair verify the official test ZIP against the version pinned by User,
|
|
59
|
+
then compare its installed files. Matching local files need no download. Missing
|
|
60
|
+
archives trigger a download; changed or incomplete extracted files are kept and
|
|
61
|
+
reported for review. These checks do not start a payment or select a newer Skill
|
|
62
|
+
from Market. Software updates display repair progress while it runs.
|
package/package.json
CHANGED
package/progress.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# AgentPay User progress
|
|
2
2
|
|
|
3
|
+
## Official Skill local verification — 2026-11-12
|
|
4
|
+
|
|
5
|
+
User 0.3.9 candidate: update/repair verifies the release-pinned local ZIP and its extracted
|
|
6
|
+
files before using the network. Matching files skip download; edited or incomplete
|
|
7
|
+
extracted files are preserved with guidance. Software-update repair progress streams
|
|
8
|
+
as it happens. Targeted update/package regressions passed; the actual pinned 1.2.1
|
|
9
|
+
archive was installed in temporary storage and verified with network access forbidden.
|
|
10
|
+
Child output streaming was checked with a real process. No live services or payments changed.
|
|
11
|
+
|
|
12
|
+
## User 0.3.8 published — 2026-11-12
|
|
13
|
+
|
|
14
|
+
User `0.3.8` and tag `v0.3.8` identify release commit `727e547`.
|
|
15
|
+
[Publication workflow](https://github.com/c4rO-0/agentpay-user/actions/runs/43148236738)
|
|
16
|
+
passed the complete behavior suite, standalone package installation and publication.
|
|
17
|
+
The public npm `latest` version and archive integrity were independently verified.
|
|
18
|
+
Dependencies remain Wallet 0.3.3 and Protocol 0.14.3; official test Skill remains 1.2.1.
|
|
19
|
+
The release includes multiple installations with one active Runtime/Wallet pair.
|
|
20
|
+
Real A/B/A switching passed; desktop login/reboot, full guided installation and
|
|
21
|
+
complete interrupted-switch acceptance remain separate pending checks.
|
|
22
|
+
Agent's candidate template is not published by this release.
|
|
23
|
+
|
|
3
24
|
## Real two-installation switch passed — 2026-11-12
|
|
4
25
|
|
|
5
26
|
Actual user-systemd switching A → B → A passed using source and folder-local npm
|
|
@@ -91,7 +91,7 @@ export function installationChildOptions({env = process.env, stdio = ['ignore','
|
|
|
91
91
|
|
|
92
92
|
// Wait for close even on timeout/error: returning on a timer while npm still writes
|
|
93
93
|
// would release the caller's lock too early. Writer children retain the descriptor.
|
|
94
|
-
export async function runInstallationCommand(file, args, {env = process.env, timeout = 120000, maxBuffer = 2 * 1024 * 1024, cwd} = {}) {
|
|
94
|
+
export async function runInstallationCommand(file, args, {env = process.env, timeout = 120000, maxBuffer = 2 * 1024 * 1024, cwd, onStdout} = {}) {
|
|
95
95
|
const {spawn} = await import('node:child_process');
|
|
96
96
|
return new Promise((resolve,reject) => {
|
|
97
97
|
const child = spawn(file,args,{cwd,...installationChildOptions({env})});
|
|
@@ -102,7 +102,7 @@ export async function runInstallationCommand(file, args, {env = process.env, tim
|
|
|
102
102
|
killTimer ||= setTimeout(()=>child.kill('SIGKILL'),1000);
|
|
103
103
|
};
|
|
104
104
|
const timer = setTimeout(()=>stop(Error('Command timed out.')),timeout);
|
|
105
|
-
child.stdout.on('data',chunk=>{if(failure)return;stdout+=chunk;if(Buffer.byteLength(stdout)>maxBuffer)stop(Error('Command output exceeded its limit.'));});
|
|
105
|
+
child.stdout.on('data',chunk=>{if(failure)return;stdout+=chunk;if(onStdout)onStdout(chunk.toString());if(Buffer.byteLength(stdout)>maxBuffer)stop(Error('Command output exceeded its limit.'));});
|
|
106
106
|
child.stderr.on('data',chunk=>{if(failure)return;stderr+=chunk;if(Buffer.byteLength(stderr)>maxBuffer)stop(Error('Command output exceeded its limit.'));});
|
|
107
107
|
child.once('error',error=>{failure=error;});
|
|
108
108
|
child.once('close',(code,signal)=>{
|
package/src/npm-update.mjs
CHANGED
|
@@ -21,12 +21,12 @@ function newer(next, current) {
|
|
|
21
21
|
for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
|
|
22
22
|
return false;
|
|
23
23
|
}
|
|
24
|
-
async function command(file, args, { logPath, timeout = 120000, env = process.env, cwd = process.cwd() } = {}) {
|
|
24
|
+
async function command(file, args, { logPath, timeout = 120000, env = process.env, cwd = process.cwd(), onStdout } = {}) {
|
|
25
25
|
try {
|
|
26
|
-
const result = await execute(file, args, { timeout, cwd, maxBuffer: 2 * 1024 * 1024,
|
|
26
|
+
const result = await execute(file, args, { timeout, cwd, onStdout, maxBuffer: 2 * 1024 * 1024,
|
|
27
27
|
env: { ...env, PATH: dirname(process.execPath) + ':' + (env.PATH || '') } });
|
|
28
28
|
if (logPath) await appendFile(logPath, result.stdout + result.stderr, {mode:0o600});
|
|
29
|
-
return result.stdout.trim();
|
|
29
|
+
return onStdout ? '' : result.stdout.trim();
|
|
30
30
|
} catch (error) {
|
|
31
31
|
if (logPath) await appendFile(logPath, String(error.stdout || '') + String(error.stderr || ''), {mode:0o600});
|
|
32
32
|
throw Error(logPath ? 'The command did not complete. Check the update log.' : 'Could not query npm. Check your network and npm configuration, then retry.');
|
|
@@ -75,7 +75,14 @@ async function updateUnderLock({
|
|
|
75
75
|
ask = askConfirmation,
|
|
76
76
|
npm = (args, options) => command('npm', args, options),
|
|
77
77
|
service = (action, options) => command(process.execPath, [join(userDir,'scripts/autostart.mjs'), action], options),
|
|
78
|
-
repair = options =>
|
|
78
|
+
repair = options => {
|
|
79
|
+
let pending = '';
|
|
80
|
+
return command(process.execPath, [join(userDir,'scripts/repair.mjs'), '-f'], {...options, onStdout(chunk) {
|
|
81
|
+
pending += chunk;
|
|
82
|
+
const lines = pending.split('\n'); pending = lines.pop();
|
|
83
|
+
for (const line of lines) write(line);
|
|
84
|
+
}}).finally(() => { if (pending) write(pending); });
|
|
85
|
+
},
|
|
79
86
|
officialTest = prepareOfficialTestUpdate,
|
|
80
87
|
serviceScope = maintenanceServiceScope,
|
|
81
88
|
} = {}) {
|
|
@@ -70,6 +70,19 @@ export async function prepareOfficialTestUpdate({
|
|
|
70
70
|
} = {}) {
|
|
71
71
|
write("\n> Official test Skill");
|
|
72
72
|
try {
|
|
73
|
+
// Only the release-pinned archive can establish the expected installed files.
|
|
74
|
+
// Never trust installation.json or a Market-provided digest for this shortcut.
|
|
75
|
+
const { inspectSavedSkillBundle } = await import("./skill-bundle.mjs");
|
|
76
|
+
const local = await inspectSavedSkillBundle(dataDir, OFFICIAL_TEST_PACKAGE);
|
|
77
|
+
if (local.status === "ready") {
|
|
78
|
+
write(`Version ${OFFICIAL_TEST_PACKAGE.version} verified locally. No download needed.`);
|
|
79
|
+
return local;
|
|
80
|
+
}
|
|
81
|
+
if (local.status === "local_edits") {
|
|
82
|
+
write("Local test Skill files are changed or incomplete. Existing files were kept; review them before testing.");
|
|
83
|
+
return local;
|
|
84
|
+
}
|
|
85
|
+
write("Downloading the official test Skill…");
|
|
73
86
|
let source;
|
|
74
87
|
for (const name of ["anvil-onboarding.json", "user-deployment.json"]) {
|
|
75
88
|
const saved = await readJson(await deploymentFilePath(dataDir, userDir, name));
|
package/src/skill-bundle.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MPL-2.0
|
|
2
2
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
3
3
|
// If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { constants } from "node:fs";
|
|
4
5
|
import { currentTimeMs } from "./local-time.mjs";
|
|
5
6
|
import { readSkillZip } from "./skill-zip.mjs";
|
|
6
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
8
|
import {
|
|
9
|
+
open,
|
|
8
10
|
mkdir,
|
|
9
11
|
lstat,
|
|
10
12
|
readFile,
|
|
@@ -140,3 +142,39 @@ export async function installSkillBundle({
|
|
|
140
142
|
await rm(staging, { recursive: true, force: true });
|
|
141
143
|
}
|
|
142
144
|
}
|
|
145
|
+
|
|
146
|
+
// Read-only fast path for a release-pinned package. A matching ZIP alone is not
|
|
147
|
+
// enough: compare every installed file with its authenticated archive content.
|
|
148
|
+
export async function inspectSavedSkillBundle(dataDir, pin) {
|
|
149
|
+
const parent = join(dataDir, "runtime", "skill-bundles");
|
|
150
|
+
const directory = join(parent, `${pin.name}-${pin.version}-${pin.sha256}`);
|
|
151
|
+
async function readBounded(file, max) {
|
|
152
|
+
const handle = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
153
|
+
try {
|
|
154
|
+
const stat = await handle.stat();
|
|
155
|
+
if (!stat.isFile() || stat.size > max) throw Error("Invalid saved Skill file");
|
|
156
|
+
return await handle.readFile();
|
|
157
|
+
} finally { await handle.close(); }
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
for (const path of [dataDir, join(dataDir, "runtime"), parent, directory]) await privateDir(path);
|
|
161
|
+
const bytes = await readBounded(join(directory, "publisher.zip"), skillArchiveLimits.compressed);
|
|
162
|
+
if (bundleDigest(bytes) !== pin.sha256) return {status: "download_required"};
|
|
163
|
+
const parsed = parseSkillBundle(bytes);
|
|
164
|
+
if (parsed.manifest.packageName !== pin.name || parsed.manifest.packageVersion !== pin.version)
|
|
165
|
+
return {status: "download_required"};
|
|
166
|
+
try {
|
|
167
|
+
for (const [name, expected] of Object.entries(parsed.files)) {
|
|
168
|
+
// Refuse symlinked intermediate directories as well as symlinked files.
|
|
169
|
+
let ancestor = dirname(join(directory, name));
|
|
170
|
+
while (ancestor !== directory) {
|
|
171
|
+
await privateDir(ancestor);
|
|
172
|
+
ancestor = dirname(ancestor);
|
|
173
|
+
}
|
|
174
|
+
const actual = await readBounded(join(directory, name), skillArchiveLimits.file);
|
|
175
|
+
if (!actual.equals(expected)) return {status: "local_edits", directory};
|
|
176
|
+
}
|
|
177
|
+
} catch { return {status: "local_edits", directory}; }
|
|
178
|
+
return {status: "ready", directory};
|
|
179
|
+
} catch { return {status: "download_required"}; }
|
|
180
|
+
}
|