sidekick-shared 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +198 -56
- package/dist/aggregation/EventAggregator.d.ts +96 -2
- package/dist/aggregation/EventAggregator.js +535 -42
- package/dist/aggregation/snapshot.d.ts +37 -8
- package/dist/aggregation/snapshot.js +31 -9
- package/dist/aggregation/types.d.ts +38 -1
- package/dist/browser.d.ts +9 -1
- package/dist/browser.js +29 -3
- package/dist/context/sessionContext.js +8 -0
- package/dist/esm/aggregation/EventAggregator.js +535 -42
- package/dist/esm/aggregation/snapshot.js +31 -9
- package/dist/esm/browser.js +3 -0
- package/dist/esm/context/sessionContext.js +8 -0
- package/dist/esm/fileContinuity.js +149 -0
- package/dist/esm/formatters/toolSummary.js +4 -1
- package/dist/esm/index.js +7 -2
- package/dist/esm/modelCatalog.js +5 -4
- package/dist/esm/modelContext.js +4 -3
- package/dist/esm/modelInfo.js +4 -3
- package/dist/esm/node.js +3 -1
- package/dist/esm/observedContextWindows.js +2 -1
- package/dist/esm/ownKey.js +26 -0
- package/dist/esm/parsers/codexParser.js +372 -7
- package/dist/esm/parsers/openCodeParser.js +2 -1
- package/dist/esm/parsers/subagentEndState.js +61 -0
- package/dist/esm/parsers/subagentScanner.js +37 -12
- package/dist/esm/parsers/subagentTraceParser.js +103 -37
- package/dist/esm/pricingCatalog.js +3 -2
- package/dist/esm/promptHistory.js +22 -11
- package/dist/esm/providers/claudeCode.js +60 -29
- package/dist/esm/providers/codex.js +235 -201
- package/dist/esm/providers/codexCompressedReader.js +184 -0
- package/dist/esm/providers/codexDatabase.js +47 -2
- package/dist/esm/providers/codexRollouts.js +20 -2
- package/dist/esm/providers/codexSubagents.js +456 -0
- package/dist/esm/providers/compressedRollouts.js +330 -0
- package/dist/esm/providers/factory.js +4 -1
- package/dist/esm/providers/rolloutWalker.js +32 -4
- package/dist/esm/schemas/sessionEvent.js +63 -14
- package/dist/esm/sessionMonitor.js +7 -1
- package/dist/esm/sessionPreviews.js +9 -0
- package/dist/esm/sessionPromptHistory.js +5 -3
- package/dist/esm/subagentActivity.js +125 -0
- package/dist/esm/subagentDisplayState.js +81 -0
- package/dist/esm/subagentSpawn.js +363 -0
- package/dist/esm/testing/codexSubagentFixtures.js +336 -0
- package/dist/esm/toolCallOrigins.js +17 -10
- package/dist/esm/toolCategory.js +6 -2
- package/dist/esm/transcript.js +8 -0
- package/dist/esm/turns/assistantTurn.js +6 -2
- package/dist/esm/usage/claudeUsageDedupe.js +68 -0
- package/dist/esm/usage/usageEvents.js +4 -1
- package/dist/esm/watchers/eventBridge.js +45 -0
- package/dist/esm/watchers/factory.js +5 -3
- package/dist/esm/watchers/jsonlTail.js +82 -48
- package/dist/esm/watchers/jsonlWatcher.js +61 -4
- package/dist/esm/watchers/providerReaderWatcher.js +18 -4
- package/dist/esm/zaiQuota.js +2 -1
- package/dist/fileContinuity.d.ts +95 -0
- package/dist/fileContinuity.js +187 -0
- package/dist/formatters/toolSummary.js +4 -1
- package/dist/index.d.ts +19 -7
- package/dist/index.js +48 -10
- package/dist/modelCatalog.js +5 -4
- package/dist/modelContext.js +4 -3
- package/dist/modelInfo.js +4 -3
- package/dist/node.d.ts +5 -1
- package/dist/node.js +18 -2
- package/dist/observedContextWindows.js +2 -1
- package/dist/ownKey.d.ts +16 -0
- package/dist/ownKey.js +30 -0
- package/dist/parsers/codexParser.d.ts +107 -0
- package/dist/parsers/codexParser.js +373 -7
- package/dist/parsers/openCodeParser.js +2 -1
- package/dist/parsers/subagentEndState.d.ts +20 -0
- package/dist/parsers/subagentEndState.js +65 -0
- package/dist/parsers/subagentScanner.d.ts +19 -6
- package/dist/parsers/subagentScanner.js +37 -12
- package/dist/parsers/subagentTraceParser.d.ts +33 -5
- package/dist/parsers/subagentTraceParser.js +103 -37
- package/dist/pricingCatalog.js +3 -2
- package/dist/promptHistory.d.ts +13 -3
- package/dist/promptHistory.js +22 -11
- package/dist/providers/claudeCode.js +59 -28
- package/dist/providers/codex.d.ts +35 -5
- package/dist/providers/codex.js +232 -198
- package/dist/providers/codexCompressedReader.d.ts +58 -0
- package/dist/providers/codexCompressedReader.js +221 -0
- package/dist/providers/codexDatabase.d.ts +25 -0
- package/dist/providers/codexDatabase.js +48 -2
- package/dist/providers/codexRollouts.d.ts +16 -2
- package/dist/providers/codexRollouts.js +20 -2
- package/dist/providers/codexSubagents.d.ts +66 -0
- package/dist/providers/codexSubagents.js +495 -0
- package/dist/providers/compressedRollouts.d.ts +114 -0
- package/dist/providers/compressedRollouts.js +380 -0
- package/dist/providers/factory.d.ts +5 -0
- package/dist/providers/factory.js +4 -1
- package/dist/providers/rolloutWalker.d.ts +15 -2
- package/dist/providers/rolloutWalker.js +33 -4
- package/dist/providers/types.d.ts +36 -1
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/sessionEvent.d.ts +51 -10
- package/dist/schemas/sessionEvent.js +63 -14
- package/dist/sessionMonitor.d.ts +6 -0
- package/dist/sessionMonitor.js +7 -1
- package/dist/sessionPreviews.js +9 -0
- package/dist/sessionPromptHistory.d.ts +5 -0
- package/dist/sessionPromptHistory.js +5 -36
- package/dist/subagentActivity.d.ts +91 -0
- package/dist/subagentActivity.js +132 -0
- package/dist/subagentDisplayState.d.ts +81 -0
- package/dist/subagentDisplayState.js +86 -0
- package/dist/subagentSpawn.d.ts +175 -0
- package/dist/subagentSpawn.js +385 -0
- package/dist/testing/codexSubagentFixtures.d.ts +48 -0
- package/dist/testing/codexSubagentFixtures.js +344 -0
- package/dist/toolCallOrigins.d.ts +5 -0
- package/dist/toolCallOrigins.js +17 -10
- package/dist/toolCategory.js +6 -2
- package/dist/transcript.js +8 -0
- package/dist/turns/assistantTurn.d.ts +4 -0
- package/dist/turns/assistantTurn.js +6 -2
- package/dist/types/codex.d.ts +71 -2
- package/dist/types/sessionEvent.d.ts +38 -3
- package/dist/usage/claudeUsageDedupe.d.ts +31 -0
- package/dist/usage/claudeUsageDedupe.js +68 -0
- package/dist/usage/usageEvents.d.ts +4 -1
- package/dist/usage/usageEvents.js +4 -1
- package/dist/watchers/eventBridge.js +45 -0
- package/dist/watchers/factory.d.ts +6 -0
- package/dist/watchers/factory.js +5 -3
- package/dist/watchers/jsonlTail.d.ts +27 -4
- package/dist/watchers/jsonlTail.js +84 -48
- package/dist/watchers/jsonlWatcher.d.ts +22 -1
- package/dist/watchers/jsonlWatcher.js +60 -3
- package/dist/watchers/providerReaderWatcher.d.ts +7 -0
- package/dist/watchers/providerReaderWatcher.js +18 -4
- package/dist/watchers/types.d.ts +12 -0
- package/dist/zaiQuota.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session reader for a compressed Codex rollout (`rollout-…jsonl.zst`).
|
|
3
|
+
*
|
|
4
|
+
* Codex compresses only rollouts it no longer writes, so the content is
|
|
5
|
+
* decompressed and parsed whole on the first read; later reads cost one
|
|
6
|
+
* stat and return nothing until the file changes (then the rollout is read
|
|
7
|
+
* again from the start, reported as `replaced`). Positions are offsets in
|
|
8
|
+
* the decompressed content. Live tailing stays with the `.jsonl` reader.
|
|
9
|
+
*
|
|
10
|
+
* Node-only: reads files.
|
|
11
|
+
*
|
|
12
|
+
* @module providers/codexCompressedReader
|
|
13
|
+
*/
|
|
14
|
+
import { type CodexRolloutResumeState } from '../parsers/codexParser';
|
|
15
|
+
import type { CodexRateLimits, CodexSessionMeta } from '../types/codex';
|
|
16
|
+
import type { SessionEvent, TokenUsage } from '../types/sessionEvent';
|
|
17
|
+
import { type ReadRolloutOptions } from './compressedRollouts';
|
|
18
|
+
import type { SessionReader } from './types';
|
|
19
|
+
type ReaderReset = NonNullable<ReturnType<NonNullable<SessionReader['lastReset']>>>;
|
|
20
|
+
export interface CompressedCodexReaderCallbacks {
|
|
21
|
+
onContextWindowLimit?: (limit: number, modelId?: string) => void;
|
|
22
|
+
onRateLimits?: (limits: CodexRateLimits) => void;
|
|
23
|
+
onTokenUsage?: (usage: TokenUsage) => void;
|
|
24
|
+
onDiagnostic?: ReadRolloutOptions['onDiagnostic'];
|
|
25
|
+
}
|
|
26
|
+
/** Reads a `.jsonl.zst` rollout through a {@link CodexRolloutParser}. */
|
|
27
|
+
export declare class CompressedCodexRolloutReader implements SessionReader {
|
|
28
|
+
private readonly rolloutPath;
|
|
29
|
+
private readonly callbacks;
|
|
30
|
+
private parser;
|
|
31
|
+
/** Offset after the last line parsed (the content is complete, so its end). */
|
|
32
|
+
private position;
|
|
33
|
+
/** Size and mtime of the compressed file the position refers to. */
|
|
34
|
+
private source;
|
|
35
|
+
private reset_;
|
|
36
|
+
/** Position from `seekTo()` whose prefix still has to be parsed for context. */
|
|
37
|
+
private pendingSeek;
|
|
38
|
+
constructor(rolloutPath: string, callbacks?: CompressedCodexReaderCallbacks);
|
|
39
|
+
getSessionMeta(): CodexSessionMeta | null;
|
|
40
|
+
readNew(): SessionEvent[];
|
|
41
|
+
readAll(): SessionEvent[];
|
|
42
|
+
reset(): void;
|
|
43
|
+
exists(): boolean;
|
|
44
|
+
flush(): void;
|
|
45
|
+
getPosition(): number;
|
|
46
|
+
seekTo(position: number): void;
|
|
47
|
+
wasTruncated(): boolean;
|
|
48
|
+
lastReset(): ReaderReset | null;
|
|
49
|
+
getResumeState(): CodexRolloutResumeState;
|
|
50
|
+
restoreResumeState(state: unknown): boolean;
|
|
51
|
+
/** Parses the content past `position`; `emit` false only rebuilds parser state. */
|
|
52
|
+
private readFrom;
|
|
53
|
+
/** Rebuilds parser context for a pending `seekTo()` position. */
|
|
54
|
+
private settleSeek;
|
|
55
|
+
/** Hands the parser's context window, rate limits, and last usage to the provider. */
|
|
56
|
+
private propagateParserState;
|
|
57
|
+
}
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Session reader for a compressed Codex rollout (`rollout-…jsonl.zst`).
|
|
4
|
+
*
|
|
5
|
+
* Codex compresses only rollouts it no longer writes, so the content is
|
|
6
|
+
* decompressed and parsed whole on the first read; later reads cost one
|
|
7
|
+
* stat and return nothing until the file changes (then the rollout is read
|
|
8
|
+
* again from the start, reported as `replaced`). Positions are offsets in
|
|
9
|
+
* the decompressed content. Live tailing stays with the `.jsonl` reader.
|
|
10
|
+
*
|
|
11
|
+
* Node-only: reads files.
|
|
12
|
+
*
|
|
13
|
+
* @module providers/codexCompressedReader
|
|
14
|
+
*/
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
31
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
32
|
+
var ownKeys = function(o) {
|
|
33
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
34
|
+
var ar = [];
|
|
35
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
36
|
+
return ar;
|
|
37
|
+
};
|
|
38
|
+
return ownKeys(o);
|
|
39
|
+
};
|
|
40
|
+
return function (mod) {
|
|
41
|
+
if (mod && mod.__esModule) return mod;
|
|
42
|
+
var result = {};
|
|
43
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
44
|
+
__setModuleDefault(result, mod);
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
})();
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.CompressedCodexRolloutReader = void 0;
|
|
50
|
+
const fs = __importStar(require("fs"));
|
|
51
|
+
const codexParser_1 = require("../parsers/codexParser");
|
|
52
|
+
const usageNormalization_1 = require("../usageNormalization");
|
|
53
|
+
const compressedRollouts_1 = require("./compressedRollouts");
|
|
54
|
+
/** Reads a `.jsonl.zst` rollout through a {@link CodexRolloutParser}. */
|
|
55
|
+
class CompressedCodexRolloutReader {
|
|
56
|
+
rolloutPath;
|
|
57
|
+
callbacks;
|
|
58
|
+
parser = new codexParser_1.CodexRolloutParser({ includeSubagentActivity: true });
|
|
59
|
+
/** Offset after the last line parsed (the content is complete, so its end). */
|
|
60
|
+
position = 0;
|
|
61
|
+
/** Size and mtime of the compressed file the position refers to. */
|
|
62
|
+
source = null;
|
|
63
|
+
reset_ = null;
|
|
64
|
+
/** Position from `seekTo()` whose prefix still has to be parsed for context. */
|
|
65
|
+
pendingSeek = null;
|
|
66
|
+
constructor(rolloutPath, callbacks = {}) {
|
|
67
|
+
this.rolloutPath = rolloutPath;
|
|
68
|
+
this.callbacks = callbacks;
|
|
69
|
+
}
|
|
70
|
+
getSessionMeta() {
|
|
71
|
+
this.settleSeek();
|
|
72
|
+
return this.parser.getSessionMeta();
|
|
73
|
+
}
|
|
74
|
+
readNew() {
|
|
75
|
+
this.settleSeek();
|
|
76
|
+
this.reset_ = null;
|
|
77
|
+
return this.readFrom(true);
|
|
78
|
+
}
|
|
79
|
+
readAll() {
|
|
80
|
+
this.reset();
|
|
81
|
+
return this.readNew();
|
|
82
|
+
}
|
|
83
|
+
reset() {
|
|
84
|
+
this.parser.reset();
|
|
85
|
+
this.position = 0;
|
|
86
|
+
this.source = null;
|
|
87
|
+
this.reset_ = null;
|
|
88
|
+
this.pendingSeek = null;
|
|
89
|
+
}
|
|
90
|
+
exists() {
|
|
91
|
+
return fs.existsSync(this.rolloutPath);
|
|
92
|
+
}
|
|
93
|
+
flush() {
|
|
94
|
+
// Nothing is buffered: each read parses through the end of the content.
|
|
95
|
+
}
|
|
96
|
+
getPosition() {
|
|
97
|
+
return this.position;
|
|
98
|
+
}
|
|
99
|
+
seekTo(position) {
|
|
100
|
+
this.reset();
|
|
101
|
+
this.position = Math.max(0, position);
|
|
102
|
+
if (this.position > 0)
|
|
103
|
+
this.pendingSeek = this.position;
|
|
104
|
+
}
|
|
105
|
+
wasTruncated() {
|
|
106
|
+
return this.reset_ !== null;
|
|
107
|
+
}
|
|
108
|
+
lastReset() {
|
|
109
|
+
return this.reset_ ? { ...this.reset_ } : null;
|
|
110
|
+
}
|
|
111
|
+
getResumeState() {
|
|
112
|
+
this.settleSeek();
|
|
113
|
+
return this.parser.exportResumeState();
|
|
114
|
+
}
|
|
115
|
+
restoreResumeState(state) {
|
|
116
|
+
if (!this.parser.importResumeState(state))
|
|
117
|
+
return false;
|
|
118
|
+
this.pendingSeek = null;
|
|
119
|
+
this.propagateParserState();
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
/** Parses the content past `position`; `emit` false only rebuilds parser state. */
|
|
123
|
+
readFrom(emit, endPosition) {
|
|
124
|
+
let stat;
|
|
125
|
+
try {
|
|
126
|
+
stat = fs.statSync(this.rolloutPath);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
const current = { size: stat.size, mtimeMs: stat.mtimeMs };
|
|
132
|
+
if (this.source &&
|
|
133
|
+
(this.source.size !== current.size || this.source.mtimeMs !== current.mtimeMs)) {
|
|
134
|
+
// Another file has the path: what was read no longer describes it.
|
|
135
|
+
this.reset_ = { reason: 'replaced', previousPosition: this.position };
|
|
136
|
+
this.parser.reset();
|
|
137
|
+
this.position = 0;
|
|
138
|
+
}
|
|
139
|
+
else if (this.source) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
const content = (0, compressedRollouts_1.readRolloutBuffer)(this.rolloutPath, {
|
|
143
|
+
onDiagnostic: this.callbacks.onDiagnostic,
|
|
144
|
+
});
|
|
145
|
+
if (!content)
|
|
146
|
+
return [];
|
|
147
|
+
this.source = current;
|
|
148
|
+
const end = Math.min(content.length, endPosition ?? content.length);
|
|
149
|
+
if (this.position > end) {
|
|
150
|
+
// The position sought lies past this content: start over.
|
|
151
|
+
this.reset_ = { reason: 'truncated', previousPosition: this.position };
|
|
152
|
+
this.parser.reset();
|
|
153
|
+
this.position = 0;
|
|
154
|
+
}
|
|
155
|
+
const events = [];
|
|
156
|
+
const text = content.toString('utf8', this.position, end);
|
|
157
|
+
for (const line of text.split('\n')) {
|
|
158
|
+
const trimmed = line.trim();
|
|
159
|
+
if (!trimmed)
|
|
160
|
+
continue;
|
|
161
|
+
try {
|
|
162
|
+
const converted = this.parser.convertLine(JSON.parse(trimmed));
|
|
163
|
+
if (emit)
|
|
164
|
+
events.push(...converted);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// Skip malformed lines.
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
this.position = end;
|
|
171
|
+
if (end < content.length)
|
|
172
|
+
this.source = null;
|
|
173
|
+
this.propagateParserState();
|
|
174
|
+
return events;
|
|
175
|
+
}
|
|
176
|
+
/** Rebuilds parser context for a pending `seekTo()` position. */
|
|
177
|
+
settleSeek() {
|
|
178
|
+
const position = this.pendingSeek;
|
|
179
|
+
if (position === null)
|
|
180
|
+
return;
|
|
181
|
+
this.pendingSeek = null;
|
|
182
|
+
this.parser.reset();
|
|
183
|
+
this.position = 0;
|
|
184
|
+
this.source = null;
|
|
185
|
+
this.readFrom(false, position);
|
|
186
|
+
}
|
|
187
|
+
/** Hands the parser's context window, rate limits, and last usage to the provider. */
|
|
188
|
+
propagateParserState() {
|
|
189
|
+
const { onContextWindowLimit, onRateLimits, onTokenUsage } = this.callbacks;
|
|
190
|
+
const window = this.parser.getModelContextWindow();
|
|
191
|
+
if (window && onContextWindowLimit) {
|
|
192
|
+
onContextWindowLimit(window, this.parser.getCurrentModel() ?? undefined);
|
|
193
|
+
}
|
|
194
|
+
const rateLimits = this.parser.getLastRateLimits();
|
|
195
|
+
if (rateLimits && onRateLimits)
|
|
196
|
+
onRateLimits(rateLimits);
|
|
197
|
+
const lastUsage = this.parser.getLastTokenUsage();
|
|
198
|
+
if (lastUsage && onTokenUsage) {
|
|
199
|
+
const normalized = (0, usageNormalization_1.normalizeProviderUsage)({
|
|
200
|
+
semantics: 'openai',
|
|
201
|
+
provider: 'openai',
|
|
202
|
+
source: 'codex-reader-snapshot',
|
|
203
|
+
model: this.parser.getCurrentModel() || undefined,
|
|
204
|
+
inputTokens: lastUsage.input_tokens,
|
|
205
|
+
outputTokens: lastUsage.output_tokens,
|
|
206
|
+
cacheReadTokens: lastUsage.cached_input_tokens,
|
|
207
|
+
reasoningTokens: lastUsage.reasoning_output_tokens,
|
|
208
|
+
});
|
|
209
|
+
onTokenUsage({
|
|
210
|
+
inputTokens: normalized.uncachedInputTokens,
|
|
211
|
+
outputTokens: normalized.outputTokens,
|
|
212
|
+
cacheWriteTokens: normalized.cacheWriteTokens,
|
|
213
|
+
cacheReadTokens: normalized.cacheReadTokens,
|
|
214
|
+
reasoningTokens: normalized.reasoningTokens,
|
|
215
|
+
model: this.parser.getCurrentModel() || 'unknown',
|
|
216
|
+
timestamp: new Date(),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
exports.CompressedCodexRolloutReader = CompressedCodexRolloutReader;
|
|
@@ -7,6 +7,21 @@ import type { ProviderRuntimeStatus } from './types';
|
|
|
7
7
|
import { codexSpawnParentFromSource as spawnParentFromSource } from '../parsers/codexRolloutOrigin';
|
|
8
8
|
/** Parent thread id of a spawned subagent's `source`; one rule shared with rollout parsing. */
|
|
9
9
|
export { spawnParentFromSource };
|
|
10
|
+
/** Default levels {@link CodexDatabase.getSpawnedDescendantThreads} follows. */
|
|
11
|
+
export declare const DEFAULT_SUBAGENT_TREE_MAX_DEPTH = 8;
|
|
12
|
+
/** A spawned thread below a session, with the thread that spawned it. */
|
|
13
|
+
export interface CodexSpawnedThread {
|
|
14
|
+
thread: CodexDbThread;
|
|
15
|
+
/** The thread that spawned it: the session for depth 1, else another descendant. */
|
|
16
|
+
parentId: string;
|
|
17
|
+
/** 1 for the session's own children, 2 for theirs, … */
|
|
18
|
+
depth: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Whether a thread row is a subagent's rather than a session a person
|
|
22
|
+
* started: `thread_source: 'subagent'`, or a `{ subagent: … }` source.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isCodexSubagentThread(thread: Pick<CodexDbThread, 'source' | 'thread_source'>): boolean;
|
|
10
25
|
export declare class CodexDatabase {
|
|
11
26
|
private readonly dbPath;
|
|
12
27
|
private sqlite3Available;
|
|
@@ -20,6 +35,10 @@ export declare class CodexDatabase {
|
|
|
20
35
|
private queryAsync;
|
|
21
36
|
private queryOne;
|
|
22
37
|
getThreadsByCwd(cwd: string): CodexDbThread[];
|
|
38
|
+
/**
|
|
39
|
+
* The workspace's most recently updated thread that a person started:
|
|
40
|
+
* subagent threads (which share their parent's cwd) are skipped.
|
|
41
|
+
*/
|
|
23
42
|
getMostRecentThread(cwd: string): CodexDbThread | null;
|
|
24
43
|
getAllDistinctCwds(): Array<{
|
|
25
44
|
cwd: string;
|
|
@@ -47,6 +66,12 @@ export declare class CodexDatabase {
|
|
|
47
66
|
* the LIKE narrows the scan and the JSON parse confirms the match.
|
|
48
67
|
*/
|
|
49
68
|
getSpawnedChildThreads(parentId: string): CodexDbThread[];
|
|
69
|
+
/**
|
|
70
|
+
* Every subagent thread below a session, level by level (one query per
|
|
71
|
+
* level, in chunks of ids), oldest first within a level: its children,
|
|
72
|
+
* their children, and so on up to `maxDepth` levels.
|
|
73
|
+
*/
|
|
74
|
+
getSpawnedDescendantThreads(rootId: string, maxDepth?: number): CodexSpawnedThread[];
|
|
50
75
|
/** Get the database file's mtime (ms epoch). Returns 0 if unavailable. */
|
|
51
76
|
getDbMtime(): number;
|
|
52
77
|
}
|
|
@@ -37,7 +37,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
37
37
|
};
|
|
38
38
|
})();
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
-
exports.CodexDatabase = exports.spawnParentFromSource = void 0;
|
|
40
|
+
exports.CodexDatabase = exports.DEFAULT_SUBAGENT_TREE_MAX_DEPTH = exports.spawnParentFromSource = void 0;
|
|
41
|
+
exports.isCodexSubagentThread = isCodexSubagentThread;
|
|
41
42
|
const fs = __importStar(require("fs"));
|
|
42
43
|
const path = __importStar(require("path"));
|
|
43
44
|
const child_process_1 = require("child_process");
|
|
@@ -48,6 +49,17 @@ Object.defineProperty(exports, "spawnParentFromSource", { enumerable: true, get:
|
|
|
48
49
|
* element to sqlite3, so an unbounded list would hit E2BIG on large stores.
|
|
49
50
|
*/
|
|
50
51
|
const SQL_ID_CHUNK_SIZE = 400;
|
|
52
|
+
/** Default levels {@link CodexDatabase.getSpawnedDescendantThreads} follows. */
|
|
53
|
+
exports.DEFAULT_SUBAGENT_TREE_MAX_DEPTH = 8;
|
|
54
|
+
/**
|
|
55
|
+
* Whether a thread row is a subagent's rather than a session a person
|
|
56
|
+
* started: `thread_source: 'subagent'`, or a `{ subagent: … }` source.
|
|
57
|
+
*/
|
|
58
|
+
function isCodexSubagentThread(thread) {
|
|
59
|
+
if (thread.thread_source === 'subagent')
|
|
60
|
+
return true;
|
|
61
|
+
return (0, codexRolloutOrigin_1.codexRolloutOrigin)({ source: thread.source }).isSubagent;
|
|
62
|
+
}
|
|
51
63
|
class CodexDatabase {
|
|
52
64
|
dbPath;
|
|
53
65
|
sqlite3Available = null;
|
|
@@ -176,8 +188,12 @@ class CodexDatabase {
|
|
|
176
188
|
threadCwd.startsWith(normalized + path.sep));
|
|
177
189
|
});
|
|
178
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* The workspace's most recently updated thread that a person started:
|
|
193
|
+
* subagent threads (which share their parent's cwd) are skipped.
|
|
194
|
+
*/
|
|
179
195
|
getMostRecentThread(cwd) {
|
|
180
|
-
return this.getThreadsByCwd(cwd)
|
|
196
|
+
return this.getThreadsByCwd(cwd).find((thread) => !isCodexSubagentThread(thread)) ?? null;
|
|
181
197
|
}
|
|
182
198
|
getAllDistinctCwds() {
|
|
183
199
|
return this.query('SELECT cwd, COUNT(*) as count, MAX(updated_at) as lastUpdated FROM threads GROUP BY cwd ORDER BY lastUpdated DESC');
|
|
@@ -230,6 +246,36 @@ class CodexDatabase {
|
|
|
230
246
|
const rows = this.query('SELECT * FROM threads WHERE source LIKE ? ORDER BY created_at ASC', [`%${parentId}%`]);
|
|
231
247
|
return rows.filter((row) => (0, codexRolloutOrigin_1.codexSpawnParentFromSource)(row.source) === parentId);
|
|
232
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Every subagent thread below a session, level by level (one query per
|
|
251
|
+
* level, in chunks of ids), oldest first within a level: its children,
|
|
252
|
+
* their children, and so on up to `maxDepth` levels.
|
|
253
|
+
*/
|
|
254
|
+
getSpawnedDescendantThreads(rootId, maxDepth = exports.DEFAULT_SUBAGENT_TREE_MAX_DEPTH) {
|
|
255
|
+
if (!this.getThreadColumns().has('source'))
|
|
256
|
+
return [];
|
|
257
|
+
const found = [];
|
|
258
|
+
const seen = new Set([rootId]);
|
|
259
|
+
let level = [rootId];
|
|
260
|
+
for (let depth = 1; depth <= maxDepth && level.length > 0; depth++) {
|
|
261
|
+
const parents = new Set(level);
|
|
262
|
+
const next = [];
|
|
263
|
+
for (let i = 0; i < level.length; i += SQL_ID_CHUNK_SIZE) {
|
|
264
|
+
const chunk = level.slice(i, i + SQL_ID_CHUNK_SIZE);
|
|
265
|
+
const rows = this.query(`SELECT * FROM threads WHERE ${chunk.map(() => 'source LIKE ?').join(' OR ')} ORDER BY created_at ASC`, chunk.map((id) => `%${id}%`));
|
|
266
|
+
for (const row of rows) {
|
|
267
|
+
const parentId = (0, codexRolloutOrigin_1.codexSpawnParentFromSource)(row.source);
|
|
268
|
+
if (!parentId || !parents.has(parentId) || seen.has(row.id))
|
|
269
|
+
continue;
|
|
270
|
+
seen.add(row.id);
|
|
271
|
+
found.push({ thread: row, parentId, depth });
|
|
272
|
+
next.push(row.id);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
level = next;
|
|
276
|
+
}
|
|
277
|
+
return found;
|
|
278
|
+
}
|
|
233
279
|
/** Get the database file's mtime (ms epoch). Returns 0 if unavailable. */
|
|
234
280
|
getDbMtime() {
|
|
235
281
|
try {
|
|
@@ -11,7 +11,8 @@ import type { ListSessionFilesOptions, SessionFileInfo } from './types';
|
|
|
11
11
|
export declare function getSessionsDirs(): string[];
|
|
12
12
|
/**
|
|
13
13
|
* Read the first N complete lines from a file using chunked reads.
|
|
14
|
-
* Handles arbitrarily long lines (Codex session_meta can be 10KB+)
|
|
14
|
+
* Handles arbitrarily long lines (Codex session_meta can be 10KB+), and
|
|
15
|
+
* compressed rollouts (`.jsonl.zst`, decompressed when the runtime can).
|
|
15
16
|
*/
|
|
16
17
|
export declare function readFirstLines(filePath: string, maxLines: number, maxBytes?: number): string[];
|
|
17
18
|
/** Read session_meta from the beginning of a rollout file. */
|
|
@@ -36,12 +37,20 @@ export declare function initializeCounters(stats: ListSessionFilesOptions['stats
|
|
|
36
37
|
export declare function cwdMatches(sessionCwd: string, workspacePath: string): boolean;
|
|
37
38
|
/** Normalize a path using realpathSync. */
|
|
38
39
|
export declare function normalizePath(input: string): string;
|
|
40
|
+
/** Options for {@link listCodexSessionFilesAsync}. */
|
|
41
|
+
export interface ListCodexSessionFilesOptions extends ListSessionFilesOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Also list compressed rollouts (`.jsonl.zst`; default false). Do not
|
|
44
|
+
* reuse one `cache` across calls that differ in this option.
|
|
45
|
+
*/
|
|
46
|
+
includeCompressed?: boolean;
|
|
47
|
+
}
|
|
39
48
|
/**
|
|
40
49
|
* List rollout files across the monitored Codex homes, newest first. With
|
|
41
50
|
* `workspacePath`, keeps rollouts whose session_meta cwd equals, contains, or
|
|
42
51
|
* sits inside it.
|
|
43
52
|
*/
|
|
44
|
-
export declare function listCodexSessionFilesAsync(workspacePath?: string, options?:
|
|
53
|
+
export declare function listCodexSessionFilesAsync(workspacePath?: string, options?: ListCodexSessionFilesOptions, cache?: DirectoryListingCache): Promise<SessionFileInfo[]>;
|
|
45
54
|
export interface FindCodexRolloutFileOptions {
|
|
46
55
|
/**
|
|
47
56
|
* Codex home whose `sessions/` subtree to search. Defaults to every
|
|
@@ -49,6 +58,11 @@ export interface FindCodexRolloutFileOptions {
|
|
|
49
58
|
* profile homes that recorded sessions.
|
|
50
59
|
*/
|
|
51
60
|
codexHome?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Also match a compressed rollout (`.jsonl.zst`) when no `.jsonl` for the
|
|
63
|
+
* id exists (default false).
|
|
64
|
+
*/
|
|
65
|
+
includeCompressed?: boolean;
|
|
52
66
|
}
|
|
53
67
|
/**
|
|
54
68
|
* Locates the rollout file for a Codex session id by walking
|
|
@@ -56,15 +56,19 @@ const path = __importStar(require("path"));
|
|
|
56
56
|
const codexPaths_1 = require("../codexPaths");
|
|
57
57
|
const directoryListingCache_1 = require("./directoryListingCache");
|
|
58
58
|
const rolloutWalker_1 = require("./rolloutWalker");
|
|
59
|
+
const compressedRollouts_1 = require("./compressedRollouts");
|
|
59
60
|
/** Every directory across the monitored homes that may hold rollouts, archived ones included. */
|
|
60
61
|
function getSessionsDirs() {
|
|
61
62
|
return (0, codexPaths_1.getCodexSessionsDirs)();
|
|
62
63
|
}
|
|
63
64
|
/**
|
|
64
65
|
* Read the first N complete lines from a file using chunked reads.
|
|
65
|
-
* Handles arbitrarily long lines (Codex session_meta can be 10KB+)
|
|
66
|
+
* Handles arbitrarily long lines (Codex session_meta can be 10KB+), and
|
|
67
|
+
* compressed rollouts (`.jsonl.zst`, decompressed when the runtime can).
|
|
66
68
|
*/
|
|
67
69
|
function readFirstLines(filePath, maxLines, maxBytes = 256 * 1024) {
|
|
70
|
+
if ((0, compressedRollouts_1.isCompressedRolloutPath)(filePath))
|
|
71
|
+
return (0, compressedRollouts_1.readRolloutFirstLines)(filePath, maxLines, maxBytes);
|
|
68
72
|
const lines = [];
|
|
69
73
|
let fd;
|
|
70
74
|
try {
|
|
@@ -128,6 +132,14 @@ async function readFirstLineAsync(rolloutPath) {
|
|
|
128
132
|
// `instructions` and can far exceed a single chunk.
|
|
129
133
|
const maxBytes = 256 * 1024;
|
|
130
134
|
const chunkSize = 64 * 1024;
|
|
135
|
+
if ((0, compressedRollouts_1.isCompressedRolloutPath)(rolloutPath)) {
|
|
136
|
+
// Compressed rollouts are complete: their first line is final.
|
|
137
|
+
const head = (0, compressedRollouts_1.readRolloutHead)(rolloutPath, maxBytes);
|
|
138
|
+
if (head.length === 0)
|
|
139
|
+
return { line: null, complete: false };
|
|
140
|
+
const firstLine = head.toString('utf8').split('\n', 1)[0]?.trim();
|
|
141
|
+
return { line: firstLine || null, complete: true };
|
|
142
|
+
}
|
|
131
143
|
let handle = null;
|
|
132
144
|
try {
|
|
133
145
|
handle = await fs.promises.open(rolloutPath, 'r');
|
|
@@ -245,6 +257,7 @@ async function listCodexSessionFilesAsync(workspacePath, options = {}, cache = n
|
|
|
245
257
|
cache,
|
|
246
258
|
revalidateRecent: options.revalidateRecent,
|
|
247
259
|
stats: options.stats,
|
|
260
|
+
includeCompressed: options.includeCompressed,
|
|
248
261
|
});
|
|
249
262
|
for (const file of files) {
|
|
250
263
|
if (options.limit !== undefined && results.length >= options.limit)
|
|
@@ -281,5 +294,10 @@ function findCodexRolloutFile(sessionId, options = {}) {
|
|
|
281
294
|
? (0, codexPaths_1.getCodexSessionsDirs)([options.codexHome])
|
|
282
295
|
: getSessionsDirs();
|
|
283
296
|
// Name-only filter, one walk across the homes, newest modification first.
|
|
284
|
-
|
|
297
|
+
const matches = (0, rolloutWalker_1.walkRolloutFiles)(sessionsDirs, {
|
|
298
|
+
sessionId: normalizedId,
|
|
299
|
+
includeCompressed: options.includeCompressed,
|
|
300
|
+
});
|
|
301
|
+
// A live `.jsonl` wins over a compressed copy found in another home.
|
|
302
|
+
return (matches.find((file) => !(0, compressedRollouts_1.isCompressedRolloutPath)(file.path)) ?? matches[0])?.path ?? null;
|
|
285
303
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex subagent discovery for `CodexProvider.scanSubagents()`.
|
|
3
|
+
*
|
|
4
|
+
* Every spawned thread below a session (children, their children, …), each
|
|
5
|
+
* with its rollout, the parent's spawn call that started it, its nickname and
|
|
6
|
+
* agent path, and stats from the child's own records. A forked child's
|
|
7
|
+
* rollout embeds the parent's session_meta and history before
|
|
8
|
+
* `session_meta.subagent_history_start_ordinal`; that prefix is the parent's
|
|
9
|
+
* and is not counted (the parser skips it, and so do the timestamps here).
|
|
10
|
+
*
|
|
11
|
+
* The spawn link comes from the parent's rollout: the `item_completed` →
|
|
12
|
+
* `SubAgentActivity` record with `kind: 'started'` names the spawn call id
|
|
13
|
+
* and the child thread. When a parent has no such record (older Codex), the
|
|
14
|
+
* spawn call whose output named the child's agent path
|
|
15
|
+
* (`{"task_name":"/root/x"}`) is used. Parent scans are cached and read
|
|
16
|
+
* incrementally as the parent rollout grows.
|
|
17
|
+
*
|
|
18
|
+
* Node-only: reads files.
|
|
19
|
+
*
|
|
20
|
+
* @module providers/codexSubagents
|
|
21
|
+
*/
|
|
22
|
+
import type { SubagentStats } from '../types/sessionEvent';
|
|
23
|
+
import type { CodexDatabase } from './codexDatabase';
|
|
24
|
+
import { type ReadRolloutOptions } from './compressedRollouts';
|
|
25
|
+
import type { RolloutFileInfo } from './rolloutWalker';
|
|
26
|
+
/** The spawn calls a rollout records, from its own (not inherited) records. */
|
|
27
|
+
export interface CodexSpawnLinks {
|
|
28
|
+
/** Child thread id → the spawn call that started it (`SubAgentActivity` `started`). */
|
|
29
|
+
started: Map<string, {
|
|
30
|
+
callId: string;
|
|
31
|
+
agentPath?: string;
|
|
32
|
+
}>;
|
|
33
|
+
/** Agent path → the spawn calls whose output named it, in file order. */
|
|
34
|
+
spawnedPaths: Map<string, string[]>;
|
|
35
|
+
}
|
|
36
|
+
/** @internal Test hook. */
|
|
37
|
+
export declare function clearCodexSpawnLinkCache(): void;
|
|
38
|
+
/**
|
|
39
|
+
* The spawn calls a rollout records (see {@link CodexSpawnLinks}). Cached per
|
|
40
|
+
* path; a growing `.jsonl` is read only past what was read before, a
|
|
41
|
+
* compressed one whole. `null` when the rollout cannot be read.
|
|
42
|
+
*/
|
|
43
|
+
export declare function readCodexSpawnLinks(filePath: string, options?: ReadRolloutOptions): CodexSpawnLinks | null;
|
|
44
|
+
/**
|
|
45
|
+
* Stats of one subagent rollout from its own records: tool calls, tokens,
|
|
46
|
+
* and first/last timestamps after the embedded parent history (the child's
|
|
47
|
+
* own `session_meta` counts). Reads `.jsonl.zst` when the runtime can.
|
|
48
|
+
* `null` when the rollout is missing or cannot be read.
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildCodexSubagentStats(agentId: string, rolloutPath: string, description?: string | null, options?: ReadRolloutOptions): SubagentStats | null;
|
|
51
|
+
export interface DiscoverCodexSubagentsOptions extends ReadRolloutOptions {
|
|
52
|
+
/** The session's state database, when available. */
|
|
53
|
+
db: CodexDatabase | null;
|
|
54
|
+
/** Read compressed rollouts (`.jsonl.zst`) for children whose `.jsonl` is gone. */
|
|
55
|
+
includeCompressed: boolean;
|
|
56
|
+
/** The session's own rollout, or a lookup for it (needed for spawn links). */
|
|
57
|
+
sessionRolloutPath: () => string | null;
|
|
58
|
+
/** Rollouts to inspect when the database has no answer (newest first). */
|
|
59
|
+
fallbackFiles: () => RolloutFileInfo[];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Every subagent below a session with links and own-record stats. Children
|
|
63
|
+
* whose rollout is gone (or compressed, without `includeCompressed`) are
|
|
64
|
+
* left out, as before.
|
|
65
|
+
*/
|
|
66
|
+
export declare function discoverCodexSubagents(sessionId: string, options: DiscoverCodexSubagentsOptions): SubagentStats[];
|