@walwal-harness/cli 3.7.1 → 3.7.3
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/bin/init.js +1 -0
- package/package.json +1 -1
- package/scripts/harness-control.sh +152 -0
- package/scripts/harness-dashboard.sh +9 -179
- package/scripts/harness-tmux.sh +50 -46
package/bin/init.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.3",
|
|
4
4
|
"description": "Production harness for AI agent engineering — Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer (requirements refinement). Supports React and Flutter FE stacks.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"walwal-harness": "bin/init.js"
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-control.sh — Control Center 프롬프트 (하단)
|
|
3
|
+
# 사용자 명령을 받아 Agent Session 관리 + progress.log 기록
|
|
4
|
+
#
|
|
5
|
+
# Commands:
|
|
6
|
+
# next / n 다음 에이전트 세션 시작
|
|
7
|
+
# retry / r 현재 에이전트 재실행
|
|
8
|
+
# stop 에이전트 세션 중지
|
|
9
|
+
# log <message> progress.log에 수동 메모 추가
|
|
10
|
+
# clear-fail failure 상태 클리어
|
|
11
|
+
# status / s 대시보드 즉시 갱신 (상단 pane)
|
|
12
|
+
# help / h 도움말
|
|
13
|
+
# quit / q 종료
|
|
14
|
+
#
|
|
15
|
+
# Usage: bash scripts/harness-control.sh [project-root]
|
|
16
|
+
|
|
17
|
+
set -uo pipefail
|
|
18
|
+
|
|
19
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
20
|
+
|
|
21
|
+
PROJECT_ROOT="${1:-}"
|
|
22
|
+
if [ -z "$PROJECT_ROOT" ]; then
|
|
23
|
+
source "$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
24
|
+
PROJECT_ROOT="$(resolve_harness_root ".")" || {
|
|
25
|
+
echo "[control] .harness/ not found."
|
|
26
|
+
exit 1
|
|
27
|
+
}
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
31
|
+
PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
|
|
32
|
+
|
|
33
|
+
BOLD="\033[1m"
|
|
34
|
+
DIM="\033[2m"
|
|
35
|
+
GREEN="\033[32m"
|
|
36
|
+
YELLOW="\033[33m"
|
|
37
|
+
RED="\033[31m"
|
|
38
|
+
CYAN="\033[36m"
|
|
39
|
+
RESET="\033[0m"
|
|
40
|
+
|
|
41
|
+
# ── Agent Session pane target ──
|
|
42
|
+
AGENT_PANE="harness-studio.2"
|
|
43
|
+
|
|
44
|
+
cmd_log() {
|
|
45
|
+
local msg="$1"
|
|
46
|
+
if [ -z "$msg" ]; then
|
|
47
|
+
echo -e " ${RED}Usage: log <message>${RESET}"
|
|
48
|
+
return
|
|
49
|
+
fi
|
|
50
|
+
local ts
|
|
51
|
+
ts=$(date +"%Y-%m-%d")
|
|
52
|
+
echo "${ts} | manual | note | ${msg}" >> "$PROGRESS_LOG"
|
|
53
|
+
echo -e " ${GREEN}Logged:${RESET} ${msg}"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
cmd_clear_fail() {
|
|
57
|
+
if [ ! -f "$PROGRESS" ]; then return; fi
|
|
58
|
+
jq '.failure = {agent: null, location: null, message: null, retry_target: null} |
|
|
59
|
+
.sprint.status = "in_progress" |
|
|
60
|
+
.sprint.retry_count = 0 |
|
|
61
|
+
.agent_status = "completed"' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
|
|
62
|
+
echo -e " ${GREEN}Failure state cleared.${RESET}"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
cmd_next() {
|
|
66
|
+
if ! tmux list-panes -t harness-studio 2>/dev/null | grep -q .; then
|
|
67
|
+
echo -e " ${RED}tmux session not found.${RESET}"
|
|
68
|
+
return
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
local next_agent
|
|
72
|
+
next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
73
|
+
if [ "$next_agent" = "none" ] || [ "$next_agent" = "null" ]; then
|
|
74
|
+
echo -e " ${YELLOW}No next agent in queue.${RESET}"
|
|
75
|
+
return
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
echo -e " ${CYAN}Starting ${next_agent} in Agent Session...${RESET}"
|
|
79
|
+
local ts
|
|
80
|
+
ts=$(date +"%Y-%m-%d")
|
|
81
|
+
echo "${ts} | manual | start-agent | ${next_agent}" >> "$PROGRESS_LOG"
|
|
82
|
+
tmux send-keys -t "$AGENT_PANE" "/harness-${next_agent}" Enter
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
cmd_retry() {
|
|
86
|
+
if ! tmux list-panes -t harness-studio 2>/dev/null | grep -q .; then
|
|
87
|
+
echo -e " ${RED}tmux session not found.${RESET}"
|
|
88
|
+
return
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
local current_agent
|
|
92
|
+
current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
93
|
+
if [ "$current_agent" = "none" ] || [ "$current_agent" = "null" ]; then
|
|
94
|
+
echo -e " ${YELLOW}No current agent to retry.${RESET}"
|
|
95
|
+
return
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
echo -e " ${CYAN}Retrying ${current_agent}...${RESET}"
|
|
99
|
+
local ts
|
|
100
|
+
ts=$(date +"%Y-%m-%d")
|
|
101
|
+
echo "${ts} | manual | retry | ${current_agent}" >> "$PROGRESS_LOG"
|
|
102
|
+
tmux send-keys -t "$AGENT_PANE" "/harness-${current_agent}" Enter
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
cmd_stop() {
|
|
106
|
+
if ! tmux list-panes -t harness-studio 2>/dev/null | grep -q .; then
|
|
107
|
+
echo -e " ${RED}tmux session not found.${RESET}"
|
|
108
|
+
return
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
echo -e " ${YELLOW}Sending Ctrl+C to Agent Session...${RESET}"
|
|
112
|
+
tmux send-keys -t "$AGENT_PANE" C-c
|
|
113
|
+
local ts
|
|
114
|
+
ts=$(date +"%Y-%m-%d")
|
|
115
|
+
echo "${ts} | manual | stop | user-initiated" >> "$PROGRESS_LOG"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
show_help() {
|
|
119
|
+
echo ""
|
|
120
|
+
echo -e " ${BOLD}Commands${RESET}"
|
|
121
|
+
echo -e " ${CYAN}next${RESET} / ${CYAN}n${RESET} Start next agent"
|
|
122
|
+
echo -e " ${CYAN}retry${RESET} / ${CYAN}r${RESET} Retry current agent"
|
|
123
|
+
echo -e " ${CYAN}stop${RESET} Send Ctrl+C to agent"
|
|
124
|
+
echo -e " ${CYAN}log${RESET} <message> Add note to progress.log"
|
|
125
|
+
echo -e " ${CYAN}clear-fail${RESET} Clear failure state"
|
|
126
|
+
echo -e " ${CYAN}help${RESET} / ${CYAN}h${RESET} Show this help"
|
|
127
|
+
echo -e " ${CYAN}quit${RESET} / ${CYAN}q${RESET} Exit"
|
|
128
|
+
echo ""
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# ── Main ──
|
|
132
|
+
echo ""
|
|
133
|
+
echo -e " ${BOLD}Harness Control${RESET} ${DIM}(type 'help' for commands)${RESET}"
|
|
134
|
+
echo ""
|
|
135
|
+
|
|
136
|
+
while true; do
|
|
137
|
+
echo -ne " ${BOLD}harness>${RESET} "
|
|
138
|
+
read -r input || exit 0
|
|
139
|
+
input=$(echo "$input" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
140
|
+
|
|
141
|
+
case "$input" in
|
|
142
|
+
next|n) cmd_next ;;
|
|
143
|
+
retry|r) cmd_retry ;;
|
|
144
|
+
stop) cmd_stop ;;
|
|
145
|
+
log\ *) cmd_log "${input#log }" ;;
|
|
146
|
+
clear-fail) cmd_clear_fail ;;
|
|
147
|
+
help|h) show_help ;;
|
|
148
|
+
quit|q) echo -e " ${DIM}Goodbye.${RESET}"; exit 0 ;;
|
|
149
|
+
"") ;; # empty — just re-prompt
|
|
150
|
+
*) echo -e " ${DIM}Logging as note.${RESET}"; cmd_log "$input" ;;
|
|
151
|
+
esac
|
|
152
|
+
done
|
|
@@ -260,12 +260,9 @@ render_agent_info() {
|
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
# ══════════════════════════════════════════
|
|
263
|
-
# Full dashboard render
|
|
263
|
+
# Full dashboard render (in-place update)
|
|
264
264
|
# ══════════════════════════════════════════
|
|
265
265
|
render_dashboard() {
|
|
266
|
-
tput cup 0 0 2>/dev/null
|
|
267
|
-
tput civis 2>/dev/null
|
|
268
|
-
|
|
269
266
|
local buf
|
|
270
267
|
buf=$(
|
|
271
268
|
render_header
|
|
@@ -280,190 +277,23 @@ render_dashboard() {
|
|
|
280
277
|
|
|
281
278
|
render_agent_info
|
|
282
279
|
)
|
|
280
|
+
|
|
281
|
+
tput civis 2>/dev/null
|
|
282
|
+
tput cup 0 0 2>/dev/null
|
|
283
283
|
echo "$buf"
|
|
284
284
|
tput ed 2>/dev/null
|
|
285
285
|
tput cnorm 2>/dev/null
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
# ══════════════════════════════════════════
|
|
289
|
-
#
|
|
289
|
+
# Main — Auto-refresh dashboard loop
|
|
290
290
|
# ══════════════════════════════════════════
|
|
291
|
+
tput civis 2>/dev/null
|
|
292
|
+
trap 'tput cnorm 2>/dev/null; exit 0' EXIT INT TERM
|
|
291
293
|
|
|
292
|
-
cmd_log() {
|
|
293
|
-
local msg="$1"
|
|
294
|
-
if [ -z "$msg" ]; then
|
|
295
|
-
echo -e " ${RED}Usage: log <message>${RESET}"
|
|
296
|
-
return
|
|
297
|
-
fi
|
|
298
|
-
|
|
299
|
-
local ts
|
|
300
|
-
ts=$(date +"%Y-%m-%d")
|
|
301
|
-
echo "${ts} | manual | note | ${msg}" >> "$PROGRESS_LOG"
|
|
302
|
-
echo -e " ${GREEN}Logged:${RESET} ${msg}"
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
cmd_clear_fail() {
|
|
306
|
-
if [ ! -f "$PROGRESS" ]; then return; fi
|
|
307
|
-
|
|
308
|
-
jq '.failure = {agent: null, location: null, message: null, retry_target: null} |
|
|
309
|
-
.sprint.status = "in_progress" |
|
|
310
|
-
.sprint.retry_count = 0 |
|
|
311
|
-
.agent_status = "completed"' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
|
|
312
|
-
echo -e " ${GREEN}Failure state cleared.${RESET}"
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
cmd_next() {
|
|
316
|
-
# Find the Agent Session pane and send the next agent command
|
|
317
|
-
local tmux_target="harness-studio.2" # Agent Session pane
|
|
318
|
-
|
|
319
|
-
if ! tmux list-panes -t harness-studio 2>/dev/null | grep -q .; then
|
|
320
|
-
echo -e " ${RED}tmux session not found. Run inside harness-studio.${RESET}"
|
|
321
|
-
return
|
|
322
|
-
fi
|
|
323
|
-
|
|
324
|
-
local next_agent
|
|
325
|
-
next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
326
|
-
|
|
327
|
-
if [ "$next_agent" = "none" ] || [ "$next_agent" = "null" ]; then
|
|
328
|
-
echo -e " ${YELLOW}No next agent in queue.${RESET}"
|
|
329
|
-
return
|
|
330
|
-
fi
|
|
331
|
-
|
|
332
|
-
echo -e " ${CYAN}Starting ${next_agent} in Agent Session pane...${RESET}"
|
|
333
|
-
|
|
334
|
-
# Log the manual intervention
|
|
335
|
-
local ts
|
|
336
|
-
ts=$(date +"%Y-%m-%d")
|
|
337
|
-
echo "${ts} | manual | start-agent | ${next_agent}" >> "$PROGRESS_LOG"
|
|
338
|
-
|
|
339
|
-
# Send command to Agent Session pane
|
|
340
|
-
tmux send-keys -t "$tmux_target" "/harness-${next_agent}" Enter
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
cmd_retry() {
|
|
344
|
-
local tmux_target="harness-studio.2"
|
|
345
|
-
|
|
346
|
-
if ! tmux list-panes -t harness-studio 2>/dev/null | grep -q .; then
|
|
347
|
-
echo -e " ${RED}tmux session not found.${RESET}"
|
|
348
|
-
return
|
|
349
|
-
fi
|
|
350
|
-
|
|
351
|
-
local current_agent
|
|
352
|
-
current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
353
|
-
|
|
354
|
-
if [ "$current_agent" = "none" ] || [ "$current_agent" = "null" ]; then
|
|
355
|
-
echo -e " ${YELLOW}No current agent to retry.${RESET}"
|
|
356
|
-
return
|
|
357
|
-
fi
|
|
358
|
-
|
|
359
|
-
echo -e " ${CYAN}Retrying ${current_agent} in Agent Session pane...${RESET}"
|
|
360
|
-
|
|
361
|
-
local ts
|
|
362
|
-
ts=$(date +"%Y-%m-%d")
|
|
363
|
-
echo "${ts} | manual | retry | ${current_agent}" >> "$PROGRESS_LOG"
|
|
364
|
-
|
|
365
|
-
tmux send-keys -t "$tmux_target" "/harness-${current_agent}" Enter
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
cmd_stop() {
|
|
369
|
-
local tmux_target="harness-studio.2"
|
|
370
|
-
|
|
371
|
-
if ! tmux list-panes -t harness-studio 2>/dev/null | grep -q .; then
|
|
372
|
-
echo -e " ${RED}tmux session not found.${RESET}"
|
|
373
|
-
return
|
|
374
|
-
fi
|
|
375
|
-
|
|
376
|
-
echo -e " ${YELLOW}Sending Ctrl+C to Agent Session...${RESET}"
|
|
377
|
-
tmux send-keys -t "$tmux_target" C-c
|
|
378
|
-
|
|
379
|
-
local ts
|
|
380
|
-
ts=$(date +"%Y-%m-%d")
|
|
381
|
-
echo "${ts} | manual | stop | user-initiated" >> "$PROGRESS_LOG"
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
show_help() {
|
|
385
|
-
echo ""
|
|
386
|
-
echo -e " ${BOLD}Commands${RESET}"
|
|
387
|
-
echo -e " ${CYAN}status${RESET} / ${CYAN}s${RESET} Refresh dashboard"
|
|
388
|
-
echo -e " ${CYAN}next${RESET} / ${CYAN}n${RESET} Start next agent in Agent Session"
|
|
389
|
-
echo -e " ${CYAN}retry${RESET} / ${CYAN}r${RESET} Retry current agent"
|
|
390
|
-
echo -e " ${CYAN}stop${RESET} Send Ctrl+C to Agent Session"
|
|
391
|
-
echo -e " ${CYAN}log${RESET} <message> Add note to progress.log"
|
|
392
|
-
echo -e " ${CYAN}clear-fail${RESET} Clear failure state"
|
|
393
|
-
echo -e " ${CYAN}help${RESET} / ${CYAN}h${RESET} Show this help"
|
|
394
|
-
echo -e " ${CYAN}quit${RESET} / ${CYAN}q${RESET} Exit dashboard"
|
|
395
|
-
echo ""
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
# ══════════════════════════════════════════
|
|
399
|
-
# Main — Interactive loop
|
|
400
|
-
# ══════════════════════════════════════════
|
|
401
294
|
clear
|
|
402
|
-
render_dashboard
|
|
403
|
-
|
|
404
|
-
echo ""
|
|
405
|
-
echo -e " ${DIM}Type 'help' for commands. Dashboard auto-refreshes every 5s.${RESET}"
|
|
406
|
-
echo ""
|
|
407
|
-
|
|
408
|
-
# Background auto-refresh
|
|
409
|
-
(
|
|
410
|
-
while true; do
|
|
411
|
-
sleep 5
|
|
412
|
-
# Signal main process to refresh
|
|
413
|
-
kill -USR1 $$ 2>/dev/null || exit 0
|
|
414
|
-
done
|
|
415
|
-
) &
|
|
416
|
-
REFRESH_PID=$!
|
|
417
|
-
|
|
418
|
-
# Trap USR1 to refresh dashboard
|
|
419
|
-
trap 'render_dashboard' USR1
|
|
420
|
-
trap 'kill $REFRESH_PID 2>/dev/null; exit 0' EXIT INT TERM
|
|
421
295
|
|
|
422
296
|
while true; do
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if read -t 5 -r input; then
|
|
426
|
-
# Trim whitespace
|
|
427
|
-
input=$(echo "$input" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
428
|
-
|
|
429
|
-
case "$input" in
|
|
430
|
-
status|s)
|
|
431
|
-
render_dashboard
|
|
432
|
-
;;
|
|
433
|
-
next|n)
|
|
434
|
-
cmd_next
|
|
435
|
-
;;
|
|
436
|
-
retry|r)
|
|
437
|
-
cmd_retry
|
|
438
|
-
;;
|
|
439
|
-
stop)
|
|
440
|
-
cmd_stop
|
|
441
|
-
;;
|
|
442
|
-
log\ *)
|
|
443
|
-
cmd_log "${input#log }"
|
|
444
|
-
;;
|
|
445
|
-
clear-fail)
|
|
446
|
-
cmd_clear_fail
|
|
447
|
-
;;
|
|
448
|
-
help|h)
|
|
449
|
-
show_help
|
|
450
|
-
;;
|
|
451
|
-
quit|q)
|
|
452
|
-
echo -e " ${DIM}Goodbye.${RESET}"
|
|
453
|
-
exit 0
|
|
454
|
-
;;
|
|
455
|
-
"")
|
|
456
|
-
# Empty input — just refresh
|
|
457
|
-
render_dashboard
|
|
458
|
-
;;
|
|
459
|
-
*)
|
|
460
|
-
# Unknown command — treat as manual note
|
|
461
|
-
echo -e " ${DIM}Unknown command. Logging as note.${RESET}"
|
|
462
|
-
cmd_log "$input"
|
|
463
|
-
;;
|
|
464
|
-
esac
|
|
465
|
-
else
|
|
466
|
-
# Timeout — auto-refresh
|
|
467
|
-
render_dashboard
|
|
468
|
-
fi
|
|
297
|
+
render_dashboard
|
|
298
|
+
sleep 3
|
|
469
299
|
done
|
package/scripts/harness-tmux.sh
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# harness-tmux.sh — Harness Studio tmux
|
|
2
|
+
# harness-tmux.sh — Harness Studio tmux 5-pane 레이아웃
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
# │
|
|
6
|
-
# │
|
|
7
|
-
# │
|
|
8
|
-
# │
|
|
9
|
-
# │
|
|
10
|
-
# │
|
|
11
|
-
#
|
|
12
|
-
# └───────────────────────────┴─────────────────────────┘
|
|
4
|
+
# ┌─────────────────────────┬────────────────────────┐
|
|
5
|
+
# │ Dashboard (auto-refresh)│ Monitor (compact) │
|
|
6
|
+
# │ ├────────────────────────┤
|
|
7
|
+
# │ │ Agent Session (claude) │
|
|
8
|
+
# ├─────────────────────────┤ │
|
|
9
|
+
# │ Control (harness> _) ├────────────────────────┤
|
|
10
|
+
# │ │ Eval Review │
|
|
11
|
+
# └─────────────────────────┴────────────────────────┘
|
|
13
12
|
#
|
|
14
13
|
# Usage:
|
|
15
14
|
# bash scripts/harness-tmux.sh [project-root]
|
|
16
15
|
# bash scripts/harness-tmux.sh /path/to/project --ai
|
|
17
16
|
#
|
|
18
17
|
# Options:
|
|
19
|
-
# --ai Eval Review에서 claude -p
|
|
18
|
+
# --ai Eval Review에서 claude -p AI 요약
|
|
20
19
|
# --detach 세션 생성 후 attach 하지 않음
|
|
21
20
|
# --kill 기존 harness-studio 세션 종료
|
|
22
21
|
|
|
@@ -34,7 +33,7 @@ for arg in "$@"; do
|
|
|
34
33
|
--ai) USE_AI="--ai" ;;
|
|
35
34
|
--detach) DETACH=true ;;
|
|
36
35
|
--kill)
|
|
37
|
-
tmux kill-session -t "$SESSION_NAME" 2>/dev/null && echo "
|
|
36
|
+
tmux kill-session -t "$SESSION_NAME" 2>/dev/null && echo "Killed." || echo "No session."
|
|
38
37
|
exit 0
|
|
39
38
|
;;
|
|
40
39
|
*)
|
|
@@ -43,7 +42,6 @@ for arg in "$@"; do
|
|
|
43
42
|
esac
|
|
44
43
|
done
|
|
45
44
|
|
|
46
|
-
# Auto-detect project root
|
|
47
45
|
if [ -z "$PROJECT_ROOT" ]; then
|
|
48
46
|
dir="$(pwd)"
|
|
49
47
|
while [ "$dir" != "/" ]; do
|
|
@@ -53,18 +51,16 @@ if [ -z "$PROJECT_ROOT" ]; then
|
|
|
53
51
|
fi
|
|
54
52
|
|
|
55
53
|
if [ -z "$PROJECT_ROOT" ] || [ ! -d "$PROJECT_ROOT/.harness" ]; then
|
|
56
|
-
echo "Error: .harness/
|
|
57
|
-
echo "Usage: bash scripts/harness-tmux.sh [project-root]"
|
|
54
|
+
echo "Error: .harness/ not found."
|
|
58
55
|
exit 1
|
|
59
56
|
fi
|
|
60
57
|
|
|
61
58
|
echo "Project: $PROJECT_ROOT"
|
|
62
59
|
echo "Session: $SESSION_NAME"
|
|
63
60
|
|
|
64
|
-
# Kill existing session if any
|
|
65
61
|
tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
|
|
66
62
|
|
|
67
|
-
# ── Resolve Claude command
|
|
63
|
+
# ── Resolve Claude command ──
|
|
68
64
|
HANDOFF="$PROJECT_ROOT/.harness/handoff.json"
|
|
69
65
|
CLAUDE_CMD="claude --dangerously-skip-permissions"
|
|
70
66
|
if [ -f "$HANDOFF" ]; then
|
|
@@ -75,57 +71,65 @@ if [ -f "$HANDOFF" ]; then
|
|
|
75
71
|
fi
|
|
76
72
|
|
|
77
73
|
# ══════════════════════════════════════════
|
|
78
|
-
#
|
|
74
|
+
# Build layout using explicit pane IDs
|
|
79
75
|
# ══════════════════════════════════════════
|
|
80
|
-
# Step 1: Create session → Pane 0 = Main (Control Center)
|
|
81
|
-
tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 200 -y 50
|
|
82
76
|
|
|
83
|
-
#
|
|
84
|
-
tmux
|
|
85
|
-
|
|
77
|
+
# 1. Create session → capture Dashboard pane ID
|
|
78
|
+
PANE_DASHBOARD=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 200 -y 50 \
|
|
79
|
+
-P -F '#{pane_id}' \
|
|
80
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"'")
|
|
86
81
|
|
|
87
|
-
#
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
# 2. Split Dashboard horizontally (55% left / 45% right) → Monitor pane
|
|
83
|
+
PANE_MONITOR=$(tmux split-window -h -p 45 -t "$PANE_DASHBOARD" -c "$PROJECT_ROOT" \
|
|
84
|
+
-P -F '#{pane_id}' \
|
|
85
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
|
|
90
86
|
|
|
91
|
-
#
|
|
92
|
-
tmux
|
|
87
|
+
# 3. Split Dashboard vertically (75% top / 25% bottom) → Control pane
|
|
88
|
+
PANE_CONTROL=$(tmux split-window -v -p 25 -t "$PANE_DASHBOARD" -c "$PROJECT_ROOT" \
|
|
89
|
+
-P -F '#{pane_id}' \
|
|
90
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-control.sh\" \"${PROJECT_ROOT}\"'")
|
|
93
91
|
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
"exec bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-eval-watcher.sh\" \"${PROJECT_ROOT}\" ${USE_AI}'"
|
|
92
|
+
# 4. Split Monitor vertically (15% top / 85% bottom) → Agent Session pane
|
|
93
|
+
PANE_AGENT=$(tmux split-window -v -p 85 -t "$PANE_MONITOR" -c "$PROJECT_ROOT" \
|
|
94
|
+
-P -F '#{pane_id}')
|
|
98
95
|
|
|
99
|
-
#
|
|
100
|
-
tmux
|
|
96
|
+
# 5. Split Agent Session vertically (55% top / 45% bottom) → Eval Review pane
|
|
97
|
+
PANE_EVAL=$(tmux split-window -v -p 45 -t "$PANE_AGENT" -c "$PROJECT_ROOT" \
|
|
98
|
+
-P -F '#{pane_id}' \
|
|
99
|
+
"bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-eval-watcher.sh\" \"${PROJECT_ROOT}\" ${USE_AI}'")
|
|
101
100
|
|
|
102
|
-
#
|
|
103
|
-
|
|
101
|
+
# 6. Launch Claude in Agent Session (needs interactive shell, so use send-keys)
|
|
102
|
+
# Prefix with unset to avoid nvm warnings
|
|
103
|
+
tmux send-keys -t "$PANE_AGENT" "unset npm_config_prefix 2>/dev/null; $CLAUDE_CMD" Enter
|
|
104
104
|
|
|
105
105
|
# ── Pane titles ──
|
|
106
|
-
tmux select-pane -t "$
|
|
107
|
-
tmux select-pane -t "$
|
|
108
|
-
tmux select-pane -t "$
|
|
109
|
-
tmux select-pane -t "$
|
|
106
|
+
tmux select-pane -t "$PANE_DASHBOARD" -T "Dashboard"
|
|
107
|
+
tmux select-pane -t "$PANE_MONITOR" -T "Monitor"
|
|
108
|
+
tmux select-pane -t "$PANE_CONTROL" -T "Control"
|
|
109
|
+
tmux select-pane -t "$PANE_AGENT" -T "Agent Session"
|
|
110
|
+
tmux select-pane -t "$PANE_EVAL" -T "Eval Review"
|
|
110
111
|
|
|
111
112
|
tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
|
|
112
113
|
tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
|
|
113
114
|
|
|
115
|
+
# ── Focus on Control pane (where user types) ──
|
|
116
|
+
tmux select-pane -t "$PANE_CONTROL"
|
|
117
|
+
|
|
114
118
|
# ── Attach ──
|
|
115
119
|
if [ "$DETACH" = true ]; then
|
|
116
120
|
echo ""
|
|
117
|
-
echo "Session created. Attach
|
|
118
|
-
echo " tmux attach -t $SESSION_NAME"
|
|
121
|
+
echo "Session created. Attach: tmux attach -t $SESSION_NAME"
|
|
119
122
|
else
|
|
120
123
|
if [ -n "${TMUX:-}" ]; then
|
|
121
124
|
tmux switch-client -t "$SESSION_NAME"
|
|
122
125
|
else
|
|
123
126
|
echo ""
|
|
124
127
|
echo "Launching harness-studio..."
|
|
125
|
-
echo "
|
|
126
|
-
echo "
|
|
127
|
-
echo "
|
|
128
|
-
echo "
|
|
128
|
+
echo " Dashboard (left↑) : Auto-refresh progress"
|
|
129
|
+
echo " Control (left↓) : Manual commands (next/retry/stop/log)"
|
|
130
|
+
echo " Monitor (right↑) : Agent lifecycle (compact)"
|
|
131
|
+
echo " Agent Session (right↔): claude --dangerously-skip-permissions"
|
|
132
|
+
echo " Eval Review (right↓): Evaluation summaries"
|
|
129
133
|
echo ""
|
|
130
134
|
tmux attach -t "$SESSION_NAME"
|
|
131
135
|
fi
|