@adhdev/daemon-core 0.8.49 → 0.8.50

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.
@@ -13,6 +13,7 @@ export interface CommandLogEntry {
13
13
  ts: string;
14
14
  cmd: string;
15
15
  source: 'ws' | 'p2p' | 'ext' | 'api' | 'standalone' | 'unknown';
16
+ interactionId?: string;
16
17
  args?: Record<string, unknown>;
17
18
  success?: boolean;
18
19
  error?: string;
@@ -0,0 +1,21 @@
1
+ import type { LogLevel } from './logger.js';
2
+ export interface DebugRuntimeOptions {
3
+ dev?: boolean;
4
+ logLevel?: LogLevel;
5
+ trace?: boolean;
6
+ traceContent?: boolean;
7
+ traceBufferSize?: number;
8
+ traceCategories?: string[];
9
+ }
10
+ export interface DebugRuntimeConfig {
11
+ logLevel: LogLevel;
12
+ collectDebugTrace: boolean;
13
+ traceContent: boolean;
14
+ traceBufferSize: number;
15
+ traceCategories: string[];
16
+ }
17
+ export declare function resolveDebugRuntimeConfig(options?: DebugRuntimeOptions): DebugRuntimeConfig;
18
+ export declare function setDebugRuntimeConfig(config: DebugRuntimeConfig): void;
19
+ export declare function getDebugRuntimeConfig(): DebugRuntimeConfig;
20
+ export declare function resetDebugRuntimeConfig(): void;
21
+ export declare function shouldCollectTraceCategory(category?: string | null): boolean;
@@ -0,0 +1,35 @@
1
+ export type DebugTraceLevel = 'debug' | 'info' | 'warn' | 'error';
2
+ export interface DebugTraceEvent {
3
+ interactionId?: string;
4
+ category: string;
5
+ stage: string;
6
+ level: DebugTraceLevel;
7
+ sessionId?: string;
8
+ providerType?: string;
9
+ payload?: Record<string, unknown>;
10
+ }
11
+ export interface DebugTraceEntry extends DebugTraceEvent {
12
+ id: string;
13
+ ts: number;
14
+ }
15
+ export interface DebugTraceStoreOptions {
16
+ enabled: boolean;
17
+ capacity: number;
18
+ }
19
+ export interface DebugTraceQuery {
20
+ interactionId?: string;
21
+ category?: string;
22
+ limit?: number;
23
+ }
24
+ export interface DebugTraceStore {
25
+ record(event: DebugTraceEvent): DebugTraceEntry | null;
26
+ list(query?: DebugTraceQuery): DebugTraceEntry[];
27
+ clear(): void;
28
+ }
29
+ export declare function sanitizeTracePayload(payload?: Record<string, unknown>): Record<string, unknown>;
30
+ export declare function createDebugTraceStore(options: DebugTraceStoreOptions): DebugTraceStore;
31
+ export declare function configureDebugTraceStore(): void;
32
+ export declare function recordDebugTrace(event: DebugTraceEvent): DebugTraceEntry | null;
33
+ export declare function getRecentDebugTrace(query?: DebugTraceQuery): DebugTraceEntry[];
34
+ export declare function clearDebugTrace(): void;
35
+ export declare function createInteractionId(prefix?: string): string;
@@ -0,0 +1,6 @@
1
+ export declare const DEFAULT_SESSION_HOST_APP_NAME = "adhdev";
2
+ export declare const DEFAULT_STANDALONE_SESSION_HOST_APP_NAME = "adhdev-standalone";
3
+ export declare function resolveSessionHostAppName(options?: {
4
+ standalone?: boolean;
5
+ env?: NodeJS.ProcessEnv;
6
+ }): string;
@@ -138,6 +138,7 @@ export interface SessionChatTailUpdate extends ReadChatSyncResult {
138
138
  key: string;
139
139
  sessionId: string;
140
140
  historySessionId?: string;
141
+ interactionId?: string;
141
142
  seq: number;
142
143
  timestamp: number;
143
144
  }
@@ -163,6 +164,7 @@ export interface SessionModalUpdate {
163
164
  title?: string;
164
165
  modalMessage?: string;
165
166
  modalButtons?: string[];
167
+ interactionId?: string;
166
168
  seq: number;
167
169
  timestamp: number;
168
170
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/session-host-core",
3
- "version": "0.8.49",
3
+ "version": "0.8.50",
4
4
  "description": "ADHDev local session host core — 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.49",
3
+ "version": "0.8.50",
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",
@@ -9,6 +9,7 @@ import { flattenContent, type ProviderModule, type ProviderScripts } from '../pr
9
9
  import type { ProviderInstance } from '../providers/provider-instance.js';
10
10
  import { readChatHistory } from '../config/chat-history.js';
11
11
  import { LOG } from '../logging/logger.js';
12
+ import { recordDebugTrace } from '../logging/debug-trace.js';
12
13
  import type { ChatMessage } from '../types.js';
13
14
  import type { ReadChatCursor, ReadChatSyncMode, SessionTransport } from '../shared-types.js';
14
15
 
@@ -103,6 +104,34 @@ function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
103
104
  return providerSessionId || targetSessionId;
104
105
  }
105
106
 
107
+ function getInteractionId(args: any): string | undefined {
108
+ return typeof args?._interactionId === 'string' && args._interactionId.trim()
109
+ ? args._interactionId.trim()
110
+ : undefined;
111
+ }
112
+
113
+ function traceProviderEvent(
114
+ args: any,
115
+ category: 'provider' | 'parser',
116
+ stage: string,
117
+ options: {
118
+ h: CommandHelpers;
119
+ provider?: ProviderModule;
120
+ payload?: Record<string, unknown>;
121
+ level?: 'debug' | 'info' | 'warn' | 'error';
122
+ },
123
+ ): void {
124
+ recordDebugTrace({
125
+ interactionId: getInteractionId(args),
126
+ category,
127
+ stage,
128
+ level: options.level || 'info',
129
+ sessionId: typeof args?.targetSessionId === 'string' ? args.targetSessionId : options.h.currentSession?.sessionId,
130
+ providerType: options.provider?.type || options.h.currentProviderType || options.h.currentSession?.providerType,
131
+ payload: options.payload,
132
+ });
133
+ }
134
+
106
135
  function callLegacyTextScript(script: ProviderScripts[keyof ProviderScripts] | undefined, text: string): string | null {
107
136
  if (typeof script !== 'function') return null;
108
137
  return (script as LegacyStringScript)(text);
@@ -392,6 +421,16 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
392
421
  if (typeof parsed === 'string') { try { parsed = JSON.parse(parsed); } catch { } }
393
422
  if (parsed && typeof parsed === 'object') {
394
423
  _log(`Extension OK: ${parsed.messages?.length || 0} msgs`);
424
+ traceProviderEvent(args, 'provider', 'extension.read_chat.success', {
425
+ h,
426
+ provider,
427
+ payload: {
428
+ method: 'evaluateProviderScript',
429
+ result: evalResult.result,
430
+ parsed,
431
+ messageCount: Array.isArray(parsed.messages) ? parsed.messages.length : 0,
432
+ },
433
+ });
395
434
  h.historyWriter.appendNewMessages(
396
435
  provider?.type || 'unknown_extension',
397
436
  toHistoryPersistedMessages(normalizeReadChatMessages(parsed)),
@@ -404,6 +443,12 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
404
443
  }
405
444
  } catch (e: any) {
406
445
  _log(`Extension error: ${e.message}`);
446
+ traceProviderEvent(args, 'provider', 'extension.read_chat.error', {
447
+ h,
448
+ provider,
449
+ level: 'warn',
450
+ payload: { method: 'evaluateProviderScript', error: e.message },
451
+ });
407
452
  }
408
453
  // Alternative: AgentStreamManager (script fail when)
409
454
  if (h.agentStream) {
@@ -471,22 +516,40 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
471
516
  const script = h.getProviderScript('readChat') || h.getProviderScript('read_chat');
472
517
  if (script) {
473
518
  try {
474
- const result = await cdp.evaluate(script, 50000);
475
- let parsed: any = result;
476
- if (typeof parsed === 'string') { try { parsed = JSON.parse(parsed); } catch { } }
477
- if (parsed && typeof parsed === 'object' && parsed.messages?.length > 0) {
478
- _log(`OK: ${parsed.messages?.length} msgs`);
479
- h.historyWriter.appendNewMessages(
480
- provider?.type || getCurrentProviderType(h, 'unknown_ide'),
481
- toHistoryPersistedMessages(normalizeReadChatMessages(parsed)),
482
- parsed.title,
483
- args?.targetSessionId,
484
- historySessionId,
485
- );
486
- return buildReadChatCommandResult(parsed, args);
519
+ const evalResult = await h.evaluateProviderScript('readChat', undefined, 50000);
520
+ if (evalResult?.result) {
521
+ let parsed: any = evalResult.result;
522
+ if (typeof parsed === 'string') { try { parsed = JSON.parse(parsed); } catch { } }
523
+ if (parsed && typeof parsed === 'object' && parsed.messages?.length > 0) {
524
+ _log(`OK: ${parsed.messages?.length} msgs`);
525
+ traceProviderEvent(args, 'provider', 'ide.read_chat.success', {
526
+ h,
527
+ provider,
528
+ payload: {
529
+ method: 'evaluate',
530
+ result: evalResult.result,
531
+ parsed,
532
+ messageCount: Array.isArray(parsed.messages) ? parsed.messages.length : 0,
533
+ },
534
+ });
535
+ h.historyWriter.appendNewMessages(
536
+ provider?.type || getCurrentProviderType(h, 'unknown_ide'),
537
+ toHistoryPersistedMessages(normalizeReadChatMessages(parsed)),
538
+ parsed.title,
539
+ args?.targetSessionId,
540
+ historySessionId,
541
+ );
542
+ return buildReadChatCommandResult(parsed, args);
543
+ }
487
544
  }
488
545
  } catch (e: any) {
489
546
  LOG.info('Command', `[read_chat] Script error: ${e.message}`);
547
+ traceProviderEvent(args, 'provider', 'ide.read_chat.error', {
548
+ h,
549
+ provider,
550
+ level: 'warn',
551
+ payload: { method: 'evaluate', error: e.message },
552
+ });
490
553
  }
491
554
  }
492
555
 
@@ -30,6 +30,7 @@ import { LOG } from '../logging/logger.js';
30
30
  import { logCommand } from '../logging/command-log.js';
31
31
  import type { CommandLogEntry } from '../logging/command-log.js';
32
32
  import { getRecentLogs, LOG_PATH } from '../logging/logger.js';
33
+ import { createInteractionId, getRecentDebugTrace, recordDebugTrace } from '../logging/debug-trace.js';
33
34
  import { buildSessionEntries } from '../status/builders.js';
34
35
  import { buildMachineInfo, buildStatusSnapshot } from '../status/snapshot.js';
35
36
  import { getSessionCompletionMarker } from '../status/snapshot.js';
@@ -104,6 +105,14 @@ function normalizeCommandSource(source: string): CommandLogEntry['source'] {
104
105
  }
105
106
  }
106
107
 
108
+ function normalizeCommandArgsWithInteractionId(args: any): Record<string, unknown> {
109
+ const base = args && typeof args === 'object' ? { ...args } : {};
110
+ if (typeof base._interactionId !== 'string' || !String(base._interactionId).trim()) {
111
+ base._interactionId = createInteractionId();
112
+ }
113
+ return base;
114
+ }
115
+
107
116
  function toHostedCliRuntimeDescriptor(record: any): HostedCliRuntimeDescriptor | null {
108
117
  if (!record || typeof record !== 'object') return null;
109
118
  const runtimeId = typeof record.sessionId === 'string' ? record.sessionId : '';
@@ -149,18 +158,42 @@ export class DaemonCommandRouter {
149
158
  async execute(cmd: string, args: any, source: string = 'unknown'): Promise<CommandRouterResult> {
150
159
  const cmdStart = Date.now();
151
160
  const logSource = normalizeCommandSource(source);
161
+ const normalizedArgs = normalizeCommandArgsWithInteractionId(args);
162
+ const interactionId = typeof normalizedArgs._interactionId === 'string' ? normalizedArgs._interactionId : undefined;
163
+
164
+ recordDebugTrace({
165
+ interactionId,
166
+ category: 'command',
167
+ stage: 'received',
168
+ level: 'info',
169
+ payload: { cmd, source: logSource },
170
+ });
152
171
 
153
172
  try {
154
173
  // 1. Try daemon-level command
155
- const daemonResult = await this.executeDaemonCommand(cmd, args);
174
+ const daemonResult = await this.executeDaemonCommand(cmd, normalizedArgs);
156
175
  if (daemonResult) {
157
- logCommand({ ts: new Date().toISOString(), cmd, source: logSource, args, success: daemonResult.success, durationMs: Date.now() - cmdStart });
176
+ logCommand({ ts: new Date().toISOString(), cmd, source: logSource, interactionId, args: normalizedArgs, success: daemonResult.success, durationMs: Date.now() - cmdStart });
177
+ recordDebugTrace({
178
+ interactionId,
179
+ category: 'command',
180
+ stage: 'completed',
181
+ level: daemonResult.success ? 'info' : 'warn',
182
+ payload: { cmd, source: logSource, success: daemonResult.success, durationMs: Date.now() - cmdStart },
183
+ });
158
184
  return daemonResult;
159
185
  }
160
186
 
161
187
  // 2. Delegate to DaemonCommandHandler
162
- const handlerResult = await this.deps.commandHandler.handle(cmd, args);
163
- logCommand({ ts: new Date().toISOString(), cmd, source: logSource, args, success: handlerResult.success, durationMs: Date.now() - cmdStart });
188
+ const handlerResult = await this.deps.commandHandler.handle(cmd, normalizedArgs);
189
+ logCommand({ ts: new Date().toISOString(), cmd, source: logSource, interactionId, args: normalizedArgs, success: handlerResult.success, durationMs: Date.now() - cmdStart });
190
+ recordDebugTrace({
191
+ interactionId,
192
+ category: 'command',
193
+ stage: 'completed',
194
+ level: handlerResult.success ? 'info' : 'warn',
195
+ payload: { cmd, source: logSource, success: handlerResult.success, durationMs: Date.now() - cmdStart },
196
+ });
164
197
 
165
198
  // 3. Post-chat command callback
166
199
  if (CHAT_COMMANDS.includes(cmd) && this.deps.onPostChatCommand) {
@@ -169,7 +202,14 @@ export class DaemonCommandRouter {
169
202
 
170
203
  return handlerResult;
171
204
  } catch (e: any) {
172
- logCommand({ ts: new Date().toISOString(), cmd, source: logSource, args, success: false, error: e.message, durationMs: Date.now() - cmdStart });
205
+ logCommand({ ts: new Date().toISOString(), cmd, source: logSource, interactionId, args: normalizedArgs, success: false, error: e.message, durationMs: Date.now() - cmdStart });
206
+ recordDebugTrace({
207
+ interactionId,
208
+ category: 'command',
209
+ stage: 'failed',
210
+ level: 'error',
211
+ payload: { cmd, source: logSource, error: e?.message || String(e), durationMs: Date.now() - cmdStart },
212
+ });
173
213
  throw e;
174
214
  }
175
215
  }
@@ -218,6 +258,16 @@ export class DaemonCommandRouter {
218
258
  }
219
259
  }
220
260
 
261
+ case 'get_debug_trace': {
262
+ const count = parseInt(args?.count) || parseInt(args?.limit) || 100;
263
+ const sinceTs = Number(args?.since) || 0;
264
+ const interactionId = typeof args?.interactionId === 'string' ? args.interactionId : undefined;
265
+ const category = typeof args?.category === 'string' ? args.category : undefined;
266
+ const trace = getRecentDebugTrace({ interactionId, category, limit: count })
267
+ .filter((entry) => !sinceTs || entry.ts > sinceTs);
268
+ return { success: true, trace, count: trace.length };
269
+ }
270
+
221
271
  case 'session_host_get_diagnostics': {
222
272
  if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
223
273
  const diagnostics = await this.deps.sessionHostControl.getDiagnostics({
package/src/index.ts CHANGED
@@ -151,6 +151,23 @@ export {
151
151
  getCurrentDaemonLogPath,
152
152
  } from './logging/logger.js';
153
153
  export type { ScopedLogger, LogLevel, LogEntry } from './logging/logger.js';
154
+ export {
155
+ resolveDebugRuntimeConfig,
156
+ setDebugRuntimeConfig,
157
+ getDebugRuntimeConfig,
158
+ resetDebugRuntimeConfig,
159
+ shouldCollectTraceCategory,
160
+ } from './logging/debug-config.js';
161
+ export type { DebugRuntimeOptions, DebugRuntimeConfig } from './logging/debug-config.js';
162
+ export {
163
+ createDebugTraceStore,
164
+ configureDebugTraceStore,
165
+ recordDebugTrace,
166
+ getRecentDebugTrace,
167
+ clearDebugTrace,
168
+ createInteractionId,
169
+ } from './logging/debug-trace.js';
170
+ export type { DebugTraceEvent, DebugTraceEntry, DebugTraceQuery, DebugTraceStore, DebugTraceLevel } from './logging/debug-trace.js';
154
171
  export { logCommand, getRecentCommands } from './logging/command-log.js';
155
172
 
156
173
  // ── CLI Management ──
@@ -191,6 +208,11 @@ export { NodePtyTransportFactory } from './cli-adapters/pty-transport.js';
191
208
  export type { PtyRuntimeTransport, PtyTransportFactory, PtySpawnOptions } from './cli-adapters/pty-transport.js';
192
209
  export { SessionHostPtyTransportFactory } from './cli-adapters/session-host-transport.js';
193
210
  export type { HostedCliRuntimeDescriptor, CliTransportFactoryParams } from './commands/cli-manager.js';
211
+ export {
212
+ DEFAULT_SESSION_HOST_APP_NAME,
213
+ DEFAULT_STANDALONE_SESSION_HOST_APP_NAME,
214
+ resolveSessionHostAppName,
215
+ } from './session-host/app-name.js';
194
216
  export { ensureSessionHostReady, listHostedCliRuntimes } from './session-host/runtime-support.js';
195
217
  export type { SessionHostEndpoint } from '@adhdev/session-host-core';
196
218
 
@@ -31,6 +31,7 @@ export interface CommandLogEntry {
31
31
  ts: string; // ISO timestamp
32
32
  cmd: string; // command name
33
33
  source: 'ws' | 'p2p' | 'ext' | 'api' | 'standalone' | 'unknown'; // where it came from
34
+ interactionId?: string;
34
35
  args?: Record<string, unknown>; // command arguments (sensitive values masked)
35
36
  success?: boolean; // result
36
37
  error?: string; // error message if failed
@@ -136,6 +137,7 @@ export function logCommand(entry: CommandLogEntry): void {
136
137
  ts: entry.ts,
137
138
  cmd: entry.cmd,
138
139
  src: entry.source,
140
+ ...(entry.interactionId ? { interactionId: entry.interactionId } : {}),
139
141
  ...(entry.args ? { args: maskArgs(entry.args) } : {}),
140
142
  ...(entry.success !== undefined ? { ok: entry.success } : {}),
141
143
  ...(entry.error ? { err: entry.error } : {}),
@@ -161,6 +163,7 @@ export function getRecentCommands(count = 50): CommandLogEntry[] {
161
163
  ts: parsed.ts,
162
164
  cmd: parsed.cmd,
163
165
  source: parsed.src,
166
+ interactionId: parsed.interactionId,
164
167
  args: parsed.args,
165
168
  success: parsed.ok,
166
169
  error: parsed.err,
@@ -0,0 +1,75 @@
1
+ import type { LogLevel } from './logger.js'
2
+
3
+ export interface DebugRuntimeOptions {
4
+ dev?: boolean
5
+ logLevel?: LogLevel
6
+ trace?: boolean
7
+ traceContent?: boolean
8
+ traceBufferSize?: number
9
+ traceCategories?: string[]
10
+ }
11
+
12
+ export interface DebugRuntimeConfig {
13
+ logLevel: LogLevel
14
+ collectDebugTrace: boolean
15
+ traceContent: boolean
16
+ traceBufferSize: number
17
+ traceCategories: string[]
18
+ }
19
+
20
+ const NORMAL_TRACE_BUFFER_SIZE = 200
21
+ const DEV_TRACE_BUFFER_SIZE = 1000
22
+
23
+ const DEFAULT_CONFIG: DebugRuntimeConfig = {
24
+ logLevel: 'info',
25
+ collectDebugTrace: false,
26
+ traceContent: false,
27
+ traceBufferSize: NORMAL_TRACE_BUFFER_SIZE,
28
+ traceCategories: [],
29
+ }
30
+
31
+ let currentConfig: DebugRuntimeConfig = { ...DEFAULT_CONFIG }
32
+
33
+ function normalizeCategories(categories?: string[]): string[] {
34
+ if (!Array.isArray(categories)) return []
35
+ return categories
36
+ .map((category) => String(category || '').trim())
37
+ .filter(Boolean)
38
+ }
39
+
40
+ export function resolveDebugRuntimeConfig(options: DebugRuntimeOptions = {}): DebugRuntimeConfig {
41
+ const dev = options.dev === true
42
+ return {
43
+ logLevel: options.logLevel || (dev ? 'debug' : DEFAULT_CONFIG.logLevel),
44
+ collectDebugTrace: typeof options.trace === 'boolean' ? options.trace : dev,
45
+ traceContent: options.traceContent === true,
46
+ traceBufferSize: Number.isFinite(options.traceBufferSize)
47
+ ? Math.max(10, Math.floor(options.traceBufferSize as number))
48
+ : (dev ? DEV_TRACE_BUFFER_SIZE : DEFAULT_CONFIG.traceBufferSize),
49
+ traceCategories: normalizeCategories(options.traceCategories),
50
+ }
51
+ }
52
+
53
+ export function setDebugRuntimeConfig(config: DebugRuntimeConfig): void {
54
+ currentConfig = {
55
+ ...config,
56
+ traceCategories: normalizeCategories(config.traceCategories),
57
+ traceBufferSize: Math.max(10, Math.floor(config.traceBufferSize || DEFAULT_CONFIG.traceBufferSize)),
58
+ }
59
+ }
60
+
61
+ export function getDebugRuntimeConfig(): DebugRuntimeConfig {
62
+ return { ...currentConfig, traceCategories: [...currentConfig.traceCategories] }
63
+ }
64
+
65
+ export function resetDebugRuntimeConfig(): void {
66
+ currentConfig = { ...DEFAULT_CONFIG }
67
+ }
68
+
69
+ export function shouldCollectTraceCategory(category?: string | null): boolean {
70
+ const config = currentConfig
71
+ if (!config.collectDebugTrace) return false
72
+ if (!category) return true
73
+ if (config.traceCategories.length === 0) return true
74
+ return config.traceCategories.includes(category)
75
+ }
@@ -0,0 +1,130 @@
1
+ import { getDebugRuntimeConfig, shouldCollectTraceCategory } from './debug-config.js'
2
+
3
+ export type DebugTraceLevel = 'debug' | 'info' | 'warn' | 'error'
4
+
5
+ export interface DebugTraceEvent {
6
+ interactionId?: string
7
+ category: string
8
+ stage: string
9
+ level: DebugTraceLevel
10
+ sessionId?: string
11
+ providerType?: string
12
+ payload?: Record<string, unknown>
13
+ }
14
+
15
+ export interface DebugTraceEntry extends DebugTraceEvent {
16
+ id: string
17
+ ts: number
18
+ }
19
+
20
+ export interface DebugTraceStoreOptions {
21
+ enabled: boolean
22
+ capacity: number
23
+ }
24
+
25
+ export interface DebugTraceQuery {
26
+ interactionId?: string
27
+ category?: string
28
+ limit?: number
29
+ }
30
+
31
+ export interface DebugTraceStore {
32
+ record(event: DebugTraceEvent): DebugTraceEntry | null
33
+ list(query?: DebugTraceQuery): DebugTraceEntry[]
34
+ clear(): void
35
+ }
36
+
37
+ function summarizeString(value: string): string {
38
+ return `[${value.length} chars]`
39
+ }
40
+
41
+ function sanitizeTraceValue(value: unknown, traceContent: boolean): unknown {
42
+ if (traceContent) {
43
+ if (Array.isArray(value)) return value.map((entry) => sanitizeTraceValue(entry, traceContent))
44
+ if (value && typeof value === 'object') {
45
+ return Object.fromEntries(
46
+ Object.entries(value as Record<string, unknown>).map(([key, nested]) => [key, sanitizeTraceValue(nested, traceContent)]),
47
+ )
48
+ }
49
+ return value
50
+ }
51
+
52
+ if (typeof value === 'string') return summarizeString(value)
53
+ if (Array.isArray(value)) return value.map((entry) => sanitizeTraceValue(entry, traceContent))
54
+ if (value && typeof value === 'object') {
55
+ return Object.fromEntries(
56
+ Object.entries(value as Record<string, unknown>).map(([key, nested]) => [key, sanitizeTraceValue(nested, traceContent)]),
57
+ )
58
+ }
59
+ return value
60
+ }
61
+
62
+ export function sanitizeTracePayload(payload?: Record<string, unknown>): Record<string, unknown> {
63
+ if (!payload) return {}
64
+ const { traceContent } = getDebugRuntimeConfig()
65
+ return sanitizeTraceValue(payload, traceContent) as Record<string, unknown>
66
+ }
67
+
68
+ function createEntry(event: DebugTraceEvent): DebugTraceEntry {
69
+ return {
70
+ id: `trace_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
71
+ ts: Date.now(),
72
+ ...event,
73
+ payload: sanitizeTracePayload(event.payload),
74
+ }
75
+ }
76
+
77
+ export function createDebugTraceStore(options: DebugTraceStoreOptions): DebugTraceStore {
78
+ const entries: DebugTraceEntry[] = []
79
+ const capacity = Math.max(1, Math.floor(options.capacity || 100))
80
+
81
+ return {
82
+ record(event: DebugTraceEvent): DebugTraceEntry | null {
83
+ if (!options.enabled) return null
84
+ const entry = createEntry(event)
85
+ entries.push(entry)
86
+ if (entries.length > capacity) {
87
+ entries.splice(0, entries.length - capacity)
88
+ }
89
+ return entry
90
+ },
91
+ list(query: DebugTraceQuery = {}): DebugTraceEntry[] {
92
+ const limit = Math.max(1, Math.floor(query.limit || 100))
93
+ return entries
94
+ .filter((entry) => !query.interactionId || entry.interactionId === query.interactionId)
95
+ .filter((entry) => !query.category || entry.category === query.category)
96
+ .slice(-limit)
97
+ .map((entry) => ({ ...entry, payload: entry.payload ? { ...entry.payload } : {} }))
98
+ },
99
+ clear(): void {
100
+ entries.splice(0, entries.length)
101
+ },
102
+ }
103
+ }
104
+
105
+ let globalStore = createDebugTraceStore({ enabled: false, capacity: getDebugRuntimeConfig().traceBufferSize })
106
+
107
+ export function configureDebugTraceStore(): void {
108
+ const config = getDebugRuntimeConfig()
109
+ globalStore = createDebugTraceStore({
110
+ enabled: config.collectDebugTrace,
111
+ capacity: config.traceBufferSize,
112
+ })
113
+ }
114
+
115
+ export function recordDebugTrace(event: DebugTraceEvent): DebugTraceEntry | null {
116
+ if (!shouldCollectTraceCategory(event.category)) return null
117
+ return globalStore.record(event)
118
+ }
119
+
120
+ export function getRecentDebugTrace(query: DebugTraceQuery = {}): DebugTraceEntry[] {
121
+ return globalStore.list(query)
122
+ }
123
+
124
+ export function clearDebugTrace(): void {
125
+ globalStore.clear()
126
+ }
127
+
128
+ export function createInteractionId(prefix = 'ix'): string {
129
+ return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`
130
+ }
@@ -0,0 +1,15 @@
1
+ export const DEFAULT_SESSION_HOST_APP_NAME = 'adhdev'
2
+ export const DEFAULT_STANDALONE_SESSION_HOST_APP_NAME = 'adhdev-standalone'
3
+
4
+ export function resolveSessionHostAppName(options: {
5
+ standalone?: boolean
6
+ env?: NodeJS.ProcessEnv
7
+ } = {}): string {
8
+ const env = options.env || process.env
9
+ const explicit = typeof env.ADHDEV_SESSION_HOST_NAME === 'string'
10
+ ? env.ADHDEV_SESSION_HOST_NAME.trim()
11
+ : ''
12
+
13
+ if (explicit) return explicit
14
+ return options.standalone ? DEFAULT_STANDALONE_SESSION_HOST_APP_NAME : DEFAULT_SESSION_HOST_APP_NAME
15
+ }
@@ -187,6 +187,7 @@ export interface SessionChatTailUpdate extends ReadChatSyncResult {
187
187
  key: string;
188
188
  sessionId: string;
189
189
  historySessionId?: string;
190
+ interactionId?: string;
190
191
  seq: number;
191
192
  timestamp: number;
192
193
  }
@@ -215,6 +216,7 @@ export interface SessionModalUpdate {
215
216
  title?: string;
216
217
  modalMessage?: string;
217
218
  modalButtons?: string[];
219
+ interactionId?: string;
218
220
  seq: number;
219
221
  timestamp: number;
220
222
  }