sidekick-shared 0.21.2 → 0.21.4
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 +60 -45
- package/dist/accountManager.js +9 -7
- package/dist/accountRegistry.d.ts +17 -0
- package/dist/accountRegistry.js +18 -14
- package/dist/accountStatus.js +7 -7
- package/dist/accounts.d.ts +13 -0
- package/dist/accounts.js +110 -25
- package/dist/aggregation/EventAggregator.d.ts +14 -11
- package/dist/aggregation/EventAggregator.js +68 -31
- package/dist/aggregation/PatternExtractor.js +5 -4
- package/dist/autoSwitch.js +6 -12
- package/dist/claudeProfiles.js +3 -3
- package/dist/codexProfiles.d.ts +15 -1
- package/dist/codexProfiles.js +96 -22
- package/dist/codexQuota.js +44 -16
- package/dist/codexQuotaWatcher.js +19 -4
- package/dist/context/composer.js +8 -6
- package/dist/context/sessionContext.js +12 -9
- package/dist/credentialIO.js +10 -8
- package/dist/extractors/sessionAssets.js +9 -1
- package/dist/extractors/sources/claudeAssets.js +7 -1
- package/dist/extractors/sources/codexAssets.js +14 -3
- package/dist/extractors/toolCall.js +1 -4
- package/dist/formatters/eventHighlighter.js +148 -28
- package/dist/formatters/noiseClassifier.js +3 -11
- package/dist/formatters/sessionDump.js +22 -13
- package/dist/formatters/toolSummary.js +1 -3
- package/dist/index.d.ts +26 -26
- package/dist/index.js +10 -4
- package/dist/modelContext.js +10 -8
- package/dist/modelInfo.js +11 -6
- package/dist/multiProviderQuotaService.d.ts +2 -2
- package/dist/multiProviderQuotaService.js +14 -10
- package/dist/parsers/codexParser.js +170 -103
- package/dist/parsers/debugLogParser.js +5 -4
- package/dist/parsers/openCodeParser.js +66 -48
- package/dist/parsers/planExtractor.js +3 -3
- package/dist/parsers/sessionActivityDetector.js +10 -6
- package/dist/parsers/sessionPathResolver.js +29 -34
- package/dist/parsers/subagentScanner.js +21 -20
- package/dist/parsers/subagentTraceParser.js +7 -4
- package/dist/phrases.js +11 -11
- package/dist/providerStatus.js +3 -4
- package/dist/providers/claudeCode.js +3 -1
- package/dist/providers/codex.js +35 -14
- package/dist/providers/codexDatabase.js +14 -5
- package/dist/providers/detect.js +10 -6
- package/dist/providers/openCode.js +91 -63
- package/dist/providers/openCodeDatabase.js +44 -17
- package/dist/quota.d.ts +19 -0
- package/dist/quota.js +50 -8
- package/dist/quotaHistory.js +10 -3
- package/dist/quotaSnapshots.js +2 -2
- package/dist/readers/decisions.js +1 -1
- package/dist/readers/notes.js +5 -3
- package/dist/readers/plans.js +43 -23
- package/dist/readers/tasks.js +1 -1
- package/dist/report/htmlHelpers.js +135 -16
- package/dist/report/htmlReportGenerator.js +38 -12
- package/dist/report/transcriptParser.js +8 -2
- package/dist/schemas/index.d.ts +4 -4
- package/dist/schemas/quota.js +2 -10
- package/dist/schemas/sessionEvent.js +20 -10
- package/dist/search/advancedFilter.js +3 -3
- package/dist/search/sessionSearch.js +10 -4
- package/dist/terminalSync.js +4 -2
- package/dist/turns/assistantTurn.js +5 -1
- package/dist/watchers/eventBridge.js +56 -26
- package/dist/watchers/factory.js +5 -2
- package/dist/watchers/jsonlTail.js +3 -1
- package/dist/watchers/jsonlWatcher.js +128 -52
- package/dist/watchers/providerReaderWatcher.js +3 -1
- package/dist/watchers/sqliteWatcher.js +52 -22
- package/dist/zaiQuota.js +6 -10
- package/dist/zaiQuotaApi.js +13 -5
- package/dist/zaiQuotaWatcher.js +1 -1
- package/package.json +5 -2
|
@@ -82,7 +82,9 @@ function getOpenCodeDataDir() {
|
|
|
82
82
|
candidates.push(path.join(os.homedir(), 'Library', 'Application Support', 'opencode'));
|
|
83
83
|
}
|
|
84
84
|
else if (process.platform === 'win32') {
|
|
85
|
-
candidates.push(path.join(process.env.LOCALAPPDATA ||
|
|
85
|
+
candidates.push(path.join(process.env.LOCALAPPDATA ||
|
|
86
|
+
process.env.APPDATA ||
|
|
87
|
+
path.join(os.homedir(), 'AppData', 'Local'), 'opencode'));
|
|
86
88
|
}
|
|
87
89
|
for (const candidate of candidates) {
|
|
88
90
|
try {
|
|
@@ -99,7 +101,9 @@ function getOpenCodeDataDir() {
|
|
|
99
101
|
return path.join(os.homedir(), 'Library', 'Application Support', 'opencode');
|
|
100
102
|
}
|
|
101
103
|
if (process.platform === 'win32') {
|
|
102
|
-
return path.join(process.env.LOCALAPPDATA ||
|
|
104
|
+
return path.join(process.env.LOCALAPPDATA ||
|
|
105
|
+
process.env.APPDATA ||
|
|
106
|
+
path.join(os.homedir(), 'AppData', 'Local'), 'opencode');
|
|
103
107
|
}
|
|
104
108
|
return path.join(os.homedir(), '.local', 'share', 'opencode');
|
|
105
109
|
}
|
|
@@ -132,7 +136,10 @@ function detectAgentTypeFromDescription(desc) {
|
|
|
132
136
|
if (lower.includes('plan') || lower.includes('architect') || lower.includes('design')) {
|
|
133
137
|
return 'Plan';
|
|
134
138
|
}
|
|
135
|
-
if (lower.includes('task') ||
|
|
139
|
+
if (lower.includes('task') ||
|
|
140
|
+
lower.includes('execute') ||
|
|
141
|
+
lower.includes('implement') ||
|
|
142
|
+
lower.includes('build')) {
|
|
136
143
|
return 'Task';
|
|
137
144
|
}
|
|
138
145
|
return undefined;
|
|
@@ -189,9 +196,7 @@ function extractRoleFromDbMessage(row) {
|
|
|
189
196
|
function extractParentIdFromDbMessage(row) {
|
|
190
197
|
try {
|
|
191
198
|
const data = JSON.parse(row.data);
|
|
192
|
-
return typeof data.parentID === 'string' && data.parentID.length > 0
|
|
193
|
-
? data.parentID
|
|
194
|
-
: null;
|
|
199
|
+
return typeof data.parentID === 'string' && data.parentID.length > 0 ? data.parentID : null;
|
|
195
200
|
}
|
|
196
201
|
catch {
|
|
197
202
|
return null;
|
|
@@ -224,7 +229,7 @@ function resolveProjectIdFromFiles(workspacePath) {
|
|
|
224
229
|
const projectDir = path.join(getStorageDir(), 'project');
|
|
225
230
|
if (!fs.existsSync(projectDir))
|
|
226
231
|
return resolveProjectIdFromGit(workspacePath);
|
|
227
|
-
const files = fs.readdirSync(projectDir).filter(f => f.endsWith('.json'));
|
|
232
|
+
const files = fs.readdirSync(projectDir).filter((f) => f.endsWith('.json'));
|
|
228
233
|
const matches = [];
|
|
229
234
|
for (const file of files) {
|
|
230
235
|
try {
|
|
@@ -263,9 +268,12 @@ function resolveProjectIdFromGit(workspacePath) {
|
|
|
263
268
|
const hash = (0, child_process_1.execSync)('git rev-list --max-parents=0 HEAD', {
|
|
264
269
|
cwd: workspacePath,
|
|
265
270
|
encoding: 'utf-8',
|
|
266
|
-
timeout:
|
|
267
|
-
|
|
268
|
-
|
|
271
|
+
timeout: 4000,
|
|
272
|
+
killSignal: 'SIGKILL',
|
|
273
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
274
|
+
})
|
|
275
|
+
.trim()
|
|
276
|
+
.split('\n')[0];
|
|
269
277
|
if (hash && /^[a-f0-9]+$/i.test(hash)) {
|
|
270
278
|
return hash;
|
|
271
279
|
}
|
|
@@ -325,7 +333,7 @@ class OpenCodeFileReader {
|
|
|
325
333
|
return [];
|
|
326
334
|
let messageFiles;
|
|
327
335
|
try {
|
|
328
|
-
messageFiles = fs.readdirSync(messageDir).filter(f => f.endsWith('.json'));
|
|
336
|
+
messageFiles = fs.readdirSync(messageDir).filter((f) => f.endsWith('.json'));
|
|
329
337
|
}
|
|
330
338
|
catch {
|
|
331
339
|
return [];
|
|
@@ -348,16 +356,17 @@ class OpenCodeFileReader {
|
|
|
348
356
|
let parts = [];
|
|
349
357
|
if (fs.existsSync(partDir)) {
|
|
350
358
|
try {
|
|
351
|
-
parts = fs
|
|
352
|
-
.
|
|
353
|
-
.
|
|
359
|
+
parts = fs
|
|
360
|
+
.readdirSync(partDir)
|
|
361
|
+
.filter((f) => f.endsWith('.json'))
|
|
362
|
+
.map((f) => readJsonSafe(path.join(partDir, f)))
|
|
354
363
|
.filter((p) => p !== null);
|
|
355
364
|
}
|
|
356
365
|
catch {
|
|
357
366
|
// Skip unreadable part directories
|
|
358
367
|
}
|
|
359
368
|
}
|
|
360
|
-
const partIds = new Set(parts.map(part => part.id));
|
|
369
|
+
const partIds = new Set(parts.map((part) => part.id));
|
|
361
370
|
const previous = this.seenMessages.get(msgId);
|
|
362
371
|
const isNewMessage = !previous;
|
|
363
372
|
let hasNewParts = false;
|
|
@@ -463,23 +472,26 @@ class OpenCodeDbReader {
|
|
|
463
472
|
messages.sort((a, b) => a.time_created - b.time_created);
|
|
464
473
|
// Filter user messages that haven't been processed yet
|
|
465
474
|
const userMessageIds = messages
|
|
466
|
-
.filter(m => extractRoleFromDbMessage(m) === 'user')
|
|
467
|
-
.map(m => m.id);
|
|
475
|
+
.filter((m) => extractRoleFromDbMessage(m) === 'user')
|
|
476
|
+
.map((m) => m.id);
|
|
468
477
|
const processedUserMessageIds = new Set(this.db.getProcessedUserMessageIds(this.sessionId, userMessageIds));
|
|
469
478
|
for (const msgRow of messages) {
|
|
470
479
|
try {
|
|
471
|
-
if (extractRoleFromDbMessage(msgRow) === 'user' &&
|
|
480
|
+
if (extractRoleFromDbMessage(msgRow) === 'user' &&
|
|
481
|
+
!processedUserMessageIds.has(msgRow.id)) {
|
|
472
482
|
continue;
|
|
473
483
|
}
|
|
474
484
|
const message = (0, openCodeParser_1.parseDbMessageData)(msgRow);
|
|
475
|
-
const msgParts = (partsByMessage.get(msgRow.id) || [])
|
|
485
|
+
const msgParts = (partsByMessage.get(msgRow.id) || [])
|
|
486
|
+
.map((row) => {
|
|
476
487
|
try {
|
|
477
488
|
return (0, openCodeParser_1.parseDbPartData)(row);
|
|
478
489
|
}
|
|
479
490
|
catch {
|
|
480
491
|
return null;
|
|
481
492
|
}
|
|
482
|
-
})
|
|
493
|
+
})
|
|
494
|
+
.filter((p) => p !== null);
|
|
483
495
|
events.push(...(0, openCodeParser_1.convertOpenCodeMessage)(message, msgParts));
|
|
484
496
|
}
|
|
485
497
|
catch {
|
|
@@ -512,7 +524,7 @@ class OpenCodeDbReader {
|
|
|
512
524
|
return [];
|
|
513
525
|
}
|
|
514
526
|
// Build set of affected message IDs from both message and part changes
|
|
515
|
-
const affectedMessageIds = new Set(messages.map(m => m.id));
|
|
527
|
+
const affectedMessageIds = new Set(messages.map((m) => m.id));
|
|
516
528
|
for (const part of parts) {
|
|
517
529
|
affectedMessageIds.add(part.message_id);
|
|
518
530
|
}
|
|
@@ -525,21 +537,21 @@ class OpenCodeDbReader {
|
|
|
525
537
|
}
|
|
526
538
|
}
|
|
527
539
|
// Fetch missing message rows for part-only updates
|
|
528
|
-
const messageMap = new Map(messages.map(m => [m.id, m]));
|
|
529
|
-
const missingMessageIds = [...affectedMessageIds].filter(id => !messageMap.has(id));
|
|
540
|
+
const messageMap = new Map(messages.map((m) => [m.id, m]));
|
|
541
|
+
const missingMessageIds = [...affectedMessageIds].filter((id) => !messageMap.has(id));
|
|
530
542
|
if (missingMessageIds.length > 0) {
|
|
531
543
|
const missingRows = this.db.getMessagesByIds(this.sessionId, missingMessageIds);
|
|
532
544
|
for (const row of missingRows) {
|
|
533
545
|
messageMap.set(row.id, row);
|
|
534
546
|
}
|
|
535
|
-
const unresolved = missingMessageIds.filter(id => !messageMap.has(id));
|
|
547
|
+
const unresolved = missingMessageIds.filter((id) => !messageMap.has(id));
|
|
536
548
|
if (unresolved.length > 0) {
|
|
537
549
|
// Keep cursor unchanged so we can retry on next polling cycle.
|
|
538
550
|
return [];
|
|
539
551
|
}
|
|
540
552
|
}
|
|
541
553
|
const targetMessages = [...messageMap.values()];
|
|
542
|
-
const targetMessageIds = targetMessages.map(m => m.id);
|
|
554
|
+
const targetMessageIds = targetMessages.map((m) => m.id);
|
|
543
555
|
// Fetch complete part sets for all affected messages in one batched query
|
|
544
556
|
const allParts = this.db.getPartsForMessages(this.sessionId, targetMessageIds);
|
|
545
557
|
const partsByMessage = new Map();
|
|
@@ -566,23 +578,26 @@ class OpenCodeDbReader {
|
|
|
566
578
|
// Sort messages by creation time
|
|
567
579
|
targetMessages.sort((a, b) => a.time_created - b.time_created);
|
|
568
580
|
const userMessageIds = targetMessages
|
|
569
|
-
.filter(m => extractRoleFromDbMessage(m) === 'user')
|
|
570
|
-
.map(m => m.id);
|
|
581
|
+
.filter((m) => extractRoleFromDbMessage(m) === 'user')
|
|
582
|
+
.map((m) => m.id);
|
|
571
583
|
const processedUserMessageIds = new Set(this.db.getProcessedUserMessageIds(this.sessionId, userMessageIds));
|
|
572
584
|
for (const msgRow of targetMessages) {
|
|
573
585
|
try {
|
|
574
|
-
if (extractRoleFromDbMessage(msgRow) === 'user' &&
|
|
586
|
+
if (extractRoleFromDbMessage(msgRow) === 'user' &&
|
|
587
|
+
!processedUserMessageIds.has(msgRow.id)) {
|
|
575
588
|
continue;
|
|
576
589
|
}
|
|
577
590
|
const message = (0, openCodeParser_1.parseDbMessageData)(msgRow);
|
|
578
|
-
const msgParts = (partsByMessage.get(msgRow.id) || [])
|
|
591
|
+
const msgParts = (partsByMessage.get(msgRow.id) || [])
|
|
592
|
+
.map((row) => {
|
|
579
593
|
try {
|
|
580
594
|
return (0, openCodeParser_1.parseDbPartData)(row);
|
|
581
595
|
}
|
|
582
596
|
catch {
|
|
583
597
|
return null;
|
|
584
598
|
}
|
|
585
|
-
})
|
|
599
|
+
})
|
|
600
|
+
.filter((p) => p !== null);
|
|
586
601
|
events.push(...(0, openCodeParser_1.convertOpenCodeMessage)(message, msgParts));
|
|
587
602
|
}
|
|
588
603
|
catch {
|
|
@@ -673,7 +688,7 @@ class OpenCodeProvider {
|
|
|
673
688
|
return [];
|
|
674
689
|
const sessions = db.getSessionsForProject(projectId);
|
|
675
690
|
const dataDir = getOpenCodeDataDir();
|
|
676
|
-
return sessions.map(session => {
|
|
691
|
+
return sessions.map((session) => {
|
|
677
692
|
const syntheticPath = makeDbSessionPath(dataDir, projectId, session.id);
|
|
678
693
|
this.sessionMetaCache.set(syntheticPath, {
|
|
679
694
|
title: session.title,
|
|
@@ -750,7 +765,7 @@ class OpenCodeProvider {
|
|
|
750
765
|
let bestPath = null;
|
|
751
766
|
let bestMtime = 0;
|
|
752
767
|
try {
|
|
753
|
-
const files = fs.readdirSync(sessionDir).filter(f => f.endsWith('.json'));
|
|
768
|
+
const files = fs.readdirSync(sessionDir).filter((f) => f.endsWith('.json'));
|
|
754
769
|
for (const file of files) {
|
|
755
770
|
const fullPath = path.join(sessionDir, file);
|
|
756
771
|
try {
|
|
@@ -773,7 +788,7 @@ class OpenCodeProvider {
|
|
|
773
788
|
const messageDir = path.join(getStorageDir(), 'message', sessionId);
|
|
774
789
|
if (fs.existsSync(messageDir)) {
|
|
775
790
|
try {
|
|
776
|
-
const messageFiles = fs.readdirSync(messageDir).filter(f => f.endsWith('.json'));
|
|
791
|
+
const messageFiles = fs.readdirSync(messageDir).filter((f) => f.endsWith('.json'));
|
|
777
792
|
for (const mf of messageFiles) {
|
|
778
793
|
try {
|
|
779
794
|
const mstat = fs.statSync(path.join(messageDir, mf));
|
|
@@ -843,9 +858,10 @@ class OpenCodeProvider {
|
|
|
843
858
|
try {
|
|
844
859
|
if (!fs.existsSync(dir))
|
|
845
860
|
return [];
|
|
846
|
-
return fs
|
|
847
|
-
.
|
|
848
|
-
.
|
|
861
|
+
return fs
|
|
862
|
+
.readdirSync(dir)
|
|
863
|
+
.filter((f) => f.endsWith('.json'))
|
|
864
|
+
.map((f) => {
|
|
849
865
|
const fullPath = path.join(dir, f);
|
|
850
866
|
try {
|
|
851
867
|
const stats = fs.statSync(fullPath);
|
|
@@ -857,7 +873,7 @@ class OpenCodeProvider {
|
|
|
857
873
|
})
|
|
858
874
|
.filter((f) => f !== null && f.size > 0)
|
|
859
875
|
.sort((a, b) => b.mtime - a.mtime)
|
|
860
|
-
.map(f => f.path);
|
|
876
|
+
.map((f) => f.path);
|
|
861
877
|
}
|
|
862
878
|
catch {
|
|
863
879
|
return [];
|
|
@@ -871,7 +887,7 @@ class OpenCodeProvider {
|
|
|
871
887
|
if (db) {
|
|
872
888
|
const projects = db.getAllProjects();
|
|
873
889
|
const stats = db.getProjectSessionStats();
|
|
874
|
-
const statsMap = new Map(stats.map(s => [s.projectId, s]));
|
|
890
|
+
const statsMap = new Map(stats.map((s) => [s.projectId, s]));
|
|
875
891
|
let currentProjectId = null;
|
|
876
892
|
if (workspacePath) {
|
|
877
893
|
currentProjectId = resolveProjectId(workspacePath, db, dbStatus);
|
|
@@ -916,7 +932,7 @@ class OpenCodeProvider {
|
|
|
916
932
|
try {
|
|
917
933
|
if (!fs.existsSync(sessionBase))
|
|
918
934
|
return [];
|
|
919
|
-
const projectIds = fs.readdirSync(sessionBase).filter(name => {
|
|
935
|
+
const projectIds = fs.readdirSync(sessionBase).filter((name) => {
|
|
920
936
|
try {
|
|
921
937
|
return fs.statSync(path.join(sessionBase, name)).isDirectory();
|
|
922
938
|
}
|
|
@@ -928,7 +944,7 @@ class OpenCodeProvider {
|
|
|
928
944
|
const projectNames = new Map();
|
|
929
945
|
if (fs.existsSync(projectDir)) {
|
|
930
946
|
try {
|
|
931
|
-
for (const file of fs.readdirSync(projectDir).filter(f => f.endsWith('.json'))) {
|
|
947
|
+
for (const file of fs.readdirSync(projectDir).filter((f) => f.endsWith('.json'))) {
|
|
932
948
|
const proj = readJsonSafe(path.join(projectDir, file));
|
|
933
949
|
if (proj) {
|
|
934
950
|
projectNames.set(proj.id, proj.path || proj.name || proj.id);
|
|
@@ -948,7 +964,7 @@ class OpenCodeProvider {
|
|
|
948
964
|
let sessionCount = 0;
|
|
949
965
|
let lastModified = new Date(0);
|
|
950
966
|
try {
|
|
951
|
-
const sessions = fs.readdirSync(projSessionDir).filter(f => f.endsWith('.json'));
|
|
967
|
+
const sessions = fs.readdirSync(projSessionDir).filter((f) => f.endsWith('.json'));
|
|
952
968
|
for (const session of sessions) {
|
|
953
969
|
try {
|
|
954
970
|
const fstats = fs.statSync(path.join(projSessionDir, session));
|
|
@@ -972,7 +988,7 @@ class OpenCodeProvider {
|
|
|
972
988
|
name: projectNames.get(projectId) || projectId,
|
|
973
989
|
encodedName: projectId,
|
|
974
990
|
sessionCount,
|
|
975
|
-
lastModified
|
|
991
|
+
lastModified,
|
|
976
992
|
});
|
|
977
993
|
}
|
|
978
994
|
folders.sort((a, b) => {
|
|
@@ -1032,15 +1048,16 @@ class OpenCodeProvider {
|
|
|
1032
1048
|
if (!fs.existsSync(messageDir))
|
|
1033
1049
|
return null;
|
|
1034
1050
|
try {
|
|
1035
|
-
const files = fs
|
|
1036
|
-
.
|
|
1051
|
+
const files = fs
|
|
1052
|
+
.readdirSync(messageDir)
|
|
1053
|
+
.filter((f) => f.endsWith('.json'))
|
|
1037
1054
|
.slice(0, 5);
|
|
1038
1055
|
for (const file of files) {
|
|
1039
1056
|
const msg = readJsonSafe(path.join(messageDir, file));
|
|
1040
1057
|
if (msg?.role === 'user') {
|
|
1041
1058
|
const partDir = path.join(getStorageDir(), 'part', msg.id);
|
|
1042
1059
|
if (fs.existsSync(partDir)) {
|
|
1043
|
-
const partFiles = fs.readdirSync(partDir).filter(f => f.endsWith('.json'));
|
|
1060
|
+
const partFiles = fs.readdirSync(partDir).filter((f) => f.endsWith('.json'));
|
|
1044
1061
|
for (const pf of partFiles) {
|
|
1045
1062
|
const part = readJsonSafe(path.join(partDir, pf));
|
|
1046
1063
|
if (part?.type === 'text' && part.text.trim().length > 0) {
|
|
@@ -1139,8 +1156,12 @@ class OpenCodeProvider {
|
|
|
1139
1156
|
let duration;
|
|
1140
1157
|
const timeInfo = state?.time;
|
|
1141
1158
|
if (timeInfo?.start && timeInfo?.end) {
|
|
1142
|
-
const startMs = typeof timeInfo.start === 'number'
|
|
1143
|
-
|
|
1159
|
+
const startMs = typeof timeInfo.start === 'number'
|
|
1160
|
+
? timeInfo.start
|
|
1161
|
+
: new Date(timeInfo.start).getTime();
|
|
1162
|
+
const endMs = typeof timeInfo.end === 'number'
|
|
1163
|
+
? timeInfo.end
|
|
1164
|
+
: new Date(timeInfo.end).getTime();
|
|
1144
1165
|
if (endMs > startMs)
|
|
1145
1166
|
duration = endMs - startMs;
|
|
1146
1167
|
}
|
|
@@ -1168,8 +1189,8 @@ class OpenCodeProvider {
|
|
|
1168
1189
|
else {
|
|
1169
1190
|
startTime = new Date(child.time_created);
|
|
1170
1191
|
}
|
|
1171
|
-
const rawAgentType = subtask?.agent ||
|
|
1172
|
-
const description = subtask?.description ||
|
|
1192
|
+
const rawAgentType = subtask?.agent || child.title || undefined;
|
|
1193
|
+
const description = subtask?.description || child.title || undefined;
|
|
1173
1194
|
results.push({
|
|
1174
1195
|
agentId: subtask?.id || child.id,
|
|
1175
1196
|
agentType: normalizeAgentType(rawAgentType, description),
|
|
@@ -1223,7 +1244,7 @@ class OpenCodeProvider {
|
|
|
1223
1244
|
try {
|
|
1224
1245
|
const parts = db.getPartsForSession(sessionId);
|
|
1225
1246
|
const messages = db.getMessagesForSession(sessionId);
|
|
1226
|
-
const messageMap = new Map(messages.map(m => [m.id, m]));
|
|
1247
|
+
const messageMap = new Map(messages.map((m) => [m.id, m]));
|
|
1227
1248
|
for (const partRow of parts) {
|
|
1228
1249
|
if (results.length >= maxResults)
|
|
1229
1250
|
break;
|
|
@@ -1262,7 +1283,7 @@ class OpenCodeProvider {
|
|
|
1262
1283
|
try {
|
|
1263
1284
|
const session = readJsonSafe(sessionPath);
|
|
1264
1285
|
const projectPath = session?.projectID || sessionId;
|
|
1265
|
-
const messageFiles = fs.readdirSync(messageDir).filter(f => f.endsWith('.json'));
|
|
1286
|
+
const messageFiles = fs.readdirSync(messageDir).filter((f) => f.endsWith('.json'));
|
|
1266
1287
|
for (const file of messageFiles) {
|
|
1267
1288
|
if (results.length >= maxResults)
|
|
1268
1289
|
break;
|
|
@@ -1273,7 +1294,7 @@ class OpenCodeProvider {
|
|
|
1273
1294
|
if (!fs.existsSync(partDir))
|
|
1274
1295
|
continue;
|
|
1275
1296
|
try {
|
|
1276
|
-
const partFiles = fs.readdirSync(partDir).filter(f => f.endsWith('.json'));
|
|
1297
|
+
const partFiles = fs.readdirSync(partDir).filter((f) => f.endsWith('.json'));
|
|
1277
1298
|
for (const pf of partFiles) {
|
|
1278
1299
|
if (results.length >= maxResults)
|
|
1279
1300
|
break;
|
|
@@ -1305,7 +1326,7 @@ class OpenCodeProvider {
|
|
|
1305
1326
|
line: snippet.replace(/\n/g, ' '),
|
|
1306
1327
|
eventType: msg.role,
|
|
1307
1328
|
timestamp: String(msg.time.created),
|
|
1308
|
-
projectPath
|
|
1329
|
+
projectPath,
|
|
1309
1330
|
});
|
|
1310
1331
|
}
|
|
1311
1332
|
}
|
|
@@ -1357,10 +1378,13 @@ class OpenCodeProvider {
|
|
|
1357
1378
|
if (!modelUsage[modelId])
|
|
1358
1379
|
modelUsage[modelId] = { calls: 0, tokens: 0 };
|
|
1359
1380
|
modelUsage[modelId].calls++;
|
|
1360
|
-
modelUsage[modelId].tokens +=
|
|
1381
|
+
modelUsage[modelId].tokens +=
|
|
1382
|
+
(msgTokens.input || 0) + (msgTokens.output || 0);
|
|
1361
1383
|
}
|
|
1362
1384
|
}
|
|
1363
|
-
catch {
|
|
1385
|
+
catch {
|
|
1386
|
+
/* skip */
|
|
1387
|
+
}
|
|
1364
1388
|
if (!startTime)
|
|
1365
1389
|
startTime = new Date(msg.time_created).toISOString();
|
|
1366
1390
|
endTime = new Date(msg.time_updated || msg.time_created).toISOString();
|
|
@@ -1373,7 +1397,9 @@ class OpenCodeProvider {
|
|
|
1373
1397
|
toolUsage[toolName] = (toolUsage[toolName] || 0) + 1;
|
|
1374
1398
|
}
|
|
1375
1399
|
}
|
|
1376
|
-
catch {
|
|
1400
|
+
catch {
|
|
1401
|
+
/* skip */
|
|
1402
|
+
}
|
|
1377
1403
|
}
|
|
1378
1404
|
}
|
|
1379
1405
|
return {
|
|
@@ -1546,9 +1572,13 @@ class OpenCodeProvider {
|
|
|
1546
1572
|
attribution.toolOutputs += toolTokens;
|
|
1547
1573
|
attribution.userMessages -= toolTokens;
|
|
1548
1574
|
}
|
|
1549
|
-
const total = attribution.systemPrompt +
|
|
1550
|
-
attribution.
|
|
1551
|
-
attribution.
|
|
1575
|
+
const total = attribution.systemPrompt +
|
|
1576
|
+
attribution.userMessages +
|
|
1577
|
+
attribution.assistantResponses +
|
|
1578
|
+
attribution.toolInputs +
|
|
1579
|
+
attribution.toolOutputs +
|
|
1580
|
+
attribution.thinking +
|
|
1581
|
+
attribution.other;
|
|
1552
1582
|
return total > 0 ? attribution : null;
|
|
1553
1583
|
}
|
|
1554
1584
|
catch {
|
|
@@ -1600,7 +1630,7 @@ class OpenCodeProvider {
|
|
|
1600
1630
|
}
|
|
1601
1631
|
if (rawRows.length === 0)
|
|
1602
1632
|
return null;
|
|
1603
|
-
const turns = rawRows.map(row => ({
|
|
1633
|
+
const turns = rawRows.map((row) => ({
|
|
1604
1634
|
timestampMs: row.timeCreated,
|
|
1605
1635
|
model: row.modelId,
|
|
1606
1636
|
inputTokens: row.inputTokens || 0,
|
|
@@ -1612,9 +1642,7 @@ class OpenCodeProvider {
|
|
|
1612
1642
|
const accumulated = (0, zaiQuota_1.accumulateZaiUsage)(turns, Date.now());
|
|
1613
1643
|
// Default to 'max' on auto when usage is non-zero — overestimating the
|
|
1614
1644
|
// tier gives a lower (safer) utilization % until calibration runs.
|
|
1615
|
-
const resolvedTier = tier === 'auto'
|
|
1616
|
-
? (accumulated.weeklyTurns > 0 ? 'max' : 'lite')
|
|
1617
|
-
: tier;
|
|
1645
|
+
const resolvedTier = tier === 'auto' ? (accumulated.weeklyTurns > 0 ? 'max' : 'lite') : tier;
|
|
1618
1646
|
// Walk rows again to trap any captured z.ai business errors.
|
|
1619
1647
|
let authoritativeFiveHourResetAt;
|
|
1620
1648
|
let authoritativeWeeklyResetAt;
|
|
@@ -47,7 +47,9 @@ class OpenCodeDatabase {
|
|
|
47
47
|
constructor(dataDir) {
|
|
48
48
|
this.dbPath = path.join(dataDir, 'opencode.db');
|
|
49
49
|
}
|
|
50
|
-
isAvailable() {
|
|
50
|
+
isAvailable() {
|
|
51
|
+
return fs.existsSync(this.dbPath);
|
|
52
|
+
}
|
|
51
53
|
getRuntimeStatus() {
|
|
52
54
|
if (this.runtimeStatus)
|
|
53
55
|
return this.runtimeStatus;
|
|
@@ -55,7 +57,11 @@ class OpenCodeDatabase {
|
|
|
55
57
|
this.runtimeStatus = { available: false, kind: 'db_missing' };
|
|
56
58
|
return this.runtimeStatus;
|
|
57
59
|
}
|
|
58
|
-
return {
|
|
60
|
+
return {
|
|
61
|
+
available: false,
|
|
62
|
+
kind: 'query_failed',
|
|
63
|
+
message: 'OpenCode database has not been initialized yet.',
|
|
64
|
+
};
|
|
59
65
|
}
|
|
60
66
|
open() {
|
|
61
67
|
if (this.runtimeStatus)
|
|
@@ -65,7 +71,12 @@ class OpenCodeDatabase {
|
|
|
65
71
|
return false;
|
|
66
72
|
}
|
|
67
73
|
try {
|
|
68
|
-
(0, child_process_1.execFileSync)('sqlite3', ['--version'], {
|
|
74
|
+
(0, child_process_1.execFileSync)('sqlite3', ['--version'], {
|
|
75
|
+
encoding: 'utf-8',
|
|
76
|
+
timeout: 4000,
|
|
77
|
+
killSignal: 'SIGKILL',
|
|
78
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
79
|
+
});
|
|
69
80
|
this.runtimeStatus = { available: true, kind: 'available' };
|
|
70
81
|
return true;
|
|
71
82
|
}
|
|
@@ -89,7 +100,11 @@ class OpenCodeDatabase {
|
|
|
89
100
|
}
|
|
90
101
|
try {
|
|
91
102
|
const result = (0, child_process_1.execFileSync)('sqlite3', ['-json', '-readonly', this.dbPath, query], {
|
|
92
|
-
encoding: 'utf-8',
|
|
103
|
+
encoding: 'utf-8',
|
|
104
|
+
timeout: 4000,
|
|
105
|
+
killSignal: 'SIGKILL',
|
|
106
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
107
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
93
108
|
});
|
|
94
109
|
this.runtimeStatus = { available: true, kind: 'available' };
|
|
95
110
|
const trimmed = result.trim();
|
|
@@ -141,17 +156,17 @@ class OpenCodeDatabase {
|
|
|
141
156
|
}
|
|
142
157
|
if (!bestProjectId)
|
|
143
158
|
return null;
|
|
144
|
-
return this.getAllProjects().find(project => project.id === bestProjectId) ?? null;
|
|
159
|
+
return this.getAllProjects().find((project) => project.id === bestProjectId) ?? null;
|
|
145
160
|
}
|
|
146
161
|
getAllProjects() {
|
|
147
|
-
return this.query('SELECT id, worktree, name, sandboxes, time_created, time_updated FROM project')
|
|
148
|
-
.map(project => ({
|
|
162
|
+
return this.query('SELECT id, worktree, name, sandboxes, time_created, time_updated FROM project').map((project) => ({
|
|
149
163
|
...project,
|
|
150
164
|
sandboxes: parseStringArray(project.sandboxes),
|
|
151
165
|
}));
|
|
152
166
|
}
|
|
153
167
|
hasProject(projectId) {
|
|
154
|
-
return this.queryOne('SELECT id FROM project WHERE id = ? LIMIT 1', [projectId]) !==
|
|
168
|
+
return (this.queryOne('SELECT id FROM project WHERE id = ? LIMIT 1', [projectId]) !==
|
|
169
|
+
null);
|
|
155
170
|
}
|
|
156
171
|
getSessionsForProject(projectId) {
|
|
157
172
|
return this.query('SELECT id, project_id, title, directory, time_created, time_updated FROM session WHERE project_id = ? AND parent_id IS NULL ORDER BY time_updated DESC', [projectId]);
|
|
@@ -197,7 +212,7 @@ class OpenCodeDatabase {
|
|
|
197
212
|
AND json_extract(data, '$.role') = 'assistant'
|
|
198
213
|
AND json_extract(data, '$.parentID') IN (${placeholders})`, [sessionId, ...userMessageIds]);
|
|
199
214
|
return rows
|
|
200
|
-
.map(r => r.parentId)
|
|
215
|
+
.map((r) => r.parentId)
|
|
201
216
|
.filter((id) => typeof id === 'string' && id.length > 0);
|
|
202
217
|
}
|
|
203
218
|
/** Get the latest assistant token usage row with non-zero context signal. */
|
|
@@ -259,7 +274,9 @@ class OpenCodeDatabase {
|
|
|
259
274
|
getAssistantMessagesByProviderId(providerIds, sinceMs) {
|
|
260
275
|
if (providerIds.length === 0)
|
|
261
276
|
return [];
|
|
262
|
-
const providerClauses = providerIds
|
|
277
|
+
const providerClauses = providerIds
|
|
278
|
+
.map(() => "json_extract(data, '$.providerID') = ?")
|
|
279
|
+
.join(' OR ');
|
|
263
280
|
return this.query(`SELECT
|
|
264
281
|
time_created AS timeCreated,
|
|
265
282
|
COALESCE(json_extract(data, '$.modelID'), 'unknown') AS modelId,
|
|
@@ -302,7 +319,9 @@ function parseStringArray(value) {
|
|
|
302
319
|
try {
|
|
303
320
|
const parsed = JSON.parse(value);
|
|
304
321
|
return Array.isArray(parsed)
|
|
305
|
-
? parsed
|
|
322
|
+
? parsed
|
|
323
|
+
.filter((entry) => typeof entry === 'string' && entry.length > 0)
|
|
324
|
+
.map(normalizePath)
|
|
306
325
|
: [];
|
|
307
326
|
}
|
|
308
327
|
catch {
|
|
@@ -334,17 +353,25 @@ function pathMatchScore(candidate, workspacePath) {
|
|
|
334
353
|
}
|
|
335
354
|
function toRuntimeStatus(error, fallback = 'sqlite_missing') {
|
|
336
355
|
if (isErrno(error, 'ENOENT')) {
|
|
337
|
-
return {
|
|
356
|
+
return {
|
|
357
|
+
available: false,
|
|
358
|
+
kind: 'sqlite_missing',
|
|
359
|
+
message: 'sqlite3 executable not found in PATH.',
|
|
360
|
+
};
|
|
338
361
|
}
|
|
339
362
|
if (isErrno(error, 'EPERM') || isErrno(error, 'EACCES')) {
|
|
340
|
-
return {
|
|
363
|
+
return {
|
|
364
|
+
available: false,
|
|
365
|
+
kind: 'sqlite_blocked',
|
|
366
|
+
message: 'sqlite3 exists but could not be executed.',
|
|
367
|
+
};
|
|
341
368
|
}
|
|
342
369
|
const message = error instanceof Error ? error.message : String(error);
|
|
343
370
|
return { available: false, kind: fallback, message };
|
|
344
371
|
}
|
|
345
372
|
function isErrno(error, code) {
|
|
346
|
-
return typeof error === 'object'
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
373
|
+
return (typeof error === 'object' &&
|
|
374
|
+
error !== null &&
|
|
375
|
+
'code' in error &&
|
|
376
|
+
error.code === code);
|
|
350
377
|
}
|
package/dist/quota.d.ts
CHANGED
|
@@ -50,6 +50,25 @@ export interface QuotaState {
|
|
|
50
50
|
/** Provider-specific rate-limit reached reason */
|
|
51
51
|
rateLimitReachedType?: string;
|
|
52
52
|
}
|
|
53
|
+
export declare const FIVE_HOUR_WINDOW_MS: number;
|
|
54
|
+
export declare const SEVEN_DAY_WINDOW_MS: number;
|
|
55
|
+
export interface QuotaProjectionInput {
|
|
56
|
+
utilization: number;
|
|
57
|
+
resetsAt: string;
|
|
58
|
+
windowMs: number;
|
|
59
|
+
capturedAt?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Projects utilization at end of window based on elapsed time.
|
|
63
|
+
*
|
|
64
|
+
* Formula: `current * (windowDuration / elapsed)`, capped at 200%.
|
|
65
|
+
*/
|
|
66
|
+
export declare function projectQuotaWindow(input: QuotaProjectionInput): number | undefined;
|
|
67
|
+
export declare function withQuotaProjections<T extends QuotaState>(state: T, options?: {
|
|
68
|
+
fiveHourWindowMs?: number;
|
|
69
|
+
sevenDayWindowMs?: number;
|
|
70
|
+
capturedAt?: string;
|
|
71
|
+
}): T;
|
|
53
72
|
/**
|
|
54
73
|
* Fetch current quota utilization from the Anthropic OAuth usage API.
|
|
55
74
|
*
|