@adhdev/daemon-core 0.9.82-rc.211 → 0.9.82-rc.212

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.
@@ -146,6 +146,10 @@ export declare class SpecDriver {
146
146
  private completionIdleKey;
147
147
  /** Previous screen lines — passed to evaluate() for `changed` condition detection. */
148
148
  private prevScreenLines;
149
+ /** Timestamp of the last PTY frame that changed the screen content.
150
+ * Used by screen_active_hold_ms to suppress idle downshifts while
151
+ * the terminal is still actively updating. */
152
+ private lastScreenChangedAt;
149
153
  /** Timer that re-runs evaluate() once the hold window expires. Needed
150
154
  * because the PTY stops emitting once the agent finishes; without an
151
155
  * explicit wake-up there's nothing to trigger the busy → idle
@@ -755,6 +755,10 @@ export declare const SCHEMA_V3: {
755
755
  readonly type: "integer";
756
756
  readonly minimum: 0;
757
757
  };
758
+ readonly screen_active_hold_ms: {
759
+ readonly type: "integer";
760
+ readonly minimum: 0;
761
+ };
758
762
  readonly completion_marker: {
759
763
  readonly type: "object";
760
764
  readonly additionalProperties: false;
@@ -1451,6 +1455,10 @@ export declare const SCHEMA: {
1451
1455
  readonly type: "integer";
1452
1456
  readonly minimum: 0;
1453
1457
  };
1458
+ readonly screen_active_hold_ms: {
1459
+ readonly type: "integer";
1460
+ readonly minimum: 0;
1461
+ };
1454
1462
  readonly completion_marker: {
1455
1463
  readonly type: "object";
1456
1464
  readonly additionalProperties: false;
@@ -225,6 +225,11 @@ export interface CliSpec {
225
225
  busy_hold_ms?: number;
226
226
  idle_hold_ms?: number;
227
227
  startup_grace_ms?: number;
228
+ /** Suppress idle downshift while the screen is actively changing.
229
+ * When set, any PTY frame that changes the screen resets a timer;
230
+ * idle transitions (busy_hold expiry and completion_idle_after) are
231
+ * blocked until the screen has been stable for this many ms. */
232
+ screen_active_hold_ms?: number;
228
233
  completion_marker?: {
229
234
  section?: string;
230
235
  matches: string;
@@ -243,6 +248,7 @@ export interface CliSpec {
243
248
  busy_hold_ms?: number;
244
249
  idle_hold_ms?: number;
245
250
  startup_grace_ms?: number;
251
+ screen_active_hold_ms?: number;
246
252
  /**
247
253
  * Legacy field name for completion_marker. Populated by the loader
248
254
  * from `timing.completion_marker` for backward compat.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.211",
3
+ "version": "0.9.82-rc.212",
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",
@@ -38,12 +38,14 @@ function formatCompletionMetadata(event: Record<string, unknown>): string {
38
38
  const finalAssistantPresent = typeof completionDiagnostic?.finalAssistantPresent === 'boolean'
39
39
  ? String(completionDiagnostic.finalAssistantPresent)
40
40
  : '';
41
+ const evidenceLevel = readNonEmptyString(event.evidenceLevel);
41
42
  const parts = [
42
43
  readNonEmptyString(event.targetSessionId) ? `session_id=${readNonEmptyString(event.targetSessionId)}` : '',
43
44
  readNonEmptyString(event.providerType) ? `provider=${readNonEmptyString(event.providerType)}` : '',
44
45
  readNonEmptyString(event.providerSessionId) ? `provider_session_id=${readNonEmptyString(event.providerSessionId)}` : '',
45
46
  diagnosticReason ? `completion_diagnostic=${diagnosticReason}` : '',
46
47
  finalAssistantPresent ? `final_assistant=${finalAssistantPresent}` : '',
48
+ evidenceLevel && evidenceLevel !== 'sufficient' ? `evidence_level=${evidenceLevel}` : '',
47
49
  ].filter(Boolean);
48
50
  return parts.length > 0 ? ` (${parts.join('; ')})` : '';
49
51
  }
@@ -59,7 +61,10 @@ export function buildMeshSystemMessage(args: {
59
61
  if (args.metadataEvent.source === 'long_generating_reconciliation') {
60
62
  return `[System] ${args.nodeLabel} already has completion evidence${metadata}. The long-generating monitor reconciled the terminal handoff and marked the session complete; wait for the queued completion event/status refresh before doing any manual transcript check.`;
61
63
  }
62
- return `[System] ${args.nodeLabel} has completed its task and is now idle${metadata}. This completion came from the agent status event path; use mesh_read_chat once to review its final progress, but do not poll repeatedly.`;
64
+ const reviewNote = args.metadataEvent.reviewRecommended === true
65
+ ? ' Completion evidence is insufficient — verify via git status or provider_session_id before assuming the task is done. Use mesh_read_chat once if needed, but do not poll repeatedly.'
66
+ : ' Use mesh_read_chat once to review its final progress, but do not poll repeatedly.';
67
+ return `[System] ${args.nodeLabel} has completed its task and is now idle${metadata}. This completion came from the agent status event path;${reviewNote}`;
63
68
  }
64
69
  if (args.event === 'agent:waiting_approval') {
65
70
  return `[System] ${args.nodeLabel} is waiting for approval to proceed${metadata}. You may use mesh_read_chat and mesh_approve to handle it.`;
@@ -1544,9 +1544,10 @@ export class CliProviderInstance implements ProviderInstance {
1544
1544
  message: typeof modal?.message === 'string' ? modal.message.trim() : '',
1545
1545
  buttons: Array.isArray(modal?.buttons) ? modal.buttons.map((button: unknown) => String(button).trim()) : [],
1546
1546
  });
1547
- // PTY redraws can briefly leave waiting_approval and then re-enter it.
1548
- // Keep one coordinator event per logical modal until the turn completes.
1549
- if (this.lastStatus !== 'waiting_approval' && approvalFingerprint !== this.lastApprovalEventFingerprint) {
1547
+ // PTY redraws repeat the same modal content; fingerprint dedup prevents duplicate events.
1548
+ // Do NOT also gate on lastStatus: consecutive approvals can arrive waiting_approval→waiting_approval
1549
+ // (e.g. antigravity-cli resolves one prompt and immediately shows the next) and would be silently dropped.
1550
+ if (approvalFingerprint !== this.lastApprovalEventFingerprint) {
1550
1551
  this.lastApprovalEventFingerprint = approvalFingerprint;
1551
1552
  this.appendRuntimeSystemMessage(
1552
1553
  this.formatApprovalRequestMessage(modal?.message, modal?.buttons),
@@ -230,6 +230,10 @@ export class SpecDriver {
230
230
  private completionIdleKey = '';
231
231
  /** Previous screen lines — passed to evaluate() for `changed` condition detection. */
232
232
  private prevScreenLines: string[] = [];
233
+ /** Timestamp of the last PTY frame that changed the screen content.
234
+ * Used by screen_active_hold_ms to suppress idle downshifts while
235
+ * the terminal is still actively updating. */
236
+ private lastScreenChangedAt = 0;
233
237
  /** Timer that re-runs evaluate() once the hold window expires. Needed
234
238
  * because the PTY stops emitting once the agent finishes; without an
235
239
  * explicit wake-up there's nothing to trigger the busy → idle
@@ -470,9 +474,14 @@ export class SpecDriver {
470
474
  private reevaluate(forceEmit = false): void {
471
475
  const screen = this.adapter.snapshot();
472
476
  const cursor = this.adapter.getCursorPosition();
477
+ const currentLines = screen.split('\n').map(l => l.endsWith('\r') ? l.slice(0, -1) : l);
473
478
  const ev = evaluate(this.spec, screen, cursor, this.prevScreenLines.length > 0 ? this.prevScreenLines : undefined);
479
+ // Track when the screen last changed for screen_active_hold_ms.
480
+ if (this.prevScreenLines.length > 0 && currentLines.join('\n') !== this.prevScreenLines.join('\n')) {
481
+ this.lastScreenChangedAt = Date.now();
482
+ }
474
483
  // Update prevScreenLines for next evaluation's `changed` condition detection.
475
- this.prevScreenLines = screen.split('\n').map(l => l.endsWith('\r') ? l.slice(0, -1) : l);
484
+ this.prevScreenLines = currentLines;
476
485
 
477
486
  // Busy hold: many TUIs flicker between busy and idle every frame
478
487
  // (claude in particular — its token counter appears and disappears
@@ -513,6 +522,7 @@ export class SpecDriver {
513
522
  }
514
523
  }
515
524
  const completionIdleRule = this.spec.debounce?.completion_idle_after;
525
+ const screenActiveHoldMs = this.spec.debounce?.screen_active_hold_ms;
516
526
  let busyWakeMs = busyHoldMs;
517
527
  // Don't fire completion_idle_after while in a modal state (approval,
518
528
  // picker, etc.) or within a grace period after leaving one. Two cases:
@@ -525,7 +535,18 @@ export class SpecDriver {
525
535
  const now = Date.now();
526
536
  const recentlyInModal = isModalState(this.currentStateId) || (this.lastModalAt > 0 && now - this.lastModalAt < postModalGraceMs);
527
537
  const recentlyLeftModal = !recentlyInModal && this.lastModalExitAt > 0 && now - this.lastModalExitAt < postModalGraceMs;
528
- if (evState.id === 'busy' && completionIdleRule && !recentlyInModal && !recentlyLeftModal) {
538
+ // screen_active_hold_ms: suppress idle downshift while the screen is
539
+ // actively changing. Any PTY frame that mutates screen content resets
540
+ // lastScreenChangedAt; we hold off until it has been stable for the
541
+ // configured duration. Applies to both completion_idle_after and the
542
+ // busy_hold expiry path below.
543
+ const screenActiveMs = screenActiveHoldMs ?? 0;
544
+ const screenStableMs = this.lastScreenChangedAt > 0 ? now - this.lastScreenChangedAt : Infinity;
545
+ const screenIsActive = screenActiveMs > 0 && screenStableMs < screenActiveMs;
546
+ if (screenIsActive) {
547
+ busyWakeMs = Math.min(busyWakeMs, screenActiveMs - screenStableMs + 50);
548
+ }
549
+ if (evState.id === 'busy' && completionIdleRule && !recentlyInModal && !recentlyLeftModal && !screenIsActive) {
529
550
  const completionKey = matchesCompletionIdleRule(this.spec, ev, screen);
530
551
  if (completionKey) {
531
552
  const now = Date.now();
@@ -560,8 +581,18 @@ export class SpecDriver {
560
581
  this.completionIdleFirstSeenAt = 0;
561
582
  }
562
583
  } else if (evState.id !== 'busy') {
563
- this.completionIdleKey = '';
564
- this.completionIdleFirstSeenAt = 0;
584
+ if (!screenIsActive) {
585
+ this.completionIdleKey = '';
586
+ this.completionIdleFirstSeenAt = 0;
587
+ }
588
+ }
589
+
590
+ // screen_active_hold_ms: if the screen is still changing and the
591
+ // evaluator wants to downshift from busy to idle, pin to busy instead.
592
+ // This catches paths not covered by the completion_idle_after gate above
593
+ // (e.g. direct idle via busy_hold expiry or default-state fallback).
594
+ if (screenIsActive && this.currentStateId === 'busy' && evState.id === (this.spec.default_state ?? 'idle')) {
595
+ evState = this.lastBusyState ?? evState;
565
596
  }
566
597
 
567
598
  if (evState.id === 'busy') {
@@ -79,6 +79,7 @@ function attachDebounceAlias(spec: CliSpec): void {
79
79
  ...(t.busy_hold_ms !== undefined ? { busy_hold_ms: t.busy_hold_ms } : {}),
80
80
  ...(t.idle_hold_ms !== undefined ? { idle_hold_ms: t.idle_hold_ms } : {}),
81
81
  ...(t.startup_grace_ms !== undefined ? { startup_grace_ms: t.startup_grace_ms } : {}),
82
+ ...(t.screen_active_hold_ms !== undefined ? { screen_active_hold_ms: t.screen_active_hold_ms } : {}),
82
83
  ...(cm ? {
83
84
  completion_idle_after: {
84
85
  ...(cm.section ? { section: cm.section } : {}),
@@ -384,6 +384,7 @@ export const SCHEMA_V3 = {
384
384
  "busy_hold_ms": { "type": "integer", "minimum": 0 },
385
385
  "idle_hold_ms": { "type": "integer", "minimum": 0 },
386
386
  "startup_grace_ms": { "type": "integer", "minimum": 0 },
387
+ "screen_active_hold_ms": { "type": "integer", "minimum": 0 },
387
388
  "completion_marker": {
388
389
  "type": "object",
389
390
  "additionalProperties": false,
@@ -46,6 +46,7 @@
46
46
  "busy_hold_ms": { "type": "integer", "minimum": 0 },
47
47
  "idle_hold_ms": { "type": "integer", "minimum": 0 },
48
48
  "startup_grace_ms": { "type": "integer", "minimum": 0 },
49
+ "screen_active_hold_ms": { "type": "integer", "minimum": 0 },
49
50
  "completion_marker": {
50
51
  "type": "object",
51
52
  "additionalProperties": false,
@@ -273,6 +273,11 @@ export interface CliSpec {
273
273
  busy_hold_ms?: number;
274
274
  idle_hold_ms?: number;
275
275
  startup_grace_ms?: number;
276
+ /** Suppress idle downshift while the screen is actively changing.
277
+ * When set, any PTY frame that changes the screen resets a timer;
278
+ * idle transitions (busy_hold expiry and completion_idle_after) are
279
+ * blocked until the screen has been stable for this many ms. */
280
+ screen_active_hold_ms?: number;
276
281
  completion_marker?: {
277
282
  section?: string;
278
283
  matches: string;
@@ -291,6 +296,7 @@ export interface CliSpec {
291
296
  busy_hold_ms?: number;
292
297
  idle_hold_ms?: number;
293
298
  startup_grace_ms?: number;
299
+ screen_active_hold_ms?: number;
294
300
  /**
295
301
  * Legacy field name for completion_marker. Populated by the loader
296
302
  * from `timing.completion_marker` for backward compat.