sidekick-shared 0.27.6 → 0.28.0
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/CHANGELOG.md +28 -0
- package/README.md +58 -18
- package/dist/browser.d.ts +13 -2
- package/dist/browser.js +30 -2
- package/dist/claudePaths.d.ts +11 -0
- package/dist/claudePaths.js +61 -0
- package/dist/claudeProfiles.d.ts +2 -9
- package/dist/claudeProfiles.js +10 -25
- package/dist/codexPaths.d.ts +22 -0
- package/dist/codexPaths.js +67 -0
- package/dist/codexProfiles.d.ts +1 -4
- package/dist/codexProfiles.js +5 -43
- package/dist/doctor.js +2 -2
- package/dist/esm/accountChangeSignal.js +15 -0
- package/dist/esm/accountChanges.js +152 -0
- package/dist/esm/accountHealth.js +288 -0
- package/dist/esm/accountLaunch.js +145 -0
- package/dist/esm/accountLease.js +60 -0
- package/dist/esm/accountManager.js +606 -0
- package/dist/esm/accountRegistry.js +214 -0
- package/dist/esm/accountStatus.js +51 -0
- package/dist/esm/accountSwitch.js +74 -0
- package/dist/esm/accountSync.js +156 -0
- package/dist/esm/accountSyncTypes.js +4 -0
- package/dist/esm/accounts.js +1291 -0
- package/dist/esm/aggregation/EventAggregator.js +2034 -0
- package/dist/esm/aggregation/FrequencyTracker.js +69 -0
- package/dist/esm/aggregation/HeatmapTracker.js +89 -0
- package/dist/esm/aggregation/PatternExtractor.js +168 -0
- package/dist/esm/aggregation/costProvenance.js +31 -0
- package/dist/esm/aggregation/snapshot.js +161 -0
- package/dist/esm/aggregation/types.js +5 -0
- package/dist/esm/analytics/codeImpact.js +18 -0
- package/dist/esm/analytics/compactionLedger.js +37 -0
- package/dist/esm/analytics/qualityScore.js +112 -0
- package/dist/esm/autoSwitch.js +185 -0
- package/dist/esm/browser.js +34 -0
- package/dist/esm/claudeCredentialDoctor.js +239 -0
- package/dist/esm/claudeCredentialLedger.js +91 -0
- package/dist/esm/claudeCredentials.js +111 -0
- package/dist/esm/claudeLiveGuard.js +43 -0
- package/dist/esm/claudePaths.js +22 -0
- package/dist/esm/claudeProfiles.js +142 -0
- package/dist/esm/claudeRefreshLock.js +106 -0
- package/dist/esm/codexAuth.js +101 -0
- package/dist/esm/codexCredentialDoctor.js +265 -0
- package/dist/esm/codexCredentialStore.js +164 -0
- package/dist/esm/codexKeepAlive.js +112 -0
- package/dist/esm/codexPaths.js +172 -0
- package/dist/esm/codexProfiles.js +1251 -0
- package/dist/esm/codexQuota.js +564 -0
- package/dist/esm/codexQuotaWatcher.js +363 -0
- package/dist/esm/context/composer.js +71 -0
- package/dist/esm/context/sessionContext.js +546 -0
- package/dist/esm/credentialIO.js +283 -0
- package/dist/esm/credentials.js +49 -0
- package/dist/esm/doctor.js +305 -0
- package/dist/esm/ensureDefaultAccounts.js +62 -0
- package/dist/esm/errorHistory.js +56 -0
- package/dist/esm/extractors/errorTaxonomy.js +43 -0
- package/dist/esm/extractors/gatherAssets.js +32 -0
- package/dist/esm/extractors/sessionAssets.js +176 -0
- package/dist/esm/extractors/sources/claudeAssets.js +158 -0
- package/dist/esm/extractors/sources/codexAssets.js +234 -0
- package/dist/esm/extractors/tokenUsage.js +41 -0
- package/dist/esm/extractors/toolCall.js +142 -0
- package/dist/esm/failingTools.js +65 -0
- package/dist/esm/fileHead.js +27 -0
- package/dist/esm/formatters/eventHighlighter.js +332 -0
- package/dist/esm/formatters/noiseClassifier.js +209 -0
- package/dist/esm/formatters/sessionDump.js +328 -0
- package/dist/esm/formatters/toolSummary.js +226 -0
- package/dist/esm/formatting.js +82 -0
- package/dist/esm/handoffUrl.js +23 -0
- package/dist/esm/historicalStore.js +305 -0
- package/dist/esm/humanPrompt.js +88 -0
- package/dist/esm/index.js +213 -0
- package/dist/esm/keepAlive.js +189 -0
- package/dist/esm/liveCredentialGuard.js +33 -0
- package/dist/esm/modelAliases.js +51 -0
- package/dist/esm/modelCatalog.js +139 -0
- package/dist/esm/modelContext.js +280 -0
- package/dist/esm/modelInfo.js +832 -0
- package/dist/esm/multiProviderQuotaService.js +450 -0
- package/dist/esm/node.js +46 -0
- package/dist/esm/observedContextWindows.js +165 -0
- package/dist/esm/observedSessionCollector.js +939 -0
- package/dist/esm/parsers/changelogParser.js +71 -0
- package/dist/esm/parsers/codexHistory.js +89 -0
- package/dist/esm/parsers/codexParser.js +906 -0
- package/dist/esm/parsers/debugLogParser.js +126 -0
- package/dist/esm/parsers/jsonl.js +66 -0
- package/dist/esm/parsers/mcpToolName.js +28 -0
- package/dist/esm/parsers/openCodeParser.js +625 -0
- package/dist/esm/parsers/planExtractor.js +335 -0
- package/dist/esm/parsers/sessionActivityDetector.js +246 -0
- package/dist/esm/parsers/sessionPathResolver.js +713 -0
- package/dist/esm/parsers/subagentScanner.js +360 -0
- package/dist/esm/parsers/subagentStatsCache.js +45 -0
- package/dist/esm/parsers/subagentTraceParser.js +325 -0
- package/dist/esm/paths.js +134 -0
- package/dist/esm/peakHours.js +194 -0
- package/dist/esm/phrases.js +1330 -0
- package/dist/esm/pricingCatalog.js +247 -0
- package/dist/esm/processDetection.js +200 -0
- package/dist/esm/projectMigration.js +59 -0
- package/dist/esm/promptHistory.js +1221 -0
- package/dist/esm/providerFailure.js +362 -0
- package/dist/esm/providerIds.js +6 -0
- package/dist/esm/providerQuota.js +1 -0
- package/dist/esm/providerServiceStatus.js +145 -0
- package/dist/esm/providerServiceStatusTypes.js +1 -0
- package/dist/esm/providerStatus.js +117 -0
- package/dist/esm/providers/claudeCode.js +575 -0
- package/dist/esm/providers/claudeListing.js +121 -0
- package/dist/esm/providers/codex.js +1034 -0
- package/dist/esm/providers/codexDatabase.js +297 -0
- package/dist/esm/providers/codexRollouts.js +215 -0
- package/dist/esm/providers/detect.js +185 -0
- package/dist/esm/providers/diagnostics.js +83 -0
- package/dist/esm/providers/directoryListingCache.js +206 -0
- package/dist/esm/providers/factory.js +54 -0
- package/dist/esm/providers/openCode.js +2008 -0
- package/dist/esm/providers/openCodeDatabase.js +419 -0
- package/dist/esm/providers/rolloutWalker.js +252 -0
- package/dist/esm/providers/types.js +12 -0
- package/dist/esm/quota.js +182 -0
- package/dist/esm/quotaHistory.js +364 -0
- package/dist/esm/quotaPoller.js +230 -0
- package/dist/esm/quotaPresentation.js +97 -0
- package/dist/esm/quotaResolver.js +231 -0
- package/dist/esm/quotaSnapshots.js +130 -0
- package/dist/esm/quotaThresholds.js +85 -0
- package/dist/esm/readers/decisions.js +24 -0
- package/dist/esm/readers/handoff.js +20 -0
- package/dist/esm/readers/helpers.js +27 -0
- package/dist/esm/readers/history.js +9 -0
- package/dist/esm/readers/notes.js +48 -0
- package/dist/esm/readers/plans.js +225 -0
- package/dist/esm/readers/tasks.js +19 -0
- package/dist/esm/report/htmlHelpers.js +280 -0
- package/dist/esm/report/htmlReportGenerator.js +704 -0
- package/dist/esm/report/index.js +7 -0
- package/dist/esm/report/logo.js +2 -0
- package/dist/esm/report/openBrowser.js +19 -0
- package/dist/esm/report/sessionReportInputs.js +33 -0
- package/dist/esm/report/transcriptParser.js +83 -0
- package/dist/esm/report/types.js +4 -0
- package/dist/esm/schemas/accountManager.js +204 -0
- package/dist/esm/schemas/accountStatus.js +25 -0
- package/dist/esm/schemas/assistantTurn.js +74 -0
- package/dist/esm/schemas/index.js +25 -0
- package/dist/esm/schemas/observedSessionV1.js +77 -0
- package/dist/esm/schemas/providerFailure.js +37 -0
- package/dist/esm/schemas/quota.js +132 -0
- package/dist/esm/schemas/quotaHistory.js +40 -0
- package/dist/esm/schemas/sessionEvent.js +216 -0
- package/dist/esm/schemas/stateFile.js +75 -0
- package/dist/esm/schemas/tokenEstimation.js +7 -0
- package/dist/esm/schemas/transcript.js +86 -0
- package/dist/esm/schemas/usageNormalization.js +38 -0
- package/dist/esm/search/advancedFilter.js +197 -0
- package/dist/esm/search/sessionSearch.js +66 -0
- package/dist/esm/sessionFingerprint.js +40 -0
- package/dist/esm/sessionMonitor.js +198 -0
- package/dist/esm/sessionPreviews.js +522 -0
- package/dist/esm/sessionPromptHistory.js +254 -0
- package/dist/esm/sessionPromptSignals.js +364 -0
- package/dist/esm/sessionStats.js +160 -0
- package/dist/esm/sessionTokenTotals.js +98 -0
- package/dist/esm/sessionTranscripts.js +49 -0
- package/dist/esm/stateFile.js +119 -0
- package/dist/esm/statusline/BurnRateCalculator.js +40 -0
- package/dist/esm/statusline/claudeStatuslinePayload.js +148 -0
- package/dist/esm/statusline/formatter.js +122 -0
- package/dist/esm/statusline/index.js +8 -0
- package/dist/esm/terminalSync.js +95 -0
- package/dist/esm/testing/credentialGuard.js +197 -0
- package/dist/esm/testing/index.js +12 -0
- package/dist/esm/testing/sessionLogFixtures.js +128 -0
- package/dist/esm/tokenEstimation.js +67 -0
- package/dist/esm/tokenSummary.js +113 -0
- package/dist/esm/toolCategory.js +82 -0
- package/dist/esm/transcript.js +257 -0
- package/dist/esm/turns/assistantTurn.js +373 -0
- package/dist/esm/types/codex.js +23 -0
- package/dist/esm/types/decisionLog.js +5 -0
- package/dist/esm/types/historicalData.js +47 -0
- package/dist/esm/types/knowledgeNote.js +15 -0
- package/dist/esm/types/observedSessionV1.js +257 -0
- package/dist/esm/types/opencode.js +12 -0
- package/dist/esm/types/plan.js +7 -0
- package/dist/esm/types/sessionEvent.js +10 -0
- package/dist/esm/types/taskPersistence.js +12 -0
- package/dist/esm/usage/billingBlocks.js +148 -0
- package/dist/esm/usage/claudeUsageDedupe.js +189 -0
- package/dist/esm/usage/importSessionHistory.js +84 -0
- package/dist/esm/usage/subagentUsage.js +18 -0
- package/dist/esm/usage/usageEvents.js +402 -0
- package/dist/esm/usage/usageReports.js +202 -0
- package/dist/esm/usageNormalization.js +95 -0
- package/dist/esm/watchers/eventBridge.js +219 -0
- package/dist/esm/watchers/factory.js +63 -0
- package/dist/esm/watchers/jsonlTail.js +289 -0
- package/dist/esm/watchers/jsonlWatcher.js +435 -0
- package/dist/esm/watchers/providerReaderWatcher.js +118 -0
- package/dist/esm/watchers/sqliteWatcher.js +285 -0
- package/dist/esm/watchers/types.js +4 -0
- package/dist/esm/workspaceRoots.js +56 -0
- package/dist/esm/writers/atomic.js +375 -0
- package/dist/esm/writers/decisions.js +51 -0
- package/dist/esm/writers/notes.js +46 -0
- package/dist/esm/writers/tasks.js +63 -0
- package/dist/esm/zaiQuota.js +323 -0
- package/dist/esm/zaiQuotaApi.js +271 -0
- package/dist/esm/zaiQuotaWatcher.js +265 -0
- package/dist/fileHead.d.ts +22 -0
- package/dist/fileHead.js +33 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.js +49 -15
- package/dist/node.d.ts +13 -1
- package/dist/node.js +43 -2
- package/dist/parsers/codexHistory.js +2 -2
- package/dist/parsers/codexParser.d.ts +12 -0
- package/dist/parsers/codexParser.js +34 -12
- package/dist/parsers/debugLogParser.js +2 -2
- package/dist/parsers/sessionPathResolver.js +5 -4
- package/dist/promptHistory.d.ts +32 -0
- package/dist/promptHistory.js +115 -59
- package/dist/providerFailure.d.ts +12 -6
- package/dist/providerFailure.js +45 -0
- package/dist/providers/claudeCode.js +7 -99
- package/dist/providers/claudeListing.d.ts +22 -0
- package/dist/providers/claudeListing.js +158 -0
- package/dist/providers/codex.d.ts +3 -1
- package/dist/providers/codex.js +26 -227
- package/dist/providers/codexRollouts.d.ts +44 -0
- package/dist/providers/codexRollouts.js +261 -0
- package/dist/providers/detect.js +4 -4
- package/dist/readers/plans.js +3 -3
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +7 -2
- package/dist/schemas/providerFailure.d.ts +154 -0
- package/dist/schemas/providerFailure.js +40 -0
- package/dist/sessionPromptHistory.d.ts +12 -7
- package/dist/sessionPromptHistory.js +15 -3
- package/dist/sessionPromptSignals.d.ts +9 -0
- package/dist/sessionPromptSignals.js +13 -0
- package/dist/testing/credentialGuard.d.ts +23 -0
- package/dist/testing/credentialGuard.js +233 -0
- package/dist/testing/index.d.ts +14 -0
- package/dist/testing/index.js +22 -0
- package/dist/testing/sessionLogFixtures.d.ts +28 -0
- package/dist/testing/sessionLogFixtures.js +169 -0
- package/dist/tokenEstimation.d.ts +2 -0
- package/dist/tokenEstimation.js +21 -11
- package/dist/toolCategory.d.ts +18 -0
- package/dist/toolCategory.js +86 -0
- package/dist/types/codex.d.ts +4 -0
- package/dist/usage/usageEvents.d.ts +16 -0
- package/dist/usage/usageEvents.js +53 -9
- package/dist/watchers/jsonlTail.d.ts +26 -1
- package/dist/watchers/jsonlTail.js +136 -28
- package/dist/watchers/jsonlWatcher.js +7 -0
- package/dist/watchers/providerReaderWatcher.js +1 -0
- package/dist/watchers/types.d.ts +6 -0
- package/dist/workspaceRoots.d.ts +23 -0
- package/dist/workspaceRoots.js +93 -0
- package/package.json +22 -2
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite session watcher for OpenCode.
|
|
3
|
+
* Polls for new messages/parts using timestamp cursor.
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { OpenCodeDatabase } from '../providers/openCodeDatabase';
|
|
8
|
+
import { normalizeToolName, detectPlanModeFromText } from '../parsers/openCodeParser';
|
|
9
|
+
const DEBOUNCE_MS = 200;
|
|
10
|
+
const POLL_INTERVAL_MS = 2_000;
|
|
11
|
+
function truncate(text, maxLen) {
|
|
12
|
+
const clean = text.replace(/\s+/g, ' ').trim();
|
|
13
|
+
return clean.length > maxLen ? clean.substring(0, maxLen - 3) + '...' : clean;
|
|
14
|
+
}
|
|
15
|
+
function normalizeMessage(msg) {
|
|
16
|
+
const events = [];
|
|
17
|
+
const ts = new Date(msg.time_created).toISOString();
|
|
18
|
+
try {
|
|
19
|
+
const data = JSON.parse(msg.data);
|
|
20
|
+
const role = data.role;
|
|
21
|
+
const content = extractContent(data);
|
|
22
|
+
const msgTokens = data.tokens;
|
|
23
|
+
const tokens = msgTokens
|
|
24
|
+
? { input: msgTokens.input || 0, output: msgTokens.output || 0 }
|
|
25
|
+
: undefined;
|
|
26
|
+
const cost = data.cost || undefined;
|
|
27
|
+
const model = data.modelID || undefined;
|
|
28
|
+
if (role === 'user') {
|
|
29
|
+
events.push({
|
|
30
|
+
providerId: 'opencode',
|
|
31
|
+
type: 'user',
|
|
32
|
+
timestamp: ts,
|
|
33
|
+
summary: content || '(user message)',
|
|
34
|
+
model,
|
|
35
|
+
raw: data,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else if (role === 'assistant') {
|
|
39
|
+
events.push({
|
|
40
|
+
providerId: 'opencode',
|
|
41
|
+
type: 'assistant',
|
|
42
|
+
timestamp: ts,
|
|
43
|
+
summary: content || '(thinking...)',
|
|
44
|
+
tokens,
|
|
45
|
+
cost,
|
|
46
|
+
model,
|
|
47
|
+
raw: data,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
/* skip malformed */
|
|
53
|
+
}
|
|
54
|
+
return events;
|
|
55
|
+
}
|
|
56
|
+
function normalizePart(part) {
|
|
57
|
+
const events = [];
|
|
58
|
+
const ts = new Date(part.time_created).toISOString();
|
|
59
|
+
try {
|
|
60
|
+
const data = JSON.parse(part.data);
|
|
61
|
+
const type = data.type;
|
|
62
|
+
if (type === 'tool' || type === 'tool-invocation') {
|
|
63
|
+
const rawToolName = data.tool || 'unknown';
|
|
64
|
+
const toolName = normalizeToolName(rawToolName);
|
|
65
|
+
const state = data.state;
|
|
66
|
+
const stateStatus = state?.status;
|
|
67
|
+
// tool-invocation with state=result is a tool_result
|
|
68
|
+
const eventType = stateStatus === 'result' ? 'tool_result' : 'tool_use';
|
|
69
|
+
const toolInput = state?.input;
|
|
70
|
+
const input = data.args ? truncate(JSON.stringify(data.args), 80) : '';
|
|
71
|
+
// Include input in raw so PlanExtractor can access raw.input
|
|
72
|
+
const toolRaw = eventType === 'tool_use' ? { ...data, input: toolInput || {} } : data;
|
|
73
|
+
events.push({
|
|
74
|
+
providerId: 'opencode',
|
|
75
|
+
type: eventType,
|
|
76
|
+
timestamp: ts,
|
|
77
|
+
summary: input ? `${toolName} ${input}` : toolName,
|
|
78
|
+
toolName,
|
|
79
|
+
toolInput: input || undefined,
|
|
80
|
+
raw: toolRaw,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
else if (type === 'text') {
|
|
84
|
+
const text = data.text || '';
|
|
85
|
+
if (text) {
|
|
86
|
+
// Wrap raw to include message.content for <proposed_plan> extraction
|
|
87
|
+
events.push({
|
|
88
|
+
providerId: 'opencode',
|
|
89
|
+
type: 'assistant',
|
|
90
|
+
timestamp: ts,
|
|
91
|
+
summary: truncate(text, 200),
|
|
92
|
+
raw: { ...data, message: { content: [{ type: 'text', text }] } },
|
|
93
|
+
});
|
|
94
|
+
// Detect plan mode enter/exit from text heuristics
|
|
95
|
+
const direction = detectPlanModeFromText(text);
|
|
96
|
+
if (direction) {
|
|
97
|
+
const syntheticToolName = direction === 'enter' ? 'EnterPlanMode' : 'ExitPlanMode';
|
|
98
|
+
events.push({
|
|
99
|
+
providerId: 'opencode',
|
|
100
|
+
type: 'tool_use',
|
|
101
|
+
timestamp: ts,
|
|
102
|
+
summary: syntheticToolName,
|
|
103
|
+
toolName: syntheticToolName,
|
|
104
|
+
raw: { input: { source: 'opencode_text_heuristic' } },
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
/* skip malformed */
|
|
112
|
+
}
|
|
113
|
+
return events;
|
|
114
|
+
}
|
|
115
|
+
function extractContent(data) {
|
|
116
|
+
const content = data.content;
|
|
117
|
+
if (typeof content === 'string')
|
|
118
|
+
return truncate(content, 200);
|
|
119
|
+
if (Array.isArray(content)) {
|
|
120
|
+
for (const part of content) {
|
|
121
|
+
if (typeof part.text === 'string')
|
|
122
|
+
return truncate(part.text, 200);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
export class SqliteSessionWatcher {
|
|
128
|
+
_isActive = false;
|
|
129
|
+
lastMessageTime = 0;
|
|
130
|
+
lastPartTime = 0;
|
|
131
|
+
seenMessageVersions = new Map();
|
|
132
|
+
seenPartVersions = new Map();
|
|
133
|
+
fsWatcher = null;
|
|
134
|
+
walWatcher = null;
|
|
135
|
+
pollTimer = null;
|
|
136
|
+
debounceTimer = null;
|
|
137
|
+
db;
|
|
138
|
+
dbPath;
|
|
139
|
+
sessionId;
|
|
140
|
+
callbacks;
|
|
141
|
+
constructor(dbPath, sessionId, callbacks) {
|
|
142
|
+
this.dbPath = dbPath;
|
|
143
|
+
this.sessionId = sessionId;
|
|
144
|
+
this.callbacks = callbacks;
|
|
145
|
+
this.db = new OpenCodeDatabase(path.dirname(dbPath));
|
|
146
|
+
}
|
|
147
|
+
get isActive() {
|
|
148
|
+
return this._isActive;
|
|
149
|
+
}
|
|
150
|
+
/** Seek to a timestamp position. Must be called before start(). */
|
|
151
|
+
seekTo(position) {
|
|
152
|
+
this.lastMessageTime = position;
|
|
153
|
+
this.lastPartTime = position;
|
|
154
|
+
this.seenMessageVersions.clear();
|
|
155
|
+
this.seenPartVersions.clear();
|
|
156
|
+
}
|
|
157
|
+
/** Get the current cursor position (max timestamp seen). */
|
|
158
|
+
getPosition() {
|
|
159
|
+
return Math.max(this.lastMessageTime, this.lastPartTime);
|
|
160
|
+
}
|
|
161
|
+
start(replay) {
|
|
162
|
+
if (this._isActive)
|
|
163
|
+
return;
|
|
164
|
+
this._isActive = true;
|
|
165
|
+
if (!this.db.isAvailable() || !this.db.open()) {
|
|
166
|
+
this.callbacks.onError?.(new Error('SQLite database not available'));
|
|
167
|
+
this._isActive = false;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (replay) {
|
|
171
|
+
// Emit all existing events
|
|
172
|
+
this.pollNewData();
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// Skip to current timestamps
|
|
176
|
+
this.skipToEnd();
|
|
177
|
+
}
|
|
178
|
+
// Watch db and wal files for changes
|
|
179
|
+
this.watchFile(this.dbPath, (w) => {
|
|
180
|
+
this.fsWatcher = w;
|
|
181
|
+
});
|
|
182
|
+
this.watchFile(this.dbPath + '-wal', (w) => {
|
|
183
|
+
this.walWatcher = w;
|
|
184
|
+
});
|
|
185
|
+
// Fallback polling
|
|
186
|
+
this.pollTimer = setInterval(() => {
|
|
187
|
+
if (this._isActive)
|
|
188
|
+
this.pollNewData();
|
|
189
|
+
}, POLL_INTERVAL_MS);
|
|
190
|
+
}
|
|
191
|
+
stop() {
|
|
192
|
+
if (!this._isActive)
|
|
193
|
+
return;
|
|
194
|
+
this._isActive = false;
|
|
195
|
+
if (this.debounceTimer) {
|
|
196
|
+
clearTimeout(this.debounceTimer);
|
|
197
|
+
this.debounceTimer = null;
|
|
198
|
+
}
|
|
199
|
+
if (this.fsWatcher) {
|
|
200
|
+
this.fsWatcher.close();
|
|
201
|
+
this.fsWatcher = null;
|
|
202
|
+
}
|
|
203
|
+
if (this.walWatcher) {
|
|
204
|
+
this.walWatcher.close();
|
|
205
|
+
this.walWatcher = null;
|
|
206
|
+
}
|
|
207
|
+
if (this.pollTimer) {
|
|
208
|
+
clearInterval(this.pollTimer);
|
|
209
|
+
this.pollTimer = null;
|
|
210
|
+
}
|
|
211
|
+
this.db.close();
|
|
212
|
+
}
|
|
213
|
+
watchFile(filePath, setter) {
|
|
214
|
+
try {
|
|
215
|
+
if (!fs.existsSync(filePath))
|
|
216
|
+
return;
|
|
217
|
+
const watcher = fs.watch(filePath, { persistent: false }, () => {
|
|
218
|
+
this.debouncedPoll();
|
|
219
|
+
});
|
|
220
|
+
watcher.on('error', () => {
|
|
221
|
+
/* ignore watch errors */
|
|
222
|
+
});
|
|
223
|
+
setter(watcher);
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
/* fs.watch not available */
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
debouncedPoll() {
|
|
230
|
+
if (this.debounceTimer)
|
|
231
|
+
clearTimeout(this.debounceTimer);
|
|
232
|
+
this.debounceTimer = setTimeout(() => {
|
|
233
|
+
this.pollNewData();
|
|
234
|
+
}, DEBOUNCE_MS);
|
|
235
|
+
}
|
|
236
|
+
skipToEnd() {
|
|
237
|
+
this.lastMessageTime = this.db.getLatestMessageTimeUpdated(this.sessionId);
|
|
238
|
+
this.lastPartTime = this.db.getLatestPartTimeUpdated(this.sessionId);
|
|
239
|
+
if (this.lastMessageTime > 0) {
|
|
240
|
+
for (const message of this.db.getMessagesNewerThan(this.sessionId, this.lastMessageTime - 1)) {
|
|
241
|
+
this.seenMessageVersions.set(message.id, `${message.time_updated}:${message.data}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (this.lastPartTime > 0) {
|
|
245
|
+
for (const part of this.db.getPartsNewerThan(this.sessionId, this.lastPartTime - 1)) {
|
|
246
|
+
this.seenPartVersions.set(part.id, `${part.time_updated}:${part.data}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
pollNewData() {
|
|
251
|
+
if (!this._isActive)
|
|
252
|
+
return;
|
|
253
|
+
try {
|
|
254
|
+
// Query incrementally by update time. Subtracting one millisecond lets us
|
|
255
|
+
// see distinct rows committed at the cursor timestamp; version maps
|
|
256
|
+
// suppress rows whose id/data have already been emitted.
|
|
257
|
+
const messages = this.db.getMessagesNewerThan(this.sessionId, Math.max(-1, this.lastMessageTime - 1));
|
|
258
|
+
const parts = this.db.getPartsNewerThan(this.sessionId, Math.max(-1, this.lastPartTime - 1));
|
|
259
|
+
// Emit new messages
|
|
260
|
+
const newMessages = messages.filter((message) => this.seenMessageVersions.get(message.id) !== `${message.time_updated}:${message.data}`);
|
|
261
|
+
for (const msg of newMessages) {
|
|
262
|
+
const events = normalizeMessage(msg);
|
|
263
|
+
for (const e of events)
|
|
264
|
+
this.callbacks.onEvent(e);
|
|
265
|
+
this.seenMessageVersions.set(msg.id, `${msg.time_updated}:${msg.data}`);
|
|
266
|
+
if (msg.time_updated > this.lastMessageTime)
|
|
267
|
+
this.lastMessageTime = msg.time_updated;
|
|
268
|
+
}
|
|
269
|
+
// Emit new parts
|
|
270
|
+
const newParts = parts.filter((part) => this.seenPartVersions.get(part.id) !== `${part.time_updated}:${part.data}`);
|
|
271
|
+
for (const part of newParts) {
|
|
272
|
+
const events = normalizePart(part);
|
|
273
|
+
for (const e of events)
|
|
274
|
+
this.callbacks.onEvent(e);
|
|
275
|
+
this.seenPartVersions.set(part.id, `${part.time_updated}:${part.data}`);
|
|
276
|
+
if (part.time_updated > this.lastPartTime)
|
|
277
|
+
this.lastPartTime = part.time_updated;
|
|
278
|
+
}
|
|
279
|
+
this.callbacks.onBatchComplete?.();
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
this.callbacks.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace-root expansion shared by the session readers: realpath each root
|
|
3
|
+
* and, optionally, add its git worktree siblings.
|
|
4
|
+
*
|
|
5
|
+
* @module workspaceRoots
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { discoverWorktreeSiblings, resolveWorktreeMainRepo } from './parsers/sessionPathResolver';
|
|
10
|
+
/** Realpath of an absolute path, or null when it does not resolve. */
|
|
11
|
+
export function realpathOrNull(input, cache = new Map()) {
|
|
12
|
+
const cached = cache.get(input);
|
|
13
|
+
if (cached !== undefined)
|
|
14
|
+
return cached;
|
|
15
|
+
let resolved = null;
|
|
16
|
+
try {
|
|
17
|
+
resolved = path.isAbsolute(input) ? fs.realpathSync.native(input) : null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
resolved = null;
|
|
21
|
+
}
|
|
22
|
+
cache.set(input, resolved);
|
|
23
|
+
return resolved;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Realpaths each absolute root and, by default, adds its main checkout and
|
|
27
|
+
* git worktree siblings. Relative and unresolvable roots are dropped.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveWorkspaceRoots(workspacePaths, options = {}, realpaths = new Map()) {
|
|
30
|
+
const includeWorktrees = options.includeWorktrees !== false;
|
|
31
|
+
const roots = new Set();
|
|
32
|
+
const listingRoots = new Set();
|
|
33
|
+
const add = (candidate) => {
|
|
34
|
+
const real = realpathOrNull(candidate, realpaths);
|
|
35
|
+
if (!real)
|
|
36
|
+
return;
|
|
37
|
+
roots.add(real);
|
|
38
|
+
listingRoots.add(real);
|
|
39
|
+
listingRoots.add(path.resolve(candidate));
|
|
40
|
+
};
|
|
41
|
+
for (const workspacePath of workspacePaths) {
|
|
42
|
+
if (typeof workspacePath !== 'string' || !path.isAbsolute(workspacePath))
|
|
43
|
+
continue;
|
|
44
|
+
add(workspacePath);
|
|
45
|
+
if (!includeWorktrees)
|
|
46
|
+
continue;
|
|
47
|
+
const real = realpathOrNull(workspacePath, realpaths);
|
|
48
|
+
if (!real)
|
|
49
|
+
continue;
|
|
50
|
+
const mainRepo = resolveWorktreeMainRepo(real) ?? real;
|
|
51
|
+
add(mainRepo);
|
|
52
|
+
for (const sibling of discoverWorktreeSiblings(mainRepo))
|
|
53
|
+
add(sibling);
|
|
54
|
+
}
|
|
55
|
+
return { roots: [...roots], listingRoots: [...listingRoots] };
|
|
56
|
+
}
|