@adhdev/daemon-core 0.9.82-rc.328 → 0.9.82-rc.329

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.
@@ -10,7 +10,7 @@ import {
10
10
  type ReadChatResultV2,
11
11
  } from './transcript-v2.js'
12
12
 
13
- const VALID_STATUSES = ['idle', 'generating', 'waiting_approval', 'error', 'panel_hidden', 'starting', 'streaming', 'long_generating'] as const
13
+ const VALID_STATUSES = ['idle', 'generating', 'waiting_approval', 'error', 'panel_hidden', 'starting', 'streaming', 'no_progress', 'long_generating'] as const
14
14
  const VALID_ROLES = ['user', 'assistant', 'system', 'human'] as const
15
15
  const VALID_BUBBLE_STATES = ['draft', 'streaming', 'final', 'removed'] as const
16
16
  const VALID_TURN_STATUSES = ['open', 'waiting_approval', 'complete', 'error'] as const
@@ -3,16 +3,16 @@
3
3
  *
4
4
  * Common across all Provider categories (IDE/Extension/CLI/ACP).
5
5
  * - Approval waiting (waiting_approval) notification
6
- * - Notification when generating persists for extended duration
6
+ * - No-progress watchdog: alert when an active turn makes no progress for a while
7
7
  * - All config toggleable via Provider Settings
8
8
  */
9
9
  export interface MonitorConfig {
10
10
  /** Enable awaiting-approval notification */
11
11
  approvalAlert: boolean;
12
- /** Prolonged generating notification enabled */
13
- longGeneratingAlert: boolean;
14
- /** Prolonged threshold (seconds) */
15
- longGeneratingThresholdSec: number;
12
+ /** No-progress watchdog notification enabled */
13
+ noProgressAlert: boolean;
14
+ /** No-progress threshold (seconds) */
15
+ noProgressThresholdSec: number;
16
16
  /** Repeat notification cooldown (seconds) */
17
17
  alertCooldownSec: number;
18
18
  }
