dscli-agent 0.5.0__tar.gz → 0.6.0__tar.gz

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.
Files changed (40) hide show
  1. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/PKG-INFO +52 -21
  2. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/README.md +51 -20
  3. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/__init__.py +1 -1
  4. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/agent.py +109 -35
  5. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/cli.py +69 -6
  6. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/compaction.py +6 -0
  7. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/config.py +1 -2
  8. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/display.py +10 -3
  9. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/prompts.py +30 -12
  10. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/protocol.py +6 -0
  11. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/tools.py +111 -18
  12. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/tui.py +323 -71
  13. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/ui.py +0 -17
  14. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli_agent.egg-info/PKG-INFO +52 -21
  15. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/pyproject.toml +3 -0
  16. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/tests/test_agent.py +103 -16
  17. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/tests/test_cli.py +88 -0
  18. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/tests/test_compaction.py +26 -1
  19. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/tests/test_display.py +2 -2
  20. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/tests/test_protocol.py +8 -0
  21. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/tests/test_tools.py +64 -0
  22. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/tests/test_tui.py +150 -14
  23. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/LICENSE +0 -0
  24. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/__main__.py +0 -0
  25. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/attachments.py +0 -0
  26. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/branding.py +0 -0
  27. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/browser.py +0 -0
  28. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/providers.py +0 -0
  29. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/scraper.py +0 -0
  30. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli/tokens.py +0 -0
  31. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli_agent.egg-info/SOURCES.txt +0 -0
  32. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli_agent.egg-info/dependency_links.txt +0 -0
  33. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli_agent.egg-info/entry_points.txt +0 -0
  34. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli_agent.egg-info/requires.txt +0 -0
  35. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/dscli_agent.egg-info/top_level.txt +0 -0
  36. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/setup.cfg +0 -0
  37. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/tests/test_branding.py +0 -0
  38. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/tests/test_browser.py +0 -0
  39. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/tests/test_config.py +0 -0
  40. {dscli_agent-0.5.0 → dscli_agent-0.6.0}/tests/test_tokens.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dscli-agent
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: A local coding agent that uses DeepSeek web chat as its model
5
5
  Author: Anish
6
6
  License-Expression: MIT
@@ -106,11 +106,33 @@ dscli
106
106
 
107
107
  dscli shows an inline terminal UI inspired by Claude Code: a cleared screen and gradient
108
108
  ASCII wordmark on launch, an animated spinner with rotating status verbs, a smoothly
109
- revealed streaming answer, compact tool cards (`⏺ Read(...)`, `⏺ Update(...)`,
110
- `⏺ Run(...)`), colored diffs for approvals, and the final answer rendered as Markdown.
109
+ revealed streaming answer, tree-style tool cards, approval prompts that name the action
110
+ and path without printing file contents, and the final answer rendered as Markdown.
111
+
112
+ Each tool call renders as a card with a short result summary and, when there is more to
113
+ see, a preview of the output:
114
+
115
+ ```text
116
+ ▪ Read(dscli/config.py · lines 1-40)
117
+ └ Read lines 1-40 of 120 (Ctrl+O to expand)
118
+
119
+ ▪ Edit(dscli/config.py)
120
+ └ Accepted config.py (Ctrl+O to expand)
121
+
122
+ ▪ Bash(grep -n "read_file" dscli/prompts.py)
123
+ └ 121:- Do NOT use read_file, write_file, apply_patch, list_files...
124
+ 243: non-empty plan. Start by investigating with read_file and list_files
125
+ … +2 lines (Ctrl+O to expand)
126
+ ```
127
+
128
+ Failed calls keep the `▪` header and show the error under the `└` branch. Long paths are
129
+ shortened in the middle and long commands are trimmed to keep the card on one line.
130
+ Press `Ctrl+O` at the prompt to print the full output of the most recent tool card or
131
+ thinking block.
111
132
  Accents use a muted palette that matches the wordmark: sage for success, dusty rose for
112
- errors, and soft teal for diffs and highlights. The same muted colors apply to the
113
- plain and non-TTY output.
133
+ errors, and soft teal for highlights. Markdown answers — streamed, interrupted, and
134
+ final — use the same palette for headings, links, inline code, and fenced code
135
+ highlighting. The same muted colors apply to the plain and non-TTY output.
114
136
  Output stays in normal scrollback after startup; dscli does not take over the full
