devchain-cli 0.17.0 → 0.17.2
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/dist/node_modules/@devchain/codebase-overview/tsconfig.tsbuildinfo +1 -1
- package/dist/node_modules/@devchain/shared/tsconfig.tsbuildinfo +1 -1
- package/dist/server/app.main.module.js +2 -0
- package/dist/server/app.main.module.js.map +1 -1
- package/dist/server/app.normal.module.js +2 -0
- package/dist/server/app.normal.module.js.map +1 -1
- package/dist/server/modules/events/catalog/broadcast-registry.js +19 -12
- package/dist/server/modules/events/catalog/broadcast-registry.js.map +1 -1
- package/dist/server/modules/events/catalog/index.d.ts +23 -3
- package/dist/server/modules/events/catalog/session.transcript.updated.d.ts +23 -3
- package/dist/server/modules/events/catalog/session.transcript.updated.js +41 -19
- package/dist/server/modules/events/catalog/session.transcript.updated.js.map +1 -1
- package/dist/server/modules/metrics/controllers/metrics.controller.d.ts +6 -0
- package/dist/server/modules/metrics/controllers/metrics.controller.js +38 -0
- package/dist/server/modules/metrics/controllers/metrics.controller.js.map +1 -0
- package/dist/server/modules/metrics/helpers/byte-accounting.helper.d.ts +17 -0
- package/dist/server/modules/metrics/helpers/byte-accounting.helper.js +88 -0
- package/dist/server/modules/metrics/helpers/byte-accounting.helper.js.map +1 -0
- package/dist/server/modules/metrics/metrics.module.d.ts +2 -0
- package/dist/server/modules/metrics/metrics.module.js +23 -0
- package/dist/server/modules/metrics/metrics.module.js.map +1 -0
- package/dist/server/modules/metrics/services/metrics.service.d.ts +20 -0
- package/dist/server/modules/metrics/services/metrics.service.js +241 -0
- package/dist/server/modules/metrics/services/metrics.service.js.map +1 -0
- package/dist/server/modules/metrics/types/metrics.types.d.ts +100 -0
- package/dist/server/modules/metrics/types/metrics.types.js +3 -0
- package/dist/server/modules/metrics/types/metrics.types.js.map +1 -0
- package/dist/server/modules/projects/helpers/profile-mapping.helpers.d.ts +5 -3
- package/dist/server/modules/projects/helpers/profile-mapping.helpers.js +25 -13
- package/dist/server/modules/projects/helpers/profile-mapping.helpers.js.map +1 -1
- package/dist/server/modules/projects/helpers/project-import.js +9 -7
- package/dist/server/modules/projects/helpers/project-import.js.map +1 -1
- package/dist/server/modules/projects/helpers/template-loader.js +11 -9
- package/dist/server/modules/projects/helpers/template-loader.js.map +1 -1
- package/dist/server/modules/projects/template-codec/codec-registry.js.map +1 -1
- package/dist/server/modules/projects/template-codec/codecs/agents.codec.d.ts +1 -1
- package/dist/server/modules/projects/template-codec/codecs/agents.codec.js +4 -4
- package/dist/server/modules/projects/template-codec/codecs/agents.codec.js.map +1 -1
- package/dist/server/modules/projects/template-codec/codecs/profiles.codec.d.ts +1 -1
- package/dist/server/modules/projects/template-codec/codecs/profiles.codec.js +27 -8
- package/dist/server/modules/projects/template-codec/codecs/profiles.codec.js.map +1 -1
- package/dist/server/modules/projects/template-codec/codecs/watchers.codec.js +1 -1
- package/dist/server/modules/projects/template-codec/codecs/watchers.codec.js.map +1 -1
- package/dist/server/modules/projects/template-codec/import-context.d.ts +6 -1
- package/dist/server/modules/projects/template-codec/import-context.js.map +1 -1
- package/dist/server/modules/projects/template-codec/template-section-codec.d.ts +2 -3
- package/dist/server/modules/session-reader/adapters/antigravity-session-reader.adapter.d.ts +6 -0
- package/dist/server/modules/session-reader/adapters/antigravity-session-reader.adapter.js +66 -0
- package/dist/server/modules/session-reader/adapters/antigravity-session-reader.adapter.js.map +1 -1
- package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.d.ts +5 -0
- package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.js +12 -0
- package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.js.map +1 -1
- package/dist/server/modules/session-reader/adapters/codex-session-reader.adapter.d.ts +5 -0
- package/dist/server/modules/session-reader/adapters/codex-session-reader.adapter.js +12 -0
- package/dist/server/modules/session-reader/adapters/codex-session-reader.adapter.js.map +1 -1
- package/dist/server/modules/session-reader/adapters/copilot-session-reader.adapter.d.ts +5 -0
- package/dist/server/modules/session-reader/adapters/copilot-session-reader.adapter.js +12 -0
- package/dist/server/modules/session-reader/adapters/copilot-session-reader.adapter.js.map +1 -1
- package/dist/server/modules/session-reader/adapters/opencode-session-reader.adapter.d.ts +5 -0
- package/dist/server/modules/session-reader/adapters/opencode-session-reader.adapter.js +15 -0
- package/dist/server/modules/session-reader/adapters/opencode-session-reader.adapter.js.map +1 -1
- package/dist/server/modules/session-reader/adapters/session-reader-adapter.interface.d.ts +10 -0
- package/dist/server/modules/session-reader/adapters/session-reader-adapter.interface.js +28 -1
- package/dist/server/modules/session-reader/adapters/session-reader-adapter.interface.js.map +1 -1
- package/dist/server/modules/session-reader/controllers/session-reader.controller.d.ts +10 -4
- package/dist/server/modules/session-reader/controllers/session-reader.controller.js +18 -19
- package/dist/server/modules/session-reader/controllers/session-reader.controller.js.map +1 -1
- package/dist/server/modules/session-reader/data/pricing.json +206 -0
- package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.d.ts +1 -0
- package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.js +7 -3
- package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.js.map +1 -1
- package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.d.ts +1 -0
- package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.js +22 -11
- package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.js.map +1 -1
- package/dist/server/modules/session-reader/parsers/copilot-jsonl.parser.d.ts +1 -0
- package/dist/server/modules/session-reader/parsers/copilot-jsonl.parser.js +21 -9
- package/dist/server/modules/session-reader/parsers/copilot-jsonl.parser.js.map +1 -1
- package/dist/server/modules/session-reader/readers/opencode-sqlite.reader.d.ts +5 -1
- package/dist/server/modules/session-reader/readers/opencode-sqlite.reader.js +102 -0
- package/dist/server/modules/session-reader/readers/opencode-sqlite.reader.js.map +1 -1
- package/dist/server/modules/session-reader/services/session-cache.service.d.ts +71 -4
- package/dist/server/modules/session-reader/services/session-cache.service.js +427 -68
- package/dist/server/modules/session-reader/services/session-cache.service.js.map +1 -1
- package/dist/server/modules/session-reader/services/session-reader.service.d.ts +21 -4
- package/dist/server/modules/session-reader/services/session-reader.service.js +126 -40
- package/dist/server/modules/session-reader/services/session-reader.service.js.map +1 -1
- package/dist/server/modules/session-reader/services/transcript-cursor.d.ts +1 -1
- package/dist/server/modules/session-reader/services/transcript-cursor.js +2 -2
- package/dist/server/modules/session-reader/services/transcript-cursor.js.map +1 -1
- package/dist/server/modules/session-reader/services/transcript-watcher.service.d.ts +2 -0
- package/dist/server/modules/session-reader/services/transcript-watcher.service.js +61 -11
- package/dist/server/modules/session-reader/services/transcript-watcher.service.js.map +1 -1
- package/dist/server/modules/session-reader/session-reader.module.js +3 -1
- package/dist/server/modules/session-reader/session-reader.module.js.map +1 -1
- package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.d.ts +4 -4
- package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js +11 -5
- package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js.map +1 -1
- package/dist/server/modules/sessions/services/session-runtime/session-restore-pipeline.service.d.ts +3 -1
- package/dist/server/modules/sessions/services/session-runtime/session-restore-pipeline.service.js +11 -2
- package/dist/server/modules/sessions/services/session-runtime/session-restore-pipeline.service.js.map +1 -1
- package/dist/server/modules/terminal/dtos/ws-envelope.dto.d.ts +140 -1
- package/dist/server/modules/terminal/dtos/ws-envelope.dto.js +59 -2
- package/dist/server/modules/terminal/dtos/ws-envelope.dto.js.map +1 -1
- package/dist/server/modules/terminal/gateways/terminal.gateway.d.ts +33 -1
- package/dist/server/modules/terminal/gateways/terminal.gateway.js +471 -61
- package/dist/server/modules/terminal/gateways/terminal.gateway.js.map +1 -1
- package/dist/server/modules/terminal/services/process-executor/fake-process-executor.d.ts +1 -0
- package/dist/server/modules/terminal/services/process-executor/fake-process-executor.js +1 -0
- package/dist/server/modules/terminal/services/process-executor/fake-process-executor.js.map +1 -1
- package/dist/server/modules/terminal/services/pty.service.d.ts +8 -3
- package/dist/server/modules/terminal/services/pty.service.js +11 -2
- package/dist/server/modules/terminal/services/pty.service.js.map +1 -1
- package/dist/server/modules/terminal/services/terminal-io/capture.js +2 -0
- package/dist/server/modules/terminal/services/terminal-io/capture.js.map +1 -1
- package/dist/server/modules/terminal/services/terminal-io/delivery.d.ts +1 -0
- package/dist/server/modules/terminal/services/terminal-io/delivery.js.map +1 -1
- package/dist/server/modules/terminal/services/terminal-io/terminal-io.service.js +28 -1
- package/dist/server/modules/terminal/services/terminal-io/terminal-io.service.js.map +1 -1
- package/dist/server/modules/terminal/services/terminal-key-input/terminal-key-input.facade.d.ts +6 -0
- package/dist/server/modules/terminal/services/terminal-key-input/terminal-key-input.facade.js +26 -1
- package/dist/server/modules/terminal/services/terminal-key-input/terminal-key-input.facade.js.map +1 -1
- package/dist/server/modules/terminal/services/terminal-seed.service.d.ts +27 -3
- package/dist/server/modules/terminal/services/terminal-seed.service.js +75 -19
- package/dist/server/modules/terminal/services/terminal-seed.service.js.map +1 -1
- package/dist/server/modules/terminal/services/terminal-send-scheduler.service.d.ts +73 -0
- package/dist/server/modules/terminal/services/terminal-send-scheduler.service.js +387 -0
- package/dist/server/modules/terminal/services/terminal-send-scheduler.service.js.map +1 -0
- package/dist/server/modules/terminal/services/terminal-session/terminal-session.d.ts +4 -10
- package/dist/server/modules/terminal/services/terminal-session/terminal-session.js +20 -48
- package/dist/server/modules/terminal/services/terminal-session/terminal-session.js.map +1 -1
- package/dist/server/modules/terminal/services/terminal-socket-drain.adapter.d.ts +10 -0
- package/dist/server/modules/terminal/services/terminal-socket-drain.adapter.js +75 -0
- package/dist/server/modules/terminal/services/terminal-socket-drain.adapter.js.map +1 -0
- package/dist/server/modules/terminal/services/terminal-stream.service.d.ts +53 -6
- package/dist/server/modules/terminal/services/terminal-stream.service.js +190 -35
- package/dist/server/modules/terminal/services/terminal-stream.service.js.map +1 -1
- package/dist/server/modules/terminal/terminal.module.js +7 -0
- package/dist/server/modules/terminal/terminal.module.js.map +1 -1
- package/dist/server/templates/3-agents-dev.json +97 -93
- package/dist/server/templates/teams-dev.json +174 -160
- package/dist/server/tsconfig.tsbuildinfo +1 -1
- package/dist/server/ui/assets/{ReviewDetailPage-Dd40lXMW.js → ReviewDetailPage-C5BI5FSx.js} +1 -1
- package/dist/server/ui/assets/{ReviewsPage-CBSbsimJ.js → ReviewsPage-C4V7MJQc.js} +1 -1
- package/dist/server/ui/assets/index-BSx7QxmM.css +32 -0
- package/dist/server/ui/assets/index-nwIXVn_B.js +1142 -0
- package/dist/server/ui/assets/{useReviewSubscription-CpDyx3vJ.js → useReviewSubscription-Bd1R13V5.js} +1 -1
- package/dist/server/ui/index.html +2 -2
- package/dist/templates/3-agents-dev.json +97 -93
- package/dist/templates/teams-dev.json +174 -160
- package/package.json +16 -5
- package/dist/server/ui/assets/index-COvyD_66.css +0 -32
- package/dist/server/ui/assets/index-DtIjl-Lt.js +0 -1113
|
@@ -38,76 +38,228 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
return result;
|
|
39
39
|
};
|
|
40
40
|
})();
|
|
41
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
42
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
43
|
+
};
|
|
44
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
45
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
46
|
+
};
|
|
41
47
|
var SessionCacheService_1;
|
|
42
48
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
-
exports.SessionCacheService = void 0;
|
|
49
|
+
exports.SessionCacheService = exports.TRANSCRIPT_CACHE_CONFIG = void 0;
|
|
50
|
+
exports.getTranscriptCacheConfig = getTranscriptCacheConfig;
|
|
44
51
|
const common_1 = require("@nestjs/common");
|
|
52
|
+
const node_crypto_1 = require("node:crypto");
|
|
45
53
|
const fs = __importStar(require("node:fs/promises"));
|
|
46
54
|
const estimate_content_tokens_1 = require("../adapters/utils/estimate-content-tokens");
|
|
47
55
|
const tool_result_fold_1 = require("../adapters/utils/tool-result-fold");
|
|
48
56
|
const coalesce_turns_1 = require("../adapters/utils/coalesce-turns");
|
|
49
|
-
const
|
|
50
|
-
const
|
|
57
|
+
const metrics_service_1 = require("../../metrics/services/metrics.service");
|
|
58
|
+
const DEFAULT_CACHE_IDLE_TTL_MS = 10 * 60 * 1_000;
|
|
59
|
+
const DEFAULT_CACHE_SWEEP_INTERVAL_MS = 60_000;
|
|
60
|
+
const DEFAULT_CACHE_BUDGET_BYTES = 64 * 1024 * 1024;
|
|
61
|
+
const SAFE_INTEGER_LOW_BITS = 0x20_0000;
|
|
62
|
+
const FILE_HASH_CHUNK_BYTES = 64 * 1024;
|
|
63
|
+
function fileSourceVersion(stat) {
|
|
64
|
+
const digest = (0, node_crypto_1.createHash)('sha256')
|
|
65
|
+
.update(`${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeMs}`)
|
|
66
|
+
.digest();
|
|
67
|
+
return digest.readUInt32BE(0) * SAFE_INTEGER_LOW_BITS + (digest.readUInt32BE(4) >>> 11);
|
|
68
|
+
}
|
|
69
|
+
exports.TRANSCRIPT_CACHE_CONFIG = Symbol('TRANSCRIPT_CACHE_CONFIG');
|
|
70
|
+
function getTranscriptCacheConfig() {
|
|
71
|
+
return {
|
|
72
|
+
budgetBytes: positiveIntegerFromEnv(process.env.TRANSCRIPT_CACHE_MAX_BYTES, DEFAULT_CACHE_BUDGET_BYTES),
|
|
73
|
+
idleTtlMs: positiveIntegerFromEnv(process.env.TRANSCRIPT_CACHE_IDLE_TTL_MS, DEFAULT_CACHE_IDLE_TTL_MS),
|
|
74
|
+
sweepIntervalMs: positiveIntegerFromEnv(process.env.TRANSCRIPT_CACHE_SWEEP_INTERVAL_MS, DEFAULT_CACHE_SWEEP_INTERVAL_MS),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
51
77
|
let SessionCacheService = SessionCacheService_1 = class SessionCacheService {
|
|
52
|
-
constructor() {
|
|
78
|
+
constructor(metricsService, config) {
|
|
79
|
+
this.metricsService = metricsService;
|
|
53
80
|
this.logger = new common_1.Logger(SessionCacheService_1.name);
|
|
54
81
|
this.cache = new Map();
|
|
82
|
+
this.hits = 0;
|
|
83
|
+
this.misses = 0;
|
|
84
|
+
this.chunksHits = 0;
|
|
85
|
+
this.chunksMisses = 0;
|
|
86
|
+
this.dtoHits = 0;
|
|
87
|
+
this.dtoMisses = 0;
|
|
88
|
+
this.budgetUsedBytes = 0;
|
|
89
|
+
this.evictions = 0;
|
|
90
|
+
this.config = config ?? getTranscriptCacheConfig();
|
|
91
|
+
}
|
|
92
|
+
onModuleInit() {
|
|
93
|
+
this.metricsService.registerCacheStatsProvider('parsed', () => this.getCacheStats(), () => Array.from(this.cache.values(), (entry) => entry.session));
|
|
94
|
+
this.metricsService.registerCacheStatsProvider('chunks', () => this.getChunksCacheStats(), () => this.getChunksRetainedRoots());
|
|
95
|
+
this.idleSweepTimer = setInterval(() => this.sweepIdleEntries(), this.config.sweepIntervalMs);
|
|
96
|
+
this.idleSweepTimer.unref();
|
|
55
97
|
}
|
|
56
98
|
onModuleDestroy() {
|
|
57
|
-
this.
|
|
99
|
+
if (this.idleSweepTimer) {
|
|
100
|
+
clearInterval(this.idleSweepTimer);
|
|
101
|
+
this.idleSweepTimer = undefined;
|
|
102
|
+
}
|
|
103
|
+
this.clear();
|
|
104
|
+
}
|
|
105
|
+
getCacheStats() {
|
|
106
|
+
const total = this.hits + this.misses;
|
|
107
|
+
return {
|
|
108
|
+
entries: this.cache.size,
|
|
109
|
+
bytesEstimated: 0,
|
|
110
|
+
hits: this.hits,
|
|
111
|
+
misses: this.misses,
|
|
112
|
+
hitRate: total > 0 ? this.hits / total : 0,
|
|
113
|
+
bytesMethod: 'deferred-to-aggregate',
|
|
114
|
+
budgetUsedBytes: this.budgetUsedBytes,
|
|
115
|
+
budgetBytes: this.config.budgetBytes,
|
|
116
|
+
evictions: this.evictions,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
getChunksCacheStats() {
|
|
120
|
+
const total = this.chunksHits + this.chunksMisses;
|
|
121
|
+
let entries = 0;
|
|
122
|
+
for (const entry of this.cache.values()) {
|
|
123
|
+
if (entry.chunks)
|
|
124
|
+
entries += 1;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
entries,
|
|
128
|
+
bytesEstimated: 0,
|
|
129
|
+
hits: this.chunksHits,
|
|
130
|
+
misses: this.chunksMisses,
|
|
131
|
+
hitRate: total > 0 ? this.chunksHits / total : 0,
|
|
132
|
+
bytesMethod: 'deferred-to-aggregate',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
getDtoCacheStats() {
|
|
136
|
+
let entries = 0;
|
|
137
|
+
let bytesEstimated = 0;
|
|
138
|
+
for (const entry of this.cache.values()) {
|
|
139
|
+
if (!entry.dto)
|
|
140
|
+
continue;
|
|
141
|
+
entries += 1;
|
|
142
|
+
bytesEstimated += entry.dto.responseBytes;
|
|
143
|
+
}
|
|
144
|
+
const total = this.dtoHits + this.dtoMisses;
|
|
145
|
+
return {
|
|
146
|
+
entries,
|
|
147
|
+
bytesEstimated,
|
|
148
|
+
hits: this.dtoHits,
|
|
149
|
+
misses: this.dtoMisses,
|
|
150
|
+
hitRate: total > 0 ? this.dtoHits / total : 0,
|
|
151
|
+
bytesMethod: 'json-stringify-length',
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
async getFreshSession(sessionId, source, adapter) {
|
|
155
|
+
const cached = this.cache.get(sessionId);
|
|
156
|
+
if (!cached)
|
|
157
|
+
return undefined;
|
|
158
|
+
const ref = this.toSourceRef(source, adapter);
|
|
159
|
+
const freshness = await this.computeFreshness(ref, adapter);
|
|
160
|
+
return this.takeFreshCachedSession(sessionId, cached, freshness.token, Date.now());
|
|
58
161
|
}
|
|
59
162
|
async getOrParse(sessionId, source, adapter) {
|
|
163
|
+
return (await this.getOrParseResult(sessionId, source, adapter)).session;
|
|
164
|
+
}
|
|
165
|
+
async getOrParseWithMeta(sessionId, source, adapter) {
|
|
166
|
+
return this.getOrParseResult(sessionId, source, adapter);
|
|
167
|
+
}
|
|
168
|
+
async getOrParseResult(sessionId, source, adapter) {
|
|
60
169
|
const now = Date.now();
|
|
61
170
|
const ref = this.toSourceRef(source, adapter);
|
|
62
171
|
const threadRef = typeof source !== 'string' ? ref : undefined;
|
|
63
|
-
|
|
172
|
+
let freshness = await this.computeFreshness(ref, adapter);
|
|
64
173
|
const cached = this.cache.get(sessionId);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
this.logger.debug({ sessionId }, 'Session cache hit');
|
|
69
|
-
this.touchLru(sessionId, cached);
|
|
70
|
-
return cached.session;
|
|
174
|
+
const freshSession = this.takeFreshCachedSession(sessionId, cached, freshness.token, now);
|
|
175
|
+
if (freshSession && cached) {
|
|
176
|
+
return this.toGetOrParseResult(freshSession, cached, true, 'cache-hit');
|
|
71
177
|
}
|
|
178
|
+
this.misses += 1;
|
|
72
179
|
let session;
|
|
73
180
|
let lastOffset;
|
|
74
181
|
let boundaryFold = false;
|
|
75
|
-
|
|
182
|
+
let acceptedFileDigests;
|
|
183
|
+
const sameFileIdentity = ref.kind !== 'file' ||
|
|
184
|
+
(cached?.fileIdentity !== undefined && cached.fileIdentity === freshness.fileIdentity);
|
|
185
|
+
const fileReplaced = cached !== undefined && ref.kind === 'file' && !sameFileIdentity;
|
|
186
|
+
const sameIdentityGrowth = cached !== undefined &&
|
|
187
|
+
ref.kind === 'file' &&
|
|
188
|
+
sameFileIdentity &&
|
|
189
|
+
freshness.size > cached.lastSize;
|
|
190
|
+
const growthDigests = sameIdentityGrowth && cached.fileContentDigest !== undefined
|
|
191
|
+
? await this.tryHashFileContent(ref.filePath, freshness, cached.lastOffset)
|
|
192
|
+
: undefined;
|
|
193
|
+
const provenSameFileAppend = sameIdentityGrowth &&
|
|
194
|
+
cached.fileContentDigest !== undefined &&
|
|
195
|
+
growthDigests?.prefixDigest === cached.fileContentDigest;
|
|
196
|
+
let sourceChangeKind = !cached
|
|
197
|
+
? 'unknown-full-parse'
|
|
198
|
+
: ref.kind === 'db'
|
|
199
|
+
? 'db-update'
|
|
200
|
+
: fileReplaced
|
|
201
|
+
? 'file-replacement'
|
|
202
|
+
: freshness.size > cached.lastSize
|
|
203
|
+
? provenSameFileAppend
|
|
204
|
+
? 'same-file-append'
|
|
205
|
+
: 'same-file-rewrite'
|
|
206
|
+
: freshness.size < cached.lastSize
|
|
207
|
+
? 'file-truncation'
|
|
208
|
+
: 'same-file-rewrite';
|
|
209
|
+
if (cached && provenSameFileAppend && growthDigests) {
|
|
76
210
|
this.logger.debug({ sessionId, lastOffset: cached.lastOffset, currentSize: freshness.size }, 'Incremental parse (source grew)');
|
|
77
211
|
const incOptions = { byteOffset: cached.lastOffset, includeToolCalls: true };
|
|
78
|
-
|
|
212
|
+
let tentativeResult = threadRef
|
|
79
213
|
? await adapter.parseIncremental(ref.filePath, incOptions, threadRef)
|
|
80
214
|
: await adapter.parseIncremental(ref.filePath, incOptions);
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
215
|
+
const postParseDigests = await this.tryHashFileContent(ref.filePath, freshness, freshness.size);
|
|
216
|
+
if (postParseDigests?.fullDigest !== growthDigests.fullDigest) {
|
|
217
|
+
this.logger.debug({ sessionId }, 'Discarding incremental parse because its proven file revision drifted');
|
|
218
|
+
tentativeResult = undefined;
|
|
219
|
+
freshness = await this.computeFreshness(ref, adapter);
|
|
220
|
+
sourceChangeKind = this.classifyUnsafeFileChange(cached, freshness);
|
|
221
|
+
session = threadRef
|
|
222
|
+
? await adapter.parseFullSession(ref.filePath, threadRef)
|
|
223
|
+
: await adapter.parseFullSession(ref.filePath);
|
|
224
|
+
lastOffset = freshness.size;
|
|
91
225
|
}
|
|
92
226
|
else {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
227
|
+
const acceptedResult = tentativeResult;
|
|
228
|
+
acceptedFileDigests = postParseDigests;
|
|
229
|
+
const newMessages = acceptedResult.entries;
|
|
230
|
+
if (adapter.incrementalMode === 'snapshot') {
|
|
231
|
+
const snapshotMetrics = acceptedResult.metrics ?? cached.session.metrics;
|
|
232
|
+
session = {
|
|
233
|
+
...cached.session,
|
|
234
|
+
messages: newMessages,
|
|
235
|
+
metrics: snapshotMetrics,
|
|
236
|
+
isOngoing: snapshotMetrics.isOngoing,
|
|
237
|
+
warnings: this.mergeWarnings(cached.session.warnings, acceptedResult.warnings),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
const folded = this.foldLeadingContinuationIntoCachedTail(cached.session.messages, newMessages);
|
|
242
|
+
boundaryFold = folded.tailMutatedWithoutNewMessage;
|
|
243
|
+
const mergedMessages = folded.merged;
|
|
244
|
+
const mergedMetrics = acceptedResult.metrics
|
|
245
|
+
? this.mergeMetrics(cached.session.metrics, acceptedResult.metrics, mergedMessages)
|
|
246
|
+
: cached.session.metrics;
|
|
247
|
+
session = {
|
|
248
|
+
...cached.session,
|
|
249
|
+
messages: mergedMessages,
|
|
250
|
+
metrics: mergedMetrics,
|
|
251
|
+
isOngoing: mergedMetrics.isOngoing,
|
|
252
|
+
warnings: this.mergeWarnings(cached.session.warnings, acceptedResult.warnings),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
lastOffset = acceptedResult.nextByteOffset;
|
|
106
256
|
}
|
|
107
|
-
lastOffset = result.nextByteOffset;
|
|
108
257
|
}
|
|
109
258
|
else {
|
|
110
|
-
if (
|
|
259
|
+
if (fileReplaced) {
|
|
260
|
+
this.logger.debug({ sessionId }, 'Full reparse (source replaced)');
|
|
261
|
+
}
|
|
262
|
+
else if (cached && freshness.size < cached.lastSize) {
|
|
111
263
|
this.logger.debug({ sessionId }, 'Full reparse (source truncated)');
|
|
112
264
|
}
|
|
113
265
|
session = threadRef
|
|
@@ -119,58 +271,175 @@ let SessionCacheService = SessionCacheService_1 = class SessionCacheService {
|
|
|
119
271
|
if (coalesced.messages !== session.messages) {
|
|
120
272
|
session = { ...session, messages: coalesced.messages, metrics: coalesced.metrics };
|
|
121
273
|
}
|
|
122
|
-
this.
|
|
123
|
-
|
|
124
|
-
|
|
274
|
+
session = this.withoutDerivedChunks(session);
|
|
275
|
+
const fileContentDigest = ref.kind === 'file'
|
|
276
|
+
? lastOffset === freshness.size && acceptedFileDigests !== undefined
|
|
277
|
+
? acceptedFileDigests.fullDigest
|
|
278
|
+
: (await this.tryHashFileContent(ref.filePath, freshness, lastOffset))?.prefixDigest
|
|
279
|
+
: undefined;
|
|
280
|
+
this.deleteEntry(sessionId);
|
|
281
|
+
const sourceWeightBytes = this.estimateSourceWeight(ref, session, freshness.size);
|
|
282
|
+
const entry = {
|
|
125
283
|
session,
|
|
126
284
|
lastOffset,
|
|
127
285
|
lastSize: freshness.size,
|
|
128
286
|
lastMtime: freshness.mtimeMs,
|
|
287
|
+
fileIdentity: freshness.fileIdentity,
|
|
288
|
+
fileContentDigest,
|
|
129
289
|
sourceVersion: freshness.sourceVersion,
|
|
130
290
|
freshnessToken: freshness.token,
|
|
131
|
-
|
|
291
|
+
lastAccessedAt: now,
|
|
292
|
+
sourceWeightBytes,
|
|
293
|
+
weights: { parsed: sourceWeightBytes * 2, chunks: 0, dto: 0 },
|
|
132
294
|
boundaryFold,
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const prevSession = this.cache.get(sessionId)?.session;
|
|
138
|
-
const session = await this.getOrParse(sessionId, source, adapter);
|
|
139
|
-
const entry = this.cache.get(sessionId);
|
|
295
|
+
};
|
|
296
|
+
this.cache.set(sessionId, entry);
|
|
297
|
+
this.budgetUsedBytes += entry.weights.parsed;
|
|
298
|
+
this.enforceBudget();
|
|
140
299
|
return {
|
|
141
300
|
session,
|
|
142
|
-
cacheHit:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
301
|
+
cacheHit: false,
|
|
302
|
+
sourceChangeKind,
|
|
303
|
+
lastOffset,
|
|
304
|
+
lastSize: freshness.size,
|
|
305
|
+
lastMtime: freshness.mtimeMs,
|
|
306
|
+
sourceVersion: freshness.sourceVersion,
|
|
307
|
+
boundaryFold,
|
|
148
308
|
};
|
|
149
309
|
}
|
|
150
310
|
getEntry(sessionId) {
|
|
151
311
|
return this.cache.get(sessionId);
|
|
152
312
|
}
|
|
313
|
+
getChunks(sessionId, sourceVersion) {
|
|
314
|
+
const entry = this.cache.get(sessionId);
|
|
315
|
+
if (!entry?.chunks || entry.chunks.sourceVersion !== sourceVersion) {
|
|
316
|
+
this.chunksMisses += 1;
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
this.chunksHits += 1;
|
|
320
|
+
this.touchLru(sessionId, entry, Date.now());
|
|
321
|
+
return entry.chunks.chunks;
|
|
322
|
+
}
|
|
323
|
+
setChunks(sessionId, sourceVersion, chunks) {
|
|
324
|
+
const entry = this.cache.get(sessionId);
|
|
325
|
+
if (!entry || entry.sourceVersion !== sourceVersion)
|
|
326
|
+
return;
|
|
327
|
+
this.budgetUsedBytes -= entry.weights.chunks;
|
|
328
|
+
entry.chunks = { chunks, sourceVersion };
|
|
329
|
+
entry.weights.chunks = entry.sourceWeightBytes;
|
|
330
|
+
this.budgetUsedBytes += entry.weights.chunks;
|
|
331
|
+
this.touchLru(sessionId, entry, Date.now());
|
|
332
|
+
this.enforceBudget();
|
|
333
|
+
}
|
|
334
|
+
getDto(sessionId, maxToolResultLength, enrichmentFingerprint) {
|
|
335
|
+
const entry = this.cache.get(sessionId);
|
|
336
|
+
const dto = entry?.dto;
|
|
337
|
+
if (!entry ||
|
|
338
|
+
!dto ||
|
|
339
|
+
dto.maxToolResultLength !== maxToolResultLength ||
|
|
340
|
+
dto.enrichmentFingerprint !== enrichmentFingerprint) {
|
|
341
|
+
this.dtoMisses += 1;
|
|
342
|
+
return undefined;
|
|
343
|
+
}
|
|
344
|
+
this.dtoHits += 1;
|
|
345
|
+
this.touchLru(sessionId, entry, Date.now());
|
|
346
|
+
return dto;
|
|
347
|
+
}
|
|
348
|
+
setDto(sessionId, dto) {
|
|
349
|
+
const entry = this.cache.get(sessionId);
|
|
350
|
+
if (!entry)
|
|
351
|
+
return;
|
|
352
|
+
this.budgetUsedBytes -= entry.weights.dto;
|
|
353
|
+
entry.dto = dto;
|
|
354
|
+
entry.weights.dto = dto.responseBytes;
|
|
355
|
+
this.budgetUsedBytes += entry.weights.dto;
|
|
356
|
+
this.touchLru(sessionId, entry, Date.now());
|
|
357
|
+
this.enforceBudget();
|
|
358
|
+
}
|
|
359
|
+
getDtoRetainedRoots() {
|
|
360
|
+
return Array.from(this.cache.values(), (entry) => entry.dto?.result);
|
|
361
|
+
}
|
|
362
|
+
getChunksRetainedRoots() {
|
|
363
|
+
return Array.from(this.cache.values(), (entry) => entry.chunks?.chunks);
|
|
364
|
+
}
|
|
365
|
+
sweepIdleEntries(now = Date.now()) {
|
|
366
|
+
let swept = 0;
|
|
367
|
+
for (const [sessionId, entry] of this.cache) {
|
|
368
|
+
if (now - entry.lastAccessedAt < this.config.idleTtlMs)
|
|
369
|
+
continue;
|
|
370
|
+
this.deleteEntry(sessionId);
|
|
371
|
+
swept += 1;
|
|
372
|
+
}
|
|
373
|
+
return swept;
|
|
374
|
+
}
|
|
153
375
|
invalidate(sessionId) {
|
|
154
|
-
this.
|
|
376
|
+
this.deleteEntry(sessionId);
|
|
155
377
|
}
|
|
156
378
|
clear() {
|
|
157
379
|
this.cache.clear();
|
|
380
|
+
this.budgetUsedBytes = 0;
|
|
158
381
|
}
|
|
159
382
|
get size() {
|
|
160
383
|
return this.cache.size;
|
|
161
384
|
}
|
|
162
|
-
|
|
385
|
+
takeFreshCachedSession(sessionId, cached, freshnessToken, now) {
|
|
386
|
+
if (!cached || JSON.stringify(cached.freshnessToken) !== JSON.stringify(freshnessToken)) {
|
|
387
|
+
return undefined;
|
|
388
|
+
}
|
|
389
|
+
this.logger.debug({ sessionId }, 'Session cache hit');
|
|
390
|
+
this.touchLru(sessionId, cached, now);
|
|
391
|
+
this.hits += 1;
|
|
392
|
+
return cached.session;
|
|
393
|
+
}
|
|
394
|
+
toGetOrParseResult(session, entry, cacheHit, sourceChangeKind) {
|
|
395
|
+
return {
|
|
396
|
+
session,
|
|
397
|
+
cacheHit,
|
|
398
|
+
sourceChangeKind,
|
|
399
|
+
lastOffset: entry.lastOffset,
|
|
400
|
+
lastSize: entry.lastSize,
|
|
401
|
+
lastMtime: entry.lastMtime,
|
|
402
|
+
sourceVersion: entry.sourceVersion,
|
|
403
|
+
boundaryFold: entry.boundaryFold,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
touchLru(sessionId, entry, now) {
|
|
407
|
+
entry.lastAccessedAt = now;
|
|
163
408
|
this.cache.delete(sessionId);
|
|
164
409
|
this.cache.set(sessionId, entry);
|
|
165
410
|
}
|
|
166
|
-
|
|
167
|
-
|
|
411
|
+
enforceBudget() {
|
|
412
|
+
while (this.budgetUsedBytes > this.config.budgetBytes && this.cache.size > 0) {
|
|
168
413
|
const oldestKey = this.cache.keys().next().value;
|
|
169
414
|
if (oldestKey !== undefined) {
|
|
170
|
-
this.
|
|
415
|
+
this.deleteEntry(oldestKey);
|
|
416
|
+
this.evictions += 1;
|
|
171
417
|
}
|
|
172
418
|
}
|
|
173
419
|
}
|
|
420
|
+
deleteEntry(sessionId) {
|
|
421
|
+
const entry = this.cache.get(sessionId);
|
|
422
|
+
if (!entry)
|
|
423
|
+
return;
|
|
424
|
+
this.budgetUsedBytes -= this.entryWeight(entry);
|
|
425
|
+
this.cache.delete(sessionId);
|
|
426
|
+
}
|
|
427
|
+
entryWeight(entry) {
|
|
428
|
+
return entry.weights.parsed + entry.weights.chunks + entry.weights.dto;
|
|
429
|
+
}
|
|
430
|
+
estimateSourceWeight(ref, session, sourceSize) {
|
|
431
|
+
if (ref.kind === 'file')
|
|
432
|
+
return Math.max(1, sourceSize);
|
|
433
|
+
const tokenProxy = Math.max(session.metrics.totalContextConsumption, session.metrics.totalTokens, session.metrics.visibleContextTokens) * 4;
|
|
434
|
+
return Math.max(1_024, tokenProxy, session.messages.length * 256);
|
|
435
|
+
}
|
|
436
|
+
withoutDerivedChunks(session) {
|
|
437
|
+
if (session.chunks === undefined)
|
|
438
|
+
return session;
|
|
439
|
+
const parsedSession = { ...session };
|
|
440
|
+
delete parsedSession.chunks;
|
|
441
|
+
return parsedSession;
|
|
442
|
+
}
|
|
174
443
|
foldLeadingContinuationIntoCachedTail(cachedMessages, newMessages) {
|
|
175
444
|
const tail = cachedMessages[cachedMessages.length - 1];
|
|
176
445
|
if (!tail || tail.role !== 'assistant' || newMessages.length === 0) {
|
|
@@ -266,7 +535,79 @@ let SessionCacheService = SessionCacheService_1 = class SessionCacheService {
|
|
|
266
535
|
}
|
|
267
536
|
async statFile(filePath) {
|
|
268
537
|
const stat = await fs.stat(filePath);
|
|
269
|
-
return {
|
|
538
|
+
return {
|
|
539
|
+
size: stat.size,
|
|
540
|
+
mtimeMs: stat.mtime.getTime(),
|
|
541
|
+
dev: stat.dev,
|
|
542
|
+
ino: stat.ino,
|
|
543
|
+
fileIdentity: `${stat.dev}:${stat.ino}`,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
classifyUnsafeFileChange(cached, current) {
|
|
547
|
+
if (cached.fileIdentity === undefined ||
|
|
548
|
+
current.fileIdentity === undefined ||
|
|
549
|
+
cached.fileIdentity !== current.fileIdentity) {
|
|
550
|
+
return 'file-replacement';
|
|
551
|
+
}
|
|
552
|
+
if (current.size < cached.lastSize) {
|
|
553
|
+
return 'file-truncation';
|
|
554
|
+
}
|
|
555
|
+
return 'same-file-rewrite';
|
|
556
|
+
}
|
|
557
|
+
async tryHashFileContent(filePath, expected, prefixLength) {
|
|
558
|
+
try {
|
|
559
|
+
return await this.hashFileContent(filePath, expected, prefixLength);
|
|
560
|
+
}
|
|
561
|
+
catch (error) {
|
|
562
|
+
this.logger.debug({ error, filePath }, 'File prefix proof unavailable — append is unsafe');
|
|
563
|
+
return undefined;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
async hashFileContent(filePath, expected, prefixLength) {
|
|
567
|
+
if (!Number.isSafeInteger(prefixLength) ||
|
|
568
|
+
prefixLength < 0 ||
|
|
569
|
+
prefixLength > expected.size ||
|
|
570
|
+
expected.fileIdentity === undefined) {
|
|
571
|
+
throw new Error('Invalid file prefix proof bounds');
|
|
572
|
+
}
|
|
573
|
+
const handle = await fs.open(filePath, 'r');
|
|
574
|
+
const assertStableSnapshot = async () => {
|
|
575
|
+
const current = await handle.stat();
|
|
576
|
+
if (current.size !== expected.size ||
|
|
577
|
+
current.mtime.getTime() !== expected.mtimeMs ||
|
|
578
|
+
`${current.dev}:${current.ino}` !== expected.fileIdentity) {
|
|
579
|
+
throw new Error('File changed while computing append proof');
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
try {
|
|
583
|
+
await assertStableSnapshot();
|
|
584
|
+
const prefixHash = (0, node_crypto_1.createHash)('sha256');
|
|
585
|
+
const fullHash = (0, node_crypto_1.createHash)('sha256');
|
|
586
|
+
const buffer = Buffer.allocUnsafe(Math.min(FILE_HASH_CHUNK_BYTES, Math.max(1, expected.size)));
|
|
587
|
+
let position = 0;
|
|
588
|
+
while (position < expected.size) {
|
|
589
|
+
const requested = Math.min(buffer.byteLength, expected.size - position);
|
|
590
|
+
const { bytesRead } = await handle.read(buffer, 0, requested, position);
|
|
591
|
+
if (bytesRead === 0) {
|
|
592
|
+
throw new Error('File ended while computing append proof');
|
|
593
|
+
}
|
|
594
|
+
const chunk = buffer.subarray(0, bytesRead);
|
|
595
|
+
fullHash.update(chunk);
|
|
596
|
+
const prefixBytes = Math.min(bytesRead, Math.max(0, prefixLength - position));
|
|
597
|
+
if (prefixBytes > 0) {
|
|
598
|
+
prefixHash.update(chunk.subarray(0, prefixBytes));
|
|
599
|
+
}
|
|
600
|
+
position += bytesRead;
|
|
601
|
+
}
|
|
602
|
+
await assertStableSnapshot();
|
|
603
|
+
return {
|
|
604
|
+
prefixDigest: prefixHash.digest('hex'),
|
|
605
|
+
fullDigest: fullHash.digest('hex'),
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
finally {
|
|
609
|
+
await handle.close();
|
|
610
|
+
}
|
|
270
611
|
}
|
|
271
612
|
toSourceRef(source, adapter) {
|
|
272
613
|
if (typeof source === 'string') {
|
|
@@ -279,12 +620,21 @@ let SessionCacheService = SessionCacheService_1 = class SessionCacheService {
|
|
|
279
620
|
return source;
|
|
280
621
|
}
|
|
281
622
|
async computeFreshness(ref, adapter) {
|
|
282
|
-
const { size, mtimeMs } = await this.statFile(ref.filePath);
|
|
283
|
-
const
|
|
623
|
+
const { size, mtimeMs, dev, ino, fileIdentity } = await this.statFile(ref.filePath);
|
|
624
|
+
const adapterToken = adapter.getFreshnessToken
|
|
284
625
|
? await adapter.getFreshnessToken(ref)
|
|
285
|
-
: { mtimeMs, size };
|
|
286
|
-
const
|
|
287
|
-
|
|
626
|
+
: { mtimeMs, size, dev, ino };
|
|
627
|
+
const token = ref.kind === 'file' && adapter.getFreshnessToken ? { adapterToken, dev, ino } : adapterToken;
|
|
628
|
+
const sourceVersion = ref.kind === 'db'
|
|
629
|
+
? this.dbSourceVersion(token, size)
|
|
630
|
+
: fileSourceVersion({ size, mtimeMs, dev, ino });
|
|
631
|
+
return {
|
|
632
|
+
token,
|
|
633
|
+
sourceVersion,
|
|
634
|
+
size,
|
|
635
|
+
mtimeMs,
|
|
636
|
+
fileIdentity: ref.kind === 'file' ? fileIdentity : undefined,
|
|
637
|
+
};
|
|
288
638
|
}
|
|
289
639
|
dbSourceVersion(token, fallbackSize) {
|
|
290
640
|
if (token && typeof token === 'object') {
|
|
@@ -298,6 +648,15 @@ let SessionCacheService = SessionCacheService_1 = class SessionCacheService {
|
|
|
298
648
|
};
|
|
299
649
|
exports.SessionCacheService = SessionCacheService;
|
|
300
650
|
exports.SessionCacheService = SessionCacheService = SessionCacheService_1 = __decorate([
|
|
301
|
-
(0, common_1.Injectable)()
|
|
651
|
+
(0, common_1.Injectable)(),
|
|
652
|
+
__param(1, (0, common_1.Optional)()),
|
|
653
|
+
__param(1, (0, common_1.Inject)(exports.TRANSCRIPT_CACHE_CONFIG)),
|
|
654
|
+
__metadata("design:paramtypes", [metrics_service_1.MetricsService, Object])
|
|
302
655
|
], SessionCacheService);
|
|
656
|
+
function positiveIntegerFromEnv(raw, fallback) {
|
|
657
|
+
if (raw === undefined)
|
|
658
|
+
return fallback;
|
|
659
|
+
const parsed = Number(raw);
|
|
660
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
661
|
+
}
|
|
303
662
|
//# sourceMappingURL=session-cache.service.js.map
|