chainlesschain 0.45.11 → 0.45.19
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/package.json +1 -1
- package/src/assets/web-panel/assets/AppLayout-B00RARl2.js +1 -0
- package/src/assets/web-panel/assets/AppLayout-CFP4dGIJ.css +1 -0
- package/src/assets/web-panel/assets/{Chat-5f__rMCR.js → Chat-DXtvKoM0.js} +1 -1
- package/src/assets/web-panel/assets/{Cron-C4mrNC4c.js → Cron-BJ4ODHOy.js} +1 -1
- package/src/assets/web-panel/assets/Dashboard-3iIpp3zd.js +3 -0
- package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +1 -0
- package/src/assets/web-panel/assets/{Logs-CC_Zuh66.js → Logs-CSeKZEG_.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-B15GiN3u.js → McpTools-BYQAK11r.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-Dbd7oLOH.js → Memory-gkUAPyuZ.js} +2 -2
- package/src/assets/web-panel/assets/{Notes-CEkc49fY.js → Notes-bjNrQgAo.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CjyPHW00.js → Providers-Dbf57Tbv.js} +1 -1
- package/src/assets/web-panel/assets/{Services-XFzHMRRd.js → Services-CS0oMdxh.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-D8oxmB3U.js → Skills-B2fgruv8.js} +1 -1
- package/src/assets/web-panel/assets/Tasks-BJjN_YEm.css +1 -0
- package/src/assets/web-panel/assets/Tasks-qULws8pc.js +1 -0
- package/src/assets/web-panel/assets/{antd-ChLPLhSn.js → antd-CJSBocer.js} +1 -1
- package/src/assets/web-panel/assets/chat-DnH09sSR.js +1 -0
- package/src/assets/web-panel/assets/{index-DQ5xXK7O.js → index-CF2CqPYX.js} +2 -2
- package/src/assets/web-panel/assets/{markdown-DtbPhnFe.js → markdown-Bo5cVN4u.js} +1 -1
- package/src/assets/web-panel/assets/ws-DjelKkD6.js +1 -0
- package/src/assets/web-panel/index.html +2 -2
- package/src/commands/agent.js +7 -8
- package/src/commands/chat.js +9 -11
- package/src/commands/serve.js +11 -106
- package/src/commands/session.js +185 -18
- package/src/commands/ui.js +10 -151
- package/src/gateways/repl/agent-repl.js +1 -0
- package/src/gateways/repl/chat-repl.js +1 -0
- package/src/gateways/ui/web-ui-server.js +1 -0
- package/src/gateways/ws/action-protocol.js +83 -0
- package/src/gateways/ws/message-dispatcher.js +73 -0
- package/src/gateways/ws/session-protocol.js +396 -0
- package/src/gateways/ws/task-protocol.js +55 -0
- package/src/gateways/ws/worktree-protocol.js +315 -0
- package/src/gateways/ws/ws-server.js +4 -0
- package/src/gateways/ws/ws-session-gateway.js +1 -0
- package/src/harness/background-task-manager.js +506 -0
- package/src/harness/background-task-worker.js +48 -0
- package/src/harness/compression-telemetry.js +214 -0
- package/src/harness/feature-flags.js +157 -0
- package/src/harness/jsonl-session-store.js +452 -0
- package/src/harness/prompt-compressor.js +416 -0
- package/src/harness/worktree-isolator.js +845 -0
- package/src/lib/agent-core.js +246 -45
- package/src/lib/background-task-manager.js +1 -305
- package/src/lib/background-task-worker.js +1 -50
- package/src/lib/compression-telemetry.js +5 -0
- package/src/lib/feature-flags.js +7 -182
- package/src/lib/interaction-adapter.js +32 -6
- package/src/lib/jsonl-session-store.js +21 -237
- package/src/lib/prompt-compressor.js +10 -351
- package/src/lib/sub-agent-context.js +91 -0
- package/src/lib/worktree-isolator.js +13 -231
- package/src/lib/ws-agent-handler.js +1 -0
- package/src/lib/ws-server.js +155 -359
- package/src/lib/ws-session-manager.js +82 -1
- package/src/repl/agent-repl.js +114 -32
- package/src/runtime/agent-runtime.js +417 -0
- package/src/runtime/contracts/agent-turn.js +11 -0
- package/src/runtime/contracts/session-record.js +31 -0
- package/src/runtime/contracts/task-record.js +18 -0
- package/src/runtime/contracts/telemetry-record.js +23 -0
- package/src/runtime/contracts/worktree-record.js +14 -0
- package/src/runtime/index.js +13 -0
- package/src/runtime/policies/agent-policy.js +45 -0
- package/src/runtime/runtime-context.js +14 -0
- package/src/runtime/runtime-events.js +37 -0
- package/src/runtime/runtime-factory.js +50 -0
- package/src/tools/index.js +22 -0
- package/src/tools/legacy-agent-tools.js +171 -0
- package/src/tools/registry.js +141 -0
- package/src/tools/tool-context.js +28 -0
- package/src/tools/tool-permissions.js +28 -0
- package/src/tools/tool-telemetry.js +39 -0
- package/src/assets/web-panel/assets/AppLayout-19ZC8w11.js +0 -1
- package/src/assets/web-panel/assets/AppLayout-CjgO-ML6.css +0 -1
- package/src/assets/web-panel/assets/Dashboard-CRFnDUFh.css +0 -1
- package/src/assets/web-panel/assets/Dashboard-DsjXpZor.js +0 -3
- package/src/assets/web-panel/assets/chat-C_hu-qNs.js +0 -1
- package/src/assets/web-panel/assets/ws-DwluTqT5.js +0 -1
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
} from "./session-manager.js";
|
|
21
21
|
import { buildSystemPrompt } from "./agent-core.js";
|
|
22
22
|
import { SubAgentRegistry } from "./sub-agent-registry.js";
|
|
23
|
+
import { createWorktree, removeWorktree } from "./worktree-isolator.js";
|
|
24
|
+
import { isGitRepo } from "./git-integration.js";
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* @typedef {object} Session
|
|
@@ -32,7 +34,11 @@ import { SubAgentRegistry } from "./sub-agent-registry.js";
|
|
|
32
34
|
* @property {string|null} apiKey
|
|
33
35
|
* @property {string|null} baseUrl
|
|
34
36
|
* @property {string} projectRoot
|
|
37
|
+
* @property {string} baseProjectRoot
|
|
35
38
|
* @property {string|null} rulesContent
|
|
39
|
+
* @property {object|null} hostManagedToolPolicy
|
|
40
|
+
* @property {boolean} worktreeIsolation
|
|
41
|
+
* @property {object|null} worktree
|
|
36
42
|
* @property {PlanModeManager} planManager
|
|
37
43
|
* @property {CLIContextEngineering|null} contextEngine
|
|
38
44
|
* @property {CLIPermanentMemory|null} permanentMemory
|
|
@@ -78,12 +84,13 @@ export class WSSessionManager {
|
|
|
78
84
|
* @param {string} [options.model]
|
|
79
85
|
* @param {string} [options.apiKey]
|
|
80
86
|
* @param {string} [options.baseUrl]
|
|
87
|
+
* @param {object} [options.hostManagedToolPolicy]
|
|
81
88
|
* @returns {{ sessionId: string }}
|
|
82
89
|
*/
|
|
83
90
|
createSession(options = {}) {
|
|
84
91
|
const sessionId = this._generateId();
|
|
85
92
|
const type = options.type || "agent";
|
|
86
|
-
const
|
|
93
|
+
const baseProjectRoot = options.projectRoot || this.defaultProjectRoot;
|
|
87
94
|
const cfgLlm = this.config?.llm || {};
|
|
88
95
|
const provider = options.provider || cfgLlm.provider || "ollama";
|
|
89
96
|
const model =
|
|
@@ -93,6 +100,16 @@ export class WSSessionManager {
|
|
|
93
100
|
const baseUrl =
|
|
94
101
|
options.baseUrl || cfgLlm.baseUrl || "http://localhost:11434";
|
|
95
102
|
const apiKey = options.apiKey || cfgLlm.apiKey || null;
|
|
103
|
+
const worktreeIsolationRequested = options.worktreeIsolation === true;
|
|
104
|
+
const isolatedWorkspace = this._prepareSessionWorkspace(
|
|
105
|
+
baseProjectRoot,
|
|
106
|
+
sessionId,
|
|
107
|
+
{
|
|
108
|
+
worktreeIsolation: worktreeIsolationRequested,
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
const projectRoot = isolatedWorkspace.projectRoot;
|
|
112
|
+
const worktree = isolatedWorkspace.worktree;
|
|
96
113
|
|
|
97
114
|
// Project context (rules.md, persona) is now loaded by buildSystemPrompt()
|
|
98
115
|
|
|
@@ -151,8 +168,12 @@ export class WSSessionManager {
|
|
|
151
168
|
model,
|
|
152
169
|
apiKey,
|
|
153
170
|
baseUrl,
|
|
171
|
+
hostManagedToolPolicy: options.hostManagedToolPolicy || null,
|
|
154
172
|
projectRoot,
|
|
173
|
+
baseProjectRoot,
|
|
155
174
|
rulesContent: null,
|
|
175
|
+
worktreeIsolation: worktreeIsolationRequested,
|
|
176
|
+
worktree,
|
|
156
177
|
planManager,
|
|
157
178
|
contextEngine,
|
|
158
179
|
permanentMemory,
|
|
@@ -226,8 +247,12 @@ export class WSSessionManager {
|
|
|
226
247
|
model: dbSession.model || null,
|
|
227
248
|
apiKey: null,
|
|
228
249
|
baseUrl: "http://localhost:11434",
|
|
250
|
+
hostManagedToolPolicy: null,
|
|
229
251
|
projectRoot: this.defaultProjectRoot,
|
|
252
|
+
baseProjectRoot: this.defaultProjectRoot,
|
|
230
253
|
rulesContent: null,
|
|
254
|
+
worktreeIsolation: false,
|
|
255
|
+
worktree: null,
|
|
231
256
|
planManager,
|
|
232
257
|
contextEngine,
|
|
233
258
|
permanentMemory,
|
|
@@ -284,6 +309,16 @@ export class WSSessionManager {
|
|
|
284
309
|
session.planManager.removeAllListeners();
|
|
285
310
|
}
|
|
286
311
|
|
|
312
|
+
if (session.worktree?.path && session.baseProjectRoot) {
|
|
313
|
+
try {
|
|
314
|
+
removeWorktree(session.baseProjectRoot, session.worktree.path, {
|
|
315
|
+
deleteBranch: true,
|
|
316
|
+
});
|
|
317
|
+
} catch (_err) {
|
|
318
|
+
// Best-effort cleanup.
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
287
322
|
this.sessions.delete(sessionId);
|
|
288
323
|
}
|
|
289
324
|
|
|
@@ -304,6 +339,9 @@ export class WSSessionManager {
|
|
|
304
339
|
provider: session.provider,
|
|
305
340
|
model: session.model,
|
|
306
341
|
messageCount: session.messages.length,
|
|
342
|
+
baseProjectRoot: session.baseProjectRoot,
|
|
343
|
+
worktreeIsolation: session.worktreeIsolation === true,
|
|
344
|
+
worktree: session.worktree || null,
|
|
307
345
|
createdAt: session.createdAt,
|
|
308
346
|
lastActivity: session.lastActivity,
|
|
309
347
|
});
|
|
@@ -346,6 +384,22 @@ export class WSSessionManager {
|
|
|
346
384
|
return this.sessions.get(sessionId) || null;
|
|
347
385
|
}
|
|
348
386
|
|
|
387
|
+
/**
|
|
388
|
+
* Update host-managed tool policy for an active session.
|
|
389
|
+
*
|
|
390
|
+
* @param {string} sessionId
|
|
391
|
+
* @param {object|null} hostManagedToolPolicy
|
|
392
|
+
* @returns {Session|null}
|
|
393
|
+
*/
|
|
394
|
+
updateSessionPolicy(sessionId, hostManagedToolPolicy) {
|
|
395
|
+
const session = this.sessions.get(sessionId);
|
|
396
|
+
if (!session) return null;
|
|
397
|
+
|
|
398
|
+
session.hostManagedToolPolicy = hostManagedToolPolicy || null;
|
|
399
|
+
session.lastActivity = new Date().toISOString();
|
|
400
|
+
return session;
|
|
401
|
+
}
|
|
402
|
+
|
|
349
403
|
/**
|
|
350
404
|
* Persist current messages for a session.
|
|
351
405
|
*/
|
|
@@ -361,4 +415,31 @@ export class WSSessionManager {
|
|
|
361
415
|
|
|
362
416
|
session.lastActivity = new Date().toISOString();
|
|
363
417
|
}
|
|
418
|
+
|
|
419
|
+
_prepareSessionWorkspace(projectRoot, sessionId, options = {}) {
|
|
420
|
+
if (options.worktreeIsolation !== true) {
|
|
421
|
+
return {
|
|
422
|
+
projectRoot,
|
|
423
|
+
worktree: null,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (!isGitRepo(projectRoot)) {
|
|
428
|
+
throw new Error(
|
|
429
|
+
`Worktree isolation requires a git repository: ${projectRoot}`,
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const branchName = `coding-agent/${sessionId}`;
|
|
434
|
+
const worktree = createWorktree(projectRoot, branchName);
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
projectRoot: worktree.path,
|
|
438
|
+
worktree: {
|
|
439
|
+
branch: worktree.branch,
|
|
440
|
+
path: worktree.path,
|
|
441
|
+
baseProjectRoot: projectRoot,
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
}
|
|
364
445
|
}
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -29,6 +29,14 @@ import {
|
|
|
29
29
|
saveMessages,
|
|
30
30
|
getSession,
|
|
31
31
|
} from "../lib/session-manager.js";
|
|
32
|
+
import {
|
|
33
|
+
startSession as jsonlStartSession,
|
|
34
|
+
appendUserMessage,
|
|
35
|
+
appendAssistantMessage,
|
|
36
|
+
appendCompactEvent,
|
|
37
|
+
rebuildMessages,
|
|
38
|
+
sessionExists,
|
|
39
|
+
} from "../lib/jsonl-session-store.js";
|
|
32
40
|
import { storeMemory, consolidateMemory } from "../lib/hierarchical-memory.js";
|
|
33
41
|
import { CLIContextEngineering } from "../lib/cli-context-engineering.js";
|
|
34
42
|
import { createChatFn } from "../lib/cowork-adapter.js";
|
|
@@ -40,6 +48,7 @@ import { CLIPermanentMemory } from "../lib/permanent-memory.js";
|
|
|
40
48
|
import { CLIAutonomousAgent, GoalStatus } from "../lib/autonomous-agent.js";
|
|
41
49
|
import { PromptCompressor } from "../lib/prompt-compressor.js";
|
|
42
50
|
import { feature } from "../lib/feature-flags.js";
|
|
51
|
+
import { recordCompressionMetric } from "../lib/compression-telemetry.js";
|
|
43
52
|
import {
|
|
44
53
|
AGENT_TOOLS,
|
|
45
54
|
buildSystemPrompt,
|
|
@@ -108,9 +117,9 @@ export async function startAgentRepl(options = {}) {
|
|
|
108
117
|
// Continue without DB — static prompt fallback
|
|
109
118
|
}
|
|
110
119
|
|
|
111
|
-
// Initialize prompt compressor
|
|
120
|
+
// Initialize prompt compressor (adaptive to model's context window)
|
|
112
121
|
if (feature("PROMPT_COMPRESSOR")) {
|
|
113
|
-
_compressor = new PromptCompressor({
|
|
122
|
+
_compressor = new PromptCompressor({ model, provider });
|
|
114
123
|
}
|
|
115
124
|
|
|
116
125
|
// Initialize permanent memory
|
|
@@ -133,7 +142,18 @@ export async function startAgentRepl(options = {}) {
|
|
|
133
142
|
_hookDb = db;
|
|
134
143
|
|
|
135
144
|
// Resume existing session or create new one
|
|
136
|
-
|
|
145
|
+
const useJsonl = feature("JSONL_SESSION");
|
|
146
|
+
|
|
147
|
+
if (useJsonl && options.sessionId) {
|
|
148
|
+
// JSONL resume: check if session file exists
|
|
149
|
+
try {
|
|
150
|
+
if (sessionExists(options.sessionId)) {
|
|
151
|
+
sessionId = options.sessionId;
|
|
152
|
+
}
|
|
153
|
+
} catch (_err) {
|
|
154
|
+
// Non-critical
|
|
155
|
+
}
|
|
156
|
+
} else if (db && options.sessionId) {
|
|
137
157
|
try {
|
|
138
158
|
const existing = getSession(db, options.sessionId);
|
|
139
159
|
if (existing && existing.messages) {
|
|
@@ -144,16 +164,25 @@ export async function startAgentRepl(options = {}) {
|
|
|
144
164
|
}
|
|
145
165
|
}
|
|
146
166
|
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
167
|
+
if (!sessionId) {
|
|
168
|
+
const meta = {
|
|
169
|
+
title: `Agent ${new Date().toISOString().slice(0, 10)}`,
|
|
170
|
+
provider,
|
|
171
|
+
model,
|
|
172
|
+
};
|
|
173
|
+
if (useJsonl) {
|
|
174
|
+
try {
|
|
175
|
+
sessionId = jsonlStartSession(null, meta);
|
|
176
|
+
} catch (_err) {
|
|
177
|
+
// Non-critical
|
|
178
|
+
}
|
|
179
|
+
} else if (db) {
|
|
180
|
+
try {
|
|
181
|
+
const session = createSession(db, meta);
|
|
182
|
+
sessionId = session.id;
|
|
183
|
+
} catch (_err) {
|
|
184
|
+
// Non-critical
|
|
185
|
+
}
|
|
157
186
|
}
|
|
158
187
|
}
|
|
159
188
|
|
|
@@ -162,16 +191,28 @@ export async function startAgentRepl(options = {}) {
|
|
|
162
191
|
];
|
|
163
192
|
|
|
164
193
|
// Load resumed session messages
|
|
165
|
-
if (
|
|
194
|
+
if (options.sessionId && sessionId) {
|
|
166
195
|
try {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
196
|
+
if (useJsonl) {
|
|
197
|
+
const rebuilt = rebuildMessages(sessionId);
|
|
198
|
+
if (rebuilt.length > 0) {
|
|
199
|
+
messages.push(...rebuilt.filter((m) => m.role !== "system"));
|
|
200
|
+
logger.info(
|
|
201
|
+
`Resumed JSONL session ${sessionId} (${rebuilt.length} messages)`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
} else if (db) {
|
|
205
|
+
const existing = getSession(db, sessionId);
|
|
206
|
+
if (existing && existing.messages) {
|
|
207
|
+
const parsed =
|
|
208
|
+
typeof existing.messages === "string"
|
|
209
|
+
? JSON.parse(existing.messages)
|
|
210
|
+
: existing.messages;
|
|
211
|
+
messages.push(...parsed.filter((m) => m.role !== "system"));
|
|
212
|
+
logger.info(
|
|
213
|
+
`Resumed session ${sessionId} (${parsed.length} messages)`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
175
216
|
}
|
|
176
217
|
} catch (_err) {
|
|
177
218
|
// Non-critical
|
|
@@ -386,6 +427,11 @@ export async function startAgentRepl(options = {}) {
|
|
|
386
427
|
await _compressor.compress(messages);
|
|
387
428
|
messages.length = 0;
|
|
388
429
|
messages.push(...compacted);
|
|
430
|
+
recordCompressionMetric(stats, {
|
|
431
|
+
source: "manual-compact",
|
|
432
|
+
provider,
|
|
433
|
+
model,
|
|
434
|
+
});
|
|
389
435
|
logger.info(
|
|
390
436
|
`Compacted: ${stats.originalMessages} → ${stats.compressedMessages} messages, saved ${stats.saved} tokens (${stats.strategy})`,
|
|
391
437
|
);
|
|
@@ -434,10 +480,16 @@ export async function startAgentRepl(options = {}) {
|
|
|
434
480
|
const sessionArg = trimmed.slice(8).trim();
|
|
435
481
|
if (sessionArg.startsWith("resume ")) {
|
|
436
482
|
const resumeId = sessionArg.slice(7).trim();
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
483
|
+
try {
|
|
484
|
+
if (useJsonl && sessionExists(resumeId)) {
|
|
485
|
+
const rebuilt = rebuildMessages(resumeId);
|
|
486
|
+
messages.length = 1; // keep system prompt
|
|
487
|
+
messages.push(...rebuilt.filter((m) => m.role !== "system"));
|
|
488
|
+
sessionId = resumeId;
|
|
489
|
+
logger.info(
|
|
490
|
+
`Resumed JSONL session ${sessionId} (${rebuilt.length} messages)`,
|
|
491
|
+
);
|
|
492
|
+
} else if (db) {
|
|
441
493
|
const existing = getSession(db, resumeId);
|
|
442
494
|
if (existing && existing.messages) {
|
|
443
495
|
const parsed =
|
|
@@ -453,9 +505,11 @@ export async function startAgentRepl(options = {}) {
|
|
|
453
505
|
} else {
|
|
454
506
|
logger.info(`Session not found: ${resumeId}`);
|
|
455
507
|
}
|
|
456
|
-
}
|
|
457
|
-
logger.
|
|
508
|
+
} else {
|
|
509
|
+
logger.info("No session store available");
|
|
458
510
|
}
|
|
511
|
+
} catch (err) {
|
|
512
|
+
logger.error(`Resume failed: ${err.message}`);
|
|
459
513
|
}
|
|
460
514
|
} else {
|
|
461
515
|
logger.info(`Session ID: ${sessionId || "none"}`);
|
|
@@ -1052,9 +1106,17 @@ export async function startAgentRepl(options = {}) {
|
|
|
1052
1106
|
}
|
|
1053
1107
|
|
|
1054
1108
|
// Auto-save session
|
|
1055
|
-
if (
|
|
1109
|
+
if (sessionId) {
|
|
1056
1110
|
try {
|
|
1057
|
-
|
|
1111
|
+
if (useJsonl) {
|
|
1112
|
+
// Append incremental events (user + assistant)
|
|
1113
|
+
appendUserMessage(sessionId, trimmed);
|
|
1114
|
+
if (response) {
|
|
1115
|
+
appendAssistantMessage(sessionId, response);
|
|
1116
|
+
}
|
|
1117
|
+
} else if (db) {
|
|
1118
|
+
saveMessages(db, sessionId, messages);
|
|
1119
|
+
}
|
|
1058
1120
|
} catch (_e) {
|
|
1059
1121
|
// Non-critical
|
|
1060
1122
|
}
|
|
@@ -1070,10 +1132,22 @@ export async function startAgentRepl(options = {}) {
|
|
|
1070
1132
|
await _compressor.compress(messages);
|
|
1071
1133
|
messages.length = 0;
|
|
1072
1134
|
messages.push(...compacted);
|
|
1135
|
+
recordCompressionMetric(stats, {
|
|
1136
|
+
source: "auto-compact",
|
|
1137
|
+
provider,
|
|
1138
|
+
model: activeModel,
|
|
1139
|
+
});
|
|
1073
1140
|
if (stats.saved > 0) {
|
|
1074
1141
|
logger.verbose(
|
|
1075
1142
|
`Auto-compacted: ${stats.strategy} (saved ${stats.saved} tokens)`,
|
|
1076
1143
|
);
|
|
1144
|
+
// Write compact checkpoint to JSONL for crash recovery
|
|
1145
|
+
if (useJsonl && sessionId) {
|
|
1146
|
+
appendCompactEvent(sessionId, {
|
|
1147
|
+
...stats,
|
|
1148
|
+
messages: compacted,
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1077
1151
|
}
|
|
1078
1152
|
} catch (_e) {
|
|
1079
1153
|
// Non-critical — continue with uncompacted messages
|
|
@@ -1116,9 +1190,17 @@ export async function startAgentRepl(options = {}) {
|
|
|
1116
1190
|
|
|
1117
1191
|
rl.on("close", async () => {
|
|
1118
1192
|
// Save session on exit
|
|
1119
|
-
if (
|
|
1193
|
+
if (sessionId) {
|
|
1120
1194
|
try {
|
|
1121
|
-
|
|
1195
|
+
if (useJsonl) {
|
|
1196
|
+
// JSONL: write final compact snapshot for fast rebuild
|
|
1197
|
+
appendCompactEvent(sessionId, {
|
|
1198
|
+
strategy: "session-end",
|
|
1199
|
+
messages,
|
|
1200
|
+
});
|
|
1201
|
+
} else if (db) {
|
|
1202
|
+
saveMessages(db, sessionId, messages);
|
|
1203
|
+
}
|
|
1122
1204
|
} catch (_e) {
|
|
1123
1205
|
// Non-critical
|
|
1124
1206
|
}
|