sidekick-shared 0.22.0 → 0.23.1
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/README.md +75 -63
- package/dist/accountManager.js +10 -0
- package/dist/accountRegistry.d.ts +2 -0
- package/dist/accountStatus.d.ts +1 -0
- package/dist/accountStatus.js +6 -0
- package/dist/accounts.js +2 -0
- package/dist/aggregation/EventAggregator.d.ts +36 -2
- package/dist/aggregation/EventAggregator.js +259 -33
- package/dist/aggregation/snapshot.d.ts +1 -1
- package/dist/aggregation/snapshot.js +2 -1
- package/dist/aggregation/types.d.ts +25 -1
- package/dist/analytics/codeImpact.d.ts +20 -0
- package/dist/analytics/codeImpact.js +21 -0
- package/dist/analytics/compactionLedger.d.ts +11 -0
- package/dist/analytics/compactionLedger.js +31 -0
- package/dist/analytics/qualityScore.d.ts +26 -0
- package/dist/analytics/qualityScore.js +115 -0
- package/dist/autoSwitch.d.ts +1 -1
- package/dist/autoSwitch.js +14 -3
- package/dist/codexProfiles.js +2 -0
- package/dist/codexQuota.d.ts +2 -0
- package/dist/codexQuota.js +27 -0
- package/dist/codexQuotaWatcher.d.ts +7 -0
- package/dist/codexQuotaWatcher.js +37 -19
- package/dist/context/composer.d.ts +3 -2
- package/dist/context/composer.js +5 -5
- package/dist/context/sessionContext.js +35 -9
- package/dist/credentialIO.js +19 -5
- package/dist/doctor.d.ts +55 -0
- package/dist/doctor.js +244 -0
- package/dist/errorHistory.d.ts +33 -0
- package/dist/errorHistory.js +95 -0
- package/dist/extractors/errorTaxonomy.d.ts +6 -0
- package/dist/extractors/errorTaxonomy.js +47 -0
- package/dist/extractors/toolCall.d.ts +9 -0
- package/dist/extractors/toolCall.js +79 -0
- package/dist/handoffUrl.d.ts +7 -0
- package/dist/handoffUrl.js +26 -0
- package/dist/index.d.ts +32 -9
- package/dist/index.js +64 -24
- package/dist/modelInfo.js +6 -0
- package/dist/multiProviderQuotaService.js +13 -2
- package/dist/parsers/codexParser.js +17 -1
- package/dist/parsers/openCodeParser.js +13 -0
- package/dist/parsers/planExtractor.js +17 -6
- package/dist/parsers/sessionActivityDetector.js +2 -0
- package/dist/parsers/sessionPathResolver.js +19 -16
- package/dist/parsers/subagentScanner.d.ts +1 -1
- package/dist/parsers/subagentScanner.js +4 -5
- package/dist/parsers/subagentTraceParser.js +4 -7
- package/dist/paths.d.ts +10 -0
- package/dist/paths.js +18 -5
- package/dist/projectMigration.d.ts +23 -0
- package/dist/projectMigration.js +95 -0
- package/dist/providerStatus.js +13 -2
- package/dist/providers/claudeCode.js +18 -4
- package/dist/providers/codex.js +42 -13
- package/dist/providers/codexDatabase.d.ts +1 -11
- package/dist/providers/detect.js +32 -3
- package/dist/providers/openCodeDatabase.d.ts +1 -31
- package/dist/providers/openCodeDatabase.js +21 -11
- package/dist/providers/types.d.ts +1 -0
- package/dist/quota.js +8 -0
- package/dist/quotaPoller.js +17 -6
- package/dist/quotaSnapshots.js +80 -26
- package/dist/readers/decisions.d.ts +2 -1
- package/dist/readers/decisions.js +5 -5
- package/dist/readers/handoff.d.ts +2 -1
- package/dist/readers/handoff.js +13 -8
- package/dist/readers/helpers.d.ts +3 -0
- package/dist/readers/helpers.js +12 -0
- package/dist/readers/notes.d.ts +2 -1
- package/dist/readers/notes.js +11 -8
- package/dist/readers/plans.js +3 -5
- package/dist/readers/tasks.d.ts +2 -1
- package/dist/readers/tasks.js +5 -5
- package/dist/report/htmlHelpers.js +6 -1
- package/dist/report/openBrowser.js +3 -3
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +10 -1
- package/dist/schemas/observedSessionV1.d.ts +499 -0
- package/dist/schemas/observedSessionV1.js +69 -0
- package/dist/schemas/sessionEvent.d.ts +13 -0
- package/dist/schemas/sessionEvent.js +19 -0
- package/dist/search/sessionSearch.d.ts +2 -2
- package/dist/search/sessionSearch.js +2 -3
- package/dist/sessionMonitor.d.ts +29 -0
- package/dist/sessionMonitor.js +71 -0
- package/dist/statusline/BurnRateCalculator.d.ts +14 -0
- package/dist/statusline/BurnRateCalculator.js +45 -0
- package/dist/statusline/formatter.d.ts +16 -0
- package/dist/statusline/formatter.js +84 -0
- package/dist/statusline/index.d.ts +7 -0
- package/dist/statusline/index.js +13 -0
- package/dist/terminalSync.js +16 -7
- package/dist/types/codex.d.ts +2 -0
- package/dist/types/historicalData.d.ts +49 -1
- package/dist/types/historicalData.js +28 -2
- package/dist/types/observedSessionV1.d.ts +70 -0
- package/dist/types/observedSessionV1.js +186 -0
- package/dist/types/opencode.d.ts +1 -0
- package/dist/types/sessionEvent.d.ts +19 -1
- package/dist/watchers/eventBridge.js +1 -0
- package/dist/watchers/factory.js +14 -3
- package/dist/watchers/jsonlTail.js +34 -14
- package/dist/watchers/jsonlWatcher.js +5 -3
- package/dist/watchers/providerReaderWatcher.js +4 -2
- package/dist/watchers/sqliteWatcher.d.ts +2 -0
- package/dist/watchers/sqliteWatcher.js +27 -14
- package/dist/watchers/types.d.ts +5 -0
- package/dist/writers/atomic.d.ts +4 -0
- package/dist/writers/atomic.js +200 -0
- package/dist/writers/decisions.d.ts +12 -0
- package/dist/writers/decisions.js +88 -0
- package/dist/writers/notes.d.ts +8 -0
- package/dist/writers/notes.js +82 -0
- package/dist/writers/tasks.d.ts +6 -0
- package/dist/writers/tasks.js +100 -0
- package/dist/zaiQuotaWatcher.js +6 -0
- package/package.json +5 -1
|
@@ -93,7 +93,7 @@ const reportedMissing = new Set();
|
|
|
93
93
|
* }
|
|
94
94
|
* ```
|
|
95
95
|
*/
|
|
96
|
-
function scanSubagentDir(sessionDir, sessionId, logger) {
|
|
96
|
+
function scanSubagentDir(sessionDir, sessionId, logger, skipFiles = new Set()) {
|
|
97
97
|
const log = logger || (() => { });
|
|
98
98
|
const subagentsDir = path.join(sessionDir, sessionId, 'subagents');
|
|
99
99
|
const results = [];
|
|
@@ -110,6 +110,8 @@ function scanSubagentDir(sessionDir, sessionId, logger) {
|
|
|
110
110
|
const files = fs.readdirSync(subagentsDir);
|
|
111
111
|
log(`[SubagentScanner] Found ${files.length} files: ${files.join(', ')}`);
|
|
112
112
|
for (const file of files) {
|
|
113
|
+
if (skipFiles.has(file))
|
|
114
|
+
continue;
|
|
113
115
|
const match = file.match(AGENT_FILE_PATTERN);
|
|
114
116
|
if (!match) {
|
|
115
117
|
continue;
|
|
@@ -169,10 +171,7 @@ function parseAgentFile(filePath, agentId, log) {
|
|
|
169
171
|
// Extract token usage from assistant messages
|
|
170
172
|
if (event.type === 'assistant' && event.message?.usage) {
|
|
171
173
|
const usage = event.message.usage;
|
|
172
|
-
inputTokens +=
|
|
173
|
-
(usage.input_tokens || 0) +
|
|
174
|
-
(usage.cache_creation_input_tokens || 0) +
|
|
175
|
-
(usage.cache_read_input_tokens || 0);
|
|
174
|
+
inputTokens += usage.input_tokens || 0;
|
|
176
175
|
outputTokens += usage.output_tokens || 0;
|
|
177
176
|
}
|
|
178
177
|
// Extract agent type and description from Task tool invocation
|
|
@@ -141,7 +141,7 @@ function parseAgentTrace(filePath, agentId) {
|
|
|
141
141
|
}
|
|
142
142
|
// Format tool summary
|
|
143
143
|
let toolSummary;
|
|
144
|
-
if (sessionEvent.
|
|
144
|
+
if (sessionEvent.tool) {
|
|
145
145
|
toolSummary = (0, toolSummary_1.formatToolSummary)(sessionEvent.tool.name, sessionEvent.tool.input);
|
|
146
146
|
}
|
|
147
147
|
events.push({
|
|
@@ -155,10 +155,7 @@ function parseAgentTrace(filePath, agentId) {
|
|
|
155
155
|
// Extract token usage
|
|
156
156
|
if (raw.type === 'assistant' && raw.message?.usage) {
|
|
157
157
|
const usage = raw.message.usage;
|
|
158
|
-
inputTokens +=
|
|
159
|
-
(usage.input_tokens || 0) +
|
|
160
|
-
(usage.cache_creation_input_tokens || 0) +
|
|
161
|
-
(usage.cache_read_input_tokens || 0);
|
|
158
|
+
inputTokens += usage.input_tokens || 0;
|
|
162
159
|
outputTokens += usage.output_tokens || 0;
|
|
163
160
|
}
|
|
164
161
|
// Extract tool calls
|
|
@@ -251,7 +248,7 @@ function linkTraces(traces) {
|
|
|
251
248
|
for (const match of matches) {
|
|
252
249
|
const childId = match[1];
|
|
253
250
|
const child = traceById.get(childId);
|
|
254
|
-
if (child && child !== trace) {
|
|
251
|
+
if (child && child !== trace && !child.parentToolUseId) {
|
|
255
252
|
child.parentToolUseId = trace.agentId;
|
|
256
253
|
trace.children.push(child);
|
|
257
254
|
}
|
|
@@ -274,7 +271,7 @@ function linkTraces(traces) {
|
|
|
274
271
|
continue; // Already linked
|
|
275
272
|
for (const traceEvent of potential.events) {
|
|
276
273
|
const evt = traceEvent.event;
|
|
277
|
-
if (evt.type === '
|
|
274
|
+
if (evt.type === 'assistant' && evt.tool?.name === 'Task') {
|
|
278
275
|
const taskTime = new Date(evt.timestamp).getTime();
|
|
279
276
|
// Within 2 seconds before child start
|
|
280
277
|
if (taskTime <= childStart && childStart - taskTime < 2000) {
|
package/dist/paths.d.ts
CHANGED
|
@@ -30,6 +30,16 @@ export declare function encodeWorkspacePath(workspacePath: string): string;
|
|
|
30
30
|
* Resolves symlinks, then encodes for use as a filename.
|
|
31
31
|
*/
|
|
32
32
|
export declare function getProjectSlug(cwd?: string): string;
|
|
33
|
+
export interface ProjectIdentity {
|
|
34
|
+
cwd: string;
|
|
35
|
+
resolvedCwd: string;
|
|
36
|
+
canonicalSlug: string;
|
|
37
|
+
legacySlug: string;
|
|
38
|
+
candidates: string[];
|
|
39
|
+
hasLegacyMismatch: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** Resolves the canonical project identity used by all new readers and writers. */
|
|
42
|
+
export declare function resolveProjectIdentity(cwd?: string): ProjectIdentity;
|
|
33
43
|
/**
|
|
34
44
|
* Derives a project slug WITHOUT resolving symlinks.
|
|
35
45
|
* Matches the VS Code extension's behavior (encodeWorkspacePath on raw path).
|
package/dist/paths.js
CHANGED
|
@@ -43,6 +43,7 @@ exports.getProjectDataPath = getProjectDataPath;
|
|
|
43
43
|
exports.getGlobalDataPath = getGlobalDataPath;
|
|
44
44
|
exports.encodeWorkspacePath = encodeWorkspacePath;
|
|
45
45
|
exports.getProjectSlug = getProjectSlug;
|
|
46
|
+
exports.resolveProjectIdentity = resolveProjectIdentity;
|
|
46
47
|
exports.getProjectSlugRaw = getProjectSlugRaw;
|
|
47
48
|
const fs = __importStar(require("fs"));
|
|
48
49
|
const path = __importStar(require("path"));
|
|
@@ -86,15 +87,28 @@ function encodeWorkspacePath(workspacePath) {
|
|
|
86
87
|
* Resolves symlinks, then encodes for use as a filename.
|
|
87
88
|
*/
|
|
88
89
|
function getProjectSlug(cwd) {
|
|
89
|
-
|
|
90
|
+
return resolveProjectIdentity(cwd).canonicalSlug;
|
|
91
|
+
}
|
|
92
|
+
/** Resolves the canonical project identity used by all new readers and writers. */
|
|
93
|
+
function resolveProjectIdentity(cwd) {
|
|
94
|
+
const dir = path.resolve(cwd || process.cwd());
|
|
90
95
|
let resolved;
|
|
91
96
|
try {
|
|
92
97
|
resolved = fs.realpathSync(dir);
|
|
93
98
|
}
|
|
94
99
|
catch {
|
|
95
|
-
resolved =
|
|
100
|
+
resolved = dir;
|
|
96
101
|
}
|
|
97
|
-
|
|
102
|
+
const canonicalSlug = encodeWorkspacePath(resolved);
|
|
103
|
+
const legacySlug = encodeWorkspacePath(dir);
|
|
104
|
+
return {
|
|
105
|
+
cwd: dir,
|
|
106
|
+
resolvedCwd: resolved,
|
|
107
|
+
canonicalSlug,
|
|
108
|
+
legacySlug,
|
|
109
|
+
candidates: canonicalSlug === legacySlug ? [canonicalSlug] : [canonicalSlug, legacySlug],
|
|
110
|
+
hasLegacyMismatch: canonicalSlug !== legacySlug,
|
|
111
|
+
};
|
|
98
112
|
}
|
|
99
113
|
/**
|
|
100
114
|
* Derives a project slug WITHOUT resolving symlinks.
|
|
@@ -102,6 +116,5 @@ function getProjectSlug(cwd) {
|
|
|
102
116
|
* Use this when reading data written by the extension.
|
|
103
117
|
*/
|
|
104
118
|
function getProjectSlugRaw(cwd) {
|
|
105
|
-
|
|
106
|
-
return encodeWorkspacePath(path.resolve(dir));
|
|
119
|
+
return resolveProjectIdentity(cwd).legacySlug;
|
|
107
120
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview One-time migration of legacy raw-slug project stores.
|
|
3
|
+
*
|
|
4
|
+
* 0.23.0 switched all readers and writers to the canonical
|
|
5
|
+
* (symlink-resolved) project slug. Workspaces whose literal path differs
|
|
6
|
+
* from its realpath still hold pre-0.23 data under the legacy raw slug;
|
|
7
|
+
* this helper copies those stores to the canonical slug so they stay
|
|
8
|
+
* visible without any manual merge.
|
|
9
|
+
*
|
|
10
|
+
* @module projectMigration
|
|
11
|
+
*/
|
|
12
|
+
import { type ProjectIdentity } from './paths';
|
|
13
|
+
/**
|
|
14
|
+
* Copies legacy raw-slug project stores to the canonical slug, once.
|
|
15
|
+
*
|
|
16
|
+
* Only copies when the canonical file does not exist yet, so it can never
|
|
17
|
+
* overwrite newer data; the legacy file is kept as a read-only fallback.
|
|
18
|
+
* Best-effort per store — a failed copy leaves that store on the legacy
|
|
19
|
+
* read path.
|
|
20
|
+
*
|
|
21
|
+
* @returns The canonical paths that were created (empty when none).
|
|
22
|
+
*/
|
|
23
|
+
export declare function migrateLegacyProjectStores(cwdOrIdentity: string | ProjectIdentity): string[];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview One-time migration of legacy raw-slug project stores.
|
|
4
|
+
*
|
|
5
|
+
* 0.23.0 switched all readers and writers to the canonical
|
|
6
|
+
* (symlink-resolved) project slug. Workspaces whose literal path differs
|
|
7
|
+
* from its realpath still hold pre-0.23 data under the legacy raw slug;
|
|
8
|
+
* this helper copies those stores to the canonical slug so they stay
|
|
9
|
+
* visible without any manual merge.
|
|
10
|
+
*
|
|
11
|
+
* @module projectMigration
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.migrateLegacyProjectStores = migrateLegacyProjectStores;
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const paths_1 = require("./paths");
|
|
51
|
+
/** Project-scoped JSON store subdomains shared by the extension and CLI. */
|
|
52
|
+
const PROJECT_STORE_SUBDOMAINS = [
|
|
53
|
+
'tasks',
|
|
54
|
+
'decisions',
|
|
55
|
+
'notifications',
|
|
56
|
+
'knowledge-notes',
|
|
57
|
+
'plans',
|
|
58
|
+
];
|
|
59
|
+
/**
|
|
60
|
+
* Copies legacy raw-slug project stores to the canonical slug, once.
|
|
61
|
+
*
|
|
62
|
+
* Only copies when the canonical file does not exist yet, so it can never
|
|
63
|
+
* overwrite newer data; the legacy file is kept as a read-only fallback.
|
|
64
|
+
* Best-effort per store — a failed copy leaves that store on the legacy
|
|
65
|
+
* read path.
|
|
66
|
+
*
|
|
67
|
+
* @returns The canonical paths that were created (empty when none).
|
|
68
|
+
*/
|
|
69
|
+
function migrateLegacyProjectStores(cwdOrIdentity) {
|
|
70
|
+
const identity = typeof cwdOrIdentity === 'string' ? (0, paths_1.resolveProjectIdentity)(cwdOrIdentity) : cwdOrIdentity;
|
|
71
|
+
if (!identity.hasLegacyMismatch)
|
|
72
|
+
return [];
|
|
73
|
+
const pairs = PROJECT_STORE_SUBDOMAINS.map((subdomain) => [
|
|
74
|
+
(0, paths_1.getProjectDataPath)(identity.legacySlug, subdomain),
|
|
75
|
+
(0, paths_1.getProjectDataPath)(identity.canonicalSlug, subdomain),
|
|
76
|
+
]);
|
|
77
|
+
pairs.push([
|
|
78
|
+
path.join((0, paths_1.getConfigDir)(), 'handoffs', `${identity.legacySlug}-latest.md`),
|
|
79
|
+
path.join((0, paths_1.getConfigDir)(), 'handoffs', `${identity.canonicalSlug}-latest.md`),
|
|
80
|
+
]);
|
|
81
|
+
const migrated = [];
|
|
82
|
+
for (const [legacyPath, canonicalPath] of pairs) {
|
|
83
|
+
try {
|
|
84
|
+
if (!fs.existsSync(legacyPath) || fs.existsSync(canonicalPath))
|
|
85
|
+
continue;
|
|
86
|
+
fs.mkdirSync(path.dirname(canonicalPath), { recursive: true });
|
|
87
|
+
fs.copyFileSync(legacyPath, canonicalPath, fs.constants.COPYFILE_EXCL);
|
|
88
|
+
migrated.push(canonicalPath);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Best effort: an unmigrated store falls back to the legacy read path.
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return migrated;
|
|
95
|
+
}
|
package/dist/providerStatus.js
CHANGED
|
@@ -24,7 +24,7 @@ function fallbackState() {
|
|
|
24
24
|
*/
|
|
25
25
|
async function fetchStatusPage(baseUrl) {
|
|
26
26
|
try {
|
|
27
|
-
const statusRes = await
|
|
27
|
+
const statusRes = await fetchWithTimeout(`${baseUrl}/api/v2/status.json`);
|
|
28
28
|
if (!statusRes.ok)
|
|
29
29
|
return fallbackState();
|
|
30
30
|
const statusData = await statusRes.json();
|
|
@@ -36,7 +36,7 @@ async function fetchStatusPage(baseUrl) {
|
|
|
36
36
|
return { indicator, description, affectedComponents: [], activeIncident: null, updatedAt };
|
|
37
37
|
}
|
|
38
38
|
// Degraded — fetch summary for components + incidents
|
|
39
|
-
const summaryRes = await
|
|
39
|
+
const summaryRes = await fetchWithTimeout(`${baseUrl}/api/v2/summary.json`);
|
|
40
40
|
if (!summaryRes.ok) {
|
|
41
41
|
return { indicator, description, affectedComponents: [], activeIncident: null, updatedAt };
|
|
42
42
|
}
|
|
@@ -61,6 +61,17 @@ async function fetchStatusPage(baseUrl) {
|
|
|
61
61
|
return fallbackState();
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
async function fetchWithTimeout(url) {
|
|
65
|
+
const controller = new AbortController();
|
|
66
|
+
const timeout = setTimeout(() => controller.abort(), 10_000);
|
|
67
|
+
timeout.unref();
|
|
68
|
+
try {
|
|
69
|
+
return await fetch(url, { signal: controller.signal });
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
clearTimeout(timeout);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
64
75
|
/**
|
|
65
76
|
* Fetch current Claude API status from status.claude.com.
|
|
66
77
|
*
|
|
@@ -46,6 +46,7 @@ exports.ClaudeCodeProvider = void 0;
|
|
|
46
46
|
const fs = __importStar(require("fs"));
|
|
47
47
|
const os = __importStar(require("os"));
|
|
48
48
|
const path = __importStar(require("path"));
|
|
49
|
+
const string_decoder_1 = require("string_decoder");
|
|
49
50
|
const sessionContext_1 = require("../context/sessionContext");
|
|
50
51
|
const jsonl_1 = require("../parsers/jsonl");
|
|
51
52
|
const sessionPathResolver_1 = require("../parsers/sessionPathResolver");
|
|
@@ -97,6 +98,7 @@ class ClaudeCodeReader {
|
|
|
97
98
|
filePosition = 0;
|
|
98
99
|
events = [];
|
|
99
100
|
_wasTruncated = false;
|
|
101
|
+
decoder = new string_decoder_1.StringDecoder('utf8');
|
|
100
102
|
constructor(sessionPath) {
|
|
101
103
|
this.sessionPath = sessionPath;
|
|
102
104
|
this.parser = new jsonl_1.JsonlParser({
|
|
@@ -120,6 +122,7 @@ class ClaudeCodeReader {
|
|
|
120
122
|
this._wasTruncated = true;
|
|
121
123
|
this.filePosition = 0;
|
|
122
124
|
this.parser.reset();
|
|
125
|
+
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
123
126
|
}
|
|
124
127
|
// No new content
|
|
125
128
|
if (currentSize <= this.filePosition) {
|
|
@@ -129,11 +132,16 @@ class ClaudeCodeReader {
|
|
|
129
132
|
const fd = fs.openSync(this.sessionPath, 'r');
|
|
130
133
|
const bufferSize = currentSize - this.filePosition;
|
|
131
134
|
const buffer = Buffer.alloc(bufferSize);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
let bytesRead = 0;
|
|
136
|
+
try {
|
|
137
|
+
bytesRead = fs.readSync(fd, buffer, 0, bufferSize, this.filePosition);
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
fs.closeSync(fd);
|
|
141
|
+
}
|
|
142
|
+
const chunk = this.decoder.write(buffer.subarray(0, bytesRead));
|
|
135
143
|
this.parser.processChunk(chunk);
|
|
136
|
-
this.filePosition
|
|
144
|
+
this.filePosition += bytesRead;
|
|
137
145
|
}
|
|
138
146
|
catch (error) {
|
|
139
147
|
console.error(`ClaudeCodeReader: error reading ${this.sessionPath}: ${error}`);
|
|
@@ -147,13 +155,18 @@ class ClaudeCodeReader {
|
|
|
147
155
|
reset() {
|
|
148
156
|
this.filePosition = 0;
|
|
149
157
|
this.parser.reset();
|
|
158
|
+
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
150
159
|
this._wasTruncated = false;
|
|
151
160
|
}
|
|
152
161
|
exists() {
|
|
153
162
|
return fs.existsSync(this.sessionPath);
|
|
154
163
|
}
|
|
155
164
|
flush() {
|
|
165
|
+
const finalChunk = this.decoder.end();
|
|
166
|
+
if (finalChunk)
|
|
167
|
+
this.parser.processChunk(finalChunk);
|
|
156
168
|
this.parser.flush();
|
|
169
|
+
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
157
170
|
}
|
|
158
171
|
getPosition() {
|
|
159
172
|
return this.filePosition;
|
|
@@ -161,6 +174,7 @@ class ClaudeCodeReader {
|
|
|
161
174
|
seekTo(position) {
|
|
162
175
|
this.filePosition = position;
|
|
163
176
|
this.parser.reset();
|
|
177
|
+
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
164
178
|
}
|
|
165
179
|
wasTruncated() {
|
|
166
180
|
return this._wasTruncated;
|
package/dist/providers/codex.js
CHANGED
|
@@ -46,6 +46,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
46
46
|
exports.CodexProvider = void 0;
|
|
47
47
|
const fs = __importStar(require("fs"));
|
|
48
48
|
const path = __importStar(require("path"));
|
|
49
|
+
const string_decoder_1 = require("string_decoder");
|
|
49
50
|
const EventAggregator_1 = require("../aggregation/EventAggregator");
|
|
50
51
|
const sessionContext_1 = require("../context/sessionContext");
|
|
51
52
|
const codexParser_1 = require("../parsers/codexParser");
|
|
@@ -131,6 +132,23 @@ function findRolloutFiles(dir) {
|
|
|
131
132
|
}
|
|
132
133
|
return results;
|
|
133
134
|
}
|
|
135
|
+
function hasRolloutFile(dir) {
|
|
136
|
+
try {
|
|
137
|
+
if (!fs.existsSync(dir))
|
|
138
|
+
return false;
|
|
139
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
140
|
+
const fullPath = path.join(dir, entry.name);
|
|
141
|
+
if (entry.isDirectory() && hasRolloutFile(fullPath))
|
|
142
|
+
return true;
|
|
143
|
+
if (entry.isFile() && isRolloutFile(entry.name))
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
134
152
|
function findRolloutFilesInConfiguredHomes() {
|
|
135
153
|
const results = [];
|
|
136
154
|
const seen = new Set();
|
|
@@ -148,7 +166,7 @@ function findRolloutFilesInConfiguredHomes() {
|
|
|
148
166
|
function getSessionsDir() {
|
|
149
167
|
const dirs = getSessionsDirs();
|
|
150
168
|
for (const dir of dirs) {
|
|
151
|
-
if (
|
|
169
|
+
if (hasRolloutFile(dir)) {
|
|
152
170
|
return dir;
|
|
153
171
|
}
|
|
154
172
|
}
|
|
@@ -430,6 +448,7 @@ class CodexReader {
|
|
|
430
448
|
filePosition = 0;
|
|
431
449
|
lineBuffer = '';
|
|
432
450
|
_wasTruncated = false;
|
|
451
|
+
decoder = new string_decoder_1.StringDecoder('utf8');
|
|
433
452
|
constructor(rolloutPath, onContextWindowLimit, onRateLimits, onTokenUsage) {
|
|
434
453
|
this.rolloutPath = rolloutPath;
|
|
435
454
|
this.onContextWindowLimit = onContextWindowLimit;
|
|
@@ -455,6 +474,7 @@ class CodexReader {
|
|
|
455
474
|
this.filePosition = 0;
|
|
456
475
|
this.lineBuffer = '';
|
|
457
476
|
this.parser.reset();
|
|
477
|
+
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
458
478
|
}
|
|
459
479
|
// No new content
|
|
460
480
|
if (currentSize <= this.filePosition)
|
|
@@ -463,10 +483,15 @@ class CodexReader {
|
|
|
463
483
|
const fd = fs.openSync(this.rolloutPath, 'r');
|
|
464
484
|
const bufferSize = currentSize - this.filePosition;
|
|
465
485
|
const buffer = Buffer.alloc(bufferSize);
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
486
|
+
let bytesRead = 0;
|
|
487
|
+
try {
|
|
488
|
+
bytesRead = fs.readSync(fd, buffer, 0, bufferSize, this.filePosition);
|
|
489
|
+
}
|
|
490
|
+
finally {
|
|
491
|
+
fs.closeSync(fd);
|
|
492
|
+
}
|
|
493
|
+
const chunk = this.decoder.write(buffer.subarray(0, bytesRead));
|
|
494
|
+
this.filePosition += bytesRead;
|
|
470
495
|
// Process lines
|
|
471
496
|
const text = this.lineBuffer + chunk;
|
|
472
497
|
const lines = text.split('\n');
|
|
@@ -497,11 +522,12 @@ class CodexReader {
|
|
|
497
522
|
// Propagate last token usage snapshot to the provider
|
|
498
523
|
const lastUsage = this.parser.getLastTokenUsage();
|
|
499
524
|
if (lastUsage && this.onTokenUsage) {
|
|
525
|
+
const cacheReadTokens = lastUsage.cached_input_tokens || 0;
|
|
500
526
|
this.onTokenUsage({
|
|
501
|
-
inputTokens: lastUsage.input_tokens || 0,
|
|
527
|
+
inputTokens: Math.max(0, (lastUsage.input_tokens || 0) - cacheReadTokens),
|
|
502
528
|
outputTokens: lastUsage.output_tokens || 0,
|
|
503
529
|
cacheWriteTokens: 0,
|
|
504
|
-
cacheReadTokens
|
|
530
|
+
cacheReadTokens,
|
|
505
531
|
reasoningTokens: lastUsage.reasoning_output_tokens || 0,
|
|
506
532
|
model: this.parser.getCurrentModel() || 'unknown',
|
|
507
533
|
timestamp: new Date(),
|
|
@@ -521,12 +547,14 @@ class CodexReader {
|
|
|
521
547
|
this.filePosition = 0;
|
|
522
548
|
this.lineBuffer = '';
|
|
523
549
|
this.parser.reset();
|
|
550
|
+
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
524
551
|
this._wasTruncated = false;
|
|
525
552
|
}
|
|
526
553
|
exists() {
|
|
527
554
|
return fs.existsSync(this.rolloutPath);
|
|
528
555
|
}
|
|
529
556
|
flush() {
|
|
557
|
+
this.lineBuffer += this.decoder.end();
|
|
530
558
|
// Process any remaining data in the line buffer
|
|
531
559
|
if (this.lineBuffer.trim()) {
|
|
532
560
|
try {
|
|
@@ -537,6 +565,7 @@ class CodexReader {
|
|
|
537
565
|
// Not a complete line yet
|
|
538
566
|
}
|
|
539
567
|
}
|
|
568
|
+
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
540
569
|
}
|
|
541
570
|
getPosition() {
|
|
542
571
|
return this.filePosition;
|
|
@@ -544,6 +573,7 @@ class CodexReader {
|
|
|
544
573
|
seekTo(position) {
|
|
545
574
|
this.filePosition = position;
|
|
546
575
|
this.lineBuffer = '';
|
|
576
|
+
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
547
577
|
}
|
|
548
578
|
wasTruncated() {
|
|
549
579
|
return this._wasTruncated;
|
|
@@ -675,9 +705,10 @@ class CodexProvider {
|
|
|
675
705
|
const db = this.ensureDb();
|
|
676
706
|
if (db) {
|
|
677
707
|
const cwdStats = db.getAllDistinctCwds();
|
|
708
|
+
const sessionsDir = getSessionsDir();
|
|
678
709
|
for (const stat of cwdStats) {
|
|
679
710
|
seenCwds.set(stat.cwd, {
|
|
680
|
-
dir:
|
|
711
|
+
dir: sessionsDir,
|
|
681
712
|
name: stat.cwd,
|
|
682
713
|
encodedName: stat.cwd,
|
|
683
714
|
sessionCount: stat.count,
|
|
@@ -971,11 +1002,9 @@ class CodexProvider {
|
|
|
971
1002
|
return this.lastTokenUsageData;
|
|
972
1003
|
}
|
|
973
1004
|
computeContextSize(usage) {
|
|
974
|
-
//
|
|
975
|
-
//
|
|
976
|
-
|
|
977
|
-
// cacheWrite + cacheRead because Anthropic reports them separately.
|
|
978
|
-
return usage.inputTokens;
|
|
1005
|
+
// Codex usage is normalized to the disjoint TokenUsage shape at the reader
|
|
1006
|
+
// boundary, so reconstruct the full prompt/context from uncached + cached.
|
|
1007
|
+
return usage.inputTokens + usage.cacheReadTokens;
|
|
979
1008
|
}
|
|
980
1009
|
// --- Rate limits (for vscode wrapper to build QuotaState) ---
|
|
981
1010
|
/** Get the last rate limits data received from token_count events. */
|
|
@@ -2,17 +2,7 @@
|
|
|
2
2
|
* Read-only SQLite wrapper for Codex CLI's state database.
|
|
3
3
|
* Ported from sidekick-vscode/src/services/providers/CodexDatabase.ts
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
id: string;
|
|
7
|
-
rollout_path: string;
|
|
8
|
-
cwd: string;
|
|
9
|
-
created_at: number;
|
|
10
|
-
updated_at: number;
|
|
11
|
-
title?: string;
|
|
12
|
-
tokens_used?: number;
|
|
13
|
-
first_user_message?: string;
|
|
14
|
-
forked_from_id?: string;
|
|
15
|
-
}
|
|
5
|
+
import type { CodexDbThread } from '../types/codex';
|
|
16
6
|
export declare class CodexDatabase {
|
|
17
7
|
private readonly dbPath;
|
|
18
8
|
private sqlite3Available;
|
package/dist/providers/detect.js
CHANGED
|
@@ -100,6 +100,35 @@ function getMostRecentMtime(dir) {
|
|
|
100
100
|
return 0;
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
+
function getMostRecentFileMtime(dir, isCandidate, depth) {
|
|
104
|
+
if (depth < 0)
|
|
105
|
+
return 0;
|
|
106
|
+
let latest = 0;
|
|
107
|
+
try {
|
|
108
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
109
|
+
const filePath = path.join(dir, entry);
|
|
110
|
+
try {
|
|
111
|
+
const stats = fs.statSync(filePath);
|
|
112
|
+
if (stats.isDirectory?.()) {
|
|
113
|
+
latest = Math.max(latest, getMostRecentFileMtime(filePath, isCandidate, depth - 1));
|
|
114
|
+
}
|
|
115
|
+
else if (isCandidate(filePath)) {
|
|
116
|
+
latest = Math.max(latest, stats.mtime.getTime());
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
/* skip */
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
return latest;
|
|
128
|
+
}
|
|
129
|
+
function getClaudeActivityMtime(claudeBase) {
|
|
130
|
+
return getMostRecentFileMtime(claudeBase, (filePath) => filePath.endsWith('.jsonl'), 2);
|
|
131
|
+
}
|
|
103
132
|
function getOpenCodeActivityMtime() {
|
|
104
133
|
const dataDir = (0, openCode_1.getOpenCodeDataDir)();
|
|
105
134
|
const dbPath = path.join(dataDir, 'opencode.db');
|
|
@@ -123,7 +152,7 @@ function getCodexActivityMtime() {
|
|
|
123
152
|
const dbMtime = getCodexStateDbMtime(codexHome);
|
|
124
153
|
if (dbMtime > latest)
|
|
125
154
|
latest = dbMtime;
|
|
126
|
-
const sessionsMtime =
|
|
155
|
+
const sessionsMtime = getMostRecentFileMtime(path.join(codexHome, 'sessions'), (filePath) => path.basename(filePath).startsWith('rollout-') && filePath.endsWith('.jsonl'), 4);
|
|
127
156
|
if (sessionsMtime > latest)
|
|
128
157
|
latest = sessionsMtime;
|
|
129
158
|
}
|
|
@@ -151,7 +180,7 @@ function getAllDetectedProviders() {
|
|
|
151
180
|
const hasCodex = codexHomes.some((codexHome) => fs.existsSync(path.join(codexHome, 'sessions')) || hasCodexStateDb(codexHome));
|
|
152
181
|
const available = [];
|
|
153
182
|
if (hasClaude)
|
|
154
|
-
available.push({ id: 'claude-code', mtime:
|
|
183
|
+
available.push({ id: 'claude-code', mtime: getClaudeActivityMtime(claudeBase) });
|
|
155
184
|
if (hasOpenCode)
|
|
156
185
|
available.push({ id: 'opencode', mtime: getOpenCodeActivityMtime() });
|
|
157
186
|
if (hasCodex)
|
|
@@ -172,7 +201,7 @@ function detectProvider(override) {
|
|
|
172
201
|
const hasCodex = codexHomes.some((codexHome) => fs.existsSync(path.join(codexHome, 'sessions')) || hasCodexStateDb(codexHome));
|
|
173
202
|
const available = [];
|
|
174
203
|
if (hasClaude) {
|
|
175
|
-
available.push({ id: 'claude-code', mtime:
|
|
204
|
+
available.push({ id: 'claude-code', mtime: getClaudeActivityMtime(claudeBase) });
|
|
176
205
|
}
|
|
177
206
|
if (hasOpenCode) {
|
|
178
207
|
available.push({ id: 'opencode', mtime: getOpenCodeActivityMtime() });
|
|
@@ -2,37 +2,7 @@
|
|
|
2
2
|
* Read-only SQLite wrapper for OpenCode's database.
|
|
3
3
|
* Ported from sidekick-vscode/src/services/providers/OpenCodeDatabase.ts
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
id: string;
|
|
7
|
-
worktree: string;
|
|
8
|
-
name: string | null;
|
|
9
|
-
sandboxes: string[];
|
|
10
|
-
time_created: number;
|
|
11
|
-
time_updated: number;
|
|
12
|
-
}
|
|
13
|
-
export interface DbSession {
|
|
14
|
-
id: string;
|
|
15
|
-
project_id: string;
|
|
16
|
-
title: string;
|
|
17
|
-
directory: string;
|
|
18
|
-
time_created: number;
|
|
19
|
-
time_updated: number;
|
|
20
|
-
}
|
|
21
|
-
export interface DbMessage {
|
|
22
|
-
id: string;
|
|
23
|
-
session_id: string;
|
|
24
|
-
time_created: number;
|
|
25
|
-
time_updated: number;
|
|
26
|
-
data: string;
|
|
27
|
-
}
|
|
28
|
-
export interface DbPart {
|
|
29
|
-
id: string;
|
|
30
|
-
message_id: string;
|
|
31
|
-
session_id: string;
|
|
32
|
-
time_created: number;
|
|
33
|
-
time_updated: number;
|
|
34
|
-
data: string;
|
|
35
|
-
}
|
|
5
|
+
import type { DbMessage, DbPart, DbProject, DbSession } from '../types/opencode';
|
|
36
6
|
export interface OpenCodeDbRuntimeStatus {
|
|
37
7
|
available: boolean;
|
|
38
8
|
kind: 'available' | 'db_missing' | 'sqlite_missing' | 'sqlite_blocked' | 'query_failed';
|