@jc_stack/ez-agents 0.1.0-beta.39 → 0.1.0-beta.40
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/CHANGELOG.md +6 -0
- package/docs/upgrades.md +10 -7
- package/package.json +1 -1
- package/src/updates/supervisor.mjs +22 -2
- package/test/updates.test.mjs +24 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0-beta.40
|
|
4
|
+
|
|
5
|
+
- Apply eligible npm releases under each agent's saved automatic update policy
|
|
6
|
+
through the existing guarded updater. Queue one update at a time, preserve
|
|
7
|
+
manual policies, and wait for a newer version after a failed automatic update.
|
|
8
|
+
|
|
3
9
|
## 0.1.0-beta.39
|
|
4
10
|
|
|
5
11
|
- Render completed, binding-scoped application replies as WAV speech with
|
package/docs/upgrades.md
CHANGED
|
@@ -12,12 +12,13 @@ automatic channel for core and plugins without a saved policy. Existing explicit
|
|
|
12
12
|
stable or manual policies are preserved. The owner may select either per target.
|
|
13
13
|
The main target and installed plugins version independently.
|
|
14
14
|
|
|
15
|
-
The
|
|
16
|
-
|
|
17
|
-
agent
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
wrapper state into a session.
|
|
15
|
+
The owner chooses the saved update policy. The host supervisor owns
|
|
16
|
+
interruption-safe replacement. There is one host service per agent, not a second
|
|
17
|
+
agent or an additional updater daemon. Its replaceable child runs the normal CLI
|
|
18
|
+
transport. It checks npm every six hours while running and queues one eligible
|
|
19
|
+
release at a time when the policy is automatic. Discovery and replacement never
|
|
20
|
+
wake the agent or inject wrapper state into a session. Manual policies retain
|
|
21
|
+
owner-invoked updates through `ez updates`.
|
|
21
22
|
Checks use the installed scoped npm identity; failures are
|
|
22
23
|
visible in `updates check` and private `tools/updates/available.json`. Plugins marked
|
|
23
24
|
`private: true` in their package metadata are reported as local-source updates
|
|
@@ -265,5 +266,7 @@ execution authority.
|
|
|
265
266
|
|
|
266
267
|
Update discovery runs independently of active host work. A discovery failure is
|
|
267
268
|
logged locally and retried at the next regular six-hour check; it does not stop
|
|
268
|
-
the host or create a repair task.
|
|
269
|
+
the host or create a repair task. An automatically failed or rolled-back version
|
|
270
|
+
is not retried until a newer version appears or the owner explicitly retries.
|
|
271
|
+
Inspect `ez updates check` and `ez updates status` for target diagnostics.
|
|
269
272
|
Actual update transactions retain their existing admission, drain and rollback rules.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jc_stack/ez-agents",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.40",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "A lightweight foundation for persistent business AI assistants using existing AI harnesses, workspaces and plugins.",
|
|
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { atomic, locked } from '../plugins/manager.mjs';
|
|
6
6
|
import { callDeliverySocket } from '../delivery-socket-client.mjs';
|
|
7
|
-
import { state, read, jobs, jobPath, check, missing, cleanupStaleBackups } from './control.mjs';
|
|
7
|
+
import { state, read, jobs, jobPath, check, prepare, submit, missing, cleanupStaleBackups } from './control.mjs';
|
|
8
8
|
import { perform, environment } from './runtime.mjs';
|
|
9
9
|
|
|
10
10
|
const reservedProviderKeys=new Set(['HOME','LANG','LC_ALL','LOGNAME','PATH','SHELL','TERM','TMPDIR','USER','CODEX_HOME','NODE_OPTIONS']);
|
|
@@ -28,6 +28,22 @@ export async function idle(control) {
|
|
|
28
28
|
const status=await callDeliverySocket(path.join(control,'delivery.sock'),{op:'status'},2000).catch(()=>null);
|
|
29
29
|
return !status||status.running===0;
|
|
30
30
|
}
|
|
31
|
+
export async function queueAutomatic(home,available) {
|
|
32
|
+
const existing=await jobs(home);
|
|
33
|
+
if(existing.some(job=>['queued','applying','recovery-required'].includes(job.status)))return null;
|
|
34
|
+
for(const candidate of available) {
|
|
35
|
+
if(!candidate.newer||!candidate.policy?.automatic||!candidate.available)continue;
|
|
36
|
+
// A failed release needs a newer version or an explicit owner retry.
|
|
37
|
+
if(existing.some(job=>job.target===candidate.target&&job.version===candidate.available&&['failed','rolled-back'].includes(job.status)))continue;
|
|
38
|
+
try {
|
|
39
|
+
const job=await prepare(home,candidate.target,{release:candidate.available});
|
|
40
|
+
return await submit(home,job.id,true);
|
|
41
|
+
}catch {
|
|
42
|
+
console.error(`Automatic update of ${candidate.target} failed; inspect ez updates check/status.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
31
47
|
export async function supervise(deployment,signal,{discover=check}={}) {
|
|
32
48
|
const host=await read(path.join(deployment,'host-executor.json'));
|
|
33
49
|
if(host.agents.length!==1||!host.agents[0].toolsHome)throw Error('Initialize a single deployment plugin registry before starting the supervisor');
|
|
@@ -96,12 +112,16 @@ export async function supervise(deployment,signal,{discover=check}={}) {
|
|
|
96
112
|
const latest=await read(path.join(jobPath(home,pending.id),'job.json'));
|
|
97
113
|
if(latest.status==='queued'){latest.status='failed';latest.error=error.message;await atomic(path.join(jobPath(home,pending.id),'job.json'),latest);}else throw error;
|
|
98
114
|
} finally {await fs.rm(pause,{force:true});}
|
|
99
|
-
if(result?.status==='completed'
|
|
115
|
+
if(result?.status==='completed') {
|
|
116
|
+
if(pending.target==='main')return;
|
|
117
|
+
nextCheck=0;
|
|
118
|
+
}
|
|
100
119
|
}
|
|
101
120
|
if(Date.now()>=nextCheck) {
|
|
102
121
|
nextCheck=Date.now()+6*60*60*1000;
|
|
103
122
|
try {
|
|
104
123
|
const results=await discover(home);await atomic(path.join(directory,'available.json'),results);
|
|
124
|
+
await queueAutomatic(home,results);
|
|
105
125
|
} catch {
|
|
106
126
|
// Discovery is optional; its failure must not terminate the host.
|
|
107
127
|
// Do not expose registry response bodies or credentials in logs.
|
package/test/updates.test.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import { perform, environment, packageManager, backupStateDirectory } from '../s
|
|
|
13
13
|
import { atomic, snapshot, compose, prepareCommand } from '../src/plugins/manager.mjs';
|
|
14
14
|
import { bindUpdates } from '../src/updates/binding.mjs';
|
|
15
15
|
import { status as runtimeStatus } from '../src/updates/status.mjs';
|
|
16
|
-
import { providerEnvironment } from '../src/updates/supervisor.mjs';
|
|
16
|
+
import { providerEnvironment, queueAutomatic } from '../src/updates/supervisor.mjs';
|
|
17
17
|
const exec=promisify(execFile);
|
|
18
18
|
|
|
19
19
|
test('main upgrade backup omits a live plugin socket and keeps private files',async t=>{
|
|
@@ -442,6 +442,29 @@ test('npm candidates verify exact version and integrity; automatic policy is enf
|
|
|
442
442
|
pkg.version='0.1.2';await assert.rejects(prepare(f.home,'main',{release:'0.1.1'}),/version mismatch/);
|
|
443
443
|
});
|
|
444
444
|
|
|
445
|
+
test('supervisor queues an eligible release once and preserves manual and failed-version gates',async t=>{
|
|
446
|
+
const f=await fixture(t),data=await fs.readFile(await f.pack());
|
|
447
|
+
const {createHash}=await import('node:crypto');
|
|
448
|
+
const pkg={name:'@ez-test/example',version:'0.1.1',dist:{tarball:'https://registry.npmjs.org/@ez-test/example/-/example-0.1.1.tgz',integrity:'sha512-'+createHash('sha512').update(data).digest('base64')}};
|
|
449
|
+
const original=globalThis.fetch;
|
|
450
|
+
globalThis.fetch=async url=>new Response(String(url).endsWith('.tgz')?data:JSON.stringify(String(url).endsWith('/0.1.1')?pkg:{name:pkg.name,'dist-tags':{latest:pkg.version},versions:{[pkg.version]:pkg}}));
|
|
451
|
+
t.after(()=>globalThis.fetch=original);
|
|
452
|
+
await atomic(path.join(f.home,'updates/supervisor.json'),{at:Date.now()});
|
|
453
|
+
await command(f.home,['policy','main','manual']);
|
|
454
|
+
assert.equal(await queueAutomatic(f.home,await command(f.home,['check'])),null);
|
|
455
|
+
assert.deepEqual(await jobs(f.home),[]);
|
|
456
|
+
await command(f.home,['policy','main','beta']);
|
|
457
|
+
const queued=await queueAutomatic(f.home,await command(f.home,['check']));
|
|
458
|
+
assert.equal(queued.status,'queued');assert.equal(queued.automatic,true);
|
|
459
|
+
assert.equal(queued.origin.type,'npm');assert.equal(queued.sha256,digest(data));
|
|
460
|
+
assert.equal(await queueAutomatic(f.home,await command(f.home,['check'])),null);
|
|
461
|
+
assert.equal((await jobs(f.home)).length,1);
|
|
462
|
+
queued.status='failed';await atomic(path.join(jobPath(f.home,queued.id),'job.json'),queued);
|
|
463
|
+
assert.equal(await queueAutomatic(f.home,await command(f.home,['check'])),null);
|
|
464
|
+
assert.equal((await jobs(f.home)).length,1);
|
|
465
|
+
await assert.rejects(fs.readFile(path.join(f.agent.controlDir,'update-attention.json')),error=>error.code==='ENOENT');
|
|
466
|
+
});
|
|
467
|
+
|
|
445
468
|
test('update discovery follows latest while preserving legacy beta and stable-only policies',async t=>{
|
|
446
469
|
const {registryCandidate}=await import('../src/updates/artifact.mjs');
|
|
447
470
|
const name='@ez-test/example',pkg=v=>({name,version:v});
|