@adhdev/daemon-core 0.9.34 → 0.9.36

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.
@@ -79,6 +79,7 @@ export declare class CliProviderInstance implements ProviderInstance {
79
79
  previousProviderSessionId?: string;
80
80
  }) => void;
81
81
  });
82
+ refreshProviderDefinition(provider: ProviderModule): void;
82
83
  init(context: InstanceContext): Promise<void>;
83
84
  onTick(): Promise<void>;
84
85
  /**
@@ -78,6 +78,7 @@ export declare class ProviderInstanceManager {
78
78
  * Called when user changes settings from dashboard.
79
79
  */
80
80
  updateInstanceSettings(providerType: string, settings: Record<string, any>): number;
81
+ refreshProviderDefinitions(resolveProvider: (providerType: string) => unknown): number;
81
82
  /**
82
83
  * All terminate
83
84
  */
@@ -7,7 +7,7 @@
7
7
  * Daemon only collects via ProviderInstance.getState(),
8
8
  * Each Instance manages its own status.
9
9
  */
10
- import type { ProviderResumeCapability } from './contracts.js';
10
+ import type { ProviderModule, ProviderResumeCapability } from './contracts.js';
11
11
  import type { AcpConfigOption, AcpMode, ProviderControlSchema, ProviderSummaryMetadata, SessionCapability } from '../shared-types.js';
12
12
  import type { ChatMessage } from '../types.js';
13
13
  export type ProviderStatus = 'idle' | 'generating' | 'waiting_approval' | 'error' | 'stopped' | 'starting';
