diff --git a/plugins/codeman/skills/codeman/SKILL.md b/plugins/codeman/skills/codeman/SKILL.md index 621748ae..c6c85f4a 100644 --- a/plugins/codeman/skills/codeman/SKILL.md +++ b/plugins/codeman/skills/codeman/SKILL.md @@ -348,6 +348,7 @@ sendwait() { case "$p" in *$'\n'*|*$'\r'*) p=$(printf '%s\n' "$p" | tr '\r' '\n' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e '/^$/d' | paste -sd ' ' -) ;; esac + [ -n "$p" ] || { echo "sendwait: empty prompt" >&2; return 1; } # `wait:"stop,exit"`, never the `wait:true` default set: that set also carries # `idle`, which is INFERRED from output stabilization and flaps mid-turn. On a # dsh worker whose TUI repaints rarely the session reads `idle` while the model diff --git a/plugins/codeman/skills/codeman/preamble.sh b/plugins/codeman/skills/codeman/preamble.sh index b6dd5f42..a0e95a32 100644 --- a/plugins/codeman/skills/codeman/preamble.sh +++ b/plugins/codeman/skills/codeman/preamble.sh @@ -247,6 +247,7 @@ sendwait() { case "$p" in *$'\n'*|*$'\r'*) p=$(printf '%s\n' "$p" | tr '\r' '\n' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e '/^$/d' | paste -sd ' ' -) ;; esac + [ -n "$p" ] || { echo "sendwait: empty prompt" >&2; return 1; } # `wait:"stop,exit"`, never the `wait:true` default set: that set also carries # `idle`, which is INFERRED from output stabilization and flaps mid-turn. On a # dsh worker whose TUI repaints rarely the session reads `idle` while the model diff --git a/plugins/codeman/skills/codeman/reference/verbs.md b/plugins/codeman/skills/codeman/reference/verbs.md index a4f43d5a..fa91a749 100644 --- a/plugins/codeman/skills/codeman/reference/verbs.md +++ b/plugins/codeman/skills/codeman/reference/verbs.md @@ -304,11 +304,12 @@ Codeman types the text and sends Enter **only when the input contains a carriage return**; without it your command sits unsubmitted on the worker's prompt and everything downstream times out. No response field catches this: `delivered:true` means "written to the pane", **not** "submitted". With `useMux`, newlines are kept: the -text goes in as one paste, and the `\r` submits all of it. **`sendwait` takes one line, +text goes in as one paste, and the `\r` submits all of it. **`sendwait` sends one line, though:** its Enter loop and the server's submit check cannot reliably recognise a -multi-line paste, so a multi-line prompt to a freshly spawned worker can sit unsent in -Claude Code's Enter-ignore window. Join the lines, or put long multi-line content in a -file ([§5.9](#59-big-input-via-the-workspace)). Build the body with `jq -n` +multi-line paste, so a multi-line prompt to a freshly spawned worker could sit unsent in +Claude Code's Enter-ignore window. `sendwait` therefore joins the lines with a space, +which loses their layout; put long multi-line content in a file +([§5.9](#59-big-input-via-the-workspace)). Build the body with `jq -n` for any prompt you did not author as a literal, because the inline `-d '{"input":"'"$P"'\r"}'` pattern breaks on the first double quote, backslash or `$` in a real prompt: diff --git a/skills/codeman/SKILL.md b/skills/codeman/SKILL.md index 621748ae..c6c85f4a 100644 --- a/skills/codeman/SKILL.md +++ b/skills/codeman/SKILL.md @@ -348,6 +348,7 @@ sendwait() { case "$p" in *$'\n'*|*$'\r'*) p=$(printf '%s\n' "$p" | tr '\r' '\n' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e '/^$/d' | paste -sd ' ' -) ;; esac + [ -n "$p" ] || { echo "sendwait: empty prompt" >&2; return 1; } # `wait:"stop,exit"`, never the `wait:true` default set: that set also carries # `idle`, which is INFERRED from output stabilization and flaps mid-turn. On a # dsh worker whose TUI repaints rarely the session reads `idle` while the model diff --git a/skills/codeman/preamble.sh b/skills/codeman/preamble.sh index b6dd5f42..a0e95a32 100644 --- a/skills/codeman/preamble.sh +++ b/skills/codeman/preamble.sh @@ -247,6 +247,7 @@ sendwait() { case "$p" in *$'\n'*|*$'\r'*) p=$(printf '%s\n' "$p" | tr '\r' '\n' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e '/^$/d' | paste -sd ' ' -) ;; esac + [ -n "$p" ] || { echo "sendwait: empty prompt" >&2; return 1; } # `wait:"stop,exit"`, never the `wait:true` default set: that set also carries # `idle`, which is INFERRED from output stabilization and flaps mid-turn. On a # dsh worker whose TUI repaints rarely the session reads `idle` while the model diff --git a/skills/codeman/reference/verbs.md b/skills/codeman/reference/verbs.md index a4f43d5a..fa91a749 100644 --- a/skills/codeman/reference/verbs.md +++ b/skills/codeman/reference/verbs.md @@ -304,11 +304,12 @@ Codeman types the text and sends Enter **only when the input contains a carriage return**; without it your command sits unsubmitted on the worker's prompt and everything downstream times out. No response field catches this: `delivered:true` means "written to the pane", **not** "submitted". With `useMux`, newlines are kept: the -text goes in as one paste, and the `\r` submits all of it. **`sendwait` takes one line, +text goes in as one paste, and the `\r` submits all of it. **`sendwait` sends one line, though:** its Enter loop and the server's submit check cannot reliably recognise a -multi-line paste, so a multi-line prompt to a freshly spawned worker can sit unsent in -Claude Code's Enter-ignore window. Join the lines, or put long multi-line content in a -file ([§5.9](#59-big-input-via-the-workspace)). Build the body with `jq -n` +multi-line paste, so a multi-line prompt to a freshly spawned worker could sit unsent in +Claude Code's Enter-ignore window. `sendwait` therefore joins the lines with a space, +which loses their layout; put long multi-line content in a file +([§5.9](#59-big-input-via-the-workspace)). Build the body with `jq -n` for any prompt you did not author as a literal, because the inline `-d '{"input":"'"$P"'\r"}'` pattern breaks on the first double quote, backslash or `$` in a real prompt: diff --git a/test/agent-skill-sendwait.test.ts b/test/agent-skill-sendwait.test.ts index d0d45e53..b38d412f 100644 --- a/test/agent-skill-sendwait.test.ts +++ b/test/agent-skill-sendwait.test.ts @@ -47,6 +47,7 @@ function sentInput(prompt: string): string { `; try { execFileSync('bash', ['-c', script], { + stdio: ['ignore', 'pipe', 'pipe'], env: { PATH: process.env.PATH, HOME: process.env.HOME, @@ -74,6 +75,10 @@ describe.skipIf(!hasJq)('preamble sendwait', () => { expect(sentInput(prompt)).toBe(toSingleLine(prompt) + '\r'); }); + it('refuses a prompt made only of blank lines instead of sending a bare Enter', () => { + expect(() => sentInput(' \n \t\n')).toThrow(/sendwait: empty prompt/); + }); + it('leaves a single-line prompt, glob characters included, as it was', () => { expect(sentInput('fix * in ?.ts ')).toBe('fix * in ?.ts \r'); });