@adhdev/daemon-core 0.9.82-rc.405 → 0.9.82-rc.406

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.405",
3
+ "version": "0.9.82-rc.406",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -46,7 +46,7 @@
46
46
  "author": "vilmire",
47
47
  "license": "AGPL-3.0-or-later",
48
48
  "dependencies": {
49
- "@adhdev/mesh-shared": "0.9.82-rc.405",
49
+ "@adhdev/mesh-shared": "0.9.82-rc.406",
50
50
  "@adhdev/session-host-core": "*",
51
51
  "@agentclientprotocol/sdk": "^0.16.1",
52
52
  "ajv": "^8.20.0",
@@ -111,6 +111,13 @@ export class CliStateEngine {
111
111
  // queued in pendingOutbound and only flushed asynchronously after idle), so the
112
112
  // completion event carries the correct id instead of the racy session scalar.
113
113
  currentTurnTaskId: string | null = null;
114
+ // GENERATING-BOUNDARY (R4d): wall-clock when the most recently STARTED turn began
115
+ // (set by onTurnStarted, persists past completion until the next turn starts).
116
+ // The startup-grace idle-stayed synthesis anchors its window on when the FIRST turn
117
+ // STARTED — not on when it finished — so a turn dispatched a few seconds after the
118
+ // grace collapse and then running for a non-trivial duration is still attributed to
119
+ // the startup collapse even though its COMPLETION lands past a now-anchored window.
120
+ currentTurnStartedAt = 0;
114
121
  activeModal: { message: string; buttons: string[] } | null = null;
115
122
 
116
123
  // ── Approval ─────────────────────────────────────
@@ -247,6 +254,9 @@ export class CliStateEngine {
247
254
  this.currentTurnTaskId = typeof turnScope.taskId === 'string' && turnScope.taskId.trim()
248
255
  ? turnScope.taskId
249
256
  : null;
257
+ // R4d: stamp the turn-start moment so the startup-grace idle-stayed synthesis can
258
+ // anchor its window on dispatch time rather than completion time.
259
+ this.currentTurnStartedAt = Date.now();
250
260
  this.responseEpoch += 1;
251
261
  }
252
262
 
@@ -1988,6 +1988,11 @@ export class ProviderCliAdapter implements CliAdapter {
1988
1988
  // reads this when stamping completion events so they carry the completing turn's
1989
1989
  // task rather than the racy last-write-wins session scalar.
1990
1990
  get currentTurnTaskId(): string | null { return this.engine.currentTurnTaskId; }
1991
+ // R4d: wall-clock when the most recently started turn began (persists past settle).
1992
+ // The provider instance's startup-grace idle-stayed synthesis anchors its window on
1993
+ // this so a delayed-dispatch first turn whose duration overruns the now-anchored
1994
+ // window is still attributed to the startup collapse.
1995
+ get currentTurnStartedAt(): number { return this.engine.currentTurnStartedAt; }
1991
1996
 
1992
1997
  get responseEpoch(): number { return this.engine.responseEpoch; }
1993
1998
  set responseEpoch(v: number) { this.engine.responseEpoch = v; }
@@ -148,7 +148,9 @@ const USER_INPUT_ACK_DEDUP_WINDOW_MS = 60_000;
148
148
  // before collapsing to idle; a turn can be dispatched a few seconds AFTER that collapse
149
149
  // (the live R4b miss: collapse at boot+8s, dispatch at boot+12.4s — already past a 12s
150
150
  // boot-anchored window before the turn even started). Anchoring on the collapse moment
151
- // makes the window cover dispatch-delay + turn-duration. The strong discriminator is
151
+ // covers dispatch-delay; R4d additionally anchors on the turn-START moment
152
+ // (engine.currentTurnStartedAt) so a non-trivial turn-DURATION cannot push the completion
153
+ // past a now-anchored window (the live rc.405 Probe2 miss). The strong discriminator is
152
154
  // generatingStartedAt===0 (generating was never observed) AND a started-but-finished turn
153
155
  // — the window only keeps the synthesized reason honest and scopes the synthesis to the
154
156
  // boot collapse, so a much-later unobservably-fast turn is not mislabelled a startup collapse.
@@ -2468,11 +2470,35 @@ export class CliProviderInstance implements ProviderInstance {
2468
2470
  // idle. Normal turns that DO reach 'busy' set generatingStartedAt and are
2469
2471
  // excluded; a queued-pending first turn that only runs after grace falls
2470
2472
  // outside the window and completes normally via idle→busy→idle.
2473
+ //
2474
+ // R4d (the live rc.405 Probe2 miss): R4c anchored the window on the collapse
2475
+ // moment but still measured its END against `now` (the poll/completion time). The
2476
+ // helper only fires once the turn has FINISHED (!hasAdapterPendingResponse()), so
2477
+ // the first eligible poll happens at completion. When the first turn is dispatched
2478
+ // a few seconds after the collapse AND runs for a non-trivial duration, that
2479
+ // completion lands PAST the 12s now-anchored window even though the turn was a
2480
+ // genuine startup-grace first turn (live: collapse→dispatch +5.2s, turn ~11s →
2481
+ // completion at collapse+16.2s > 12s). Anchor the window on when the first turn
2482
+ // STARTED (engine.currentTurnStartedAt, set by onTurnStarted) instead: a turn that
2483
+ // STARTED within the collapse window is a startup-grace first turn no matter how
2484
+ // long it then ran. The now-anchored check is retained as a union so a fast turn
2485
+ // (dispatched+completed quickly within 12s of collapse) keeps firing too; both
2486
+ // close for a much-later turn, preserving the "don't mislabel a late fast turn"
2487
+ // honesty the window exists for.
2488
+ const firstTurnStartedAt = typeof (this.adapter as any)?.currentTurnStartedAt === 'number'
2489
+ ? (this.adapter as any).currentTurnStartedAt as number
2490
+ : 0;
2491
+ const collapsedAt = this.startupGraceCollapseAt;
2492
+ const turnStartedWithinCollapseWindow = collapsedAt !== null
2493
+ && firstTurnStartedAt > 0
2494
+ && firstTurnStartedAt >= collapsedAt
2495
+ && (firstTurnStartedAt - collapsedAt) < STARTUP_GRACE_IDLE_COLLAPSE_WINDOW_MS;
2496
+ const nowWithinCollapseWindow = collapsedAt !== null
2497
+ && (now - collapsedAt) < STARTUP_GRACE_IDLE_COLLAPSE_WINDOW_MS;
2471
2498
  if (
2472
2499
  newStatus === 'idle'
2473
2500
  && previousStatus === 'idle'
2474
- && this.startupGraceCollapseAt !== null
2475
- && (now - this.startupGraceCollapseAt) < STARTUP_GRACE_IDLE_COLLAPSE_WINDOW_MS
2501
+ && (turnStartedWithinCollapseWindow || nowWithinCollapseWindow)
2476
2502
  ) {
2477
2503
  this.maybeSynthesizeStartupGraceCollapse(chatTitle, now, 'startup_grace_idle_turn_collapse');
2478
2504
  }