@plot-pm/board 0.15.0 → 0.16.1
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/dist/board-server.mjs +109 -107
- package/package.json +2 -1
- package/plot-agent-manifest.sh +80 -1
- package/plot-approve.sh +3 -1
- package/plot-budget.sh +378 -19
- package/plot-config.sh +61 -9
- package/plot-deliver.sh +3 -1
- package/plot-dispatch.sh +149 -46
- package/plot-fleet-scan.sh +61 -150
- package/plot-host.sh +227 -72
- package/plot-plan-meta.sh +58 -10
- package/plot-reap.sh +104 -8
- package/plot-resolve-artifact.sh +4 -2
- package/plot-tmp.sh +108 -0
- package/plot-transcript-quiet.sh +29 -1
- package/plot-worker-monitor.sh +66 -5
package/plot-config.sh
CHANGED
|
@@ -39,7 +39,11 @@
|
|
|
39
39
|
# Non-numeric or empty falls back to the default. It bounds
|
|
40
40
|
# a HUNG agent — one whose CLI crashed without exiting — so
|
|
41
41
|
# one dead worker cannot hold a slot for hours.
|
|
42
|
-
#
|
|
42
|
+
# Temp sweep after hours before `plot-reap.sh --sweep-temp` removes an
|
|
43
|
+
# owned `$TMPDIR/plot-*` entry or a dead pid's budget
|
|
44
|
+
# memo that a SIGKILL left behind. Default 24; a value
|
|
45
|
+
# that is not a whole number is refused by the sweep.
|
|
46
|
+
# Agent registry the directory the dispatcher writes agent manifests to,
|
|
43
47
|
# read by the board's registry. Default `.plot/agents`
|
|
44
48
|
# (repo-relative, gitignored, hence per-worktree). A board
|
|
45
49
|
# served from a worktree the dispatcher never wrote to
|
|
@@ -48,6 +52,53 @@
|
|
|
48
52
|
# the board finds the registry wherever it was started.
|
|
49
53
|
# Absent = the default, so a single-checkout project is
|
|
50
54
|
# unaffected.
|
|
55
|
+
# Board artifact the board-server.mjs this repository runs, read by
|
|
56
|
+
# plot-board-probe.sh. Declared, it is resolved FIRST and
|
|
57
|
+
# reported as `artifact_source: checkout`; absent, the
|
|
58
|
+
# order stays plugin, npm, checkout — the adopting
|
|
59
|
+
# project's case, unchanged. It exists because a
|
|
60
|
+
# repository that BUILDS the artifact must run the one it
|
|
61
|
+
# built: without it `pnpm build:board` writes a file the
|
|
62
|
+
# board never reads whenever a plugin is installed, and
|
|
63
|
+
# the symptom looks like the fix not working.
|
|
64
|
+
# A relative value resolves against the MAIN CHECKOUT (the
|
|
65
|
+
# parent of `--git-common-dir`), never `--show-toplevel`,
|
|
66
|
+
# so a dispatch desk resolves the same file rather than
|
|
67
|
+
# its own copy; an absolute one is taken as given.
|
|
68
|
+
# A declared file that is missing reports `none` and does
|
|
69
|
+
# NOT fall back to the plugin — the key names which
|
|
70
|
+
# artifact runs, so a silent substitution is the wrong
|
|
71
|
+
# answer it removes. Absent = today's order.
|
|
72
|
+
# Agent settings a JSON settings file every `claude -p` the fleet starts
|
|
73
|
+
# is given, through `--settings`. It names the plugins this
|
|
74
|
+
# project's agents start WITHOUT. Every dispatched agent
|
|
75
|
+
# inherits every `SessionStart` hook the operator's plugins
|
|
76
|
+
# declare, and the fleet starts a session on every worker
|
|
77
|
+
# start, restart, retry and hop plus every agent-runner
|
|
78
|
+
# command the board runs: measured 2026-09-30, one plugin's
|
|
79
|
+
# lockless sync ran three times at once, the 1-minute load
|
|
80
|
+
# reached 195, and the supervisor did not tick for 12
|
|
81
|
+
# minutes.
|
|
82
|
+
# Resolved by `plot-agent-settings.sh`, which prints an
|
|
83
|
+
# absolute path (exit 0), nothing for an absent or empty key
|
|
84
|
+
# (exit 0), or nothing with the reason on stderr (exit 3)
|
|
85
|
+
# for a missing, unparseable or gate-disabling file. A
|
|
86
|
+
# relative value resolves against the MAIN CHECKOUT (the
|
|
87
|
+
# parent of `--git-common-dir`), never `--show-toplevel`,
|
|
88
|
+
# for `Board artifact`'s reason: a desk must resolve the
|
|
89
|
+
# same file, and one cut from an older main may not hold it.
|
|
90
|
+
# The path travels to every consumer as
|
|
91
|
+
# `PLOT_AGENT_SETTINGS`, and each command key interpolates
|
|
92
|
+
# `${PLOT_AGENT_SETTINGS:+--settings "$PLOT_AGENT_SETTINGS"}`
|
|
93
|
+
# itself — Plot rewrites no configured command.
|
|
94
|
+
# A file setting any `plot@…` plugin false, `disableAllHooks`
|
|
95
|
+
# true, or ANY `env` key is REFUSED: those switch Plot's own
|
|
96
|
+
# four gates off, and a settings `PATH` hiding the gates'
|
|
97
|
+
# tools makes them fail open. It is a check against an
|
|
98
|
+
# accidental switch-off, not a boundary — the file is
|
|
99
|
+
# project-owned and reviewed like this one.
|
|
100
|
+
# Absent or empty = no change, so an adopting project that
|
|
101
|
+
# sets nothing behaves exactly as today.
|
|
51
102
|
# Worktree root where /plot-dispatch creates fleet worktrees. A relative
|
|
52
103
|
# value resolves against the repo root, an absolute one is
|
|
53
104
|
# taken as given. Absent = the default `repo_root/..` with
|
|
@@ -58,14 +109,15 @@
|
|
|
58
109
|
# branch" read asks `git worktree list` instead.
|
|
59
110
|
# Agent-runner keys (optional; Plot hardcodes no agent tooling, Principle 5):
|
|
60
111
|
# Worker command how /plot-dispatch runs an agent headless on a worktree.
|
|
61
|
-
# `
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
# the
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
112
|
+
# /plot-init writes `PLOT_UNATTENDED=1 plot-worker-loop.sh`;
|
|
113
|
+
# the bare name resolves to the loop beside
|
|
114
|
+
# plot-dispatch.sh, which puts its own directory first on
|
|
115
|
+
# PATH. A free agent (`--start`) refuses a command that
|
|
116
|
+
# does not run the loop, because only the loop waits for a
|
|
117
|
+
# slice. Absent = not set up: nothing starts, and
|
|
118
|
+
# /plot-dispatch offers to write the loop. `none` = asked,
|
|
119
|
+
# and this repo starts workers by hand — a DELIBERATE
|
|
120
|
+
# absence, never run as a command.
|
|
69
121
|
# Approve command how the board runs `/plot-approve <slug>`; the prompt is
|
|
70
122
|
# appended as one argument. Absent = the board's Approve
|
|
71
123
|
# button renders disabled, naming this key as the fix.
|
package/plot-deliver.sh
CHANGED
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
set -uo pipefail
|
|
52
52
|
|
|
53
53
|
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
54
|
+
. "$script_dir/plot-tmp.sh"
|
|
54
55
|
|
|
55
56
|
# The receipt this script leaves for plot-state-gate.sh, which refuses every
|
|
56
57
|
# other writer of a `State:` line. Sourced rather than run: the gate and the
|
|
@@ -660,7 +661,8 @@ git -C "$tmpwt" add -- "${DELIVERED_DIR#/}" >/dev/null 2>&1 || true
|
|
|
660
661
|
# THE BOOKED PLAN, KEPT FOR THE TRACKER. The booking worktree is removed on
|
|
661
662
|
# every exit below, and the working tree may still read `Approved`; the issue
|
|
662
663
|
# status is decided from the file that reached the default branch.
|
|
663
|
-
booked_plan
|
|
664
|
+
booked_plan=""
|
|
665
|
+
plot_tmpfile booked_plan deliver-plan
|
|
664
666
|
cp "$tmpwt/$rel" "$booked_plan" 2>/dev/null || : > "$booked_plan"
|
|
665
667
|
|
|
666
668
|
if git -C "$tmpwt" diff --cached --quiet 2>/dev/null; then
|
package/plot-dispatch.sh
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
# hand each a queued slice with nobody touching a desk. The count
|
|
15
15
|
# is a REQUEST: a machine at its bound answers with fewer and says
|
|
16
16
|
# so, and the shortfall is reported rather than remembered.
|
|
17
|
+
# A `Worker command` that does not run plot-worker-loop.sh
|
|
18
|
+
# refuses (exit 1, `worker=no-loop`): only the loop waits.
|
|
17
19
|
# --restart <br>
|
|
18
20
|
# start a worker on <br>, which already holds a claim — the
|
|
19
21
|
# counterpart to --stop, and the only way to hand a stopped
|
|
@@ -178,6 +180,7 @@
|
|
|
178
180
|
set -uo pipefail
|
|
179
181
|
|
|
180
182
|
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
183
|
+
. "$script_dir/plot-tmp.sh"
|
|
181
184
|
|
|
182
185
|
# The shared worker classifier. Sourced by both this script and
|
|
183
186
|
# plot-fleet-scan.sh so a worker has ONE state, not one per reader.
|
|
@@ -341,7 +344,7 @@ while [ $# -gt 0 ]; do
|
|
|
341
344
|
# `--release` pushed it from 78 to 91. Two records
|
|
342
345
|
# of one fact, and nothing compares them — a stale number here silently
|
|
343
346
|
# truncates the help rather than failing, so it is checked by a test.
|
|
344
|
-
-h|--help) sed -n '2,
|
|
347
|
+
-h|--help) sed -n '2,93p' "$0"; exit 0 ;;
|
|
345
348
|
*) slug="$1" ;;
|
|
346
349
|
esac
|
|
347
350
|
shift
|
|
@@ -392,28 +395,9 @@ fi
|
|
|
392
395
|
# until this moved. Nothing here executes at definition time; only the position
|
|
393
396
|
# changed.
|
|
394
397
|
|
|
395
|
-
#
|
|
396
|
-
#
|
|
397
|
-
#
|
|
398
|
-
# — never to `$RANDOM` or a timestamp. Two workers launched in the same second by
|
|
399
|
-
# the same fan-out would collide on either, and a collision here silently merges
|
|
400
|
-
# two agents into one manifest.
|
|
401
|
-
#
|
|
402
|
-
# Lowercased because the runtime writes its transcript filename in lowercase and
|
|
403
|
-
# the board joins on exact string equality; `uuidgen` on macOS returns uppercase.
|
|
404
|
-
plot_session_id() {
|
|
405
|
-
local id=""
|
|
406
|
-
if command -v uuidgen >/dev/null 2>&1; then
|
|
407
|
-
id=$(uuidgen 2>/dev/null | tr 'A-Z' 'a-z')
|
|
408
|
-
fi
|
|
409
|
-
if [ -z "$id" ]; then
|
|
410
|
-
# 16 random bytes rendered as a v4-shaped id. The shape matters only for
|
|
411
|
-
# recognisability; nothing parses it.
|
|
412
|
-
id=$(od -An -tx1 -N16 /dev/urandom 2>/dev/null | tr -d ' \n' \
|
|
413
|
-
| sed -E 's/(.{8})(.{4})(.{4})(.{4})(.{12})/\1-\2-\3-\4-\5/')
|
|
414
|
-
fi
|
|
415
|
-
printf '%s' "$id"
|
|
416
|
-
}
|
|
398
|
+
# `plot_session_id` — the session id a launch records — is SOURCED from
|
|
399
|
+
# `plot-agent-manifest.sh` above, because the worker loop mints one on a hop to
|
|
400
|
+
# a new branch and two generators of one id drift.
|
|
417
401
|
|
|
418
402
|
# JSON-escape one string for a manifest value.
|
|
419
403
|
#
|
|
@@ -806,8 +790,20 @@ request_brief() { # $1 = branch, $2 = slug → 0 if a command was started
|
|
|
806
790
|
# this outlives the dispatch run by design, because the fan-out must not block
|
|
807
791
|
# on a `claude -p` session of unknown length. `setsid` is not used — it does
|
|
808
792
|
# not exist on macOS, where most of this fleet runs.
|
|
793
|
+
# THE SETTINGS FILE THIS PROJECT STARTS ITS AGENTS WITH. The `Brief command` is
|
|
794
|
+
# a `claude -p` session like any other agent and inherits every `SessionStart`
|
|
795
|
+
# hook the operator's plugins declare, so it carries the same variable the
|
|
796
|
+
# worker loop exports — and the configured command interpolates
|
|
797
|
+
# `${PLOT_AGENT_SETTINGS:+--settings "$PLOT_AGENT_SETTINGS"}` itself.
|
|
798
|
+
#
|
|
799
|
+
# SET INLINE rather than exported, because this spawn already names its
|
|
800
|
+
# environment on the command line. A refusal or an absent key leaves it EMPTY,
|
|
801
|
+
# which `${VAR:+…}` reads as nothing — so a broken config cannot stop a brief
|
|
802
|
+
# being written.
|
|
803
|
+
_brief_settings="$(bash "$(dirname "${BASH_SOURCE[0]}")/plot-agent-settings.sh" 2>/dev/null || echo "")"
|
|
809
804
|
( cd "$repo_root" \
|
|
810
805
|
&& PLOT_UNATTENDED=1 PLOT_PLAN_SLUG="$bslug" PLOT_BRIEF_BRANCH="$branch" \
|
|
806
|
+
PLOT_AGENT_SETTINGS="$_brief_settings" \
|
|
811
807
|
nohup sh -c "$cmd \"\$@\"" plot-brief \
|
|
812
808
|
"$(brief_prompt "$branch" "$bslug")" \
|
|
813
809
|
>"$log" 2>&1 </dev/null & ) 2>/dev/null
|
|
@@ -1028,6 +1024,46 @@ handover_refusal() { # $1=branch $2=worktree $3=state → 0 may hand over, 1 ref
|
|
|
1028
1024
|
return 0
|
|
1029
1025
|
}
|
|
1030
1026
|
|
|
1027
|
+
# WHICH COMMAND STARTS THIS AGENT is the domain's answer: `startCommand`, asked
|
|
1028
|
+
# through `board/plot-start-command.mjs`. An absent `Worker command` is not set
|
|
1029
|
+
# up and starts nothing, `none` is declined, and a free agent refuses a command
|
|
1030
|
+
# that does not run the loop (#1124). The loop's name and the value that runs it
|
|
1031
|
+
# are this script's readings; the rule names no script.
|
|
1032
|
+
#
|
|
1033
|
+
# THE VALUE IS THE BARE NAME, `PLOT_UNATTENDED=1 plot-worker-loop.sh`, the one
|
|
1034
|
+
# `/plot-init` writes: a plugin path carries a version and an absolute path
|
|
1035
|
+
# carries a machine. The launch puts this script's directory first on PATH, so
|
|
1036
|
+
# the bare name resolves to the loop shipped beside it.
|
|
1037
|
+
#
|
|
1038
|
+
# It sets `start_cmd_verb` (`run`, `unconfigured`, `declined`, `refused` or
|
|
1039
|
+
# `unaskable`), `start_cmd_run`, and `start_cmd_why` / `start_cmd_repair` where
|
|
1040
|
+
# they apply. `unaskable` is a bundle that could not answer, which starts
|
|
1041
|
+
# nothing.
|
|
1042
|
+
ask_start_command() { # $1 = free | assigned
|
|
1043
|
+
local answer rc
|
|
1044
|
+
start_cmd_verb="" start_cmd_run="" start_cmd_why="" start_cmd_repair=""
|
|
1045
|
+
start_cmd_configured=$("$script_dir/plot-config.sh" get "Worker command" "")
|
|
1046
|
+
start_cmd_bundle="$script_dir/board/plot-start-command.mjs"
|
|
1047
|
+
answer=$(printf '%s' "$start_cmd_configured" | node "$start_cmd_bundle" "$1" \
|
|
1048
|
+
plot-worker-loop.sh "PLOT_UNATTENDED=1 plot-worker-loop.sh" 2>/dev/null)
|
|
1049
|
+
rc=$?
|
|
1050
|
+
case "$rc:$answer" in
|
|
1051
|
+
0:run$'\t'*) start_cmd_verb=run; start_cmd_run=${answer#run$'\t'} ;;
|
|
1052
|
+
0:unconfigured$'\t'*)
|
|
1053
|
+
start_cmd_verb=unconfigured
|
|
1054
|
+
start_cmd_why="no 'Worker command' is configured"
|
|
1055
|
+
start_cmd_repair=${answer#unconfigured$'\t'}
|
|
1056
|
+
;;
|
|
1057
|
+
0:declined) start_cmd_verb=declined ;;
|
|
1058
|
+
3:*)
|
|
1059
|
+
start_cmd_verb=refused
|
|
1060
|
+
start_cmd_why=${answer%%$'\n'*}
|
|
1061
|
+
start_cmd_repair=${answer#*$'\n'}
|
|
1062
|
+
;;
|
|
1063
|
+
*) start_cmd_verb=unaskable; start_cmd_why="the rule's bundle $start_cmd_bundle answered exit $rc" ;;
|
|
1064
|
+
esac
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1031
1067
|
start_worker() {
|
|
1032
1068
|
local branch="$1" wt="$2"
|
|
1033
1069
|
local cmd
|
|
@@ -1090,11 +1126,26 @@ start_worker() {
|
|
|
1090
1126
|
return 1
|
|
1091
1127
|
fi
|
|
1092
1128
|
|
|
1093
|
-
cmd=$("$script_dir/plot-config.sh" get "Worker command" "")
|
|
1094
1129
|
# `none` means "asked, and this repo starts them by hand". Running it would
|
|
1095
1130
|
# spawn a worker per branch that fails with `none: command not found` — a
|
|
1096
|
-
# deliberate answer turned into N crashed workers.
|
|
1097
|
-
|
|
1131
|
+
# deliberate answer turned into N crashed workers. An absent key starts
|
|
1132
|
+
# nothing either, and its line names the value to set.
|
|
1133
|
+
ask_start_command "$([ -n "$branch" ] && echo assigned || echo free)"
|
|
1134
|
+
case "$start_cmd_verb" in
|
|
1135
|
+
run) cmd="$start_cmd_run" ;;
|
|
1136
|
+
declined) cmd="" ;;
|
|
1137
|
+
unconfigured)
|
|
1138
|
+
echo " worktree ready — ${start_cmd_why}, so nothing started:"
|
|
1139
|
+
echo " $start_cmd_repair."
|
|
1140
|
+
echo " cd $wt # ${branch:+branch $branch is claimed and waiting}"
|
|
1141
|
+
return 1
|
|
1142
|
+
;;
|
|
1143
|
+
*)
|
|
1144
|
+
echo " refusing to start ${branch:-a free agent} — ${start_cmd_why}" >&2
|
|
1145
|
+
[ -n "$start_cmd_repair" ] && echo " Repair: $start_cmd_repair." >&2
|
|
1146
|
+
return 1
|
|
1147
|
+
;;
|
|
1148
|
+
esac
|
|
1098
1149
|
if [ -z "$cmd" ]; then
|
|
1099
1150
|
# Not an error: Plot deliberately hardcodes no agent tooling (Principle 5).
|
|
1100
1151
|
# Word it as the next step rather than a failure, or a first run reads as
|
|
@@ -1105,11 +1156,7 @@ start_worker() {
|
|
|
1105
1156
|
# in the summary, because per-branch output is exactly where it was missed
|
|
1106
1157
|
# five times on 2026-08-17. Saying it in both places would train the reader
|
|
1107
1158
|
# to skip both.
|
|
1108
|
-
|
|
1109
|
-
echo " worktree ready — start it yourself:"
|
|
1110
|
-
else
|
|
1111
|
-
echo " worktree ready — no 'Worker command' configured, so start it yourself:"
|
|
1112
|
-
fi
|
|
1159
|
+
echo " worktree ready — start it yourself:"
|
|
1113
1160
|
echo " cd $wt # branch $branch is claimed and waiting"
|
|
1114
1161
|
return 1
|
|
1115
1162
|
fi
|
|
@@ -1454,7 +1501,8 @@ start_worker() {
|
|
|
1454
1501
|
PLOT_BUILD_MONITOR="$build_monitor" \
|
|
1455
1502
|
PLOT_EXIT_FILE="$wt/.plot-worker.exit" PLOT_PID_FILE="$wt/.plot-worker.pid" \
|
|
1456
1503
|
PLOT_WRAPPER_PID_FILE="$wt/.plot-worker.wrapper.pid" \
|
|
1457
|
-
|
|
1504
|
+
PLOT_SCRIPT_DIR="$script_dir" \
|
|
1505
|
+
nohup sh -c 'printf "%s" "$$" > "$PLOT_WRAPPER_PID_FILE"; wmon=""; amon=""; bmon=""; if [ -n "$PLOT_WORKER_MONITOR" ]; then "$PLOT_WORKER_MONITOR" & wmon=$!; fi; if [ -n "$PLOT_AGENT_MONITOR" ]; then "$PLOT_AGENT_MONITOR" & amon=$!; fi; if [ -n "$PLOT_BUILD_MONITOR" ]; then "$PLOT_BUILD_MONITOR" & bmon=$!; fi; PATH="$PLOT_SCRIPT_DIR:$PATH"; export PATH; ( '"$cmd"' ) & agent=$!; printf "%s" "$agent" > "$PLOT_PID_FILE"; if [ -f "$PLOT_MANIFEST_FILE" ]; then awk -v pid="$agent" -v started="$PLOT_STAMP_STARTED" -v wrapper="$$" -v wmon="$wmon" -v amon="$amon" -v bmon="$bmon" '"'"'
|
|
1458
1506
|
BEGIN { relaunch = 0; count = 1; stamped = 0 }
|
|
1459
1507
|
FNR == NR {
|
|
1460
1508
|
if ($0 ~ /^ "pid": "[^"]*",$/) {
|
|
@@ -1731,11 +1779,30 @@ if [ "$mode" = "stop" ]; then
|
|
|
1731
1779
|
case "$st" in
|
|
1732
1780
|
running*)
|
|
1733
1781
|
pid=${st#running }
|
|
1734
|
-
|
|
1782
|
+
# THE WHOLE PROCESS GROUP, NOT ONE PID. Measured 2026-09-30 (#1084):
|
|
1783
|
+
# `--stop` signalled the wrapper, the group leader, and the loop, the
|
|
1784
|
+
# prompt shell, `claude` and its children survived reparented to pid 1;
|
|
1785
|
+
# `kill -TERM -<pgid>` ended all of them. The pid is the fallback when the
|
|
1786
|
+
# group cannot be read, or is this script's own group: a worker started
|
|
1787
|
+
# without job control shares its starter's group, and signalling that
|
|
1788
|
+
# group would stop the caller with it.
|
|
1789
|
+
stop_pgid=$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')
|
|
1790
|
+
stop_own_pgid=$(ps -o pgid= -p "$$" 2>/dev/null | tr -d ' ')
|
|
1791
|
+
stop_target="$pid"
|
|
1792
|
+
case "$stop_pgid" in
|
|
1793
|
+
''|*[!0-9]*|0|1) ;;
|
|
1794
|
+
*) [ "$stop_pgid" != "$stop_own_pgid" ] && stop_target="-$stop_pgid" ;;
|
|
1795
|
+
esac
|
|
1796
|
+
kill -TERM -- "$stop_target" 2>/dev/null && echo "stopped $stop_branch (pid $pid)" \
|
|
1735
1797
|
|| { echo "plot-dispatch: could not stop pid $pid — it may have exited between the read and the signal, or belong to another user." >&2
|
|
1736
1798
|
echo " Check it: ps -p $pid -o pid=,stat=,command=" >&2
|
|
1737
1799
|
echo " Nothing else was written; the worktree and the claim stand." >&2
|
|
1738
1800
|
exit 1; }
|
|
1801
|
+
if [ "$stop_target" = "$pid" ]; then
|
|
1802
|
+
echo " signalled pid $pid alone — its process group is unreadable or is this run's own"
|
|
1803
|
+
else
|
|
1804
|
+
echo " signalled its whole process group $stop_pgid"
|
|
1805
|
+
fi
|
|
1739
1806
|
# The worktree and its claim are left in place: the branch is still taken,
|
|
1740
1807
|
# and deleting either would be the kind of write this design avoids.
|
|
1741
1808
|
echo " worktree kept at $wt — the claim stands until you release it:"
|
|
@@ -2239,6 +2306,38 @@ EOF
|
|
|
2239
2306
|
start_headroom=${start_rest%%$'\t'*}
|
|
2240
2307
|
start_why=${start_rest#*$'\t'}
|
|
2241
2308
|
|
|
2309
|
+
# A FREE AGENT NEEDS THE LOOP, so a configured command that does not run it
|
|
2310
|
+
# refuses here, before any desk is cut. A free agent starts with an empty
|
|
2311
|
+
# `PLOT_BRANCH`, and only `plot-worker-loop.sh` waits for the registry to hand
|
|
2312
|
+
# it a slice; any other command runs its prompt at once and exits. Measured
|
|
2313
|
+
# 2026-10-01 (#1124): a plain `claude -p "… $PLOT_BRANCH …"` received
|
|
2314
|
+
# "Implementiere den Branch in nach dem Plan", exited within 30 s, and
|
|
2315
|
+
# `--start` reported `agents=1`. A branch dispatch is not affected: a worker
|
|
2316
|
+
# given a branch works on any command.
|
|
2317
|
+
#
|
|
2318
|
+
# THE DECISION IS THE DOMAIN'S: `ask_start_command` asks `startCommand`
|
|
2319
|
+
# through its bundle. A refusal names the defect and the repair; a bundle that
|
|
2320
|
+
# cannot answer starts nothing, as the fleet-size ask above does.
|
|
2321
|
+
# `worker=no-loop` is the footer word the performer reads, the way it reads
|
|
2322
|
+
# `unconfigured` and `declined`.
|
|
2323
|
+
ask_start_command free
|
|
2324
|
+
case "$start_cmd_verb" in
|
|
2325
|
+
run|declined|unconfigured) ;;
|
|
2326
|
+
refused)
|
|
2327
|
+
echo "plot-dispatch: --start refuses — ${start_cmd_why}." >&2
|
|
2328
|
+
echo " configured: $start_cmd_configured" >&2
|
|
2329
|
+
echo " Repair: ${start_cmd_repair}." >&2
|
|
2330
|
+
echo " A branch dispatch (--restart <branch>) starts a worker on any command." >&2
|
|
2331
|
+
echo "summary: agents=0 requested=${start_count:-default} running=$start_running headroom=$start_headroom worker=no-loop"
|
|
2332
|
+
exit 1
|
|
2333
|
+
;;
|
|
2334
|
+
*)
|
|
2335
|
+
echo "plot-dispatch: --start could not ask which command starts an agent — starting none." >&2
|
|
2336
|
+
echo " ${start_cmd_why}." >&2
|
|
2337
|
+
exit 1
|
|
2338
|
+
;;
|
|
2339
|
+
esac
|
|
2340
|
+
|
|
2242
2341
|
echo "starting $start_n agent(s) — machine $start_headroom, $start_running already running"
|
|
2243
2342
|
|
|
2244
2343
|
# THE WORKER COMMAND IS ASKED ONCE, BEFORE THE LOOP. `start_worker` prints its
|
|
@@ -2252,14 +2351,18 @@ EOF
|
|
|
2252
2351
|
# summary — which is now a performer as well as a person — must be able to
|
|
2253
2352
|
# tell *the machine bounded it* from *nobody has configured how to start one*.
|
|
2254
2353
|
worker_cmd_declined=0
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2354
|
+
case "$start_cmd_verb" in
|
|
2355
|
+
declined) worker_cmd_declined=1; start_worker_state=declined ;;
|
|
2356
|
+
unconfigured) start_worker_state=unconfigured ;;
|
|
2357
|
+
*) start_worker_state=configured ;;
|
|
2259
2358
|
esac
|
|
2260
2359
|
if [ "$start_worker_state" != configured ]; then
|
|
2261
2360
|
echo " no worker will start — 'Worker command' is $start_worker_state in this repo's Plot Config."
|
|
2262
|
-
|
|
2361
|
+
if [ "$start_worker_state" = unconfigured ]; then
|
|
2362
|
+
echo " The desks below are cut and registered. Repair: $start_cmd_repair."
|
|
2363
|
+
else
|
|
2364
|
+
echo " The desks below are cut and registered; start them by hand, or set the key."
|
|
2365
|
+
fi
|
|
2263
2366
|
fi
|
|
2264
2367
|
|
|
2265
2368
|
# `slug` STAYS EMPTY, and the loop reads it. A free agent belongs to no plan
|
|
@@ -2659,18 +2762,18 @@ fi
|
|
|
2659
2762
|
# materialised into a temp file rather than parsed here — the parser stays the
|
|
2660
2763
|
# one place that knows what a plan file looks like.
|
|
2661
2764
|
#
|
|
2662
|
-
#
|
|
2663
|
-
# suffix after them, while GNU accepts it. The first version
|
|
2664
|
-
# `plot-gate-XXXXXX.md` and failed on macOS — and because the failure fell
|
|
2665
|
-
# to the working tree, the gate silently went back to reading the exact
|
|
2666
|
-
# this fix exists to stop reading. Hence also: NO working-tree fallback
|
|
2667
|
-
# If the shared blob cannot be materialised, the gate refuses.
|
|
2765
|
+
# `plot-tmp.sh` puts the template's X's last: BSD mktemp (macOS) rejects a
|
|
2766
|
+
# template with a suffix after them, while GNU accepts it. The first version
|
|
2767
|
+
# wrote `plot-gate-XXXXXX.md` and failed on macOS — and because the failure fell
|
|
2768
|
+
# back to the working tree, the gate silently went back to reading the exact
|
|
2769
|
+
# surface this fix exists to stop reading. Hence also: NO working-tree fallback
|
|
2770
|
+
# below. If the shared blob cannot be materialised, the gate refuses.
|
|
2668
2771
|
plan_file="$plan_path"
|
|
2669
2772
|
gate_blob=""
|
|
2670
2773
|
if [ -n "$gate_sha" ]; then
|
|
2671
|
-
gate_dir
|
|
2774
|
+
gate_dir=""
|
|
2775
|
+
plot_tmpdir gate_dir gate || gate_dir=""
|
|
2672
2776
|
if [ -n "$gate_dir" ]; then
|
|
2673
|
-
trap 'rm -rf "$gate_dir"' EXIT
|
|
2674
2777
|
gate_blob="$gate_dir/$(basename "$plan_path")"
|
|
2675
2778
|
git show "$gate_ref:$plan_path" >"$gate_blob" 2>/dev/null || gate_blob=""
|
|
2676
2779
|
fi
|