@adhdev/daemon-core 0.5.30 → 0.5.32

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.5.30",
3
+ "version": "0.5.32",
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",
@@ -24,15 +24,13 @@ import { LOG } from '../logging/logger.js';
24
24
  let pty: any;
25
25
  try {
26
26
  pty = require('node-pty');
27
- // node-pty@1.1.0 ships spawn-helper without +x on macOS — fix it
27
+ // node-pty ships spawn-helper without +x on macOS (npm umask issue) — fix it
28
28
  if (os.platform() !== 'win32') {
29
29
  try {
30
30
  const fs = require('fs');
31
- // require.resolve('node-pty') → .../node-pty/lib/index.js
32
- // prebuilds/ is at .../node-pty/prebuilds/, not .../node-pty/lib/prebuilds/
33
31
  const ptyDir = path.resolve(path.dirname(require.resolve('node-pty')), '..');
34
- const arch = os.arch() === 'arm64' ? 'darwin-arm64' : 'darwin-x64';
35
- const helper = path.join(ptyDir, 'prebuilds', arch, 'spawn-helper');
32
+ const platformArch = `${os.platform()}-${os.arch()}`;
33
+ const helper = path.join(ptyDir, 'prebuilds', platformArch, 'spawn-helper');
36
34
  if (fs.existsSync(helper)) {
37
35
  const stat = fs.statSync(helper);
38
36
  if (!(stat.mode & 0o111)) {
@@ -43,7 +41,7 @@ try {
43
41
  } catch { /* best-effort */ }
44
42
  }
45
43
  } catch {
46
- LOG.error('CLI', '[ProviderCliAdapter] node-pty not found. Install: npm install node-pty@1.0.0');
44
+ LOG.error('CLI', '[ProviderCliAdapter] node-pty not found. Terminal features disabled.');
47
45
  }
48
46
 
49
47
  export interface CliChatMessage {
@@ -97,9 +95,16 @@ export interface CliProviderModule {
97
95
 
98
96
  function stripAnsi(str: string): string {
99
97
  // eslint-disable-next-line no-control-regex
100
- return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '')
98
+ return str
99
+ // Cursor movement sequences → space (prevents word concatenation)
100
+ .replace(/\x1B\[\d*[A-HJKSTfG]/g, ' ')
101
+ // SGR and other CSI sequences → remove
102
+ .replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '')
103
+ // OSC sequences (title bar etc)
101
104
  .replace(/\x1B\][^\x07]*\x07/g, '')
102
- .replace(/\x1B\][^\x1B]*\x1B\\/g, '');
105
+ .replace(/\x1B\][^\x1B]*\x1B\\/g, '')
106
+ // Collapse multiple spaces
107
+ .replace(/ +/g, ' ');
103
108
  }
104
109
 
105
110
  function findBinary(name: string): string {
@@ -471,6 +476,10 @@ export class ProviderCliAdapter implements CliAdapter {
471
476
  }
472
477
 
473
478
  // ─── Phase 2: Approval detect
479
+ // DEBUG: log recent output for approval pattern debugging
480
+ if (cleanData.trim().length > 5) {
481
+ LOG.debug('CLI', `[${this.cliType}] output chunk (${cleanData.length}): ${cleanData.slice(0, 300).replace(/\n/g, '\\n')}`);
482
+ }
474
483
  const hasApproval = patterns.approval.some(p => p.test(this.recentOutputBuffer));
475
484
  if (hasApproval && this.currentStatus !== 'waiting_approval') {
476
485
  const inCooldown = this.lastApprovalResolvedAt && (Date.now() - this.lastApprovalResolvedAt) < this.timeouts.approvalCooldown;
@@ -149,7 +149,8 @@ export class CliProviderInstance implements ProviderInstance {
149
149
  this.monitor.reset();
150
150
  }
151
151
 
152
- // ─── Status transition detection (moved from daemon-status.ts) ──────
152
+ private completedDebounceTimer: NodeJS.Timeout | null = null;
153
+ private completedDebouncePending: { chatTitle: string; duration: number; timestamp: number } | null = null;
153
154
 
154
155
  private detectStatusTransition(): void {
155
156
  const now = Date.now();
@@ -161,7 +162,14 @@ export class CliProviderInstance implements ProviderInstance {
161
162
  if (newStatus !== this.lastStatus) {
162
163
  LOG.info('CLI', `[${this.type}] status: ${this.lastStatus} → ${newStatus}`);
163
164
  if (this.lastStatus === 'idle' && newStatus === 'generating') {
164
- this.generatingStartedAt = now;
165
+ // Cancel any pending completed event (multi-step: idle→generating resume)
166
+ if (this.completedDebouncePending) {
167
+ LOG.info('CLI', `[${this.type}] cancelled pending completed (resumed generating)`);
168
+ if (this.completedDebounceTimer) { clearTimeout(this.completedDebounceTimer); this.completedDebounceTimer = null; }
169
+ this.completedDebouncePending = null;
170
+ }
171
+
172
+ if (!this.generatingStartedAt) this.generatingStartedAt = now;
165
173
  // Defer the generating_started event — if idle comes back within 1s,
166
174
  // the whole started→completed pair was a false positive from PTY noise
167
175
  if (this.generatingDebounceTimer) clearTimeout(this.generatingDebounceTimer);
@@ -180,6 +188,10 @@ export class CliProviderInstance implements ProviderInstance {
180
188
  this.pushEvent({ event: 'agent:generating_started', ...this.generatingDebouncePending });
181
189
  this.generatingDebouncePending = null;
182
190
  }
191
+ // Cancel any pending completed
192
+ if (this.completedDebounceTimer) { clearTimeout(this.completedDebounceTimer); this.completedDebounceTimer = null; }
193
+ this.completedDebouncePending = null;
194
+
183
195
  if (!this.generatingStartedAt) this.generatingStartedAt = now;
184
196
  const modal = adapterStatus.activeModal;
185
197
  LOG.info('CLI', `[${this.type}] approval modal: "${modal?.message?.slice(0, 80) ?? 'none'}"`);
@@ -195,15 +207,27 @@ export class CliProviderInstance implements ProviderInstance {
195
207
  LOG.info('CLI', `[${this.type}] suppressed short generating (${now - this.generatingStartedAt}ms)`);
196
208
  if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
197
209
  this.generatingDebouncePending = null;
210
+ this.generatingStartedAt = 0;
198
211
  } else {
199
- LOG.info('CLI', `[${this.type}] completed in ${duration}s`);
200
- this.pushEvent({ event: 'agent:generating_completed', chatTitle, duration, timestamp: now });
212
+ // Debounce completed wait 2s, if still idle then emit
213
+ if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
214
+ this.completedDebouncePending = { chatTitle, duration, timestamp: now };
215
+ this.completedDebounceTimer = setTimeout(() => {
216
+ if (this.completedDebouncePending) {
217
+ LOG.info('CLI', `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
218
+ this.pushEvent({ event: 'agent:generating_completed', ...this.completedDebouncePending });
219
+ this.completedDebouncePending = null;
220
+ this.generatingStartedAt = 0;
221
+ }
222
+ this.completedDebounceTimer = null;
223
+ }, 2000);
201
224
  }
202
- this.generatingStartedAt = 0;
203
225
  } else if (newStatus === 'stopped') {
204
226
  // Cancel any pending debounce
205
227
  if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
206
228
  this.generatingDebouncePending = null;
229
+ if (this.completedDebounceTimer) { clearTimeout(this.completedDebounceTimer); this.completedDebounceTimer = null; }
230
+ this.completedDebouncePending = null;
207
231
  this.pushEvent({ event: 'agent:stopped', chatTitle, timestamp: now });
208
232
  }
209
233
  this.lastStatus = newStatus;