@@ -28,7 +28,7 @@ export declare class StatusMonitor {
28
28
  private config;
29
29
  private lastAlertTime;
30
30
  private generatingStartTimes;
31
- private longGeneratingAlerted;
31
+ private noProgressAlerted;
32
32
  private lastProgressFingerprint;
33
33
  private lastProgressChangeAt;
34
34
  constructor(config?: Partial<MonitorConfig>);
@@ -40,7 +40,7 @@ export declare class StatusMonitor {
40
40
  * Check status transition → return notification event array.
41
41
  * Called from each onTick() or detectStatusTransition().
42
42
  */
43
- check(agentKey: string, status: string, now: number, progressFingerprint?: string): MonitorEvent[];
43
+ check(agentKey: string, status: string, now: number, progressFingerprint?: string, approvalPending?: boolean): MonitorEvent[];
44
44
  /** Cooldown check — prevent sending the same notification too frequently */
45
45
  private shouldAlert;
46
46
  /** Reset (on agent terminate/restart) */
@@ -3,26 +3,26 @@
3
3
  *
4
4
  * Common across all Provider categories (IDE/Extension/CLI/ACP).
5
5
  * - Approval waiting (waiting_approval) notification
6
- * - Notification when generating persists for extended duration
6
+ * - No-progress watchdog: alert when an active turn makes no progress for a while
7
7
  * - All config toggleable via Provider Settings
8
8
  */
9
9
 
10
10
  export interface MonitorConfig {
11
11
  /** Enable awaiting-approval notification */
12
12
  approvalAlert: boolean;
13
- /** Prolonged generating notification enabled */
14
- longGeneratingAlert: boolean;
15
- /** Prolonged threshold (seconds) */
16
- longGeneratingThresholdSec: number;
13
+ /** No-progress watchdog notification enabled */
14
+ noProgressAlert: boolean;
15
+ /** No-progress threshold (seconds) */
16
+ noProgressThresholdSec: number;
17
17
  /** Repeat notification cooldown (seconds) */
18
18
  alertCooldownSec: number;
19
19
  }
20
20
 
21
21
  export const DEFAULT_MONITOR_CONFIG: MonitorConfig = {
22
22
  approvalAlert: true,
23
- longGeneratingAlert: true,
24
- longGeneratingThresholdSec: 180, // 3 minutes
25
- alertCooldownSec: 60, // 1 minute cooldown
23
+ noProgressAlert: true,
24
+ noProgressThresholdSec: 180, // 3 minutes
25
+ alertCooldownSec: 60, // 1 minute cooldown
26
26
  };
27
27
 
28
28
  export interface MonitorEvent {
@@ -37,7 +37,7 @@ export class StatusMonitor {
37
37
  private config: MonitorConfig;
38
38
  private lastAlertTime = new Map<string, number>();
39
39
  private generatingStartTimes = new Map<string, number>();
40
- private longGeneratingAlerted = new Map<string, boolean>();
40
+ private noProgressAlerted = new Map<string, boolean>();
41
41
  private lastProgressFingerprint = new Map<string, string>();
42
42
  private lastProgressChangeAt = new Map<string, number>();
43
43
 
@@ -59,7 +59,7 @@ export class StatusMonitor {
59
59
  * Check status transition → return notification event array.
60
60
  * Called from each onTick() or detectStatusTransition().
61
61
  */
62
- check(agentKey: string, status: string, now: number, progressFingerprint?: string): MonitorEvent[] {
62
+ check(agentKey: string, status: string, now: number, progressFingerprint?: string, approvalPending?: boolean): MonitorEvent[] {
63
63
  const events: MonitorEvent[] = [];
64
64
 
65
65
  // 1. Approval waiting notification
@@ -74,11 +74,26 @@ export class StatusMonitor {
74
74
  }
75
75
  }
76
76
 
77
- // 2. Detect prolonged generating (identical for IDE/Extension/CLI/ACP)
77
+ // 2. No-progress watchdog (identical for IDE/Extension/CLI/ACP)
78
78
  if (status === 'generating' || status === 'streaming') {
79
+ // While an approval modal is pending we must NOT count the wait as
80
+ // no-progress. The assistant emits no tokens and the screen is frozen
81
+ // on the modal, so the progress fingerprint goes static — but this is
82
+ // a user action wait, not a freeze. Some callers (CLI/IDE auto-approve)
83
+ // also synthesize the reported status to 'generating' during the wait,
84
+ // so the plain `waiting_approval` branch below cannot catch it. Hold the
85
+ // progress anchor at `now` so the elapsed timer restarts the moment the
86
+ // approval clears and real generating resumes.
87
+ if (approvalPending) {
88
+ this.generatingStartTimes.set(agentKey, now);
89
+ this.lastProgressFingerprint.set(agentKey, progressFingerprint ?? '');
90
+ this.lastProgressChangeAt.set(agentKey, now);
91
+ this.noProgressAlerted.set(agentKey, false);
92
+ return events;
93
+ }
79
94
  if (!this.generatingStartTimes.has(agentKey)) {
80
95
  this.generatingStartTimes.set(agentKey, now);
81
- this.longGeneratingAlerted.set(agentKey, false);
96
+ this.noProgressAlerted.set(agentKey, false);
82
97
  const initialFingerprint = progressFingerprint ?? '';
83
98
  this.lastProgressFingerprint.set(agentKey, initialFingerprint);
84
99
  this.lastProgressChangeAt.set(agentKey, now);
@@ -88,17 +103,17 @@ export class StatusMonitor {
88
103
  if (previousFingerprint !== currentFingerprint) {
89
104
  this.lastProgressFingerprint.set(agentKey, currentFingerprint);
90
105
  this.lastProgressChangeAt.set(agentKey, now);
91
- this.longGeneratingAlerted.set(agentKey, false);
106
+ this.noProgressAlerted.set(agentKey, false);
92
107
  }
93
- if (this.config.longGeneratingAlert) {
108
+ if (this.config.noProgressAlert) {
94
109
  const progressChangedAt = this.lastProgressChangeAt.get(agentKey) || this.generatingStartTimes.get(agentKey)!;
95
110
  const elapsedSec = Math.round((now - progressChangedAt) / 1000);
96
- const alreadyAlerted = this.longGeneratingAlerted.get(agentKey) === true;
97
- if (elapsedSec > this.config.longGeneratingThresholdSec && !alreadyAlerted) {
98
- if (this.shouldAlert(agentKey + ':long_gen', now)) {
99
- this.longGeneratingAlerted.set(agentKey, true);
111
+ const alreadyAlerted = this.noProgressAlerted.get(agentKey) === true;
112
+ if (elapsedSec > this.config.noProgressThresholdSec && !alreadyAlerted) {
113
+ if (this.shouldAlert(agentKey + ':no_progress', now)) {
114
+ this.noProgressAlerted.set(agentKey, true);
100
115
  events.push({
101
- type: 'monitor:long_generating',
116
+ type: 'monitor:no_progress',
102
117
  agentKey,
103
118
  elapsedSec,
104
119
  timestamp: now,
@@ -110,7 +125,7 @@ export class StatusMonitor {
110
125
  } else {
111
126
  // Reset timer when switching to non-generating status
112
127
  this.generatingStartTimes.delete(agentKey);
113
- this.longGeneratingAlerted.delete(agentKey);
128
+ this.noProgressAlerted.delete(agentKey);
114
129
  this.lastProgressFingerprint.delete(agentKey);
115
130
  this.lastProgressChangeAt.delete(agentKey);
116
131
  }
@@ -132,7 +147,7 @@ export class StatusMonitor {
132
147
  reset(agentKey?: string): void {
133
148
  if (agentKey) {
134
149
  this.generatingStartTimes.delete(agentKey);
135
- this.longGeneratingAlerted.delete(agentKey);
150
+ this.noProgressAlerted.delete(agentKey);
136
151
  this.lastProgressFingerprint.delete(agentKey);
137
152
  this.lastProgressChangeAt.delete(agentKey);
138
153
  // Delete all cooldowns for this key
@@ -141,7 +156,7 @@ export class StatusMonitor {
141
156
  }
142
157
  } else {
143
158
  this.generatingStartTimes.clear();
144
- this.longGeneratingAlerted.clear();
159
+ this.noProgressAlerted.clear();
145
160
  this.lastProgressFingerprint.clear();
146
161
  this.lastProgressChangeAt.clear();
147
162
  this.lastAlertTime.clear();
@@ -740,6 +740,9 @@ export type DaemonStatusEventName =
740
740
  | 'agent:waiting_approval'
741
741
  | 'agent:generating_completed'
742
742
  | 'agent:stopped'
743
+ | 'monitor:no_progress'
744
+ // Legacy alias for 'monitor:no_progress' — kept so older daemons that still
745
+ // emit it remain type-compatible with consumers during rollout.
743
746
  | 'monitor:long_generating';
744
747
 
745
748
  /** Minimal daemon-originated event payload relayed through the server. */
@@ -120,7 +120,7 @@ export class DaemonStatusReporter {
120
120
  case 'agent:waiting_approval':
121
121
  case 'agent:generating_completed':
122
122
  case 'agent:stopped':
123
- case 'monitor:long_generating':
123
+ case 'monitor:no_progress':
124
124
  return value;
125
125
  default:
126
126
  return null;