@adhdev/daemon-core 0.6.47 → 0.6.48

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.6.47",
3
+ "version": "0.6.48",
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",
@@ -194,12 +194,11 @@ function coercePatternArray(raw: unknown, fallbacks: RegExp[]): RegExp[] {
194
194
  /** Defaults tuned for Claude Code / similar agent CLIs when provider.json patterns are empty. */
195
195
  const FALLBACK_PROMPT: RegExp[] = [
196
196
  /Type your message/i,
197
- /^>\s*$/m, // '>' alone on its own line
198
- /[›❯]\s*[\r\n]/, // prompt char followed by line ending (ANSI-stripped may not have $ at end)
199
- /[›❯]\s*$/m, // prompt char at end of line (multiline)
200
- /for\s*shortcuts/i, // Claude Code prompt (ANSI strip may remove spaces → 'forshortcuts')
201
- /\?\s*for\s*help/i,
197
+ /for\s*shortcuts/i, // Claude Code prompt
198
+ /\?\s*for\s*help/i, // Claude Code help prompt
202
199
  /Press enter/i,
200
+ /^[>›❯]\s*$/i, // Prompt char as the complete evaluated string
201
+ /[>›❯]\s*$/, // Prompt char at the very end of evaluated string
203
202
  ];
204
203
 
205
204
  const FALLBACK_GENERATING: RegExp[] = [
@@ -214,9 +213,7 @@ const FALLBACK_APPROVAL: RegExp[] = [
214
213
  /Always\s*allow/i,
215
214
  /\(y\/n\)/i,
216
215
  /\[Y\/n\]/i,
217
- /Run\s+\w+\s+command/i, // "Run bash command" etc — requires surrounding words to avoid false matches
218
216
  /Yes,?\s*don'?t\s*ask/i, // "Yes, don't ask again" (Claude Code)
219
- /\bDeny\b/i, // Word-boundary match — avoids "allow & deny" in /permissions menu text
220
217
  ];
221
218
 
222
219
  function defaultCleanOutput(raw: string, _lastUserInput?: string): string {
@@ -1088,6 +1088,9 @@ export class AcpProviderInstance implements ProviderInstance {
1088
1088
  const newStatus = this.currentStatus;
1089
1089
  const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
1090
1090
  const chatTitle = `${this.provider.name} · ${dirName}`;
1091
+ const progressFingerprint = newStatus === 'generating'
1092
+ ? `${this.partialContent}::${JSON.stringify(this.partialBlocks)}::${JSON.stringify(this.activeToolCalls.map(t => ({ name: t.name, status: t.status })))}`.slice(-2000)
1093
+ : undefined;
1091
1094
 
1092
1095
  if (newStatus !== this.lastStatus) {
1093
1096
  if (this.lastStatus === 'idle' && newStatus === 'generating') {
@@ -1111,7 +1114,7 @@ export class AcpProviderInstance implements ProviderInstance {
1111
1114
 
1112
1115
  // Monitor check
1113
1116
  const agentKey = `${this.type}:acp`;
1114
- const monitorEvents = this.monitor.check(agentKey, newStatus, now);
1117
+ const monitorEvents = this.monitor.check(agentKey, newStatus, now, progressFingerprint);
1115
1118
  for (const me of monitorEvents) {
1116
1119
  this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
1117
1120
  }
@@ -159,6 +159,10 @@ export class CliProviderInstance implements ProviderInstance {
159
159
  const newStatus = adapterStatus.status;
160
160
  const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
161
161
  const chatTitle = `${this.provider.name} · ${dirName}`;
162
+ const partial = this.adapter.getPartialResponse();
163
+ const progressFingerprint = newStatus === 'generating'
164
+ ? `${partial || ''}::${adapterStatus.messages.at(-1)?.content || ''}`.slice(-2000)
165
+ : undefined;
162
166
 
163
167
  if (newStatus !== this.lastStatus) {
164
168
  LOG.info('CLI', `[${this.type}] status: ${this.lastStatus} → ${newStatus}`);
@@ -241,7 +245,7 @@ export class CliProviderInstance implements ProviderInstance {
241
245
 
242
246
  // Monitor check (cooldown based notification, IDE/CLI common)
243
247
  const agentKey = `${this.type}:cli`;
244
- const monitorEvents = this.monitor.check(agentKey, newStatus, now);
248
+ const monitorEvents = this.monitor.check(agentKey, newStatus, now, progressFingerprint);
245
249
  for (const me of monitorEvents) {
246
250
  this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
247
251
  }
@@ -123,11 +123,17 @@ export class ExtensionProviderInstance implements ProviderInstance {
123
123
  // via its own detectAgentTransitions(). Emitting here would cause
124
124
  // duplicate toasts with slightly different content.
125
125
 
126
- private detectTransition(newStatus: string, _data: any): void {
126
+ private detectTransition(newStatus: string, data: any): void {
127
127
  const now = Date.now();
128
128
  const agentStatus = (newStatus === 'streaming' || newStatus === 'generating') ? 'generating'
129
129
  : newStatus === 'waiting_approval' ? 'waiting_approval'
130
130
  : 'idle';
131
+ const lastMsg = Array.isArray(data?.messages) && data.messages.length > 0
132
+ ? data.messages[data.messages.length - 1]
133
+ : null;
134
+ const progressFingerprint = agentStatus === 'generating'
135
+ ? `${lastMsg?.role || ''}:${typeof lastMsg?.content === 'string' ? lastMsg.content : JSON.stringify(lastMsg?.content || '')}`.slice(-2000)
136
+ : undefined;
131
137
 
132
138
  if (agentStatus !== this.lastAgentStatus) {
133
139
  // Track generating start time (for monitor elapsed calculation)
@@ -142,7 +148,7 @@ export class ExtensionProviderInstance implements ProviderInstance {
142
148
 
143
149
  // Monitor check (cooldown based notification) — keep monitor events (long_generating etc)
144
150
  const agentKey = `${this.type}:ext`;
145
- const monitorEvents = this.monitor.check(agentKey, agentStatus, now);
151
+ const monitorEvents = this.monitor.check(agentKey, agentStatus, now, progressFingerprint);
146
152
  for (const me of monitorEvents) {
147
153
  this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
148
154
  }
@@ -334,6 +334,12 @@ export class IdeProviderInstance implements ProviderInstance {
334
334
  const agentStatus = (chatStatus === 'streaming' || chatStatus === 'generating') ? 'generating'
335
335
  : chatStatus === 'waiting_approval' ? 'waiting_approval'
336
336
  : 'idle';
337
+ const lastMsg = Array.isArray(chatData?.messages) && chatData.messages.length > 0
338
+ ? chatData.messages[chatData.messages.length - 1]
339
+ : null;
340
+ const progressFingerprint = agentStatus === 'generating'
341
+ ? `${lastMsg?.role || ''}:${typeof lastMsg?.content === 'string' ? lastMsg.content : JSON.stringify(lastMsg?.content || '')}`.slice(-2000)
342
+ : undefined;
337
343
 
338
344
  this.currentStatus = agentStatus;
339
345
  const lastStatus = this.lastAgentStatuses.get(agentKey) || 'idle';
@@ -368,7 +374,7 @@ export class IdeProviderInstance implements ProviderInstance {
368
374
  }
369
375
 
370
376
  // Monitor check (cooldown based notification)
371
- const monitorEvents = this.monitor.check(agentKey, agentStatus, now);
377
+ const monitorEvents = this.monitor.check(agentKey, agentStatus, now, progressFingerprint);
372
378
  for (const me of monitorEvents) {
373
379
  this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
374
380
  }
@@ -37,6 +37,9 @@ 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>();
41
+ private lastProgressFingerprint = new Map<string, string>();
42
+ private lastProgressChangeAt = new Map<string, number>();
40
43
 
41
44
  constructor(config?: Partial<MonitorConfig>) {
42
45
  this.config = { ...DEFAULT_MONITOR_CONFIG, ...config };
@@ -56,7 +59,7 @@ export class StatusMonitor {
56
59
  * Check status transition → return notification event array.
57
60
  * Called from each onTick() or detectStatusTransition().
58
61
  */
59
- check(agentKey: string, status: string, now: number): MonitorEvent[] {
62
+ check(agentKey: string, status: string, now: number, progressFingerprint?: string): MonitorEvent[] {
60
63
  const events: MonitorEvent[] = [];
61
64
 
62
65
  // 1. Approval waiting notification
@@ -75,18 +78,31 @@ export class StatusMonitor {
75
78
  if (status === 'generating' || status === 'streaming') {
76
79
  if (!this.generatingStartTimes.has(agentKey)) {
77
80
  this.generatingStartTimes.set(agentKey, now);
81
+ this.longGeneratingAlerted.set(agentKey, false);
82
+ const initialFingerprint = progressFingerprint ?? '';
83
+ this.lastProgressFingerprint.set(agentKey, initialFingerprint);
84
+ this.lastProgressChangeAt.set(agentKey, now);
85
+ }
86
+ const currentFingerprint = progressFingerprint ?? '';
87
+ const previousFingerprint = this.lastProgressFingerprint.get(agentKey);
88
+ if (previousFingerprint !== currentFingerprint) {
89
+ this.lastProgressFingerprint.set(agentKey, currentFingerprint);
90
+ this.lastProgressChangeAt.set(agentKey, now);
91
+ this.longGeneratingAlerted.set(agentKey, false);
78
92
  }
79
93
  if (this.config.longGeneratingAlert) {
80
- const startedAt = this.generatingStartTimes.get(agentKey)!;
81
- const elapsedSec = Math.round((now - startedAt) / 1000);
82
- if (elapsedSec > this.config.longGeneratingThresholdSec) {
94
+ const progressChangedAt = this.lastProgressChangeAt.get(agentKey) || this.generatingStartTimes.get(agentKey)!;
95
+ const elapsedSec = Math.round((now - progressChangedAt) / 1000);
96
+ const alreadyAlerted = this.longGeneratingAlerted.get(agentKey) === true;
97
+ if (elapsedSec > this.config.longGeneratingThresholdSec && !alreadyAlerted) {
83
98
  if (this.shouldAlert(agentKey + ':long_gen', now)) {
99
+ this.longGeneratingAlerted.set(agentKey, true);
84
100
  events.push({
85
101
  type: 'monitor:long_generating',
86
102
  agentKey,
87
103
  elapsedSec,
88
104
  timestamp: now,
89
- message: `${agentKey} has been generating for ${Math.round(elapsedSec / 60)}min`,
105
+ message: `${agentKey} output appears stuck for ${Math.round(elapsedSec / 60)}min`,
90
106
  });
91
107
  }
92
108
  }
@@ -94,6 +110,9 @@ export class StatusMonitor {
94
110
  } else {
95
111
  // Reset timer when switching to non-generating status
96
112
  this.generatingStartTimes.delete(agentKey);
113
+ this.longGeneratingAlerted.delete(agentKey);
114
+ this.lastProgressFingerprint.delete(agentKey);
115
+ this.lastProgressChangeAt.delete(agentKey);
97
116
  }
98
117
 
99
118
  return events;
@@ -113,12 +132,18 @@ export class StatusMonitor {
113
132
  reset(agentKey?: string): void {
114
133
  if (agentKey) {
115
134
  this.generatingStartTimes.delete(agentKey);
135
+ this.longGeneratingAlerted.delete(agentKey);
136
+ this.lastProgressFingerprint.delete(agentKey);
137
+ this.lastProgressChangeAt.delete(agentKey);
116
138
  // Delete all cooldowns for this key
117
139
  for (const k of this.lastAlertTime.keys()) {
118
140
  if (k.startsWith(agentKey)) this.lastAlertTime.delete(k);
119
141
  }
120
142
  } else {
121
143
  this.generatingStartTimes.clear();
144
+ this.longGeneratingAlerted.clear();
145
+ this.lastProgressFingerprint.clear();
146
+ this.lastProgressChangeAt.clear();
122
147
  this.lastAlertTime.clear();
123
148
  }
124
149
  }