diff --git a/VERSION b/VERSION index b616048..844f6a9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.2 +0.6.3 diff --git a/larry.sh b/larry.sh index ca5ec07..edb41c4 100755 --- a/larry.sh +++ b/larry.sh @@ -36,7 +36,7 @@ set -o pipefail # ───────────────────────────────────────────────────────────────────────────── # Config # ───────────────────────────────────────────────────────────────────────────── -LARRY_VERSION="0.6.2" +LARRY_VERSION="0.6.3" LARRY_HOME="${LARRY_HOME:-$HOME/.larry}" LARRY_BASE_URL="${LARRY_BASE_URL:-https://raw.githubusercontent.com/bojj27/cloverleaf-larry/main}" LARRY_UPDATE_URL="${LARRY_UPDATE_URL:-${LARRY_BASE_URL}/larry.sh}" @@ -457,23 +457,37 @@ log_append() { printf '%s\n' "$1" >> "$LOG_FILE"; } # On MobaXterm/Cygwin the bundled jq is a Windows-native binary that can't # resolve Cygwin paths like /home/mobaxterm/... when they come in as argv. # Stdin redirection always works because bash does the path open() itself. +# Each of these passes the value through a tempfile (--rawfile / --slurpfile) +# rather than argv (--arg / --argjson). Argv overflow ("Argument list too +# long") on Cygwin's ~32KB total cap was the v0.6.1 bug for TOOLS_JSON; the +# same pattern applies to any value that could grow with user input or +# assistant output (multi-paragraph prompts, large tool results, etc.). add_user_text() { local content="$1" - local tmp; tmp=$(mktemp) - jq --arg c "$content" '. + [{"role":"user","content":[{"type":"text","text":$c}]}]' < "$MESSAGES_FILE" > "$tmp" \ + local cfile tmp + cfile=$(mktemp); tmp=$(mktemp) + printf '%s' "$content" > "$cfile" + jq --rawfile c "$cfile" '. + [{"role":"user","content":[{"type":"text","text":$c}]}]' < "$MESSAGES_FILE" > "$tmp" \ && mv "$tmp" "$MESSAGES_FILE" + rm -f "$cfile" } add_assistant_blocks() { local blocks="$1" - local tmp; tmp=$(mktemp) - jq --argjson b "$blocks" '. + [{"role":"assistant","content":$b}]' < "$MESSAGES_FILE" > "$tmp" \ + local bfile tmp + bfile=$(mktemp); tmp=$(mktemp) + printf '%s' "$blocks" > "$bfile" + jq --slurpfile b "$bfile" '. + [{"role":"assistant","content":$b[0]}]' < "$MESSAGES_FILE" > "$tmp" \ && mv "$tmp" "$MESSAGES_FILE" + rm -f "$bfile" } add_user_tool_results() { local blocks="$1" - local tmp; tmp=$(mktemp) - jq --argjson b "$blocks" '. + [{"role":"user","content":$b}]' < "$MESSAGES_FILE" > "$tmp" \ + local bfile tmp + bfile=$(mktemp); tmp=$(mktemp) + printf '%s' "$blocks" > "$bfile" + jq --slurpfile b "$bfile" '. + [{"role":"user","content":$b[0]}]' < "$MESSAGES_FILE" > "$tmp" \ && mv "$tmp" "$MESSAGES_FILE" + rm -f "$bfile" } # ───────────────────────────────────────────────────────────────────────────── @@ -1040,17 +1054,19 @@ build_system_prompt() { # ───────────────────────────────────────────────────────────────────────────── agent_turn() { local system_prompt="$1" - # Write the TOOLS_JSON to a file ONCE per agent_turn rather than passing it - # via --argjson (which puts the full 21KB blob on the jq command line and - # blows up Cygwin/Windows argv limits with E2BIG / "Argument list too long"). - local tools_file; tools_file=$(mktemp) - printf '%s' "$TOOLS_JSON" > "$tools_file" + # Write the large blobs to files ONCE per agent_turn rather than passing + # them via --arg / --argjson. Combined budget (TOOLS_JSON ~21KB + system + # prompt ~25KB) easily exceeds Cygwin's ~32KB argv cap → E2BIG. + local tools_file system_file + tools_file=$(mktemp); system_file=$(mktemp) + printf '%s' "$TOOLS_JSON" > "$tools_file" + printf '%s' "$system_prompt" > "$system_file" while true; do local payload_file; payload_file=$(mktemp) jq -n \ --arg model "$LARRY_MODEL" \ --argjson max_tokens "$LARRY_MAX_TOKENS" \ - --arg system "$system_prompt" \ + --rawfile system "$system_file" \ --slurpfile messages "$MESSAGES_FILE" \ --slurpfile tools "$tools_file" \ '{model:$model, max_tokens:$max_tokens, system:$system, messages:$messages[0], tools:$tools[0]}' \ @@ -1059,12 +1075,12 @@ agent_turn() { local resp; resp=$(call_api "$payload_file") rm -f "$payload_file" - if [ -z "$resp" ]; then err "empty response from API (timeout or network?)"; rm -f "$tools_file"; return 1; fi + if [ -z "$resp" ]; then err "empty response from API (timeout or network?)"; rm -f "$tools_file" "$system_file"; return 1; fi local err_type; err_type=$(printf '%s' "$resp" | jq -r '.error.type // empty' 2>/dev/null) if [ -n "$err_type" ]; then err "API error: $err_type — $(printf '%s' "$resp" | jq -r '.error.message // "no message"')" - rm -f "$tools_file" + rm -f "$tools_file" "$system_file" return 1 fi @@ -1100,14 +1116,20 @@ agent_turn() { local result; result=$(execute_tool "$name" "$input_json") log_append '```'; log_append "$result"; log_append '```' + # Tool results can be large (read_file up to 250KB, ssh_exec up to + # 500 lines, etc.) — pass via tempfile, not --arg, to avoid Cygwin + # argv overflow. + local result_file; result_file=$(mktemp) + printf '%s' "$result" > "$result_file" results=$(printf '%s' "$results" | jq \ - --arg id "$tu_id" --arg c "$result" \ + --arg id "$tu_id" --rawfile c "$result_file" \ '. + [{"type":"tool_result","tool_use_id":$id,"content":$c}]') + rm -f "$result_file" done < <(printf '%s' "$resp" | jq -c '.content[] | select(.type=="tool_use")') add_user_tool_results "$results" done - rm -f "$tools_file" + rm -f "$tools_file" "$system_file" } # ─────────────────────────────────────────────────────────────────────────────