bmalph 2.7.5 → 2.7.6
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.
- package/README.md +20 -5
- package/dist/commands/doctor-runtime-checks.js +104 -86
- package/dist/commands/doctor-runtime-checks.js.map +1 -1
- package/dist/installer/bmad-assets.js +182 -0
- package/dist/installer/bmad-assets.js.map +1 -0
- package/dist/installer/commands.js +324 -0
- package/dist/installer/commands.js.map +1 -0
- package/dist/installer/install.js +42 -0
- package/dist/installer/install.js.map +1 -0
- package/dist/installer/metadata.js +56 -0
- package/dist/installer/metadata.js.map +1 -0
- package/dist/installer/project-files.js +169 -0
- package/dist/installer/project-files.js.map +1 -0
- package/dist/installer/ralph-assets.js +91 -0
- package/dist/installer/ralph-assets.js.map +1 -0
- package/dist/installer/template-files.js +168 -0
- package/dist/installer/template-files.js.map +1 -0
- package/dist/installer/types.js +2 -0
- package/dist/installer/types.js.map +1 -0
- package/dist/installer.js +5 -843
- package/dist/installer.js.map +1 -1
- package/dist/transition/artifact-loading.js +91 -0
- package/dist/transition/artifact-loading.js.map +1 -0
- package/dist/transition/context-output.js +85 -0
- package/dist/transition/context-output.js.map +1 -0
- package/dist/transition/fix-plan-sync.js +119 -0
- package/dist/transition/fix-plan-sync.js.map +1 -0
- package/dist/transition/orchestration.js +25 -362
- package/dist/transition/orchestration.js.map +1 -1
- package/dist/transition/specs-sync.js +78 -2
- package/dist/transition/specs-sync.js.map +1 -1
- package/dist/utils/ralph-runtime-state.js +222 -0
- package/dist/utils/ralph-runtime-state.js.map +1 -0
- package/dist/utils/state.js +17 -16
- package/dist/utils/state.js.map +1 -1
- package/dist/utils/validate.js +16 -0
- package/dist/utils/validate.js.map +1 -1
- package/dist/watch/renderer.js +48 -6
- package/dist/watch/renderer.js.map +1 -1
- package/dist/watch/state-reader.js +79 -44
- package/dist/watch/state-reader.js.map +1 -1
- package/package.json +1 -1
- package/ralph/RALPH-REFERENCE.md +33 -13
- package/ralph/drivers/claude-code.sh +25 -0
- package/ralph/drivers/codex.sh +11 -0
- package/ralph/drivers/copilot.sh +11 -0
- package/ralph/drivers/cursor.sh +11 -0
- package/ralph/lib/circuit_breaker.sh +3 -3
- package/ralph/lib/date_utils.sh +28 -9
- package/ralph/lib/response_analyzer.sh +127 -7
- package/ralph/ralph_loop.sh +464 -121
- package/ralph/templates/PROMPT.md +5 -0
- package/ralph/templates/ralphrc.template +14 -4
|
@@ -5,12 +5,12 @@ import { RALPH_DIR } from "../utils/constants.js";
|
|
|
5
5
|
import { parseFixPlan } from "../transition/fix-plan.js";
|
|
6
6
|
import { debug } from "../utils/logger.js";
|
|
7
7
|
import { formatError } from "../utils/errors.js";
|
|
8
|
-
import {
|
|
8
|
+
import { readRalphCircuitBreaker, readRalphRuntimeSession, readRalphRuntimeStatus, } from "../utils/ralph-runtime-state.js";
|
|
9
9
|
const LOG_LINE_PATTERN = /^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] \[(\w+)\] (.+)$/;
|
|
10
10
|
const DEFAULT_MAX_LOG_LINES = 8;
|
|
11
11
|
const TAIL_BYTES = 4096;
|
|
12
12
|
export async function readDashboardState(projectDir) {
|
|
13
|
-
const [loop, circuitBreaker, stories, analysis, execution, session, recentLogs] = await Promise.all([
|
|
13
|
+
const [loop, circuitBreaker, stories, analysis, execution, session, recentLogs, liveLog] = await Promise.all([
|
|
14
14
|
readLoopInfo(projectDir),
|
|
15
15
|
readCircuitBreakerInfo(projectDir),
|
|
16
16
|
readStoryProgress(projectDir),
|
|
@@ -18,6 +18,7 @@ export async function readDashboardState(projectDir) {
|
|
|
18
18
|
readExecutionProgress(projectDir),
|
|
19
19
|
readSessionInfo(projectDir),
|
|
20
20
|
readRecentLogs(projectDir),
|
|
21
|
+
readLiveLog(projectDir),
|
|
21
22
|
]);
|
|
22
23
|
const ralphCompleted = loop !== null && loop.status === "completed";
|
|
23
24
|
return {
|
|
@@ -28,50 +29,43 @@ export async function readDashboardState(projectDir) {
|
|
|
28
29
|
execution,
|
|
29
30
|
session,
|
|
30
31
|
recentLogs,
|
|
32
|
+
liveLog,
|
|
31
33
|
ralphCompleted,
|
|
32
34
|
lastUpdated: new Date(),
|
|
33
35
|
};
|
|
34
36
|
}
|
|
35
37
|
export async function readLoopInfo(projectDir) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return null;
|
|
40
|
-
const normalized = normalizeRalphStatus(data);
|
|
41
|
-
const lastAction = typeof data.last_action === "string" ? data.last_action : "";
|
|
42
|
-
const callsMadeThisHour = typeof data.calls_made_this_hour === "number" ? data.calls_made_this_hour : 0;
|
|
43
|
-
const maxCallsPerHour = typeof data.max_calls_per_hour === "number" ? data.max_calls_per_hour : 0;
|
|
44
|
-
return {
|
|
45
|
-
loopCount: normalized.loopCount,
|
|
46
|
-
status: normalized.status,
|
|
47
|
-
lastAction,
|
|
48
|
-
callsMadeThisHour,
|
|
49
|
-
maxCallsPerHour,
|
|
50
|
-
};
|
|
38
|
+
const result = await readRalphRuntimeStatus(projectDir);
|
|
39
|
+
if (result.kind === "missing") {
|
|
40
|
+
return null;
|
|
51
41
|
}
|
|
52
|
-
|
|
53
|
-
debug(`Failed to read loop info: ${formatError(
|
|
42
|
+
if (result.kind !== "ok") {
|
|
43
|
+
debug(`Failed to read loop info: ${formatError(result.error)}`);
|
|
54
44
|
return null;
|
|
55
45
|
}
|
|
46
|
+
return {
|
|
47
|
+
loopCount: result.value.loopCount,
|
|
48
|
+
status: result.value.status,
|
|
49
|
+
lastAction: result.value.lastAction,
|
|
50
|
+
callsMadeThisHour: result.value.callsMadeThisHour,
|
|
51
|
+
maxCallsPerHour: result.value.maxCallsPerHour,
|
|
52
|
+
};
|
|
56
53
|
}
|
|
57
54
|
export async function readCircuitBreakerInfo(projectDir) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return null;
|
|
62
|
-
const validated = validateCircuitBreakerState(data);
|
|
63
|
-
const totalOpens = typeof data.total_opens === "number" ? data.total_opens : 0;
|
|
64
|
-
return {
|
|
65
|
-
state: validated.state,
|
|
66
|
-
consecutiveNoProgress: validated.consecutive_no_progress,
|
|
67
|
-
totalOpens,
|
|
68
|
-
reason: validated.reason,
|
|
69
|
-
};
|
|
55
|
+
const result = await readRalphCircuitBreaker(projectDir);
|
|
56
|
+
if (result.kind === "missing") {
|
|
57
|
+
return null;
|
|
70
58
|
}
|
|
71
|
-
|
|
72
|
-
debug(`Failed to read circuit breaker info: ${formatError(
|
|
59
|
+
if (result.kind !== "ok") {
|
|
60
|
+
debug(`Failed to read circuit breaker info: ${formatError(result.error)}`);
|
|
73
61
|
return null;
|
|
74
62
|
}
|
|
63
|
+
return {
|
|
64
|
+
state: result.value.state,
|
|
65
|
+
consecutiveNoProgress: result.value.consecutiveNoProgress,
|
|
66
|
+
totalOpens: result.value.totalOpens,
|
|
67
|
+
reason: result.value.reason,
|
|
68
|
+
};
|
|
75
69
|
}
|
|
76
70
|
export async function readStoryProgress(projectDir) {
|
|
77
71
|
let content;
|
|
@@ -129,7 +123,9 @@ export async function readExecutionProgress(projectDir) {
|
|
|
129
123
|
if (status !== "executing")
|
|
130
124
|
return null;
|
|
131
125
|
const elapsedSeconds = typeof data.elapsed_seconds === "number" ? data.elapsed_seconds : 0;
|
|
132
|
-
|
|
126
|
+
const indicator = typeof data.indicator === "string" ? data.indicator : "⠋";
|
|
127
|
+
const lastOutput = typeof data.last_output === "string" ? data.last_output : "";
|
|
128
|
+
return { status, elapsedSeconds, indicator, lastOutput };
|
|
133
129
|
}
|
|
134
130
|
catch (err) {
|
|
135
131
|
debug(`Failed to read execution progress: ${formatError(err)}`);
|
|
@@ -137,20 +133,59 @@ export async function readExecutionProgress(projectDir) {
|
|
|
137
133
|
}
|
|
138
134
|
}
|
|
139
135
|
export async function readSessionInfo(projectDir) {
|
|
136
|
+
const result = await readRalphRuntimeSession(projectDir);
|
|
137
|
+
if (result.kind === "missing") {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
if (result.kind !== "ok") {
|
|
141
|
+
debug(`Failed to read session info: ${formatError(result.error)}`);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
if (result.value.kind !== "active") {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
createdAt: result.value.created_at,
|
|
149
|
+
lastUsed: result.value.last_used,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const LIVE_LOG_MAX_LINES = 5;
|
|
153
|
+
const LIVE_LOG_TAIL_BYTES = 2048;
|
|
154
|
+
export async function readLiveLog(projectDir) {
|
|
155
|
+
const logPath = join(projectDir, RALPH_DIR, "live.log");
|
|
156
|
+
let content;
|
|
140
157
|
try {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
158
|
+
const fh = await open(logPath, "r");
|
|
159
|
+
try {
|
|
160
|
+
const stats = await fh.stat();
|
|
161
|
+
if (stats.size === 0) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
if (stats.size <= LIVE_LOG_TAIL_BYTES) {
|
|
165
|
+
content = await fh.readFile("utf-8");
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
const position = stats.size - LIVE_LOG_TAIL_BYTES;
|
|
169
|
+
const buf = Buffer.alloc(LIVE_LOG_TAIL_BYTES);
|
|
170
|
+
const { bytesRead } = await fh.read(buf, 0, LIVE_LOG_TAIL_BYTES, position);
|
|
171
|
+
const raw = buf.toString("utf-8", 0, bytesRead);
|
|
172
|
+
const newlineIdx = raw.indexOf("\n");
|
|
173
|
+
content = newlineIdx >= 0 ? raw.slice(newlineIdx + 1) : raw;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
await fh.close();
|
|
178
|
+
}
|
|
149
179
|
}
|
|
150
180
|
catch (err) {
|
|
151
|
-
debug(`Failed to read
|
|
152
|
-
return
|
|
181
|
+
debug(`Failed to read live log: ${formatError(err)}`);
|
|
182
|
+
return [];
|
|
153
183
|
}
|
|
184
|
+
const lines = content
|
|
185
|
+
.split(/\r?\n/)
|
|
186
|
+
.map((line) => line.trimEnd())
|
|
187
|
+
.filter((line) => line.length > 0);
|
|
188
|
+
return lines.slice(-LIVE_LOG_MAX_LINES);
|
|
154
189
|
}
|
|
155
190
|
export async function readRecentLogs(projectDir, maxLines = DEFAULT_MAX_LOG_LINES) {
|
|
156
191
|
const logPath = join(projectDir, RALPH_DIR, "logs", "ralph.log");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-reader.js","sourceRoot":"","sources":["../../src/watch/state-reader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"state-reader.js","sourceRoot":"","sources":["../../src/watch/state-reader.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/ralph/RALPH-REFERENCE.md
CHANGED
|
@@ -60,7 +60,9 @@ Environment variables > Ralph config file > script defaults
|
|
|
60
60
|
| `MAX_CALLS_PER_HOUR` | `100` | Rate limit for API calls |
|
|
61
61
|
| `CLAUDE_TIMEOUT_MINUTES` | `15` | Timeout per loop driver invocation |
|
|
62
62
|
| `CLAUDE_OUTPUT_FORMAT` | `json` | Output format (json or text) |
|
|
63
|
-
| `ALLOWED_TOOLS` | `Write,Read,Edit,
|
|
63
|
+
| `ALLOWED_TOOLS` | `Write,Read,Edit,MultiEdit,Glob,Grep,Task,TodoWrite,WebFetch,WebSearch,NotebookEdit,Bash` | Claude Code only. Ignored by codex, cursor, and copilot |
|
|
64
|
+
| `CLAUDE_PERMISSION_MODE` | `auto` | Claude Code only. Prevents interactive approval workflows from blocking unattended loops |
|
|
65
|
+
| `PERMISSION_DENIAL_MODE` | `continue` | How Ralph responds to permission denials: continue, halt, or threshold |
|
|
64
66
|
| `SESSION_CONTINUITY` | `true` | Maintain context across loops |
|
|
65
67
|
| `SESSION_EXPIRY_HOURS` | `24` | Session expiration time |
|
|
66
68
|
| `RALPH_VERBOSE` | `false` | Enable verbose logging |
|
|
@@ -73,7 +75,7 @@ Environment variables > Ralph config file > script defaults
|
|
|
73
75
|
|
|
74
76
|
### Generation
|
|
75
77
|
|
|
76
|
-
bmalph copies `ralphrc.template` to `.ralph/.ralphrc` during `bmalph init`.
|
|
78
|
+
bmalph copies `ralphrc.template` to `.ralph/.ralphrc` during `bmalph init`. Untouched managed configs are updated on upgrade, while customized `.ralph/.ralphrc` files are preserved.
|
|
77
79
|
|
|
78
80
|
---
|
|
79
81
|
|
|
@@ -95,7 +97,7 @@ This applies to every driver that exposes resumable IDs today:
|
|
|
95
97
|
|
|
96
98
|
| File | Purpose |
|
|
97
99
|
|------|---------|
|
|
98
|
-
| `.ralph/.ralph_session` | Current session
|
|
100
|
+
| `.ralph/.ralph_session` | Current Ralph session state (active or reset/inactive) |
|
|
99
101
|
| `.ralph/.ralph_session_history` | History of last 50 session transitions |
|
|
100
102
|
| `.ralph/.claude_session_id` | Persisted driver session ID (shared filename for historical reasons; used by Claude Code, Codex, and Cursor) |
|
|
101
103
|
|
|
@@ -117,13 +119,23 @@ Sessions expire after 24 hours (configurable via `SESSION_EXPIRY_HOURS` in `.ral
|
|
|
117
119
|
|
|
118
120
|
### Session State Structure
|
|
119
121
|
|
|
122
|
+
Active session payload:
|
|
123
|
+
|
|
120
124
|
```json
|
|
121
125
|
{
|
|
122
126
|
"session_id": "uuid-string",
|
|
123
127
|
"created_at": "ISO-timestamp",
|
|
124
|
-
"last_used": "ISO-timestamp"
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
"last_used": "ISO-timestamp"
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Reset/inactive payload:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"session_id": "",
|
|
137
|
+
"reset_at": "ISO-timestamp",
|
|
138
|
+
"reset_reason": "reason string"
|
|
127
139
|
}
|
|
128
140
|
```
|
|
129
141
|
|
|
@@ -156,8 +168,13 @@ The circuit breaker prevents runaway loops by detecting stagnation.
|
|
|
156
168
|
|
|
157
169
|
When the active driver is denied permission to execute commands, Ralph:
|
|
158
170
|
1. Detects permission denials from the JSON output
|
|
159
|
-
2.
|
|
160
|
-
3.
|
|
171
|
+
2. Applies `PERMISSION_DENIAL_MODE` from `.ralph/.ralphrc`
|
|
172
|
+
3. Keeps `last_action: permission_denied` visible in the status file and dashboard
|
|
173
|
+
|
|
174
|
+
`PERMISSION_DENIAL_MODE` behavior:
|
|
175
|
+
- `continue` keeps looping and logs the denial
|
|
176
|
+
- `halt` stops immediately with reason `permission_denied`
|
|
177
|
+
- `threshold` keeps looping until `CB_PERMISSION_DENIAL_THRESHOLD` opens the circuit breaker
|
|
161
178
|
|
|
162
179
|
### Auto-Recovery Cooldown
|
|
163
180
|
|
|
@@ -323,18 +340,21 @@ When using `--monitor` with `--live`, tmux creates a 3-pane layout:
|
|
|
323
340
|
2. Fix the underlying issue causing the error
|
|
324
341
|
3. Reset circuit breaker: `bash .ralph/ralph_loop.sh --reset-circuit`
|
|
325
342
|
|
|
326
|
-
#### Permission denied
|
|
343
|
+
#### Permission denied blocks progress
|
|
327
344
|
|
|
328
345
|
**Symptoms:** "OPEN - permission_denied" message
|
|
329
346
|
|
|
330
347
|
**Causes:**
|
|
331
348
|
- The active driver denied permission to run commands
|
|
332
|
-
- `ALLOWED_TOOLS` in `.ralph/.ralphrc` too restrictive
|
|
349
|
+
- `ALLOWED_TOOLS` in `.ralph/.ralphrc` too restrictive for Claude Code
|
|
350
|
+
- The active non-Claude driver rejected a tool under its native permission model
|
|
333
351
|
|
|
334
352
|
**Solutions:**
|
|
335
|
-
1.
|
|
336
|
-
2.
|
|
337
|
-
3.
|
|
353
|
+
1. For Claude Code, update `ALLOWED_TOOLS` in `.ralph/.ralphrc` to include needed tools
|
|
354
|
+
2. For Claude Code unattended loops, keep `CLAUDE_PERMISSION_MODE="auto"` in `.ralph/.ralphrc`
|
|
355
|
+
3. For codex, cursor, and copilot, review the driver's native permission settings; `ALLOWED_TOOLS` is ignored
|
|
356
|
+
4. If you want unattended behavior, keep `PERMISSION_DENIAL_MODE="continue"` in `.ralph/.ralphrc`
|
|
357
|
+
5. Reset circuit breaker if needed: `bash .ralph/ralph_loop.sh --reset-circuit`
|
|
338
358
|
|
|
339
359
|
#### Session expires mid-project
|
|
340
360
|
|
|
@@ -38,6 +38,7 @@ driver_valid_tools() {
|
|
|
38
38
|
"TodoWrite"
|
|
39
39
|
"WebFetch"
|
|
40
40
|
"WebSearch"
|
|
41
|
+
"AskUserQuestion"
|
|
41
42
|
"Bash"
|
|
42
43
|
"Bash(git *)"
|
|
43
44
|
"Bash(npm *)"
|
|
@@ -48,6 +49,26 @@ driver_valid_tools() {
|
|
|
48
49
|
)
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
driver_supports_tool_allowlist() {
|
|
53
|
+
return 0
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
driver_permission_denial_help() {
|
|
57
|
+
echo " 1. Edit $RALPHRC_FILE and keep CLAUDE_PERMISSION_MODE=auto for unattended Claude Code loops"
|
|
58
|
+
echo " 2. If Claude was denied on an interactive approval step, ALLOWED_TOOLS will not fix it"
|
|
59
|
+
echo " 3. If Claude was denied on a normal tool, update ALLOWED_TOOLS to include the required tools"
|
|
60
|
+
echo " 4. Common ALLOWED_TOOLS patterns:"
|
|
61
|
+
echo " - Bash - All shell commands"
|
|
62
|
+
echo " - Bash(node *) - All Node.js commands"
|
|
63
|
+
echo " - Bash(npm *) - All npm commands"
|
|
64
|
+
echo " - Bash(pnpm *) - All pnpm commands"
|
|
65
|
+
echo " - AskUserQuestion - Allow interactive clarification when you want pauses"
|
|
66
|
+
echo ""
|
|
67
|
+
echo "After updating $RALPHRC_FILE:"
|
|
68
|
+
echo " bash .ralph/ralph_loop.sh --reset-session # Clear stale session state"
|
|
69
|
+
echo " bmalph run # Restart the loop"
|
|
70
|
+
}
|
|
71
|
+
|
|
51
72
|
# Build the CLI command arguments
|
|
52
73
|
# Populates global CLAUDE_CMD_ARGS array
|
|
53
74
|
# Parameters:
|
|
@@ -58,6 +79,7 @@ driver_build_command() {
|
|
|
58
79
|
local prompt_file=$1
|
|
59
80
|
local loop_context=$2
|
|
60
81
|
local session_id=$3
|
|
82
|
+
local resolved_permission_mode="${CLAUDE_PERMISSION_MODE:-auto}"
|
|
61
83
|
|
|
62
84
|
# Note: We do NOT use --dangerously-skip-permissions here. Tool permissions
|
|
63
85
|
# are controlled via --allowedTools from CLAUDE_ALLOWED_TOOLS in .ralphrc.
|
|
@@ -74,6 +96,9 @@ driver_build_command() {
|
|
|
74
96
|
CLAUDE_CMD_ARGS+=("--output-format" "json")
|
|
75
97
|
fi
|
|
76
98
|
|
|
99
|
+
# Prevent interactive approval flows from blocking unattended -p loops.
|
|
100
|
+
CLAUDE_CMD_ARGS+=("--permission-mode" "$resolved_permission_mode")
|
|
101
|
+
|
|
77
102
|
# Allowed tools
|
|
78
103
|
if [[ -n "$CLAUDE_ALLOWED_TOOLS" ]]; then
|
|
79
104
|
CLAUDE_CMD_ARGS+=("--allowedTools")
|
package/ralph/drivers/codex.sh
CHANGED
|
@@ -34,6 +34,17 @@ driver_valid_tools() {
|
|
|
34
34
|
)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
driver_supports_tool_allowlist() {
|
|
38
|
+
return 1
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
driver_permission_denial_help() {
|
|
42
|
+
echo " - $DRIVER_DISPLAY_NAME uses its native sandbox and approval model."
|
|
43
|
+
echo " - ALLOWED_TOOLS in $RALPHRC_FILE is ignored for this driver."
|
|
44
|
+
echo " - Ralph already runs Codex with --sandbox workspace-write."
|
|
45
|
+
echo " - Review Codex approval settings, then restart the loop."
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
# Build Codex CLI command
|
|
38
49
|
# Codex uses: codex exec [--resume <id>] --json "prompt"
|
|
39
50
|
driver_build_command() {
|
package/ralph/drivers/copilot.sh
CHANGED
|
@@ -38,6 +38,17 @@ driver_valid_tools() {
|
|
|
38
38
|
)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
driver_supports_tool_allowlist() {
|
|
42
|
+
return 1
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
driver_permission_denial_help() {
|
|
46
|
+
echo " - $DRIVER_DISPLAY_NAME uses its own autonomy and approval controls."
|
|
47
|
+
echo " - ALLOWED_TOOLS in $RALPHRC_FILE is ignored for this driver."
|
|
48
|
+
echo " - Ralph already runs Copilot with --no-ask-user for unattended mode."
|
|
49
|
+
echo " - Review Copilot CLI permissions, then restart the loop."
|
|
50
|
+
}
|
|
51
|
+
|
|
41
52
|
# Build Copilot CLI command
|
|
42
53
|
# Context is prepended to the prompt (same pattern as Codex driver).
|
|
43
54
|
# Uses --autopilot --yolo for autonomous mode, -s to strip stats, -p for prompt.
|
package/ralph/drivers/cursor.sh
CHANGED
|
@@ -48,6 +48,17 @@ driver_valid_tools() {
|
|
|
48
48
|
)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
driver_supports_tool_allowlist() {
|
|
52
|
+
return 1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
driver_permission_denial_help() {
|
|
56
|
+
echo " - $DRIVER_DISPLAY_NAME uses its native permission model."
|
|
57
|
+
echo " - ALLOWED_TOOLS in $RALPHRC_FILE is ignored for this driver."
|
|
58
|
+
echo " - Ralph already runs Cursor with --force."
|
|
59
|
+
echo " - Review Cursor permissions or approval settings, then restart the loop."
|
|
60
|
+
}
|
|
61
|
+
|
|
51
62
|
driver_build_command() {
|
|
52
63
|
local prompt_file=$1
|
|
53
64
|
local loop_context=$2
|
|
@@ -203,7 +203,7 @@ record_loop_result() {
|
|
|
203
203
|
has_permission_denials=$(jq -r '.analysis.has_permission_denials // false' "$response_analysis_file" 2>/dev/null || echo "false")
|
|
204
204
|
fi
|
|
205
205
|
|
|
206
|
-
if [[ "$has_permission_denials" == "true" ]]; then
|
|
206
|
+
if [[ "${PERMISSION_DENIAL_MODE:-halt}" == "threshold" && "$has_permission_denials" == "true" ]]; then
|
|
207
207
|
consecutive_permission_denials=$((consecutive_permission_denials + 1))
|
|
208
208
|
else
|
|
209
209
|
consecutive_permission_denials=0
|
|
@@ -248,7 +248,7 @@ record_loop_result() {
|
|
|
248
248
|
# Permission denials take highest priority (Issue #101)
|
|
249
249
|
if [[ $consecutive_permission_denials -ge $CB_PERMISSION_DENIAL_THRESHOLD ]]; then
|
|
250
250
|
new_state="$CB_STATE_OPEN"
|
|
251
|
-
reason="Permission denied in $consecutive_permission_denials consecutive loops
|
|
251
|
+
reason="Permission denied in $consecutive_permission_denials consecutive loops"
|
|
252
252
|
elif [[ $consecutive_no_progress -ge $CB_NO_PROGRESS_THRESHOLD ]]; then
|
|
253
253
|
new_state="$CB_STATE_OPEN"
|
|
254
254
|
reason="No progress detected in $consecutive_no_progress consecutive loops"
|
|
@@ -266,7 +266,7 @@ record_loop_result() {
|
|
|
266
266
|
# Permission denials take highest priority (Issue #101)
|
|
267
267
|
if [[ $consecutive_permission_denials -ge $CB_PERMISSION_DENIAL_THRESHOLD ]]; then
|
|
268
268
|
new_state="$CB_STATE_OPEN"
|
|
269
|
-
reason="Permission denied in $consecutive_permission_denials consecutive loops
|
|
269
|
+
reason="Permission denied in $consecutive_permission_denials consecutive loops"
|
|
270
270
|
elif [[ "$has_progress" == "true" ]]; then
|
|
271
271
|
new_state="$CB_STATE_CLOSED"
|
|
272
272
|
reason="Progress detected, circuit recovered"
|
package/ralph/lib/date_utils.sh
CHANGED
|
@@ -49,35 +49,37 @@ get_epoch_seconds() {
|
|
|
49
49
|
# Convert ISO 8601 timestamp to Unix epoch seconds
|
|
50
50
|
# Input: ISO timestamp (e.g., "2025-01-15T10:30:00+00:00")
|
|
51
51
|
# Returns: Unix epoch seconds on stdout
|
|
52
|
-
#
|
|
53
|
-
|
|
52
|
+
# Returns non-zero on parse failure.
|
|
53
|
+
parse_iso_to_epoch_strict() {
|
|
54
54
|
local iso_timestamp=$1
|
|
55
55
|
|
|
56
56
|
if [[ -z "$iso_timestamp" || "$iso_timestamp" == "null" ]]; then
|
|
57
|
-
|
|
58
|
-
return
|
|
57
|
+
return 1
|
|
59
58
|
fi
|
|
60
59
|
|
|
60
|
+
local normalized_iso
|
|
61
|
+
normalized_iso=$(printf '%s' "$iso_timestamp" | sed -E 's/\.([0-9]+)(Z|[+-][0-9]{2}:[0-9]{2})$/\2/')
|
|
62
|
+
|
|
61
63
|
# Try GNU date -d (Linux, macOS with Homebrew coreutils)
|
|
62
64
|
local result
|
|
63
65
|
if result=$(date -d "$iso_timestamp" +%s 2>/dev/null) && [[ "$result" =~ ^[0-9]+$ ]]; then
|
|
64
66
|
echo "$result"
|
|
65
|
-
return
|
|
67
|
+
return 0
|
|
66
68
|
fi
|
|
67
69
|
|
|
68
70
|
# Try BSD date -j (native macOS)
|
|
69
71
|
# Normalize timezone for BSD parsing (Z → +0000, ±HH:MM → ±HHMM)
|
|
70
72
|
local tz_fixed
|
|
71
|
-
tz_fixed=$(
|
|
73
|
+
tz_fixed=$(printf '%s' "$normalized_iso" | sed -E 's/Z$/+0000/; s/([+-][0-9]{2}):([0-9]{2})$/\1\2/')
|
|
72
74
|
if result=$(date -j -f "%Y-%m-%dT%H:%M:%S%z" "$tz_fixed" +%s 2>/dev/null) && [[ "$result" =~ ^[0-9]+$ ]]; then
|
|
73
75
|
echo "$result"
|
|
74
|
-
return
|
|
76
|
+
return 0
|
|
75
77
|
fi
|
|
76
78
|
|
|
77
79
|
# Fallback: manual epoch arithmetic from ISO components
|
|
78
80
|
# Parse: YYYY-MM-DDTHH:MM:SS (ignore timezone, assume UTC)
|
|
79
81
|
local year month day hour minute second
|
|
80
|
-
if [[ "$
|
|
82
|
+
if [[ "$normalized_iso" =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}) ]]; then
|
|
81
83
|
year="${BASH_REMATCH[1]}"
|
|
82
84
|
month="${BASH_REMATCH[2]}"
|
|
83
85
|
day="${BASH_REMATCH[3]}"
|
|
@@ -88,10 +90,26 @@ parse_iso_to_epoch() {
|
|
|
88
90
|
# Use date with explicit components if available
|
|
89
91
|
if result=$(date -u -d "${year}-${month}-${day} ${hour}:${minute}:${second}" +%s 2>/dev/null) && [[ "$result" =~ ^[0-9]+$ ]]; then
|
|
90
92
|
echo "$result"
|
|
91
|
-
return
|
|
93
|
+
return 0
|
|
92
94
|
fi
|
|
93
95
|
fi
|
|
94
96
|
|
|
97
|
+
return 1
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# Convert ISO 8601 timestamp to Unix epoch seconds
|
|
101
|
+
# Input: ISO timestamp (e.g., "2025-01-15T10:30:00+00:00")
|
|
102
|
+
# Returns: Unix epoch seconds on stdout
|
|
103
|
+
# Falls back to current epoch on parse failure (safe default)
|
|
104
|
+
parse_iso_to_epoch() {
|
|
105
|
+
local iso_timestamp=$1
|
|
106
|
+
local result
|
|
107
|
+
|
|
108
|
+
if result=$(parse_iso_to_epoch_strict "$iso_timestamp"); then
|
|
109
|
+
echo "$result"
|
|
110
|
+
return 0
|
|
111
|
+
fi
|
|
112
|
+
|
|
95
113
|
# Ultimate fallback: return current epoch (safe default)
|
|
96
114
|
date +%s
|
|
97
115
|
}
|
|
@@ -101,4 +119,5 @@ export -f get_iso_timestamp
|
|
|
101
119
|
export -f get_next_hour_time
|
|
102
120
|
export -f get_basic_timestamp
|
|
103
121
|
export -f get_epoch_seconds
|
|
122
|
+
export -f parse_iso_to_epoch_strict
|
|
104
123
|
export -f parse_iso_to_epoch
|