@prestyj/cli 4.2.77 → 4.3.34
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/README.md +2 -2
- package/dist/cli.js +335 -85
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +33 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +42 -26
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +17 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/auto-update.d.ts +16 -3
- package/dist/core/auto-update.d.ts.map +1 -1
- package/dist/core/auto-update.js +126 -56
- package/dist/core/auto-update.js.map +1 -1
- package/dist/core/auto-update.test.d.ts +2 -0
- package/dist/core/auto-update.test.d.ts.map +1 -0
- package/dist/core/auto-update.test.js +205 -0
- package/dist/core/auto-update.test.js.map +1 -0
- package/dist/core/compaction/compactor.d.ts +18 -1
- package/dist/core/compaction/compactor.d.ts.map +1 -1
- package/dist/core/compaction/compactor.js +48 -7
- package/dist/core/compaction/compactor.js.map +1 -1
- package/dist/core/compaction/compactor.test.js +39 -6
- package/dist/core/compaction/compactor.test.js.map +1 -1
- package/dist/core/compaction/token-estimator.d.ts.map +1 -1
- package/dist/core/compaction/token-estimator.js +4 -0
- package/dist/core/compaction/token-estimator.js.map +1 -1
- package/dist/core/compaction/token-estimator.test.js +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +15 -2
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +74 -5
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/oauth/types.d.ts +1 -0
- package/dist/core/oauth/types.d.ts.map +1 -1
- package/dist/core/prompt-commands.d.ts.map +1 -1
- package/dist/core/prompt-commands.js +134 -0
- package/dist/core/prompt-commands.js.map +1 -1
- package/dist/core/settings-manager.d.ts +5 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +14 -2
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/interactive.js +1 -1
- package/dist/interactive.js.map +1 -1
- package/dist/modes/agent-home-mode.d.ts +21 -0
- package/dist/modes/agent-home-mode.d.ts.map +1 -0
- package/dist/modes/agent-home-mode.js +553 -0
- package/dist/modes/agent-home-mode.js.map +1 -0
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +30 -14
- package/dist/system-prompt.js.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +9 -2
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +4 -2
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +48 -33
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/edit.test.js +80 -20
- package/dist/tools/edit.test.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +12 -0
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +6 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/overflow.js +1 -1
- package/dist/tools/overflow.js.map +1 -1
- package/dist/tools/plan-mode.test.js +2 -2
- package/dist/tools/plan-mode.test.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +14 -1
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +20 -3
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/web-search.d.ts +9 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +241 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/tools/write.js +2 -2
- package/dist/tools/write.js.map +1 -1
- package/dist/tools/write.test.js +7 -9
- package/dist/tools/write.test.js.map +1 -1
- package/dist/ui/App.d.ts +4 -0
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +324 -161
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/activity-phrases.d.ts.map +1 -1
- package/dist/ui/activity-phrases.js +74 -129
- package/dist/ui/activity-phrases.js.map +1 -1
- package/dist/ui/components/ActivityIndicator.d.ts +3 -1
- package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
- package/dist/ui/components/ActivityIndicator.js +5 -2
- package/dist/ui/components/ActivityIndicator.js.map +1 -1
- package/dist/ui/components/AssistantMessage.d.ts +1 -0
- package/dist/ui/components/AssistantMessage.d.ts.map +1 -1
- package/dist/ui/components/AssistantMessage.js +2 -2
- package/dist/ui/components/AssistantMessage.js.map +1 -1
- package/dist/ui/components/DiffFrame.d.ts +10 -0
- package/dist/ui/components/DiffFrame.d.ts.map +1 -0
- package/dist/ui/components/DiffFrame.js +13 -0
- package/dist/ui/components/DiffFrame.js.map +1 -0
- package/dist/ui/components/Footer.d.ts +2 -3
- package/dist/ui/components/Footer.d.ts.map +1 -1
- package/dist/ui/components/Footer.js +6 -5
- package/dist/ui/components/Footer.js.map +1 -1
- package/dist/ui/components/InputArea.d.ts.map +1 -1
- package/dist/ui/components/InputArea.js +297 -8
- package/dist/ui/components/InputArea.js.map +1 -1
- package/dist/ui/components/Markdown.d.ts +1 -1
- package/dist/ui/components/Markdown.d.ts.map +1 -1
- package/dist/ui/components/Markdown.js +25 -213
- package/dist/ui/components/Markdown.js.map +1 -1
- package/dist/ui/components/ModelSelector.d.ts.map +1 -1
- package/dist/ui/components/ModelSelector.js +10 -2
- package/dist/ui/components/ModelSelector.js.map +1 -1
- package/dist/ui/components/Ratchet.d.ts.map +1 -1
- package/dist/ui/components/Ratchet.js +10 -3
- package/dist/ui/components/Ratchet.js.map +1 -1
- package/dist/ui/components/StreamingArea.d.ts.map +1 -1
- package/dist/ui/components/StreamingArea.js +2 -2
- package/dist/ui/components/StreamingArea.js.map +1 -1
- package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
- package/dist/ui/components/SubAgentPanel.js +5 -1
- package/dist/ui/components/SubAgentPanel.js.map +1 -1
- package/dist/ui/components/ThemeSelector.d.ts +9 -0
- package/dist/ui/components/ThemeSelector.d.ts.map +1 -0
- package/dist/ui/components/ThemeSelector.js +25 -0
- package/dist/ui/components/ThemeSelector.js.map +1 -0
- package/dist/ui/components/ToolExecution.d.ts +2 -0
- package/dist/ui/components/ToolExecution.d.ts.map +1 -1
- package/dist/ui/components/ToolExecution.js +95 -30
- package/dist/ui/components/ToolExecution.js.map +1 -1
- package/dist/ui/components/ToolGroupExecution.d.ts +3 -2
- package/dist/ui/components/ToolGroupExecution.d.ts.map +1 -1
- package/dist/ui/components/ToolGroupExecution.js +68 -51
- package/dist/ui/components/ToolGroupExecution.js.map +1 -1
- package/dist/ui/hooks/stall-retry-duplicate.test.d.ts +2 -0
- package/dist/ui/hooks/stall-retry-duplicate.test.d.ts.map +1 -0
- package/dist/ui/hooks/stall-retry-duplicate.test.js +94 -0
- package/dist/ui/hooks/stall-retry-duplicate.test.js.map +1 -0
- package/dist/ui/hooks/useAgentLoop.d.ts +10 -3
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +131 -16
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/hooks/useDoublePress.d.ts +8 -0
- package/dist/ui/hooks/useDoublePress.d.ts.map +1 -0
- package/dist/ui/hooks/useDoublePress.js +44 -0
- package/dist/ui/hooks/useDoublePress.js.map +1 -0
- package/dist/ui/hooks/useTerminalProgress.d.ts +1 -1
- package/dist/ui/hooks/useTerminalProgress.d.ts.map +1 -1
- package/dist/ui/hooks/useTerminalProgress.js +18 -15
- package/dist/ui/hooks/useTerminalProgress.js.map +1 -1
- package/dist/ui/hooks/useTerminalTitle.d.ts +3 -6
- package/dist/ui/hooks/useTerminalTitle.d.ts.map +1 -1
- package/dist/ui/hooks/useTerminalTitle.js +10 -19
- package/dist/ui/hooks/useTerminalTitle.js.map +1 -1
- package/dist/ui/live-item-flush.d.ts +4 -0
- package/dist/ui/live-item-flush.d.ts.map +1 -1
- package/dist/ui/live-item-flush.js +36 -0
- package/dist/ui/live-item-flush.js.map +1 -1
- package/dist/ui/login.d.ts.map +1 -1
- package/dist/ui/login.js +3 -0
- package/dist/ui/login.js.map +1 -1
- package/dist/ui/render.d.ts +3 -1
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +9 -3
- package/dist/ui/render.js.map +1 -1
- package/dist/ui/stores/create-store.d.ts +15 -0
- package/dist/ui/stores/create-store.d.ts.map +1 -0
- package/dist/ui/stores/create-store.js +30 -0
- package/dist/ui/stores/create-store.js.map +1 -0
- package/dist/ui/stores/index.d.ts +3 -0
- package/dist/ui/stores/index.d.ts.map +1 -0
- package/dist/ui/stores/index.js +3 -0
- package/dist/ui/stores/index.js.map +1 -0
- package/dist/ui/stores/taskbar-store.d.ts +19 -0
- package/dist/ui/stores/taskbar-store.d.ts.map +1 -0
- package/dist/ui/stores/taskbar-store.js +59 -0
- package/dist/ui/stores/taskbar-store.js.map +1 -0
- package/dist/ui/theme/dark-ansi.json +48 -0
- package/dist/ui/theme/dark-daltonized.json +48 -0
- package/dist/ui/theme/dark.json +22 -1
- package/dist/ui/theme/detect-theme.d.ts +12 -8
- package/dist/ui/theme/detect-theme.d.ts.map +1 -1
- package/dist/ui/theme/detect-theme.js +58 -39
- package/dist/ui/theme/detect-theme.js.map +1 -1
- package/dist/ui/theme/light-ansi.json +48 -0
- package/dist/ui/theme/light-daltonized.json +48 -0
- package/dist/ui/theme/light.json +44 -23
- package/dist/ui/theme/theme.d.ts +27 -1
- package/dist/ui/theme/theme.d.ts.map +1 -1
- package/dist/ui/theme/theme.js +24 -1
- package/dist/ui/theme/theme.js.map +1 -1
- package/dist/ui/utils/highlight.d.ts +1 -1
- package/dist/ui/utils/highlight.d.ts.map +1 -1
- package/dist/ui/utils/highlight.js +24 -4
- package/dist/ui/utils/highlight.js.map +1 -1
- package/dist/ui/utils/hyperlink.d.ts +11 -0
- package/dist/ui/utils/hyperlink.d.ts.map +1 -0
- package/dist/ui/utils/hyperlink.js +27 -0
- package/dist/ui/utils/hyperlink.js.map +1 -0
- package/dist/ui/utils/markdown-cache.d.ts +5 -6
- package/dist/ui/utils/markdown-cache.d.ts.map +1 -1
- package/dist/ui/utils/markdown-cache.js +5 -5
- package/dist/ui/utils/markdown-cache.js.map +1 -1
- package/dist/ui/utils/signal.d.ts +21 -0
- package/dist/ui/utils/signal.d.ts.map +1 -0
- package/dist/ui/utils/signal.js +30 -0
- package/dist/ui/utils/signal.js.map +1 -0
- package/dist/ui/utils/supports-hyperlinks.d.ts +6 -0
- package/dist/ui/utils/supports-hyperlinks.d.ts.map +1 -0
- package/dist/ui/utils/supports-hyperlinks.js +52 -0
- package/dist/ui/utils/supports-hyperlinks.js.map +1 -0
- package/dist/ui/utils/token-to-ansi.d.ts +9 -0
- package/dist/ui/utils/token-to-ansi.d.ts.map +1 -0
- package/dist/ui/utils/token-to-ansi.js +305 -0
- package/dist/ui/utils/token-to-ansi.js.map +1 -0
- package/dist/utils/session-title.d.ts +19 -0
- package/dist/utils/session-title.d.ts.map +1 -0
- package/dist/utils/session-title.js +82 -0
- package/dist/utils/session-title.js.map +1 -0
- package/package.json +7 -5
package/dist/core/auto-update.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
const PACKAGE_NAME = "@prestyj/cli";
|
|
6
6
|
const REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
7
|
-
const CHECK_INTERVAL_MS =
|
|
8
|
-
const FETCH_TIMEOUT_MS =
|
|
7
|
+
const CHECK_INTERVAL_MS = 1 * 60 * 60 * 1000; // 1 hour
|
|
8
|
+
const FETCH_TIMEOUT_MS = 10_000; // 10s — npm can be slow
|
|
9
9
|
var PackageManager;
|
|
10
10
|
(function (PackageManager) {
|
|
11
11
|
PackageManager["NPM"] = "npm";
|
|
@@ -35,12 +35,6 @@ function writeState(state) {
|
|
|
35
35
|
// Non-fatal
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
function shouldCheck() {
|
|
39
|
-
const state = readState();
|
|
40
|
-
if (!state)
|
|
41
|
-
return true;
|
|
42
|
-
return Date.now() - state.lastCheckedAt > CHECK_INTERVAL_MS;
|
|
43
|
-
}
|
|
44
38
|
function compareVersions(a, b) {
|
|
45
39
|
const pa = a.split(".").map(Number);
|
|
46
40
|
const pb = b.split(".").map(Number);
|
|
@@ -77,76 +71,152 @@ function detectInstallInfo() {
|
|
|
77
71
|
updateCommand: `npm install -g ${PACKAGE_NAME}@latest`,
|
|
78
72
|
};
|
|
79
73
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Fetch latest version from npm registry asynchronously.
|
|
76
|
+
*/
|
|
77
|
+
async function fetchLatestVersion() {
|
|
83
78
|
try {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
.catch(() => { clearTimeout(t); process.exit(1); });
|
|
91
|
-
`;
|
|
92
|
-
const result = spawnSync(process.execPath, ["--input-type=module", "-e", script], {
|
|
93
|
-
encoding: "utf-8",
|
|
94
|
-
timeout: FETCH_TIMEOUT_MS + 1000,
|
|
95
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
96
|
-
});
|
|
97
|
-
const version = result.stdout?.trim();
|
|
79
|
+
const controller = new AbortController();
|
|
80
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
81
|
+
const response = await fetch(REGISTRY_URL, { signal: controller.signal });
|
|
82
|
+
clearTimeout(timeout);
|
|
83
|
+
const data = (await response.json());
|
|
84
|
+
const version = data.version?.trim();
|
|
98
85
|
return version && /^\d+\.\d+\.\d+/.test(version) ? version : null;
|
|
99
86
|
}
|
|
100
87
|
catch {
|
|
101
88
|
return null;
|
|
102
89
|
}
|
|
103
90
|
}
|
|
104
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Perform the update in a detached background process so it doesn't block
|
|
93
|
+
* or interfere with the current CLI session. The update takes effect on
|
|
94
|
+
* the next launch.
|
|
95
|
+
*/
|
|
96
|
+
function performUpdateInBackground(command) {
|
|
105
97
|
try {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
const parts = command.split(" ");
|
|
99
|
+
const child = spawn(parts[0], parts.slice(1), {
|
|
100
|
+
detached: true,
|
|
101
|
+
stdio: "ignore",
|
|
109
102
|
env: { ...process.env, npm_config_loglevel: "silent" },
|
|
110
103
|
});
|
|
111
|
-
|
|
104
|
+
child.unref();
|
|
112
105
|
}
|
|
113
106
|
catch {
|
|
114
|
-
|
|
107
|
+
// Non-fatal — will retry next startup
|
|
115
108
|
}
|
|
116
109
|
}
|
|
117
110
|
/**
|
|
118
|
-
* Check for updates
|
|
119
|
-
*
|
|
111
|
+
* Check for updates at CLI startup. Two-phase approach:
|
|
112
|
+
*
|
|
113
|
+
* Phase 1 (instant): If a previous background check found a newer version,
|
|
114
|
+
* kick off a background update now. Non-blocking.
|
|
120
115
|
*
|
|
121
|
-
*
|
|
116
|
+
* Phase 2 (async): Fire off a background version check for the *next* startup.
|
|
117
|
+
*
|
|
118
|
+
* Returns a message to display, or null.
|
|
122
119
|
*/
|
|
123
120
|
export function checkAndAutoUpdate(currentVersion) {
|
|
124
121
|
try {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
122
|
+
const state = readState();
|
|
123
|
+
let message = null;
|
|
124
|
+
// Phase 1: Apply pending update from previous check
|
|
125
|
+
if (state?.updatePending && state.latestVersion) {
|
|
126
|
+
if (compareVersions(state.latestVersion, currentVersion) > 0) {
|
|
127
|
+
const info = detectInstallInfo();
|
|
128
|
+
if (info.updateCommand) {
|
|
129
|
+
// Run update in background — takes effect next launch
|
|
130
|
+
performUpdateInBackground(info.updateCommand);
|
|
131
|
+
message = `Updating ${PACKAGE_NAME} ${currentVersion} → ${state.latestVersion} (takes effect next launch)`;
|
|
132
|
+
writeState({
|
|
133
|
+
...state,
|
|
134
|
+
lastCheckedAt: Date.now(),
|
|
135
|
+
updatePending: false,
|
|
136
|
+
lastUpdateAttempt: Date.now(),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// Already on latest (user may have updated manually)
|
|
142
|
+
writeState({ ...state, updatePending: false });
|
|
143
|
+
}
|
|
143
144
|
}
|
|
144
|
-
//
|
|
145
|
-
|
|
145
|
+
// Phase 2: Schedule background check for next startup
|
|
146
|
+
const shouldCheck = !state || Date.now() - state.lastCheckedAt > CHECK_INTERVAL_MS;
|
|
147
|
+
if (shouldCheck) {
|
|
148
|
+
scheduleBackgroundCheck(currentVersion);
|
|
149
|
+
}
|
|
150
|
+
return message;
|
|
146
151
|
}
|
|
147
152
|
catch {
|
|
148
|
-
// Never block CLI startup
|
|
149
153
|
return null;
|
|
150
154
|
}
|
|
151
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Fire-and-forget async version check. Updates the state file so the
|
|
158
|
+
* next startup knows whether an update is available.
|
|
159
|
+
*/
|
|
160
|
+
function scheduleBackgroundCheck(currentVersion) {
|
|
161
|
+
fetchLatestVersion()
|
|
162
|
+
.then((latestVersion) => {
|
|
163
|
+
const newState = {
|
|
164
|
+
lastCheckedAt: Date.now(),
|
|
165
|
+
latestVersion: latestVersion ?? undefined,
|
|
166
|
+
updatePending: false,
|
|
167
|
+
};
|
|
168
|
+
if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
|
|
169
|
+
newState.updatePending = true;
|
|
170
|
+
}
|
|
171
|
+
writeState(newState);
|
|
172
|
+
})
|
|
173
|
+
.catch(() => {
|
|
174
|
+
// Non-fatal — will retry next time
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
// ── In-session periodic check ──────────────────────────────────────────
|
|
178
|
+
let periodicTimer = null;
|
|
179
|
+
/**
|
|
180
|
+
* Start periodic update checks during a long-running session.
|
|
181
|
+
* Checks every hour. If an update is found, calls `onUpdate` with a message.
|
|
182
|
+
*/
|
|
183
|
+
export function startPeriodicUpdateCheck(currentVersion, onUpdate) {
|
|
184
|
+
if (periodicTimer)
|
|
185
|
+
return; // Already running
|
|
186
|
+
periodicTimer = setInterval(() => {
|
|
187
|
+
fetchLatestVersion()
|
|
188
|
+
.then((latestVersion) => {
|
|
189
|
+
if (!latestVersion)
|
|
190
|
+
return;
|
|
191
|
+
if (compareVersions(latestVersion, currentVersion) <= 0)
|
|
192
|
+
return;
|
|
193
|
+
const info = detectInstallInfo();
|
|
194
|
+
if (!info.updateCommand)
|
|
195
|
+
return;
|
|
196
|
+
// Mark pending for next startup
|
|
197
|
+
writeState({
|
|
198
|
+
lastCheckedAt: Date.now(),
|
|
199
|
+
latestVersion,
|
|
200
|
+
updatePending: true,
|
|
201
|
+
});
|
|
202
|
+
onUpdate(`Update available: ${currentVersion} → ${latestVersion} (will update on next launch, or run: ${info.updateCommand})`);
|
|
203
|
+
// Stop checking once we've notified
|
|
204
|
+
stopPeriodicUpdateCheck();
|
|
205
|
+
})
|
|
206
|
+
.catch(() => {
|
|
207
|
+
// Non-fatal
|
|
208
|
+
});
|
|
209
|
+
}, CHECK_INTERVAL_MS);
|
|
210
|
+
// Don't keep the process alive just for update checks
|
|
211
|
+
periodicTimer.unref();
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Stop periodic update checks.
|
|
215
|
+
*/
|
|
216
|
+
export function stopPeriodicUpdateCheck() {
|
|
217
|
+
if (periodicTimer) {
|
|
218
|
+
clearInterval(periodicTimer);
|
|
219
|
+
periodicTimer = null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
152
222
|
//# sourceMappingURL=auto-update.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-update.js","sourceRoot":"","sources":["../../src/core/auto-update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"auto-update.js","sourceRoot":"","sources":["../../src/core/auto-update.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-update.test.d.ts","sourceRoot":"","sources":["../../src/core/auto-update.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { checkAndAutoUpdate, startPeriodicUpdateCheck, stopPeriodicUpdateCheck, } from "./auto-update.js";
|
|
7
|
+
// Use a temp directory for state file instead of the real ~/.gg
|
|
8
|
+
const tmpDir = path.join(os.tmpdir(), `gg-update-test-${process.pid}`);
|
|
9
|
+
// Mock the state file path
|
|
10
|
+
vi.mock("node:os", async () => {
|
|
11
|
+
const actual = await vi.importActual("node:os");
|
|
12
|
+
return {
|
|
13
|
+
...actual,
|
|
14
|
+
default: {
|
|
15
|
+
...actual,
|
|
16
|
+
homedir: () => tmpDir,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
// Mock spawn so we never actually run install commands
|
|
21
|
+
vi.mock("node:child_process", () => ({
|
|
22
|
+
spawn: vi.fn(() => ({ unref: vi.fn() })),
|
|
23
|
+
}));
|
|
24
|
+
function writeStateFile(state) {
|
|
25
|
+
fs.mkdirSync(path.join(tmpDir, ".ezcoder"), { recursive: true });
|
|
26
|
+
fs.writeFileSync(path.join(tmpDir, ".ezcoder", "update-state.json"), JSON.stringify(state));
|
|
27
|
+
}
|
|
28
|
+
function readStateFile() {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(fs.readFileSync(path.join(tmpDir, ".ezcoder", "update-state.json"), "utf-8"));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
fs.mkdirSync(path.join(tmpDir, ".ezcoder"), { recursive: true });
|
|
38
|
+
// Clear any existing state
|
|
39
|
+
try {
|
|
40
|
+
fs.unlinkSync(path.join(tmpDir, ".ezcoder", "update-state.json"));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// fine
|
|
44
|
+
}
|
|
45
|
+
vi.restoreAllMocks();
|
|
46
|
+
});
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
stopPeriodicUpdateCheck();
|
|
49
|
+
vi.restoreAllMocks();
|
|
50
|
+
try {
|
|
51
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// fine
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
describe("checkAndAutoUpdate", () => {
|
|
58
|
+
it("returns null on first run with no state file", () => {
|
|
59
|
+
// Mock fetch to return a version (but the background check is async, so
|
|
60
|
+
// on first call it just schedules a check and returns null)
|
|
61
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
62
|
+
json: () => Promise.resolve({ version: "99.0.0" }),
|
|
63
|
+
}));
|
|
64
|
+
const result = checkAndAutoUpdate("1.0.0");
|
|
65
|
+
expect(result).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
it("returns null when already on latest version", () => {
|
|
68
|
+
writeStateFile({
|
|
69
|
+
lastCheckedAt: Date.now(),
|
|
70
|
+
latestVersion: "1.0.0",
|
|
71
|
+
updatePending: true,
|
|
72
|
+
});
|
|
73
|
+
const result = checkAndAutoUpdate("1.0.0");
|
|
74
|
+
expect(result).toBeNull();
|
|
75
|
+
// Should clear the pending flag
|
|
76
|
+
const state = readStateFile();
|
|
77
|
+
expect(state?.updatePending).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
it("returns null when on a newer version than registry (manual install)", () => {
|
|
80
|
+
writeStateFile({
|
|
81
|
+
lastCheckedAt: Date.now(),
|
|
82
|
+
latestVersion: "1.0.0",
|
|
83
|
+
updatePending: true,
|
|
84
|
+
});
|
|
85
|
+
const result = checkAndAutoUpdate("2.0.0");
|
|
86
|
+
expect(result).toBeNull();
|
|
87
|
+
const state = readStateFile();
|
|
88
|
+
expect(state?.updatePending).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
it("triggers background update when pending update exists", () => {
|
|
91
|
+
writeStateFile({
|
|
92
|
+
lastCheckedAt: Date.now(),
|
|
93
|
+
latestVersion: "2.0.0",
|
|
94
|
+
updatePending: true,
|
|
95
|
+
});
|
|
96
|
+
const result = checkAndAutoUpdate("1.0.0");
|
|
97
|
+
expect(result).toContain("Updating");
|
|
98
|
+
expect(result).toContain("1.0.0");
|
|
99
|
+
expect(result).toContain("2.0.0");
|
|
100
|
+
expect(vi.mocked(spawn)).toHaveBeenCalled();
|
|
101
|
+
// Should clear the pending flag
|
|
102
|
+
const state = readStateFile();
|
|
103
|
+
expect(state?.updatePending).toBe(false);
|
|
104
|
+
expect(state?.lastUpdateAttempt).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
it("schedules background check when last check was long ago", async () => {
|
|
107
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
108
|
+
json: () => Promise.resolve({ version: "5.0.0" }),
|
|
109
|
+
});
|
|
110
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
111
|
+
writeStateFile({
|
|
112
|
+
lastCheckedAt: 0, // very old
|
|
113
|
+
latestVersion: "1.0.0",
|
|
114
|
+
updatePending: false,
|
|
115
|
+
});
|
|
116
|
+
checkAndAutoUpdate("1.0.0");
|
|
117
|
+
// Give the async check time to complete
|
|
118
|
+
await vi.waitFor(() => {
|
|
119
|
+
expect(fetchMock).toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
// Wait for the state to be written
|
|
122
|
+
await vi.waitFor(() => {
|
|
123
|
+
const state = readStateFile();
|
|
124
|
+
expect(state?.latestVersion).toBe("5.0.0");
|
|
125
|
+
expect(state?.updatePending).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
it("does not schedule check when recently checked", () => {
|
|
129
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
130
|
+
json: () => Promise.resolve({ version: "5.0.0" }),
|
|
131
|
+
});
|
|
132
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
133
|
+
writeStateFile({
|
|
134
|
+
lastCheckedAt: Date.now(), // just now
|
|
135
|
+
latestVersion: "1.0.0",
|
|
136
|
+
updatePending: false,
|
|
137
|
+
});
|
|
138
|
+
checkAndAutoUpdate("1.0.0");
|
|
139
|
+
// fetch should NOT be called since we just checked
|
|
140
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
141
|
+
});
|
|
142
|
+
it("handles fetch failure gracefully", async () => {
|
|
143
|
+
const fetchMock = vi.fn().mockRejectedValue(new Error("network error"));
|
|
144
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
145
|
+
writeStateFile({
|
|
146
|
+
lastCheckedAt: 0,
|
|
147
|
+
updatePending: false,
|
|
148
|
+
});
|
|
149
|
+
// Should not throw
|
|
150
|
+
const result = checkAndAutoUpdate("1.0.0");
|
|
151
|
+
expect(result).toBeNull();
|
|
152
|
+
});
|
|
153
|
+
it("handles corrupt state file gracefully", () => {
|
|
154
|
+
fs.writeFileSync(path.join(tmpDir, ".ezcoder", "update-state.json"), "not json{{{");
|
|
155
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
156
|
+
json: () => Promise.resolve({ version: "2.0.0" }),
|
|
157
|
+
}));
|
|
158
|
+
// Should not throw — treats corrupt file like no file
|
|
159
|
+
const result = checkAndAutoUpdate("1.0.0");
|
|
160
|
+
expect(result).toBeNull();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
describe("startPeriodicUpdateCheck", () => {
|
|
164
|
+
it("calls onUpdate when a newer version is found", async () => {
|
|
165
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
166
|
+
json: () => Promise.resolve({ version: "99.0.0" }),
|
|
167
|
+
}));
|
|
168
|
+
const onUpdate = vi.fn();
|
|
169
|
+
vi.useFakeTimers();
|
|
170
|
+
startPeriodicUpdateCheck("1.0.0", onUpdate);
|
|
171
|
+
// Advance past one check interval
|
|
172
|
+
await vi.advanceTimersByTimeAsync(1 * 60 * 60 * 1000 + 100);
|
|
173
|
+
expect(onUpdate).toHaveBeenCalledWith(expect.stringContaining("99.0.0"));
|
|
174
|
+
vi.useRealTimers();
|
|
175
|
+
});
|
|
176
|
+
it("does not call onUpdate when already on latest", async () => {
|
|
177
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
178
|
+
json: () => Promise.resolve({ version: "1.0.0" }),
|
|
179
|
+
}));
|
|
180
|
+
const onUpdate = vi.fn();
|
|
181
|
+
vi.useFakeTimers();
|
|
182
|
+
startPeriodicUpdateCheck("1.0.0", onUpdate);
|
|
183
|
+
await vi.advanceTimersByTimeAsync(1 * 60 * 60 * 1000 + 100);
|
|
184
|
+
expect(onUpdate).not.toHaveBeenCalled();
|
|
185
|
+
vi.useRealTimers();
|
|
186
|
+
});
|
|
187
|
+
it("stops checking after first notification", async () => {
|
|
188
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
189
|
+
json: () => Promise.resolve({ version: "99.0.0" }),
|
|
190
|
+
});
|
|
191
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
192
|
+
const onUpdate = vi.fn();
|
|
193
|
+
vi.useFakeTimers();
|
|
194
|
+
startPeriodicUpdateCheck("1.0.0", onUpdate);
|
|
195
|
+
// First interval
|
|
196
|
+
await vi.advanceTimersByTimeAsync(1 * 60 * 60 * 1000 + 100);
|
|
197
|
+
expect(onUpdate).toHaveBeenCalledTimes(1);
|
|
198
|
+
// Second interval — should NOT fire again (timer stopped)
|
|
199
|
+
fetchMock.mockClear();
|
|
200
|
+
await vi.advanceTimersByTimeAsync(1 * 60 * 60 * 1000 + 100);
|
|
201
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
202
|
+
vi.useRealTimers();
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
//# sourceMappingURL=auto-update.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-update.test.js","sourceRoot":"","sources":["../../src/core/auto-update.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,16 +1,33 @@
|
|
|
1
1
|
import { type Message, type Provider, type ContentPart } from "@prestyj/ai";
|
|
2
2
|
export interface CompactionResult {
|
|
3
|
+
/** Whether messages were actually reduced. */
|
|
4
|
+
compacted: boolean;
|
|
5
|
+
/** Why compaction was skipped (only set when compacted is false). */
|
|
6
|
+
reason?: string;
|
|
3
7
|
originalCount: number;
|
|
4
8
|
newCount: number;
|
|
5
9
|
tokensBeforeEstimate: number;
|
|
6
10
|
tokensAfterEstimate: number;
|
|
7
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Default token reserve for compaction.
|
|
14
|
+
* Leaves headroom for the model's next response + system overhead.
|
|
15
|
+
* Matches the widely-used Pi / Grok-CLI default of 16 384 tokens.
|
|
16
|
+
*/
|
|
17
|
+
export declare const COMPACTION_RESERVE_TOKENS = 16384;
|
|
8
18
|
/**
|
|
9
19
|
* Check if compaction should be triggered.
|
|
20
|
+
*
|
|
21
|
+
* Uses the reserve-based approach (contextWindow − reserveTokens) used by
|
|
22
|
+
* Pi, Grok-CLI, OpenClaw, BrowserOS, and most real-world agent frameworks.
|
|
23
|
+
* A percentage-based threshold is still supported: when both are supplied the
|
|
24
|
+
* more conservative (lower) limit wins.
|
|
10
25
|
*/
|
|
11
26
|
export declare function shouldCompact(messages: Message[], contextWindow: number, threshold?: number,
|
|
12
27
|
/** Actual API-reported token count — preferred over char-based estimate when available. */
|
|
13
|
-
actualTokens?: number
|
|
28
|
+
actualTokens?: number,
|
|
29
|
+
/** Fixed token reserve subtracted from contextWindow. Defaults to 16 384. */
|
|
30
|
+
reserveTokens?: number): boolean;
|
|
14
31
|
/**
|
|
15
32
|
* Find the index where recent messages should start, given a token budget.
|
|
16
33
|
* Walks backward from the end, accumulating token estimates, and returns the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compactor.d.ts","sourceRoot":"","sources":["../../../src/core/compaction/compactor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,KAAK,WAAW,EAEjB,MAAM,aAAa,CAAC;AAwCrB,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED
|
|
1
|
+
{"version":3,"file":"compactor.d.ts","sourceRoot":"","sources":["../../../src/core/compaction/compactor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,KAAK,WAAW,EAEjB,MAAM,aAAa,CAAC;AAwCrB,MAAM,WAAW,gBAAgB;IAC/B,8CAA8C;IAC9C,SAAS,EAAE,OAAO,CAAC;IACnB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAKhD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,OAAO,EAAE,EACnB,aAAa,EAAE,MAAM,EACrB,SAAS,SAAM;AACf,2FAA2F;AAC3F,YAAY,CAAC,EAAE,MAAM;AACrB,6EAA6E;AAC7E,aAAa,SAA4B,GACxC,OAAO,CAoBT;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CA4CnF;AAiED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CA6CpE;AAqID;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,EAAE,CAkBtF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,OAAO,EAAE,EACzB,OAAO,EAAE;IAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,GACpD,MAAM,CAgCR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,GAAG,MAAM,CAM1E;AAKD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,OAAO,CAC3B,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE;IACP,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GACA,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,CAAC,CAiP5D"}
|
|
@@ -30,14 +30,42 @@ const COMPACTION_SYSTEM_PROMPT = "You are a conversation compaction assistant. Y
|
|
|
30
30
|
"Focus on technical precision. Include specific identifiers (file paths, function names, etc.) " +
|
|
31
31
|
"that would be essential for continuation. Write in third person and maintain an objective, technical tone.";
|
|
32
32
|
const COMPACTION_USER_PROMPT = "Summarize the conversation above into a concise summary following the instructions. Output only the summary, nothing else.";
|
|
33
|
+
/**
|
|
34
|
+
* Default token reserve for compaction.
|
|
35
|
+
* Leaves headroom for the model's next response + system overhead.
|
|
36
|
+
* Matches the widely-used Pi / Grok-CLI default of 16 384 tokens.
|
|
37
|
+
*/
|
|
38
|
+
export const COMPACTION_RESERVE_TOKENS = 16_384;
|
|
39
|
+
/** Minimum messages before compaction is attempted (Mysti uses 4). */
|
|
40
|
+
const COMPACTION_MIN_MESSAGES = 4;
|
|
33
41
|
/**
|
|
34
42
|
* Check if compaction should be triggered.
|
|
43
|
+
*
|
|
44
|
+
* Uses the reserve-based approach (contextWindow − reserveTokens) used by
|
|
45
|
+
* Pi, Grok-CLI, OpenClaw, BrowserOS, and most real-world agent frameworks.
|
|
46
|
+
* A percentage-based threshold is still supported: when both are supplied the
|
|
47
|
+
* more conservative (lower) limit wins.
|
|
35
48
|
*/
|
|
36
49
|
export function shouldCompact(messages, contextWindow, threshold = 0.8,
|
|
37
50
|
/** Actual API-reported token count — preferred over char-based estimate when available. */
|
|
38
|
-
actualTokens
|
|
51
|
+
actualTokens,
|
|
52
|
+
/** Fixed token reserve subtracted from contextWindow. Defaults to 16 384. */
|
|
53
|
+
reserveTokens = COMPACTION_RESERVE_TOKENS) {
|
|
54
|
+
// Don't attempt compaction with too few messages — compact() would bail
|
|
55
|
+
// anyway (middleMessages <= 2), but this avoids the spinner + LLM auth dance.
|
|
56
|
+
// Skip the guard when actualTokens is provided (force-compact / overflow paths
|
|
57
|
+
// where the caller has precise token info regardless of message count).
|
|
58
|
+
if (actualTokens == null && messages.length < COMPACTION_MIN_MESSAGES) {
|
|
59
|
+
log("INFO", "compaction", `Context check: skipping — only ${messages.length} messages`);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
39
62
|
const estimated = actualTokens ?? estimateConversationTokens(messages);
|
|
40
|
-
const
|
|
63
|
+
const percentageLimit = contextWindow * threshold;
|
|
64
|
+
// Only apply the fixed reserve when the context window is large enough
|
|
65
|
+
// that the reserve is meaningful (< 50% of the window).
|
|
66
|
+
const limit = reserveTokens > 0 && reserveTokens < contextWindow * 0.5
|
|
67
|
+
? Math.min(percentageLimit, contextWindow - reserveTokens)
|
|
68
|
+
: percentageLimit;
|
|
41
69
|
const source = actualTokens != null ? "actual" : "estimated";
|
|
42
70
|
log("INFO", "compaction", `Context check: ${estimated} ${source} tokens, threshold ${limit}`);
|
|
43
71
|
return estimated > limit;
|
|
@@ -426,6 +454,8 @@ export async function compact(messages, options) {
|
|
|
426
454
|
return {
|
|
427
455
|
messages: [...messages],
|
|
428
456
|
result: {
|
|
457
|
+
compacted: false,
|
|
458
|
+
reason: "too_few_messages",
|
|
429
459
|
originalCount,
|
|
430
460
|
newCount: messages.length,
|
|
431
461
|
tokensBeforeEstimate,
|
|
@@ -543,13 +573,21 @@ export async function compact(messages, options) {
|
|
|
543
573
|
role: "user",
|
|
544
574
|
content: `[Previous conversation summary]\n\n${summaryText}${fileTrackingSection}`,
|
|
545
575
|
};
|
|
576
|
+
// Skip the assistant ack when recentMessages starts with an assistant message
|
|
577
|
+
// to prevent consecutive assistant messages that the Anthropic API rejects.
|
|
578
|
+
// This happens when findRecentCutPoint backs up from a tool to an assistant.
|
|
579
|
+
const skipAck = recentMessages.length > 0 && recentMessages[0].role === "assistant";
|
|
546
580
|
const newMessages = [
|
|
547
581
|
systemMessage,
|
|
548
582
|
summaryMessage,
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
583
|
+
...(skipAck
|
|
584
|
+
? []
|
|
585
|
+
: [
|
|
586
|
+
{
|
|
587
|
+
role: "assistant",
|
|
588
|
+
content: "Understood — I have the context from what was discussed earlier.",
|
|
589
|
+
},
|
|
590
|
+
]),
|
|
553
591
|
...recentMessages,
|
|
554
592
|
];
|
|
555
593
|
// Repair tool_use / tool_result pairing in the final message array.
|
|
@@ -562,7 +600,9 @@ export async function compact(messages, options) {
|
|
|
562
600
|
// But never pop below 3 messages (system + summary + ack) — removing the
|
|
563
601
|
// compaction ack would leave only the summary, causing `ezcoder continue`
|
|
564
602
|
// to restore just 1 message instead of the full session.
|
|
565
|
-
|
|
603
|
+
const minMessages = skipAck ? 2 : 3;
|
|
604
|
+
while (newMessages.length > minMessages &&
|
|
605
|
+
newMessages[newMessages.length - 1].role === "assistant") {
|
|
566
606
|
newMessages.pop();
|
|
567
607
|
}
|
|
568
608
|
const tokensAfterEstimate = estimateConversationTokens(newMessages);
|
|
@@ -581,6 +621,7 @@ export async function compact(messages, options) {
|
|
|
581
621
|
return {
|
|
582
622
|
messages: newMessages,
|
|
583
623
|
result: {
|
|
624
|
+
compacted: true,
|
|
584
625
|
originalCount,
|
|
585
626
|
newCount: newMessages.length,
|
|
586
627
|
tokensBeforeEstimate,
|