@adhdev/daemon-core 0.8.87 → 0.8.89

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.
@@ -0,0 +1,11 @@
1
+ export declare const DEFAULT_CDP_SCAN_INTERVAL_MS = 30000;
2
+ export declare const DEFAULT_CDP_DISCOVERY_INTERVAL_MS = 30000;
3
+ export declare const DEFAULT_STATUS_INITIAL_REPORT_DELAY_MS = 2000;
4
+ export declare const DEFAULT_STATUS_SERVER_REPORT_INTERVAL_MS = 30000;
5
+ export declare const DEFAULT_STATUS_P2P_REPORT_INTERVAL_MS = 5000;
6
+ export declare const MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS = 5000;
7
+ export declare const DEFAULT_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS = 15000;
8
+ export declare const MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS = 5000;
9
+ export declare const DEFAULT_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS = 10000;
10
+ export declare const DEFAULT_SESSION_HOST_READY_TIMEOUT_MS = 15000;
11
+ export declare const STANDALONE_CDP_SCAN_INTERVAL_MS = 15000;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/session-host-core",
3
- "version": "0.8.87",
3
+ "version": "0.8.89",
4
4
  "description": "ADHDev local session host core \u2014 session registry, protocol, buffers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.8.87",
3
+ "version": "0.8.89",
4
4
  "description": "ADHDev daemon core \u2014 CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -27,7 +27,11 @@ import { DevServer } from '../daemon/dev-server.js';
27
27
  import { detectIDEs, type IDEInfo } from '../detection/ide-detector.js';
28
28
  import { detectCLI, detectCLIs } from '../detection/cli-detector.js';
29
29
  import { SessionRegistry } from '../sessions/registry.js';
30
- import { installGlobalInterceptor, LOG } from '../logging/logger.js';
30
+ import { LOG, installGlobalInterceptor } from '../logging/logger.js';
31
+ import {
32
+ DEFAULT_CDP_DISCOVERY_INTERVAL_MS,
33
+ DEFAULT_CDP_SCAN_INTERVAL_MS,
34
+ } from '../runtime-defaults.js';
31
35
  import { loadConfig } from '../config/config.js';
32
36
  import type { PtyTransportFactory } from '../cli-adapters/pty-transport.js';
33
37
  import type { IdeProviderInstance } from '../providers/ide-provider-instance.js';
@@ -240,8 +244,8 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
240
244
  },
241
245
  });
242
246
  await cdpInitializer.connectAll(detectedIdesRef.value);
243
- cdpInitializer.startPeriodicScan(config.cdpScanIntervalMs ?? 30_000);
244
- cdpInitializer.startDiscovery(30_000);
247
+ cdpInitializer.startPeriodicScan(config.cdpScanIntervalMs ?? DEFAULT_CDP_SCAN_INTERVAL_MS);
248
+ cdpInitializer.startDiscovery(DEFAULT_CDP_DISCOVERY_INTERVAL_MS);
245
249
 
246
250
  // 7. CommandHandler