115
137
  screen, and the welcome card shows the workspace, model, active modes, and shortcuts.
116
138
  Plans
@@ -132,12 +154,12 @@ DeepThink is always enabled, and the UI streams the model's reasoning live in a
132
154
  compact block above the reply:
133
155
 
134
156
  ```text
135
- Thinking
136
- │ I should inspect the configuration first.
137
- │ … +12 rows (Ctrl+O)
138
- │ charlie line
139
- │ delta line
140
- │ echo line
157
+ Thinking
158
+ │ I should inspect the configuration first.
159
+ │ … +12 rows (Ctrl+O to expand)
160
+ │ charlie line
161
+ │ delta line
162
+ │ echo line
141
163
  ```
142
164
 
143
165
  The first sentence stays pinned at the top while the newest three wrapped rows
@@ -182,6 +204,11 @@ runs before every model turn, so it also works mid-task; the TUI shows a
182
204
  `⟳ Compacted context at 1.0M tokens → … · new chat` notice. `/new` clears the recorded
183
205
  conversation as well.
184
206
 
207
+ Type `/compact` in `dscli chat` to compact on demand. dscli asks the current chat for the
208
+ summary, writes the compaction file, and starts a new DeepSeek chat; the next message you
209
+ send resumes the compacted conversation there, with the compaction attached. `/compact`
210
+ needs at least one earlier message, and `/new` discards a pending compaction.
211
+
185
212
  ### Deeplanning mode
186
213
 
187
214
  ```powershell
@@ -208,14 +235,15 @@ every run with `dscli config --set deeplanning=true`, or turn it off for one run
208
235
  | `Ctrl+C` | While the agent is working: interrupt the current turn, keep the browser session, and return to the prompt. At the prompt: clear the input. |
209
236
  | `Ctrl+D` | Exit dscli. |
210
237
  | `↑` / `↓` | Browse previous messages. |
211
- | `Ctrl+O` | Expand the last thinking block in full. |
238
+ | `Ctrl+O` | Expand the last thinking block or tool output in full. |
212
239
 
213
240
  Typing `/` opens an autocomplete menu. History is saved to
214
241
  `%APPDATA%\dscli\chat_history` (`~/Library/Application Support/dscli/chat_history` on
215
242
  macOS, `~/.config/dscli/chat_history` elsewhere).
216
243
 
217
- Slash commands: `/help` lists commands and shortcuts, `/new` starts a fresh
218
- conversation so the agent forgets earlier turns, and `/exit` (or `/quit`) closes dscli.
244
+ Slash commands: `/help` lists commands and shortcuts, `/compact` summarizes the
245
+ conversation and continues it in a new chat, `/new` starts a fresh conversation so the
246
+ agent forgets earlier turns, and `/exit` (or `/quit`) closes dscli.
219
247
 
220
248
  If Cloudflare interrupts a headless run, dscli opens the persistent profile in a visible
221
249
  Chrome window and resubmits the interrupted prompt. Complete the check in that window.
@@ -225,8 +253,8 @@ current model turn restarts with the same prompt in a fresh chat, so an answer t
225
253
  already streaming when the check appeared is fetched again. Set
226
254
  `dscli config --set auto_human_check=false` to fail fast instead.
227
255
 
228
- When dscli asks to write a file or run a command, it shows the title and a compact
229
- colored diff (additions green, deletions red, hunks cyan) even when `-y` is active.
256
+ When dscli asks to write a file or run a command, it shows the title and, for commands,
257
+ the exact command. File contents and diffs are never printed.
230
258
  Answer `y` to approve once, `n` to deny, `a` to approve everything for the rest of the
231
259
  session, or `q` to quit.
232
260
 
@@ -269,7 +297,7 @@ dscli run --prompt-file task.md -y
269
297
  dscli run "Review the docs" --no-shell
270
298
  ```
271
299
 
272
- The final answer is printed to stdout. Progress, diffs, and approval prompts go to
300
+ The final answer is printed to stdout. Progress and approval prompts go to
273
301
  stderr, so `dscli run ... > result.txt` captures only the answer. `run` never uses the
274
302
  interactive UI; its output contract is unchanged.
275
303
 
