@adhdev/daemon-core 0.9.82-rc.464 → 0.9.82-rc.466

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,123 @@
1
+ /**
2
+ * CLI provider status/launch pure helpers.
3
+ *
4
+ * Pure move out of cli-provider-instance.ts (no behavior change): the
5
+ * side-effect-free status predicates, the turn-anchored duration computation,
6
+ * the forced-new-session script resolver, the adapter-ready poll, and the lazy
7
+ * node:sqlite DatabaseSync loader. cli-provider-instance re-exports the
8
+ * public symbols (computeTurnAnchoredDurationMs, getForcedNewSessionScriptName,
9
+ * waitForCliAdapterReady) so existing importers/tests keep their path.
10
+ */
11
+
12
+ import * as path from 'path';
13
+ import { createRequire } from 'node:module';
14
+ import type { ProviderModule } from './contracts.js';
15
+
16
+ export function isIdleStatus(value: unknown): boolean {
17
+ const status = typeof value === 'string' ? value.trim().toLowerCase() : '';
18
+ return !status || status === 'idle' || status === 'ready';
19
+ }
20
+
21
+ export function getMessageTime(message: unknown): number {
22
+ if (!message || typeof message !== 'object') return 0;
23
+ const record = message as { receivedAt?: unknown; timestamp?: unknown };
24
+ const value = Number(record.receivedAt ?? record.timestamp ?? 0);
25
+ return Number.isFinite(value) ? value : 0;
26
+ }
27
+
28
+ export function hasNonEmptyCliModalButtons(activeModal: unknown): boolean {
29
+ const buttons = (activeModal as any)?.buttons;
30
+ return Array.isArray(buttons) && buttons.some((button) => String(button || '').trim().length > 0);
31
+ }
32
+
33
+ export function isCliGeneratingLikeStatus(status: unknown): boolean {
34
+ return status === 'generating' || status === 'streaming' || status === 'no_progress' || status === 'long_generating' || status === 'starting';
35
+ }
36
+
37
+ /**
38
+ * NOTIF Defect-2a: the REPORTED short-generating duration, anchored on the IMMUTABLE turn
39
+ * start. generatingStartedAt is reset to 0 on every mid-turn waiting_approval/idle blip and
40
+ * re-armed on the next →generating, so a long turn that blips would otherwise measure only the
41
+ * final 1.5-2.5s sliver. engine.currentTurnStartedAt (set once at onTurnStarted, surviving
42
+ * mid-turn blips until the next turn starts) is preferred; generatingStartedAt is the fallback
43
+ * for turns that never recorded an engine turn start. Returns 0 when neither anchor is set.
44
+ * Pure / unit-testable.
45
+ */
46
+ export function computeTurnAnchoredDurationMs(
47
+ engineTurnStartedAt: number | undefined,
48
+ generatingStartedAt: number,
49
+ now: number,
50
+ ): { durationMs: number; anchor: 'turn-start' | 'generatingStartedAt' | 'none' } {
51
+ const engineStart = typeof engineTurnStartedAt === 'number' && Number.isFinite(engineTurnStartedAt)
52
+ ? engineTurnStartedAt
53
+ : 0;
54
+ if (engineStart > 0) return { durationMs: now - engineStart, anchor: 'turn-start' };
55
+ if (generatingStartedAt > 0) return { durationMs: now - generatingStartedAt, anchor: 'generatingStartedAt' };
56
+ return { durationMs: 0, anchor: 'none' };
57
+ }
58
+
59
+ let CachedDatabaseSync: (new (path: string, options?: { readOnly?: boolean }) => {
60
+ prepare(sql: string): { get(...params: Array<string | number>): unknown };
61
+ close(): void;
62
+ }) | null = null;
63
+
64
+ export function getDatabaseSync() {
65
+ if (CachedDatabaseSync) return CachedDatabaseSync;
66
+ const requireFn = typeof require === 'function'
67
+ ? require
68
+ : createRequire(path.join(process.cwd(), '__adhdev_sqlite_loader__.js'));
69
+ const sqliteModule = requireFn(`node:${'sqlite'}`) as {
70
+ DatabaseSync: typeof CachedDatabaseSync;
71
+ };
72
+ CachedDatabaseSync = sqliteModule.DatabaseSync;
73
+ if (!CachedDatabaseSync) {
74
+ throw new Error('node:sqlite DatabaseSync unavailable');
75
+ }
76
+ return CachedDatabaseSync;
77
+ }
78
+
79
+ export function getForcedNewSessionScriptName(
80
+ provider: ProviderModule | undefined,
81
+ launchMode: 'new' | 'resume' | 'manual',
82
+ ): string | null {
83
+ if (!provider || launchMode !== 'new') return null;
84
+ const resume = provider.resume;
85
+ if (!resume?.supported) return null;
86
+ if (Array.isArray(resume.newSessionArgs) && resume.newSessionArgs.length > 0) return null;
87
+
88
+ const controls = Array.isArray((provider as any).controls) ? (provider as any).controls : [];
89
+ for (const control of controls) {
90
+ if (control?.type !== 'action') continue;
91
+ if (typeof control?.confirmTitle === 'string' && control.confirmTitle.trim()) continue;
92
+ if (typeof control?.confirmMessage === 'string' && control.confirmMessage.trim()) continue;
93
+ if (typeof control?.confirmLabel === 'string' && control.confirmLabel.trim()) continue;
94
+ const invokeScript = typeof control?.invokeScript === 'string' ? control.invokeScript.trim() : '';
95
+ if (!invokeScript) continue;
96
+ const controlId = typeof control?.id === 'string' ? control.id.trim() : '';
97
+ if (controlId === 'new_session' || /^new.?session$/i.test(invokeScript)) {
98
+ return invokeScript;
99
+ }
100
+ }
101
+
102
+ return null;
103
+ }
104
+
105
+ export async function waitForCliAdapterReady(
106
+ adapter: { isReady?: () => boolean; getStatus?: () => { status?: string } },
107
+ options?: { timeoutMs?: number; pollMs?: number },
108
+ ): Promise<void> {
109
+ const timeoutMs = Math.max(100, options?.timeoutMs ?? 15_000);
110
+ const pollMs = Math.max(10, options?.pollMs ?? 50);
111
+ const deadline = Date.now() + timeoutMs;
112
+
113
+ while (Date.now() < deadline) {
114
+ if (adapter?.isReady?.()) return;
115
+ const status = adapter?.getStatus?.()?.status;
116
+ if (status === 'stopped') {
117
+ throw new Error('CLI runtime stopped before it became ready');
118
+ }
119
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
120
+ }
121
+
122
+ throw new Error(`CLI runtime did not become ready within ${timeoutMs}ms`);
123
+ }