dscli-agent 0.3.0__tar.gz → 0.5.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.3.0 → dscli_agent-0.5.0}/PKG-INFO +42 -24
  2. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/README.md +41 -23
  3. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/__init__.py +1 -1
  4. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/agent.py +74 -16
  5. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/browser.py +1780 -1697
  6. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/cli.py +13 -7
  7. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/compaction.py +9 -6
  8. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/config.py +6 -1
  9. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/display.py +8 -6
  10. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/prompts.py +60 -27
  11. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/protocol.py +30 -2
  12. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/scraper.py +39 -2
  13. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/tokens.py +2 -2
  14. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/tools.py +135 -24
  15. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/tui.py +1060 -1056
  16. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli_agent.egg-info/PKG-INFO +42 -24
  17. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_agent.py +1268 -1125
  18. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_browser.py +1062 -833
  19. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_cli.py +26 -0
  20. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_compaction.py +304 -302
  21. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_config.py +20 -0
  22. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_display.py +438 -398
  23. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_protocol.py +47 -0
  24. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_tokens.py +15 -8
  25. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_tools.py +101 -2
  26. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_tui.py +670 -663
  27. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/LICENSE +0 -0
  28. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/__main__.py +0 -0
  29. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/attachments.py +0 -0
  30. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/branding.py +0 -0
  31. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/providers.py +0 -0
  32. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/ui.py +0 -0
  33. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli_agent.egg-info/SOURCES.txt +0 -0
  34. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli_agent.egg-info/dependency_links.txt +0 -0
  35. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli_agent.egg-info/entry_points.txt +0 -0
  36. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli_agent.egg-info/requires.txt +0 -0
  37. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli_agent.egg-info/top_level.txt +0 -0
  38. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/pyproject.toml +0 -0
  39. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/setup.cfg +0 -0
  40. {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_branding.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dscli-agent
3
- Version: 0.3.0
3
+ Version: 0.5.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
@@ -147,11 +147,11 @@ it collapses to the same limited view with a `Ctrl+O` hint; press `Ctrl+O` at th
147
147
  prompt to print the full reasoning block (up to 500 rows) into the transcript.
148
148
  The streamed block is TUI-only; `--plain` and `run` do not show it.
149
149
 
150
- The prompt footer tracks session token usage against a 1,000,000-token budget, and the
151
- same progress bar renders below the live Thinking and Responding output:
150
+ The prompt footer tracks the session's context volume against a 1,000,000-token budget,
151
+ and the same progress bar renders below the live Thinking and Responding output:
152
152
 
153
153
  ```text
154
- tokens ███████░░░░░ 612.4k / 1.0M · 61%
154
+ context ███████░░░░░ 612.4k / 1.0M · 61%
155
155
  ```
156
156
 
157
157
  Counts are estimated locally from every prompt sent and reply received (one token per
@@ -161,7 +161,7 @@ a new chat is opened.
161
161
 
162
162
  ### Conversation compaction
163
163
 
164
- When the estimate reaches 1,000,000 tokens, dscli compacts the session instead of
164
+ When the count reaches 1,000,000 tokens, dscli compacts the session instead of
165
165
  stopping. Before the next prompt is sent it asks the current chat to write a structured
166
166
  compaction summary — an `<analysis>` pass followed by a `<summary>` covering the request,
167
167
  technical decisions, files and code, errors and fixes, user messages, pending tasks, and
@@ -179,7 +179,7 @@ Files are saved under `%APPDATA%\dscli\compactions` on Windows
179
179
  `~/.config/dscli/compactions` elsewhere) as `compaction-YYYYmmdd-HHMMSS.txt`. The check
180
180
  runs before every model turn, so it also works mid-task; the TUI shows a
181
181
  `Compacting conversation` spinner while the summary is written, then a
182
- `⟳ Compacted 1.0M tokens → … · new chat` notice. `/new` clears the recorded
182
+ `⟳ Compacted context at 1.0M tokens → … · new chat` notice. `/new` clears the recorded
183
183
  conversation as well.
184
184
 
185
185
  ### Deeplanning mode
@@ -262,8 +262,11 @@ dscli run "Refactor the parser" -f src/parser.py -f tests/test_parser.py
262
262
  # Globs work too; quote them so the shell does not expand them
263
263
  dscli run "Fix the bug" -f "src/**/*.py" --workspace C:\projects\app
264
264
 
265
- # Read a long task from a file; auto-approve edits; allow shell commands
266
- dscli run --prompt-file task.md -y --allow-shell
265
+ # Read a long task from a file; auto-approve edits and approved shell commands
266
+ dscli run --prompt-file task.md -y
267
+
268
+ # Disable command execution entirely for this run
269
+ dscli run "Review the docs" --no-shell
267
270
  ```
268
271
 
269
272
  The final answer is printed to stdout. Progress, diffs, and approval prompts go to
@@ -281,7 +284,7 @@ failures and unconfirmed completions are always logged to stderr, including unde
281
284
  | `-f, --file PATH` | Attach a file (repeatable, globs supported). |
282
285
  | `--workspace PATH` | Root the tools may touch. Defaults to the current directory. |
283
286
  | `-y, --yes` | Approve every write/patch/command without asking. |
284
- | `--allow-shell` | Enable `run_command` (still approved per command). |
287
+ | `--no-shell` | Disable `run_command` for this run (commands are otherwise enabled and approved per command). |
285
288
  | `--allow-outside` | Let tools read/write outside the workspace. |
286
289
  | `--headed` / `--headless` | Show or hide the browser. Headless is the default. |
287
290
  | `--search` | Turn DeepSeek's Search mode on for the run (DeepThink is always on). |
@@ -355,12 +358,19 @@ creep and unnecessary abstractions, confirm hard-to-reverse actions, reference c
355
358
  | `apply_patch` | The only way to edit an existing file: exact `search` → `replace`. |
356
359
  | `list_files` | Bounded recursive listing. |
357
360
  | `delete_file` | Delete one file when explicitly requested (approval required). |
358
- | `run_command` | Run a shell command (only with `--allow-shell`, approval required). |
361
+ | `run_command` | Run a command with an approval prompt via PowerShell, cmd.exe, sh, or bash. |
359
362
  | `update_plan` | Create or revise the task plan; the task cannot finish while steps are unfinished. |
360
363
 
361
364
  `apply_patch` normalises line endings for matching, preserves the file's encoding and
362
365
  line endings when writing, and refuses ambiguous matches unless `replace_all` is set.
363
366
 
367
+ `run_command` uses `shell: auto` by default: PowerShell when available on Windows
368
+ (cmd.exe otherwise), and `/bin/sh` elsewhere. Pass `"shell": "cmd"` for cmd.exe syntax
369
+ such as `%VAR%` or batch builtins, `"powershell"`, `"sh"`, or `"bash"` to choose another
370
+ interpreter. On POSIX systems, `auto` falls back to `/bin/sh`; `bash` is selected only
371
+ when explicitly requested. The approval prompt names the resolved interpreter, so a
372
+ command is never run under a shell you did not see.
373
+
364
374
  ### Plan and verification loop
365
375
 
366
376
  The agent can maintain a short task plan with `update_plan` (at most 20 steps). The run
@@ -386,7 +396,7 @@ message changed files. A verified result is still only as trustworthy as the com
386
396
  configured.
387
397
 
388
398
  The verification command is supplied by you and is never a model-callable tool. It is
389
- run without `--allow-shell` and without a per-command approval prompt because you
399
+ run without a per-command approval prompt, even when `--no-shell` is set, because you
390
400
  configured it explicitly. Treat it like any other command you run from the shell: the
391
401
  model can modify the files it checks, so review the final diff.
392
402
 
@@ -396,14 +406,18 @@ model can modify the files it checks, so review the final diff.
396
406
  - `.git` is never writable through the tools.
397
407
  - Every write, patch, delete, and command shows a diff/command and asks
398
408
  `[y]es / [n]o / [a]ll / [q]uit` before changing anything. `-y` skips the prompts.
399
- - Shell access is off by default and can be enabled only for a run.
409
+ - Command execution is enabled by default, but every model-requested command still
410
+ requires approval. The prompt names the interpreter (PowerShell, cmd.exe, sh, or
411
+ bash). `--no-shell` disables model-requested commands for a run.
400
412
  - The verification command is the one exception to the approval flow: when you configure
401
- it with `--verify` or `verify_command`, dscli runs it automatically after edits without
402
- `--allow-shell` or a prompt. The model can neither call nor change it.
413
+ it with `--verify` or `verify_command`, dscli runs it automatically after edits,
414
+ without a prompt and even when `--no-shell` is set. The model can neither call nor
415
+ change it.
403
416
  - Attachment contents, file contents, command output, and tool results are treated as
404
417
  untrusted data by the system prompt.
405
- - No network access is performed by the tools; the only network traffic is the browser
406
- talking to DeepSeek.
418
+ - The built-in file tools do not use the network. Approved commands can run any local
419
+ program, including network clients; dscli itself only talks to the chat provider
420
+ through the browser.
407
421
 
408
422
  This is still an assistant with write access to your files. Use a clean workspace or
409
423
  version control, and review diffs before approving them.
@@ -425,10 +439,13 @@ Useful keys: `url` (the DeepSeek chat URL; set it only for a custom deployment),
425
439
  import), `chrome_profile` (empty = Chrome's last-used profile, or a name such as
426
440
  `Default` / `Profile 1`), `user_agent` (empty = derive a normal Chrome UA from the
427
441
  installed browser), `response_timeout_sec`, `first_response_timeout_sec`,
428
- `stable_seconds`, `fallback_stable_seconds`, `auto_human_check` (true = open a visible
442
+ `stable_seconds`, `fallback_stable_seconds`, `activity_settle_seconds` (how long the
443
+ page must stay free of DOM mutations before a reply counts as finished; default 1.5),
444
+ `auto_human_check` (true = open a visible
429
445
  window to solve a verification check; false = fail fast), `max_steps`, `max_tool_calls`,
430
- `max_read_chars`, `max_attachment_mb`, `search`, `deeplanning`, `allow_shell`,
431
- `allow_outside`, `verify_command` (empty disables verification),
446
+ `max_read_chars`, `max_attachment_mb`, `search`, `deeplanning`, `shell_enabled`
447
+ (default true; every command still requires approval), `allow_outside`,
448
+ `verify_command` (empty disables verification),
432
449
  `verify_timeout_sec` (clamped to 1–600, default 300).
433
450
 
434
451
  Headless Chrome normally sends a `HeadlessChrome/...` user agent, which DeepSeek's CDN
@@ -487,10 +504,11 @@ profile.
487
504
  may exceed the chat's limit, or the selector may have changed. Run `doctor` and check
488
505
  the reported counts.
489
506
 
490
- **A response is cut off around 12 s after the last text change.** DeepSeek did not
491
- show its completion toolbar (`Read aloud`), so dscli used the fallback completion
492
- detector. Increase `fallback_stable_seconds` if replies are still streaming at that
493
- point.
507
+ **A response is cut off around 12 s after the last text change.** dscli normally waits
508
+ for the reply text to stabilize while the page itself stops changing DOM nodes and
509
+ text (`activity_settle_seconds`, default 1.5 s). If the page keeps mutating, the
510
+ fallback detector returns the reply after `fallback_stable_seconds` of text stability.
511
+ Increase `fallback_stable_seconds` if replies are still streaming at that point.
494
512
 
495
513
  **Chrome will not launch.** Set `"channel": ""` in the config to use Playwright's
496
514
  Chromium, after running `python -m playwright install chromium`.
@@ -507,7 +525,7 @@ Debug screenshots and unparsed replies are written to `%APPDATA%\dscli\debug`.
507
525
  - Streaming is approximated by polling the DOM, not by tokens from the model. The
508
526
  interactive chat streams a final answer as the page updates only after it declares
509
527
  `complete: true`; tool calls still wait for the complete, validated JSON envelope.
510
- The token bar is likewise a local estimate of sent and received text, not usage
528
+ The context bar is likewise a local estimate of sent and received text, not usage
511
529
  reported by DeepSeek.
512
530
  - Interrupting with Ctrl+C clicks DeepSeek's labelled stop button when dscli can find
513
531
  it. If the page does not expose one, generation may continue in the web chat; the
@@ -117,11 +117,11 @@ it collapses to the same limited view with a `Ctrl+O` hint; press `Ctrl+O` at th
117
117
  prompt to print the full reasoning block (up to 500 rows) into the transcript.
118
118
  The streamed block is TUI-only; `--plain` and `run` do not show it.
119
119
 
120
- The prompt footer tracks session token usage against a 1,000,000-token budget, and the
121
- same progress bar renders below the live Thinking and Responding output:
120
+ The prompt footer tracks the session's context volume against a 1,000,000-token budget,
121
+ and the same progress bar renders below the live Thinking and Responding output:
122
122
 
123
123
  ```text
124
- tokens ███████░░░░░ 612.4k / 1.0M · 61%
124
+ context ███████░░░░░ 612.4k / 1.0M · 61%
125
125
  ```
126
126
 
127
127
  Counts are estimated locally from every prompt sent and reply received (one token per
@@ -131,7 +131,7 @@ a new chat is opened.
131
131
 
132
132
  ### Conversation compaction
133
133
 
134
- When the estimate reaches 1,000,000 tokens, dscli compacts the session instead of
134
+ When the count reaches 1,000,000 tokens, dscli compacts the session instead of
135
135
  stopping. Before the next prompt is sent it asks the current chat to write a structured
136
136
  compaction summary — an `<analysis>` pass followed by a `<summary>` covering the request,
137
137
  technical decisions, files and code, errors and fixes, user messages, pending tasks, and
@@ -149,7 +149,7 @@ Files are saved under `%APPDATA%\dscli\compactions` on Windows
149
149
  `~/.config/dscli/compactions` elsewhere) as `compaction-YYYYmmdd-HHMMSS.txt`. The check
150
150
  runs before every model turn, so it also works mid-task; the TUI shows a
151
151
  `Compacting conversation` spinner while the summary is written, then a
152
- `⟳ Compacted 1.0M tokens → … · new chat` notice. `/new` clears the recorded
152
+ `⟳ Compacted context at 1.0M tokens → … · new chat` notice. `/new` clears the recorded
153
153
  conversation as well.
154
154
 
155
155
  ### Deeplanning mode
@@ -232,8 +232,11 @@ dscli run "Refactor the parser" -f src/parser.py -f tests/test_parser.py
232
232
  # Globs work too; quote them so the shell does not expand them
233
233
  dscli run "Fix the bug" -f "src/**/*.py" --workspace C:\projects\app
234
234
 
235
- # Read a long task from a file; auto-approve edits; allow shell commands
236
- dscli run --prompt-file task.md -y --allow-shell
235
+ # Read a long task from a file; auto-approve edits and approved shell commands
236
+ dscli run --prompt-file task.md -y
237
+
238
+ # Disable command execution entirely for this run
239
+ dscli run "Review the docs" --no-shell
237
240
  ```
238
241
 
239
242
  The final answer is printed to stdout. Progress, diffs, and approval prompts go to
@@ -251,7 +254,7 @@ failures and unconfirmed completions are always logged to stderr, including unde
251
254
  | `-f, --file PATH` | Attach a file (repeatable, globs supported). |
252
255
  | `--workspace PATH` | Root the tools may touch. Defaults to the current directory. |
253
256
  | `-y, --yes` | Approve every write/patch/command without asking. |
254
- | `--allow-shell` | Enable `run_command` (still approved per command). |
257
+ | `--no-shell` | Disable `run_command` for this run (commands are otherwise enabled and approved per command). |
255
258
  | `--allow-outside` | Let tools read/write outside the workspace. |
256
259
  | `--headed` / `--headless` | Show or hide the browser. Headless is the default. |
257
260
  | `--search` | Turn DeepSeek's Search mode on for the run (DeepThink is always on). |
@@ -325,12 +328,19 @@ creep and unnecessary abstractions, confirm hard-to-reverse actions, reference c
325
328
  | `apply_patch` | The only way to edit an existing file: exact `search` → `replace`. |
326
329
  | `list_files` | Bounded recursive listing. |
327
330
  | `delete_file` | Delete one file when explicitly requested (approval required). |
328
- | `run_command` | Run a shell command (only with `--allow-shell`, approval required). |
331
+ | `run_command` | Run a command with an approval prompt via PowerShell, cmd.exe, sh, or bash. |
329
332
  | `update_plan` | Create or revise the task plan; the task cannot finish while steps are unfinished. |
330
333
 
331
334
  `apply_patch` normalises line endings for matching, preserves the file's encoding and
332
335
  line endings when writing, and refuses ambiguous matches unless `replace_all` is set.
333
336
 
337
+ `run_command` uses `shell: auto` by default: PowerShell when available on Windows
338
+ (cmd.exe otherwise), and `/bin/sh` elsewhere. Pass `"shell": "cmd"` for cmd.exe syntax
339
+ such as `%VAR%` or batch builtins, `"powershell"`, `"sh"`, or `"bash"` to choose another
340
+ interpreter. On POSIX systems, `auto` falls back to `/bin/sh`; `bash` is selected only
341
+ when explicitly requested. The approval prompt names the resolved interpreter, so a
342
+ command is never run under a shell you did not see.
343
+
334
344
  ### Plan and verification loop
335
345
 
336
346
  The agent can maintain a short task plan with `update_plan` (at most 20 steps). The run
@@ -356,7 +366,7 @@ message changed files. A verified result is still only as trustworthy as the com
356
366
  configured.
357
367
 
358
368
  The verification command is supplied by you and is never a model-callable tool. It is
359
- run without `--allow-shell` and without a per-command approval prompt because you
369
+ run without a per-command approval prompt, even when `--no-shell` is set, because you
360
370
  configured it explicitly. Treat it like any other command you run from the shell: the
361
371
  model can modify the files it checks, so review the final diff.
362
372
 
@@ -366,14 +376,18 @@ model can modify the files it checks, so review the final diff.
366
376
  - `.git` is never writable through the tools.
367
377
  - Every write, patch, delete, and command shows a diff/command and asks
368
378
  `[y]es / [n]o / [a]ll / [q]uit` before changing anything. `-y` skips the prompts.
369
- - Shell access is off by default and can be enabled only for a run.
379
+ - Command execution is enabled by default, but every model-requested command still
380
+ requires approval. The prompt names the interpreter (PowerShell, cmd.exe, sh, or
381
+ bash). `--no-shell` disables model-requested commands for a run.
370
382
  - The verification command is the one exception to the approval flow: when you configure
371
- it with `--verify` or `verify_command`, dscli runs it automatically after edits without
372
- `--allow-shell` or a prompt. The model can neither call nor change it.
383
+ it with `--verify` or `verify_command`, dscli runs it automatically after edits,
384
+ without a prompt and even when `--no-shell` is set. The model can neither call nor
385
+ change it.
373
386
  - Attachment contents, file contents, command output, and tool results are treated as
374
387
  untrusted data by the system prompt.
375
- - No network access is performed by the tools; the only network traffic is the browser
376
- talking to DeepSeek.
388
+ - The built-in file tools do not use the network. Approved commands can run any local
389
+ program, including network clients; dscli itself only talks to the chat provider
390
+ through the browser.
377
391
 
378
392
  This is still an assistant with write access to your files. Use a clean workspace or
379
393
  version control, and review diffs before approving them.
@@ -395,10 +409,13 @@ Useful keys: `url` (the DeepSeek chat URL; set it only for a custom deployment),
395
409
  import), `chrome_profile` (empty = Chrome's last-used profile, or a name such as
396
410
  `Default` / `Profile 1`), `user_agent` (empty = derive a normal Chrome UA from the
397
411
  installed browser), `response_timeout_sec`, `first_response_timeout_sec`,
