@agfpd/iapeer 0.4.127 → 0.4.128

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.
@@ -456,7 +456,11 @@ five-hour wall at 04:55; claude painted «resets 8:30am», the daemon idle-reape
456
456
  3. **Nudge.** After `resetsAt + 3 min`, if the session made no progress since the event (claude: a
457
457
  real, non-`<synthetic>` assistant line after it; codex: a `task_complete` with a message whose
458
458
  `completed_at` is after it), the core delivers ONE continuation message through the normal
459
- route — wake-on-miss resumes a reaped session (an idle reap is resume-eligible). Progress
459
+ route — wake-on-miss RESUMES a reaped session: the nudge carries an explicit continue-work
460
+ wake intent that satisfies the executor topic gate by itself (wake cause
461
+ `idle-reaped-continue`); it never outranks an owner `/new`, a soft-reload or a crash. Without
462
+ it the first live run (0.4.127) woke the peer FRESH (`idle-reaped-new-topic`) and the peer
463
+ answered «nothing to resume» — the very loss this section exists to prevent. Progress
460
464
  already made → the park is dropped without a message (no duplicate turn). A failed delivery is
461
465
  retried (5 min backoff, 3 attempts); an operator `stop` is never fought.
462
466
  4. **Sender** = the core's system identity `iapeer-system` (runtime `core`, intelligence
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agfpd/iapeer",
3
- "version": "0.4.127",
3
+ "version": "0.4.128",
4
4
  "description": "Run Claude Code and Codex CLI as local background agents that message each other, wake on demand, and share one memory.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "agfpd",
@@ -74,7 +74,10 @@ export async function processLimitResumes(
74
74
  }
75
75
  let reason: string
76
76
  try {
77
- const r = await send(systemCaller(), { personality: p.personality, runtime: p.runtime, message: limitResumeMessage(p), topic: 'limit-resume' }, routeDeps)
77
+ // Topic = the stopped session's own thread (captured at park time), so the topic set still
78
+ // follows the work; the RESUME itself rides the explicit continue-work intent, not the topic.
79
+ const topic = p.topic || 'limit-resume'
80
+ const r = await send(systemCaller(), { personality: p.personality, runtime: p.runtime, message: limitResumeMessage(p), topic }, { ...routeDeps, wakeContinueWork: true })
78
81
  if (r.ok) {
79
82
  clearLimitPark(cfg, p.identity)
80
83
  log({ identity: p.identity, outcome: 'nudged', woke: String(r.value.woke), resets: new Date(p.resetsAtMs).toISOString() })
@@ -140,7 +140,7 @@ const DAEMON_MAIN_PATH = fileURLToPath(import.meta.url)
140
140
  export function makeWakeFn(cfg: LifecycleConfig, env: NodeJS.ProcessEnv): WakeFn {
141
141
  return (req: WakeRequest): Promise<WakeOutcome> =>
142
142
  wakeOrSpawn(
143
- { personality: req.personality, runtime: req.runtime, topic: req.topic, task: req.task },
143
+ { personality: req.personality, runtime: req.runtime, topic: req.topic, task: req.task, ...(req.continueWork ? { continueWork: true } : {}) },
144
144
  { cfg, env },
145
145
  )
146
146
  }
@@ -493,6 +493,7 @@ export async function startConfiguredDaemon(opts: ConfiguredDaemonOptions = {}):
493
493
  ...(d.file ? { file: d.file } : {}),
494
494
  ...(d.sessionId ? { sessionId: d.sessionId } : {}),
495
495
  errorType: d.errorType,
496
+ ...(readTopic(cfg, identity) ? { topic: readTopic(cfg, identity) } : {}),
496
497
  })
497
498
  if (fresh) appendLifecycleEvent(cfg.eventLogDir, { ev: 'limit-park', identity, resets: new Date(d.resetsAtMs).toISOString(), error_type: d.errorType }, { env })
498
499
  },