@@ -289,7 +317,7 @@ failures and unconfirmed completions are always logged to stderr, including unde
289
317
  | `--headed` / `--headless` | Show or hide the browser. Headless is the default. |
290
318
  | `--search` | Turn DeepSeek's Search mode on for the run (DeepThink is always on). |
291
319
  | `--deeplanning` | Require a non-empty `update_plan` before writes or commands (alias: `--deep-planning`). |
292
- | `--max-steps N` | Maximum model turns (default 12). |
320
+ | `--max-steps N` | Maximum model turns (default 20, cap 150). |
293
321
  | `--timeout N` | Response timeout per turn in seconds (default 300). |
294
322
  | `--verify CMD` | Run CMD automatically after file edits; failures block success. |
295
323
  | `--no-verify` | Disable the configured `verify_command` for this run. |
@@ -357,6 +385,7 @@ creep and unnecessary abstractions, confirm hard-to-reverse actions, reference c
357
385
  | `write_file` | **Create** a new file. Refuses to overwrite an existing non-empty file. |
358
386
  | `apply_patch` | The only way to edit an existing file: exact `search` → `replace`. |
359
387
  | `list_files` | Bounded recursive listing. |
388
+ | `search_files` | Search text files for a literal string; returns `path:line` matches. |
360
389
  | `delete_file` | Delete one file when explicitly requested (approval required). |
361
390
  | `run_command` | Run a command with an approval prompt via PowerShell, cmd.exe, sh, or bash. |
362
391
  | `update_plan` | Create or revise the task plan; the task cannot finish while steps are unfinished. |
@@ -404,8 +433,9 @@ model can modify the files it checks, so review the final diff.
404
433
 
405
434
  - Tools are confined to the workspace unless `--allow-outside` is passed.
406
435
  - `.git` is never writable through the tools.
407
- - Every write, patch, delete, and command shows a diff/command and asks
408
- `[y]es / [n]o / [a]ll / [q]uit` before changing anything. `-y` skips the prompts.
436
+ - Every write, patch, delete, and command shows the action and affected path (or the
437
+ exact command) and asks `[y]es / [n]o / [a]ll / [q]uit` before changing anything.
438
+ File contents and diffs are never printed. `-y` skips the prompts.
409
439
  - Command execution is enabled by default, but every model-requested command still
410
440
  requires approval. The prompt names the interpreter (PowerShell, cmd.exe, sh, or
411
441
  bash). `--no-shell` disables model-requested commands for a run.
@@ -420,7 +450,8 @@ model can modify the files it checks, so review the final diff.
420
450
  through the browser.
421
451
 
422
452
  This is still an assistant with write access to your files. Use a clean workspace or
423
- version control, and review diffs before approving them.
453
+ version control, and review the changes in your VCS after a run; approval prompts show
454
+ only the action and path, not the file contents.
424
455
 
425
456
  ## Configuration
426
457
 
@@ -76,11 +76,33 @@ dscli
76
76
 
77
77
  dscli shows an inline terminal UI inspired by Claude Code: a cleared screen and gradient
78
78
  ASCII wordmark on launch, an animated spinner with rotating status verbs, a smoothly
79
- revealed streaming answer, compact tool cards (`⏺ Read(...)`, `⏺ Update(...)`,
80
- `⏺ Run(...)`), colored diffs for approvals, and the final answer rendered as Markdown.
79
+ revealed streaming answer, tree-style tool cards, approval prompts that name the action
80
+ and path without printing file contents, and the final answer rendered as Markdown.
81
+
82
+ Each tool call renders as a card with a short result summary and, when there is more to
83
+ see, a preview of the output:
84
+
85
+ ```text
86
+ ▪ Read(dscli/config.py · lines 1-40)
87
+ └ Read lines 1-40 of 120 (Ctrl+O to expand)
88
+
89
+ ▪ Edit(dscli/config.py)
90
+ └ Accepted config.py (Ctrl+O to expand)
91
+
92
+ ▪ Bash(grep -n "read_file" dscli/prompts.py)
93
+ └ 121:- Do NOT use read_file, write_file, apply_patch, list_files...
94
+ 243: non-empty plan. Start by investigating with read_file and list_files
95
+ … +2 lines (Ctrl+O to expand)
96
+ ```
97
+
98
+ Failed calls keep the `▪` header and show the error under the `└` branch. Long paths are
99
+ shortened in the middle and long commands are trimmed to keep the card on one line.
100
+ Press `Ctrl+O` at the prompt to print the full output of the most recent tool card or
101
+ thinking block.
81
102
  Accents use a muted palette that matches the wordmark: sage for success, dusty rose for
