@curie-agent/daemon 0.4.1 → 0.4.3
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/src/channel-manager.d.ts.map +1 -1
- package/dist/src/channel-manager.js +1 -0
- package/dist/src/channel-manager.js.map +1 -1
- package/dist/src/jsonrpc-handler.d.ts +18 -1
- package/dist/src/jsonrpc-handler.d.ts.map +1 -1
- package/dist/src/jsonrpc-handler.js +218 -285
- package/dist/src/jsonrpc-handler.js.map +1 -1
- package/dist/src/ws-handler.d.ts.map +1 -1
- package/dist/src/ws-handler.js +1 -0
- package/dist/src/ws-handler.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/web/dist/assets/index-CyCuINoz.js +234 -0
- package/web/dist/assets/index-ulOOFddN.css +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-DV8ePK4N.css +0 -1
- package/web/dist/assets/index-De5KT5pu.js +0 -165
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import os, { homedir } from 'node:os';
|
|
2
2
|
import path, { join } from 'node:path';
|
|
3
3
|
import { readFileSync, existsSync, readdirSync, statSync, writeFileSync, mkdirSync, realpathSync } from 'node:fs';
|
|
4
|
-
import {
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { Method, renderSlashCommandHelp, findSlashCommand } from '@curie-agent/protocol';
|
|
5
6
|
import { TurnLoop, parseReminderTime, listSnapshots, revertTo, createIdentityFilesAuto, PURE_TOOLS } from '@curie-agent/core';
|
|
7
|
+
import { compactMessages, reconstructMessagesFromEvents, resolveBudget, estimateRequestTokens, breakdownChars, fillPct, formatTokens, DEFAULT_CALIBRATION, DEFAULT_SETTINGS, } from '@curie-agent/core';
|
|
6
8
|
import { listSkills, discoverAllSkills } from '@curie-agent/tools';
|
|
7
9
|
import { executeCd } from './slash-cd.js';
|
|
10
|
+
/** Real package version — previously hardcoded and years out of date. */
|
|
11
|
+
const VERSION = (() => {
|
|
12
|
+
try {
|
|
13
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const pkg = JSON.parse(readFileSync(join(here, '..', '..', 'package.json'), 'utf-8'));
|
|
15
|
+
return pkg.version ?? '0.0.0';
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return '0.0.0';
|
|
19
|
+
}
|
|
20
|
+
})();
|
|
8
21
|
export class JsonRpcHandler {
|
|
9
22
|
sessionStore;
|
|
10
23
|
settingsManager;
|
|
@@ -293,10 +306,11 @@ export class JsonRpcHandler {
|
|
|
293
306
|
}
|
|
294
307
|
case Method.CONFIG_GET: {
|
|
295
308
|
const key = this.getStringParam(params, 'key');
|
|
296
|
-
if (!key)
|
|
297
|
-
return this.paramError('key');
|
|
298
309
|
const settings = this.settingsManager.get();
|
|
299
|
-
|
|
310
|
+
// '*' or omitted → the whole tree, in one consistent snapshot. Reading
|
|
311
|
+
// ~23 top-level keys one at a time can tear if another client writes
|
|
312
|
+
// mid-fetch, yielding a snapshot that never existed on disk.
|
|
313
|
+
result = !key || key === '*' ? settings : this.getNestedValue(settings, key);
|
|
300
314
|
break;
|
|
301
315
|
}
|
|
302
316
|
case Method.CONFIG_SET: {
|
|
@@ -328,14 +342,7 @@ export class JsonRpcHandler {
|
|
|
328
342
|
else {
|
|
329
343
|
this.settingsManager.update({ [key]: value });
|
|
330
344
|
}
|
|
331
|
-
this.
|
|
332
|
-
this.sharedEventBus?.emit({
|
|
333
|
-
type: 'config-changed',
|
|
334
|
-
id: Math.random().toString(36).substring(7),
|
|
335
|
-
timestamp: Date.now(),
|
|
336
|
-
key,
|
|
337
|
-
value,
|
|
338
|
-
});
|
|
345
|
+
this.emitConfigChanged(key, value);
|
|
339
346
|
if (key.startsWith('heartbeat') && this.daemonApp) {
|
|
340
347
|
const updatedSettings = this.settingsManager.get();
|
|
341
348
|
if (updatedSettings.heartbeat?.schedule === 'on') {
|
|
@@ -351,6 +358,17 @@ export class JsonRpcHandler {
|
|
|
351
358
|
this.daemonApp.taskManager.cancelAllHeartbeats();
|
|
352
359
|
}
|
|
353
360
|
}
|
|
361
|
+
// Keep the derived top-level `model` in sync with the active provider.
|
|
362
|
+
// SettingsManager.load() recomputes it from providers[current_provider].model,
|
|
363
|
+
// so a stale top-level value silently reverts on the next daemon start.
|
|
364
|
+
if (key === 'current_provider' || /^providers\.[^.]+\.model$/.test(key)) {
|
|
365
|
+
const synced = this.settingsManager.get();
|
|
366
|
+
const active = synced.providers[synced.current_provider];
|
|
367
|
+
if (active && synced.model !== active.model) {
|
|
368
|
+
this.settingsManager.update({ model: active.model });
|
|
369
|
+
this.emitConfigChanged('model', active.model);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
354
372
|
result = { status: 'ok', key, value };
|
|
355
373
|
break;
|
|
356
374
|
}
|
|
@@ -395,7 +413,7 @@ export class JsonRpcHandler {
|
|
|
395
413
|
case Method.DAEMON_STATUS:
|
|
396
414
|
result = {
|
|
397
415
|
status: 'ok',
|
|
398
|
-
version:
|
|
416
|
+
version: VERSION,
|
|
399
417
|
clients: 0, // will be set by server
|
|
400
418
|
};
|
|
401
419
|
break;
|
|
@@ -893,6 +911,7 @@ export class JsonRpcHandler {
|
|
|
893
911
|
'tool-result', 'approval-decision', 'usage',
|
|
894
912
|
'error', 'session-start', 'session-stop', 'hook', 'status',
|
|
895
913
|
'session-resumed', 'context-warning', 'thinking-delta',
|
|
914
|
+
'compaction', 'context-report',
|
|
896
915
|
// Subagent events
|
|
897
916
|
'agent-start', 'agent-text-delta', 'agent-thinking-delta',
|
|
898
917
|
'agent-tool-call', 'agent-tool-result', 'agent-usage',
|
|
@@ -906,7 +925,9 @@ export class JsonRpcHandler {
|
|
|
906
925
|
}
|
|
907
926
|
try {
|
|
908
927
|
const result = await loop.run(text);
|
|
909
|
-
|
|
928
|
+
// No post-run threshold check: TurnLoop enforces the budget before every
|
|
929
|
+
// provider call, on every code path (channels, tasks, heartbeat,
|
|
930
|
+
// subagents) — not just between prompts on this one.
|
|
910
931
|
return { status: 'completed', sessionId: result.sessionId, events: result.events.length };
|
|
911
932
|
}
|
|
912
933
|
catch (err) {
|
|
@@ -924,6 +945,21 @@ export class JsonRpcHandler {
|
|
|
924
945
|
paramError(key) {
|
|
925
946
|
return { jsonrpc: '2.0', id: 0, error: { code: -32602, message: `Missing required parameter: ${key}` } };
|
|
926
947
|
}
|
|
948
|
+
/**
|
|
949
|
+
* Broadcast a settings change so every connected client re-reads config.
|
|
950
|
+
* 'config-changed' is already in ws-handler's newEventTypes, so this reaches
|
|
951
|
+
* the browser. Every settings write must emit — a write that doesn't leaves
|
|
952
|
+
* the web dashboard showing a stale value until the user reloads.
|
|
953
|
+
*/
|
|
954
|
+
emitConfigChanged(key, value) {
|
|
955
|
+
this.sharedEventBus?.emit({
|
|
956
|
+
type: 'config-changed',
|
|
957
|
+
id: Math.random().toString(36).substring(7),
|
|
958
|
+
timestamp: Date.now(),
|
|
959
|
+
key,
|
|
960
|
+
value,
|
|
961
|
+
});
|
|
962
|
+
}
|
|
927
963
|
/** Get a nested value from an object using dot notation (e.g. "providers.anthropic.model"). */
|
|
928
964
|
getNestedValue(obj, path) {
|
|
929
965
|
const parts = path.split('.');
|
|
@@ -984,44 +1020,9 @@ export class JsonRpcHandler {
|
|
|
984
1020
|
const args = parts.slice(1).join(' ').trim();
|
|
985
1021
|
switch (command) {
|
|
986
1022
|
case 'help': {
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
* \`/status\` — Show version, active model, provider, approval mode, CWD, active settings, and pricing.
|
|
991
|
-
* \`/help\` — List all available commands with usage details.
|
|
992
|
-
|
|
993
|
-
**Model & Config**
|
|
994
|
-
* \`/provider <name>\` — Switch AI provider (\`anthropic | openai | google | local | openrouter | ollama\`).
|
|
995
|
-
* \`/model <name>\` — Switch active model. Or use subcommands:
|
|
996
|
-
* \`/model pricing <in;out>\` — Customize pricing format per million tokens.
|
|
997
|
-
* \`/model window <tokens>\` — Adjust model context window capacity.
|
|
998
|
-
* \`/effort <low|medium|high|max|auto>\` — Set reasoning effort level.
|
|
999
|
-
* \`/mode <plan|edit|auto|yolo>\` — Set agent approval mode.
|
|
1000
|
-
* \`/tools <max_tools> [max_websearch]\` — Configure dynamic tool limit per turn.
|
|
1001
|
-
* \`/websearch <limit>\` — Configure maximum web search limits per turn.
|
|
1002
|
-
|
|
1003
|
-
**Skills & MCP**
|
|
1004
|
-
* \`/skill [name]\` — List all globally and project-registered skills or view a specific skill's instructions.
|
|
1005
|
-
* \`/mcp [list|reload]\` — List connected Model Context Protocol (MCP) servers and their tools, or reload configuration.
|
|
1006
|
-
|
|
1007
|
-
**Memory & Context**
|
|
1008
|
-
* \`/memory [status|add <text>]\` — View active memory files or add new memories to be organized on next turn.
|
|
1009
|
-
|
|
1010
|
-
**System Info**
|
|
1011
|
-
* \`/system\` — Show OS, platform, Node version, home dir, CWD, and PathGuard status.
|
|
1012
|
-
* \`/context [auto [on|off|threshold N|warn N|pricing on/off]]\` — View visual token capacity fill percentage bar or configure auto-compaction.
|
|
1013
|
-
|
|
1014
|
-
**Automation & Scheduling**
|
|
1015
|
-
* \`/remind <message at time>\` — Create a scheduled reminder (e.g., \`/remind review current pull request in 30 mins\`).
|
|
1016
|
-
* \`/cron [list|delete <id>|clear]\` — View list of active reminders or manage completed ones.
|
|
1017
|
-
* \`/heartbeat [status|enable|disable|now|daily <H:MM>|weekly <day@H:MM>...]\` — Control scheduled heartbeat cycles or run immediately.
|
|
1018
|
-
* \`/task [create <instruction at time>|list [status]|delete <id>]\` — Schedule background autonomous agent tasks.
|
|
1019
|
-
|
|
1020
|
-
**Workspace Safety**
|
|
1021
|
-
* \`/snapshots\` — List Git-backed state snapshots.
|
|
1022
|
-
* \`/revert <index>\` — Revert workspace to a specific snapshot index.
|
|
1023
|
-
* \`/cd <path>\` — Change working directory with safety checks.`;
|
|
1024
|
-
emitDelta(helpText);
|
|
1023
|
+
// Rendered from the shared registry so help can never drift from the
|
|
1024
|
+
// set of commands that actually exist.
|
|
1025
|
+
emitDelta(renderSlashCommandHelp());
|
|
1025
1026
|
break;
|
|
1026
1027
|
}
|
|
1027
1028
|
case 'status': {
|
|
@@ -1031,18 +1032,19 @@ export class JsonRpcHandler {
|
|
|
1031
1032
|
const pricing = pConfig?.model_cost
|
|
1032
1033
|
? `\`${pConfig.model_cost}\` (per million)`
|
|
1033
1034
|
: 'Not configured';
|
|
1035
|
+
const statusBudget = resolveBudget(settings);
|
|
1034
1036
|
const statusText = `### Curie Agent Status
|
|
1035
|
-
* **Version:** \`
|
|
1037
|
+
* **Version:** \`${VERSION}\`
|
|
1036
1038
|
* **Active Model:** \`${settings.model}\`
|
|
1037
1039
|
* **Active Provider:** \`${provider}\`
|
|
1038
1040
|
* **Approval Mode:** \`${settings.mode || 'auto'}\`
|
|
1039
1041
|
* **Reasoning Effort:** \`${settings.effort || 'auto'}\`
|
|
1040
1042
|
* **Workspace CWD:** \`${this.getSessionCwd(sessionId) || process.cwd()}\`
|
|
1041
|
-
* **Tools per Turn
|
|
1042
|
-
* **Web Search per Turn
|
|
1043
|
-
* **Model Context Window:** \`${(
|
|
1043
|
+
* **Tools per Turn / Run:** \`${String(settings.tools_per_call || DEFAULT_SETTINGS.tools_per_call)}\` / \`${String(settings.tools_per_run || DEFAULT_SETTINGS.tools_per_run)}\`
|
|
1044
|
+
* **Web Search per Turn / Run:** \`${String(settings.websearch_per_call || DEFAULT_SETTINGS.websearch_per_call)}\` / \`${String(settings.websearch_per_run || DEFAULT_SETTINGS.websearch_per_run)}\`
|
|
1045
|
+
* **Model Context Window:** \`${statusBudget.windowTokens.toLocaleString()} tokens\` (\`${statusBudget.usableTokens.toLocaleString()}\` usable, \`${statusBudget.reservedOutput.toLocaleString()}\` reserved for output)
|
|
1044
1046
|
* **Model Pricing:** ${pricing}
|
|
1045
|
-
* **Auto-Compaction:** \`${settings.auto_compact?.enabled
|
|
1047
|
+
* **Auto-Compaction:** \`${settings.auto_compact?.enabled ?? DEFAULT_SETTINGS.auto_compact.enabled}\` (warn \`${String(settings.auto_compact?.warn_threshold ?? DEFAULT_SETTINGS.auto_compact.warn_threshold)}%\`, suggest \`${String(settings.auto_compact?.threshold ?? DEFAULT_SETTINGS.auto_compact.threshold)}%\`, compact \`${String(settings.auto_compact?.forced_threshold ?? DEFAULT_SETTINGS.auto_compact.forced_threshold)}%\`)`;
|
|
1046
1048
|
emitDelta(statusText);
|
|
1047
1049
|
break;
|
|
1048
1050
|
}
|
|
@@ -1149,13 +1151,7 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
1149
1151
|
else {
|
|
1150
1152
|
settings.theme = theme;
|
|
1151
1153
|
this.settingsManager.update(settings);
|
|
1152
|
-
this.
|
|
1153
|
-
type: 'config-changed',
|
|
1154
|
-
id: Math.random().toString(36).substring(7),
|
|
1155
|
-
timestamp: Date.now(),
|
|
1156
|
-
key: 'theme',
|
|
1157
|
-
value: theme,
|
|
1158
|
-
});
|
|
1154
|
+
this.emitConfigChanged('theme', theme);
|
|
1159
1155
|
emitDelta(`Successfully switched theme to: **${theme}**`);
|
|
1160
1156
|
}
|
|
1161
1157
|
break;
|
|
@@ -1172,6 +1168,7 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
1172
1168
|
else {
|
|
1173
1169
|
settings.mode = args.toLowerCase();
|
|
1174
1170
|
this.settingsManager.update(settings);
|
|
1171
|
+
this.emitConfigChanged('mode', settings.mode);
|
|
1175
1172
|
emitDelta(`Successfully switched approval mode to: **${args.toLowerCase()}**`);
|
|
1176
1173
|
}
|
|
1177
1174
|
break;
|
|
@@ -1188,6 +1185,7 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
1188
1185
|
else {
|
|
1189
1186
|
settings.effort = args.toLowerCase();
|
|
1190
1187
|
this.settingsManager.update(settings);
|
|
1188
|
+
this.emitConfigChanged('effort', settings.effort);
|
|
1191
1189
|
emitDelta(`Successfully switched reasoning effort to: **${args.toLowerCase()}**`);
|
|
1192
1190
|
}
|
|
1193
1191
|
break;
|
|
@@ -1215,8 +1213,11 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
1215
1213
|
settings.websearch_per_call = wsVal;
|
|
1216
1214
|
}
|
|
1217
1215
|
}
|
|
1218
|
-
this.settingsManager.
|
|
1219
|
-
|
|
1216
|
+
this.settingsManager.update({
|
|
1217
|
+
tools_per_call: settings.tools_per_call,
|
|
1218
|
+
websearch_per_call: settings.websearch_per_call,
|
|
1219
|
+
});
|
|
1220
|
+
emitDelta(`Tool limits updated! Tools per turn: **${String(settings.tools_per_call)}**, WebSearch per turn: **${String(settings.websearch_per_call ?? 5)}**`);
|
|
1220
1221
|
}
|
|
1221
1222
|
}
|
|
1222
1223
|
break;
|
|
@@ -1235,9 +1236,8 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
1235
1236
|
emitDelta(`Invalid value: "${args}". Must be a positive integer.`);
|
|
1236
1237
|
}
|
|
1237
1238
|
else {
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
emitDelta(`WebSearch/WebFetch limit per turn set to: **${val}**`);
|
|
1239
|
+
this.settingsManager.update({ websearch_per_call: val });
|
|
1240
|
+
emitDelta(`WebSearch/WebFetch limit per turn set to: **${String(val)}**`);
|
|
1241
1241
|
}
|
|
1242
1242
|
}
|
|
1243
1243
|
break;
|
|
@@ -1255,9 +1255,13 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
1255
1255
|
const lines = [`### Configured MCP Servers (${keys.length}):`];
|
|
1256
1256
|
keys.forEach(k => {
|
|
1257
1257
|
const cfg = servers[k];
|
|
1258
|
-
const status = this.daemonApp?.mcpStatus
|
|
1259
|
-
|
|
1260
|
-
|
|
1258
|
+
const status = this.daemonApp?.mcpStatus.find(s => s.serverId === k);
|
|
1259
|
+
// No recorded status is not the same as a failed connection —
|
|
1260
|
+
// say so rather than claiming the server is down.
|
|
1261
|
+
const connectedLabel = status === undefined
|
|
1262
|
+
? '❔ Unknown (no connection status recorded)'
|
|
1263
|
+
: status.connected ? '✅ Connected' : '❌ Disconnected';
|
|
1264
|
+
const toolsList = status?.tools.length ? status.tools.join(', ') : 'none';
|
|
1261
1265
|
lines.push(`* **${k}**: \`${cfg?.command}\` ${cfg?.args?.join(' ') ?? ''}`);
|
|
1262
1266
|
lines.push(` └─ Status: ${connectedLabel}`);
|
|
1263
1267
|
lines.push(` └─ Tools: _${toolsList}_`);
|
|
@@ -1362,122 +1366,121 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
1362
1366
|
const sub = parts[0]?.toLowerCase();
|
|
1363
1367
|
const arg1 = parts[1]?.toLowerCase();
|
|
1364
1368
|
const arg2 = parts[2]?.toLowerCase();
|
|
1369
|
+
const d = DEFAULT_SETTINGS.auto_compact;
|
|
1370
|
+
/** Persist through update() — get() returns a deep clone, so mutating it is a no-op. */
|
|
1371
|
+
const saveAutoCompact = (patch) => {
|
|
1372
|
+
this.settingsManager.update({ auto_compact: { ...settings.auto_compact, ...patch } });
|
|
1373
|
+
};
|
|
1365
1374
|
if (sub === 'auto') {
|
|
1366
|
-
const
|
|
1367
|
-
if (!s.auto_compact) {
|
|
1368
|
-
s.auto_compact = { enabled: 'on', threshold: 80, warn_threshold: 15, forced_threshold: 85 };
|
|
1369
|
-
}
|
|
1375
|
+
const ac = settings.auto_compact ?? d;
|
|
1370
1376
|
if (!arg1) {
|
|
1371
|
-
emitDelta(`### Auto-Compaction Settings
|
|
1372
|
-
* **Auto-compact**: \`${s.auto_compact.enabled}\`
|
|
1373
|
-
* **Threshold**: \`${s.auto_compact.threshold ?? 80}%\`
|
|
1374
|
-
* **Warning Threshold**: \`${s.auto_compact.warn_threshold ?? 15}%\`
|
|
1375
|
-
* **Pricing Warn**: \`${s.pricing_tier_warn ?? 'off'}\`
|
|
1377
|
+
emitDelta(`### Auto-Compaction Settings
|
|
1376
1378
|
|
|
1377
|
-
**
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1379
|
+
* **Auto-compact**: \`${ac.enabled}\`
|
|
1380
|
+
* **Warn at**: \`${String(ac.warn_threshold ?? d.warn_threshold)}%\`
|
|
1381
|
+
* **Suggest at**: \`${String(ac.threshold ?? d.threshold)}%\`
|
|
1382
|
+
* **Compact at**: \`${String(ac.forced_threshold ?? d.forced_threshold)}%\`
|
|
1383
|
+
* **Summarizer model**: \`${ac.model || '(active model)'}\`
|
|
1384
|
+
* **Pricing warn**: \`${settings.pricing_tier_warn ?? 'off'}\`
|
|
1385
|
+
|
|
1386
|
+
**Usage**: \`/context auto [on|off|threshold N|warn N|forced N|pricing on/off]\``);
|
|
1383
1387
|
}
|
|
1384
|
-
else if (arg1 === 'off') {
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
emitDelta(`Autocompaction disabled.`);
|
|
1388
|
+
else if (arg1 === 'on' || arg1 === 'off') {
|
|
1389
|
+
saveAutoCompact({ enabled: arg1 });
|
|
1390
|
+
emitDelta(`Auto-compaction ${arg1 === 'on' ? 'enabled' : 'disabled'}.`);
|
|
1388
1391
|
}
|
|
1389
|
-
else if (arg1 === 'threshold' && arg2) {
|
|
1392
|
+
else if ((arg1 === 'threshold' || arg1 === 'warn' || arg1 === 'forced') && arg2) {
|
|
1390
1393
|
const pct = parseInt(arg2, 10);
|
|
1391
|
-
if (isNaN(pct) || pct <
|
|
1392
|
-
emitDelta(`Invalid
|
|
1394
|
+
if (isNaN(pct) || pct < 5 || pct > 99) {
|
|
1395
|
+
emitDelta(`Invalid value. Use a percentage between 5 and 99.`);
|
|
1393
1396
|
}
|
|
1394
1397
|
else {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
emitDelta(
|
|
1398
|
+
const key = arg1 === 'threshold' ? 'threshold' : arg1 === 'warn' ? 'warn_threshold' : 'forced_threshold';
|
|
1399
|
+
saveAutoCompact({ [key]: pct });
|
|
1400
|
+
emitDelta(`${arg1 === 'threshold' ? 'Suggest' : arg1 === 'warn' ? 'Warn' : 'Forced compaction'} threshold set to ${String(pct)}%.`);
|
|
1398
1401
|
}
|
|
1399
1402
|
}
|
|
1400
|
-
else if (arg1 === '
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
emitDelta(`Invalid warning threshold. Use a value between 5 and 95.`);
|
|
1404
|
-
}
|
|
1405
|
-
else {
|
|
1406
|
-
s.auto_compact.warn_threshold = pct;
|
|
1407
|
-
this.settingsManager.save();
|
|
1408
|
-
emitDelta(`Warning threshold set to ${pct}%.`);
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
else if (arg1 === 'pricing') {
|
|
1412
|
-
if (arg2 === 'on') {
|
|
1413
|
-
s.pricing_tier_warn = 'on';
|
|
1414
|
-
this.settingsManager.save();
|
|
1415
|
-
emitDelta(`Pricing tier warnings enabled.`);
|
|
1416
|
-
}
|
|
1417
|
-
else if (arg2 === 'off') {
|
|
1418
|
-
s.pricing_tier_warn = 'off';
|
|
1419
|
-
this.settingsManager.save();
|
|
1420
|
-
emitDelta(`Pricing tier warnings disabled.`);
|
|
1421
|
-
}
|
|
1422
|
-
else {
|
|
1423
|
-
emitDelta(`Usage: \`/context auto pricing on/off\``);
|
|
1424
|
-
}
|
|
1403
|
+
else if (arg1 === 'pricing' && (arg2 === 'on' || arg2 === 'off')) {
|
|
1404
|
+
this.settingsManager.update({ pricing_tier_warn: arg2 });
|
|
1405
|
+
emitDelta(`Pricing tier warnings ${arg2 === 'on' ? 'enabled' : 'disabled'}.`);
|
|
1425
1406
|
}
|
|
1426
1407
|
else {
|
|
1427
|
-
emitDelta(`Usage: \`/context auto [on|off|threshold N|warn N|pricing on/off]\``);
|
|
1408
|
+
emitDelta(`Usage: \`/context auto [on|off|threshold N|warn N|forced N|pricing on/off]\``);
|
|
1428
1409
|
}
|
|
1429
1410
|
}
|
|
1430
1411
|
else if (sub === 'compact') {
|
|
1431
|
-
emitDelta(
|
|
1412
|
+
emitDelta(`Compacting conversation history…`);
|
|
1432
1413
|
try {
|
|
1433
|
-
const
|
|
1434
|
-
emitDelta(`\n\n
|
|
1414
|
+
const r = await this.runAutomaticCompaction(sessionId);
|
|
1415
|
+
emitDelta(`\n\n**Compacted.** ${String(r.summarizedMessageCount)} message(s) summarized, ` +
|
|
1416
|
+
`~${formatTokens(r.estimatedTokensBefore)} → ~${formatTokens(r.estimatedTokensAfter)} tokens. ` +
|
|
1417
|
+
`The full transcript is preserved on disk.\n\n**Summary:**\n\n${r.summary}`);
|
|
1435
1418
|
}
|
|
1436
1419
|
catch (err) {
|
|
1437
|
-
emitDelta(`\n\n
|
|
1420
|
+
emitDelta(`\n\n**Compaction failed**: ${err instanceof Error ? err.message : String(err)}`);
|
|
1438
1421
|
}
|
|
1439
1422
|
}
|
|
1423
|
+
else if (sub === 'messages') {
|
|
1424
|
+
const messages = reconstructMessagesFromEvents(this.sessionStore.loadEvents(sessionId) || []);
|
|
1425
|
+
if (messages.length === 0) {
|
|
1426
|
+
emitDelta(`No messages in this session yet.`);
|
|
1427
|
+
break;
|
|
1428
|
+
}
|
|
1429
|
+
const lines = [`### Messages (${String(messages.length)})`, '', '| # | Role | Tokens | Detail |', '|---|---|---|---|'];
|
|
1430
|
+
messages.forEach((m, i) => {
|
|
1431
|
+
const tokens = estimateRequestTokens({ messages: [m] });
|
|
1432
|
+
const detail = m.role === 'tool'
|
|
1433
|
+
? (m.toolName ?? 'tool')
|
|
1434
|
+
: m.role === 'user'
|
|
1435
|
+
? `${String(m.content).slice(0, 60).replace(/[\n|]/g, ' ')}…`
|
|
1436
|
+
: m.content.map((b) => b.type === 'tool-use' ? `→ ${b.name}` : b.type).join(', ');
|
|
1437
|
+
lines.push(`| ${String(i + 1)} | ${m.role} | ${formatTokens(tokens)} | ${detail} |`);
|
|
1438
|
+
});
|
|
1439
|
+
emitDelta(lines.join('\n'));
|
|
1440
|
+
}
|
|
1440
1441
|
else {
|
|
1442
|
+
// Emit measurements, not markup. The TUI and the web dashboard each
|
|
1443
|
+
// render this natively — the hand-written HTML this replaces showed
|
|
1444
|
+
// a themed gauge in the browser and nothing at all in the terminal.
|
|
1445
|
+
const budget = resolveBudget(settings);
|
|
1441
1446
|
const activeLoop = this.turnLoops.get(sessionId);
|
|
1442
|
-
const
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
const
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
const
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1447
|
+
const model = settings.providers[settings.current_provider]?.model || settings.model;
|
|
1448
|
+
const messages = activeLoop
|
|
1449
|
+
? activeLoop.getMessages()
|
|
1450
|
+
: reconstructMessagesFromEvents(this.sessionStore.loadEvents(sessionId) || []);
|
|
1451
|
+
const chars = breakdownChars({
|
|
1452
|
+
system: this.systemPrompt,
|
|
1453
|
+
messages,
|
|
1454
|
+
toolDefinitions: this.tools.map((t) => t.definition),
|
|
1455
|
+
});
|
|
1456
|
+
const toTokens = (c) => Math.ceil(c / DEFAULT_CALIBRATION);
|
|
1457
|
+
const breakdown = [
|
|
1458
|
+
{ label: 'System prompt', tokens: toTokens(chars.system) },
|
|
1459
|
+
{ label: 'Tool definitions', tokens: toTokens(chars.toolDefinitions) },
|
|
1460
|
+
{ label: 'Conversation', tokens: toTokens(chars.conversation) },
|
|
1461
|
+
{ label: 'Tool results', tokens: toTokens(chars.toolResults) },
|
|
1462
|
+
];
|
|
1463
|
+
const usedTokens = breakdown.reduce((sum, b) => sum + b.tokens, 0);
|
|
1464
|
+
const report = {
|
|
1465
|
+
type: 'context-report',
|
|
1466
|
+
id: crypto.randomUUID(),
|
|
1467
|
+
model,
|
|
1468
|
+
windowTokens: budget.windowTokens,
|
|
1469
|
+
usedTokens,
|
|
1470
|
+
reservedOutput: budget.reservedOutput,
|
|
1471
|
+
breakdown,
|
|
1472
|
+
timestamp: Date.now(),
|
|
1458
1473
|
};
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
emitDelta(`<div style="background:var(--s3);border-radius:8px;padding:12px;margin:8px 0">` +
|
|
1470
|
-
`<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">` +
|
|
1471
|
-
`<span style="font-family:monospace;font-size:12px;color:var(--text)">Context Window (${model})</span>` +
|
|
1472
|
-
`<span style="font-family:monospace;font-size:13px;font-weight:bold;color:${barColor}">${pct}%</span>` +
|
|
1473
|
-
`</div>` +
|
|
1474
|
-
`<div style="background:var(--s2);border-radius:4px;height:8px;overflow:hidden">` +
|
|
1475
|
-
`<div style="width:${pct}%;height:100%;background:${barGradient};border-radius:4px"></div>` +
|
|
1476
|
-
`</div>` +
|
|
1477
|
-
`<div style="display:flex;justify-content:space-between;margin-top:8px;font-family:monospace;font-size:11px;color:var(--muted)">` +
|
|
1478
|
-
`<span>In: ${fmt(input)}</span><span>Out: ${fmt(output)}</span><span>Max: ${fmt(windowSize)}</span>` +
|
|
1479
|
-
`</div></div>`);
|
|
1480
|
-
}
|
|
1474
|
+
this.sharedEventBus?.emit({ ...report, sessionId });
|
|
1475
|
+
// Plain-text fallback so the command still says something useful on a
|
|
1476
|
+
// surface that has not yet learned the event.
|
|
1477
|
+
const pct = fillPct(usedTokens, budget);
|
|
1478
|
+
const rows = breakdown
|
|
1479
|
+
.map((b) => `* ${b.label}: ${formatTokens(b.tokens)} (${String(usedTokens > 0 ? Math.round((b.tokens / usedTokens) * 100) : 0)}%)`)
|
|
1480
|
+
.join('\n');
|
|
1481
|
+
emitDelta(`### Context Window — \`${model}\`\n\n` +
|
|
1482
|
+
`**${String(pct)}%** — ${formatTokens(usedTokens)} of ${formatTokens(budget.usableTokens)} usable ` +
|
|
1483
|
+
`(${formatTokens(budget.windowTokens)} window, ${formatTokens(budget.reservedOutput)} reserved for output)\n\n${rows}`);
|
|
1481
1484
|
}
|
|
1482
1485
|
break;
|
|
1483
1486
|
}
|
|
@@ -1581,7 +1584,7 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
1581
1584
|
}
|
|
1582
1585
|
case 'enable': {
|
|
1583
1586
|
settings.heartbeat.schedule = 'on';
|
|
1584
|
-
this.settingsManager.
|
|
1587
|
+
this.settingsManager.update({ heartbeat: settings.heartbeat });
|
|
1585
1588
|
if (this.daemonApp) {
|
|
1586
1589
|
this.daemonApp.taskManager.rescheduleFromSettings({
|
|
1587
1590
|
HEARTBEAT_INTRADAY: settings.heartbeat.intraday,
|
|
@@ -1596,7 +1599,7 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
1596
1599
|
}
|
|
1597
1600
|
case 'disable': {
|
|
1598
1601
|
settings.heartbeat.schedule = 'off';
|
|
1599
|
-
this.settingsManager.
|
|
1602
|
+
this.settingsManager.update({ heartbeat: settings.heartbeat });
|
|
1600
1603
|
if (this.daemonApp) {
|
|
1601
1604
|
this.daemonApp.taskManager.cancelAllHeartbeats();
|
|
1602
1605
|
}
|
|
@@ -1624,7 +1627,7 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
1624
1627
|
}
|
|
1625
1628
|
else {
|
|
1626
1629
|
settings.heartbeat.intraday = tokens.join(',');
|
|
1627
|
-
this.settingsManager.
|
|
1630
|
+
this.settingsManager.update({ heartbeat: settings.heartbeat });
|
|
1628
1631
|
if (this.daemonApp && settings.heartbeat.schedule === 'on') {
|
|
1629
1632
|
this.daemonApp.taskManager.rescheduleFromSettings({
|
|
1630
1633
|
HEARTBEAT_INTRADAY: settings.heartbeat.intraday,
|
|
@@ -1652,7 +1655,7 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
1652
1655
|
}
|
|
1653
1656
|
else {
|
|
1654
1657
|
settings.heartbeat.daily = rest;
|
|
1655
|
-
this.settingsManager.
|
|
1658
|
+
this.settingsManager.update({ heartbeat: settings.heartbeat });
|
|
1656
1659
|
if (this.daemonApp && settings.heartbeat.schedule === 'on') {
|
|
1657
1660
|
this.daemonApp.taskManager.rescheduleFromSettings({
|
|
1658
1661
|
HEARTBEAT_INTRADAY: settings.heartbeat.intraday,
|
|
@@ -1681,7 +1684,7 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
1681
1684
|
}
|
|
1682
1685
|
else {
|
|
1683
1686
|
settings.heartbeat.weekly = rest;
|
|
1684
|
-
this.settingsManager.
|
|
1687
|
+
this.settingsManager.update({ heartbeat: settings.heartbeat });
|
|
1685
1688
|
if (this.daemonApp && settings.heartbeat.schedule === 'on') {
|
|
1686
1689
|
this.daemonApp.taskManager.rescheduleFromSettings({
|
|
1687
1690
|
HEARTBEAT_INTRADAY: settings.heartbeat.intraday,
|
|
@@ -1709,7 +1712,7 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
1709
1712
|
}
|
|
1710
1713
|
else {
|
|
1711
1714
|
settings.heartbeat.monthly = rest;
|
|
1712
|
-
this.settingsManager.
|
|
1715
|
+
this.settingsManager.update({ heartbeat: settings.heartbeat });
|
|
1713
1716
|
if (this.daemonApp && settings.heartbeat.schedule === 'on') {
|
|
1714
1717
|
this.daemonApp.taskManager.rescheduleFromSettings({
|
|
1715
1718
|
HEARTBEAT_INTRADAY: settings.heartbeat.intraday,
|
|
@@ -1737,7 +1740,7 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
1737
1740
|
}
|
|
1738
1741
|
else {
|
|
1739
1742
|
settings.heartbeat.dreaming = rest;
|
|
1740
|
-
this.settingsManager.
|
|
1743
|
+
this.settingsManager.update({ heartbeat: settings.heartbeat });
|
|
1741
1744
|
if (this.daemonApp && settings.heartbeat.schedule === 'on') {
|
|
1742
1745
|
this.daemonApp.taskManager.rescheduleFromSettings({
|
|
1743
1746
|
HEARTBEAT_INTRADAY: settings.heartbeat.intraday,
|
|
@@ -2182,7 +2185,16 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
2182
2185
|
break;
|
|
2183
2186
|
}
|
|
2184
2187
|
default: {
|
|
2185
|
-
|
|
2188
|
+
// Distinguish "no such command" from "real command, wrong surface" —
|
|
2189
|
+
// client-handled commands need terminal/browser state the daemon
|
|
2190
|
+
// doesn't have.
|
|
2191
|
+
const known = findSlashCommand(command);
|
|
2192
|
+
if (known?.handler === 'client') {
|
|
2193
|
+
emitDelta(`\`/${known.name}\` is handled by the interface, not the daemon — ${known.description.toLowerCase()}. Usage: \`${known.usage}\``);
|
|
2194
|
+
}
|
|
2195
|
+
else {
|
|
2196
|
+
emitDelta(`Unknown slash command: **${text}**. Type \`/help\` to see all available commands.`);
|
|
2197
|
+
}
|
|
2186
2198
|
break;
|
|
2187
2199
|
}
|
|
2188
2200
|
}
|
|
@@ -2224,129 +2236,50 @@ Usage: \`/model window <tokens>\` (e.g., \`/model window 120000\`).`);
|
|
|
2224
2236
|
}
|
|
2225
2237
|
return true;
|
|
2226
2238
|
}
|
|
2227
|
-
|
|
2239
|
+
/**
|
|
2240
|
+
* Compact a session's history.
|
|
2241
|
+
*
|
|
2242
|
+
* The summarization itself lives in `@curie-agent/core` so the TurnLoop can
|
|
2243
|
+
* run it mid-run — this is the out-of-band entry point for `/context compact`
|
|
2244
|
+
* on an idle session.
|
|
2245
|
+
*
|
|
2246
|
+
* Nothing is deleted. A `compaction` marker is appended and message
|
|
2247
|
+
* reconstruction replays forward from it, so the full transcript survives for
|
|
2248
|
+
* the UI, resume and audit while the model carries only the summary.
|
|
2249
|
+
*/
|
|
2250
|
+
async runAutomaticCompaction(sessionId) {
|
|
2228
2251
|
if (!this.createProvider) {
|
|
2229
2252
|
throw new Error('No provider configured');
|
|
2230
2253
|
}
|
|
2231
2254
|
const settings = this.settingsManager.get();
|
|
2232
2255
|
const provider = this.createProvider(settings);
|
|
2233
|
-
const
|
|
2256
|
+
const activeModel = settings.providers[settings.current_provider]?.model || settings.model;
|
|
2234
2257
|
const events = this.sessionStore.loadEvents(sessionId) || [];
|
|
2235
2258
|
if (events.length === 0) {
|
|
2236
2259
|
throw new Error('No events to compact');
|
|
2237
2260
|
}
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
for (const e of events) {
|
|
2241
|
-
if (e.type === 'user-prompt' && e.text) {
|
|
2242
|
-
transcriptParts.push(`User: ${e.text}`);
|
|
2243
|
-
}
|
|
2244
|
-
else if (e.type === 'assistant-delta' && e.text) {
|
|
2245
|
-
const lastIdx = transcriptParts.length - 1;
|
|
2246
|
-
if (lastIdx >= 0 && transcriptParts[lastIdx]?.startsWith('Assistant:')) {
|
|
2247
|
-
transcriptParts[lastIdx] += e.text;
|
|
2248
|
-
}
|
|
2249
|
-
else {
|
|
2250
|
-
transcriptParts.push(`Assistant: ${e.text}`);
|
|
2251
|
-
}
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
const transcript = transcriptParts.join('\n\n');
|
|
2255
|
-
if (!transcript.trim()) {
|
|
2261
|
+
const messages = reconstructMessagesFromEvents(events);
|
|
2262
|
+
if (messages.length === 0) {
|
|
2256
2263
|
throw new Error('No conversational history found to compact');
|
|
2257
2264
|
}
|
|
2258
|
-
const
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
const
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
text: `This is a continuation of a compacted conversation. Here is the high-fidelity summary of our session so far:\n\n${cleanSummary}\n\nLet's continue!`,
|
|
2277
|
-
cwd: process.cwd(),
|
|
2278
|
-
timestamp: Date.now() + 1,
|
|
2279
|
-
},
|
|
2280
|
-
{
|
|
2281
|
-
type: 'assistant-delta',
|
|
2282
|
-
id: crypto.randomUUID(),
|
|
2283
|
-
text: `Got it! I have fully restored our conversation summary and details. Let let me know what you would like to do next!`,
|
|
2284
|
-
timestamp: Date.now() + 2,
|
|
2285
|
-
},
|
|
2286
|
-
{
|
|
2287
|
-
type: 'assistant-stop',
|
|
2288
|
-
id: crypto.randomUUID(),
|
|
2289
|
-
timestamp: Date.now() + 3,
|
|
2290
|
-
}
|
|
2291
|
-
];
|
|
2292
|
-
const data = newEvents.map((e) => JSON.stringify(e)).join('\n') + '\n';
|
|
2293
|
-
writeFileSync(eventsPath, data, 'utf-8');
|
|
2294
|
-
return cleanSummary;
|
|
2295
|
-
}
|
|
2296
|
-
async checkContextThresholds(sessionId) {
|
|
2297
|
-
const settings = this.settingsManager.get();
|
|
2298
|
-
const history = this.sessionStore.loadEvents(sessionId) || [];
|
|
2299
|
-
const usageEvents = history.filter((e) => e.type === 'usage');
|
|
2300
|
-
const latestUsage = usageEvents[usageEvents.length - 1];
|
|
2301
|
-
const input = latestUsage?.inputTokens ?? 0;
|
|
2302
|
-
if (input === 0)
|
|
2303
|
-
return; // No token data yet
|
|
2304
|
-
const windowSize = settings.providers?.[settings.current_provider]?.model_context_window ?? 200000;
|
|
2305
|
-
const pct = Math.min(100, Math.round((input / windowSize) * 100));
|
|
2306
|
-
const autoCompact = settings.auto_compact || { enabled: 'on', threshold: 80, warn_threshold: 60, forced_threshold: 85 };
|
|
2307
|
-
const warnThresh = autoCompact.warn_threshold ?? 60;
|
|
2308
|
-
const compactThresh = autoCompact.threshold ?? 80;
|
|
2309
|
-
const forcedThresh = autoCompact.forced_threshold ?? 85;
|
|
2310
|
-
const enabled = autoCompact.enabled ?? 'on';
|
|
2311
|
-
if (pct >= forcedThresh && enabled === 'on') {
|
|
2312
|
-
try {
|
|
2313
|
-
const summary = await this.runAutomaticCompaction(sessionId, 'detailed');
|
|
2314
|
-
const successMessage = `⚡ **Auto-Compaction Executed Successfully!**\n\nContext usage was at **${pct}%** (forced threshold: **${forcedThresh}%**).\nWe have summarized the conversation, reducing the history size down to just ~500 tokens. The agent will continue seamlessly!\n\n**Restored Context Summary:**\n\n${summary}`;
|
|
2315
|
-
const warningEvent = {
|
|
2316
|
-
type: 'context-warning',
|
|
2317
|
-
id: crypto.randomUUID(),
|
|
2318
|
-
message: successMessage,
|
|
2319
|
-
timestamp: Date.now(),
|
|
2320
|
-
};
|
|
2321
|
-
this.sharedEventBus?.emit({ ...warningEvent, sessionId });
|
|
2322
|
-
this.sessionStore.appendEvent(sessionId, { ...warningEvent, sessionId });
|
|
2323
|
-
}
|
|
2324
|
-
catch (err) {
|
|
2325
|
-
console.error('[compaction] Auto-compaction failed:', err);
|
|
2326
|
-
}
|
|
2327
|
-
}
|
|
2328
|
-
else if (pct >= compactThresh) {
|
|
2329
|
-
const suggestMessage = `⚠️ **Context Fill High (${pct}%)**\n\nYour context fill is at **${pct}%** (Threshold: **${compactThresh}%**). Suggesting conversation compaction.\n\nType \`/context compact\` to run compaction, summarize history, and free memory immediately!`;
|
|
2330
|
-
const warningEvent = {
|
|
2331
|
-
type: 'context-warning',
|
|
2332
|
-
id: crypto.randomUUID(),
|
|
2333
|
-
message: suggestMessage,
|
|
2334
|
-
timestamp: Date.now(),
|
|
2335
|
-
};
|
|
2336
|
-
this.sharedEventBus?.emit({ ...warningEvent, sessionId });
|
|
2337
|
-
this.sessionStore.appendEvent(sessionId, { ...warningEvent, sessionId });
|
|
2338
|
-
}
|
|
2339
|
-
else if (pct >= warnThresh) {
|
|
2340
|
-
const warnMessage = `⚠️ **Context Warning (${pct}%)**\n\nContext window is **${pct}%** full (Warning threshold: **${warnThresh}%**).`;
|
|
2341
|
-
const warningEvent = {
|
|
2342
|
-
type: 'context-warning',
|
|
2343
|
-
id: crypto.randomUUID(),
|
|
2344
|
-
message: warnMessage,
|
|
2345
|
-
timestamp: Date.now(),
|
|
2346
|
-
};
|
|
2347
|
-
this.sharedEventBus?.emit({ ...warningEvent, sessionId });
|
|
2348
|
-
this.sessionStore.appendEvent(sessionId, { ...warningEvent, sessionId });
|
|
2349
|
-
}
|
|
2265
|
+
const result = await compactMessages({
|
|
2266
|
+
messages,
|
|
2267
|
+
provider: provider,
|
|
2268
|
+
model: settings.auto_compact?.model || activeModel,
|
|
2269
|
+
budget: resolveBudget(settings),
|
|
2270
|
+
});
|
|
2271
|
+
const marker = {
|
|
2272
|
+
type: 'compaction',
|
|
2273
|
+
id: crypto.randomUUID(),
|
|
2274
|
+
summary: result.summary,
|
|
2275
|
+
summarizedMessageCount: result.summarizedMessageCount,
|
|
2276
|
+
tokensBefore: result.estimatedTokensBefore,
|
|
2277
|
+
tokensAfter: result.estimatedTokensAfter,
|
|
2278
|
+
timestamp: Date.now(),
|
|
2279
|
+
};
|
|
2280
|
+
this.sessionStore.appendEvent(sessionId, { ...marker, sessionId });
|
|
2281
|
+
this.sharedEventBus?.emit({ ...marker, sessionId });
|
|
2282
|
+
return result;
|
|
2350
2283
|
}
|
|
2351
2284
|
}
|
|
2352
2285
|
//# sourceMappingURL=jsonrpc-handler.js.map
|