@@ -653,6 +653,10 @@ export function resolveWakeMode(
653
653
  * next resume/fresh decision (a crash-looping binary used to eat .idle-reaped, and after the fix the
654
654
  * peer came back FRESH, its context lost). Absent → consume immediately (legacy/test callers). */
655
655
  defer?: (consume: () => void) => void,
656
+ /** The waking message is an explicit "continue THIS work" (the limit-resume nudge, docs/19 §9):
657
+ * in the idle-reaped executor branch it satisfies the topic gate by itself. Never outranks an
658
+ * owner's /new, a soft-reload, a folder-launch or a crash (those decide before 3b). */
659
+ continueWork?: boolean,
656
660
  ): WakeMode {
657
661
  const done = defer ?? ((f: () => void) => f())
658
662
  // 1. folder-launch → always fresh. Consume a standing .new-eager too — the mark's
@@ -725,6 +729,12 @@ export function resolveWakeMode(
725
729
  // gap is not a request to resume specific work; defaulting it to resume revived stale
726
730
  // context (e.g. a 3-day-old session). The sender opts INTO continuity by passing the topic.
727
731
  const topic = incomingTopic?.trim() ?? ''
732
+ if (continueWork) {
733
+ const r = resolveResume(cwd)
734
+ return r.ok
735
+ ? { resume: true, resumeRef: r.ref, cause: 'idle-reaped-continue' }
736
+ : { resume: false, cause: 'idle-reaped-no-resume' }
737
+ }
728
738
  if (!topic) return { resume: false, cause: 'idle-reaped-no-topic' }
729
739
  // RESUME if the incoming topic matches ANY thread this session lineage worked on — not
730
740
  // just the last. Different threads accumulate in one session; a later ping on an earlier
@@ -967,6 +977,8 @@ export interface WakeArgs {
967
977
  /** First message delivered to the woken session (the routed envelope). */
968
978
  task: string
969
979
  resume?: boolean
980
+ /** Explicit "continue THIS work" intent — see resolveWakeMode `continueWork`. */
981
+ continueWork?: boolean
970
982
  }
971
983
 
972
984
  export interface WakeResult {
@@ -1124,6 +1136,7 @@ export async function wakeOrSpawn(args: WakeArgs, deps: WakeDeps = {}): Promise<
1124
1136
  c => adapter.resolveResume(c, { personality: args.personality, env }),
1125
1137
  args.topic,
1126
1138
  f => consumeOnUp.push(f),
1139
+ args.continueWork,
1127
1140
  )
1128
1141
  const applyConsumed = (): void => {
1129
1142
  for (const f of consumeOnUp.splice(0)) {
@@ -42,6 +42,8 @@ export interface LimitPark {
42
42
  file?: string
43
43
  sessionId?: string
44
44
  errorType?: string
45
+ /** The session's most-recent topic when parked — the nudge continues that thread. */
46
+ topic?: string
45
47
  attempts?: number
46
48
  lastAttemptMs?: number
47
49
  }
@@ -1272,6 +1272,8 @@ export interface WakeRequest {
1272
1272
  topic?: string
1273
1273
  /** First message delivered to the woken session (the routed envelope). */
1274
1274
  task: string
1275
+ /** Explicit "continue THIS work" intent (core-originated limit-resume only). */
1276
+ continueWork?: boolean
1275
1277
  }
1276
1278
  export interface WakeOutcome {
1277
1279
  status: 'READY' | 'FAILED'
@@ -1347,6 +1349,9 @@ export interface ComposerQueueRouteDeps {
1347
1349
  }
1348
1350
 
1349
1351
  export interface RouteDeps {
1352
+ /** A wake triggered by THIS send carries the "continue THIS work" intent (resume an idle-reaped
1353
+ * session regardless of topic). Set only by the core's limit-resume pass — never from a request. */
1354
+ wakeContinueWork?: boolean
1350
1355
  /** Б7 — injected env sandboxes ALL of routeSend's env-resolved reads (the registry, the supervisor
1351
1356
  * run-dir liveness/delivery). Default process.env (prod: env === process.env). Closes the daemon:286
1352
1357
  * isolation invariant — a library/test daemon with an injected root no longer reads the real registry
@@ -1970,7 +1975,7 @@ async function routeSendInner(
1970
1975
  `(restart window outlasted the grace?) — message NOT delivered; retry`,
1971
1976
  )
1972
1977
  }
1973
- const woke = await deps.wake({ personality, runtime, topic, task: envelope })
1978
+ const woke = await deps.wake({ personality, runtime, topic, task: envelope, ...(deps.wakeContinueWork ? { continueWork: true } : {}) })
1974
1979
  if (woke.status === 'FAILED') {
1975
1980
  // C1 — a durably STOPPED peer is a deliberate halt, not a transient miss:
1976
1981
  // surface the explicit "stopped" reason (no "offline and wake failed" wrapping).