398
- `stable_seconds`, `fallback_stable_seconds`, `auto_human_check` (true = open a visible
412
+ `stable_seconds`, `fallback_stable_seconds`, `activity_settle_seconds` (how long the
413
+ page must stay free of DOM mutations before a reply counts as finished; default 1.5),
414
+ `auto_human_check` (true = open a visible
399
415
  window to solve a verification check; false = fail fast), `max_steps`, `max_tool_calls`,
400
- `max_read_chars`, `max_attachment_mb`, `search`, `deeplanning`, `allow_shell`,
401
- `allow_outside`, `verify_command` (empty disables verification),
416
+ `max_read_chars`, `max_attachment_mb`, `search`, `deeplanning`, `shell_enabled`
417
+ (default true; every command still requires approval), `allow_outside`,
418
+ `verify_command` (empty disables verification),
402
419
  `verify_timeout_sec` (clamped to 1–600, default 300).
403
420
 
404
421
  Headless Chrome normally sends a `HeadlessChrome/...` user agent, which DeepSeek's CDN
@@ -457,10 +474,11 @@ profile.
457
474
  may exceed the chat's limit, or the selector may have changed. Run `doctor` and check
458
475
  the reported counts.
459
476
 
460
- **A response is cut off around 12 s after the last text change.** DeepSeek did not
461
- show its completion toolbar (`Read aloud`), so dscli used the fallback completion
462
- detector. Increase `fallback_stable_seconds` if replies are still streaming at that
463
- point.
477
+ **A response is cut off around 12 s after the last text change.** dscli normally waits
478
+ for the reply text to stabilize while the page itself stops changing DOM nodes and
479
+ text (`activity_settle_seconds`, default 1.5 s). If the page keeps mutating, the
480
+ fallback detector returns the reply after `fallback_stable_seconds` of text stability.
481
+ Increase `fallback_stable_seconds` if replies are still streaming at that point.
464
482
 
465
483
  **Chrome will not launch.** Set `"channel": ""` in the config to use Playwright's
466
484
  Chromium, after running `python -m playwright install chromium`.
@@ -477,7 +495,7 @@ Debug screenshots and unparsed replies are written to `%APPDATA%\dscli\debug`.
477
495
  - Streaming is approximated by polling the DOM, not by tokens from the model. The
478
496
  interactive chat streams a final answer as the page updates only after it declares
479
497
  `complete: true`; tool calls still wait for the complete, validated JSON envelope.
480
- The token bar is likewise a local estimate of sent and received text, not usage
498
+ The context bar is likewise a local estimate of sent and received text, not usage
481
499
  reported by DeepSeek.
482
500
  - Interrupting with Ctrl+C clicks DeepSeek's labelled stop button when dscli can find
483
501
  it. If the page does not expose one, generation may continue in the web chat; the
@@ -1,3 +1,3 @@
1
1
  """dscli - a local coding agent for your terminal."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.5.0"
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import logging
5
+ import secrets
5
6
  import time
6
7
  from dataclasses import dataclass, field
7
8
  from pathlib import Path
@@ -23,7 +24,7 @@ from .prompts import (
23
24
  )
24
25
  from .protocol import ProtocolError, Step, ToolCall, extract_final_prefix, parse_step
25
26
  from .providers import MODEL_NAME
26
- from .tokens import MAX_TOKENS, format_tokens
27
+ from .tokens import MAX_CONTEXT_TOKENS, format_tokens
27
28
  from .tools import (
28
29
  ApprovalRequired,
29
30
  PlanStep,
@@ -102,11 +103,12 @@ class Agent:
102
103
  self._verify_timeout = config.verification_timeout()
103
104
  self._sent_any = False
104
105
  self._previous_plan: list[PlanStep] = []
106
+ self.turn_id = ""
105
107
 
106
- def _token_budget_reached(self) -> bool:
107
- used = getattr(self.session, "tokens_used", 0)
108
+ def _context_budget_reached(self) -> bool:
109
+ used = getattr(self.session, "context_tokens", 0)
108
110
  try:
109
- return int(used or 0) >= MAX_TOKENS
111
+ return int(used or 0) >= MAX_CONTEXT_TOKENS
110
112
  except (TypeError, ValueError):
111
113
  return False
112
114
 
@@ -144,9 +146,9 @@ class Agent:
144
146
  *,
145
147
  wrapup: bool,
146
148
  ) -> tuple[str, list[Path] | None]:
147
- if not self._token_budget_reached():
149
+ if not self._context_budget_reached():
148
150
  return message, files
149
- used = int(getattr(self.session, "tokens_used", 0) or 0)
151
+ used = int(getattr(self.session, "context_tokens", 0) or 0)
150
152
  plan = self.toolbox.plan if self._sent_any else self._previous_plan
151
153
  tree = project_structure(self.workspace)
152
154
  summary = self._request_compaction_summary(plan)
@@ -157,20 +159,20 @@ class Agent:
157
159
  plan=plan,
158
160
  changed=self.toolbox.changed_files,
159
161
  verification=self._verification_summary(),
160
- tokens_used=used,
162
+ context_tokens=used,
161
163
  project_tree=tree,
162
164
  summary=summary,
163
165
  )
164
166
  self.session.new_chat()
165
167
  self.display.compaction_started(str(compaction.path), used)
166
168
  log.warning(
167
- "Token estimate reached %s. Compacted the conversation to %s and started "
169
+ "Context estimate reached %s. Compacted the conversation to %s and started "
168
170
  "a new chat.",
169
171
  format_tokens(used),
170
172
  compaction.path,
171
173
  )
172
174
  self.history.add_note(
173
- f"Compacted at {used:,} estimated tokens into {compaction.path.name}; "
175
+ f"Compacted at {used:,} estimated context tokens into {compaction.path.name}; "
174
176
  "a new chat was started."
175
177
  )
176
178
  resume = build_resume_prompt(
@@ -182,7 +184,7 @@ class Agent:
182
184
  live_plan=self.toolbox.plan,
183
185
  changed=self.toolbox.changed_files,
184
186
  attachments=self.attachments,
185
- allow_shell=self.cfg.allow_shell,
187
+ allow_shell=self.cfg.shell_enabled,
186
188
  allow_outside=self.cfg.allow_outside,
187
189
  max_steps=self.cfg.max_steps,
188
190
  verify_command=self._verify_command,
@@ -190,6 +192,7 @@ class Agent:
190
192
  wrapup=wrapup,
191
193
  deeplanning=self.cfg.deeplanning,
192
194
  max_tool_calls=self.cfg.max_tool_calls,
195
+ turn_id=self.turn_id,
193
196
  )
194
197
  attachments = [compaction.path]
195
198
  for attachment in self.attachments:
@@ -221,7 +224,7 @@ class Agent:
221
224
 
222
225
  def on_reply_snapshot(raw: str) -> None:
223
226
  nonlocal last_final
224
- final = extract_final_prefix(raw)
227
+ final = extract_final_prefix(raw, turn_id=self.turn_id or None)
225
228
  if final is not None and final != last_final:
226
229
  last_final = final
227
230
  if not self._streamed_final:
@@ -251,7 +254,25 @@ class Agent:
251
254
  ),
252
255
  )
253
256
 
257
+ def _turn_id_error(self, step: Step) -> str | None:
258
+ if not self.turn_id:
259
+ return None
260
+ if step.turn_id is None:
261
+ return (
262
+ "The envelope is missing the required turn_id field. Every reply to this "
263
+ f'message must include "turn_id":"{self.turn_id}".'
264
+ )
265
+ if step.turn_id != self.turn_id:
266
+ return (
267
+ f"The envelope's turn_id {step.turn_id!r} does not match the current "
268
+ f"message's turn id {self.turn_id!r}. It looks like text from an earlier "
269
+ "message, so it was not executed. Answer the current message and include "
270
+ f'"turn_id":"{self.turn_id}".'
271
+ )
272
+ return None
273
+
254
274
  def run(self) -> AgentOutcome:
275
+ self.turn_id = secrets.token_hex(4)
255
276
  self._previous_plan = [
256
277
  step for step in self.toolbox.plan if step.status != "completed"
257
278
  ]
@@ -267,6 +288,7 @@ class Agent:
267
288
  if self.followup:
268
289
  initial = build_followup_prompt(
269
290
  self.task,
291
+ turn_id=self.turn_id,
270
292
  verify_command=self._verify_command,
271
293
  deeplanning=self.cfg.deeplanning,
272
294
  max_tool_calls=self.cfg.max_tool_calls,
@@ -277,12 +299,13 @@ class Agent:
277
299
  workspace=self.workspace,
278
300
  attachments=self.attachments,
279
301
  workspace_tree=workspace_preview(self.workspace),
280
- allow_shell=self.cfg.allow_shell,
302
+ allow_shell=self.cfg.shell_enabled,
281
303
  allow_outside=self.cfg.allow_outside,
282
304
  max_steps=self.cfg.max_steps,
283
305
  verify_command=self._verify_command,
284
306
  deeplanning=self.cfg.deeplanning,
285
307
  max_tool_calls=self.cfg.max_tool_calls,
308
+ turn_id=self.turn_id,
286
309
  )
287
310
  if len(initial) > 80000:
288
311
  raise ValueError(
@@ -330,7 +353,32 @@ class Agent:
330
353
  if turn >= self.cfg.max_steps:
331
354
  return self._wrap_up(turn, last_reply or str(exc))
332
355
  message = build_protocol_repair(
333
- str(exc), task=self.task, step=turn, max_steps=self.cfg.max_steps
356
+ str(exc),
357
+ task=self.task,
358
+ step=turn,
359
+ max_steps=self.cfg.max_steps,
360
+ turn_id=self.turn_id,
361
+ )
362
+ continue
363
+
364
+ turn_error = self._turn_id_error(step)
365
+ if turn_error is not None:
366
+ protocol_failures += 1
367
+ 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
+ self.display.protocol_error(turn_error)
374
+ if turn >= self.cfg.max_steps:
375
+ return self._wrap_up(turn, last_reply)
376
+ message = build_protocol_repair(
377
+ turn_error,
378
+ task=self.task,
379
+ step=turn,
380
+ max_steps=self.cfg.max_steps,
381
+ turn_id=self.turn_id,
334
382
  )
335
383
  continue
336
384
 
@@ -378,6 +426,7 @@ class Agent:
378
426
  task=self.task,
379
427
  step=turn,
380
428
  max_steps=self.cfg.max_steps,
429
+ turn_id=self.turn_id,
381
430
  )
382
431
  continue
383
432
 
@@ -468,7 +517,7 @@ class Agent:
468
517
  step=turn,
469
518
  max_steps=self.cfg.max_steps,
470
519
  changed=self.toolbox.changed_files,
471
- allow_shell=self.cfg.allow_shell,
520
+ allow_shell=self.cfg.shell_enabled,
472
521
  per_result_chars=self.cfg.max_tool_output_chars,
473
522
  total_chars=self.cfg.max_turn_output_chars,
474
523
  notice=notice,
@@ -476,6 +525,7 @@ class Agent:
476
525
  verify_command=self._verify_command,
477
526
  deeplanning=self.cfg.deeplanning,
478
527
  max_tool_calls=self.cfg.max_tool_calls,
528
+ turn_id=self.turn_id,
479
529
  )
480
530
 
481
531
  def _completion_reminder_message(
@@ -594,6 +644,7 @@ class Agent:
594
644
  step=turn,
595
645
  plan=self.toolbox.plan,
596
646
  verify_command=self._verify_command,
647
+ turn_id=self.turn_id,
597
648
  )
598
649
  self.display.wrapup_started()
599
650
  reply = self._send_and_wait(prompt, stream_final=False, wrapup=True)
@@ -611,13 +662,20 @@ class Agent:
611
662
  limit_reached=True,
612
663
  plan_incomplete=plan_incomplete,
613
664
  )
614
- if step.final is not None:
665
+ turn_error = self._turn_id_error(step)
666
+ if turn_error is not None:
667
+ self.session.save_last_response(reply, "wrapup-turn-id-error")
668
+ log.warning("The wrap-up reply failed the turn-id check; showing the raw text.")
669
+ final = reply.strip() or "Stopped: the model-turn budget was exhausted."
670
+ elif step.final is not None:
615
671
  final = (step.final or "").strip() or "Stopped: the model-turn budget was exhausted."
616
672
  else:
617
673
  self.session.save_last_response(reply, "wrapup-with-tools")
618
674
  final = reply.strip() or "Stopped: the model-turn budget was exhausted."
619
675
  plan_incomplete = self.toolbox.plan_has_pending()
620
- completed = step.complete is True and not plan_incomplete
676
+ completed = (
677
+ turn_error is None and step.complete is True and not plan_incomplete
678
+ )
621
679
  self._verify_if_needed()
622
680
  return self._outcome(
623
681
  final,