sidekick-shared 0.22.0 → 0.23.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/README.md +51 -43
- 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 +32 -1
- package/dist/aggregation/EventAggregator.js +238 -25
- package/dist/aggregation/snapshot.js +1 -1
- package/dist/aggregation/types.d.ts +23 -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/codexProfiles.js +2 -0
- package/dist/context/composer.d.ts +3 -2
- package/dist/context/composer.js +5 -5
- package/dist/doctor.d.ts +55 -0
- package/dist/doctor.js +244 -0
- package/dist/errorHistory.d.ts +32 -0
- package/dist/errorHistory.js +94 -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 +62 -24
- package/dist/parsers/codexParser.js +13 -0
- package/dist/parsers/openCodeParser.js +13 -0
- package/dist/parsers/planExtractor.js +17 -6
- 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/providers/codexDatabase.d.ts +1 -11
- package/dist/providers/openCodeDatabase.d.ts +1 -31
- package/dist/providers/openCodeDatabase.js +9 -8
- package/dist/providers/types.d.ts +1 -0
- package/dist/readers/decisions.d.ts +2 -1
- package/dist/readers/decisions.js +2 -4
- 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 +2 -4
- package/dist/readers/plans.js +1 -1
- package/dist/readers/tasks.d.ts +2 -1
- package/dist/readers/tasks.js +2 -4
- 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 +77 -0
- package/dist/statusline/index.d.ts +7 -0
- package/dist/statusline/index.js +13 -0
- package/dist/types/codex.d.ts +2 -0
- package/dist/types/historicalData.d.ts +35 -1
- package/dist/types/historicalData.js +6 -2
- package/dist/types/observedSessionV1.d.ts +70 -0
- package/dist/types/observedSessionV1.js +185 -0
- package/dist/types/opencode.d.ts +1 -0
- package/dist/types/sessionEvent.d.ts +18 -1
- package/dist/watchers/eventBridge.js +1 -0
- package/dist/watchers/types.d.ts +5 -0
- package/dist/writers/atomic.d.ts +4 -0
- package/dist/writers/atomic.js +113 -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/package.json +5 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scoreSessionQuality = scoreSessionQuality;
|
|
4
|
+
exports.calculateQualityTrend = calculateQualityTrend;
|
|
5
|
+
function awarded(maximum, ratio) {
|
|
6
|
+
return Math.round(maximum * Math.max(0, Math.min(1, ratio)) * 10) / 10;
|
|
7
|
+
}
|
|
8
|
+
function scoreSessionQuality(metrics) {
|
|
9
|
+
const latencyMs = metrics.latencyStats?.avgFirstTokenLatencyMs ?? 0;
|
|
10
|
+
const latencyRatio = latencyMs > 0 ? 1 - Math.min(1, latencyMs / 15_000) : 1;
|
|
11
|
+
const completedCalls = metrics.toolStats.reduce((sum, tool) => sum + tool.completedCount, 0);
|
|
12
|
+
const errorRate = completedCalls > 0 ? metrics.errorRollup.totalFailures / completedCalls : 0;
|
|
13
|
+
const contextTokens = Math.max(1, metrics.tokens.inputTokens + metrics.tokens.cacheReadTokens + metrics.tokens.cacheWriteTokens);
|
|
14
|
+
const evicted = metrics.compactionEvents.reduce((sum, event) => sum + event.tokensReclaimed, 0);
|
|
15
|
+
const contextRatio = 1 - Math.min(1, evicted / contextTokens);
|
|
16
|
+
const start = metrics.sessionStartTime ? Date.parse(metrics.sessionStartTime) : NaN;
|
|
17
|
+
const end = metrics.lastEventTime ? Date.parse(metrics.lastEventTime) : NaN;
|
|
18
|
+
let bypassMs = 0;
|
|
19
|
+
for (let index = 0; index < metrics.permissionModeHistory.length; index++) {
|
|
20
|
+
const change = metrics.permissionModeHistory[index];
|
|
21
|
+
if (change.mode !== 'bypassPermissions')
|
|
22
|
+
continue;
|
|
23
|
+
const from = Date.parse(change.timestamp);
|
|
24
|
+
const to = Date.parse(metrics.permissionModeHistory[index + 1]?.timestamp ?? metrics.lastEventTime ?? '');
|
|
25
|
+
if (Number.isFinite(from) && Number.isFinite(to))
|
|
26
|
+
bypassMs += Math.max(0, to - from);
|
|
27
|
+
}
|
|
28
|
+
const sessionMs = Number.isFinite(start) && Number.isFinite(end) ? Math.max(1, end - start) : 1;
|
|
29
|
+
const bypassRatio = Math.min(1, bypassMs / sessionMs);
|
|
30
|
+
const tasks = Array.from(metrics.taskState.tasks.values()).filter((task) => task.status !== 'deleted');
|
|
31
|
+
const completedTasks = tasks.filter((task) => task.status === 'completed').length;
|
|
32
|
+
const goalGates = tasks.filter((task) => task.isGoalGate);
|
|
33
|
+
const completedGates = goalGates.filter((task) => task.status === 'completed').length;
|
|
34
|
+
const taskRatio = tasks.length > 0 ? completedTasks / tasks.length : 1;
|
|
35
|
+
const goalRatio = goalGates.length > 0 ? completedGates / goalGates.length : taskRatio;
|
|
36
|
+
const outcomeRatio = taskRatio * 0.6 + goalRatio * 0.4;
|
|
37
|
+
const factors = [
|
|
38
|
+
{
|
|
39
|
+
id: 'latency',
|
|
40
|
+
label: 'Response latency',
|
|
41
|
+
contribution: awarded(15, latencyRatio),
|
|
42
|
+
maximum: 15,
|
|
43
|
+
detail: latencyMs > 0 ? `${Math.round(latencyMs)}ms average first token` : 'No latency penalty',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'reliability',
|
|
47
|
+
label: 'Tool reliability',
|
|
48
|
+
contribution: awarded(30, 1 - Math.min(1, errorRate * 2)),
|
|
49
|
+
maximum: 30,
|
|
50
|
+
detail: `${metrics.errorRollup.totalFailures}/${completedCalls} completed tool calls failed`,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'context',
|
|
54
|
+
label: 'Context stability',
|
|
55
|
+
contribution: awarded(15, contextRatio),
|
|
56
|
+
maximum: 15,
|
|
57
|
+
detail: `${metrics.compactionCount} compactions, ${evicted} tokens evicted`,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'permission_safety',
|
|
61
|
+
label: 'Permission safety',
|
|
62
|
+
contribution: awarded(10, 1 - bypassRatio),
|
|
63
|
+
maximum: 10,
|
|
64
|
+
detail: `${Math.round(bypassRatio * 100)}% of observed time bypassing permissions`,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'outcomes',
|
|
68
|
+
label: 'Task outcomes',
|
|
69
|
+
contribution: awarded(30, outcomeRatio),
|
|
70
|
+
maximum: 30,
|
|
71
|
+
detail: `${completedTasks}/${tasks.length} tasks and ${completedGates}/${goalGates.length} goal gates completed`,
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
return {
|
|
75
|
+
score: Math.round(factors.reduce((sum, factor) => sum + factor.contribution, 0)),
|
|
76
|
+
beta: true,
|
|
77
|
+
factors,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function calculateQualityTrend(records, now = new Date()) {
|
|
81
|
+
const end = now.getTime();
|
|
82
|
+
const week = 7 * 24 * 60 * 60 * 1000;
|
|
83
|
+
const eligible = records.filter((record) => record.qualityScore > 0 && Number.isFinite(Date.parse(record.endTime)));
|
|
84
|
+
const current = eligible.filter((record) => {
|
|
85
|
+
const at = Date.parse(record.endTime);
|
|
86
|
+
return at >= end - week && at <= end;
|
|
87
|
+
});
|
|
88
|
+
const previous = eligible.filter((record) => {
|
|
89
|
+
const at = Date.parse(record.endTime);
|
|
90
|
+
return at >= end - 2 * week && at < end - week;
|
|
91
|
+
});
|
|
92
|
+
const average = (items) => items.length > 0
|
|
93
|
+
? Math.round((items.reduce((sum, item) => sum + item.qualityScore, 0) / items.length) * 10) /
|
|
94
|
+
10
|
|
95
|
+
: null;
|
|
96
|
+
const currentWeekAverage = average(current);
|
|
97
|
+
const previousWeekAverage = average(previous);
|
|
98
|
+
const delta = currentWeekAverage != null && previousWeekAverage != null
|
|
99
|
+
? Math.round((currentWeekAverage - previousWeekAverage) * 10) / 10
|
|
100
|
+
: null;
|
|
101
|
+
return {
|
|
102
|
+
currentWeekAverage,
|
|
103
|
+
previousWeekAverage,
|
|
104
|
+
delta,
|
|
105
|
+
direction: delta == null
|
|
106
|
+
? 'insufficient'
|
|
107
|
+
: delta > 0.5
|
|
108
|
+
? 'improving'
|
|
109
|
+
: delta < -0.5
|
|
110
|
+
? 'declining'
|
|
111
|
+
: 'flat',
|
|
112
|
+
currentWeekSessions: current.length,
|
|
113
|
+
previousWeekSessions: previous.length,
|
|
114
|
+
};
|
|
115
|
+
}
|
package/dist/codexProfiles.js
CHANGED
|
@@ -382,6 +382,7 @@ function resolveActiveCodexAccount() {
|
|
|
382
382
|
email: identity.email ?? match?.email ?? match?.metadata?.email,
|
|
383
383
|
label: match?.label,
|
|
384
384
|
providerAccountId: match?.providerAccountId ?? identity.workspaceId,
|
|
385
|
+
registryAccountId: match?.id,
|
|
385
386
|
source: 'live',
|
|
386
387
|
};
|
|
387
388
|
}
|
|
@@ -392,6 +393,7 @@ function resolveActiveCodexAccount() {
|
|
|
392
393
|
email: active.email ?? active.metadata?.email,
|
|
393
394
|
label: active.label,
|
|
394
395
|
providerAccountId: active.providerAccountId,
|
|
396
|
+
registryAccountId: active.id,
|
|
395
397
|
source: 'registry',
|
|
396
398
|
};
|
|
397
399
|
}
|
|
@@ -5,7 +5,8 @@ import type { PersistedTask } from '../types/taskPersistence';
|
|
|
5
5
|
import type { DecisionEntry } from '../types/decisionLog';
|
|
6
6
|
import type { KnowledgeNote } from '../types/knowledgeNote';
|
|
7
7
|
import type { TokenTotals } from '../types/historicalData';
|
|
8
|
-
import type {
|
|
8
|
+
import type { SessionProviderBase, SessionFileStats, ProviderId } from '../providers/types';
|
|
9
|
+
import type { ProjectIdentity } from '../paths';
|
|
9
10
|
export type Fidelity = 'full' | 'compact' | 'brief';
|
|
10
11
|
export interface ContextResult {
|
|
11
12
|
provider: ProviderId;
|
|
@@ -28,4 +29,4 @@ export interface ContextResult {
|
|
|
28
29
|
} | null;
|
|
29
30
|
sessionSummaries: SessionFileStats[];
|
|
30
31
|
}
|
|
31
|
-
export declare function composeContext(
|
|
32
|
+
export declare function composeContext(project: string | ProjectIdentity, fidelity: Fidelity, provider: SessionProviderBase, workspacePath?: string): Promise<ContextResult>;
|
package/dist/context/composer.js
CHANGED
|
@@ -9,14 +9,14 @@ const decisions_1 = require("../readers/decisions");
|
|
|
9
9
|
const notes_1 = require("../readers/notes");
|
|
10
10
|
const history_1 = require("../readers/history");
|
|
11
11
|
const handoff_1 = require("../readers/handoff");
|
|
12
|
-
async function composeContext(
|
|
12
|
+
async function composeContext(project, fidelity, provider, workspacePath) {
|
|
13
13
|
// Fetch all data in parallel
|
|
14
14
|
const [allTasks, allDecisions, allNotes, history, handoff] = await Promise.all([
|
|
15
|
-
(0, tasks_1.readTasks)(
|
|
16
|
-
(0, decisions_1.readDecisions)(
|
|
17
|
-
(0, notes_1.readNotes)(
|
|
15
|
+
(0, tasks_1.readTasks)(project),
|
|
16
|
+
(0, decisions_1.readDecisions)(project),
|
|
17
|
+
(0, notes_1.readNotes)(project),
|
|
18
18
|
(0, history_1.readHistory)(),
|
|
19
|
-
(0, handoff_1.readLatestHandoff)(
|
|
19
|
+
(0, handoff_1.readLatestHandoff)(project),
|
|
20
20
|
]);
|
|
21
21
|
// Get session summaries
|
|
22
22
|
const sessionFiles = workspacePath ? provider.findAllSessions(workspacePath) : [];
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type ActiveAccountStatus } from './accountStatus';
|
|
2
|
+
import { type ProjectIdentity } from './paths';
|
|
3
|
+
import { type ProviderStatusState } from './providerStatus';
|
|
4
|
+
import { type OpenCodeDbRuntimeStatus } from './providers/openCodeDatabase';
|
|
5
|
+
export type HealthCheckStatus = 'ok' | 'warning' | 'error' | 'info';
|
|
6
|
+
export interface HealthCheck {
|
|
7
|
+
id: 'project_slug' | 'session_discovery' | 'opencode_sqlite' | 'accounts' | 'provider_api' | 'deprecated_settings';
|
|
8
|
+
status: HealthCheckStatus;
|
|
9
|
+
title: string;
|
|
10
|
+
message: string;
|
|
11
|
+
repair?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface DeprecatedSetting {
|
|
14
|
+
key: string;
|
|
15
|
+
replacement?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SessionDiagnostics {
|
|
18
|
+
workspacePath: string;
|
|
19
|
+
encodedPath: string;
|
|
20
|
+
expectedSessionDir: string;
|
|
21
|
+
expectedDirExists: boolean;
|
|
22
|
+
discoveredSessionDir: string | null;
|
|
23
|
+
existingProjectDirs: string[];
|
|
24
|
+
similarDirs: string[];
|
|
25
|
+
platform: string;
|
|
26
|
+
subdirectoryMatches: string[];
|
|
27
|
+
selectedSubdirectoryMatch: string | null;
|
|
28
|
+
}
|
|
29
|
+
export interface HealthReport {
|
|
30
|
+
schemaVersion: 1;
|
|
31
|
+
generatedAt: string;
|
|
32
|
+
project: ProjectIdentity;
|
|
33
|
+
status: 'healthy' | 'attention' | 'unhealthy';
|
|
34
|
+
checks: HealthCheck[];
|
|
35
|
+
accounts: ActiveAccountStatus;
|
|
36
|
+
openCode: OpenCodeDbRuntimeStatus;
|
|
37
|
+
providerStatus: {
|
|
38
|
+
claude: ProviderStatusState;
|
|
39
|
+
openai: ProviderStatusState;
|
|
40
|
+
};
|
|
41
|
+
sessions: SessionDiagnostics;
|
|
42
|
+
}
|
|
43
|
+
export interface DoctorOptions {
|
|
44
|
+
cwd?: string;
|
|
45
|
+
deprecatedSettings?: DeprecatedSetting[];
|
|
46
|
+
openCodeStatus?: OpenCodeDbRuntimeStatus;
|
|
47
|
+
fetchStatuses?: () => Promise<{
|
|
48
|
+
claude: ProviderStatusState;
|
|
49
|
+
openai: ProviderStatusState;
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
52
|
+
/** Collects the Claude Code path-resolution detail used by both host surfaces. */
|
|
53
|
+
export declare function getSessionDiagnostics(workspacePath: string): SessionDiagnostics;
|
|
54
|
+
export declare function runDoctor(options?: DoctorOptions): Promise<HealthReport>;
|
|
55
|
+
export declare function formatHealthReport(report: HealthReport): string;
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getSessionDiagnostics = getSessionDiagnostics;
|
|
37
|
+
exports.runDoctor = runDoctor;
|
|
38
|
+
exports.formatHealthReport = formatHealthReport;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const accountStatus_1 = require("./accountStatus");
|
|
43
|
+
const paths_1 = require("./paths");
|
|
44
|
+
const sessionPathResolver_1 = require("./parsers/sessionPathResolver");
|
|
45
|
+
const providerStatus_1 = require("./providerStatus");
|
|
46
|
+
const openCode_1 = require("./providers/openCode");
|
|
47
|
+
const openCodeDatabase_1 = require("./providers/openCodeDatabase");
|
|
48
|
+
/** Collects the Claude Code path-resolution detail used by both host surfaces. */
|
|
49
|
+
function getSessionDiagnostics(workspacePath) {
|
|
50
|
+
const encodedPath = (0, sessionPathResolver_1.encodeWorkspacePath)(workspacePath);
|
|
51
|
+
const expectedSessionDir = (0, sessionPathResolver_1.getSessionDirectory)(workspacePath);
|
|
52
|
+
const projectsDir = path.join(os.homedir(), '.claude', 'projects');
|
|
53
|
+
let existingProjectDirs = [];
|
|
54
|
+
let expectedDirExists = false;
|
|
55
|
+
try {
|
|
56
|
+
if (fs.existsSync(projectsDir)) {
|
|
57
|
+
existingProjectDirs = fs
|
|
58
|
+
.readdirSync(projectsDir)
|
|
59
|
+
.filter((name) => {
|
|
60
|
+
try {
|
|
61
|
+
return fs.statSync(path.join(projectsDir, name)).isDirectory();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
.sort();
|
|
68
|
+
}
|
|
69
|
+
expectedDirExists = fs.existsSync(expectedSessionDir);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Diagnostics remain useful even if a directory cannot be read.
|
|
73
|
+
}
|
|
74
|
+
const discoveredSessionDir = (0, sessionPathResolver_1.discoverSessionDirectory)(workspacePath);
|
|
75
|
+
const workspaceBasename = path.basename(workspacePath).toLowerCase();
|
|
76
|
+
const similarDirs = existingProjectDirs.filter((directory) => {
|
|
77
|
+
const lower = directory.toLowerCase();
|
|
78
|
+
return (lower.includes(workspaceBasename) || workspaceBasename.includes(lower.split('-').pop() || ''));
|
|
79
|
+
});
|
|
80
|
+
const subdirectoryMatches = (0, sessionPathResolver_1.findSubdirectorySessionDirs)(workspacePath);
|
|
81
|
+
return {
|
|
82
|
+
workspacePath,
|
|
83
|
+
encodedPath,
|
|
84
|
+
expectedSessionDir,
|
|
85
|
+
expectedDirExists,
|
|
86
|
+
discoveredSessionDir,
|
|
87
|
+
existingProjectDirs,
|
|
88
|
+
similarDirs,
|
|
89
|
+
platform: process.platform,
|
|
90
|
+
subdirectoryMatches,
|
|
91
|
+
selectedSubdirectoryMatch: subdirectoryMatches.length > 0 ? (0, sessionPathResolver_1.getMostRecentlyActiveSessionDir)(subdirectoryMatches) : null,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function statusUnavailable() {
|
|
95
|
+
return {
|
|
96
|
+
indicator: 'none',
|
|
97
|
+
description: 'Status unavailable',
|
|
98
|
+
affectedComponents: [],
|
|
99
|
+
activeIncident: null,
|
|
100
|
+
updatedAt: new Date().toISOString(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function runDoctor(options = {}) {
|
|
104
|
+
const project = (0, paths_1.resolveProjectIdentity)(options.cwd);
|
|
105
|
+
const sessions = getSessionDiagnostics(project.cwd);
|
|
106
|
+
const accounts = (0, accountStatus_1.getActiveAccountStatus)();
|
|
107
|
+
let openCode = options.openCodeStatus;
|
|
108
|
+
if (!openCode) {
|
|
109
|
+
const database = new openCodeDatabase_1.OpenCodeDatabase((0, openCode_1.getOpenCodeDataDir)());
|
|
110
|
+
database.open();
|
|
111
|
+
openCode = database.getRuntimeStatus();
|
|
112
|
+
}
|
|
113
|
+
const providerStatus = await (options.fetchStatuses ??
|
|
114
|
+
(async () => {
|
|
115
|
+
const [claude, openai] = await Promise.all([(0, providerStatus_1.fetchProviderStatus)(), (0, providerStatus_1.fetchOpenAIStatus)()]);
|
|
116
|
+
return { claude, openai };
|
|
117
|
+
}))().catch(() => ({ claude: statusUnavailable(), openai: statusUnavailable() }));
|
|
118
|
+
const checks = [];
|
|
119
|
+
checks.push(project.hasLegacyMismatch
|
|
120
|
+
? {
|
|
121
|
+
id: 'project_slug',
|
|
122
|
+
status: 'warning',
|
|
123
|
+
title: 'Project path resolves to a different slug',
|
|
124
|
+
message: `Legacy ${project.legacySlug}; canonical ${project.canonicalSlug}.`,
|
|
125
|
+
repair: `Open the resolved path (${project.resolvedCwd}) and merge any legacy project stores into the canonical slug. Sidekick readers keep the legacy fallback.`,
|
|
126
|
+
}
|
|
127
|
+
: {
|
|
128
|
+
id: 'project_slug',
|
|
129
|
+
status: 'ok',
|
|
130
|
+
title: 'Project identity',
|
|
131
|
+
message: `Canonical slug: ${project.canonicalSlug}`,
|
|
132
|
+
});
|
|
133
|
+
checks.push(sessions.discoveredSessionDir
|
|
134
|
+
? {
|
|
135
|
+
id: 'session_discovery',
|
|
136
|
+
status: 'ok',
|
|
137
|
+
title: 'Session discovery',
|
|
138
|
+
message: `Found Claude Code sessions at ${sessions.discoveredSessionDir}.`,
|
|
139
|
+
}
|
|
140
|
+
: {
|
|
141
|
+
id: 'session_discovery',
|
|
142
|
+
status: 'warning',
|
|
143
|
+
title: 'Session discovery',
|
|
144
|
+
message: `No Claude Code session directory found for ${project.cwd}.`,
|
|
145
|
+
repair: `Start Claude Code in this project, or inspect ${sessions.expectedSessionDir}.`,
|
|
146
|
+
});
|
|
147
|
+
const openCodeCheck = openCode.available
|
|
148
|
+
? {
|
|
149
|
+
id: 'opencode_sqlite',
|
|
150
|
+
status: 'ok',
|
|
151
|
+
title: 'OpenCode database',
|
|
152
|
+
message: 'OpenCode database and sqlite3 are available.',
|
|
153
|
+
}
|
|
154
|
+
: openCode.kind === 'db_missing'
|
|
155
|
+
? {
|
|
156
|
+
id: 'opencode_sqlite',
|
|
157
|
+
status: 'info',
|
|
158
|
+
title: 'OpenCode database',
|
|
159
|
+
message: 'No OpenCode database was found; this is expected if OpenCode is not used.',
|
|
160
|
+
}
|
|
161
|
+
: {
|
|
162
|
+
id: 'opencode_sqlite',
|
|
163
|
+
status: 'warning',
|
|
164
|
+
title: 'OpenCode database',
|
|
165
|
+
message: openCode.message ?? `OpenCode database unavailable (${openCode.kind}).`,
|
|
166
|
+
repair: openCode.kind === 'sqlite_missing'
|
|
167
|
+
? 'Install sqlite3 and ensure the sqlite3 executable is on PATH, then rerun sidekick doctor.'
|
|
168
|
+
: 'Make sqlite3 executable and verify the OpenCode database is readable.',
|
|
169
|
+
};
|
|
170
|
+
checks.push(openCodeCheck);
|
|
171
|
+
checks.push({
|
|
172
|
+
id: 'accounts',
|
|
173
|
+
status: accounts.ok ? 'ok' : 'warning',
|
|
174
|
+
title: 'Accounts and credentials',
|
|
175
|
+
message: accounts.ok
|
|
176
|
+
? `Detected ${[accounts.claude.present && 'Claude', accounts.codex.present && 'Codex'].filter(Boolean).join(' and ')} credentials.`
|
|
177
|
+
: (accounts.error ?? 'No Claude or Codex credentials were detected.'),
|
|
178
|
+
...(!accounts.ok
|
|
179
|
+
? { repair: 'Sign in with claude or codex, then rerun sidekick doctor.' }
|
|
180
|
+
: {}),
|
|
181
|
+
});
|
|
182
|
+
const unavailableProviders = Object.entries(providerStatus)
|
|
183
|
+
.filter(([, status]) => status.description === 'Status unavailable')
|
|
184
|
+
.map(([name]) => name);
|
|
185
|
+
const degradedProviders = Object.entries(providerStatus)
|
|
186
|
+
.filter(([, status]) => status.indicator !== 'none')
|
|
187
|
+
.map(([name]) => name);
|
|
188
|
+
checks.push({
|
|
189
|
+
id: 'provider_api',
|
|
190
|
+
status: degradedProviders.length > 0 ? 'warning' : unavailableProviders.length > 0 ? 'info' : 'ok',
|
|
191
|
+
title: 'Provider API status',
|
|
192
|
+
message: degradedProviders.length > 0
|
|
193
|
+
? `Provider incidents reported for: ${degradedProviders.join(', ')}.`
|
|
194
|
+
: unavailableProviders.length > 0
|
|
195
|
+
? `Status pages unavailable for: ${unavailableProviders.join(', ')}.`
|
|
196
|
+
: 'Claude and OpenAI status pages report normal operation.',
|
|
197
|
+
});
|
|
198
|
+
const deprecatedSettings = options.deprecatedSettings ?? [];
|
|
199
|
+
checks.push({
|
|
200
|
+
id: 'deprecated_settings',
|
|
201
|
+
status: deprecatedSettings.length > 0 ? 'warning' : 'ok',
|
|
202
|
+
title: 'Deprecated settings',
|
|
203
|
+
message: deprecatedSettings.length > 0
|
|
204
|
+
? deprecatedSettings
|
|
205
|
+
.map((setting) => `${setting.key}${setting.replacement ? ` → ${setting.replacement}` : ''}`)
|
|
206
|
+
.join(', ')
|
|
207
|
+
: 'No deprecated Sidekick settings detected.',
|
|
208
|
+
...(deprecatedSettings.length > 0
|
|
209
|
+
? {
|
|
210
|
+
repair: 'Move each configured value to its replacement and remove the deprecated setting.',
|
|
211
|
+
}
|
|
212
|
+
: {}),
|
|
213
|
+
});
|
|
214
|
+
const status = checks.some((check) => check.status === 'error')
|
|
215
|
+
? 'unhealthy'
|
|
216
|
+
: checks.some((check) => check.status === 'warning')
|
|
217
|
+
? 'attention'
|
|
218
|
+
: 'healthy';
|
|
219
|
+
return {
|
|
220
|
+
schemaVersion: 1,
|
|
221
|
+
generatedAt: new Date().toISOString(),
|
|
222
|
+
project,
|
|
223
|
+
status,
|
|
224
|
+
checks,
|
|
225
|
+
accounts,
|
|
226
|
+
openCode,
|
|
227
|
+
providerStatus,
|
|
228
|
+
sessions,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function formatHealthReport(report) {
|
|
232
|
+
const icon = { ok: '✓', warning: '!', error: '✕', info: '·' };
|
|
233
|
+
const lines = [
|
|
234
|
+
`Sidekick Doctor — ${report.status}`,
|
|
235
|
+
`Project: ${report.project.resolvedCwd}`,
|
|
236
|
+
'',
|
|
237
|
+
];
|
|
238
|
+
for (const check of report.checks) {
|
|
239
|
+
lines.push(`${icon[check.status]} ${check.title}: ${check.message}`);
|
|
240
|
+
if (check.repair)
|
|
241
|
+
lines.push(` Repair: ${check.repair}`);
|
|
242
|
+
}
|
|
243
|
+
return lines.join('\n');
|
|
244
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ErrorRollup, ErrorRollupEntry } from './aggregation/types';
|
|
2
|
+
export declare const ERROR_HISTORY_SCHEMA_VERSION: 1;
|
|
3
|
+
export interface ErrorHistorySessionRecord {
|
|
4
|
+
sessionId: string;
|
|
5
|
+
providerId: string;
|
|
6
|
+
project: string;
|
|
7
|
+
endedAt: string;
|
|
8
|
+
failures: ErrorRollupEntry[];
|
|
9
|
+
retryAttempts: number;
|
|
10
|
+
finishReasons: Array<{
|
|
11
|
+
reason: string;
|
|
12
|
+
count: number;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export interface ErrorHistoryStore {
|
|
16
|
+
schemaVersion: 1;
|
|
17
|
+
sessions: ErrorHistorySessionRecord[];
|
|
18
|
+
lastSaved: string;
|
|
19
|
+
}
|
|
20
|
+
export interface TopFailingTool {
|
|
21
|
+
tool: string;
|
|
22
|
+
failures: number;
|
|
23
|
+
categories: Record<string, number>;
|
|
24
|
+
}
|
|
25
|
+
export declare function appendErrorHistory(identity: {
|
|
26
|
+
sessionId: string;
|
|
27
|
+
providerId: string;
|
|
28
|
+
project: string;
|
|
29
|
+
endedAt?: string;
|
|
30
|
+
}, rollup: ErrorRollup, filePath?: string): Promise<ErrorHistorySessionRecord>;
|
|
31
|
+
export declare function readErrorHistory(filePath?: string): Promise<ErrorHistoryStore>;
|
|
32
|
+
export declare function getTopFailingTools(days?: number, now?: Date, filePath?: string): Promise<TopFailingTool[]>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ERROR_HISTORY_SCHEMA_VERSION = void 0;
|
|
37
|
+
exports.appendErrorHistory = appendErrorHistory;
|
|
38
|
+
exports.readErrorHistory = readErrorHistory;
|
|
39
|
+
exports.getTopFailingTools = getTopFailingTools;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const paths_1 = require("./paths");
|
|
42
|
+
const atomic_1 = require("./writers/atomic");
|
|
43
|
+
exports.ERROR_HISTORY_SCHEMA_VERSION = 1;
|
|
44
|
+
function emptyStore() {
|
|
45
|
+
return {
|
|
46
|
+
schemaVersion: exports.ERROR_HISTORY_SCHEMA_VERSION,
|
|
47
|
+
sessions: [],
|
|
48
|
+
lastSaved: new Date().toISOString(),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async function appendErrorHistory(identity, rollup, filePath = (0, paths_1.getGlobalDataPath)('error-history.json')) {
|
|
52
|
+
const record = {
|
|
53
|
+
...identity,
|
|
54
|
+
endedAt: identity.endedAt ?? new Date().toISOString(),
|
|
55
|
+
failures: rollup.byToolCategory,
|
|
56
|
+
retryAttempts: rollup.retryAttempts,
|
|
57
|
+
finishReasons: rollup.finishReasons,
|
|
58
|
+
};
|
|
59
|
+
await (0, atomic_1.updateJsonStoreAtomic)(filePath, emptyStore, (store) => ({
|
|
60
|
+
...store,
|
|
61
|
+
schemaVersion: exports.ERROR_HISTORY_SCHEMA_VERSION,
|
|
62
|
+
sessions: [...store.sessions, record],
|
|
63
|
+
lastSaved: record.endedAt,
|
|
64
|
+
}));
|
|
65
|
+
return record;
|
|
66
|
+
}
|
|
67
|
+
async function readErrorHistory(filePath = (0, paths_1.getGlobalDataPath)('error-history.json')) {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(await fs.promises.readFile(filePath, 'utf8'));
|
|
70
|
+
if (parsed.schemaVersion === exports.ERROR_HISTORY_SCHEMA_VERSION && Array.isArray(parsed.sessions))
|
|
71
|
+
return parsed;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Missing and malformed history are represented as an empty store.
|
|
75
|
+
}
|
|
76
|
+
return emptyStore();
|
|
77
|
+
}
|
|
78
|
+
async function getTopFailingTools(days = 7, now = new Date(), filePath = (0, paths_1.getGlobalDataPath)('error-history.json')) {
|
|
79
|
+
const cutoff = now.getTime() - Math.max(1, days) * 24 * 60 * 60 * 1000;
|
|
80
|
+
const store = await readErrorHistory(filePath);
|
|
81
|
+
const tools = new Map();
|
|
82
|
+
for (const session of store.sessions) {
|
|
83
|
+
const endedAt = Date.parse(session.endedAt);
|
|
84
|
+
if (!Number.isFinite(endedAt) || endedAt < cutoff)
|
|
85
|
+
continue;
|
|
86
|
+
for (const failure of session.failures) {
|
|
87
|
+
const item = tools.get(failure.tool) ?? { tool: failure.tool, failures: 0, categories: {} };
|
|
88
|
+
item.failures += failure.count;
|
|
89
|
+
item.categories[failure.category] = (item.categories[failure.category] ?? 0) + failure.count;
|
|
90
|
+
tools.set(failure.tool, item);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return Array.from(tools.values()).sort((left, right) => right.failures - left.failures);
|
|
94
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Canonical error categories used across providers and Sidekick surfaces. */
|
|
2
|
+
export type ErrorCategory = 'permission' | 'not_found' | 'timeout' | 'syntax' | 'exit_code' | 'tool_error' | 'other';
|
|
3
|
+
/** Classifies provider and tool failures into the stable shared taxonomy. */
|
|
4
|
+
export declare function categorizeError(output: unknown, providerErrorType?: string): ErrorCategory;
|
|
5
|
+
/** Produces the compact error label used by the extension and CLI. */
|
|
6
|
+
export declare function extractErrorMessage(content: unknown, toolName: string): string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.categorizeError = categorizeError;
|
|
4
|
+
exports.extractErrorMessage = extractErrorMessage;
|
|
5
|
+
function stringifyError(value) {
|
|
6
|
+
if (typeof value === 'string')
|
|
7
|
+
return value;
|
|
8
|
+
if (value == null)
|
|
9
|
+
return '';
|
|
10
|
+
try {
|
|
11
|
+
return JSON.stringify(value);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return String(value);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Classifies provider and tool failures into the stable shared taxonomy. */
|
|
18
|
+
function categorizeError(output, providerErrorType) {
|
|
19
|
+
const type = (providerErrorType ?? '').toLowerCase();
|
|
20
|
+
const text = `${type} ${stringifyError(output)}`.toLowerCase();
|
|
21
|
+
if (type === 'autherror' ||
|
|
22
|
+
/permission denied|not permitted|unauthori[sz]ed|forbidden|authentication|access denied/.test(text)) {
|
|
23
|
+
return 'permission';
|
|
24
|
+
}
|
|
25
|
+
if (/not found|no such file|enoent|does not exist|unknown command/.test(text))
|
|
26
|
+
return 'not_found';
|
|
27
|
+
if (/timeout|timed out|deadline exceeded|etimedout/.test(text))
|
|
28
|
+
return 'timeout';
|
|
29
|
+
if (/syntax error|parse error|invalid syntax/.test(text))
|
|
30
|
+
return 'syntax';
|
|
31
|
+
if (/exit[ _-]?code|non[- ]zero exit|process exited/.test(text))
|
|
32
|
+
return 'exit_code';
|
|
33
|
+
if (type === 'apierror' ||
|
|
34
|
+
type === 'outputlengtherror' ||
|
|
35
|
+
/tool_use_error|tool error|output length|rate limit|retry attempt/.test(text)) {
|
|
36
|
+
return 'tool_error';
|
|
37
|
+
}
|
|
38
|
+
return 'other';
|
|
39
|
+
}
|
|
40
|
+
/** Produces the compact error label used by the extension and CLI. */
|
|
41
|
+
function extractErrorMessage(content, toolName) {
|
|
42
|
+
let message = stringifyError(content) || 'Unknown error';
|
|
43
|
+
message = message.replace(/<\/?tool_use_error>/g, '').trim();
|
|
44
|
+
if (message.length > 150)
|
|
45
|
+
message = `${message.slice(0, 147)}...`;
|
|
46
|
+
return `${toolName}: ${message}`;
|
|
47
|
+
}
|
|
@@ -25,6 +25,15 @@ import type { SessionEvent, ToolCall } from '../types/sessionEvent';
|
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
export declare function extractToolCalls(event: SessionEvent): ToolCall[];
|
|
28
|
+
/**
|
|
29
|
+
* Stateful shared extractor that correlates provider-normalized tool uses with
|
|
30
|
+
* their later results. Completed calls always carry the canonical taxonomy.
|
|
31
|
+
*/
|
|
32
|
+
export declare class ToolCallTracker {
|
|
33
|
+
private readonly pending;
|
|
34
|
+
process(event: SessionEvent): ToolCall[];
|
|
35
|
+
reset(): void;
|
|
36
|
+
}
|
|
28
37
|
/**
|
|
29
38
|
* Extracts a single top-level `tool_use` event.
|
|
30
39
|
*
|