diff --git a/docs/local-models.md b/docs/local-models.md index b3814c5d..6f957816 100644 --- a/docs/local-models.md +++ b/docs/local-models.md @@ -223,7 +223,7 @@ the agent, so a session with one agent and a session with another run on the sam | OpenCode | about 8,100 tokens, 11 tools (v1.19.5; more with user-level skills) | yes | A tool round trip on a Mac with a real model (the catalogue's tested entry), and on a scripted endpoint for v1.19.5 | | Claude Code | about 36,000 tokens, so the first answer takes minutes | yes | Shipped in 1.50.0; not measured again for this table | | Pi | 2,096 tokens, 4 tools (v1.5.2) | yes | A complete tool round trip against a real model: tool call, tool result, final answer, the file on disk | -| DeepSeek Harness | about 7,650 tokens, 25 tools (0.1.1-rc.2) | **no** | A tool round trip on a scripted endpoint only. Against a real model the launch and the model name reached the runtime, and no turn finished | +| DeepSeek Harness | about 7,650 tokens, 25 tools (0.1.1-rc.2); 6,983, 27 tools (0.2.2-rc.1) | **no** | A tool round trip on a scripted endpoint only, on both versions. Against a real model the launch and the model name reached the runtime, and no turn finished | How to read the last column: the runs of 2026-11-10 were made on **Linux**, through Codeman's own custom model launch, against Ollama 0.13.0 with a 1.7B model on a CPU ("a real model") or @@ -248,7 +248,11 @@ Three things follow from how the agents differ: the one agent of the four besides Claude Code that reports the end of a turn. Its custom endpoint recipe itself was verified with a real model on a llama.cpp server ([custom-model-endpoints.md](custom-model-endpoints.md)); what is missing is a finished turn - on this feature's runtime. + on this feature's runtime. On today's version (0.2.2-rc.1 with dsh-TUI 0.19.0) two more + things were seen: a setup guide opens in every session until a person has finished it once + (a person in a tab presses through it; a script must not, see the codeman skill), and its + transcript file was renamed, so Codeman's response viewer and `last-response` are empty for + a dsh session on that version until the reader learns the new name. - **OpenCode and DeepSeek Harness send a second, small request** beside the first prompt (a title, a side question). The runtime answers one request at a time, so it queues. diff --git a/plugins/codeman/skills/codeman/SKILL.md b/plugins/codeman/skills/codeman/SKILL.md index 3aebe247..c6ffbf58 100644 --- a/plugins/codeman/skills/codeman/SKILL.md +++ b/plugins/codeman/skills/codeman/SKILL.md @@ -197,6 +197,17 @@ _dsh_up() { # -> "true"/"false". The DeepSeek Harness T # composer glyph. Override with DSH_READY_MARK for a profile that draws another one. _mark_up "${1}" "${DSH_READY_MARK:-❯}" "${2}" } +_dsh_guide() { # -> 0 when the pane shows dsh-TUI's first-run SETUP GUIDE, not a composer. + # dsh-TUI 0.19.0 opens a guide ("Welcome to dsh-TUI Step 1 of 4") in every session until a + # person has finished it once, and the guide's own cursor is `❯`: the readiness mark matches + # on it (measured, 2 of 2 launches), and an Enter there acts on the highlighted step, which + # is a sign-in. So a worker whose mark matched is still checked for the guide, on the + # RENDERED pane with its whitespace removed, and never handed back while it shows. + "${CURL[@]}" -G "$API/api/v1/sessions/${1}/terminal" --data-urlencode 'full=1' \ + | jq -r '.data.terminalBuffer // empty' \ + | sed -e "s/$(printf '\033')\[[0-9;?]*[a-zA-Z]//g" -e "s/$(printf '\033')[()][AB0]//g" \ + | tr -d ' \t' | grep -qi 'Step[0-9]of[0-9]' +} # ---- the workspace-trust dialog: READ the screen, never press Enter blind ---- # Claude Code 2.1.252 dropped the option numbers, REVERSED them, and highlights # "No, exit" by default: @@ -455,11 +466,17 @@ spawn_worker() { fi if [ -n "$cm" ] && [ "$mode" != claude ]; then # No trust dialog and no hook file to vet. opencode and pi have no end-of-turn signal, - # which is exactly why sendwait refuses them (400) and sendmark drives them; a deepseek - # worker reports the end of a turn on the local model too, so sendwait works for it. + # which is exactly why sendwait refuses them (400) and sendmark drives them. A deepseek + # worker reports the end of a turn on the local model too, but sendmark is still the way + # to get its ANSWER: dsh 0.2.2 renamed its transcript file, and until Codeman reads the + # new one `last-response` comes back empty for it. r=$(_mark_up "$sid" "$mark" 45000) [ "$r" = true ] || { echo "local worker $sid ($mode) never drew '$mark' within 45 s; deleted it. Look at GET /api/v1/local-model and the pane of a hand-made session before retrying" >&2 delete_session "$sid" >/dev/null; return 1; } + if [ "$mode" = deepseek ] && _dsh_guide "$sid"; then + echo "dsh worker $sid opened DeepSeek Harness's setup guide instead of a composer; deleted it. A person finishes that guide once (start a DeepSeek session from the Run menu), then spawn again" >&2 + delete_session "$sid" >/dev/null; return 1 + fi printf '%s\n' "$sid"; return 0 fi if [ "$mode" = deepseek ]; then @@ -475,6 +492,10 @@ spawn_worker() { r=$(_dsh_up "$sid" 45000) [ "$r" = true ] || { echo "dsh worker $sid never drew a composer: no pane-capable profile, a profile whose composer is not '${DSH_READY_MARK:-❯}' (set DSH_READY_MARK), or a harness that failed to boot -- check GET /api/v1/deepseek/status. Deleted it" >&2 delete_session "$sid" >/dev/null; return 1; } + if _dsh_guide "$sid"; then + echo "dsh worker $sid opened DeepSeek Harness's setup guide instead of a composer; deleted it. A person finishes that guide once (start a DeepSeek session from the Run menu), then spawn again" >&2 + delete_session "$sid" >/dev/null; return 1 + fi printf '%s\n' "$sid"; return 0 fi [ "$mode" = claude ] || { printf '%s\n' "$sid"; return 0; } # no other mode draws a composer to wait on @@ -909,10 +930,10 @@ on), and from then on it is the same verbs. It mixes into one fleet like any oth | Worker kind | Agent | Why you would pick it | Drive it with | | --- | --- | --- | --- | -| `local` | the default one (`cli` of `local_model`) | you have no reason to choose | `sendmark` (`sendwait` when it is claude or deepseek) | +| `local` | the default one (`cli` of `local_model`) | you have no reason to choose | `sendmark` (`sendwait` when it is claude) | | `local:opencode` | OpenCode | the tested pair; a full tool set | `sendmark` | | `local:pi` | Pi | the smallest fixed prompt (about 2,100 tokens against 8,000 and more), so the first answer comes soonest and the most context is left for the work | `sendmark` | -| `local:deepseek` | DeepSeek Harness | the one of these that reports the end of a turn | `sendwait` or `sendmark` | +| `local:deepseek` | DeepSeek Harness | the user already works with it | `sendmark` (it reports the end of a turn, but on dsh 0.2.2 its answer cannot be read back through `sendwait` yet) | | `local:claude` | Claude Code | hooks and `sendwait`, at the price of a first answer that takes minutes | `sendwait` | An agent whose status is `installable`, `installing` or `setup-needed` is not startable, and @@ -968,8 +989,14 @@ The limits, before you promise the user anything: loads the model first. A local worker run by claude spends minutes on its first answer (its fixed prompt is about 36,000 tokens). - **Markers, not hooks.** OpenCode and Pi report no end of turn, so `sendwait` answers 400 - for those workers; `sendmark` is the way through. A claude worker has hooks and a - DeepSeek Harness worker reports its turns, so `sendwait` works for those two. + for those workers; `sendmark` is the way through. A claude worker has hooks, so + `sendwait` works for it. A DeepSeek Harness worker reports its turns too, but drive it + with `sendmark` on the local model: dsh 0.2.2 renamed its transcript file, and Codeman + reads an empty answer from it for now. +- **DeepSeek Harness has a setup guide a person finishes once.** Until then every dsh + session opens on it, and the spawn refuses the worker (and deletes it) instead of typing + a task into a sign-in screen. Tell the user to start one DeepSeek session from the Run + menu and finish the guide. - **Pi starts without trusting the folder.** A `local:pi` worker is started with `--no-approve`, so it loads no project extensions or skills from the case folder: on the local model Pi would otherwise ask to trust the folder in every session, and a task diff --git a/plugins/codeman/skills/codeman/preamble.sh b/plugins/codeman/skills/codeman/preamble.sh index 88a42ce7..a0fab818 100644 --- a/plugins/codeman/skills/codeman/preamble.sh +++ b/plugins/codeman/skills/codeman/preamble.sh @@ -59,6 +59,17 @@ _dsh_up() { # -> "true"/"false". The DeepSeek Harness T # composer glyph. Override with DSH_READY_MARK for a profile that draws another one. _mark_up "${1}" "${DSH_READY_MARK:-❯}" "${2}" } +_dsh_guide() { # -> 0 when the pane shows dsh-TUI's first-run SETUP GUIDE, not a composer. + # dsh-TUI 0.19.0 opens a guide ("Welcome to dsh-TUI Step 1 of 4") in every session until a + # person has finished it once, and the guide's own cursor is `❯`: the readiness mark matches + # on it (measured, 2 of 2 launches), and an Enter there acts on the highlighted step, which + # is a sign-in. So a worker whose mark matched is still checked for the guide, on the + # RENDERED pane with its whitespace removed, and never handed back while it shows. + "${CURL[@]}" -G "$API/api/v1/sessions/${1}/terminal" --data-urlencode 'full=1' \ + | jq -r '.data.terminalBuffer // empty' \ + | sed -e "s/$(printf '\033')\[[0-9;?]*[a-zA-Z]//g" -e "s/$(printf '\033')[()][AB0]//g" \ + | tr -d ' \t' | grep -qi 'Step[0-9]of[0-9]' +} # ---- the workspace-trust dialog: READ the screen, never press Enter blind ---- # Claude Code 2.1.252 dropped the option numbers, REVERSED them, and highlights # "No, exit" by default: @@ -317,11 +328,17 @@ spawn_worker() { fi if [ -n "$cm" ] && [ "$mode" != claude ]; then # No trust dialog and no hook file to vet. opencode and pi have no end-of-turn signal, - # which is exactly why sendwait refuses them (400) and sendmark drives them; a deepseek - # worker reports the end of a turn on the local model too, so sendwait works for it. + # which is exactly why sendwait refuses them (400) and sendmark drives them. A deepseek + # worker reports the end of a turn on the local model too, but sendmark is still the way + # to get its ANSWER: dsh 0.2.2 renamed its transcript file, and until Codeman reads the + # new one `last-response` comes back empty for it. r=$(_mark_up "$sid" "$mark" 45000) [ "$r" = true ] || { echo "local worker $sid ($mode) never drew '$mark' within 45 s; deleted it. Look at GET /api/v1/local-model and the pane of a hand-made session before retrying" >&2 delete_session "$sid" >/dev/null; return 1; } + if [ "$mode" = deepseek ] && _dsh_guide "$sid"; then + echo "dsh worker $sid opened DeepSeek Harness's setup guide instead of a composer; deleted it. A person finishes that guide once (start a DeepSeek session from the Run menu), then spawn again" >&2 + delete_session "$sid" >/dev/null; return 1 + fi printf '%s\n' "$sid"; return 0 fi if [ "$mode" = deepseek ]; then @@ -337,6 +354,10 @@ spawn_worker() { r=$(_dsh_up "$sid" 45000) [ "$r" = true ] || { echo "dsh worker $sid never drew a composer: no pane-capable profile, a profile whose composer is not '${DSH_READY_MARK:-❯}' (set DSH_READY_MARK), or a harness that failed to boot -- check GET /api/v1/deepseek/status. Deleted it" >&2 delete_session "$sid" >/dev/null; return 1; } + if _dsh_guide "$sid"; then + echo "dsh worker $sid opened DeepSeek Harness's setup guide instead of a composer; deleted it. A person finishes that guide once (start a DeepSeek session from the Run menu), then spawn again" >&2 + delete_session "$sid" >/dev/null; return 1 + fi printf '%s\n' "$sid"; return 0 fi [ "$mode" = claude ] || { printf '%s\n' "$sid"; return 0; } # no other mode draws a composer to wait on diff --git a/plugins/codeman/skills/codeman/reference/verbs.md b/plugins/codeman/skills/codeman/reference/verbs.md index 85968846..709e3b76 100644 --- a/plugins/codeman/skills/codeman/reference/verbs.md +++ b/plugins/codeman/skills/codeman/reference/verbs.md @@ -893,7 +893,7 @@ confirms in the window, and no route this skill calls can do it. | Agent | Token | What was measured | |-------|-------|-------------------| | OpenCode | `┃` | 1.3.0 and 1.19.5: nothing draws it before the composer, no first-run screen, and a prompt pasted at the first `┃` was submitted (2 of 2 on 1.19.5) | - | DeepSeek Harness | `❯` | 0.1.1-rc.2 with dsh-TUI 0.10.0-beta.1: splash, composer and status line arrive in one frame; a prompt sent at the mark was submitted (2 of 2). The harness reports idle about 150 ms BEFORE that frame, so `stop` is no readiness signal | + | DeepSeek Harness | `❯`, then a look at the pane | 0.1.1-rc.2 with dsh-TUI 0.10.0-beta.1: splash, composer and status line arrive in one frame; a prompt sent at the mark was submitted (2 of 2). The harness reports idle about 150 ms BEFORE that frame, so `stop` is no readiness signal. 0.2.2-rc.1 with dsh-TUI 0.19.0: a setup guide ("Step 1 of 4") opens in every session until a person has finished it once, and its cursor is `❯` too (the mark matched on it, 2 of 2), so after the mark the rendered pane is read and a worker that shows the guide is deleted, never handed back; with the guide finished, a prompt sent at the mark was submitted | | Pi | `extended-keys` | 1.5.2: Pi paints its composer rules in its first frame and ignores Enter until startup has finished, so the rule `─` is NOT a mark (a prompt sent at it stayed in the composer, 2 of 2). The last line of its startup is a warning that tmux's `extended-keys` option is off, which it is on Codeman's socket; a prompt sent once that word showed was submitted (4 of 4). A side effect, not a contract: a Pi that stops printing it fails the spawn until `LOCAL_READY_MARK` names what it draws instead | A claude agent takes the claude path instead (hooks vetted, `shift+tab`, the trust diff --git a/skills/codeman/SKILL.md b/skills/codeman/SKILL.md index 3aebe247..c6ffbf58 100644 --- a/skills/codeman/SKILL.md +++ b/skills/codeman/SKILL.md @@ -197,6 +197,17 @@ _dsh_up() { # -> "true"/"false". The DeepSeek Harness T # composer glyph. Override with DSH_READY_MARK for a profile that draws another one. _mark_up "${1}" "${DSH_READY_MARK:-❯}" "${2}" } +_dsh_guide() { # -> 0 when the pane shows dsh-TUI's first-run SETUP GUIDE, not a composer. + # dsh-TUI 0.19.0 opens a guide ("Welcome to dsh-TUI Step 1 of 4") in every session until a + # person has finished it once, and the guide's own cursor is `❯`: the readiness mark matches + # on it (measured, 2 of 2 launches), and an Enter there acts on the highlighted step, which + # is a sign-in. So a worker whose mark matched is still checked for the guide, on the + # RENDERED pane with its whitespace removed, and never handed back while it shows. + "${CURL[@]}" -G "$API/api/v1/sessions/${1}/terminal" --data-urlencode 'full=1' \ + | jq -r '.data.terminalBuffer // empty' \ + | sed -e "s/$(printf '\033')\[[0-9;?]*[a-zA-Z]//g" -e "s/$(printf '\033')[()][AB0]//g" \ + | tr -d ' \t' | grep -qi 'Step[0-9]of[0-9]' +} # ---- the workspace-trust dialog: READ the screen, never press Enter blind ---- # Claude Code 2.1.252 dropped the option numbers, REVERSED them, and highlights # "No, exit" by default: @@ -455,11 +466,17 @@ spawn_worker() { fi if [ -n "$cm" ] && [ "$mode" != claude ]; then # No trust dialog and no hook file to vet. opencode and pi have no end-of-turn signal, - # which is exactly why sendwait refuses them (400) and sendmark drives them; a deepseek - # worker reports the end of a turn on the local model too, so sendwait works for it. + # which is exactly why sendwait refuses them (400) and sendmark drives them. A deepseek + # worker reports the end of a turn on the local model too, but sendmark is still the way + # to get its ANSWER: dsh 0.2.2 renamed its transcript file, and until Codeman reads the + # new one `last-response` comes back empty for it. r=$(_mark_up "$sid" "$mark" 45000) [ "$r" = true ] || { echo "local worker $sid ($mode) never drew '$mark' within 45 s; deleted it. Look at GET /api/v1/local-model and the pane of a hand-made session before retrying" >&2 delete_session "$sid" >/dev/null; return 1; } + if [ "$mode" = deepseek ] && _dsh_guide "$sid"; then + echo "dsh worker $sid opened DeepSeek Harness's setup guide instead of a composer; deleted it. A person finishes that guide once (start a DeepSeek session from the Run menu), then spawn again" >&2 + delete_session "$sid" >/dev/null; return 1 + fi printf '%s\n' "$sid"; return 0 fi if [ "$mode" = deepseek ]; then @@ -475,6 +492,10 @@ spawn_worker() { r=$(_dsh_up "$sid" 45000) [ "$r" = true ] || { echo "dsh worker $sid never drew a composer: no pane-capable profile, a profile whose composer is not '${DSH_READY_MARK:-❯}' (set DSH_READY_MARK), or a harness that failed to boot -- check GET /api/v1/deepseek/status. Deleted it" >&2 delete_session "$sid" >/dev/null; return 1; } + if _dsh_guide "$sid"; then + echo "dsh worker $sid opened DeepSeek Harness's setup guide instead of a composer; deleted it. A person finishes that guide once (start a DeepSeek session from the Run menu), then spawn again" >&2 + delete_session "$sid" >/dev/null; return 1 + fi printf '%s\n' "$sid"; return 0 fi [ "$mode" = claude ] || { printf '%s\n' "$sid"; return 0; } # no other mode draws a composer to wait on @@ -909,10 +930,10 @@ on), and from then on it is the same verbs. It mixes into one fleet like any oth | Worker kind | Agent | Why you would pick it | Drive it with | | --- | --- | --- | --- | -| `local` | the default one (`cli` of `local_model`) | you have no reason to choose | `sendmark` (`sendwait` when it is claude or deepseek) | +| `local` | the default one (`cli` of `local_model`) | you have no reason to choose | `sendmark` (`sendwait` when it is claude) | | `local:opencode` | OpenCode | the tested pair; a full tool set | `sendmark` | | `local:pi` | Pi | the smallest fixed prompt (about 2,100 tokens against 8,000 and more), so the first answer comes soonest and the most context is left for the work | `sendmark` | -| `local:deepseek` | DeepSeek Harness | the one of these that reports the end of a turn | `sendwait` or `sendmark` | +| `local:deepseek` | DeepSeek Harness | the user already works with it | `sendmark` (it reports the end of a turn, but on dsh 0.2.2 its answer cannot be read back through `sendwait` yet) | | `local:claude` | Claude Code | hooks and `sendwait`, at the price of a first answer that takes minutes | `sendwait` | An agent whose status is `installable`, `installing` or `setup-needed` is not startable, and @@ -968,8 +989,14 @@ The limits, before you promise the user anything: loads the model first. A local worker run by claude spends minutes on its first answer (its fixed prompt is about 36,000 tokens). - **Markers, not hooks.** OpenCode and Pi report no end of turn, so `sendwait` answers 400 - for those workers; `sendmark` is the way through. A claude worker has hooks and a - DeepSeek Harness worker reports its turns, so `sendwait` works for those two. + for those workers; `sendmark` is the way through. A claude worker has hooks, so + `sendwait` works for it. A DeepSeek Harness worker reports its turns too, but drive it + with `sendmark` on the local model: dsh 0.2.2 renamed its transcript file, and Codeman + reads an empty answer from it for now. +- **DeepSeek Harness has a setup guide a person finishes once.** Until then every dsh + session opens on it, and the spawn refuses the worker (and deletes it) instead of typing + a task into a sign-in screen. Tell the user to start one DeepSeek session from the Run + menu and finish the guide. - **Pi starts without trusting the folder.** A `local:pi` worker is started with `--no-approve`, so it loads no project extensions or skills from the case folder: on the local model Pi would otherwise ask to trust the folder in every session, and a task diff --git a/skills/codeman/preamble.sh b/skills/codeman/preamble.sh index 88a42ce7..a0fab818 100644 --- a/skills/codeman/preamble.sh +++ b/skills/codeman/preamble.sh @@ -59,6 +59,17 @@ _dsh_up() { # -> "true"/"false". The DeepSeek Harness T # composer glyph. Override with DSH_READY_MARK for a profile that draws another one. _mark_up "${1}" "${DSH_READY_MARK:-❯}" "${2}" } +_dsh_guide() { # -> 0 when the pane shows dsh-TUI's first-run SETUP GUIDE, not a composer. + # dsh-TUI 0.19.0 opens a guide ("Welcome to dsh-TUI Step 1 of 4") in every session until a + # person has finished it once, and the guide's own cursor is `❯`: the readiness mark matches + # on it (measured, 2 of 2 launches), and an Enter there acts on the highlighted step, which + # is a sign-in. So a worker whose mark matched is still checked for the guide, on the + # RENDERED pane with its whitespace removed, and never handed back while it shows. + "${CURL[@]}" -G "$API/api/v1/sessions/${1}/terminal" --data-urlencode 'full=1' \ + | jq -r '.data.terminalBuffer // empty' \ + | sed -e "s/$(printf '\033')\[[0-9;?]*[a-zA-Z]//g" -e "s/$(printf '\033')[()][AB0]//g" \ + | tr -d ' \t' | grep -qi 'Step[0-9]of[0-9]' +} # ---- the workspace-trust dialog: READ the screen, never press Enter blind ---- # Claude Code 2.1.252 dropped the option numbers, REVERSED them, and highlights # "No, exit" by default: @@ -317,11 +328,17 @@ spawn_worker() { fi if [ -n "$cm" ] && [ "$mode" != claude ]; then # No trust dialog and no hook file to vet. opencode and pi have no end-of-turn signal, - # which is exactly why sendwait refuses them (400) and sendmark drives them; a deepseek - # worker reports the end of a turn on the local model too, so sendwait works for it. + # which is exactly why sendwait refuses them (400) and sendmark drives them. A deepseek + # worker reports the end of a turn on the local model too, but sendmark is still the way + # to get its ANSWER: dsh 0.2.2 renamed its transcript file, and until Codeman reads the + # new one `last-response` comes back empty for it. r=$(_mark_up "$sid" "$mark" 45000) [ "$r" = true ] || { echo "local worker $sid ($mode) never drew '$mark' within 45 s; deleted it. Look at GET /api/v1/local-model and the pane of a hand-made session before retrying" >&2 delete_session "$sid" >/dev/null; return 1; } + if [ "$mode" = deepseek ] && _dsh_guide "$sid"; then + echo "dsh worker $sid opened DeepSeek Harness's setup guide instead of a composer; deleted it. A person finishes that guide once (start a DeepSeek session from the Run menu), then spawn again" >&2 + delete_session "$sid" >/dev/null; return 1 + fi printf '%s\n' "$sid"; return 0 fi if [ "$mode" = deepseek ]; then @@ -337,6 +354,10 @@ spawn_worker() { r=$(_dsh_up "$sid" 45000) [ "$r" = true ] || { echo "dsh worker $sid never drew a composer: no pane-capable profile, a profile whose composer is not '${DSH_READY_MARK:-❯}' (set DSH_READY_MARK), or a harness that failed to boot -- check GET /api/v1/deepseek/status. Deleted it" >&2 delete_session "$sid" >/dev/null; return 1; } + if _dsh_guide "$sid"; then + echo "dsh worker $sid opened DeepSeek Harness's setup guide instead of a composer; deleted it. A person finishes that guide once (start a DeepSeek session from the Run menu), then spawn again" >&2 + delete_session "$sid" >/dev/null; return 1 + fi printf '%s\n' "$sid"; return 0 fi [ "$mode" = claude ] || { printf '%s\n' "$sid"; return 0; } # no other mode draws a composer to wait on diff --git a/skills/codeman/reference/verbs.md b/skills/codeman/reference/verbs.md index 85968846..709e3b76 100644 --- a/skills/codeman/reference/verbs.md +++ b/skills/codeman/reference/verbs.md @@ -893,7 +893,7 @@ confirms in the window, and no route this skill calls can do it. | Agent | Token | What was measured | |-------|-------|-------------------| | OpenCode | `┃` | 1.3.0 and 1.19.5: nothing draws it before the composer, no first-run screen, and a prompt pasted at the first `┃` was submitted (2 of 2 on 1.19.5) | - | DeepSeek Harness | `❯` | 0.1.1-rc.2 with dsh-TUI 0.10.0-beta.1: splash, composer and status line arrive in one frame; a prompt sent at the mark was submitted (2 of 2). The harness reports idle about 150 ms BEFORE that frame, so `stop` is no readiness signal | + | DeepSeek Harness | `❯`, then a look at the pane | 0.1.1-rc.2 with dsh-TUI 0.10.0-beta.1: splash, composer and status line arrive in one frame; a prompt sent at the mark was submitted (2 of 2). The harness reports idle about 150 ms BEFORE that frame, so `stop` is no readiness signal. 0.2.2-rc.1 with dsh-TUI 0.19.0: a setup guide ("Step 1 of 4") opens in every session until a person has finished it once, and its cursor is `❯` too (the mark matched on it, 2 of 2), so after the mark the rendered pane is read and a worker that shows the guide is deleted, never handed back; with the guide finished, a prompt sent at the mark was submitted | | Pi | `extended-keys` | 1.5.2: Pi paints its composer rules in its first frame and ignores Enter until startup has finished, so the rule `─` is NOT a mark (a prompt sent at it stayed in the composer, 2 of 2). The last line of its startup is a warning that tmux's `extended-keys` option is off, which it is on Codeman's socket; a prompt sent once that word showed was submitted (4 of 4). A side effect, not a contract: a Pi that stops printing it fails the spawn until `LOCAL_READY_MARK` names what it draws instead | A claude agent takes the claude path instead (hooks vetted, `shift+tab`, the trust diff --git a/src/config/local-model-catalog.ts b/src/config/local-model-catalog.ts index 49fcf1db..91302862 100644 --- a/src/config/local-model-catalog.ts +++ b/src/config/local-model-catalog.ts @@ -83,10 +83,11 @@ * | | | | prompts sent at the rule stayed | * | | | | unsent), and it draws nothing that | * | | | | is a contract once it does | - * | `deepseek` | about 7,650 tokens, 25 tools | SCRIPTED only (0.1.1-rc.2 with dsh-TUI | none here: the CLI registry's `❯` | - * | | (0.1.1-rc.2) | 0.10.0-beta.1, not today's versions). | stands; a prompt sent at it was | - * | | | REAL: the launch and the model name | submitted, 2 of 2 launches | - * | | | reached the runtime, no turn finished | | + * | `deepseek` | about 7,650 tokens, 25 tools | SCRIPTED only, on both versions | none here: the CLI registry's `❯` | + * | | (0.1.1-rc.2); 6,983 tokens, 27 | (0.1.1-rc.2 with dsh-TUI 0.10.0-beta.1 | stands; a prompt sent at it was | + * | | tools (0.2.2-rc.1) | and 0.2.2-rc.1 with dsh-TUI 0.19.0). | submitted, 2 of 2 launches. On | + * | | | REAL: the launch and the model name | dsh-TUI 0.19.0 a setup guide draws | + * | | | reached the runtime, no turn finished | `❯` too until it was finished once | * * Every one of them sent the `name:tag` model id unchanged. `pi` was gated until 2026-11-10 for * two open questions, both answered by its run: the id (above), and the window, which Codeman @@ -94,7 +95,11 @@ * 128,000-token one). `deepseek` is a LAUNCHER: its install command leaves it with nothing to * launch, so src/local-model/harness-facts.ts never reports it installable and a Mac is offered * it only where it was already set up. It is offered on a scripted run and one unfinished real - * one: gate the row again (`gated: true`) should a run on a Mac not finish a turn. + * one: gate the row again (`gated: true`) should a run on a Mac not finish a turn. Two things + * about dsh-TUI 0.19.0 that a client of its `readyMark` must know: until a person has finished + * its setup guide once, every session opens on that guide and the guide's cursor is the mark + * (the codeman skill reads the pane after the mark for that reason), and its transcript file + * was renamed, so Codeman's `last-response` is empty for it for now. * * MEMORY. The need is `bytes + heldBytesPerToken x context + overheadBytes + peakExtraBytes` * (src/local-model/recommend.ts). The two runners spend memory differently, so a row's three diff --git a/test/agent-skill-local-model.test.ts b/test/agent-skill-local-model.test.ts index 167a37f1..a0a961a5 100644 --- a/test/agent-skill-local-model.test.ts +++ b/test/agent-skill-local-model.test.ts @@ -551,6 +551,58 @@ describe.skipIf(!hasJq)('preamble spawn_worker local', () => { expect(run.calls[2].q).toContain('match=❯'); }); + it('a DeepSeek Harness worker that opened its setup guide is deleted, never handed back: its cursor is the mark too', () => { + // dsh-TUI 0.19.0, as the rendered pane reads: the guide, with `❯` on its first step. + const guide = JSON.stringify({ + success: true, + data: { + terminalBuffer: + '\u001b[1mWelcome to dsh-TUI\u001b[0m Step 1 of 4\r\n ✗ No DEEPSEEK_API_KEY yet\r\n❯ Sign in to a subscription (OAuth)\r\n', + }, + }); + const composer = JSON.stringify({ + success: true, + data: { + terminalBuffer: + '╭────╮\r\n│ ❯ Say something, or type / for commands…\r\n╰────╯\r\n qwen3.6:35b-a3b · standard\r\n', + }, + }); + const answers = { 'lm-get': WITH_AGENTS, 'quick-start': QUICK_START, 'wait-output': MATCHED }; + + const blocked = runSkill('spawn_worker rev local:deepseek', { + ...answers, + terminal: guide, + delete: JSON.stringify({ success: true, data: {} }), + }); + expect(blocked.rc).toBe(1); + expect(blocked.stdout).toBe(''); + expect(blocked.stderr).toMatch(/setup guide/); + expect(blocked.calls.map((call) => `${call.m} ${call.key}`)).toEqual([ + 'GET lm-get', + 'POST quick-start', + 'GET wait-output', + 'GET terminal', + 'DELETE delete', + ]); + + const ready = runSkill('spawn_worker rev local:deepseek', { ...answers, terminal: composer }); + expect(ready.stderr).toBe(''); + expect(ready.rc).toBe(0); + expect(ready.stdout).toBe(`${SID}\n`); + + // The same look for a DeepSeek worker that is not on the local model. + const cloud = runSkill('spawn_worker rev deepseek', { + 'quick-start': QUICK_START, + 'wait-output': MATCHED, + terminal: guide, + delete: JSON.stringify({ success: true, data: {} }), + }); + expect(cloud.rc).toBe(1); + expect(cloud.stdout).toBe(''); + expect(cloud.stderr).toMatch(/setup guide/); + expect(cloud.calls.map((call) => call.key)).toEqual(['quick-start', 'wait-output', 'terminal', 'delete']); + }); + it('a mark the server names wins over the one this skill measured, and LOCAL_READY_MARK over both', () => { const named = status({ ...INSTALLED,