82
- errors, and soft teal for diffs and highlights. The same muted colors apply to the
83
- plain and non-TTY output.
103
+ errors, and soft teal for highlights. Markdown answers — streamed, interrupted, and
104
+ final — use the same palette for headings, links, inline code, and fenced code
105
+ highlighting. The same muted colors apply to the plain and non-TTY output.
84
106
  Output stays in normal scrollback after startup; dscli does not take over the full
85
107
  screen, and the welcome card shows the workspace, model, active modes, and shortcuts.
86
108
  Plans
@@ -102,12 +124,12 @@ DeepThink is always enabled, and the UI streams the model's reasoning live in a
102
124
  compact block above the reply:
103
125
 
104
126
  ```text
105
- Thinking
106
- │ I should inspect the configuration first.
107
- │ … +12 rows (Ctrl+O)
108
- │ charlie line
109
- │ delta line
110
- │ echo line
127
+ Thinking
128
+ │ I should inspect the configuration first.
129
+ │ … +12 rows (Ctrl+O to expand)
130
+ │ charlie line
131
+ │ delta line
132
+ │ echo line
111
133
  ```
112
134
 
113
135
  The first sentence stays pinned at the top while the newest three wrapped rows
@@ -152,6 +174,11 @@ runs before every model turn, so it also works mid-task; the TUI shows a
152
174
  `⟳ Compacted context at 1.0M tokens → … · new chat` notice. `/new` clears the recorded
153
175
  conversation as well.
154
176
 
177
+ Type `/compact` in `dscli chat` to compact on demand. dscli asks the current chat for the
178
+ summary, writes the compaction file, and starts a new DeepSeek chat; the next message you
179
+ send resumes the compacted conversation there, with the compaction attached. `/compact`
180
+ needs at least one earlier message, and `/new` discards a pending compaction.
181
+
155
182
  ### Deeplanning mode
156
183
 
157
184
  ```powershell
@@ -178,14 +205,15 @@ every run with `dscli config --set deeplanning=true`, or turn it off for one run
178
205
  | `Ctrl+C` | While the agent is working: interrupt the current turn, keep the browser session, and return to the prompt. At the prompt: clear the input. |
179
206
  | `Ctrl+D` | Exit dscli. |
180
207
  | `↑` / `↓` | Browse previous messages. |
181
- | `Ctrl+O` | Expand the last thinking block in full. |
208
+ | `Ctrl+O` | Expand the last thinking block or tool output in full. |
182
209
 
183
210
  Typing `/` opens an autocomplete menu. History is saved to
184
211
  `%APPDATA%\dscli\chat_history` (`~/Library/Application Support/dscli/chat_history` on
185
212
  macOS, `~/.config/dscli/chat_history` elsewhere).
186
213
 
187
- Slash commands: `/help` lists commands and shortcuts, `/new` starts a fresh
188
- conversation so the agent forgets earlier turns, and `/exit` (or `/quit`) closes dscli.
214
+ Slash commands: `/help` lists commands and shortcuts, `/compact` summarizes the
215
+ conversation and continues it in a new chat, `/new` starts a fresh conversation so the
216
+ agent forgets earlier turns, and `/exit` (or `/quit`) closes dscli.
189
217
 
190
218
  If Cloudflare interrupts a headless run, dscli opens the persistent profile in a visible
191
219
  Chrome window and resubmits the interrupted prompt. Complete the check in that window.
@@ -195,8 +223,8 @@ current model turn restarts with the same prompt in a fresh chat, so an answer t
195
223
  already streaming when the check appeared is fetched again. Set
196
224
  `dscli config --set auto_human_check=false` to fail fast instead.
197
225
 
198
- When dscli asks to write a file or run a command, it shows the title and a compact
199
- colored diff (additions green, deletions red, hunks cyan) even when `-y` is active.
226
+ When dscli asks to write a file or run a command, it shows the title and, for commands,
227
+ the exact command. File contents and diffs are never printed.
200
228
  Answer `y` to approve once, `n` to deny, `a` to approve everything for the rest of the
201
229
  session, or `q` to quit.
202
230
 
@@ -239,7 +267,7 @@ dscli run --prompt-file task.md -y
239
267
  dscli run "Review the docs" --no-shell
240
268
  ```
