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.
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/PKG-INFO +42 -24
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/README.md +41 -23
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/__init__.py +1 -1
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/agent.py +74 -16
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/browser.py +1780 -1697
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/cli.py +13 -7
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/compaction.py +9 -6
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/config.py +6 -1
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/display.py +8 -6
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/prompts.py +60 -27
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/protocol.py +30 -2
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/scraper.py +39 -2
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/tokens.py +2 -2
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/tools.py +135 -24
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/tui.py +1060 -1056
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli_agent.egg-info/PKG-INFO +42 -24
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_agent.py +1268 -1125
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_browser.py +1062 -833
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_cli.py +26 -0
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_compaction.py +304 -302
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_config.py +20 -0
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_display.py +438 -398
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_protocol.py +47 -0
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_tokens.py +15 -8
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_tools.py +101 -2
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/tests/test_tui.py +670 -663
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/LICENSE +0 -0
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/__main__.py +0 -0
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/attachments.py +0 -0
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/branding.py +0 -0
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/providers.py +0 -0
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli/ui.py +0 -0
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli_agent.egg-info/SOURCES.txt +0 -0
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli_agent.egg-info/dependency_links.txt +0 -0
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli_agent.egg-info/entry_points.txt +0 -0
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli_agent.egg-info/requires.txt +0 -0
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/dscli_agent.egg-info/top_level.txt +0 -0
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/pyproject.toml +0 -0
- {dscli_agent-0.3.0 → dscli_agent-0.5.0}/setup.cfg +0 -0
- {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
|
+
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
|
|
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
|
-
|
|
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
|
|
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
|
|
266
|
-
dscli run --prompt-file task.md -y
|
|
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
|
-
| `--
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
|
402
|
-
`--
|
|
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
|
-
-
|
|
406
|
-
|
|
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`, `
|
|
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`, `
|
|
431
|
-
|
|
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.**
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
236
|
-
dscli run --prompt-file task.md -y
|
|
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
|
-
| `--
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
|
372
|
-
`--
|
|
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
|
-
-
|
|
376
|
-
|
|
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`, `
|
|
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`, `
|
|
401
|
-
|
|
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.**
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
|
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
|
|
@@ -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
|
|
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
|
|
107
|
-
used = getattr(self.session, "
|
|
108
|
+
def _context_budget_reached(self) -> bool:
|
|
109
|
+
used = getattr(self.session, "context_tokens", 0)
|
|
108
110
|
try:
|
|
109
|
-
return int(used or 0) >=
|
|
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.
|
|
149
|
+
if not self._context_budget_reached():
|
|
148
150
|
return message, files
|
|
149
|
-
used = int(getattr(self.session, "
|
|
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
|
-
|
|
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
|
-
"
|
|
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.
|
|
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.
|
|
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),
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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,
|