@jc_stack/ez-agents 0.1.0-beta.46 → 0.1.0-beta.48
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/.dockerignore +2 -0
- package/.env.example +3 -0
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +38 -0
- package/CONTRIBUTING.md +1 -1
- package/Dockerfile +18 -10
- package/THIRD_PARTY_NOTICES.md +6 -3
- package/bin/ez +7 -0
- package/docker/broker-readiness.mjs +54 -0
- package/docker/broker-update-smoke.mjs +86 -0
- package/docker/engine-pnpm-workspace.yaml +4 -0
- package/docker/run.ts +1 -1
- package/docker/smoke.mjs +1 -1
- package/docs/application-channel.md +32 -1
- package/docs/architecture/ai-selection.md +26 -5
- package/docs/architecture/authority-boundaries.md +2 -2
- package/docs/development-and-testing.md +1 -1
- package/docs/docker-runtime.md +39 -0
- package/docs/isolated-plugin-update-readiness.md +66 -0
- package/docs/plugin-guide.md +1 -1
- package/docs/plugins.md +1 -1
- package/docs/releasing.md +5 -1
- package/docs/scheduling.md +117 -9
- package/docs/selective-monitoring.md +13 -0
- package/docs/upgrades.md +38 -1
- package/docs/work-preflight.md +9 -0
- package/package.json +4 -3
- package/pnpm-workspace.yaml +7 -0
- package/scripts/smoke-scheduler.ts +4 -1
- package/src/ai.ts +41 -4
- package/src/application-channel.ts +43 -3
- package/src/application-cli.ts +7 -1
- package/src/application-origin.ts +2 -0
- package/src/audio.ts +37 -22
- package/src/client-defaults.ts +3 -2
- package/src/config.ts +9 -0
- package/src/control-state.ts +3 -1
- package/src/delivery-socket-client.d.mts +2 -0
- package/src/delivery-socket-client.mjs +2 -0
- package/src/delivery-socket.ts +38 -1
- package/src/event-sources.ts +2 -2
- package/src/executor.ts +72 -1
- package/src/files.ts +47 -3
- package/src/host-executor.ts +40 -26
- package/src/index.ts +42 -17
- package/src/install-tools.mjs +18 -9
- package/src/menu.ts +4 -2
- package/src/model-policy.ts +4 -0
- package/src/plugin-broker.mjs +47 -4
- package/src/plugins/manager.mjs +11 -4
- package/src/run-evidence.ts +78 -0
- package/src/runs.ts +16 -2
- package/src/schedule-cli.ts +80 -10
- package/src/schedule-preflight.ts +23 -0
- package/src/scheduled-tasks.ts +16 -12
- package/src/scheduler.ts +102 -16
- package/src/scripts.ts +167 -0
- package/src/setup.ts +2 -2
- package/src/task-capability.ts +17 -1
- package/src/task-executor.ts +11 -8
- package/src/task-mcp.ts +20 -8
- package/src/tasks.ts +89 -9
- package/src/updates/runtime.mjs +53 -11
- package/src/updates/supervisor.mjs +23 -13
- package/test/ai.test.ts +85 -5
- package/test/application-channel.test.ts +16 -1
- package/test/audio.test.ts +19 -0
- package/test/channel-owner.test.ts +2 -2
- package/test/config.test.ts +8 -0
- package/test/desktop-bridge.test.ts +2 -1
- package/test/executor.test.ts +37 -0
- package/test/failure.test.ts +4 -4
- package/test/files.test.ts +44 -0
- package/test/host-executor.test.ts +49 -6
- package/test/install-json.test.mjs +21 -0
- package/test/install-tools.test.mjs +6 -5
- package/test/native-tasks.test.ts +1 -1
- package/test/plugin-broker.test.mjs +64 -0
- package/test/plugin-manager.test.mjs +20 -0
- package/test/private-speech-config.test.ts +22 -0
- package/test/run-evidence.test.ts +78 -0
- package/test/schedule-cli.test.ts +47 -3
- package/test/schedule-preflight.test.ts +49 -0
- package/test/scheduled-tasks.test.ts +2 -2
- package/test/scheduler-host.test.ts +21 -3
- package/test/scheduler-relay.test.ts +84 -4
- package/test/scheduler.test.ts +21 -1
- package/test/script-schedules.test.ts +219 -0
- package/test/task-native.test.ts +50 -12
- package/test/tasks.test.ts +97 -4
- package/test/updates.test.mjs +77 -18
package/test/updates.test.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import { perform, environment, packageManager, backupStateDirectory, execute } f
|
|
|
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, queueAutomatic, waitForHostHeartbeat } from '../src/updates/supervisor.mjs';
|
|
16
|
+
import { providerEnvironment, queueAutomatic, waitForHostHeartbeat, withIdleUpgrade } from '../src/updates/supervisor.mjs';
|
|
17
17
|
const exec=promisify(execFile);
|
|
18
18
|
|
|
19
19
|
test('structured runtime output excludes warnings but failures retain diagnostics',async()=>{
|
|
@@ -113,14 +113,17 @@ test('root preparation leaves private plugin updates readable by the installer',
|
|
|
113
113
|
const script=`const fs=require('fs');fs.readFileSync(${JSON.stringify(path.join(dir,'job.json'))});fs.readFileSync(${JSON.stringify(path.join(dir,'candidate.tgz'))});`;
|
|
114
114
|
await exec('setpriv',['--reuid=20001','--regid=20002','--clear-groups','--no-new-privs',process.execPath,'-e',script]);
|
|
115
115
|
});
|
|
116
|
-
function runtime(f,{fail,stopped=false,buildArgs={CODEX_CLI_VERSION:'0.156.1'}}={}) {
|
|
117
|
-
const calls=[];let failed=false;
|
|
116
|
+
function runtime(f,{fail,stopped=false,buildArgs={CODEX_CLI_VERSION:'0.156.1'},latest='0.160.0'}={}) {
|
|
117
|
+
const calls=[];let failed=false,brokerImage='sha256:'+'a'.repeat(64);
|
|
118
118
|
const execute=async(command,args,opts)=>{
|
|
119
|
-
calls.push([command,...args]);if(fail&&!failed&&fail(command,args)){failed=true;throw Error('Synthetic failure');}
|
|
119
|
+
calls.push([command,...args]);if(args.includes('view'))return JSON.stringify(latest);if(fail&&!failed&&fail(command,args)){failed=true;throw Error('Synthetic failure');}
|
|
120
120
|
if(args.at(-1)==='--version')return '10.30.3';
|
|
121
121
|
if(args.includes('config'))return JSON.stringify({services:{relay:{build:{args:buildArgs}}}});
|
|
122
|
-
if(args.includes('ps'))return stopped?'':'
|
|
123
|
-
if(args
|
|
122
|
+
if(args.includes('ps'))return stopped?'':'a'.repeat(64);
|
|
123
|
+
if(args.some(arg=>arg.includes('EZ_BROKER_STRUCTURAL_READINESS_FAILED'))) {const [hostSha256,registrySha256,configSha256]=args.slice(-3);return JSON.stringify({version:1,ready:true,hostSha256,registrySha256,configSha256,plugins:[]});}
|
|
124
|
+
if(args[0]==='image'&&args[1]==='inspect')return 'sha256:'+'b'.repeat(64);
|
|
125
|
+
if(args.includes('--force-recreate'))brokerImage=(await fs.readFile(args[args.lastIndexOf('--env-file')+1],'utf8')).trim().split('=')[1];
|
|
126
|
+
if(args[0]==='inspect')return brokerImage;
|
|
124
127
|
if(args[0]==='volume'&&args[1]==='ls')return 'existing';
|
|
125
128
|
if(args[0]==='volume')return JSON.stringify([{Name:args[2]}]);
|
|
126
129
|
if(args[0]==='run')await fs.writeFile(opts.outputFile,'retained-private-state',{mode:0o600});
|
|
@@ -306,7 +309,7 @@ test('main transaction stages before stopping, pins rollback image, preserves st
|
|
|
306
309
|
assert.notEqual(await fs.readFile(path.join(active,'bin/example.mjs'),'utf8'),'tampered');
|
|
307
310
|
assert(r.calls.findIndex(c=>c.includes('build'))<r.calls.findIndex(c=>c[0]==='stopHost'));
|
|
308
311
|
const build=r.calls.find(c=>c[0]==='docker'&&c[1]==='build');
|
|
309
|
-
assert.equal(build[build.indexOf('--build-arg')+1],'CODEX_CLI_VERSION=0.
|
|
312
|
+
assert.equal(build[build.indexOf('--build-arg')+1],'CODEX_CLI_VERSION=0.160.0');
|
|
310
313
|
const status=await command(f.home,['status']);assert(!JSON.stringify(status).includes('private-test-token'));assert(!('rollback'in status.jobs[0]));
|
|
311
314
|
assert.equal((await fs.stat(path.join(jobPath(f.home,job.id),'job.json'))).mode&0o777,0o600);
|
|
312
315
|
});
|
|
@@ -320,12 +323,14 @@ test('main activation and rollback bind guidance from the selected package root'
|
|
|
320
323
|
assert.equal((await perform(failed.home,await queued(failed),runtime(failed,{fail:(_command,args)=>args.includes('up')}))).status,'rolled-back');
|
|
321
324
|
assert.equal(await fs.readFile(path.join(failed.agent.workspace,'AGENTS.md'),'utf8'),'old guidance\n');
|
|
322
325
|
});
|
|
323
|
-
test('
|
|
326
|
+
test('latest overrides stale deployment pins and changing latest changes image bindings',async t=>{
|
|
324
327
|
const tags=[],hashes=[];
|
|
325
328
|
for(const pin of ['0.153.4','0.156.1']) {
|
|
326
|
-
const f=await fixture(t),job=await queued(f),r=runtime(f,{buildArgs:{CODEX_CLI_VERSION:pin}
|
|
329
|
+
const f=await fixture(t),job=await queued(f),r=runtime(f,{buildArgs:{CODEX_CLI_VERSION:'0.153.4'},latest:pin});
|
|
327
330
|
assert.equal((await perform(f.home,job,r)).status,'completed');
|
|
328
331
|
const build=r.calls.find(c=>c[0]==='docker'&&c[1]==='build'),tag=build[build.indexOf('-t')+1];
|
|
332
|
+
assert.ok(build.includes(`CODEX_CLI_VERSION=${pin}`));
|
|
333
|
+
assert.equal((await read(path.join(jobPath(f.home,job.id),'job.json'))).codexVersion,pin);
|
|
329
334
|
assert.match(await fs.readFile(path.join(f.config.deploymentDir,'docker.env'),'utf8'),new RegExp(tag));
|
|
330
335
|
tags.push(tag);hashes.push(job.sha256);
|
|
331
336
|
}
|
|
@@ -382,27 +387,60 @@ test('plugin updates register additive command routes on an unchanged deployment
|
|
|
382
387
|
const dispatch=await prepareCommand(f.home,'query',[]);assert.deepEqual(dispatch.argv.slice(-4),['node','sample','/app/bin/example.mjs','query']);
|
|
383
388
|
assert(r.calls.some(call=>call.includes('up')));
|
|
384
389
|
});
|
|
385
|
-
test('isolated plugin replacement
|
|
390
|
+
test('isolated plugin replacement recreates only the pinned broker and verifies activation and rollback',async t=>{
|
|
386
391
|
const f=await fixture(t,'plugin');
|
|
387
392
|
await fs.appendFile(path.join(f.config.deploymentDir,'docker.env'),'EZ_EXECUTOR_TRANSPORT=local\n');
|
|
388
393
|
const job=await queued(f),r=runtime(f),execute=r.execute;
|
|
389
394
|
let observed;
|
|
390
395
|
r.execute=async(command,args,options)=>{
|
|
391
|
-
if(args.includes('
|
|
396
|
+
if(args.some(arg=>arg.includes('EZ_BROKER_STRUCTURAL_READINESS_FAILED'))) observed=(await read(path.join(f.home,'registry.json'))).plugins.sample.manifest.version;
|
|
397
|
+
if(args.includes('--force-recreate')) {
|
|
398
|
+
assert.equal(args.at(-1),'plugin-broker');
|
|
399
|
+
for(const flag of ['--no-deps','--no-build','--wait'])assert(args.includes(flag));
|
|
400
|
+
assert.equal(args[args.indexOf('--pull')+1],'never');
|
|
401
|
+
assert.equal(await fs.readFile(args[args.lastIndexOf('--env-file')+1],'utf8'),'EZ_RELAY_IMAGE=sha256:'+ 'a'.repeat(64)+'\n');
|
|
402
|
+
}
|
|
392
403
|
return execute(command,args,options);
|
|
393
404
|
};
|
|
394
405
|
assert.equal((await perform(f.home,job,r)).status,'completed');
|
|
395
|
-
assert.equal(observed,'0.1.1');
|
|
396
|
-
assert.equal(r.calls.filter(call=>call.includes('restart')).length,1);
|
|
406
|
+
assert.equal(observed,'0.1.1');assert.equal(job.brokerReadiness.ready,true);
|
|
397
407
|
const interrupted=await read(path.join(jobPath(f.home,job.id),'job.json'));interrupted.status='applying';
|
|
398
408
|
assert.equal((await perform(f.home,interrupted,r)).status,'rolled-back');
|
|
399
|
-
assert.equal(observed,'0.1.0');
|
|
400
|
-
assert.equal(r.calls.filter(call=>call.includes('
|
|
409
|
+
assert.equal(observed,'0.1.0');assert.equal(interrupted.rollbackBrokerReadiness.ready,true);
|
|
410
|
+
assert.equal(r.calls.filter(call=>call.includes('--force-recreate')).length,2);
|
|
411
|
+
assert(!r.calls.some(call=>call.includes('restart')||call[0]==='stopHost'));
|
|
412
|
+
});
|
|
413
|
+
for(const boundary of ['recreate','probe','readback'])test(`isolated broker ${boundary} failure cannot complete an update`,async t=>{
|
|
414
|
+
const f=await fixture(t,'plugin');await fs.appendFile(path.join(f.config.deploymentDir,'docker.env'),'EZ_EXECUTOR_TRANSPORT=local\n');
|
|
415
|
+
const job=await queued(f),r=runtime(f),base=r.execute;
|
|
416
|
+
r.execute=async(c,a,o)=>{
|
|
417
|
+
if(boundary==='recreate'&&a.includes('--force-recreate')||boundary==='probe'&&a.some(arg=>arg.includes('EZ_BROKER_STRUCTURAL_READINESS_FAILED')))throw Error('Synthetic broker failure');
|
|
418
|
+
if(boundary==='readback'&&a.some(arg=>arg.includes('EZ_BROKER_STRUCTURAL_READINESS_FAILED')))return JSON.stringify({version:1,ready:true,hostSha256:'0'.repeat(64)});
|
|
419
|
+
return base(c,a,o);
|
|
420
|
+
};
|
|
421
|
+
const result=await perform(f.home,job,r);
|
|
422
|
+
assert.equal(result.status,'recovery-required');assert(result.error);assert(result.recoveryError);
|
|
423
|
+
assert.equal((await read(path.join(f.home,'registry.json'))).plugins.sample.manifest.version,'0.1.0');
|
|
424
|
+
});
|
|
425
|
+
test('main isolated activation and retained-relay rollback also refresh the broker',async t=>{
|
|
426
|
+
const f=await fixture(t);await fs.appendFile(path.join(f.config.deploymentDir,'docker.env'),'EZ_EXECUTOR_TRANSPORT=local\n');
|
|
427
|
+
const job=await queued(f),r=runtime(f);
|
|
428
|
+
assert.equal((await perform(f.home,job,r)).status,'completed');assert.equal(job.brokerReadiness.ready,true);
|
|
429
|
+
const base=r.execute;r.execute=async(c,a,o)=>a[0]==='inspect'&&a.includes('{{json .}}')?JSON.stringify({Image:'sha256:'+'a'.repeat(64),State:{Running:true,Health:{Status:'healthy'}}}):base(c,a,o);
|
|
430
|
+
const interrupted=await read(path.join(jobPath(f.home,job.id),'job.json'));interrupted.status='applying';
|
|
431
|
+
assert.equal((await perform(f.home,interrupted,r)).status,'rolled-back');
|
|
432
|
+
assert.equal(interrupted.rollbackBrokerReadiness.ready,true);
|
|
433
|
+
assert.equal(r.calls.filter(call=>call.includes('--force-recreate')).length,2);
|
|
434
|
+
assert.equal(r.calls.filter(call=>call.includes('up')&&call.at(-1)==='relay').length,1);
|
|
435
|
+
});
|
|
436
|
+
test('host-capable updates never inspect, recreate or probe an isolated broker',async t=>{
|
|
437
|
+
const f=await fixture(t,'plugin'),r=runtime(f);assert.equal((await perform(f.home,await queued(f),r)).status,'completed');
|
|
438
|
+
assert(!r.calls.some(call=>call.includes('plugin-broker')||call.some(arg=>arg.includes('EZ_BROKER_STRUCTURAL_READINESS_FAILED'))));
|
|
401
439
|
});
|
|
402
440
|
test('plugin update preserves a created but never running service as stopped',async t=>{
|
|
403
441
|
const f=await fixture(t,'plugin'),job=await queued(f),r=runtime(f),execute=r.execute;
|
|
404
442
|
r.execute=async(command,args,options)=>{
|
|
405
|
-
if(args.includes('ps')) {r.calls.push([command,...args]);return args.includes('--status')?'':'created-container';}
|
|
443
|
+
if(args.includes('ps')) {r.calls.push([command,...args]);if(args.includes('view'))return JSON.stringify(latest);return args.includes('--status')?'':'created-container';}
|
|
406
444
|
return execute(command,args,options);
|
|
407
445
|
};
|
|
408
446
|
assert.equal((await perform(f.home,job,r)).status,'completed');
|
|
@@ -495,7 +533,7 @@ for(const provider of ['pnpm','corepack']) test(`supervisor with only ${provider
|
|
|
495
533
|
// Package archives never contain node_modules; the fake pnpm below provisions the fixture loader.
|
|
496
534
|
await fs.rm(path.join(f.source,'node_modules'),{recursive:true});
|
|
497
535
|
const log=path.join(f.root,'commands.jsonl');
|
|
498
|
-
await fs.writeFile(path.join(fake,provider),`#!${process.execPath}\nif(${JSON.stringify(provider)}==='corepack'&&process.argv[2]!=='pnpm@10.30.3')throw Error('Unpinned manager');if(process.argv.includes('--version')){console.log('10.30.3');process.exit(0)}const fs=require('fs');fs.mkdirSync('node_modules/tsx/dist',{recursive:true});fs.writeFileSync('node_modules/tsx/dist/loader.mjs','');`,{mode:0o755});
|
|
536
|
+
await fs.writeFile(path.join(fake,provider),`#!${process.execPath}\nif(${JSON.stringify(provider)}==='corepack'&&process.argv[2]!=='pnpm@10.30.3')throw Error('Unpinned manager');if(process.argv.includes('view')){console.log(JSON.stringify('0.160.0'));process.exit(0)}if(process.argv.includes('--version')){console.log('10.30.3');process.exit(0)}const fs=require('fs');fs.mkdirSync('node_modules/tsx/dist',{recursive:true});fs.writeFileSync('node_modules/tsx/dist/loader.mjs','');`,{mode:0o755});
|
|
499
537
|
await fs.writeFile(path.join(fake,'docker'),`#!${process.execPath}\nconst fs=require('fs');const a=process.argv.slice(2);fs.appendFileSync(${JSON.stringify(log)},JSON.stringify(a)+'\\n');if(a.includes('config'))console.log(JSON.stringify({services:{relay:{build:{args:{}}}}}));if(a.includes('ps'))console.log('cid');if(a[0]==='inspect')console.log('sha256:'+'a'.repeat(64));`,{mode:0o755});
|
|
500
538
|
const wrapper=path.join(f.root,'supervisor.mjs'),module=new URL('../src/updates/supervisor.mjs',import.meta.url).href;
|
|
501
539
|
await fs.writeFile(wrapper,`import {supervise} from ${JSON.stringify(module)};const a=new AbortController();process.on('SIGTERM',()=>a.abort());await supervise(${JSON.stringify(f.config.deploymentDir)},a.signal,{discover:async()=>{${provider==='pnpm' ? "throw Error('Synthetic discovery failure')" : 'return []'}}});`);
|
|
@@ -510,6 +548,7 @@ for(const provider of ['pnpm','corepack']) test(`supervisor with only ${provider
|
|
|
510
548
|
const requester=await exec(process.execPath,['--input-type=module','-e',`import {submit} from ${JSON.stringify(new URL('../src/updates/control.mjs',import.meta.url).href)};console.log(JSON.stringify(await submit(${JSON.stringify(f.home)},${JSON.stringify(job.id)},false)));`]);
|
|
511
549
|
assert.equal(JSON.parse(requester.stdout).status,'queued');
|
|
512
550
|
await new Promise(r=>setTimeout(r,1800));assert.equal((await read(path.join(jobPath(f.home,job.id),'job.json'))).status,'queued');
|
|
551
|
+
await assert.rejects(fs.readFile(path.join(f.agent.controlDir,'upgrade-pause.json')),e=>e.code==='ENOENT');
|
|
513
552
|
await fs.rm(running);
|
|
514
553
|
await wait(async()=>{const j=await read(path.join(jobPath(f.home,job.id),'job.json'));if(j.status==='failed'||j.status==='rolled-back')throw Error(JSON.stringify(j)+first.output());return j.status==='completed';});
|
|
515
554
|
const newBeat=await heartbeat();assert.notEqual(newBeat.pid,oldBeat.pid);assert.equal(await firstClosed,0,first.output());
|
|
@@ -529,6 +568,26 @@ for(const provider of ['pnpm','corepack']) test(`supervisor with only ${provider
|
|
|
529
568
|
const closed3=new Promise(r=>third.p.once('close',r));third.p.kill('SIGTERM');assert.equal(await closed3,0,third.output());
|
|
530
569
|
});
|
|
531
570
|
|
|
571
|
+
test('queued upgrades keep admission open while busy and release a raced or aborted pause',async t=>{
|
|
572
|
+
const root=await fs.mkdtemp(path.join(tmpdir(),'ez-upgrade-admission-'));
|
|
573
|
+
t.after(()=>fs.rm(root,{recursive:true,force:true}));
|
|
574
|
+
const pause=path.join(root,'upgrade-pause.json'),job={id:'test'},abort=new AbortController();
|
|
575
|
+
const absent=()=>assert.rejects(fs.readFile(pause),e=>e.code==='ENOENT');
|
|
576
|
+
let applied=0;
|
|
577
|
+
const apply=async()=>{assert.equal((await read(pause)).id,job.id);applied++;return 'applied';};
|
|
578
|
+
await withIdleUpgrade(root,job,abort.signal,apply,{isIdle:async()=>false});
|
|
579
|
+
await absent();assert.equal(applied,0);
|
|
580
|
+
let checks=0;
|
|
581
|
+
await withIdleUpgrade(root,job,abort.signal,apply,{isIdle:async()=>++checks===1,wait:async()=>{}});
|
|
582
|
+
await absent();assert.equal(applied,0);
|
|
583
|
+
assert.equal(await withIdleUpgrade(root,job,abort.signal,apply,{isIdle:async()=>true,wait:async()=>{}}),'applied');
|
|
584
|
+
await absent();assert.equal(applied,1);
|
|
585
|
+
await assert.rejects(withIdleUpgrade(root,job,abort.signal,async()=>{throw Error('apply failed');},{isIdle:async()=>true,wait:async()=>{}}),/apply failed/);
|
|
586
|
+
await absent();
|
|
587
|
+
await withIdleUpgrade(root,job,abort.signal,apply,{isIdle:async()=>true,wait:async()=>abort.abort()});
|
|
588
|
+
await absent();assert.equal(applied,1);
|
|
589
|
+
});
|
|
590
|
+
|
|
532
591
|
test('npm candidates verify exact version and integrity; automatic policy is enforced again at execution',async t=>{
|
|
533
592
|
const f=await fixture(t),data=await fs.readFile(await f.pack()),original=globalThis.fetch;
|
|
534
593
|
const {createHash}=await import('node:crypto');
|
|
@@ -627,7 +686,7 @@ for(const mode of ['exact','wrong-image','unhealthy']) test(`recovery reuses onl
|
|
|
627
686
|
const f=await fixture(t),job=await queued(f),r=runtime(f);await perform(f.home,job,r);
|
|
628
687
|
const interrupted=await read(path.join(jobPath(f.home,job.id),'job.json'));interrupted.status='applying';
|
|
629
688
|
const calls=[],execute=async(command,args,options)=>{
|
|
630
|
-
calls.push([command,...args]);
|
|
689
|
+
calls.push([command,...args]);if(args.includes('view'))return JSON.stringify(latest);
|
|
631
690
|
if(args.includes('ps'))return 'b'.repeat(64);
|
|
632
691
|
if(args[0]==='inspect'&&args.includes('{{json .}}'))return JSON.stringify({Image:'sha256:'+(mode==='wrong-image'?'c':'a').repeat(64),State:{Running:true,Health:{Status:mode==='unhealthy'?'unhealthy':'healthy'}}});
|
|
633
692
|
if(mode==='exact'&&args.includes('up'))throw Error('Pinned image index no longer exists');
|