@@ -163,6 +163,8 @@ export interface ProviderInstance {
163
163
  onEvent(event: string, data?: any): void;
164
164
  /** Update settings at runtime (called when user changes settings from dashboard) */
165
165
  updateSettings?(newSettings: Record<string, any>): void;
166
+ /** Refresh static provider definition/scripts without restarting the live runtime. */
167
+ refreshProviderDefinition?(provider: ProviderModule): void;
166
168
  /** cleanup */
167
169
  dispose(): void;
168
170
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/session-host-core",
3
- "version": "0.9.34",
3
+ "version": "0.9.36",
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.9.34",
3
+ "version": "0.9.36",
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",
@@ -51,6 +51,7 @@ export interface CliAdapter {
51
51
  isReady(): boolean;
52
52
  setOnStatusChange(callback: () => void): void;
53
53
  updateRuntimeSettings?(settings: Record<string, unknown>): void;
54
+ setCliScripts?(scripts: Record<string, unknown>): void;
54
55
  setServerConn?(serverConn: unknown): void;
55
56
  clearHistory?(): void;
56
57
  resolveAction?(data: unknown): Promise<void>;
@@ -32,6 +32,7 @@ import {
32
32
  extractPromptRetrySnippet,
33
33
  getLastUserPromptText,
34
34
  listCliScriptNames,
35
+ normalizeComparableMessageContent,
35
36
  normalizePromptText,
36
37
  normalizeScreenSnapshot,
37
38
  promptLikelyVisible,
@@ -183,6 +184,8 @@ export class ProviderCliAdapter implements CliAdapter {
183
184
  private messages: CliChatMessage[] = [];
184
185
  private committedMessages: CliChatMessage[] = [];
185
186
  private structuredMessages: CliChatMessage[] = [];
187
+ private committedMessagesActivitySignature = '';
188
+ private committedMessagesChangedAt = 0;
186
189
  private currentStatus: CliSessionStatus['status'] = 'starting';
187
190
  private onStatusChange: (() => void) | null = null;
188
191
 
@@ -288,11 +291,38 @@ export class ProviderCliAdapter implements CliAdapter {
288
291
  private static readonly FINISH_RETRY_DELAY_MS = 300;
289
292
  private static readonly MAX_FINISH_RETRIES = 2;
290
293
 
294
+ private buildCommittedMessagesActivitySignature(): string {
295
+ const last = this.committedMessages[this.committedMessages.length - 1];
296
+ return [
297
+ String(this.committedMessages.length),
298
+ String(last?.role || ''),
299
+ String(last?.kind || ''),
300
+ String(last?.senderName || ''),
301
+ String(last?.timestamp || ''),
302
+ String(last?.receivedAt || ''),
303
+ normalizeComparableMessageContent(last?.content || '').slice(-240),
304
+ ].join('|');
305
+ }
306
+
291
307
  private syncMessageViews(): void {
308
+ const signature = this.buildCommittedMessagesActivitySignature();
309
+ if (signature !== this.committedMessagesActivitySignature) {
310
+ this.committedMessagesActivitySignature = signature;
311
+ this.committedMessagesChangedAt = Date.now();
312
+ }
292
313
  this.messages = [...this.committedMessages];
293
314
  this.structuredMessages = [...this.committedMessages];
294
315
  }
295
316
 
317
+ getLastCommittedMessageActivityAt(): number {
318
+ const last = this.committedMessages[this.committedMessages.length - 1];
319
+ const messageTime = Math.max(
320
+ typeof last?.receivedAt === 'number' && Number.isFinite(last.receivedAt) ? last.receivedAt : 0,
321
+ typeof last?.timestamp === 'number' && Number.isFinite(last.timestamp) ? last.timestamp : 0,
322
+ );
323
+ return Math.max(messageTime, this.committedMessagesChangedAt || 0);
324
+ }
325
+
296
326
  private readTerminalScreenText(now = Date.now()): string {
297
327
  const screenText = this.terminalScreen.getText() || '';
298
328
  this.lastScreenText = screenText;
@@ -339,7 +369,17 @@ export class ProviderCliAdapter implements CliAdapter {
339
369
  return baseMessages.slice(-ProviderCliAdapter.PARSE_MESSAGE_TAIL_LIMIT);
340
370
  }
341
371
 
372
+ private messagesShareStableIdentity(left: any, right: any): boolean {
373
+ if (left === right) return true;
374
+ if (!left || !right) return false;
375
+ if ((left.role || '') !== (right.role || '')) return false;
376
+ if (left.id && right.id && String(left.id) === String(right.id)) return true;
377
+ if (typeof left.index === 'number' && typeof right.index === 'number' && left.index === right.index) return true;
378
+ return false;
379
+ }
380
+
342
381
  private messagesComparable(left: any, right: any): boolean {
382
+ if (this.messagesShareStableIdentity(left, right)) return true;
343
383
  if (!left || !right) return false;
344
384
  if ((left.role || '') !== (right.role || '')) return false;
345
385
  const leftText = normalizeComparableTranscriptText(left.content);
@@ -519,10 +559,18 @@ export class ProviderCliAdapter implements CliAdapter {
519
559
  /** Inject CLI scripts after construction (e.g. when resolved by ProviderLoader) */
520
560
  setCliScripts(scripts: CliScripts): void {
521
561
  this.cliScripts = scripts;
562
+ this.parsedStatusCache = null;
563
+ this.parseErrorMessage = null;
522
564
  const scriptNames = listCliScriptNames(scripts);
523
565
  LOG.info('CLI', `[${this.cliType}] CLI scripts injected: [${scriptNames.join(', ')}]`);
524
566
  }
525
567
 
568
+ /** Refresh provider scripts/config used by this adapter without restarting the PTY runtime. */
569
+ refreshProviderDefinition(provider: CliProviderModule): void {
570
+ this.provider = provider;
571
+ this.setCliScripts(provider.scripts || {});
572
+ }
573
+
526
574
  updateRuntimeSettings(settings: Record<string, any>): void {
527
575
  this.runtimeSettings = { ...settings };
528
576
  }
@@ -1685,6 +1733,82 @@ export class ProviderCliAdapter implements CliAdapter {
1685
1733
  this.syncMessageViews();
1686
1734
  }
1687
1735
 
1736
+ private getSharedCommittedPrefixLength(parsedMessages: any[]): number {
1737
+ const committedMessages = this.committedMessages;
1738
+ const max = Math.min(parsedMessages.length, committedMessages.length);
1739
+ let index = 0;
1740
+ while (index < max && this.messagesShareStableIdentity(parsedMessages[index], committedMessages[index])) {
1741
+ index += 1;
1742
+ }
1743
+ return index;
1744
+ }
1745
+
1746
+ private hydrateCommittedPrefixForParsedStatus(parsedMessages: any[]): any[] | null {
1747
+ const sharedPrefixLength = this.getSharedCommittedPrefixLength(parsedMessages);
1748
+ if (sharedPrefixLength !== this.committedMessages.length) return null;
1749
+
1750
+ const committedHydratedMessages = this.committedMessages.map((message, index) => {
1751
+ const timestamp = typeof message.timestamp === 'number' && Number.isFinite(message.timestamp)
1752
+ ? message.timestamp
1753
+ : (this.lastOutputAt || this.currentTurnScope?.startedAt || Date.now());
1754
+ const contentValue = message.content;
1755
+ return {
1756
+ role: message.role,
1757
+ content: typeof contentValue === 'string' ? contentValue : String(contentValue || ''),
1758
+ timestamp,
1759
+ receivedAt: typeof message.receivedAt === 'number' && Number.isFinite(message.receivedAt)
1760
+ ? message.receivedAt
1761
+ : timestamp,
1762
+ kind: message.kind,
1763
+ id: message.id || `msg_${index}`,
1764
+ index: typeof message.index === 'number' ? message.index : index,
1765
+ meta: message.meta,
1766
+ senderName: message.senderName,
1767
+ };
1768
+ });
1769
+ const extraMessages = parsedMessages.slice(sharedPrefixLength);
1770
+ if (extraMessages.length === 0) return committedHydratedMessages;
1771
+
1772
+ const extraHydratedMessages = hydrateCliParsedMessages(extraMessages, {
1773
+ committedMessages: [],
1774
+ scope: this.currentTurnScope,
1775
+ lastOutputAt: this.lastOutputAt,
1776
+ }).map((message, offset) => ({
1777
+ ...message,
1778
+ id: message.id || `msg_${sharedPrefixLength + offset}`,
1779
+ index: typeof message.index === 'number' ? message.index : sharedPrefixLength + offset,
1780
+ }));
1781
+ return [...committedHydratedMessages, ...extraHydratedMessages];
1782
+ }
1783
+
1784
+ private hydrateParsedMessagesForStatus(parsedMessages: any[]): any[] {
1785
+ return this.hydrateCommittedPrefixForParsedStatus(parsedMessages)
1786
+ || hydrateCliParsedMessages(parsedMessages, {
1787
+ committedMessages: this.committedMessages,
1788
+ scope: this.currentTurnScope,
1789
+ lastOutputAt: this.lastOutputAt,
1790
+ });
1791
+ }
1792
+
1793
+ private buildCommittedChatMessages(): any[] {
1794
+ return this.committedMessages.map((message, index) => {
1795
+ const contentValue = message.content;
1796
+ return buildChatMessage({
1797
+ role: message.role,
1798
+ content: typeof contentValue === 'string' ? contentValue : String(contentValue || ''),
1799
+ timestamp: message.timestamp,
1800
+ kind: message.kind,
1801
+ meta: message.meta,
1802
+ senderName: message.senderName,
1803
+ id: message.id || `msg_${index}`,
1804
+ index: typeof message.index === 'number' ? message.index : index,
1805
+ receivedAt: typeof message.receivedAt === 'number'
1806
+ ? message.receivedAt
1807
+ : message.timestamp,
1808
+ });
1809
+ });
1810
+ }
1811
+
1688
1812
  /**
1689
1813
  * Script-based full parse — returns ReadChatResult.
1690
1814
  * Called by command handler / dashboard for rich content rendering.
@@ -1727,7 +1851,7 @@ export class ProviderCliAdapter implements CliAdapter {
1727
1851
  this.onStatusChange?.();
1728
1852
  }
1729
1853
  }
1730
- if (parsed && Array.isArray(parsed.messages)) {
1854
+ if (parsed && Array.isArray(parsed.messages) && this.provider.allowInputDuringGeneration === true) {
1731
1855
  const hydratedForCommit = normalizeCliParsedMessages(parsed.messages, {
1732
1856
  committedMessages: this.committedMessages,
1733
1857
  scope: this.currentTurnScope,
@@ -1749,25 +1873,13 @@ export class ProviderCliAdapter implements CliAdapter {
1749
1873
  && this.currentStatus === 'idle';
1750
1874
  let result: any;
1751
1875
  if (parsed && Array.isArray(parsed.messages)) {
1752
- const parsedHydratedMessages = hydrateCliParsedMessages(parsed.messages, {
1753
- committedMessages: this.committedMessages,
1754
- scope: this.currentTurnScope,
1755
- lastOutputAt: this.lastOutputAt,
1756
- });
1757
- const committedHydratedMessages = this.committedMessages.map((message, index) => buildChatMessage({
1758
- ...message,
1759
- id: message.id || `msg_${index}`,
1760
- index: typeof message.index === 'number' ? message.index : index,
1761
- receivedAt: typeof message.receivedAt === 'number'
1762
- ? message.receivedAt
1763
- : message.timestamp,
1764
- }));
1876
+ const parsedHydratedMessages = this.hydrateParsedMessagesForStatus(parsed.messages);
1765
1877
  const parsedLastAssistant = [...parsedHydratedMessages].reverse().find((message) => message.role === 'assistant' && typeof message.content === 'string' && message.content.trim());
1766
1878
  const shouldAdoptParsedIdleReplay =
1767
1879
  !this.currentTurnScope
1768
1880
  && !this.activeModal
1769
1881
  && !!parsedLastAssistant
1770
- && parsedTranscriptIsRicherThanCommitted(parsedHydratedMessages, committedHydratedMessages)
1882
+ && parsedTranscriptIsRicherThanCommitted(parsedHydratedMessages, this.committedMessages)
1771
1883
  && (
1772
1884
  this.currentStatus === 'idle'
1773
1885
  || (
@@ -1778,11 +1890,23 @@ export class ProviderCliAdapter implements CliAdapter {
1778
1890
  )
1779
1891
  );
1780
1892
  if (shouldAdoptParsedIdleReplay) {
1781
- this.committedMessages = normalizeCliParsedMessages(parsed.messages, {
1782
- committedMessages: this.committedMessages,
1783
- scope: this.currentTurnScope,
1784
- lastOutputAt: this.lastOutputAt,
1785
- });
1893
+ this.committedMessages = this.getSharedCommittedPrefixLength(parsed.messages) === this.committedMessages.length
1894
+ ? parsedHydratedMessages.map((message) => ({
1895
+ role: message.role,
1896
+ content: typeof message.content === 'string' ? message.content : String(message.content || ''),
1897
+ timestamp: message.timestamp,
1898
+ receivedAt: message.receivedAt,
1899
+ kind: message.kind,
1900
+ id: message.id,
1901
+ index: message.index,
1902
+ meta: message.meta,
1903
+ senderName: message.senderName,
1904
+ }))
1905
+ : normalizeCliParsedMessages(parsed.messages, {
1906
+ committedMessages: this.committedMessages,
1907
+ scope: this.currentTurnScope,
1908
+ lastOutputAt: this.lastOutputAt,
1909
+ });
1786
1910
  this.syncMessageViews();
1787
1911
  if (this.currentStatus !== 'idle' || this.isWaitingForResponse) {
1788
1912
  this.responseBuffer = '';
@@ -1797,25 +1921,15 @@ export class ProviderCliAdapter implements CliAdapter {
1797
1921
  this.onStatusChange?.();
1798
1922
  }
1799
1923
  }
1800
- const effectiveCommittedHydratedMessages = shouldAdoptParsedIdleReplay
1801
- ? this.committedMessages.map((message, index) => buildChatMessage({
1802
- ...message,
1803
- id: message.id || `msg_${index}`,
1804
- index: typeof message.index === 'number' ? message.index : index,
1805
- receivedAt: typeof message.receivedAt === 'number'
1806
- ? message.receivedAt
1807
- : message.timestamp,
1808
- }))
1809
- : committedHydratedMessages;
1810
1924
  const shouldPreferCommittedHistoryReplay =
1811
1925
  !this.currentTurnScope
1812
1926
  && !this.activeModal
1813
- && effectiveCommittedHydratedMessages.length > parsedHydratedMessages.length;
1927
+ && this.committedMessages.length > parsedHydratedMessages.length;
1814
1928
  const shouldPreferCommittedIdleReplay =
1815
1929
  shouldPreferCommittedMessages
1816
1930
  && !shouldAdoptParsedIdleReplay;
1817
1931
  const hydratedMessages = (shouldPreferCommittedIdleReplay || shouldPreferCommittedHistoryReplay)
1818
- ? effectiveCommittedHydratedMessages
1932
+ ? this.buildCommittedChatMessages()
1819
1933
  : parsedHydratedMessages;
1820
1934
  result = {
1821
1935
  id: parsed.id || 'cli_session',
@@ -507,7 +507,16 @@ export class DaemonCommandHandler implements CommandHelpers {
507
507
  if (this._ctx.providerLoader) {
508
508
  await this._ctx.providerLoader.fetchLatest().catch(() => {});
509
509
  this._ctx.providerLoader.reload();
510
- return { success: true };
510
+ this._ctx.providerLoader.registerToDetector();
511
+ const refreshedInstances = this._ctx.instanceManager
512
+ ? this._ctx.instanceManager.refreshProviderDefinitions((providerType) => this._ctx.providerLoader!.resolve(providerType))
513
+ : 0;
514
+ const providers = this._ctx.providerLoader.getAll().map((provider) => ({
515
+ type: provider.type,
516
+ name: provider.name,
517
+ category: provider.category,
518
+ }));
519
+ return { success: true, refreshedInstances, providers };
511
520
  }
512
521
  return { success: false, error: 'ProviderLoader not initialized' };
513
522
  }
@@ -507,20 +507,9 @@ export class DevServer implements DevServerContext {
507
507
  private async handleReload(_req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
508
508
  try {
509
509
  this.providerLoader.reload();
510
- let refreshedInstances = 0;
511
- if (this.instanceManager) {
512
- for (const id of this.instanceManager.listInstanceIds()) {
513
- const instance = this.instanceManager.getInstance(id) as any;
514
- const providerType = typeof instance?.type === 'string' ? instance.type : '';
515
- if (!providerType) continue;
516
- const resolved = this.providerLoader.resolve(providerType);
517
- if (!resolved) continue;
518
- if (instance && typeof instance === 'object' && 'provider' in instance) {
519
- instance.provider = resolved;
520
- refreshedInstances += 1;
521
- }
522
- }
523
- }
510
+ const refreshedInstances = this.instanceManager
511
+ ? this.instanceManager.refreshProviderDefinitions((providerType) => this.providerLoader.resolve(providerType))
512
+ : 0;
524
513
  const providers = this.providerLoader.getAll().map(p => ({
525
514
  type: p.type, name: p.name, category: p.category,
526
515
  }));
@@ -219,6 +219,12 @@ export class CliProviderInstance implements ProviderInstance {
219
219
  this.historyWriter = new ChatHistoryWriter();
220
220
  }
221
221
 
222
+ refreshProviderDefinition(provider: ProviderModule): void {
223
+ if (provider.type !== this.type || provider.category !== 'cli') return;
224
+ this.provider = provider;
225
+ this.adapter.refreshProviderDefinition(provider as CliProviderModule);
226
+ }
227
+
222
228
  // ─── Lifecycle ─────────────────────────────────
223
229
 
224
230
  async init(context: InstanceContext): Promise<void> {
@@ -492,9 +498,13 @@ export class CliProviderInstance implements ProviderInstance {
492
498
  const autoApproveActive = adapterStatus.status === 'waiting_approval' && this.shouldAutoApprove();
493
499
  const visibleStatus = autoApproveActive ? 'generating' : adapterStatus.status;
494
500
  const runtime = this.adapter.getRuntimeMetadata();
501
+ const lastCommittedMessageActivityAt = typeof this.adapter.getLastCommittedMessageActivityAt === 'function'
502
+ ? this.adapter.getLastCommittedMessageActivityAt()
503
+ : 0;
495
504
  return {
496
505
  id: this.instanceId,
497
506
  status: visibleStatus,
507
+ lastMessageAt: lastCommittedMessageActivityAt || undefined,
498
508
  runtimeLifecycle: runtime?.lifecycle ?? null,
499
509
  runtimeSurfaceKind: runtime?.surfaceKind,
500
510
  runtimeRestoredFromStorage: runtime?.restoredFromStorage === true,
@@ -15,6 +15,9 @@ function projectHotChatSessionStatesFromProviderState(state: ProviderState): Hot
15
15
  const project = (item: ProviderState): HotChatSessionState => ({
16
16
  id: item.instanceId,
17
17
  status: item.activeChat?.status || item.status,
18
+ unread: (item as any).unread,
19
+ inboxBucket: (item as any).inboxBucket,
20
+ lastMessageAt: (item as any).lastMessageAt ?? (item.activeChat as any)?.lastMessageAt,
18
21
  runtimeLifecycle: item.runtime?.lifecycle ?? null,
19
22
  runtimeSurfaceKind: item.runtime?.surfaceKind,
20
23
  runtimeRestoredFromStorage: item.runtime?.restoredFromStorage === true,
@@ -258,6 +261,18 @@ export class ProviderInstanceManager {
258
261
  return updated;
259
262
  }
260
263
 
264
+ refreshProviderDefinitions(resolveProvider: (providerType: string) => unknown): number {
265
+ let refreshed = 0;
266
+ for (const instance of this.instances.values()) {
267
+ if (typeof instance.refreshProviderDefinition !== 'function') continue;
268
+ const provider = resolveProvider(instance.type);
269
+ if (!provider || typeof provider !== 'object') continue;
270
+ instance.refreshProviderDefinition(provider as any);
271
+ refreshed += 1;
272
+ }
273
+ return refreshed;
274
+ }
275
+
261
276
  // ─── cleanup ──────────────────────────────────────
262
277
 
263
278
  /**
@@ -197,6 +197,9 @@ export interface ProviderInstance {
197
197
  /** Update settings at runtime (called when user changes settings from dashboard) */
198
198
  updateSettings?(newSettings: Record<string, any>): void;
199
199
 
200
+ /** Refresh static provider definition/scripts without restarting the live runtime. */
201
+ refreshProviderDefinition?(provider: ProviderModule): void;
202
+
200
203
  /** cleanup */
201
204
  dispose(): void;
202
205
  }