247
251
  const commandHandler = new DaemonCommandHandler({
@@ -14,6 +14,7 @@ import { registerExtensionProviders } from './setup.js';
14
14
  import { probeCdpPort } from './setup.js';
15
15
  import type { ProviderLoader } from '../providers/provider-loader.js';
16
16
  import { LOG } from '../logging/logger.js';
17
+ import { DEFAULT_CDP_DISCOVERY_INTERVAL_MS, DEFAULT_CDP_SCAN_INTERVAL_MS } from '../runtime-defaults.js';
17
18
 
18
19
  // ─── Config ───
19
20
 
@@ -206,7 +207,7 @@ export class DaemonCdpInitializer {
206
207
  * Start periodic scanning for newly opened IDEs.
207
208
  * Idempotent — ignored if already started.
208
209
  */
209
- startPeriodicScan(intervalMs = 30_000): void {
210
+ startPeriodicScan(intervalMs = DEFAULT_CDP_SCAN_INTERVAL_MS): void {
210
211
  if (this.scanTimer) return;
211
212
 
212
213
  this.scanTimer = setInterval(async () => {
@@ -224,7 +225,7 @@ export class DaemonCdpInitializer {
224
225
  /**
225
226
  * Start periodic agent webview discovery.
226
227
  */
227
- startDiscovery(intervalMs = 30_000): void {
228
+ startDiscovery(intervalMs = DEFAULT_CDP_DISCOVERY_INTERVAL_MS): void {
228
229
  if (this.discoveryTimer) return;
229
230
 
230
231
  this.discoveryTimer = setInterval(async () => {
@@ -12,6 +12,7 @@ import { DaemonCdpManager } from './manager.js';
12
12
  import { ProviderLoader } from '../providers/provider-loader.js';
13
13
  import { connectCdpManager, probeCdpPort, registerExtensionProviders, setupIdeInstance, type CdpSetupContext } from './setup.js';
14
14
  import { LOG } from '../logging/logger.js';
15
+ import { DEFAULT_CDP_DISCOVERY_INTERVAL_MS, DEFAULT_CDP_SCAN_INTERVAL_MS } from '../runtime-defaults.js';
15
16
 
16
17
  export interface CdpScannerOptions {
17
18
  /** Context for setup operations */
@@ -68,7 +69,7 @@ export class DaemonCdpScanner {
68
69
  */
69
70
  startPeriodicScan(): void {
70
71
  if (this.scanTimer) return;
71
- const interval = this.opts.scanIntervalMs || 30_000;
72
+ const interval = this.opts.scanIntervalMs || DEFAULT_CDP_SCAN_INTERVAL_MS;
72
73
 
73
74
  this.scanTimer = setInterval(async () => {
74
75
  const portMap = this.ctx.providerLoader.getCdpPortMap();
@@ -92,7 +93,7 @@ export class DaemonCdpScanner {
92
93
  /**
93
94
  * Start periodic agent webview discovery on all connected CDPs.
94
95
  */
95
- startWebviewDiscovery(intervalMs = 30_000): void {
96
+ startWebviewDiscovery(intervalMs = DEFAULT_CDP_DISCOVERY_INTERVAL_MS): void {
96
97
  if (this.discoveryTimer) return;
97
98
  this.discoveryTimer = setInterval(async () => {
98
99
  for (const m of this.ctx.cdpManagers.values()) {
@@ -804,9 +804,7 @@ export class ProviderCliAdapter implements CliAdapter {
804
804
  const buttons = Array.isArray(modal.buttons) ? modal.buttons : [];
805
805
  if (buttons.length !== 1) return false;
806
806
  const buttonLabel = String(buttons[0] || '').trim();
807
- const modalText = `${modal.message || ''} ${buttonLabel}`.trim();
808
- return looksLikeConfirmOnlyLabel(buttonLabel)
809
- || /Quick safety check|project trust|trust (?:this project|the contents of this directory|the files in this folder)|Enter to confirm/i.test(modalText);
807
+ return looksLikeConfirmOnlyLabel(buttonLabel);
810
808
  }
811
809
 
812
810
  private async waitForInteractivePrompt(maxWaitMs = 5000): Promise<void> {
@@ -1466,11 +1464,16 @@ export class ProviderCliAdapter implements CliAdapter {
1466
1464
  // ─── Public API (CliAdapter) ───────────────────
1467
1465
 
1468
1466
  getStatus(): CliSessionStatus {
1467
+ const screenText = this.terminalScreen.getText() || '';
1468
+ const startupModal = this.startupParseGate ? this.getStartupConfirmationModal(screenText) : null;
1469
+ const effectiveStatus = this.parseErrorMessage
1470
+ ? 'error'
1471
+ : (startupModal ? 'waiting_approval' : this.currentStatus);
1469
1472
  return {
1470
- status: this.parseErrorMessage ? 'error' : this.currentStatus,
1473
+ status: effectiveStatus,
1471
1474
  messages: [...this.committedMessages],
1472
1475
  workingDir: this.workingDir,
1473
- activeModal: this.activeModal,
1476
+ activeModal: startupModal || this.activeModal,
1474
1477
  errorMessage: this.parseErrorMessage || undefined,
1475
1478
  errorReason: this.parseErrorMessage ? 'parse_error' : undefined,
1476
1479
  };
@@ -1550,12 +1553,61 @@ export class ProviderCliAdapter implements CliAdapter {
1550
1553
  ? message.receivedAt
1551
1554
  : message.timestamp,
1552
1555
  }));
1556
+ const parsedLastAssistant = [...parsedHydratedMessages].reverse().find((message) => message.role === 'assistant' && typeof message.content === 'string' && message.content.trim());
1557
+ const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
1558
+ const shouldAdoptParsedIdleReplay =
1559
+ !this.currentTurnScope
1560
+ && !this.activeModal
1561
+ && !!parsedLastAssistant
1562
+ && parsedHydratedMessages.length > committedHydratedMessages.length
1563
+ && (
1564
+ this.currentStatus === 'idle'
1565
+ || (
1566
+ this.currentStatus === 'generating'
1567
+ && this.isWaitingForResponse
1568
+ && parsed.status === 'idle'
1569
+ && visibleIdlePrompt
1570
+ )
1571
+ );
1572
+ if (shouldAdoptParsedIdleReplay) {
1573
+ this.committedMessages = normalizeCliParsedMessages(parsed.messages, {
1574
+ committedMessages: this.committedMessages,
1575
+ scope: this.currentTurnScope,
1576
+ lastOutputAt: this.lastOutputAt,
1577
+ });
1578
+ this.syncMessageViews();
1579
+ if (this.currentStatus !== 'idle' || this.isWaitingForResponse) {
1580
+ this.responseBuffer = '';
1581
+ this.isWaitingForResponse = false;
1582
+ this.responseSettleIgnoreUntil = 0;
1583
+ this.submitRetryUsed = false;
1584
+ this.submitRetryPromptSnippet = '';
1585
+ this.finishRetryCount = 0;
1586
+ this.currentTurnScope = null;
1587
+ this.activeModal = null;
1588
+ this.setStatus('idle', 'parsed_idle_replay_commit');
1589
+ this.onStatusChange?.();
1590
+ }
1591
+ }
1592
+ const effectiveCommittedHydratedMessages = shouldAdoptParsedIdleReplay
1593
+ ? this.committedMessages.map((message, index) => buildChatMessage({
1594
+ ...message,
1595
+ id: message.id || `msg_${index}`,
1596
+ index: typeof message.index === 'number' ? message.index : index,
1597
+ receivedAt: typeof message.receivedAt === 'number'
1598
+ ? message.receivedAt
1599
+ : message.timestamp,
1600
+ }))
1601
+ : committedHydratedMessages;
1553
1602
  const shouldPreferCommittedHistoryReplay =
1554
1603
  !this.currentTurnScope
1555
1604
  && !this.activeModal
1556
- && committedHydratedMessages.length > parsedHydratedMessages.length;
1557
- const hydratedMessages = (shouldPreferCommittedMessages || shouldPreferCommittedHistoryReplay)
1558
- ? committedHydratedMessages
1605
+ && effectiveCommittedHydratedMessages.length > parsedHydratedMessages.length;
1606
+ const shouldPreferCommittedIdleReplay =
1607
+ shouldPreferCommittedMessages
1608
+ && !shouldAdoptParsedIdleReplay;
1609
+ const hydratedMessages = (shouldPreferCommittedIdleReplay || shouldPreferCommittedHistoryReplay)
1610
+ ? effectiveCommittedHydratedMessages
1559
1611
  : parsedHydratedMessages;
1560
1612
  result = {
1561
1613
  id: parsed.id || 'cli_session',
@@ -2132,8 +2184,9 @@ export class ProviderCliAdapter implements CliAdapter {
2132
2184
  }
2133
2185
 
2134
2186
  resolveModal(buttonIndex: number): void {
2135
- if (!this.ptyProcess || (this.currentStatus !== 'waiting_approval' && !this.activeModal)) return;
2136
- const modal = this.activeModal;
2187
+ const screenText = this.terminalScreen.getText() || '';
2188
+ const modal = this.activeModal || this.getStartupConfirmationModal(screenText);
2189
+ if (!this.ptyProcess || ((this.currentStatus !== 'waiting_approval') && !modal)) return;
2137
2190
  this.clearIdleFinishCandidate('resolve_modal');
2138
2191
  this.recordTrace('resolve_modal', {
2139
2192
  buttonIndex,
@@ -2148,7 +2201,10 @@ export class ProviderCliAdapter implements CliAdapter {
2148
2201
  }
2149
2202
  this.setStatus('generating', 'approval_resolved');
2150
2203
  this.onStatusChange?.();
2151
- if (this.shouldResolveModalWithEnter(modal, buttonIndex)) {
2204
+ const startupTrustModal = /Quick safety check|project trust|trust (?:this project|the contents of this directory|the files in this folder)/i.test(String(modal?.message || ''));
2205
+ if (startupTrustModal && buttonIndex in this.approvalKeys) {
2206
+ this.ptyProcess.write(`${this.approvalKeys[buttonIndex]}\r`);
2207
+ } else if (this.shouldResolveModalWithEnter(modal, buttonIndex)) {
2152
2208
  this.ptyProcess.write('\r');
2153
2209
  } else if (buttonIndex in this.approvalKeys) {
2154
2210
  this.ptyProcess.write(this.approvalKeys[buttonIndex]);
@@ -2170,20 +2226,24 @@ export class ProviderCliAdapter implements CliAdapter {
2170
2226
  }
2171
2227
 
2172
2228
  getDebugState(): Record<string, any> {
2229
+ const screenText = sanitizeTerminalText(this.terminalScreen.getText());
2230
+ const startupModal = this.startupParseGate ? this.getStartupConfirmationModal(screenText) : null;
2231
+ const effectiveStatus = startupModal ? 'waiting_approval' : this.currentStatus;
2232
+ const effectiveReady = this.ready || !!startupModal;
2173
2233
  return {
2174
2234
  type: this.cliType,
2175
2235
  name: this.cliName,
2176
2236
  providerResolution: this.providerResolutionMeta,
2177
- status: this.currentStatus,
2178
- ready: this.ready,
2237
+ status: effectiveStatus,
2238
+ ready: effectiveReady,
2179
2239
  startupParseGate: this.startupParseGate,
2180
2240
  spawnAt: this.spawnAt,
2181
2241
  workingDir: this.workingDir,
2182
- messages: this.messages.slice(-20),
2183
- committedMessages: this.committedMessages.slice(-20),
2184
- structuredMessages: this.structuredMessages.slice(-20),
2242
+ messages: this.messages,
2243
+ committedMessages: this.committedMessages,
2244
+ structuredMessages: this.structuredMessages,
2185
2245
  messageCount: this.committedMessages.length,
2186
- screenText: sanitizeTerminalText(this.terminalScreen.getText()).slice(-4000),
2246
+ screenText: screenText.slice(-4000),
2187
2247
  currentTurnScope: this.currentTurnScope,
2188
2248
  startupBuffer: this.startupBuffer.slice(-4000),
2189
2249
  recentOutputBuffer: this.recentOutputBuffer.slice(-500),
@@ -2198,7 +2258,7 @@ export class ProviderCliAdapter implements CliAdapter {
2198
2258
  lastScreenChangeAt: this.lastScreenChangeAt,
2199
2259
  lastScreenSnapshot: this.lastScreenSnapshot.slice(-500),
2200
2260
  isWaitingForResponse: this.isWaitingForResponse,
2201
- activeModal: this.activeModal,
2261
+ activeModal: startupModal || this.activeModal,
2202
2262
  lastApprovalResolvedAt: this.lastApprovalResolvedAt,
2203
2263
  sendDelayMs: this.sendDelayMs,
2204
2264
  sendKey: this.sendKey,
package/src/index.ts CHANGED
@@ -185,6 +185,19 @@ export { launchWithCdp, getAvailableIdeIds, killIdeProcess, isIdeRunning } from
185
185
 
186
186
  // ── IPC ──
187
187
  export { DEFAULT_DAEMON_PORT, DAEMON_WS_PATH } from './ipc-protocol.js';
188
+ export {
189
+ DEFAULT_CDP_SCAN_INTERVAL_MS,
190
+ DEFAULT_CDP_DISCOVERY_INTERVAL_MS,
191
+ DEFAULT_STATUS_INITIAL_REPORT_DELAY_MS,
192
+ DEFAULT_STATUS_SERVER_REPORT_INTERVAL_MS,
193
+ DEFAULT_STATUS_P2P_REPORT_INTERVAL_MS,
194
+ MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
195
+ DEFAULT_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
196
+ MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
197
+ DEFAULT_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
198
+ DEFAULT_SESSION_HOST_READY_TIMEOUT_MS,
199
+ STANDALONE_CDP_SCAN_INTERVAL_MS,
200
+ } from './runtime-defaults.js';
188
201
 
189
202
  // ── Chat History ──
190
203
  export { readChatHistory } from './config/chat-history.js';
@@ -253,7 +266,7 @@ export { VersionArchive, detectAllVersions } from './providers/version-archive.j
253
266
  export type { ProviderVersionInfo, VersionHistory } from './providers/version-archive.js';
254
267
 
255
268
  // ── Dev Server ──
256
- export { DevServer } from './daemon/dev-server.js';
269
+ export { DevServer, DEV_SERVER_PORT } from './daemon/dev-server.js';
257
270
 
258
271
  // ── CLI Adapters ──
259
272
  export { ProviderCliAdapter } from './cli-adapters/provider-cli-adapter.js';
@@ -0,0 +1,16 @@
1
+ export const DEFAULT_CDP_SCAN_INTERVAL_MS = 30_000;
2
+ export const DEFAULT_CDP_DISCOVERY_INTERVAL_MS = 30_000;
3
+
4
+ export const DEFAULT_STATUS_INITIAL_REPORT_DELAY_MS = 2_000;
5
+ export const DEFAULT_STATUS_SERVER_REPORT_INTERVAL_MS = 30_000;
6
+ export const DEFAULT_STATUS_P2P_REPORT_INTERVAL_MS = 5_000;
7
+
8
+ export const MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS = 5_000;
9
+ export const DEFAULT_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS = 15_000;
10
+
11
+ export const MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS = 5_000;
12
+ export const DEFAULT_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS = 10_000;
13
+
14
+ export const DEFAULT_SESSION_HOST_READY_TIMEOUT_MS = 15_000;
15
+
16
+ export const STANDALONE_CDP_SCAN_INTERVAL_MS = 15_000;
@@ -5,8 +5,9 @@ import {
5
5
  type SessionHostRecord,
6
6
  } from '@adhdev/session-host-core';
7
7
  import type { HostedCliRuntimeDescriptor } from '../commands/cli-manager.js';
8
+ import { DEFAULT_SESSION_HOST_READY_TIMEOUT_MS } from '../runtime-defaults.js';
8
9
 
9
- const STARTUP_TIMEOUT_MS = 8000;
10
+ const STARTUP_TIMEOUT_MS = DEFAULT_SESSION_HOST_READY_TIMEOUT_MS;
10
11
  const STARTUP_POLL_MS = 200;
11
12
 
12
13
  async function canConnect(endpoint: SessionHostEndpoint): Promise<boolean> {
@@ -6,6 +6,11 @@
6
6
  */
7
7
 
8
8
  import { LOG } from '../logging/logger.js';
9
+ import {
10
+ DEFAULT_STATUS_INITIAL_REPORT_DELAY_MS,
11
+ DEFAULT_STATUS_P2P_REPORT_INTERVAL_MS,
12
+ DEFAULT_STATUS_SERVER_REPORT_INTERVAL_MS,
13
+ } from '../runtime-defaults.js';
9
14
  import type { DaemonCdpManager } from '../cdp/manager.js';
10
15
  import type { MachineInfo } from '../shared-types.js';
11
16
  import type { CloudStatusReportPayload, DaemonStatusEventPayload } from '../shared-types.js';
@@ -63,13 +68,13 @@ export class DaemonStatusReporter {
63
68
  startReporting(): void {
64
69
  setTimeout(() => {
65
70
  this.sendUnifiedStatusReport({ forceServer: true, reason: 'initial' }).catch(e => LOG.warn('Status', `Initial report failed: ${e?.message}`));
66
- }, 2000);
71
+ }, DEFAULT_STATUS_INITIAL_REPORT_DELAY_MS);
67
72
 
68
73
  const scheduleServerReport = () => {
69
74
  this.statusTimer = setTimeout(() => {
70
75
  this.sendUnifiedStatusReport({ forceServer: true, reason: 'periodic' }).catch(e => LOG.warn('Status', `Periodic report failed: ${e?.message}`));
71
76
  scheduleServerReport();
72
- }, 30_000);
77
+ }, DEFAULT_STATUS_SERVER_REPORT_INTERVAL_MS);
73
78
  };
74
79
  scheduleServerReport();
75
80
 
@@ -77,7 +82,7 @@ export class DaemonStatusReporter {
77
82
  if (this.deps.p2p?.isConnected) {
78
83
  this.sendUnifiedStatusReport({ p2pOnly: true }).catch(e => LOG.warn('Status', `P2P status send failed: ${e?.message}`));
79
84
  }
80
- }, 5_000);
85
+ }, DEFAULT_STATUS_P2P_REPORT_INTERVAL_MS);
81
86
  }
82
87
 
83
88
  stopReporting(): void {
@@ -92,14 +97,14 @@ export class DaemonStatusReporter {
92
97
  throttledReport(): void {
93
98
  const now = Date.now();
94
99
  const elapsed = now - this.lastStatusSentAt;
95
- if (elapsed >= 5_000) {
100
+ if (elapsed >= DEFAULT_STATUS_P2P_REPORT_INTERVAL_MS) {
96
101
  this.sendUnifiedStatusReport().catch(e => LOG.warn('Status', `Throttled report failed: ${e?.message}`));
97
102
  } else if (!this.statusPendingThrottle) {
98
103
  this.statusPendingThrottle = true;
99
104
  setTimeout(() => {
100
105
  this.statusPendingThrottle = false;
101
106
  this.sendUnifiedStatusReport().catch(e => LOG.warn('Status', `Deferred report failed: ${e?.message}`));
102
- }, 5_000 - elapsed);
107
+ }, DEFAULT_STATUS_P2P_REPORT_INTERVAL_MS - elapsed);
103
108
  }
104
109
  }
105
110