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
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
* Session snapshot sidecar — persists aggregator state + reader position
|
|
3
3
|
* so that re-attaching to a session skips full replay.
|
|
4
4
|
*
|
|
5
|
-
* Snapshots are stored in
|
|
6
|
-
* They are invalidated when the source file
|
|
5
|
+
* Snapshots are stored in `<config dir>/snapshots/{sessionId}.json` (see
|
|
6
|
+
* `getConfigDir()`). They are invalidated when the source file shrank below
|
|
7
|
+
* the snapshot position or, for file-backed sessions that recorded a
|
|
8
|
+
* {@link SessionSnapshot.sourceCheckpoint}, when the file is no longer the
|
|
9
|
+
* same append-only file.
|
|
7
10
|
*
|
|
8
11
|
* @module aggregation/snapshot
|
|
9
12
|
*/
|
|
10
13
|
import * as fs from 'fs';
|
|
11
14
|
import * as path from 'path';
|
|
12
15
|
import { getConfigDir } from '../paths';
|
|
16
|
+
import { verifyJsonlCheckpoint } from '../watchers/jsonlTail';
|
|
13
17
|
import { atomicWriteJsonSync } from '../writers/atomic';
|
|
14
18
|
import { SNAPSHOT_SCHEMA_VERSION } from './EventAggregator';
|
|
15
19
|
/** Current schema version — bump when format changes. */
|
|
@@ -101,8 +105,8 @@ export function pruneSnapshots(keep = MAX_SNAPSHOT_FILES) {
|
|
|
101
105
|
* - Schema version doesn't match
|
|
102
106
|
* - JSON parse fails
|
|
103
107
|
*
|
|
104
|
-
* The caller is responsible for staleness checks
|
|
105
|
-
*
|
|
108
|
+
* The caller is responsible for staleness checks against the actual file:
|
|
109
|
+
* `isSnapshotValid(snapshot, currentSize, sourcePath)`.
|
|
106
110
|
*/
|
|
107
111
|
export function loadSnapshot(sessionId) {
|
|
108
112
|
try {
|
|
@@ -142,13 +146,21 @@ export function deleteSnapshot(sessionId) {
|
|
|
142
146
|
/**
|
|
143
147
|
* Validates whether a snapshot is still usable for a given source file.
|
|
144
148
|
*
|
|
145
|
-
* For JSONL providers: checks that the file hasn't been truncated
|
|
146
|
-
* (current size >= snapshot position) and that the size matches or has grown.
|
|
147
|
-
*
|
|
148
149
|
* For DB-backed providers: sourceSize is 0, so we always consider valid
|
|
149
150
|
* (the DB reader's timestamp-based cursor handles staleness naturally).
|
|
151
|
+
*
|
|
152
|
+
* For JSONL providers: the file must not have shrunk below the snapshot
|
|
153
|
+
* position. With `sourcePath` the check is also content-aware: the snapshot
|
|
154
|
+
* must carry a {@link SessionSnapshot.sourceCheckpoint} for exactly
|
|
155
|
+
* `readerPosition`, and that checkpoint must still match the file at
|
|
156
|
+
* `sourcePath` (same device and inode, same first bytes, position just after
|
|
157
|
+
* a newline), so a file replaced by another one or rewritten to the same or
|
|
158
|
+
* a larger size is not resumed mid-content. A snapshot without a checkpoint
|
|
159
|
+
* (written before 0.29.1) is invalid when `sourcePath` is given, which costs
|
|
160
|
+
* one full replay. Without `sourcePath` only the size check applies, as it
|
|
161
|
+
* did before.
|
|
150
162
|
*/
|
|
151
|
-
export function isSnapshotValid(snapshot, currentSourceSize) {
|
|
163
|
+
export function isSnapshotValid(snapshot, currentSourceSize, sourcePath) {
|
|
152
164
|
// DB-backed providers store sourceSize=0 — always valid (cursor is a timestamp)
|
|
153
165
|
if (snapshot.sourceSize === 0) {
|
|
154
166
|
return true;
|
|
@@ -157,5 +169,15 @@ export function isSnapshotValid(snapshot, currentSourceSize) {
|
|
|
157
169
|
if (currentSourceSize < snapshot.readerPosition) {
|
|
158
170
|
return false;
|
|
159
171
|
}
|
|
160
|
-
|
|
172
|
+
if (typeof sourcePath !== 'string') {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
// Snapshot content comes from disk: guard the shape before trusting it.
|
|
176
|
+
const checkpoint = snapshot.sourceCheckpoint;
|
|
177
|
+
if (!checkpoint ||
|
|
178
|
+
typeof checkpoint !== 'object' ||
|
|
179
|
+
checkpoint.offset !== snapshot.readerPosition) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
return verifyJsonlCheckpoint(sourcePath, checkpoint);
|
|
161
183
|
}
|
package/dist/esm/browser.js
CHANGED
|
@@ -17,6 +17,8 @@ export { addLocalDays, formatDurationMs, formatLocalDateKey, formatTokenCount, p
|
|
|
17
17
|
export { parseMcpToolName } from './parsers/mcpToolName';
|
|
18
18
|
export { buildSessionContextSnapshot, calculateSessionContextPressure, createSessionContextProjector, } from './context/sessionContext';
|
|
19
19
|
export { ACCOUNT_PROVIDER_IDS, MODEL_PROVIDER_IDS, QUOTA_PROVIDER_IDS, RUNTIME_QUOTA_PROVIDER_IDS, SESSION_PROVIDER_IDS, } from './providerIds';
|
|
20
|
+
export { SUBAGENT_SPAWN_TOOL_NAMES, agentsStoppedNoticeFromEvent, isAgentsKilledSystemEvent, isCodexSubagentSpawnTool, isSubagentSpawnTool, isTaskNotificationSystemEvent, isTaskStatusSystemEvent, parseAgentsStoppedNotice, parseCodexSpawnLaunch, parseTaskNotification, taskNotificationFromEvent, taskStatusFromAttachment, taskStatusFromEvent, } from './subagentSpawn';
|
|
21
|
+
export { SUBAGENT_PARENT_IDLE_ENDED_MS, SUBAGENT_UNREPORTED_QUIET_MS, resolveSubagentDisplayState, subagentDisplayLabel, } from './subagentDisplayState';
|
|
20
22
|
export { assistantTurnEventsFromSessionEvents, extractTurnSubagents, isAssistantTurnSubagentTool, reasoningSummary, segmentAssistantTurn, } from './turns/assistantTurn';
|
|
21
23
|
export { parseModelId, getModelPricing, resolveModelPricing, getModelInfo, calculateCost, calculateCostWithPricing, calculateCostWithProvenance, mergeCostSources, shortModelName, getModelDisplayInfo, compareModelIds, sortModelIds, formatCost, canonicalModelId, isMovingModelAlias, registerModelPricing, clearRegisteredModelPricing, getPricingCatalogInfo, costSourceFromPricingProvenance, mergeCostComponents, } from './modelInfo';
|
|
22
24
|
export { exportResolvedModelCatalog, importResolvedModelCatalog, registerModelAlias, resolveModelAlias, RESOLVED_MODEL_CATALOG_SCHEMA_VERSION, } from './modelCatalog';
|
|
@@ -30,6 +32,7 @@ export { isAggregateCodexLimit } from './types/codex';
|
|
|
30
32
|
// Codex rollout parsing (pure: no filesystem access)
|
|
31
33
|
export { CodexRolloutParser, DEFAULT_MAX_TRACKED_TOOL_CALLS, extractPatchFilePaths, normalizeCodexToolName, normalizeCodexToolInput, resolveCodexCallUsage, } from './parsers/codexParser';
|
|
32
34
|
export { codexRolloutOrigin, codexSpawnParentFromSource } from './parsers/codexRolloutOrigin';
|
|
35
|
+
export { SUBAGENT_ACTIVITY_BLOCK_TYPE, SUBAGENT_ACTIVITY_ORIGIN_KIND, describeSubagentActivity, isSubagentActivitySystemEvent, parseCodexAgentMessageText, subagentActivityFromEvent, } from './subagentActivity';
|
|
33
36
|
export { HUMAN_CLAUDE_ENTRYPOINTS, HUMAN_CODEX_SOURCES, humanPromptText, isHumanPrompt, } from './humanPrompt';
|
|
34
37
|
export { renderHandoffUrlTemplate } from './handoffUrl';
|
|
35
38
|
// Tool categories across providers
|
|
@@ -8,6 +8,8 @@ import { EventAggregator } from '../aggregation/EventAggregator';
|
|
|
8
8
|
import { getModelContextWindowSize } from '../modelContext';
|
|
9
9
|
import { parseMcpToolName } from '../parsers/mcpToolName';
|
|
10
10
|
import { estimateTextTokens } from '../tokenEstimation';
|
|
11
|
+
import { isTaskNotificationSystemEvent, isTaskStatusSystemEvent } from '../subagentSpawn';
|
|
12
|
+
import { isSubagentActivitySystemEvent } from '../subagentActivity';
|
|
11
13
|
const DEFAULT_CONTEXT_SOURCE_LIMIT = 200;
|
|
12
14
|
const DEFAULT_SNIPPET_MAX_CHARS = 240;
|
|
13
15
|
const DEFAULT_BODY_MAX_CHARS = 5000;
|
|
@@ -205,6 +207,12 @@ function extractSourcesFromEvent(event, state) {
|
|
|
205
207
|
}
|
|
206
208
|
}
|
|
207
209
|
function extractSystemSource(event, state) {
|
|
210
|
+
// Background-task notices only update subagent lifecycles (like the aggregator).
|
|
211
|
+
if (isTaskNotificationSystemEvent(event) || isTaskStatusSystemEvent(event))
|
|
212
|
+
return;
|
|
213
|
+
// So are subagent lifecycle records (opt-in Codex parser events).
|
|
214
|
+
if (isSubagentActivitySystemEvent(event))
|
|
215
|
+
return;
|
|
208
216
|
const message = event.message;
|
|
209
217
|
const text = extractText(message?.content);
|
|
210
218
|
const label = message?.sourceLabel ?? message?.role ?? 'system';
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Continuity of a file read incrementally by byte offset: whether the file
|
|
3
|
+
* now at a path is still the content earlier reads consumed, or was
|
|
4
|
+
* truncated, rewritten, or replaced since.
|
|
5
|
+
*
|
|
6
|
+
* {@link FileContinuity} holds the rules `createJsonlTail` applies, so every
|
|
7
|
+
* incremental reader detects the same cases the same way:
|
|
8
|
+
*
|
|
9
|
+
* - `replaced`: another file (by device and inode, read from the open
|
|
10
|
+
* descriptor, so a rename between a stat and an open cannot mix two files)
|
|
11
|
+
* now has the path.
|
|
12
|
+
* - `truncated`: the file is shorter than the read position.
|
|
13
|
+
* - `rewritten`: the mtime changed and either the first {@link HEAD_BYTES}
|
|
14
|
+
* bytes differ from those recorded, or the byte before the committed
|
|
15
|
+
* (resume-safe) offset is no longer a newline.
|
|
16
|
+
*
|
|
17
|
+
* Replacement and truncation are checked on every read; the content probe
|
|
18
|
+
* only when the mtime changed or the recorded head is still short.
|
|
19
|
+
*
|
|
20
|
+
* Known limits: a rewrite that leaves the mtime unchanged (coarse mtime
|
|
21
|
+
* resolution, or an mtime restored on purpose) or that changes only bytes
|
|
22
|
+
* past the first 4 KiB while the resume point still follows a newline goes
|
|
23
|
+
* unnoticed; on filesystems without stable inode numbers (some SMB mounts,
|
|
24
|
+
* Windows) an unchanged file can read as replaced, which costs a replay.
|
|
25
|
+
*
|
|
26
|
+
* @module fileContinuity
|
|
27
|
+
*/
|
|
28
|
+
import * as fs from 'fs';
|
|
29
|
+
import { fileHeadOf, HEAD_BYTES, headMatches } from './fileHead';
|
|
30
|
+
/**
|
|
31
|
+
* What one reader knows about the file its offsets refer to: identity,
|
|
32
|
+
* mtime, and a digest of the first bytes, recorded by {@link adopt} after
|
|
33
|
+
* each read.
|
|
34
|
+
*
|
|
35
|
+
* Synchronous readers call {@link check} on the open descriptor, restart
|
|
36
|
+
* from offset 0 when it returns a reason, then call {@link adopt}. A reader
|
|
37
|
+
* that repositions without reading (a seek to a stored offset, a reset)
|
|
38
|
+
* calls {@link forget}, so its next read adopts the file instead of
|
|
39
|
+
* comparing it with the old one. The lower-level methods ({@link needsProbe},
|
|
40
|
+
* {@link reasonFor}, {@link record}) serve asynchronous readers that probe
|
|
41
|
+
* on their own.
|
|
42
|
+
*/
|
|
43
|
+
export class FileContinuity {
|
|
44
|
+
identity = null;
|
|
45
|
+
head = null;
|
|
46
|
+
lastMtimeMs = -1;
|
|
47
|
+
/** What the last {@link check} probed and decided, for {@link adopt}. */
|
|
48
|
+
checked = null;
|
|
49
|
+
/** Device and inode of the adopted file; `null` before the first read and after {@link forget}. */
|
|
50
|
+
get knownIdentity() {
|
|
51
|
+
return this.identity;
|
|
52
|
+
}
|
|
53
|
+
/** Length and digest of the adopted file's first bytes, once probed. */
|
|
54
|
+
get knownHead() {
|
|
55
|
+
return this.head;
|
|
56
|
+
}
|
|
57
|
+
/** Whether {@link reasonFor} needs bytes from the file to decide. */
|
|
58
|
+
needsProbe(stat, readOffset) {
|
|
59
|
+
return (this.identity === null ||
|
|
60
|
+
stat.dev !== this.identity.dev ||
|
|
61
|
+
stat.ino !== this.identity.ino ||
|
|
62
|
+
stat.size < readOffset ||
|
|
63
|
+
stat.mtimeMs !== this.lastMtimeMs ||
|
|
64
|
+
this.head === null ||
|
|
65
|
+
this.head.bytes < Math.min(HEAD_BYTES, stat.size));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Why the reader must start over: the path now names another file, the
|
|
69
|
+
* file shrank below `readOffset`, its first bytes changed, or the resume
|
|
70
|
+
* point no longer follows a newline. `null` when it can continue.
|
|
71
|
+
*/
|
|
72
|
+
reasonFor(stat, readOffset, probe) {
|
|
73
|
+
if (this.identity && (stat.dev !== this.identity.dev || stat.ino !== this.identity.ino)) {
|
|
74
|
+
return 'replaced';
|
|
75
|
+
}
|
|
76
|
+
if (stat.size < readOffset)
|
|
77
|
+
return 'truncated';
|
|
78
|
+
if (this.identity && stat.mtimeMs !== this.lastMtimeMs && probe) {
|
|
79
|
+
if (this.head && !headMatches(this.head, probe.prefix))
|
|
80
|
+
return 'rewritten';
|
|
81
|
+
if (probe.before && probe.before[0] !== 0x0a)
|
|
82
|
+
return 'rewritten';
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
/** Records the identity, mtime, and (from `probe`) first bytes of the file the offsets now refer to. */
|
|
87
|
+
record(stat, probe) {
|
|
88
|
+
this.identity = { dev: stat.dev, ino: stat.ino };
|
|
89
|
+
this.lastMtimeMs = stat.mtimeMs;
|
|
90
|
+
if (probe && (!this.head || this.head.bytes < Math.min(HEAD_BYTES, stat.size))) {
|
|
91
|
+
this.head = fileHeadOf(probe.prefix);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Drops the recorded first bytes (the reader restarted); the next {@link record} with a probe sets them again. */
|
|
95
|
+
discardHead() {
|
|
96
|
+
this.head = null;
|
|
97
|
+
}
|
|
98
|
+
/** Records the file as known from `prefix`, its first `min(HEAD_BYTES, size)` bytes. */
|
|
99
|
+
adoptPrefix(stat, prefix) {
|
|
100
|
+
this.identity = { dev: stat.dev, ino: stat.ino };
|
|
101
|
+
this.lastMtimeMs = stat.mtimeMs;
|
|
102
|
+
this.head = fileHeadOf(prefix);
|
|
103
|
+
}
|
|
104
|
+
/** Forgets the file, so the next {@link check} adopts whatever file has the path. */
|
|
105
|
+
forget() {
|
|
106
|
+
this.identity = null;
|
|
107
|
+
this.head = null;
|
|
108
|
+
this.lastMtimeMs = -1;
|
|
109
|
+
this.checked = null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Applies the rules to the file open on `fd` (`stat` from `fstat` on that
|
|
113
|
+
* descriptor), reading the bytes they need. `readOffset` is how far the
|
|
114
|
+
* reader has read; `committedOffset` (default `readOffset`) is its
|
|
115
|
+
* resume-safe position just after a newline. Returns why the reader must
|
|
116
|
+
* restart from offset 0, or `null`. Call {@link adopt} with the same
|
|
117
|
+
* `stat` once the reader has acted on the answer.
|
|
118
|
+
*/
|
|
119
|
+
check(fd, stat, readOffset, committedOffset = readOffset) {
|
|
120
|
+
const probe = this.needsProbe(stat, readOffset)
|
|
121
|
+
? probeFileContinuity(fd, stat, committedOffset)
|
|
122
|
+
: null;
|
|
123
|
+
const reason = this.reasonFor(stat, readOffset, probe);
|
|
124
|
+
this.checked = { probe, reset: reason !== null };
|
|
125
|
+
return reason;
|
|
126
|
+
}
|
|
127
|
+
/** Records the file just checked as the one the reader's offsets now refer to. */
|
|
128
|
+
adopt(stat) {
|
|
129
|
+
const checked = this.checked;
|
|
130
|
+
this.checked = null;
|
|
131
|
+
if (checked?.reset)
|
|
132
|
+
this.head = null;
|
|
133
|
+
this.record(stat, checked?.probe ?? null);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Reads the bytes {@link FileContinuity.reasonFor} compares, from the file open on `fd`. */
|
|
137
|
+
export function probeFileContinuity(fd, stat, committedOffset) {
|
|
138
|
+
return {
|
|
139
|
+
prefix: readAt(fd, 0, Math.min(HEAD_BYTES, stat.size)),
|
|
140
|
+
before: committedOffset > 0 && committedOffset <= stat.size
|
|
141
|
+
? readAt(fd, committedOffset - 1, 1)
|
|
142
|
+
: null,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function readAt(fd, position, length) {
|
|
146
|
+
const buffer = Buffer.alloc(length);
|
|
147
|
+
const count = length > 0 ? fs.readSync(fd, buffer, 0, length, position) : 0;
|
|
148
|
+
return buffer.subarray(0, count);
|
|
149
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module formatters/toolSummary
|
|
9
9
|
*/
|
|
10
|
+
import { ownValue } from '../ownKey';
|
|
10
11
|
// ── Helpers ──
|
|
11
12
|
function truncate(text, maxLen) {
|
|
12
13
|
const clean = text.replace(/\s+/g, ' ').trim();
|
|
@@ -171,6 +172,8 @@ const TOOL_FORMATTERS = {
|
|
|
171
172
|
Bash: formatBash,
|
|
172
173
|
Grep: formatGrep,
|
|
173
174
|
Glob: formatGlob,
|
|
175
|
+
// Claude Code's subagent tool; `Task` is its earlier name.
|
|
176
|
+
Agent: formatTask,
|
|
174
177
|
Task: formatTask,
|
|
175
178
|
WebFetch: formatWebFetch,
|
|
176
179
|
WebSearch: formatWebSearch,
|
|
@@ -214,7 +217,7 @@ function genericFallback(input) {
|
|
|
214
217
|
export function formatToolSummary(toolName, input) {
|
|
215
218
|
// Strip MCP prefixes: "mcp__server__tool" → use "tool" for matching
|
|
216
219
|
const baseName = toolName.includes('__') ? (toolName.split('__').pop() ?? toolName) : toolName;
|
|
217
|
-
const formatter = TOOL_FORMATTERS
|
|
220
|
+
const formatter = ownValue(TOOL_FORMATTERS, toolName) ?? ownValue(TOOL_FORMATTERS, baseName);
|
|
218
221
|
if (formatter) {
|
|
219
222
|
const result = formatter(input);
|
|
220
223
|
if (result)
|
package/dist/esm/index.js
CHANGED
|
@@ -51,6 +51,7 @@ export { ClaudeCodeProvider } from './providers/claudeCode';
|
|
|
51
51
|
export { OpenCodeProvider, getOpenCodeDataDir } from './providers/openCode';
|
|
52
52
|
export { CodexProvider, findCodexRolloutFile } from './providers/codex';
|
|
53
53
|
export { walkRolloutFiles, walkRolloutFilesAsync } from './providers/rolloutWalker';
|
|
54
|
+
export { COMPRESSED_ROLLOUT_SUFFIX, isCompressedRolloutPath, isZstdDecompressionAvailable, readRolloutBuffer, readRolloutText, resolveRolloutPath, } from './providers/compressedRollouts';
|
|
54
55
|
// Parsers — JSONL
|
|
55
56
|
export { JsonlParser, TRUNCATION_PATTERNS } from './parsers/jsonl';
|
|
56
57
|
// Parsers — OpenCode
|
|
@@ -58,6 +59,7 @@ export { normalizeToolName, normalizeToolInput, detectPlanModeFromText, convertO
|
|
|
58
59
|
// Parsers — Codex
|
|
59
60
|
export { CodexRolloutParser, DEFAULT_MAX_TRACKED_TOOL_CALLS, extractPatchFilePaths, normalizeCodexToolName, normalizeCodexToolInput, resolveCodexCallUsage, } from './parsers/codexParser';
|
|
60
61
|
export { codexRolloutOrigin, codexSpawnParentFromSource } from './parsers/codexRolloutOrigin';
|
|
62
|
+
export { SUBAGENT_ACTIVITY_BLOCK_TYPE, SUBAGENT_ACTIVITY_ORIGIN_KIND, describeSubagentActivity, isSubagentActivitySystemEvent, parseCodexAgentMessageText, subagentActivityFromEvent, } from './subagentActivity';
|
|
61
63
|
// Parsers — Subagent scanning
|
|
62
64
|
export { scanSubagentDir, extractTaskInfo, readAgentMeta } from './parsers/subagentScanner';
|
|
63
65
|
// Usage — split-line dedupe, subagent tokens, session totals
|
|
@@ -80,13 +82,16 @@ export { listSessionPreviews, listSessionPreviewsAsync, readSessionPreview, read
|
|
|
80
82
|
export { parseDebugLog, filterByLevel, collapseDuplicates, discoverDebugLogs, } from './parsers/debugLogParser';
|
|
81
83
|
// Database wrappers
|
|
82
84
|
export { OpenCodeDatabase } from './providers/openCodeDatabase';
|
|
83
|
-
export { CodexDatabase } from './providers/codexDatabase';
|
|
85
|
+
export { CodexDatabase, isCodexSubagentThread } from './providers/codexDatabase';
|
|
84
86
|
// Search
|
|
85
87
|
export { searchSessions } from './search/sessionSearch';
|
|
86
88
|
export { FilterEngine } from './search/advancedFilter';
|
|
87
89
|
// Context
|
|
88
90
|
export { composeContext } from './context/composer';
|
|
89
91
|
export { buildSessionContextSnapshot, calculateSessionContextPressure, createSessionContextProjector, readSessionContextSnapshot, } from './context/sessionContext';
|
|
92
|
+
// Subagent spawns (`Agent` / `Task`) and background-task notifications
|
|
93
|
+
export { SUBAGENT_SPAWN_TOOL_NAMES, agentsStoppedNoticeFromEvent, isAgentsKilledSystemEvent, isCodexSubagentSpawnTool, isSubagentSpawnTool, isTaskNotificationSystemEvent, isTaskStatusSystemEvent, parseAgentsStoppedNotice, parseCodexSpawnLaunch, parseTaskNotification, taskNotificationFromEvent, taskStatusFromAttachment, taskStatusFromEvent, } from './subagentSpawn';
|
|
94
|
+
export { SUBAGENT_PARENT_IDLE_ENDED_MS, SUBAGENT_UNREPORTED_QUIET_MS, resolveSubagentDisplayState, subagentDisplayLabel, } from './subagentDisplayState';
|
|
90
95
|
// Assistant turn projection
|
|
91
96
|
export { assistantTurnEventsFromSessionEvents, extractTurnSubagents, isAssistantTurnSubagentTool, reasoningSummary, segmentAssistantTurn, } from './turns/assistantTurn';
|
|
92
97
|
// Plan Extraction
|
|
@@ -95,7 +100,7 @@ export { PlanExtractor, parsePlanMarkdown as parsePlanMarkdownShared, extractPro
|
|
|
95
100
|
export { parseChangelog } from './parsers/changelogParser';
|
|
96
101
|
export { createWatcher, resolveSessionPath } from './watchers/factory';
|
|
97
102
|
export { toFollowEvents } from './watchers/eventBridge';
|
|
98
|
-
export { createJsonlTail, DEFAULT_JSONL_TAIL_MAX_LINE_BYTES, DEFAULT_JSONL_TAIL_READ_BYTES, JSONL_TAIL_CHECKPOINT_VERSION, JsonlTailSymlinkError, } from './watchers/jsonlTail';
|
|
103
|
+
export { captureJsonlCheckpoint, createJsonlTail, DEFAULT_JSONL_TAIL_MAX_LINE_BYTES, DEFAULT_JSONL_TAIL_READ_BYTES, JSONL_TAIL_CHECKPOINT_VERSION, JsonlTailSymlinkError, verifyJsonlCheckpoint, } from './watchers/jsonlTail';
|
|
99
104
|
// Formatters
|
|
100
105
|
export { addLocalDays, formatDurationMs, formatLocalDateKey, formatTokenCount, parseLocalDateKey, } from './formatting';
|
|
101
106
|
export { formatToolSummary } from './formatters/toolSummary';
|
package/dist/esm/modelCatalog.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { setOwnValue } from './ownKey';
|
|
1
2
|
import { _getModelContextIds, _setImportedResolvedContextWindows, resolveModelContextWindow, } from './modelContext';
|
|
2
3
|
import { _getModelPricingIds, _setImportedResolvedPricing, resolveModelPricing, } from './modelInfo';
|
|
3
4
|
import { _getModelAliases, _importModelAliases, registerModelAlias, resolveModelAlias, } from './modelAliases';
|
|
@@ -24,13 +25,13 @@ export function exportResolvedModelCatalog(ids) {
|
|
|
24
25
|
const selectedIds = normalizedIds(ids);
|
|
25
26
|
for (const id of selectedIds) {
|
|
26
27
|
const canonicalId = resolveModelAlias(id);
|
|
27
|
-
models
|
|
28
|
+
setOwnValue(models, id, {
|
|
28
29
|
id,
|
|
29
30
|
canonicalId,
|
|
30
31
|
contextWindow: resolveModelContextWindow(id),
|
|
31
32
|
// Registered prices are this realm's local configuration, never exported.
|
|
32
33
|
pricing: resolveModelPricing(id, { includeRegistered: false }),
|
|
33
|
-
};
|
|
34
|
+
});
|
|
34
35
|
}
|
|
35
36
|
const selectedCanonicalIds = new Set(Object.values(models).flatMap(({ id, canonicalId }) => [id, canonicalId]));
|
|
36
37
|
const aliases = Object.fromEntries(Object.entries(_getModelAliases()).filter(([alias, canonicalId]) => selectedCanonicalIds.has(alias) || selectedCanonicalIds.has(canonicalId)));
|
|
@@ -75,9 +76,9 @@ export function importResolvedModelCatalog(snapshot) {
|
|
|
75
76
|
if (!id)
|
|
76
77
|
continue;
|
|
77
78
|
if (contextResolved)
|
|
78
|
-
contexts
|
|
79
|
+
setOwnValue(contexts, id, context);
|
|
79
80
|
if (pricingResolved)
|
|
80
|
-
prices
|
|
81
|
+
setOwnValue(prices, id, pricing);
|
|
81
82
|
}
|
|
82
83
|
imported++;
|
|
83
84
|
}
|
package/dist/esm/modelContext.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* webviews. Same pattern as `_setPricingOverrides` in `modelInfo.ts`.
|
|
29
29
|
*/
|
|
30
30
|
import { resolveModelAlias } from './modelAliases';
|
|
31
|
+
import { ownValue, setOwnValue } from './ownKey';
|
|
31
32
|
/** Known model context window sizes (in tokens); current models verified 2026-09-05. */
|
|
32
33
|
const MODEL_CONTEXT_SIZES = {
|
|
33
34
|
// Published API limits verified 2026-10-30; GLM's API documents 1M even
|
|
@@ -144,7 +145,7 @@ function normalizeOverrides(overrides) {
|
|
|
144
145
|
for (const [key, value] of Object.entries(overrides)) {
|
|
145
146
|
if (!Number.isFinite(value) || value <= 0)
|
|
146
147
|
continue;
|
|
147
|
-
out
|
|
148
|
+
setOwnValue(out, key.trim().toLowerCase(), value);
|
|
148
149
|
}
|
|
149
150
|
return out;
|
|
150
151
|
}
|
|
@@ -208,7 +209,7 @@ export function _getModelContextIds() {
|
|
|
208
209
|
// ── Lookup ──
|
|
209
210
|
/** Exact match against one table. */
|
|
210
211
|
function exactWindow(table, modelId) {
|
|
211
|
-
const hit = table
|
|
212
|
+
const hit = ownValue(table, modelId);
|
|
212
213
|
return hit === undefined ? null : hit;
|
|
213
214
|
}
|
|
214
215
|
function exactWindowMatch(table, modelId) {
|
|
@@ -256,7 +257,7 @@ export function resolveModelContextWindow(modelId) {
|
|
|
256
257
|
// Imported resolutions rank below local observed data: a window this realm
|
|
257
258
|
// saw first-hand is fresher than a snapshot transferred from another realm.
|
|
258
259
|
if (!observedExact) {
|
|
259
|
-
const imported = importedTable
|
|
260
|
+
const imported = ownValue(importedTable, stripped) ?? ownValue(importedTable, canonicalModelId);
|
|
260
261
|
if (imported) {
|
|
261
262
|
return { ...imported, modelId: rawModelId, canonicalModelId };
|
|
262
263
|
}
|
package/dist/esm/modelInfo.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { getModelContextWindowSize } from './modelContext';
|
|
25
25
|
import { resolveModelAlias } from './modelAliases';
|
|
26
|
+
import { ownValue } from './ownKey';
|
|
26
27
|
export { MODEL_PROVIDER_IDS } from './providerIds';
|
|
27
28
|
export { formatCost } from './formatting';
|
|
28
29
|
// ── Static Pricing Table ──
|
|
@@ -1078,11 +1079,11 @@ export function resolveModelPricing(modelId, options) {
|
|
|
1078
1079
|
});
|
|
1079
1080
|
if (registered && !registered.entry.onlyIfMissing)
|
|
1080
1081
|
return registeredResult(registered);
|
|
1081
|
-
const imported = importedTable
|
|
1082
|
+
const imported = ownValue(importedTable, rawId) ?? ownValue(importedTable, aliasId);
|
|
1082
1083
|
if (imported)
|
|
1083
1084
|
return { ...imported, modelId, canonicalModelId: aliasId };
|
|
1084
1085
|
for (const id of candidateIds) {
|
|
1085
|
-
const exactOverride = overrideTable
|
|
1086
|
+
const exactOverride = ownValue(overrideTable, id);
|
|
1086
1087
|
if (exactOverride) {
|
|
1087
1088
|
return {
|
|
1088
1089
|
modelId,
|
|
@@ -1098,7 +1099,7 @@ export function resolveModelPricing(modelId, options) {
|
|
|
1098
1099
|
}
|
|
1099
1100
|
}
|
|
1100
1101
|
for (const id of candidateIds) {
|
|
1101
|
-
const exactStatic = PRICING_TABLE
|
|
1102
|
+
const exactStatic = ownValue(PRICING_TABLE, id);
|
|
1102
1103
|
if (exactStatic) {
|
|
1103
1104
|
return {
|
|
1104
1105
|
modelId,
|
package/dist/esm/node.js
CHANGED
|
@@ -24,6 +24,7 @@ export { ACTIVE_SESSION_MTIME_THRESHOLD_MS, importSessionHistory, } from './usag
|
|
|
24
24
|
export { bucketUsage, summarizeUsageRows, usageBucketKey, weekKey } from './usage/usageReports';
|
|
25
25
|
export { findCodexRolloutFile } from './providers/codex';
|
|
26
26
|
export { walkRolloutFiles, walkRolloutFilesAsync } from './providers/rolloutWalker';
|
|
27
|
+
export { COMPRESSED_ROLLOUT_SUFFIX, isCompressedRolloutPath, isZstdDecompressionAvailable, readRolloutBuffer, readRolloutText, resolveRolloutPath, } from './providers/compressedRollouts';
|
|
27
28
|
export { readCodexHistory } from './parsers/codexHistory';
|
|
28
29
|
export { listSessionPreviews, listSessionPreviewsAsync, readSessionPreview, readSessionPreviewAsync, } from './sessionPreviews';
|
|
29
30
|
export { ObservedSessionCollector, observedSessionSourceFromProvider, fileFingerprint, fileFingerprintParts, } from './observedSessionCollector';
|
|
@@ -41,6 +42,7 @@ export { parseAuthJson as parseCodexAuthJson, readAuthIdentityFromRaw as readCod
|
|
|
41
42
|
export { atomicWriteJson, atomicWriteJsonSync } from './writers/atomic';
|
|
42
43
|
export { parseClaudeAuthStatus, parseCodexLoginStatus } from './authStatus';
|
|
43
44
|
export { codexRolloutOrigin, codexSpawnParentFromSource } from './parsers/codexRolloutOrigin';
|
|
45
|
+
export { SUBAGENT_ACTIVITY_BLOCK_TYPE, SUBAGENT_ACTIVITY_ORIGIN_KIND, describeSubagentActivity, isSubagentActivitySystemEvent, parseCodexAgentMessageText, subagentActivityFromEvent, } from './subagentActivity';
|
|
44
46
|
export { getPricingCatalogInfo, registerModelPricing, clearRegisteredModelPricing, } from './modelInfo';
|
|
45
47
|
export { fetchProviderServiceStatus } from './providerServiceStatus';
|
|
46
48
|
// Tool categories across providers
|
|
@@ -49,4 +51,4 @@ export { classifyTool, TOOL_CATEGORIES } from './toolCategory';
|
|
|
49
51
|
export { resolveWorkspaceRoots } from './workspaceRoots';
|
|
50
52
|
export { discoverWorktreeSiblings, resolveWorktreeMainRepo } from './parsers/sessionPathResolver';
|
|
51
53
|
// Bounded JSONL tail reader
|
|
52
|
-
export { createJsonlTail, DEFAULT_JSONL_TAIL_MAX_LINE_BYTES, DEFAULT_JSONL_TAIL_READ_BYTES, JSONL_TAIL_CHECKPOINT_VERSION, JsonlTailSymlinkError, } from './watchers/jsonlTail';
|
|
54
|
+
export { captureJsonlCheckpoint, createJsonlTail, DEFAULT_JSONL_TAIL_MAX_LINE_BYTES, DEFAULT_JSONL_TAIL_READ_BYTES, JSONL_TAIL_CHECKPOINT_VERSION, JsonlTailSymlinkError, verifyJsonlCheckpoint, } from './watchers/jsonlTail';
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
import { promises as fs } from 'node:fs';
|
|
33
33
|
import * as path from 'node:path';
|
|
34
34
|
import { _setObservedContextWindows, _getObservedContextWindows } from './modelContext';
|
|
35
|
+
import { setOwnValue } from './ownKey';
|
|
35
36
|
import { getConfigDir } from './paths';
|
|
36
37
|
import { updateJsonStoreAtomic } from './writers/atomic';
|
|
37
38
|
// ── Constants ──
|
|
@@ -80,7 +81,7 @@ function toOverrideMap(store, now = Date.now()) {
|
|
|
80
81
|
const observedAt = typeof entry.observedAt === 'string' ? Date.parse(entry.observedAt) : NaN;
|
|
81
82
|
if (Number.isFinite(observedAt) && now - observedAt > OBSERVED_CONTEXT_WINDOW_TTL_MS)
|
|
82
83
|
continue;
|
|
83
|
-
out
|
|
84
|
+
setOwnValue(out, modelId, window);
|
|
84
85
|
}
|
|
85
86
|
return out;
|
|
86
87
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Own-property lookups for plain-object tables indexed by external strings.
|
|
3
|
+
*
|
|
4
|
+
* `table[key]` also finds inherited members, so a model id, tool name, or
|
|
5
|
+
* error code such as `constructor`, `toString`, or `__proto__` would return an
|
|
6
|
+
* `Object.prototype` member instead of `undefined`.
|
|
7
|
+
*
|
|
8
|
+
* @module ownKey
|
|
9
|
+
*/
|
|
10
|
+
const hasOwn = Object.prototype.hasOwnProperty;
|
|
11
|
+
/** The value stored under `key` in `table` itself, or `undefined`. */
|
|
12
|
+
export function ownValue(table, key) {
|
|
13
|
+
return hasOwn.call(table, key) ? table[key] : undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Store `value` as an own, enumerable property, even for `__proto__`, which a
|
|
17
|
+
* plain assignment would treat as a prototype change.
|
|
18
|
+
*/
|
|
19
|
+
export function setOwnValue(table, key, value) {
|
|
20
|
+
Object.defineProperty(table, key, {
|
|
21
|
+
value,
|
|
22
|
+
enumerable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
});
|
|
26
|
+
}
|