241
269
 
242
- The final answer is printed to stdout. Progress, diffs, and approval prompts go to
270
+ The final answer is printed to stdout. Progress and approval prompts go to
243
271
  stderr, so `dscli run ... > result.txt` captures only the answer. `run` never uses the
244
272
  interactive UI; its output contract is unchanged.
245
273
 
@@ -259,7 +287,7 @@ failures and unconfirmed completions are always logged to stderr, including unde
259
287
  | `--headed` / `--headless` | Show or hide the browser. Headless is the default. |
260
288
  | `--search` | Turn DeepSeek's Search mode on for the run (DeepThink is always on). |
261
289
  | `--deeplanning` | Require a non-empty `update_plan` before writes or commands (alias: `--deep-planning`). |
262
- | `--max-steps N` | Maximum model turns (default 12). |
290
+ | `--max-steps N` | Maximum model turns (default 20, cap 150). |
263
291
  | `--timeout N` | Response timeout per turn in seconds (default 300). |
264
292
  | `--verify CMD` | Run CMD automatically after file edits; failures block success. |
265
293
  | `--no-verify` | Disable the configured `verify_command` for this run. |
@@ -327,6 +355,7 @@ creep and unnecessary abstractions, confirm hard-to-reverse actions, reference c
327
355
  | `write_file` | **Create** a new file. Refuses to overwrite an existing non-empty file. |
328
356
  | `apply_patch` | The only way to edit an existing file: exact `search` → `replace`. |
329
357
  | `list_files` | Bounded recursive listing. |
358
+ | `search_files` | Search text files for a literal string; returns `path:line` matches. |
330
359
  | `delete_file` | Delete one file when explicitly requested (approval required). |
331
360
  | `run_command` | Run a command with an approval prompt via PowerShell, cmd.exe, sh, or bash. |
332
361
  | `update_plan` | Create or revise the task plan; the task cannot finish while steps are unfinished. |
@@ -374,8 +403,9 @@ model can modify the files it checks, so review the final diff.
374
403
 
375
404
  - Tools are confined to the workspace unless `--allow-outside` is passed.
376
405
  - `.git` is never writable through the tools.
377
- - Every write, patch, delete, and command shows a diff/command and asks
378
- `[y]es / [n]o / [a]ll / [q]uit` before changing anything. `-y` skips the prompts.
406
+ - Every write, patch, delete, and command shows the action and affected path (or the
407
+ exact command) and asks `[y]es / [n]o / [a]ll / [q]uit` before changing anything.
408
+ File contents and diffs are never printed. `-y` skips the prompts.
379
409
  - Command execution is enabled by default, but every model-requested command still
380
410
  requires approval. The prompt names the interpreter (PowerShell, cmd.exe, sh, or
381
411
  bash). `--no-shell` disables model-requested commands for a run.
@@ -390,7 +420,8 @@ model can modify the files it checks, so review the final diff.
390
420
  through the browser.
391
421
 
392
422
  This is still an assistant with write access to your files. Use a clean workspace or
393
- version control, and review diffs before approving them.
423
+ version control, and review the changes in your VCS after a run; approval prompts show
424
+ only the action and path, not the file contents.
394
425
 
395
426
  ## Configuration
396
427
 
@@ -1,3 +1,3 @@
1
1
  """dscli - a local coding agent for your terminal."""
2
2
 
3
- __version__ = "0.5.0"
3
+ __version__ = "0.6.0"
@@ -10,7 +10,12 @@ from typing import Callable
10
10
 
11
11
  from .attachments import Attachment
12
12
  from .browser import ChatSession
13
- from .compaction import ConversationLog, extract_compact_summary, write_compaction
13
+ from .compaction import (
14
+ Compaction,
15
+ ConversationLog,
16
+ extract_compact_summary,
17
+ write_compaction,
18
+ )
14
19
  from .config import Config
15
20
  from .display import AgentDisplay, PlainDisplay
