refactor(ux): consolidate BMAD skills, update design system, and clean up Prisma generated client
This commit is contained in:
422
.ralph/drivers/DRIVER_INTERFACE.md
Normal file
422
.ralph/drivers/DRIVER_INTERFACE.md
Normal file
@@ -0,0 +1,422 @@
|
||||
# Ralph Driver Interface Contract
|
||||
|
||||
## Overview
|
||||
|
||||
The Ralph loop loads a platform driver by sourcing `ralph/drivers/${PLATFORM_DRIVER}.sh`
|
||||
inside `load_platform_driver()` (`ralph_loop.sh` line 296). The `PLATFORM_DRIVER` variable
|
||||
defaults to `"claude-code"` and can be overridden via `.ralphrc`.
|
||||
|
||||
After sourcing, `ralph_loop.sh` immediately calls three functions to populate core globals:
|
||||
|
||||
1. `driver_valid_tools` -- populates `VALID_TOOL_PATTERNS`
|
||||
2. `driver_cli_binary` -- stored in `CLAUDE_CODE_CMD`
|
||||
3. `driver_display_name` -- stored in `DRIVER_DISPLAY_NAME`
|
||||
|
||||
**File naming convention:** `${PLATFORM_DRIVER}.sh` (e.g., `claude-code.sh`, `codex.sh`).
|
||||
|
||||
**Scope:** This documents the sourceable driver contract used by `ralph_loop.sh`. Helper
|
||||
scripts like `cursor-agent-wrapper.sh` are out of scope.
|
||||
|
||||
**Calling conventions:**
|
||||
|
||||
- Data is returned via stdout (`echo`).
|
||||
- Booleans are returned via exit status (`0` = true, `1` = false).
|
||||
- Some functions mutate global arrays as side effects.
|
||||
|
||||
---
|
||||
|
||||
## Required Hooks
|
||||
|
||||
Called unconditionally by `ralph_loop.sh` with no `declare -F` guard or default stub.
|
||||
Omitting any of these will break the loop at runtime.
|
||||
|
||||
### `driver_name()`
|
||||
|
||||
```bash
|
||||
driver_name()
|
||||
```
|
||||
|
||||
No arguments. Echo a short lowercase identifier (e.g., `"claude-code"`, `"codex"`).
|
||||
Used at line 2382 to gate platform-specific logic.
|
||||
|
||||
### `driver_display_name()`
|
||||
|
||||
```bash
|
||||
driver_display_name()
|
||||
```
|
||||
|
||||
No arguments. Echo a human-readable name (e.g., `"Claude Code"`, `"OpenAI Codex"`).
|
||||
Stored in `DRIVER_DISPLAY_NAME`, used in log messages and tmux pane titles.
|
||||
|
||||
### `driver_cli_binary()`
|
||||
|
||||
```bash
|
||||
driver_cli_binary()
|
||||
```
|
||||
|
||||
No arguments. Echo the CLI executable name or resolved path (e.g., `"claude"`, `"codex"`).
|
||||
Stored in `CLAUDE_CODE_CMD`. Most drivers return a static string; cursor resolves
|
||||
dynamically.
|
||||
|
||||
### `driver_valid_tools()`
|
||||
|
||||
```bash
|
||||
driver_valid_tools()
|
||||
```
|
||||
|
||||
No arguments. Must populate the global `VALID_TOOL_PATTERNS` array with the platform's
|
||||
recognized tool name patterns. Used by `validate_allowed_tools()`.
|
||||
|
||||
### `driver_build_command(prompt_file, loop_context, session_id)`
|
||||
|
||||
```bash
|
||||
driver_build_command "$prompt_file" "$loop_context" "$session_id"
|
||||
```
|
||||
|
||||
Three string arguments:
|
||||
|
||||
| Argument | Description |
|
||||
|----------|-------------|
|
||||
| `$1` prompt_file | Path to the prompt file (e.g., `.ralph/PROMPT.md`) |
|
||||
| `$2` loop_context | Context string for session continuity (may be empty) |
|
||||
| `$3` session_id | Session ID for resume (empty string = new session) |
|
||||
|
||||
Must populate the global `CLAUDE_CMD_ARGS` array with the complete CLI command and
|
||||
arguments. Return `0` on success, `1` on failure (e.g., prompt file not found).
|
||||
|
||||
**Reads globals:** `CLAUDE_OUTPUT_FORMAT`, `CLAUDE_PERMISSION_MODE` (claude-code only),
|
||||
`CLAUDE_ALLOWED_TOOLS` (claude-code only), `CLAUDE_USE_CONTINUE`.
|
||||
|
||||
---
|
||||
|
||||
## Optional Overrides with Loop Defaults
|
||||
|
||||
`ralph_loop.sh` defines default stubs at lines 284 and 288. All existing drivers override
|
||||
them, but a minimal driver can rely on the defaults.
|
||||
|
||||
### `driver_supports_tool_allowlist()`
|
||||
|
||||
```bash
|
||||
driver_supports_tool_allowlist()
|
||||
```
|
||||
|
||||
No arguments. Return `0` if the driver supports `--allowedTools` filtering, `1` otherwise.
|
||||
|
||||
**Default:** returns `1` (false). Currently only `claude-code` returns `0`.
|
||||
|
||||
### `driver_permission_denial_help()`
|
||||
|
||||
```bash
|
||||
driver_permission_denial_help()
|
||||
```
|
||||
|
||||
No arguments. Print platform-specific troubleshooting guidance when the loop detects a
|
||||
permission denial.
|
||||
|
||||
**Reads:** `RALPHRC_FILE`, `DRIVER_DISPLAY_NAME`.
|
||||
|
||||
**Default:** generic guidance text.
|
||||
|
||||
---
|
||||
|
||||
## Optional Capability Hooks
|
||||
|
||||
Guarded by `declare -F` checks or wrapper functions in `ralph_loop.sh` (lines 1917-1954,
|
||||
1576-1583). Safe to omit -- documented fallback behavior applies.
|
||||
|
||||
### `driver_supports_sessions()`
|
||||
|
||||
```bash
|
||||
driver_supports_sessions()
|
||||
```
|
||||
|
||||
No arguments. Return `0` if the driver supports session resume, `1` otherwise.
|
||||
|
||||
**If not defined:** assumed true (`0`).
|
||||
|
||||
Implemented by all 5 drivers; `copilot` returns `1`.
|
||||
|
||||
### `driver_supports_live_output()`
|
||||
|
||||
```bash
|
||||
driver_supports_live_output()
|
||||
```
|
||||
|
||||
No arguments. Return `0` if the driver supports structured streaming output (stream-json
|
||||
or JSONL), `1` otherwise.
|
||||
|
||||
**If not defined:** assumed true (`0`).
|
||||
|
||||
`copilot` returns `1`; all others return `0`.
|
||||
|
||||
### `driver_prepare_live_command()`
|
||||
|
||||
```bash
|
||||
driver_prepare_live_command()
|
||||
```
|
||||
|
||||
No arguments. Transform `CLAUDE_CMD_ARGS` into `LIVE_CMD_ARGS` for streaming mode.
|
||||
|
||||
**If not defined:** `LIVE_CMD_ARGS` is copied from `CLAUDE_CMD_ARGS` unchanged.
|
||||
|
||||
| Driver | Behavior |
|
||||
|--------|----------|
|
||||
| claude-code | Replaces `json` with `stream-json` and adds `--verbose --include-partial-messages` |
|
||||
| codex | Copies as-is (output is already suitable) |
|
||||
| opencode | Copies as-is (output is already suitable) |
|
||||
| cursor | Replaces `json` with `stream-json` |
|
||||
|
||||
### `driver_stream_filter()`
|
||||
|
||||
```bash
|
||||
driver_stream_filter()
|
||||
```
|
||||
|
||||
No arguments. Echo a `jq` filter expression that transforms raw streaming events into
|
||||
displayable text.
|
||||
|
||||
**If not defined:** returns `"empty"` (no output).
|
||||
|
||||
Each driver has a platform-specific filter; `copilot` returns `'.'` (passthrough).
|
||||
|
||||
### `driver_extract_session_id_from_output(output_file)`
|
||||
|
||||
```bash
|
||||
driver_extract_session_id_from_output "$output_file"
|
||||
```
|
||||
|
||||
One argument: path to the CLI output log file. Echo the extracted session ID.
|
||||
|
||||
Tried first in the session save chain before the generic `jq` extractor. Only `opencode`
|
||||
implements this (uses `sed` to extract from a `"session"` JSON object).
|
||||
|
||||
### `driver_fallback_session_id(output_file)`
|
||||
|
||||
```bash
|
||||
driver_fallback_session_id "$output_file"
|
||||
```
|
||||
|
||||
One argument: path to the output file (caller passes it at line 1583; the only
|
||||
implementation in `opencode` ignores it).
|
||||
|
||||
Last-resort session ID recovery when both driver-specific and generic extractors fail.
|
||||
Only `opencode` implements this (queries `opencode session list --format json`).
|
||||
|
||||
---
|
||||
|
||||
## Conventional Metadata Hooks
|
||||
|
||||
Present in every driver but NOT called by `ralph_loop.sh`. Consumed by bmalph's TypeScript
|
||||
doctor/preflight checks in `src/platform/`. A new driver should implement these for
|
||||
`bmalph doctor` compatibility.
|
||||
|
||||
### `driver_min_version()`
|
||||
|
||||
```bash
|
||||
driver_min_version()
|
||||
```
|
||||
|
||||
No arguments. Echo the minimum required CLI version as a semver string.
|
||||
|
||||
### `driver_check_available()`
|
||||
|
||||
```bash
|
||||
driver_check_available()
|
||||
```
|
||||
|
||||
No arguments. Return `0` if the CLI binary is installed and reachable, `1` otherwise.
|
||||
|
||||
---
|
||||
|
||||
## Global Variables
|
||||
|
||||
### Written by drivers
|
||||
|
||||
| Variable | Written by | Type | Description |
|
||||
|----------------------|----------------------------------|-------|------------------------------------------------------|
|
||||
| `VALID_TOOL_PATTERNS`| `driver_valid_tools()` | array | Valid tool name patterns for allowlist validation |
|
||||
| `CLAUDE_CMD_ARGS` | `driver_build_command()` | array | Complete CLI command with all arguments |
|
||||
| `LIVE_CMD_ARGS` | `driver_prepare_live_command()` | array | Modified command for live streaming |
|
||||
|
||||
### Read by drivers (set by ralph_loop.sh or .ralphrc)
|
||||
|
||||
| Variable | Used in | Description |
|
||||
|-------------------------|--------------------------------------------|------------------------------------------------|
|
||||
| `CLAUDE_OUTPUT_FORMAT` | `driver_build_command()` | `"json"` or `"text"` |
|
||||
| `CLAUDE_PERMISSION_MODE`| `driver_build_command()` (claude-code) | Permission mode flag, default `"bypassPermissions"` |
|
||||
| `CLAUDE_ALLOWED_TOOLS` | `driver_build_command()` (claude-code) | Comma-separated tool allowlist |
|
||||
| `CLAUDE_USE_CONTINUE` | `driver_build_command()` | `"true"` or `"false"`, gates session resume |
|
||||
| `RALPHRC_FILE` | `driver_permission_denial_help()` | Path to `.ralphrc` config file |
|
||||
| `DRIVER_DISPLAY_NAME` | `driver_permission_denial_help()` | Human-readable driver name |
|
||||
|
||||
### Environment globals (cursor-specific)
|
||||
|
||||
| Variable | Used in | Description |
|
||||
|---------------|--------------------------------------|-------------------------------------|
|
||||
| `OS`, `OSTYPE`| `driver_running_on_windows()` | OS detection |
|
||||
| `LOCALAPPDATA`| `driver_localappdata_cli_binary()` | Windows local app data path |
|
||||
| `PATH` | `driver_find_windows_path_candidate()`| Manual PATH scanning on Windows |
|
||||
|
||||
### Set by ralph_loop.sh from driver output
|
||||
|
||||
| Variable | Source | Description |
|
||||
|----------------------|-------------------------|-------------------------------|
|
||||
| `CLAUDE_CODE_CMD` | `driver_cli_binary()` | CLI binary name/path |
|
||||
| `DRIVER_DISPLAY_NAME`| `driver_display_name()` | Human-readable display name |
|
||||
|
||||
---
|
||||
|
||||
## Capability Matrix
|
||||
|
||||
| Capability | claude-code | codex | opencode | copilot | cursor |
|
||||
|---------------------------------------------------------|:-----------:|:-----------:|:-----------:|:-----------:|:-----------:|
|
||||
| Tool allowlist (`driver_supports_tool_allowlist`) | yes | no | no | no | no |
|
||||
| Session continuity (`driver_supports_sessions`) | yes | yes | yes | no | yes |
|
||||
| Structured live output (`driver_supports_live_output`) | yes | yes | yes | no | yes |
|
||||
| Live command transform (`driver_prepare_live_command`) | transform | passthrough | passthrough | -- | transform |
|
||||
| Stream filter (`driver_stream_filter`) | complex jq | JSONL select| JSONL select| passthrough | complex jq |
|
||||
| Custom session extraction (`driver_extract_session_id_from_output`) | -- | -- | yes | -- | -- |
|
||||
| Fallback session lookup (`driver_fallback_session_id`) | -- | -- | yes | -- | -- |
|
||||
| Dynamic binary resolution (`driver_cli_binary`) | static | static | static | static | dynamic |
|
||||
|
||||
---
|
||||
|
||||
## Creating a New Driver
|
||||
|
||||
### Minimal driver skeleton
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# ralph/drivers/my-platform.sh
|
||||
# Driver for My Platform CLI
|
||||
#
|
||||
# Sourced by ralph_loop.sh via load_platform_driver().
|
||||
# PLATFORM_DRIVER must be set to "my-platform" in .ralphrc.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Required hooks (5) -- omitting any of these breaks the loop
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Short lowercase identifier used to gate platform-specific logic.
|
||||
driver_name() {
|
||||
echo "my-platform"
|
||||
}
|
||||
|
||||
# Human-readable name for log messages and tmux pane titles.
|
||||
driver_display_name() {
|
||||
echo "My Platform"
|
||||
}
|
||||
|
||||
# CLI executable name or resolved path.
|
||||
driver_cli_binary() {
|
||||
echo "my-platform"
|
||||
}
|
||||
|
||||
# Populate VALID_TOOL_PATTERNS with recognized tool name patterns.
|
||||
# Used by validate_allowed_tools() to check allowlist entries.
|
||||
driver_valid_tools() {
|
||||
VALID_TOOL_PATTERNS=(
|
||||
"Read"
|
||||
"Write"
|
||||
"Edit"
|
||||
"Bash"
|
||||
# Add your platform's tool patterns here
|
||||
)
|
||||
}
|
||||
|
||||
# Build the complete CLI command array.
|
||||
# $1 = prompt_file Path to .ralph/PROMPT.md
|
||||
# $2 = loop_context Context string for session continuity (may be empty)
|
||||
# $3 = session_id Session ID for resume (empty = new session)
|
||||
driver_build_command() {
|
||||
local prompt_file="$1"
|
||||
local loop_context="$2"
|
||||
local session_id="$3"
|
||||
|
||||
if [[ ! -f "$prompt_file" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
CLAUDE_CMD_ARGS=(
|
||||
"my-platform"
|
||||
"--prompt" "$prompt_file"
|
||||
"--output-format" "${CLAUDE_OUTPUT_FORMAT:-json}"
|
||||
)
|
||||
|
||||
# Append session resume flag if continuing a session
|
||||
if [[ "$CLAUDE_USE_CONTINUE" == "true" && -n "$session_id" ]]; then
|
||||
CLAUDE_CMD_ARGS+=("--session" "$session_id")
|
||||
fi
|
||||
|
||||
# Append context if provided
|
||||
if [[ -n "$loop_context" ]]; then
|
||||
CLAUDE_CMD_ARGS+=("--context" "$loop_context")
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Optional overrides (2) -- loop provides default stubs
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Return 0 if the platform supports --allowedTools filtering, 1 otherwise.
|
||||
driver_supports_tool_allowlist() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# Print troubleshooting guidance on permission denial.
|
||||
driver_permission_denial_help() {
|
||||
echo "Permission denied. Check that $DRIVER_DISPLAY_NAME has the required permissions."
|
||||
echo "See $RALPHRC_FILE for configuration options."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Metadata hooks (2) -- used by bmalph doctor, not called by ralph_loop.sh
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Minimum required CLI version (semver).
|
||||
driver_min_version() {
|
||||
echo "1.0.0"
|
||||
}
|
||||
|
||||
# Return 0 if the CLI binary is installed and reachable, 1 otherwise.
|
||||
driver_check_available() {
|
||||
command -v my-platform &>/dev/null
|
||||
}
|
||||
```
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] All 5 required hooks implemented (`driver_name`, `driver_display_name`,
|
||||
`driver_cli_binary`, `driver_valid_tools`, `driver_build_command`)
|
||||
- [ ] `driver_valid_tools` populates `VALID_TOOL_PATTERNS` with your platform's tool names
|
||||
- [ ] `driver_build_command` handles all three arguments correctly
|
||||
(`prompt_file`, `loop_context`, `session_id`)
|
||||
- [ ] `driver_check_available` returns `0` only when the CLI is installed
|
||||
- [ ] File named `${platform_id}.sh` matching the `PLATFORM_DRIVER` value in `.ralphrc`
|
||||
- [ ] Register corresponding platform definition in `src/platform/` for bmalph CLI integration
|
||||
- [ ] Tested with `bmalph doctor`
|
||||
|
||||
---
|
||||
|
||||
## Session ID Recovery Chain
|
||||
|
||||
When the loop needs to persist a session ID for resume, it follows a three-step priority
|
||||
chain (`ralph_loop.sh` lines 1574-1588):
|
||||
|
||||
1. **`driver_extract_session_id_from_output($output_file)`** -- Driver-specific extraction.
|
||||
If the function exists (`declare -F` guard) and echoes a non-empty string, that value
|
||||
is used. Only `opencode` implements this (uses `sed` to extract from a `"session"` JSON
|
||||
object).
|
||||
|
||||
2. **`extract_session_id_from_output($output_file)`** -- Generic `jq` extractor from
|
||||
`response_analyzer.sh`. Searches the output file for `.sessionId`,
|
||||
`.metadata.session_id`, and `.session_id` in that order.
|
||||
|
||||
3. **`driver_fallback_session_id($output_file)`** -- CLI-based last-resort recovery. If the
|
||||
function exists and the previous steps produced nothing, this is called. Only `opencode`
|
||||
implements this (queries `opencode session list --format json`).
|
||||
|
||||
The first step that returns a non-empty string wins. If all three steps fail, no session ID
|
||||
is saved and the next iteration starts a fresh session.
|
||||
187
.ralph/drivers/claude-code.sh
Executable file
187
.ralph/drivers/claude-code.sh
Executable file
@@ -0,0 +1,187 @@
|
||||
#!/bin/bash
|
||||
# Claude Code driver for Ralph
|
||||
# Provides platform-specific CLI invocation logic
|
||||
|
||||
# Driver identification
|
||||
driver_name() {
|
||||
echo "claude-code"
|
||||
}
|
||||
|
||||
driver_display_name() {
|
||||
echo "Claude Code"
|
||||
}
|
||||
|
||||
driver_cli_binary() {
|
||||
echo "claude"
|
||||
}
|
||||
|
||||
driver_min_version() {
|
||||
echo "2.0.76"
|
||||
}
|
||||
|
||||
# Check if the CLI binary is available
|
||||
driver_check_available() {
|
||||
command -v "$(driver_cli_binary)" &>/dev/null
|
||||
}
|
||||
|
||||
# Valid tool patterns for --allowedTools validation
|
||||
# Sets the global VALID_TOOL_PATTERNS array
|
||||
driver_valid_tools() {
|
||||
VALID_TOOL_PATTERNS=(
|
||||
"Write"
|
||||
"Read"
|
||||
"Edit"
|
||||
"MultiEdit"
|
||||
"Glob"
|
||||
"Grep"
|
||||
"Task"
|
||||
"TodoWrite"
|
||||
"WebFetch"
|
||||
"WebSearch"
|
||||
"AskUserQuestion"
|
||||
"EnterPlanMode"
|
||||
"ExitPlanMode"
|
||||
"Bash"
|
||||
"Bash(git *)"
|
||||
"Bash(npm *)"
|
||||
"Bash(bats *)"
|
||||
"Bash(python *)"
|
||||
"Bash(node *)"
|
||||
"NotebookEdit"
|
||||
)
|
||||
}
|
||||
|
||||
driver_supports_tool_allowlist() {
|
||||
return 0
|
||||
}
|
||||
|
||||
driver_permission_denial_help() {
|
||||
echo " 1. Edit $RALPHRC_FILE and keep CLAUDE_PERMISSION_MODE=bypassPermissions for unattended Claude Code loops"
|
||||
echo " 2. If Claude was denied on an interactive approval step, ALLOWED_TOOLS will not fix it"
|
||||
echo " 3. If Claude was denied on a normal tool, update ALLOWED_TOOLS to include the required tools"
|
||||
echo " 4. Common ALLOWED_TOOLS patterns:"
|
||||
echo " - Bash - All shell commands"
|
||||
echo " - Bash(node *) - All Node.js commands"
|
||||
echo " - Bash(npm *) - All npm commands"
|
||||
echo " - Bash(pnpm *) - All pnpm commands"
|
||||
echo " - AskUserQuestion - Allow interactive clarification when you want pauses"
|
||||
echo ""
|
||||
echo "After updating $RALPHRC_FILE:"
|
||||
echo " bash .ralph/ralph_loop.sh --reset-session # Clear stale session state"
|
||||
echo " bmalph run # Restart the loop"
|
||||
}
|
||||
|
||||
# Build the CLI command arguments
|
||||
# Populates global CLAUDE_CMD_ARGS array
|
||||
# Parameters:
|
||||
# $1 - prompt_file: path to the prompt file
|
||||
# $2 - loop_context: context string for session continuity
|
||||
# $3 - session_id: session ID for resume (empty for new session)
|
||||
driver_build_command() {
|
||||
local prompt_file=$1
|
||||
local loop_context=$2
|
||||
local session_id=$3
|
||||
local resolved_permission_mode="${CLAUDE_PERMISSION_MODE:-bypassPermissions}"
|
||||
|
||||
# Note: We do NOT use --dangerously-skip-permissions here. Tool permissions
|
||||
# are controlled via --allowedTools from CLAUDE_ALLOWED_TOOLS in .ralphrc.
|
||||
# This preserves the permission denial circuit breaker (Issue #101).
|
||||
CLAUDE_CMD_ARGS=("$(driver_cli_binary)")
|
||||
|
||||
if [[ ! -f "$prompt_file" ]]; then
|
||||
echo "ERROR: Prompt file not found: $prompt_file" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Output format
|
||||
if [[ "$CLAUDE_OUTPUT_FORMAT" == "json" ]]; then
|
||||
CLAUDE_CMD_ARGS+=("--output-format" "json")
|
||||
fi
|
||||
|
||||
# Prevent interactive approval flows from blocking unattended -p loops.
|
||||
CLAUDE_CMD_ARGS+=("--permission-mode" "$resolved_permission_mode")
|
||||
|
||||
# Allowed tools
|
||||
if [[ -n "$CLAUDE_ALLOWED_TOOLS" ]]; then
|
||||
CLAUDE_CMD_ARGS+=("--allowedTools")
|
||||
local IFS=','
|
||||
read -ra tools_array <<< "$CLAUDE_ALLOWED_TOOLS"
|
||||
for tool in "${tools_array[@]}"; do
|
||||
tool=$(echo "$tool" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
if [[ -n "$tool" ]]; then
|
||||
CLAUDE_CMD_ARGS+=("$tool")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Session resume
|
||||
# IMPORTANT: Use --resume with explicit session ID instead of --continue.
|
||||
# --continue resumes the "most recent session in current directory" which
|
||||
# can hijack active Claude Code sessions. --resume with a specific session ID
|
||||
# ensures we only resume Ralph's own sessions. (Issue #151)
|
||||
if [[ "$CLAUDE_USE_CONTINUE" == "true" && -n "$session_id" ]]; then
|
||||
CLAUDE_CMD_ARGS+=("--resume" "$session_id")
|
||||
fi
|
||||
|
||||
# Loop context as system prompt
|
||||
if [[ -n "$loop_context" ]]; then
|
||||
CLAUDE_CMD_ARGS+=("--append-system-prompt" "$loop_context")
|
||||
fi
|
||||
|
||||
# Prompt content
|
||||
local prompt_content
|
||||
prompt_content=$(cat "$prompt_file")
|
||||
CLAUDE_CMD_ARGS+=("-p" "$prompt_content")
|
||||
}
|
||||
|
||||
# Whether this driver supports session continuity
|
||||
driver_supports_sessions() {
|
||||
return 0 # true
|
||||
}
|
||||
|
||||
# Claude Code supports stream-json live output.
|
||||
driver_supports_live_output() {
|
||||
return 0 # true
|
||||
}
|
||||
|
||||
# Prepare command arguments for live stream-json output.
|
||||
driver_prepare_live_command() {
|
||||
LIVE_CMD_ARGS=()
|
||||
local skip_next=false
|
||||
|
||||
for arg in "${CLAUDE_CMD_ARGS[@]}"; do
|
||||
if [[ "$skip_next" == "true" ]]; then
|
||||
LIVE_CMD_ARGS+=("stream-json")
|
||||
skip_next=false
|
||||
elif [[ "$arg" == "--output-format" ]]; then
|
||||
LIVE_CMD_ARGS+=("$arg")
|
||||
skip_next=true
|
||||
else
|
||||
LIVE_CMD_ARGS+=("$arg")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$skip_next" == "true" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
LIVE_CMD_ARGS+=("--verbose" "--include-partial-messages")
|
||||
}
|
||||
|
||||
# Stream filter for raw Claude stream-json events.
|
||||
driver_stream_filter() {
|
||||
echo '
|
||||
if .type == "stream_event" then
|
||||
if .event.type == "content_block_delta" and .event.delta.type == "text_delta" then
|
||||
.event.delta.text
|
||||
elif .event.type == "content_block_start" and .event.content_block.type == "tool_use" then
|
||||
"\n\n⚡ [" + .event.content_block.name + "]\n"
|
||||
elif .event.type == "content_block_stop" then
|
||||
"\n"
|
||||
else
|
||||
empty
|
||||
end
|
||||
else
|
||||
empty
|
||||
end'
|
||||
}
|
||||
101
.ralph/drivers/codex.sh
Executable file
101
.ralph/drivers/codex.sh
Executable file
@@ -0,0 +1,101 @@
|
||||
#!/bin/bash
|
||||
# OpenAI Codex driver for Ralph
|
||||
# Provides platform-specific CLI invocation logic for Codex
|
||||
|
||||
driver_name() {
|
||||
echo "codex"
|
||||
}
|
||||
|
||||
driver_display_name() {
|
||||
echo "OpenAI Codex"
|
||||
}
|
||||
|
||||
driver_cli_binary() {
|
||||
echo "codex"
|
||||
}
|
||||
|
||||
driver_min_version() {
|
||||
echo "0.1.0"
|
||||
}
|
||||
|
||||
driver_check_available() {
|
||||
command -v "$(driver_cli_binary)" &>/dev/null
|
||||
}
|
||||
|
||||
# Codex tool names differ from Claude Code
|
||||
driver_valid_tools() {
|
||||
VALID_TOOL_PATTERNS=(
|
||||
"shell"
|
||||
"read_file"
|
||||
"write_file"
|
||||
"edit_file"
|
||||
"list_directory"
|
||||
"search_files"
|
||||
)
|
||||
}
|
||||
|
||||
driver_supports_tool_allowlist() {
|
||||
return 1
|
||||
}
|
||||
|
||||
driver_permission_denial_help() {
|
||||
echo " - $DRIVER_DISPLAY_NAME uses its native sandbox and approval model."
|
||||
echo " - ALLOWED_TOOLS in $RALPHRC_FILE is ignored for this driver."
|
||||
echo " - Ralph already runs Codex with --sandbox workspace-write."
|
||||
echo " - Review Codex approval settings, then restart the loop."
|
||||
}
|
||||
|
||||
# Build Codex CLI command
|
||||
# Codex uses: codex exec [resume <id>] --json "prompt"
|
||||
driver_build_command() {
|
||||
local prompt_file=$1
|
||||
local loop_context=$2
|
||||
local session_id=$3
|
||||
|
||||
CLAUDE_CMD_ARGS=("$(driver_cli_binary)" "exec")
|
||||
|
||||
if [[ ! -f "$prompt_file" ]]; then
|
||||
echo "ERROR: Prompt file not found: $prompt_file" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# JSON output
|
||||
CLAUDE_CMD_ARGS+=("--json")
|
||||
|
||||
# Sandbox mode - workspace write access
|
||||
CLAUDE_CMD_ARGS+=("--sandbox" "workspace-write")
|
||||
|
||||
# Session resume — gated on CLAUDE_USE_CONTINUE to respect --no-continue flag
|
||||
if [[ "$CLAUDE_USE_CONTINUE" == "true" && -n "$session_id" ]]; then
|
||||
CLAUDE_CMD_ARGS+=("resume" "$session_id")
|
||||
fi
|
||||
|
||||
# Build prompt with context
|
||||
local prompt_content
|
||||
prompt_content=$(cat "$prompt_file")
|
||||
if [[ -n "$loop_context" ]]; then
|
||||
prompt_content="$loop_context
|
||||
|
||||
$prompt_content"
|
||||
fi
|
||||
|
||||
CLAUDE_CMD_ARGS+=("$prompt_content")
|
||||
}
|
||||
|
||||
driver_supports_sessions() {
|
||||
return 0 # true - Codex supports session resume
|
||||
}
|
||||
|
||||
# Codex JSONL output is already suitable for live display.
|
||||
driver_supports_live_output() {
|
||||
return 0 # true
|
||||
}
|
||||
|
||||
driver_prepare_live_command() {
|
||||
LIVE_CMD_ARGS=("${CLAUDE_CMD_ARGS[@]}")
|
||||
}
|
||||
|
||||
# Codex outputs JSONL events
|
||||
driver_stream_filter() {
|
||||
echo 'select(.type == "item.completed" and .item.type == "agent_message") | (.item.text // ([.item.content[]? | select(.type == "output_text") | .text] | join("\n")) // empty)'
|
||||
}
|
||||
105
.ralph/drivers/copilot.sh
Executable file
105
.ralph/drivers/copilot.sh
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/bin/bash
|
||||
# GitHub Copilot CLI driver for Ralph (EXPERIMENTAL)
|
||||
# Provides platform-specific CLI invocation logic for Copilot CLI.
|
||||
#
|
||||
# Known limitations:
|
||||
# - No session continuity (session IDs not capturable from -p output)
|
||||
# - No structured output (plain text only, no --json flag)
|
||||
# - Coarse tool permissions (only shell, shell(git:*), shell(npm:*), write)
|
||||
# - CLI is new (GA Feb 25, 2026) — scripting interface may change
|
||||
|
||||
driver_name() {
|
||||
echo "copilot"
|
||||
}
|
||||
|
||||
driver_display_name() {
|
||||
echo "GitHub Copilot CLI"
|
||||
}
|
||||
|
||||
driver_cli_binary() {
|
||||
echo "copilot"
|
||||
}
|
||||
|
||||
driver_min_version() {
|
||||
echo "0.0.418"
|
||||
}
|
||||
|
||||
driver_check_available() {
|
||||
command -v "$(driver_cli_binary)" &>/dev/null
|
||||
}
|
||||
|
||||
# Copilot CLI tool names
|
||||
driver_valid_tools() {
|
||||
VALID_TOOL_PATTERNS=(
|
||||
"shell"
|
||||
"shell(git:*)"
|
||||
"shell(npm:*)"
|
||||
"write"
|
||||
)
|
||||
}
|
||||
|
||||
driver_supports_tool_allowlist() {
|
||||
return 1
|
||||
}
|
||||
|
||||
driver_permission_denial_help() {
|
||||
echo " - $DRIVER_DISPLAY_NAME uses its own autonomy and approval controls."
|
||||
echo " - ALLOWED_TOOLS in $RALPHRC_FILE is ignored for this driver."
|
||||
echo " - Ralph already runs Copilot with --no-ask-user for unattended mode."
|
||||
echo " - Review Copilot CLI permissions, then restart the loop."
|
||||
}
|
||||
|
||||
# Build Copilot CLI command
|
||||
# Context is prepended to the prompt (same pattern as Codex driver).
|
||||
# Uses --autopilot --yolo for autonomous mode, -s to strip stats, -p for prompt.
|
||||
driver_build_command() {
|
||||
local prompt_file=$1
|
||||
local loop_context=$2
|
||||
# $3 (session_id) is intentionally ignored — Copilot CLI does not
|
||||
# expose session IDs in -p output, so resume is not possible.
|
||||
|
||||
CLAUDE_CMD_ARGS=("$(driver_cli_binary)")
|
||||
|
||||
if [[ ! -f "$prompt_file" ]]; then
|
||||
echo "ERROR: Prompt file not found: $prompt_file" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Autonomous execution flags
|
||||
CLAUDE_CMD_ARGS+=("--autopilot" "--yolo")
|
||||
|
||||
# Limit auto-continuation loops
|
||||
CLAUDE_CMD_ARGS+=("--max-autopilot-continues" "50")
|
||||
|
||||
# Disable interactive prompts
|
||||
CLAUDE_CMD_ARGS+=("--no-ask-user")
|
||||
|
||||
# Strip stats for cleaner output
|
||||
CLAUDE_CMD_ARGS+=("-s")
|
||||
|
||||
# Build prompt with context prepended
|
||||
local prompt_content
|
||||
prompt_content=$(cat "$prompt_file")
|
||||
if [[ -n "$loop_context" ]]; then
|
||||
prompt_content="$loop_context
|
||||
|
||||
$prompt_content"
|
||||
fi
|
||||
|
||||
CLAUDE_CMD_ARGS+=("-p" "$prompt_content")
|
||||
}
|
||||
|
||||
driver_supports_sessions() {
|
||||
return 1 # false — session IDs not capturable from -p output
|
||||
}
|
||||
|
||||
# Copilot CLI does not expose structured live output for jq streaming.
|
||||
driver_supports_live_output() {
|
||||
return 1 # false
|
||||
}
|
||||
|
||||
# Copilot CLI outputs plain text only (no JSON streaming).
|
||||
# Passthrough filter — no transformation needed.
|
||||
driver_stream_filter() {
|
||||
echo '.'
|
||||
}
|
||||
13
.ralph/drivers/cursor-agent-wrapper.sh
Executable file
13
.ralph/drivers/cursor-agent-wrapper.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
# Wrap Windows .cmd execution so GNU timeout launches a bash script instead of the .cmd directly.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cli_path=${1:-}
|
||||
if [[ -z "$cli_path" ]]; then
|
||||
echo "ERROR: Missing Cursor CLI path" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
shift
|
||||
exec "$cli_path" "$@"
|
||||
283
.ralph/drivers/cursor.sh
Executable file
283
.ralph/drivers/cursor.sh
Executable file
@@ -0,0 +1,283 @@
|
||||
#!/bin/bash
|
||||
# Cursor CLI driver for Ralph
|
||||
# Uses the documented cursor-agent contract for background execution and
|
||||
# switches to stream-json only for live display paths.
|
||||
|
||||
driver_name() {
|
||||
echo "cursor"
|
||||
}
|
||||
|
||||
driver_display_name() {
|
||||
echo "Cursor CLI"
|
||||
}
|
||||
|
||||
driver_cli_binary() {
|
||||
local binary
|
||||
binary=$(driver_resolve_cli_binary)
|
||||
|
||||
if [[ -n "$binary" ]]; then
|
||||
echo "$binary"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "cursor-agent"
|
||||
}
|
||||
|
||||
driver_min_version() {
|
||||
echo "0.1.0"
|
||||
}
|
||||
|
||||
driver_check_available() {
|
||||
local cli_binary
|
||||
cli_binary=$(driver_cli_binary)
|
||||
|
||||
if [[ -f "$cli_binary" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
command -v "$cli_binary" &>/dev/null
|
||||
}
|
||||
|
||||
driver_valid_tools() {
|
||||
VALID_TOOL_PATTERNS=(
|
||||
"file_edit"
|
||||
"file_read"
|
||||
"file_write"
|
||||
"terminal"
|
||||
"search"
|
||||
)
|
||||
}
|
||||
|
||||
driver_supports_tool_allowlist() {
|
||||
return 1
|
||||
}
|
||||
|
||||
driver_permission_denial_help() {
|
||||
echo " - $DRIVER_DISPLAY_NAME uses its native permission model."
|
||||
echo " - ALLOWED_TOOLS in $RALPHRC_FILE is ignored for this driver."
|
||||
echo " - Ralph already runs Cursor with --force."
|
||||
echo " - Review Cursor permissions or approval settings, then restart the loop."
|
||||
}
|
||||
|
||||
driver_build_command() {
|
||||
local prompt_file=$1
|
||||
local loop_context=$2
|
||||
local session_id=$3
|
||||
local cli_binary
|
||||
cli_binary=$(driver_cli_binary)
|
||||
|
||||
if [[ ! -f "$prompt_file" ]]; then
|
||||
echo "ERROR: Prompt file not found: $prompt_file" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
CLAUDE_CMD_ARGS=()
|
||||
if [[ "$cli_binary" == *.cmd ]]; then
|
||||
CLAUDE_CMD_ARGS+=("$(driver_wrapper_path)" "$cli_binary")
|
||||
else
|
||||
CLAUDE_CMD_ARGS+=("$cli_binary")
|
||||
fi
|
||||
|
||||
CLAUDE_CMD_ARGS+=("-p" "--force" "--output-format" "json")
|
||||
|
||||
if [[ "$CLAUDE_USE_CONTINUE" == "true" && -n "$session_id" ]]; then
|
||||
CLAUDE_CMD_ARGS+=("--resume" "$session_id")
|
||||
fi
|
||||
|
||||
local prompt_content
|
||||
if driver_running_on_windows; then
|
||||
prompt_content=$(driver_build_windows_bootstrap_prompt "$loop_context" "$prompt_file")
|
||||
else
|
||||
prompt_content=$(cat "$prompt_file")
|
||||
if [[ -n "$loop_context" ]]; then
|
||||
prompt_content="$loop_context
|
||||
|
||||
$prompt_content"
|
||||
fi
|
||||
fi
|
||||
|
||||
CLAUDE_CMD_ARGS+=("$prompt_content")
|
||||
}
|
||||
|
||||
driver_supports_sessions() {
|
||||
return 0
|
||||
}
|
||||
|
||||
driver_supports_live_output() {
|
||||
return 0
|
||||
}
|
||||
|
||||
driver_prepare_live_command() {
|
||||
LIVE_CMD_ARGS=()
|
||||
local skip_next=false
|
||||
|
||||
for arg in "${CLAUDE_CMD_ARGS[@]}"; do
|
||||
if [[ "$skip_next" == "true" ]]; then
|
||||
LIVE_CMD_ARGS+=("stream-json")
|
||||
skip_next=false
|
||||
elif [[ "$arg" == "--output-format" ]]; then
|
||||
LIVE_CMD_ARGS+=("$arg")
|
||||
skip_next=true
|
||||
else
|
||||
LIVE_CMD_ARGS+=("$arg")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$skip_next" == "true" ]]; then
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
driver_stream_filter() {
|
||||
echo '
|
||||
if .type == "assistant" then
|
||||
[(.message.content[]? | select(.type == "text") | .text)] | join("\n")
|
||||
elif .type == "tool_call" then
|
||||
"\n\n⚡ [" + (.tool_call.name // .name // "tool_call") + "]\n"
|
||||
else
|
||||
empty
|
||||
end'
|
||||
}
|
||||
|
||||
driver_running_on_windows() {
|
||||
[[ "${OS:-}" == "Windows_NT" || "${OSTYPE:-}" == msys* || "${OSTYPE:-}" == cygwin* || "${OSTYPE:-}" == win32* ]]
|
||||
}
|
||||
|
||||
driver_resolve_cli_binary() {
|
||||
local candidate
|
||||
local resolved
|
||||
local fallback
|
||||
local candidates=(
|
||||
"cursor-agent"
|
||||
"cursor-agent.cmd"
|
||||
"agent"
|
||||
"agent.cmd"
|
||||
)
|
||||
|
||||
for candidate in "${candidates[@]}"; do
|
||||
resolved=$(driver_lookup_cli_candidate "$candidate")
|
||||
if [[ -n "$resolved" ]]; then
|
||||
echo "$resolved"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
fallback=$(driver_localappdata_cli_binary)
|
||||
if [[ -n "$fallback" ]]; then
|
||||
echo "$fallback"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
driver_lookup_cli_candidate() {
|
||||
local candidate=$1
|
||||
local resolved
|
||||
|
||||
resolved=$(command -v "$candidate" 2>/dev/null || true)
|
||||
if [[ -n "$resolved" ]]; then
|
||||
echo "$resolved"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! driver_running_on_windows; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
driver_find_windows_path_candidate "$candidate"
|
||||
}
|
||||
|
||||
driver_find_windows_path_candidate() {
|
||||
local candidate=$1
|
||||
local path_entry
|
||||
local normalized_entry
|
||||
local resolved_candidate
|
||||
local path_entries="${PATH:-}"
|
||||
local -a path_parts=()
|
||||
|
||||
if [[ "$path_entries" == *";"* ]]; then
|
||||
IFS=';' read -r -a path_parts <<< "$path_entries"
|
||||
else
|
||||
IFS=':' read -r -a path_parts <<< "$path_entries"
|
||||
fi
|
||||
|
||||
for path_entry in "${path_parts[@]}"; do
|
||||
[[ -z "$path_entry" ]] && continue
|
||||
|
||||
normalized_entry=$path_entry
|
||||
if command -v cygpath &>/dev/null && [[ "$normalized_entry" =~ ^[A-Za-z]:\\ ]]; then
|
||||
normalized_entry=$(cygpath -u "$normalized_entry")
|
||||
fi
|
||||
|
||||
resolved_candidate="$normalized_entry/$candidate"
|
||||
if [[ -f "$resolved_candidate" ]]; then
|
||||
echo "$resolved_candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
driver_localappdata_cli_binary() {
|
||||
local local_app_data="${LOCALAPPDATA:-}"
|
||||
|
||||
if [[ -z "$local_app_data" ]] || ! driver_running_on_windows; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v cygpath &>/dev/null && [[ "$local_app_data" =~ ^[A-Za-z]:\\ ]]; then
|
||||
local_app_data=$(cygpath -u "$local_app_data")
|
||||
fi
|
||||
|
||||
local candidates=(
|
||||
"$local_app_data/cursor-agent/cursor-agent.cmd"
|
||||
"$local_app_data/cursor-agent/agent.cmd"
|
||||
)
|
||||
|
||||
local candidate
|
||||
for candidate in "${candidates[@]}"; do
|
||||
if [[ -f "$candidate" ]]; then
|
||||
echo "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
driver_wrapper_path() {
|
||||
local driver_dir
|
||||
driver_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
echo "$driver_dir/cursor-agent-wrapper.sh"
|
||||
}
|
||||
|
||||
driver_build_windows_bootstrap_prompt() {
|
||||
local loop_context=$1
|
||||
local prompt_file=$2
|
||||
|
||||
cat <<EOF
|
||||
Read these Ralph workspace files before taking action:
|
||||
- .ralph/PROMPT.md
|
||||
- .ralph/PROJECT_CONTEXT.md
|
||||
- .ralph/SPECS_INDEX.md
|
||||
- .ralph/@fix_plan.md
|
||||
- .ralph/@AGENT.md
|
||||
- relevant files under .ralph/specs/
|
||||
|
||||
Then follow the Ralph instructions from those files and continue the next task.
|
||||
EOF
|
||||
|
||||
if [[ -n "$loop_context" ]]; then
|
||||
cat <<EOF
|
||||
|
||||
Current loop context:
|
||||
$loop_context
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [[ "$prompt_file" != ".ralph/PROMPT.md" ]]; then
|
||||
cat <<EOF
|
||||
|
||||
Also read the active prompt file if it differs:
|
||||
- $prompt_file
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
147
.ralph/drivers/opencode.sh
Executable file
147
.ralph/drivers/opencode.sh
Executable file
@@ -0,0 +1,147 @@
|
||||
#!/bin/bash
|
||||
# OpenCode driver for Ralph
|
||||
# Uses OpenCode's build agent with JSON event output and optional session resume.
|
||||
|
||||
driver_name() {
|
||||
echo "opencode"
|
||||
}
|
||||
|
||||
driver_display_name() {
|
||||
echo "OpenCode"
|
||||
}
|
||||
|
||||
driver_cli_binary() {
|
||||
echo "opencode"
|
||||
}
|
||||
|
||||
driver_min_version() {
|
||||
echo "0.1.0"
|
||||
}
|
||||
|
||||
driver_check_available() {
|
||||
command -v "$(driver_cli_binary)" &>/dev/null
|
||||
}
|
||||
|
||||
driver_valid_tools() {
|
||||
VALID_TOOL_PATTERNS=(
|
||||
"bash"
|
||||
"read"
|
||||
"write"
|
||||
"edit"
|
||||
"grep"
|
||||
"question"
|
||||
)
|
||||
}
|
||||
|
||||
driver_supports_tool_allowlist() {
|
||||
return 1
|
||||
}
|
||||
|
||||
driver_permission_denial_help() {
|
||||
echo " - $DRIVER_DISPLAY_NAME uses its native permission and approval model."
|
||||
echo " - ALLOWED_TOOLS in $RALPHRC_FILE is ignored for this driver."
|
||||
echo " - BMAD workflows can use OpenCode's native question tool when needed."
|
||||
echo " - Review OpenCode permissions, then restart the loop."
|
||||
}
|
||||
|
||||
driver_build_command() {
|
||||
local prompt_file=$1
|
||||
local loop_context=$2
|
||||
local session_id=$3
|
||||
|
||||
CLAUDE_CMD_ARGS=("$(driver_cli_binary)" "run" "--agent" "build" "--format" "json")
|
||||
|
||||
if [[ ! -f "$prompt_file" ]]; then
|
||||
echo "ERROR: Prompt file not found: $prompt_file" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$CLAUDE_USE_CONTINUE" == "true" && -n "$session_id" ]]; then
|
||||
CLAUDE_CMD_ARGS+=("--continue" "--session" "$session_id")
|
||||
fi
|
||||
|
||||
local prompt_content
|
||||
prompt_content=$(cat "$prompt_file")
|
||||
if [[ -n "$loop_context" ]]; then
|
||||
prompt_content="$loop_context
|
||||
|
||||
$prompt_content"
|
||||
fi
|
||||
|
||||
CLAUDE_CMD_ARGS+=("$prompt_content")
|
||||
}
|
||||
|
||||
driver_supports_sessions() {
|
||||
return 0
|
||||
}
|
||||
|
||||
driver_supports_live_output() {
|
||||
return 0
|
||||
}
|
||||
|
||||
driver_prepare_live_command() {
|
||||
LIVE_CMD_ARGS=("${CLAUDE_CMD_ARGS[@]}")
|
||||
}
|
||||
|
||||
driver_stream_filter() {
|
||||
echo 'select((.type == "message.updated" or .type == "message.completed") and .message.role == "assistant") | ([.message.parts[]? | select(.type == "text") | .text] | join("\n"))'
|
||||
}
|
||||
|
||||
driver_extract_session_id_from_output() {
|
||||
local output_file=$1
|
||||
|
||||
if [[ ! -f "$output_file" ]]; then
|
||||
echo ""
|
||||
return 1
|
||||
fi
|
||||
|
||||
local session_id
|
||||
session_id=$(sed -n 's/.*"session"[^{]*{[^}]*"id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$output_file" | head -n 1 | tr -d '\r')
|
||||
|
||||
echo "$session_id"
|
||||
[[ -n "$session_id" && "$session_id" != "null" ]]
|
||||
}
|
||||
|
||||
driver_fallback_session_id() {
|
||||
local cli_binary
|
||||
cli_binary=$(driver_cli_binary)
|
||||
|
||||
local sessions_json
|
||||
sessions_json=$("$cli_binary" session list --format json 2>/dev/null) || {
|
||||
echo ""
|
||||
return 1
|
||||
}
|
||||
|
||||
local session_ids
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
session_ids=$(printf '%s' "$sessions_json" | jq -r '
|
||||
if type == "array" then
|
||||
[.[]?.id // empty]
|
||||
elif (.sessions? | type) == "array" then
|
||||
[.sessions[]?.id // empty]
|
||||
else
|
||||
[]
|
||||
end
|
||||
| map(select(length > 0))
|
||||
| .[]
|
||||
' 2>/dev/null | tr -d '\r')
|
||||
else
|
||||
session_ids=$(printf '%s' "$sessions_json" | grep -oE '"id"[[:space:]]*:[[:space:]]*"[^"]+"' | sed 's/.*"id"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/' | tr -d '\r')
|
||||
fi
|
||||
|
||||
local -a session_id_candidates=()
|
||||
local session_id
|
||||
while IFS= read -r session_id; do
|
||||
if [[ -n "$session_id" && "$session_id" != "null" ]]; then
|
||||
session_id_candidates+=("$session_id")
|
||||
fi
|
||||
done <<< "$session_ids"
|
||||
|
||||
if [[ ${#session_id_candidates[@]} -ne 1 ]]; then
|
||||
echo ""
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "${session_id_candidates[0]}"
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user