rename chat_compaction plugin to match convention
Nicolas Leão committed
Mar 26, 2026 at 11:01 UTC
9fc2d9570f995e156571b8f2414368926052951c
21 files changed
+14
-559
.vscode/launch.json
+1
-1
@@ -9,7 +9,7 @@
9
"program": "./run_ui.py",
10
"console": "integratedTerminal",
11
"justMyCode": false,
12
- "args": ["--development=true", "-Xfrozen_modules=off"]
12
+ "args": ["--development=true", "--port=5555", "-Xfrozen_modules=off"]
13
},
14
{
15
"name": "Debug current file",
plugins/_chat_compaction/.toggle-1
renamed
plugins/_chat_compaction/api/compact_chat.py
renamed
+8
-19
@@ -1,7 +1,10 @@
1
"""API handler for chat compaction."""
2
-import importlib
2
from helpers.api import ApiHandler, Input, Output, Request, Response
3
from agent import AgentContext
4
+from plugins._chat_compaction.helpers.compactor import (
5
+ get_compaction_stats,
6
+ run_compaction,
7
+)
8
9
10
class CompactChat(ApiHandler):
@@ -18,33 +21,23 @@ class CompactChat(ApiHandler):
21
if not context:
22
return Response("Context not found", 404)
23
21
- # Validate context is not running
24
if context.is_running():
25
return Response("Cannot compact while agent is running", 409)
26
25
- # Check if there's enough content to compact
26
- # Count user-visible log items (what the user sees in the UI)
27
visible_count = len(context.log.logs)
28
if visible_count <= 1:
29
return Response("Not enough messages to compact", 400)
30
31
- # Force reload compactor module to pick up latest changes
32
- import usr.plugins.compaction.helpers.compactor as _compactor_mod
33
- importlib.reload(_compactor_mod)
34
-
31
if action == "stats":
36
- # Return statistics for confirmation modal
37
- stats = await _compactor_mod.get_compaction_stats(context)
32
+ stats = await get_compaction_stats(context)
33
return {"ok": True, "stats": stats}
34
35
elif action == "compact":
41
- # Get plugin config for model choice
36
from helpers.plugins import get_plugin_config
37
agent = context.agent0
44
- plugin_config = get_plugin_config("compaction", agent=agent) or {}
38
+ plugin_config = get_plugin_config("_chat_compaction", agent=agent) or {}
39
use_chat_model = plugin_config.get("use_chat_model", True)
40
47
- # Start compaction as a deferred task
41
context.run_task(_run_compaction_task, context, use_chat_model)
42
43
return {"ok": True, "message": "Compaction started"}
@@ -56,16 +49,12 @@ class CompactChat(ApiHandler):
49
async def _run_compaction_task(context, use_chat_model: bool):
50
"""Wrapper to run compaction and handle errors."""
51
try:
59
- import importlib
60
- import usr.plugins.compaction.helpers.compactor as _compactor_mod
61
- importlib.reload(_compactor_mod)
62
- await _compactor_mod.run_compaction(context, use_chat_model)
52
+ await run_compaction(context, use_chat_model)
53
except Exception as e:
64
- # Log error but don't crash the task
54
context.log.log(
55
type="error",
56
heading="Compaction Failed",
57
content=str(e),
58
)
59
from helpers.state_monitor_integration import mark_dirty_all
71
- mark_dirty_all(reason="plugins.compaction.compact_chat_error")
60
+ mark_dirty_all(reason="plugins._chat_compaction.compact_chat_error")
plugins/_chat_compaction/default_config.yaml
renamed
plugins/_chat_compaction/extensions/webui/chat-input-bottom-actions-start/compact-button.html
renamed
+1
-1
@@ -1,7 +1,7 @@
1
<html>
2
<head>
3
<title>Compact Button Extension</title>
4
- <script type="module" src="/plugins/compaction/webui/compact-store.js"></script>
4
+ <script type="module" src="/plugins/_chat_compaction/webui/compact-store.js"></script>
5
</head>
6
<body>
7
<div x-data="{
plugins/_chat_compaction/helpers/compactor.py
renamed
plugins/_chat_compaction/plugin.yaml
renamed
+1
-1
@@ -1,4 +1,4 @@
1
-name: compaction
1
+name: _chat_compaction
2
title: Chat Compaction
3
description: Compact entire chat history into a single optimized summary message.
4
version: 1.0.0
plugins/_chat_compaction/prompts/compact.msg.md
renamed
plugins/_chat_compaction/prompts/compact.sys.md
renamed
plugins/_chat_compaction/webui/compact-modal.html
renamed
+1
-1
@@ -255,7 +255,7 @@
255
</style>
256
257
<script type="module">
258
- import { store } from "/plugins/compaction/webui/compact-store.js";
258
+ import { store } from "/plugins/_chat_compaction/webui/compact-store.js";
259
</script>
260
</body>
261
</html>
plugins/_chat_compaction/webui/compact-store.js
renamed
+2
-2
@@ -18,7 +18,7 @@ export const store = createStore("compactStore", {
18
return;
19
}
20
21
- const res = await callJsonApi("/plugins/compaction/compact_chat", {
21
+ const res = await callJsonApi("/plugins/_chat_compaction/compact_chat", {
22
context: ctxid,
23
action: "stats",
24
});
@@ -42,7 +42,7 @@ export const store = createStore("compactStore", {
42
throw new Error("No active chat");
43
}
44
45
- const res = await callJsonApi("/plugins/compaction/compact_chat", {
45
+ const res = await callJsonApi("/plugins/_chat_compaction/compact_chat", {
46
context: ctxid,
47
action: "compact",
48
});
plugins/_chat_compaction/webui/config.html
renamed
plugins/chat_compaction/compaction/.toggle-1
plugins/chat_compaction/compaction/api/compact_chat.py
deleted
-71
@@ -1,71 +0,0 @@
1
-"""API handler for chat compaction."""
2
-import importlib
3
-from helpers.api import ApiHandler, Input, Output, Request, Response
4
-from agent import AgentContext
5
-
6
-
7
-class CompactChat(ApiHandler):
8
- """Compact the current chat history into a summarized message."""
9
-
10
- async def process(self, input: Input, request: Request) -> Output:
11
- ctxid = input.get("context", "")
12
- action = input.get("action", "compact")
13
-
14
- if not ctxid:
15
- return Response("Missing context id", 400)
16
-
17
- context = AgentContext.get(ctxid)
18
- if not context:
19
- return Response("Context not found", 404)
20
-
21
- # Validate context is not running
22
- if context.is_running():
23
- return Response("Cannot compact while agent is running", 409)
24
-
25
- # Check if there's enough content to compact
26
- # Count user-visible log items (what the user sees in the UI)
27
- visible_count = len(context.log.logs)
28
- if visible_count <= 1:
29
- return Response("Not enough messages to compact", 400)
30
-
31
- # Force reload compactor module to pick up latest changes
32
- import usr.plugins.compaction.helpers.compactor as _compactor_mod
33
- importlib.reload(_compactor_mod)
34
-
35
- if action == "stats":
36
- # Return statistics for confirmation modal
37
- stats = await _compactor_mod.get_compaction_stats(context)
38
- return {"ok": True, "stats": stats}
39
-
40
- elif action == "compact":
41
- # Get plugin config for model choice
42
- from helpers.plugins import get_plugin_config
43
- agent = context.agent0
44
- plugin_config = get_plugin_config("compaction", agent=agent) or {}
45
- use_chat_model = plugin_config.get("use_chat_model", True)
46
-
47
- # Start compaction as a deferred task
48
- context.run_task(_run_compaction_task, context, use_chat_model)
49
-
50
- return {"ok": True, "message": "Compaction started"}
51
-
52
- else:
53
- return Response(f"Unknown action: {action}", 400)
54
-
55
-
56
-async def _run_compaction_task(context, use_chat_model: bool):
57
- """Wrapper to run compaction and handle errors."""
58
- try:
59
- import importlib
60
- import usr.plugins.compaction.helpers.compactor as _compactor_mod
61
- importlib.reload(_compactor_mod)
62
- await _compactor_mod.run_compaction(context, use_chat_model)
63
- except Exception as e:
64
- # Log error but don't crash the task
65
- context.log.log(
66
- type="error",
67
- heading="Compaction Failed",
68
- content=str(e),
69
- )
70
- from helpers.state_monitor_integration import mark_dirty_all
71
- mark_dirty_all(reason="plugins.compaction.compact_chat_error")
plugins/chat_compaction/compaction/plugin.yaml
deleted
-8
@@ -1,8 +0,0 @@
1
-name: compaction
2
-title: Chat Compaction
3
-description: Compact entire chat history into a single optimized summary message.
4
-version: 1.0.0
5
-settings_sections:
6
- - agent
7
-per_project_config: false
8
-per_agent_config: false
plugins/chat_compaction/default_config.yaml
deleted
-1
@@ -1 +0,0 @@
1
-use_chat_model: true
plugins/chat_compaction/prompts/compact.msg.md
deleted
-5
@@ -1,5 +0,0 @@
1
-Compact this conversation to its essential facts. Be maximally concise.
2
-
3
----
4
-{{conversation}}
5
----
plugins/chat_compaction/prompts/compact.sys.md
deleted
-17
@@ -1,17 +0,0 @@
1
-You are a conversation compactor. Produce the most concise summary possible while preserving critical information.
2
-
3
-Rules:
4
-- Extract only: key decisions, final outcomes, actionable facts, unresolved items
5
-- Discard: intermediate reasoning, failed attempts, redundant exchanges, pleasantries
6
-- Use terse bullet points, not prose
7
-- Collapse related items into single lines
8
-- Keep exact values: file paths, config values, code identifiers, credentials, URLs
9
-- Omit anything that can be re-derived from context
10
-- Group by topic, not chronology
11
-- No meta-commentary about the summarization
12
-- Target: 10-20% of original length
13
-
14
-Output format:
15
-- Markdown with short section headers
16
-- Bullet lists, no paragraphs
17
-- Code/paths in backticks inline, not fenced blocks unless multi-line
plugins/chat_compaction/webui/compact-modal.html
deleted
-261
@@ -1,261 +0,0 @@
1
-<html>
2
-<head>
3
- <title>Compact Chat</title>
4
-</head>
5
-<body>
6
- <div x-data x-show="$store.compactStore?.showModal" style="display: none;">
7
- <div class="modal-overlay" @click="$store.compactStore?.closeModal()">
8
- <div class="modal-container" @click.stop>
9
- <div class="modal-header">
10
- <h3>Compact Chat History</h3>
11
- <button class="modal-close" @click="$store.compactStore?.closeModal()">
12
- <span class="material-symbols-outlined">close</span>
13
- </button>
14
- </div>
15
-
16
- <div class="modal-content">
17
- <template x-if="$store.compactStore?.stats">
18
- <div class="stats-container">
19
- <p class="stats-description">
20
- This will summarize your entire conversation into a single optimized message.
21
- </p>
22
-
23
- <div class="stats-grid">
24
- <div class="stat-item">
25
- <span class="stat-label">Messages</span>
26
- <span class="stat-value" x-text="$store.compactStore?.stats?.message_count"></span>
27
- </div>
28
- <div class="stat-item">
29
- <span class="stat-label">Tokens</span>
30
- <span class="stat-value" x-text="$store.compactStore?.stats?.token_count?.toLocaleString()"></span>
31
- </div>
32
- <div class="stat-item">
33
- <span class="stat-label">Model</span>
34
- <span class="stat-value" x-text="$store.compactStore?.stats?.model_name"></span>
35
- </div>
36
- </div>
37
-
38
- <div class="stats-warning">
39
- <span class="material-symbols-outlined">warning</span>
40
- <span>This action cannot be undone. The original conversation will be replaced with a summary.</span>
41
- </div>
42
- </div>
43
- </template>
44
-
45
- <template x-if="!$store.compactStore?.stats">
46
- <div class="stats-loading">
47
- <span class="loading-spinner"></span>
48
- <span>Loading statistics...</span>
49
- </div>
50
- </template>
51
- </div>
52
-
53
- <div class="modal-footer">
54
- <button class="button secondary" @click="$store.compactStore?.closeModal()">
55
- Cancel
56
- </button>
57
- <button
58
- class="button primary danger"
59
- @click="$store.compactStore?.compact()"
60
- :disabled="$store.compactStore?.compacting || !$store.compactStore?.stats"
61
- >
62
- <template x-if="$store.compactStore?.compacting">
63
- <span class="loading-spinner"></span>
64
- </template>
65
- <span>Compact</span>
66
- </button>
67
- </div>
68
- </div>
69
- </div>
70
- </div>
71
-
72
- <style>
73
- .modal-overlay {
74
- position: fixed;
75
- top: 0;
76
- left: 0;
77
- right: 0;
78
- bottom: 0;
79
- background: rgba(0, 0, 0, 0.5);
80
- display: flex;
81
- align-items: center;
82
- justify-content: center;
83
- z-index: 1000;
84
- }
85
-
86
- .modal-container {
87
- background: var(--color-background-elevated);
88
- border-radius: var(--border-radius-md);
89
- box-shadow: var(--shadow-lg);
90
- width: 90%;
91
- max-width: 500px;
92
- max-height: 90vh;
93
- overflow-y: auto;
94
- }
95
-
96
- .modal-header {
97
- display: flex;
98
- align-items: center;
99
- justify-content: space-between;
100
- padding: var(--spacing-md) var(--spacing-lg);
101
- border-bottom: 1px solid var(--color-border);
102
- }
103
-
104
- .modal-header h3 {
105
- margin: 0;
106
- font-size: 1.1rem;
107
- font-weight: 600;
108
- }
109
-
110
- .modal-close {
111
- background: transparent;
112
- border: none;
113
- cursor: pointer;
114
- padding: var(--spacing-xs);
115
- border-radius: var(--border-radius-sm);
116
- color: var(--color-text-secondary);
117
- transition: color 0.2s;
118
- }
119
-
120
- .modal-close:hover {
121
- color: var(--color-text);
122
- }
123
-
124
- .modal-content {
125
- padding: var(--spacing-lg);
126
- }
127
-
128
- .stats-description {
129
- margin: 0 0 var(--spacing-md) 0;
130
- color: var(--color-text-secondary);
131
- font-size: 0.9rem;
132
- }
133
-
134
- .stats-grid {
135
- display: grid;
136
- grid-template-columns: repeat(3, 1fr);
137
- gap: var(--spacing-md);
138
- margin-bottom: var(--spacing-lg);
139
- }
140
-
141
- .stat-item {
142
- display: flex;
143
- flex-direction: column;
144
- align-items: center;
145
- padding: var(--spacing-md);
146
- background: var(--color-background-muted);
147
- border-radius: var(--border-radius-md);
148
- }
149
-
150
- .stat-label {
151
- font-size: 0.75rem;
152
- color: var(--color-text-secondary);
153
- text-transform: uppercase;
154
- letter-spacing: 0.05em;
155
- margin-bottom: var(--spacing-xs);
156
- }
157
-
158
- .stat-value {
159
- font-size: 1.25rem;
160
- font-weight: 600;
161
- color: var(--color-text);
162
- }
163
-
164
- .stats-warning {
165
- display: flex;
166
- align-items: flex-start;
167
- gap: var(--spacing-sm);
168
- padding: var(--spacing-md);
169
- background: var(--color-warning-bg, rgba(245, 158, 11, 0.1));
170
- border: 1px solid var(--color-warning-border, rgba(245, 158, 11, 0.3));
171
- border-radius: var(--border-radius-md);
172
- color: var(--color-warning-text, #92400e);
173
- font-size: 0.85rem;
174
- }
175
-
176
- .stats-warning .material-symbols-outlined {
177
- font-size: 1.2rem;
178
- flex-shrink: 0;
179
- }
180
-
181
- .stats-loading {
182
- display: flex;
183
- align-items: center;
184
- justify-content: center;
185
- gap: var(--spacing-sm);
186
- padding: var(--spacing-xl);
187
- color: var(--color-text-secondary);
188
- }
189
-
190
- .modal-footer {
191
- display: flex;
192
- justify-content: flex-end;
193
- gap: var(--spacing-sm);
194
- padding: var(--spacing-md) var(--spacing-lg);
195
- border-top: 1px solid var(--color-border);
196
- }
197
-
198
- .button {
199
- padding: var(--spacing-sm) var(--spacing-md);
200
- border-radius: var(--border-radius-md);
201
- font-size: 0.9rem;
202
- font-weight: 500;
203
- cursor: pointer;
204
- transition: all 0.2s;
205
- display: flex;
206
- align-items: center;
207
- gap: var(--spacing-xs);
208
- }
209
-
210
- .button:disabled {
211
- opacity: 0.6;
212
- cursor: not-allowed;
213
- }
214
-
215
- .button.secondary {
216
- background: var(--color-background-muted);
217
- border: 1px solid var(--color-border);
218
- color: var(--color-text);
219
- }
220
-
221
- .button.secondary:hover:not(:disabled) {
222
- background: var(--color-background-hover);
223
- }
224
-
225
- .button.primary {
226
- background: var(--color-primary);
227
- border: none;
228
- color: white;
229
- }
230
-
231
- .button.primary:hover:not(:disabled) {
232
- background: var(--color-primary-hover);
233
- }
234
-
235
- .button.danger {
236
- background: var(--color-danger, #dc2626);
237
- }
238
-
239
- .button.danger:hover:not(:disabled) {
240
- background: var(--color-danger-hover, #b91c1c);
241
- }
242
-
243
- .loading-spinner {
244
- width: 16px;
245
- height: 16px;
246
- border: 2px solid currentColor;
247
- border-top-color: transparent;
248
- border-radius: 50%;
249
- animation: spin 0.8s linear infinite;
250
- }
251
-
252
- @keyframes spin {
253
- to { transform: rotate(360deg); }
254
- }
255
- </style>
256
-
257
- <script type="module">
258
- import { store } from "/plugins/compaction/webui/compact-store.js";
259
- </script>
260
-</body>
261
-</html>
plugins/chat_compaction/webui/compact-store.js
deleted
-67
@@ -1,67 +0,0 @@
1
-import { createStore } from "/js/AlpineStore.js";
2
-import { callJsonApi } from "/js/api.js";
3
-import {
4
- toastFrontendSuccess,
5
- toastFrontendError,
6
-} from "/components/notifications/notification-store.js";
7
-
8
-export const store = createStore("compactStore", {
9
- compacting: false,
10
- stats: null,
11
- showModal: false,
12
-
13
- async fetchStats() {
14
- try {
15
- const ctxid = globalThis.getContext?.();
16
- if (!ctxid) {
17
- toastFrontendError("No active chat", "Compaction");
18
- return;
19
- }
20
-
21
- const res = await callJsonApi("/plugins/compaction/compact_chat", {
22
- context: ctxid,
23
- action: "stats",
24
- });
25
-
26
- if (!res?.ok) {
27
- throw new Error(res?.message || "Failed to fetch stats");
28
- }
29
-
30
- this.stats = res.stats;
31
- this.showModal = true;
32
- } catch (e) {
33
- toastFrontendError(e.message, "Compaction");
34
- }
35
- },
36
-
37
- async compact() {
38
- this.compacting = true;
39
- try {
40
- const ctxid = globalThis.getContext?.();
41
- if (!ctxid) {
42
- throw new Error("No active chat");
43
- }
44
-
45
- const res = await callJsonApi("/plugins/compaction/compact_chat", {
46
- context: ctxid,
47
- action: "compact",
48
- });
49
-
50
- if (!res?.ok) {
51
- throw new Error(res?.message || "Compaction failed");
52
- }
53
-
54
- toastFrontendSuccess("Compaction started", "Compaction");
55
- this.showModal = false;
56
- } catch (e) {
57
- toastFrontendError(e.message, "Compaction");
58
- } finally {
59
- this.compacting = false;
60
- }
61
- },
62
-
63
- closeModal() {
64
- this.showModal = false;
65
- this.stats = null;
66
- },
67
-});
plugins/chat_compaction/webui/config.html
deleted
-104
@@ -1,104 +0,0 @@
1
-<html>
2
-<head>
3
- <title>Compaction Plugin Settings</title>
4
-</head>
5
-<body>
6
- <div x-data>
7
- <template x-if="config">
8
- <div>
9
- <div class="section-title">Compaction Configuration</div>
10
- <div class="section-description">
11
- Configure how the chat compaction feature works.
12
- </div>
13
-
14
- <div class="field">
15
- <div class="field-label">
16
- <div class="field-title">Use chat model</div>
17
- <div class="field-description">
18
- When enabled, uses the currently selected chat model for compaction.
19
- When disabled, uses the utility model (usually faster and cheaper).
20
- </div>
21
- </div>
22
- <div class="field-control">
23
- <label class="toggle">
24
- <input type="checkbox" x-model="config.use_chat_model" />
25
- <span class="toggler"></span>
26
- </label>
27
- </div>
28
- </div>
29
- </div>
30
- </template>
31
- </div>
32
-
33
- <style>
34
- .section-title {
35
- font-size: 1rem;
36
- font-weight: 600;
37
- margin-bottom: 8px;
38
- color: var(--color-text, #e5e5e5);
39
- }
40
- .section-description {
41
- font-size: 0.85rem;
42
- color: var(--color-text-secondary, #999);
43
- margin-bottom: 20px;
44
- line-height: 1.5;
45
- }
46
- .field {
47
- margin-bottom: 20px;
48
- }
49
- .field-label {
50
- margin-bottom: 8px;
51
- }
52
- .field-title {
53
- font-weight: 500;
54
- color: var(--color-text, #e5e5e5);
55
- margin-bottom: 4px;
56
- }
57
- .field-description {
58
- font-size: 0.8rem;
59
- color: var(--color-text-secondary, #999);
60
- line-height: 1.4;
61
- }
62
- .field-control {
63
- margin-top: 8px;
64
- }
65
- .toggle {
66
- display: inline-flex;
67
- align-items: center;
68
- cursor: pointer;
69
- }
70
- .toggle input {
71
- display: none;
72
- }
73
- .toggler {
74
- width: 44px;
75
- height: 24px;
76
- background: var(--color-border, #444);
77
- border-radius: 12px;
78
- position: relative;
79
- transition: background 0.2s;
80
- }
81
- .toggler::after {
82
- content: '';
83
- position: absolute;
84
- width: 20px;
85
- height: 20px;
86
- background: white;
87
- border-radius: 50%;
88
- top: 2px;
89
- left: 2px;
90
- transition: transform 0.2s;
91
- }
92
- .toggle input:checked + .toggler {
93
- background: var(--color-primary, #3b82f6);
94
- }
95
- .toggle input:checked + .toggler::after {
96
- transform: translateX(20px);
97
- }
98
- </style>
99
-
100
- <script type="module">
101
- import { store } from "/components/plugins/plugin-settings-store.js";
102
- </script>
103
-</body>
104
-</html>