16
21
  from .prompts import (
@@ -59,6 +64,73 @@ class AgentOutcome:
59
64
  completed: bool = False
60
65
 
61
66
 
67
+ def request_compaction_summary(
68
+ *,
69
+ session: ChatSession,
70
+ display: AgentDisplay,
71
+ task: str,
72
+ plan: list[PlanStep] | None,
73
+ changed: list[str],
74
+ manual: bool = False,
75
+ ) -> str:
76
+ display.compaction_summarizing()
77
+ prompt = build_compact_prompt(
78
+ task=task,
79
+ plan=plan,
80
+ changed=changed,
81
+ manual=manual,
82
+ )
83
+ try:
84
+ reply = session.send_and_wait(prompt)
85
+ except Exception as exc:
86
+ log.warning("Could not request a compaction summary: %s", exc)
87
+ return ""
88
+ summary = extract_compact_summary(reply)
89
+ if not summary:
90
+ log.warning(
91
+ "The model's compaction summary was empty or unreadable; "
92
+ "using the activity log only."
93
+ )
94
+ return summary
95
+
96
+
97
+ def compact_conversation(
98
+ *,
99
+ session: ChatSession,
100
+ toolbox: Toolbox,
101
+ workspace: Path,
102
+ task: str,
103
+ history: ConversationLog,
104
+ display: AgentDisplay,
105
+ verification: str = "",
106
+ ) -> Compaction:
107
+ """Summarize the chat, write the compaction file, and open a new chat."""
108
+ summary = request_compaction_summary(
109
+ session=session,
110
+ display=display,
111
+ task=task,
112
+ plan=toolbox.plan,
113
+ changed=toolbox.changed_files,
114
+ manual=True,
115
+ )
116
+ used = int(getattr(session, "context_tokens", 0) or 0)
117
+ compaction = write_compaction(
118
+ workspace=workspace,
119
+ task=task,
120
+ log=history,
121
+ plan=toolbox.plan,
122
+ changed=toolbox.changed_files,
123
+ verification=verification,
124
+ context_tokens=used,
125
+ project_tree=project_structure(workspace),
126
+ summary=summary,
127
+ )
128
+ session.new_chat()
129
+ session.set_modes()
130
+ display.compaction_started(str(compaction.path), used)
131
+ return compaction
132
+
133
+
62
134
  class Agent:
63
135
  def __init__(
64
136
  self,
@@ -76,6 +148,7 @@ class Agent:
76
148
  on_thinking_snapshot: Callable[[str], None] | None = None,
77
149
  display: AgentDisplay | None = None,
78
150
  history: ConversationLog | None = None,
151
+ resume: Compaction | None = None,
79
152
  ) -> None:
80
153
  self.session = session
81
154
  self.cfg = config
@@ -85,6 +158,7 @@ class Agent:
85
158
  self.attachments = attachments
86
159
  self.verbose = verbose
87
160
  self.followup = followup
161
+ self.resume = resume
88
162
  self.on_final_snapshot = on_final_snapshot
89
163
  self.on_thinking_snapshot = on_thinking_snapshot
90
164
  self.display = display or PlainDisplay(
@@ -120,24 +194,13 @@ class Agent:
120
194
  return self._verify_command or "(none configured)"
121
195
 
122
196
  def _request_compaction_summary(self, plan: list[PlanStep] | None) -> str:
123
- self.display.compaction_summarizing()
124
- prompt = build_compact_prompt(
197
+ return request_compaction_summary(
198
+ session=self.session,
199
+ display=self.display,
125
200
  task=self.task,
126
201
  plan=plan,
127
202
  changed=self.toolbox.changed_files,
128
203
  )
129
- try:
130
- reply = self.session.send_and_wait(prompt)
131
- except Exception as exc:
132
- log.warning("Could not request a compaction summary: %s", exc)
133
- return ""
134
- summary = extract_compact_summary(reply)
135
- if not summary:
136
- log.warning(
137
- "The model's compaction summary was empty or unreadable; "
138
- "using the activity log only."
139
- )
140
- return summary
141
204
 
142
205
  def _prepare_send(
143
206
  self,
@@ -285,7 +348,30 @@ class Agent:
285
348
  self._sent_any = False
286
349
  self.history.add_task(self.task)
287
350
 
288
- if self.followup:
351
+ if self.resume is not None:
352
+ self.history.add_note(
353
+ f"Resumed from {self.resume.path.name} in a new chat."
354
+ )
355
+ initial = build_resume_prompt(
356
+ task=self.task,
357
+ workspace=self.workspace,
358
+ compaction=self.resume.text,
359
+ workspace_tree=workspace_preview(self.workspace),
360
+ plan=self.toolbox.plan,
361
+ live_plan=self.toolbox.plan,
362
+ changed=self.toolbox.changed_files,
363
+ attachments=self.attachments,
364
+ allow_shell=self.cfg.shell_enabled,
365
+ allow_outside=self.cfg.allow_outside,
366
+ max_steps=self.cfg.max_steps,
367
+ verify_command=self._verify_command,
368
+ compaction_name=self.resume.path.name,
369
+ deeplanning=self.cfg.deeplanning,
370
+ max_tool_calls=self.cfg.max_tool_calls,
371
+ turn_id=self.turn_id,
372
+ manual=True,
373
+ )
374
+ elif self.followup:
289
375
  initial = build_followup_prompt(
290
376
  self.task,
291
377
  turn_id=self.turn_id,
@@ -314,10 +400,11 @@ class Agent:
314
400
  )
315
401
 
316
402
  attachment_paths = [a.path for a in self.attachments]
403
+ if self.resume is not None:
404
+ attachment_paths.insert(0, self.resume.path)
317
405
  message = initial
318
406
  files = attachment_paths if attachment_paths else None
319
407
  turn = 0
320
- protocol_failures = 0
321
408
  last_reply = ""
322
409
 
323
410
  while True:
@@ -342,13 +429,7 @@ class Agent:
342
429
  try:
343
430
  step = parse_step(last_reply)
344
431
  except ProtocolError as exc:
345
- protocol_failures += 1
346
432
  self.session.save_last_response(last_reply, "protocol-error")
347
- if protocol_failures > self.cfg.max_protocol_retries:
348
- raise ProtocolError(
349
- f"{exc} The raw reply was saved to the dscli debug directory.",
350
- last_reply,
351
- ) from exc
352
433
  self.display.protocol_error(str(exc))
353
434
  if turn >= self.cfg.max_steps:
354
435
  return self._wrap_up(turn, last_reply or str(exc))
@@ -363,13 +444,7 @@ class Agent:
363
444
 
364
445
  turn_error = self._turn_id_error(step)
365
446
  if turn_error is not None:
366
- protocol_failures += 1
367
447
  self.session.save_last_response(last_reply, "turn-id-error")
368
- if protocol_failures > self.cfg.max_protocol_retries:
369
- raise ProtocolError(
370
- f"{turn_error} The raw reply was saved to the dscli debug directory.",
371
- last_reply,
372
- )
373
448
  self.display.protocol_error(turn_error)
374
449
  if turn >= self.cfg.max_steps:
375
450
  return self._wrap_up(turn, last_reply)
@@ -413,14 +488,11 @@ class Agent:
413
488
 
414
489
  if not step.tool_calls:
415
490
  # Should have been caught by the parser; treat it as a protocol miss.
416
- protocol_failures += 1
417
- if protocol_failures > self.cfg.max_protocol_retries:
418
- raise ProtocolError(
419
- "The model returned an envelope with no tool calls and no final answer.",
420
- last_reply,
421
- )
422
491
  if turn >= self.cfg.max_steps:
423
492
  return self._wrap_up(turn, last_reply)
493
+ self.display.protocol_error(
494
+ "The envelope had neither tool_calls nor final."
495
+ )
424
496
  message = build_protocol_repair(
425
497
  "The envelope had neither tool_calls nor final.",
426
498
  task=self.task,
@@ -701,6 +773,7 @@ def run_agent(
701
773
  on_thinking_snapshot: Callable[[str], None] | None = None,
702
774
  display: AgentDisplay | None = None,
703
775
  history: ConversationLog | None = None,
776
+ resume: Compaction | None = None,
704
777
  ) -> AgentOutcome:
705
778
  return Agent(
706
779
  session=session,
@@ -716,4 +789,5 @@ def run_agent(
716
789
  on_thinking_snapshot=on_thinking_snapshot,
717
790
  display=display,
718
791
  history=history,
792
+ resume=resume,
719
793
  ).run()