@jc_stack/ez-agents 0.1.0-beta.25 → 0.1.0-beta.26

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0-beta.26
4
+
5
+ - Start local software-maintenance wakeups in independent ephemeral sessions,
6
+ including after a relay restart, without trying to resume or persist a
7
+ normal owner conversation. This prevents successful upgrades from producing
8
+ false "failed to start" notifications across direct relays.
9
+
3
10
  ## 0.1.0-beta.25
4
11
 
5
12
  - Enable Codex Luna `max` reasoning for durable work, schedules and deferred
package/docs/releasing.md CHANGED
@@ -17,8 +17,9 @@ separate release bot is not required.
17
17
  3. Build both Docker targets from a clean checkout:
18
18
  `docker build --target test -t ez-release-tests .` and
19
19
  `docker build --target runtime -t ez-release-runtime .`.
20
- Main: `EZ_RELAY_IMAGE=ez-release-runtime node docker/smoke.mjs`.
21
- WhatsApp: `EZ_WHATSAPP_IMAGE=ez-release-runtime node docker/smoke.mjs`.
20
+ Main relay: `EZ_RELAY_IMAGE=ez-release-runtime node docker/smoke.mjs`.
21
+ The WhatsApp Compose overlay has no separate relay image; verify its
22
+ provider integration with the synthetic plugin smoke in `docs/plugins.md`.
22
23
  4. Create an artifact with `npm pack --ignore-scripts`. Inspect its file list,
23
24
  hash it, extract into a fresh directory, copy `docker/pnpm-lock.yaml` to
24
25
  `pnpm-lock.yaml`, and run `pnpm install --frozen-lockfile`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jc_stack/ez-agents",
3
- "version": "0.1.0-beta.25",
3
+ "version": "0.1.0-beta.26",
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.",
package/src/index.ts CHANGED
@@ -193,7 +193,9 @@ export const createRelay = (config: Config, launch = startExecutorJob) => {
193
193
  const launchStarted = performance.now()
194
194
  const started = await runs.patch(run.id, { status: 'running', startedAt: new Date().toISOString() })
195
195
  if (!started.execution && !run.taskId) throw new Error('Legacy queued work has no pinned AI. Resend the request after /new.')
196
- const session = run.external || run.taskId || run.scheduled || run.replyOnly
196
+ const maintenanceWakeup = run.id.startsWith('r_update_')
197
+ const startsOwnSession = Boolean(run.external || run.taskId || run.scheduled || run.replyOnly || maintenanceWakeup)
198
+ const session = startsOwnSession
197
199
  ? { sessionId: randomUUID(), hasStarted: false, nativeSessionId: undefined }
198
200
  : await control.executionSession(started.execution!)
199
201
  const selected = run.taskId ? chatPreset('codex') : started.execution!.preset
@@ -211,7 +213,7 @@ export const createRelay = (config: Config, launch = startExecutorJob) => {
211
213
  sessionId: session.nativeSessionId || session.sessionId,
212
214
  isResume: session.hasStarted,
213
215
  eventSource: run.external?.sourceId,
214
- onSession: run.external || run.taskId || run.replyOnly ? undefined : async (id) => { await runs.patch(run.id,{nativeSessionId:id}); if (!run.scheduled) await control.saveNativeSession(session.sessionId,id) },
216
+ onSession: run.external || run.taskId || run.replyOnly || maintenanceWakeup ? undefined : async (id) => { await runs.patch(run.id,{nativeSessionId:id}); if (!run.scheduled) await control.saveNativeSession(session.sessionId,id) },
215
217
  })
216
218
  const executionStarted = performance.now()
217
219
  // Attach before disk writes: a fast child can close while PID persistence
@@ -11,8 +11,33 @@ import { ControlStore } from '../src/control-state.js'
11
11
  import { RunStore } from '../src/runs.js'
12
12
  import { Scheduler } from '../src/scheduler.js'
13
13
  import { initialPreset } from '../src/ai.js'
14
+ import { randomUUID } from 'node:crypto'
14
15
 
15
16
  const until=async(check:()=>Promise<boolean>)=>{for(let i=0;i<200;i++){if(await check())return;await new Promise(r=>setTimeout(r,20))}throw new Error('Timed out')}
17
+
18
+ test('maintenance wakeups use an independent ephemeral session after a relay restart',async()=>{
19
+ const dir=await mkdtemp(join(tmpdir(),'ez-maintenance-session-')),control=new ControlStore(dir,1000),runs=new RunStore(dir)
20
+ let launchSession='',onSessionCalls=0
21
+ const relay=createRelay({workspace:dir,controlDir:dir,pairingTtlMs:1000,executorTimeoutMs:0,executorCli:'codex',telegramBotToken:'fixture'},async(_texts,options)=>{
22
+ launchSession=options.sessionId ?? ''
23
+ if(options.onSession){onSessionCalls++;await options.onSession('native-maintenance')}
24
+ const child=spawn(process.execPath,['-e','setTimeout(()=>{},10)'],{detached:process.platform!=='win32'})
25
+ await once(child,'spawn')
26
+ return {child,cleanup:async()=>{},stdout:''}
27
+ })
28
+ relay.bot.botInfo={id:999,is_bot:true,first_name:'Fixture',username:'fixture_bot'} as typeof relay.bot.botInfo
29
+ relay.bot.api.config.use(async()=>({ok:true,result:{message_id:42}}) as never)
30
+ try{
31
+ await control.requestPairing(101,101);await control.approveOwner(101)
32
+ const execution={sessionId:randomUUID(),preset:initialPreset('codex')}
33
+ await runs.create({id:'r_update_fixture',chatId:101,telegramUserId:101,texts:['maintenance'],execution})
34
+ await relay.drainSources()
35
+ await until(async()=> (await runs.get('r_update_fixture'))?.status==='completed')
36
+ assert.notEqual(launchSession,execution.sessionId)
37
+ assert.equal(onSessionCalls,0)
38
+ }finally{await relay.stop();await rm(dir,{recursive:true,force:true})}
39
+ })
40
+
16
41
  test('chat replies through the real ingress/outbox while scheduled CLI remains alive; targeted cancellation',async()=>{
17
42
  const dir=await mkdtemp(join(tmpdir(),'ez-scheduler-relay-')), children:ReturnType<typeof spawn>[]=[]
18
43
  const runs=new RunStore(dir),scheduler=new Scheduler(dir),control=new ControlStore(dir,1000)