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
package/dist/credentialIO.js
CHANGED
|
@@ -52,6 +52,12 @@ const os = __importStar(require("os"));
|
|
|
52
52
|
const child_process_1 = require("child_process");
|
|
53
53
|
const claudeProfiles_1 = require("./claudeProfiles");
|
|
54
54
|
exports.KEYCHAIN_SERVICE = 'Claude Code-credentials';
|
|
55
|
+
function quoteSecurityInteractiveArg(value) {
|
|
56
|
+
// `security -i` is not a POSIX shell: adjacent quoted segments do NOT
|
|
57
|
+
// concatenate (so the shell-style '\'' splice splits the token), and
|
|
58
|
+
// backslash escapes the next character even inside single quotes.
|
|
59
|
+
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, `\\'`)}'`;
|
|
60
|
+
}
|
|
55
61
|
function getCredentialsFilePath(configDir) {
|
|
56
62
|
return path.join(configDir ?? path.join(os.homedir(), '.claude'), '.credentials.json');
|
|
57
63
|
}
|
|
@@ -93,16 +99,24 @@ function writeActiveCredentials(credentials, configDir) {
|
|
|
93
99
|
const json = JSON.stringify(credentials);
|
|
94
100
|
JSON.parse(json); // validate round-trip
|
|
95
101
|
if (process.platform === 'darwin') {
|
|
96
|
-
(0,
|
|
102
|
+
const service = (0, claudeProfiles_1.claudeKeychainService)(configDir);
|
|
103
|
+
const account = process.env.USER || 'user';
|
|
104
|
+
const command = [
|
|
97
105
|
'add-generic-password',
|
|
98
106
|
'-U',
|
|
99
107
|
'-s',
|
|
100
|
-
(
|
|
108
|
+
quoteSecurityInteractiveArg(service),
|
|
101
109
|
'-a',
|
|
102
|
-
|
|
110
|
+
quoteSecurityInteractiveArg(account),
|
|
103
111
|
'-w',
|
|
104
|
-
json,
|
|
105
|
-
]
|
|
112
|
+
quoteSecurityInteractiveArg(json),
|
|
113
|
+
].join(' ');
|
|
114
|
+
(0, child_process_1.execFileSync)('security', ['-i'], {
|
|
115
|
+
input: command + '\n',
|
|
116
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
117
|
+
timeout: 4000,
|
|
118
|
+
killSignal: 'SIGKILL',
|
|
119
|
+
});
|
|
106
120
|
return;
|
|
107
121
|
}
|
|
108
122
|
// Linux / WSL / Windows — file-based
|
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,33 @@
|
|
|
1
|
+
import type { ErrorRollup, ErrorRollupEntry } from './aggregation/types';
|
|
2
|
+
export declare const ERROR_HISTORY_SCHEMA_VERSION: 1;
|
|
3
|
+
export declare const MAX_ERROR_HISTORY_SESSIONS = 500;
|
|
4
|
+
export interface ErrorHistorySessionRecord {
|
|
5
|
+
sessionId: string;
|
|
6
|
+
providerId: string;
|
|
7
|
+
project: string;
|
|
8
|
+
endedAt: string;
|
|
9
|
+
failures: ErrorRollupEntry[];
|
|
10
|
+
retryAttempts: number;
|
|
11
|
+
finishReasons: Array<{
|
|
12
|
+
reason: string;
|
|
13
|
+
count: number;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
export interface ErrorHistoryStore {
|
|
17
|
+
schemaVersion: 1;
|
|
18
|
+
sessions: ErrorHistorySessionRecord[];
|
|
19
|
+
lastSaved: string;
|
|
20
|
+
}
|
|
21
|
+
export interface TopFailingTool {
|
|
22
|
+
tool: string;
|
|
23
|
+
failures: number;
|
|
24
|
+
categories: Record<string, number>;
|
|
25
|
+
}
|
|
26
|
+
export declare function appendErrorHistory(identity: {
|
|
27
|
+
sessionId: string;
|
|
28
|
+
providerId: string;
|
|
29
|
+
project: string;
|
|
30
|
+
endedAt?: string;
|
|
31
|
+
}, rollup: ErrorRollup, filePath?: string): Promise<ErrorHistorySessionRecord>;
|
|
32
|
+
export declare function readErrorHistory(filePath?: string): Promise<ErrorHistoryStore>;
|
|
33
|
+
export declare function getTopFailingTools(days?: number, now?: Date, filePath?: string): Promise<TopFailingTool[]>;
|
|
@@ -0,0 +1,95 @@
|
|
|
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.MAX_ERROR_HISTORY_SESSIONS = 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
|
+
exports.MAX_ERROR_HISTORY_SESSIONS = 500;
|
|
45
|
+
function emptyStore() {
|
|
46
|
+
return {
|
|
47
|
+
schemaVersion: exports.ERROR_HISTORY_SCHEMA_VERSION,
|
|
48
|
+
sessions: [],
|
|
49
|
+
lastSaved: new Date().toISOString(),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async function appendErrorHistory(identity, rollup, filePath = (0, paths_1.getGlobalDataPath)('error-history.json')) {
|
|
53
|
+
const record = {
|
|
54
|
+
...identity,
|
|
55
|
+
endedAt: identity.endedAt ?? new Date().toISOString(),
|
|
56
|
+
failures: rollup.byToolCategory,
|
|
57
|
+
retryAttempts: rollup.retryAttempts,
|
|
58
|
+
finishReasons: rollup.finishReasons,
|
|
59
|
+
};
|
|
60
|
+
await (0, atomic_1.updateJsonStoreAtomic)(filePath, emptyStore, (store) => ({
|
|
61
|
+
...store,
|
|
62
|
+
schemaVersion: exports.ERROR_HISTORY_SCHEMA_VERSION,
|
|
63
|
+
sessions: [...(Array.isArray(store.sessions) ? store.sessions : []), record].slice(-exports.MAX_ERROR_HISTORY_SESSIONS),
|
|
64
|
+
lastSaved: record.endedAt,
|
|
65
|
+
}));
|
|
66
|
+
return record;
|
|
67
|
+
}
|
|
68
|
+
async function readErrorHistory(filePath = (0, paths_1.getGlobalDataPath)('error-history.json')) {
|
|
69
|
+
try {
|
|
70
|
+
const parsed = JSON.parse(await fs.promises.readFile(filePath, 'utf8'));
|
|
71
|
+
if (parsed.schemaVersion === exports.ERROR_HISTORY_SCHEMA_VERSION && Array.isArray(parsed.sessions))
|
|
72
|
+
return parsed;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Missing and malformed history are represented as an empty store.
|
|
76
|
+
}
|
|
77
|
+
return emptyStore();
|
|
78
|
+
}
|
|
79
|
+
async function getTopFailingTools(days = 7, now = new Date(), filePath = (0, paths_1.getGlobalDataPath)('error-history.json')) {
|
|
80
|
+
const cutoff = now.getTime() - Math.max(1, days) * 24 * 60 * 60 * 1000;
|
|
81
|
+
const store = await readErrorHistory(filePath);
|
|
82
|
+
const tools = new Map();
|
|
83
|
+
for (const session of store.sessions) {
|
|
84
|
+
const endedAt = Date.parse(session.endedAt);
|
|
85
|
+
if (!Number.isFinite(endedAt) || endedAt < cutoff)
|
|
86
|
+
continue;
|
|
87
|
+
for (const failure of session.failures) {
|
|
88
|
+
const item = tools.get(failure.tool) ?? { tool: failure.tool, failures: 0, categories: {} };
|
|
89
|
+
item.failures += failure.count;
|
|
90
|
+
item.categories[failure.category] = (item.categories[failure.category] ?? 0) + failure.count;
|
|
91
|
+
tools.set(failure.tool, item);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return Array.from(tools.values()).sort((left, right) => right.failures - left.failures);
|
|
95
|
+
}
|
|
@@ -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
|
*
|
|
@@ -8,8 +8,16 @@
|
|
|
8
8
|
* @module extractors/toolCall
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.ToolCallTracker = void 0;
|
|
11
12
|
exports.extractToolCalls = extractToolCalls;
|
|
12
13
|
exports.extractToolCall = extractToolCall;
|
|
14
|
+
const errorTaxonomy_1 = require("./errorTaxonomy");
|
|
15
|
+
function outputString(value) {
|
|
16
|
+
if (value == null)
|
|
17
|
+
return undefined;
|
|
18
|
+
const text = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
|
19
|
+
return text.length > 5000 ? `${text.slice(0, 5000)}\n...(truncated)` : text;
|
|
20
|
+
}
|
|
13
21
|
/**
|
|
14
22
|
* Extracts ToolCall objects from a SessionEvent.
|
|
15
23
|
*
|
|
@@ -40,17 +48,88 @@ function extractToolCalls(event) {
|
|
|
40
48
|
if (block && typeof block === 'object' && 'type' in block && block.type === 'tool_use') {
|
|
41
49
|
const toolBlock = block;
|
|
42
50
|
if (toolBlock.name) {
|
|
51
|
+
const isError = toolBlock.is_error === true || toolBlock.error != null;
|
|
52
|
+
const errorOutput = toolBlock.error ?? toolBlock.output;
|
|
43
53
|
calls.push({
|
|
44
54
|
name: toolBlock.name,
|
|
45
55
|
input: toolBlock.input ?? {},
|
|
46
56
|
timestamp,
|
|
47
57
|
toolUseId: toolBlock.id,
|
|
58
|
+
...(isError
|
|
59
|
+
? {
|
|
60
|
+
isError: true,
|
|
61
|
+
output: outputString(errorOutput),
|
|
62
|
+
errorMessage: (0, errorTaxonomy_1.extractErrorMessage)(errorOutput, toolBlock.name),
|
|
63
|
+
errorCategory: (0, errorTaxonomy_1.categorizeError)(errorOutput),
|
|
64
|
+
}
|
|
65
|
+
: {}),
|
|
48
66
|
});
|
|
49
67
|
}
|
|
50
68
|
}
|
|
51
69
|
}
|
|
52
70
|
return calls;
|
|
53
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Stateful shared extractor that correlates provider-normalized tool uses with
|
|
74
|
+
* their later results. Completed calls always carry the canonical taxonomy.
|
|
75
|
+
*/
|
|
76
|
+
class ToolCallTracker {
|
|
77
|
+
pending = new Map();
|
|
78
|
+
process(event) {
|
|
79
|
+
const completed = [];
|
|
80
|
+
for (const call of extractToolCalls(event)) {
|
|
81
|
+
if (call.toolUseId)
|
|
82
|
+
this.pending.set(call.toolUseId, call);
|
|
83
|
+
else
|
|
84
|
+
completed.push(call);
|
|
85
|
+
}
|
|
86
|
+
const topLevel = extractToolCall(event);
|
|
87
|
+
if (topLevel) {
|
|
88
|
+
const id = event.message?.id;
|
|
89
|
+
if (id) {
|
|
90
|
+
topLevel.toolUseId = id;
|
|
91
|
+
this.pending.set(id, topLevel);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
completed.push(topLevel);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const content = event.message?.content;
|
|
98
|
+
if (!Array.isArray(content))
|
|
99
|
+
return completed;
|
|
100
|
+
for (const block of content) {
|
|
101
|
+
if (!block ||
|
|
102
|
+
typeof block !== 'object' ||
|
|
103
|
+
!('type' in block) ||
|
|
104
|
+
block.type !== 'tool_result') {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const result = block;
|
|
108
|
+
if (!result.tool_use_id)
|
|
109
|
+
continue;
|
|
110
|
+
const call = this.pending.get(result.tool_use_id);
|
|
111
|
+
if (!call)
|
|
112
|
+
continue;
|
|
113
|
+
this.pending.delete(result.tool_use_id);
|
|
114
|
+
call.duration =
|
|
115
|
+
typeof result.duration === 'number'
|
|
116
|
+
? result.duration
|
|
117
|
+
: Math.max(0, new Date(event.timestamp).getTime() - call.timestamp.getTime());
|
|
118
|
+
call.isError = result.is_error === true;
|
|
119
|
+
call.output = outputString(result.content);
|
|
120
|
+
if (call.isError) {
|
|
121
|
+
call.errorMessage = (0, errorTaxonomy_1.extractErrorMessage)(result.content, call.name);
|
|
122
|
+
call.errorCategory = (0, errorTaxonomy_1.categorizeError)(result.content);
|
|
123
|
+
}
|
|
124
|
+
completed.push(call);
|
|
125
|
+
}
|
|
126
|
+
return completed;
|
|
127
|
+
}
|
|
128
|
+
reset() {
|
|
129
|
+
this.pending.clear();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
exports.ToolCallTracker = ToolCallTracker;
|
|
54
133
|
/**
|
|
55
134
|
* Extracts a single top-level `tool_use` event.
|
|
56
135
|
*
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface HandoffUrlIdentifiers {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
provider: string;
|
|
4
|
+
projectPath: string;
|
|
5
|
+
}
|
|
6
|
+
/** Renders an external handoff URL using identifiers only. */
|
|
7
|
+
export declare function renderHandoffUrlTemplate(template: string, identifiers: HandoffUrlIdentifiers): string;
|