tickmarkr 2.2.1 → 2.3.0
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 +10 -9
- package/dist/adapters/types.d.ts +20 -1
- package/dist/adapters/types.js +42 -2
- package/dist/cli/commands/approve.js +5 -4
- package/dist/cli/commands/beat.js +7 -4
- package/dist/cli/commands/doctor.d.ts +5 -1
- package/dist/cli/commands/doctor.js +67 -7
- package/dist/cli/commands/init.js +36 -21
- package/dist/cli/commands/plan.js +16 -2
- package/dist/cli/commands/report.js +37 -1
- package/dist/cli/commands/verify.d.ts +5 -0
- package/dist/cli/commands/verify.js +140 -25
- package/dist/compile/collateral.js +15 -9
- package/dist/compile/native.js +3 -3
- package/dist/config/config.js +1 -1
- package/dist/drivers/index.d.ts +6 -0
- package/dist/drivers/index.js +19 -4
- package/dist/drivers/orca.d.ts +22 -1
- package/dist/drivers/orca.js +147 -6
- package/dist/drivers/subprocess.d.ts +3 -3
- package/dist/drivers/subprocess.js +16 -9
- package/dist/drivers/types.d.ts +2 -0
- package/dist/gates/baseline.d.ts +2 -0
- package/dist/gates/baseline.js +21 -4
- package/dist/gates/llm.d.ts +6 -0
- package/dist/gates/llm.js +25 -9
- package/dist/gates/review.d.ts +3 -1
- package/dist/gates/review.js +40 -12
- package/dist/gates/run-gates.js +17 -10
- package/dist/gates/verdict-cause.d.ts +6 -2
- package/dist/gates/verdict-cause.js +8 -4
- package/dist/run/consult.js +5 -1
- package/dist/run/daemon.d.ts +12 -0
- package/dist/run/daemon.js +218 -25
- package/dist/run/git.d.ts +1 -0
- package/dist/run/git.js +4 -0
- package/dist/run/journal.d.ts +15 -2
- package/dist/run/journal.js +70 -12
- package/dist/run/supervision.d.ts +6 -0
- package/dist/run/supervision.js +29 -1
- package/dist/tui/ink/init-app.js +4 -4
- package/package.json +1 -1
- package/skills/tickmarkr-overseer/SKILL.md +77 -18
- package/skills/tickmarkr-overseer/scripts/seat-send.sh +88 -18
- package/skills/tickmarkr-overseer/scripts/watch-artifacts.sh +36 -2
- package/skills/tickmarkr-overseer/scripts/watch-contamination.sh +36 -15
- package/skills/tickmarkr-overseer/scripts/watch-context.sh +33 -7
- package/skills/tickmarkr-overseer/scripts/watch-pending-input.sh +32 -8
package/dist/tui/ink/init-app.js
CHANGED
|
@@ -5,13 +5,13 @@ import { ToggleMark } from "./components.js";
|
|
|
5
5
|
import { clip, INK, inkInput, inkOutput, KeyBar, padCell, Pointer } from "./frame.js";
|
|
6
6
|
const DRIVERS = ["auto", "herdr", "subprocess", "orca"];
|
|
7
7
|
// One line per driver, shown for the value currently selected: four environments no longer fit on
|
|
8
|
-
// a single description row at 80 columns (the shipped three already clipped)
|
|
9
|
-
//
|
|
8
|
+
// a single description row at 80 columns (the shipped three already clipped). Auto identifies its
|
|
9
|
+
// innermost host from environment markers only; outside an Orca terminal, Orca is named explicitly.
|
|
10
10
|
const DRIVER_DESC = {
|
|
11
|
-
auto: "auto: herdr when HERDR_ENV=1, else
|
|
11
|
+
auto: "auto: herdr when HERDR_ENV=1, else orca inside an Orca terminal, else subprocess",
|
|
12
12
|
herdr: "herdr: every worker runs in a visible pane you can watch and unblock",
|
|
13
13
|
subprocess: "subprocess: headless child processes — no cockpit, same fail-closed gates",
|
|
14
|
-
orca: "orca: visible terminals in the Orca app —
|
|
14
|
+
orca: "orca: visible terminals in the Orca app — picked by auto inside one; name it elsewhere",
|
|
15
15
|
};
|
|
16
16
|
const VISIBILITY = ["pane", "headless"];
|
|
17
17
|
// Three fields, one toggle, one action — a descriptor array, deliberately not a forms framework.
|
package/package.json
CHANGED
|
@@ -48,6 +48,15 @@ through brief lineage. **An executor choice nobody made is still an executor cho
|
|
|
48
48
|
a watcher you know is alive. Earned 2026-08-25 (OBS-622): a handoff recorded *"artifact watcher armed"*
|
|
49
49
|
over two live consult verdicts; at adopt the only `watch-artifacts.sh` on the machine belonged to a
|
|
50
50
|
different repository, and nothing had been watching either file.
|
|
51
|
+
**WATCHER OWNERSHIP IS THE ARMING SEAT'S RECORDED PID, NEVER A NAME PATTERN.** Every bundled
|
|
52
|
+
`watch-*.sh` arm sets `TKR_ARMING_SEAT=<seat>` and writes its own pid under
|
|
53
|
+
`<state-dir>/overseer/pids/<arming-seat>-<script>-<pid>.pid`. A seat retires only watchers it armed,
|
|
54
|
+
by reading those files and killing the exact recorded pids; it never uses `pkill -f`, `pgrep -f`, or
|
|
55
|
+
any argv/path pattern. A journal path is shared by partner tiers and therefore cannot prove ownership.
|
|
56
|
+
Verify each executing pid in two process-table reads before acting, kill-by-pid, arm the replacement,
|
|
57
|
+
then verify its new pid in two process-table reads. A stand-down order inventories both sets: the
|
|
58
|
+
ordering seat's recorded pids to retire, and the partner's watchers armed on the ordering seat that
|
|
59
|
+
must survive it. This is the stand-down order of 222-11, not a best-effort sweep.
|
|
51
60
|
**An adopted seat ANNOUNCES itself, in the same act as re-arming:** tell the adopted orchestrator the
|
|
52
61
|
fresh seat is live (verified send: probe token + read-back). Through the gap its view of your tier read
|
|
53
62
|
STALE, and a tier that believes it is unsupervised escalates into a file nobody is reading. Earned
|
|
@@ -289,8 +298,11 @@ on its owner being free is scheduled, not armed* — applies to itself:
|
|
|
289
298
|
.claude/skills/tickmarkr-overseer/scripts/watch-contamination.sh <journal> <load-ceiling> <poll-s> <cap-s>
|
|
290
299
|
```
|
|
291
300
|
|
|
292
|
-
It wakes on a new **failed
|
|
293
|
-
|
|
301
|
+
It wakes on a new **failed runner `test`, `build`, or `lint`** `gate-result` carrying an infrastructure
|
|
302
|
+
fingerprint in the runner-emitted failure lines, or on sustained load above a ceiling, and prints one
|
|
303
|
+
wake reason. It deliberately ignores the same tokens inside review findings, acceptance prose, and the
|
|
304
|
+
secondary fingerprint echo list: quoted discussion of contamination is not contamination. **Two triggers,
|
|
305
|
+
because one is provably not enough:** run
|
|
294
306
|
against the four reds above, the fingerprint trigger caught the `vitest-worker` timeout and correctly
|
|
295
307
|
refused to launder either review rejection — and **missed** the 1-of-3074 case, whose text looks like an
|
|
296
308
|
ordinary assertion failure. Only the load ceiling catches that one. A single-signal version reads as
|
|
@@ -323,7 +335,10 @@ when the run's own status text pushed the percentage off the statusline; and an
|
|
|
323
335
|
The protocol, in both directions:
|
|
324
336
|
|
|
325
337
|
1. **Overseer sees orch at ≥50%** → nudge it: write `HANDOFF-ORCH-<ver>.md`, then `/clear`, then re-read
|
|
326
|
-
its brief **and** its handoff
|
|
338
|
+
its brief **and** its handoff. **A same-process `/clear` keeps every background task alive**; it clears
|
|
339
|
+
conversation context, not processes. Re-arm is therefore a controlled replacement: read only this
|
|
340
|
+
seat's pid files, verify each pid in two process-table reads, kill those exact pids, arm replacements,
|
|
341
|
+
and verify every new pid in two reads. Never retire by pattern.
|
|
327
342
|
2. **The returning orch, now fresh, checks the OVERSEER.** If the overseer is at ≥50%, it directs the
|
|
328
343
|
overseer to write its handoff and clear, and **points it at `HANDOFF-OVERSEER-<ver>.md` by path**.
|
|
329
344
|
3. Whichever seat is fresh performs the check. **Never both at once** — the run keeps one supervising tier
|
|
@@ -345,8 +360,9 @@ The protocol, in both directions:
|
|
|
345
360
|
herdr pane run <my-pane> "/clear"
|
|
346
361
|
# read the pane back — a cleared claude session shows an empty prompt and a reset context gauge
|
|
347
362
|
# 4. and only THEN, as a SEPARATE send, the re-orientation:
|
|
348
|
-
herdr pane run <my-pane> "You were cleared at <N>%. Read <handoff> and <brief
|
|
349
|
-
|
|
363
|
+
herdr pane run <my-pane> "You were cleared at <N>%. Read <handoff> and <brief>. Same-process clear kept
|
|
364
|
+
every background task alive: retire only your recorded watcher pids, verify twice, kill-by-pid, arm,
|
|
365
|
+
verify every new pid twice, preserve the partner-owned watchers the stand-down lists, then confirm."
|
|
350
366
|
```
|
|
351
367
|
|
|
352
368
|
⚠ **Steps 3 and 4 are two sends, never one.** A pointer batched with the clear lands *during* it and is
|
|
@@ -507,7 +523,11 @@ they are left implicit:
|
|
|
507
523
|
sleep 2–3s → send-keys Enter → read back (input empty / agent `working`). Never report "briefed" without
|
|
508
524
|
the read-back. Long content goes in a brief file, never pane text. `scripts/seat-send.sh` encodes
|
|
509
525
|
this whole path — size guard, atomic prompt, prompt-line read-back, optional interrupt — and never
|
|
510
|
-
auto-resends.
|
|
526
|
+
auto-resends. Each adapter declares its prompt glyph beside its input-box matchers; `seat-send.sh` reads
|
|
527
|
+
that declaration rather than assuming Claude's `❯`. **Probe the prompt line before writing:** if it
|
|
528
|
+
already holds a non-ghost draft, wait only the script's bounded window and return `SEND_DEFERRED`
|
|
529
|
+
without writing a byte. An unsubmitted post-send draft returns `SEND_UNSUBMITTED`. This protects both
|
|
530
|
+
Codex's `›` prompt and a HUMAN typing in a pane; delivery is never allowed to glue onto either draft.
|
|
511
531
|
**PROBE THE READ-BACK WITH THE SHORTEST DISTINCTIVE TOKEN — a commit hash, a pid, an OBS id — NEVER a
|
|
512
532
|
sentence.** A long phrase crosses the pane's render wrap boundary, so grepping for it returns zero on a
|
|
513
533
|
message that arrived intact, and **a badly-probed successful send is byte-identical to a truncated one.**
|
|
@@ -802,12 +822,17 @@ nowhere.
|
|
|
802
822
|
**The artifact watcher** — bundled, and keyed on the deliverable rather than the seat:
|
|
803
823
|
|
|
804
824
|
```bash
|
|
805
|
-
.claude/skills/tickmarkr-overseer/scripts/watch-artifacts.sh
|
|
825
|
+
TKR_ARMING_SEAT=<this-seat> .claude/skills/tickmarkr-overseer/scripts/watch-artifacts.sh \
|
|
826
|
+
[--changed-from <content-sha-or-now>] <MARKER> <cap-s> <poll-s> <file>...
|
|
806
827
|
```
|
|
807
828
|
|
|
808
829
|
It wakes when every named file exists AND ends with its terminal marker, and on timeout it reports each
|
|
809
830
|
file as READY / PARTIAL / ABSENT so a quiet arm still proves the watcher was alive. Tell each seat, in its
|
|
810
831
|
brief, the exact marker its report must end with — you cannot watch for a marker you never demanded.
|
|
832
|
+
**A re-arm over an artifact that already has its marker uses `--changed-from now` (or its captured SHA).**
|
|
833
|
+
The existing marked bytes are the baseline, not a fresh delivery: the watcher fires only after content
|
|
834
|
+
differs from that baseline and then stabilizes, once. Without the changed-from baseline, re-arm is an
|
|
835
|
+
instant replay of old completion rather than observation of new work.
|
|
811
836
|
**Arm on the marker YOU demanded, verified against the FILE — never on the seat's report of its own
|
|
812
837
|
marker.** Measured 2026-08-17: a seat reported its sweep "ends `SWEEP-END`"; the file on disk ended
|
|
813
838
|
`ORDER4-END`. A watcher armed on the reported marker never fires while the artifact sits COMPLETE, and
|
|
@@ -941,9 +966,18 @@ orchestrator turn boundary.
|
|
|
941
966
|
filed a *different* finding as OBS-437, then repeated it as OBS-438 and OBS-439 — and a sweep of the
|
|
942
967
|
ledger's history found **twelve** more. A duplicated id makes every citation ambiguous, and this project
|
|
943
968
|
cites them in rulings, handoffs, memory entries and shipped source comments. **Allocate from the current
|
|
944
|
-
maximum and then
|
|
945
|
-
nothing** —
|
|
946
|
-
|
|
969
|
+
maximum and then run the duplicate-id check against the committed historical baseline below; it must
|
|
970
|
+
print nothing NEW** — the raw `uniq -d` list is intentionally non-empty history and cannot be used as a
|
|
971
|
+
zero-output gate:
|
|
972
|
+
|
|
973
|
+
```bash
|
|
974
|
+
comm -13 \
|
|
975
|
+
<(printf '%s\n' OBS-106 OBS-12 OBS-129 OBS-148 OBS-24 OBS-26 OBS-29 OBS-35 OBS-36 OBS-40 OBS-41 OBS-415 OBS-459 OBS-463 OBS-466 OBS-470 OBS-472 OBS-542 OBS-548 OBS-552 OBS-562 OBS-563 OBS-564 OBS-592 OBS-785 OBS-791 | sort -u) \
|
|
976
|
+
<(grep -o '^## OBS-[0-9]*' <ledger> | sed 's/^## //' | sort | uniq -d)
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
Allocation alone is a guess about what the other tier is doing, and only this baseline-relative check
|
|
980
|
+
catches you both guessing the same. Renumber the LATER entry and say so in its heading. **Never renumber a
|
|
947
981
|
historical id**: every record already citing it would then point at the wrong finding.
|
|
948
982
|
7. **Every fix is evaluated for shipping.** The tarball is `files: [dist, schema, skills, fixtures]` — so
|
|
949
983
|
`src/**` and `skills/**` reach users while `.overseer/**` and `.tickmarkr/**` reach nobody. Before
|
|
@@ -1029,6 +1063,29 @@ Distilled from a v1.86 spec-repair mission that produced 31 numbered rules, ~90
|
|
|
1029
1063
|
errors authored by the supervising seat itself. **Every line below was earned by a defect, most of them
|
|
1030
1064
|
twice.** They are mission-independent on purpose: nothing here names a task, a line number or a figure.
|
|
1031
1065
|
|
|
1066
|
+
**Five queue-and-handoff method laws — execute them; citing their OBS id is not execution:**
|
|
1067
|
+
|
|
1068
|
+
1. **CITE-IS-NOT-READ.** A queue, handoff, finding, or brief that cites an observation does not prove its
|
|
1069
|
+
author opened it. Before acting on a cited premise, open the primary record, quote the operative bytes,
|
|
1070
|
+
and state the qualifier or falsifier the citation would otherwise hide.
|
|
1071
|
+
2. **EXECUTING-FORM PROBE.** Process ownership starts from the watcher pid file. Where legacy discovery is
|
|
1072
|
+
unavoidable, match the executing form — interpreter plus exact script path and arguments — not a journal
|
|
1073
|
+
path or name substring, resolve the candidate's cwd/parent, read the arm log for startup failure, then
|
|
1074
|
+
require the same pid in two reads.
|
|
1075
|
+
3. **RESUME-AWARE CONTAMINATION.** Capture the current engagement boundary before watching a resumable
|
|
1076
|
+
journal. Never terminate on a historical `run-end`: continue across `run-resume`, and stop only at the
|
|
1077
|
+
cap or after current lock release proves no later engagement follows. Classify only runner-emitted lines
|
|
1078
|
+
of new failed `test`, `build`, or `lint` gates; review prose and the secondary fingerprint echo list are
|
|
1079
|
+
evidence about findings, not contamination occurrences.
|
|
1080
|
+
4. **THE QUEUE-INHERITANCE DIFF.** In the same act that opens a queue, extract observation ids from the
|
|
1081
|
+
previous queue and the new queue, run `comm -23 <previous-queue-ids> <new-queue-ids>`, paste its output
|
|
1082
|
+
into the new queue, and classify every row as queued, shipped, or no-ship with a falsifiable removal
|
|
1083
|
+
condition. Reading the previous queue is not a diff.
|
|
1084
|
+
5. **RE-RUN A PREMISE.** Before inheriting any queue item or handoff conclusion, run its named positive and
|
|
1085
|
+
negative controls on the current tree. For an executable premise, execute it with a negative control;
|
|
1086
|
+
for a call-site premise, open the code line and read any rationale through its rejected alternatives.
|
|
1087
|
+
Record the current output; if the premise dissolved, mark it superseded rather than carrying it forward.
|
|
1088
|
+
|
|
1032
1089
|
**Rot**
|
|
1033
1090
|
|
|
1034
1091
|
1. **A quotation is exact bytes.** `grep` it before attributing it; if it does not hit, it is not a
|
|
@@ -1112,11 +1169,12 @@ twice.** They are mission-independent on purpose: nothing here names a task, a l
|
|
|
1112
1169
|
owns it** — "watchers alive" is the one claim a seat cannot verify about itself. Measured 2026-08-06:
|
|
1113
1170
|
an orchestrator sat `idle` through three merges and two dispatches with no journal watcher in the
|
|
1114
1171
|
process table, while its own last report read *"daemon, board, sweeper, watcher all alive"* (OBS-366).
|
|
1115
|
-
**STATE THE LIFETIME, because an unstated one is read as the mission's
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1172
|
+
**STATE THE LIFETIME, because an unstated one is read as the mission's.** Every watcher a seat arms —
|
|
1173
|
+
journal, artifact, dialog, beat loop — records its own pid and arming seat. **A same-process `/clear`
|
|
1174
|
+
keeps every background task alive**; it does not retire even one watcher. A crash may orphan or kill
|
|
1175
|
+
tasks according to how they were launched, while adopt and stand-down are explicit kill-by-recorded-pid
|
|
1176
|
+
transitions. A handoff must therefore inventory real pids, not infer process lifetime from the context
|
|
1177
|
+
lifecycle. The inverse failure is
|
|
1120
1178
|
the same root read the other way: a DETACHED loop outlives its seat and holds a tier `ARMED` with
|
|
1121
1179
|
nobody home (OBS-583). Neither direction may be assumed; the lifetime is a property of how the watcher
|
|
1122
1180
|
was launched, and it belongs in writing next to every claim that one is armed.
|
|
@@ -1173,9 +1231,10 @@ twice.** They are mission-independent on purpose: nothing here names a task, a l
|
|
|
1173
1231
|
The general rule: **an exclusion filter is exactly as
|
|
1174
1232
|
dangerous as an over-broad inclusion filter, and it fails in the direction that reads as "not there" —
|
|
1175
1233
|
which is the direction that gets acted on.**
|
|
1176
|
-
Two corollaries: **
|
|
1177
|
-
|
|
1178
|
-
handoff that re-arms one tier's watchers must say which
|
|
1234
|
+
Two corollaries: **handling a fire-and-exit watcher's wake and re-arming it are the same act.** The wake
|
|
1235
|
+
handling line is incomplete until it names the new recorded pid and two successful process-table reads;
|
|
1236
|
+
never defer the arm to the turn's end. And **a handoff that re-arms one tier's watchers must say which
|
|
1237
|
+
tier's it did NOT re-arm.**
|
|
1179
1238
|
**That first corollary prescribes DISCIPLINE, and discipline is the wrong fix — measured 2026-08-06.**
|
|
1180
1239
|
One orchestrator lapsed its journal tier **31 minutes**, then, after diagnosing it and fully intending
|
|
1181
1240
|
to re-arm, lapsed it again for 3 minutes **while actively thinking about watchers**. Its own diagnosis
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
# under load; this encodes the steps.
|
|
6
6
|
#
|
|
7
7
|
# seat-send.sh <agent-name-or-pane> <message>
|
|
8
|
+
# TKR_ADAPTER=<id> supplies the adapter when a bare pane/name does not expose it in `agent get`.
|
|
8
9
|
#
|
|
9
10
|
# TKR_INTERRUPT=1 end a `working` seat's turn first (Esc, at most twice, verified between) so the
|
|
10
11
|
# directive is READ now instead of queued behind the turn. A message to a working
|
|
@@ -24,8 +25,8 @@
|
|
|
24
25
|
# alone, or nothing — a decorative check beside a real one reads as corroboration.
|
|
25
26
|
#
|
|
26
27
|
# First output line is the machine-greppable verdict:
|
|
27
|
-
# DELIVERED_SUBMITTED | QUEUED_BEHIND_TURN | SEND_UNSUBMITTED |
|
|
28
|
-
# |
|
|
28
|
+
# DELIVERED_SUBMITTED | QUEUED_BEHIND_TURN | SEND_UNSUBMITTED | SEND_DEFERRED
|
|
29
|
+
# | INTERRUPT_FAILED | TARGET_GONE | REFUSED_SIZE | SEND_UNVERIFIED | SEND_REFUSED | SEND_FAILED
|
|
29
30
|
#
|
|
30
31
|
# A success verdict names the surface that delivered when it was not the default one, so a fallback
|
|
31
32
|
# is visible in the transcript rather than inferred (OBS-552).
|
|
@@ -36,6 +37,7 @@ set -u
|
|
|
36
37
|
|
|
37
38
|
TARGET="${1:?usage: seat-send.sh <agent-name-or-pane> <message>}"
|
|
38
39
|
MSG="${2:?message required}"
|
|
40
|
+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
39
41
|
|
|
40
42
|
# PTY input front-truncates around 1KB and a truncated brief silently drops policy — the drop is at
|
|
41
43
|
# the FRONT, so what survives still reads as a complete message. Long content goes in a FILE; the
|
|
@@ -57,9 +59,68 @@ pane_of() {
|
|
|
57
59
|
| sed -n 's/.*"pane_id":"\([^"]*\)".*/\1/p' | head -1
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
62
|
+
# Adapter identity comes from the worker name or Herdr's agent field; a caller targeting a bare pane
|
|
63
|
+
# supplies TKR_ADAPTER. The glyph itself is never duplicated here: this parser reads the declaration
|
|
64
|
+
# beside the input-box matchers in adapters/types.ts (or its built dist twin in an installed package).
|
|
65
|
+
adapter_of() {
|
|
66
|
+
if [ -n "${TKR_ADAPTER:-}" ]; then printf '%s\n' "$TKR_ADAPTER"; return 0; fi
|
|
67
|
+
local record identity adapter
|
|
68
|
+
record=$(herdr agent get "$TARGET" 2>/dev/null)
|
|
69
|
+
identity=$(printf '%s\n' "$record" |
|
|
70
|
+
sed -n 's/.*"agent":"\([^"]*\)".*/\1/p; s/.*"name":"\([^"]*\)".*/\1/p' | head -1)
|
|
71
|
+
[ -n "$identity" ] || identity="$TARGET"
|
|
72
|
+
adapter=$(printf '%s\n' "$identity" |
|
|
73
|
+
sed -n 's/^.*-worker-\(.*\)-a[0-9][0-9]*-.*$/\1/p' | head -1)
|
|
74
|
+
[ -n "$adapter" ] || adapter="$identity"
|
|
75
|
+
case "$adapter" in claude) adapter="claude-code" ;; cursor) adapter="cursor-agent" ;; esac
|
|
76
|
+
printf '%s\n' "$adapter"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
prompt_glyph_of() {
|
|
80
|
+
local adapter="$1" source glyph root tickmarkr_bin tickmarkr_entry
|
|
81
|
+
# The canonical script is <repo>/skills/.../scripts; the independently tracked installed copy is
|
|
82
|
+
# <repo>/.claude/skills/.../scripts. Search both ancestor shapes so identical bytes work from either
|
|
83
|
+
# shipped location. TKR_ADAPTER_TYPES remains the explicit consumer-install override.
|
|
84
|
+
for source in ${TKR_ADAPTER_TYPES:+"$TKR_ADAPTER_TYPES"}; do
|
|
85
|
+
[ -f "$source" ] || continue
|
|
86
|
+
glyph=$(awk -F'"' -v adapter="$adapter" '$2 == adapter && $3 ~ /^[[:space:]]*:[[:space:]]*$/ { print $4; exit }' "$source")
|
|
87
|
+
if [ -n "$glyph" ]; then printf '%s\n' "$glyph"; return 0; fi
|
|
88
|
+
done
|
|
89
|
+
for root in "$SCRIPT_DIR/../../.." "$SCRIPT_DIR/../../../.."; do
|
|
90
|
+
root=$(CDPATH= cd -- "$root" 2>/dev/null && pwd) || continue
|
|
91
|
+
for source in "$root/src/adapters/types.ts" "$root/dist/adapters/types.js"; do
|
|
92
|
+
[ -f "$source" ] || continue
|
|
93
|
+
glyph=$(awk -F'"' -v adapter="$adapter" '$2 == adapter && $3 ~ /^[[:space:]]*:[[:space:]]*$/ { print $4; exit }' "$source")
|
|
94
|
+
if [ -n "$glyph" ]; then printf '%s\n' "$glyph"; return 0; fi
|
|
95
|
+
done
|
|
96
|
+
done
|
|
97
|
+
# `tickmarkr init` copies this skill into a consumer repository, so neither ancestor above belongs
|
|
98
|
+
# to the package. The executable is shipped from <package>/dist/cli/index.js; resolve its symlink and
|
|
99
|
+
# read the adjacent compiled declaration. Node is already a runtime requirement of that executable.
|
|
100
|
+
tickmarkr_bin=$(command -v tickmarkr 2>/dev/null) || tickmarkr_bin=""
|
|
101
|
+
if [ -n "$tickmarkr_bin" ]; then
|
|
102
|
+
tickmarkr_entry=$(node -e 'try { process.stdout.write(require("fs").realpathSync(process.argv[1])) } catch {}' "$tickmarkr_bin" 2>/dev/null)
|
|
103
|
+
if [ -n "$tickmarkr_entry" ]; then
|
|
104
|
+
root=$(CDPATH= cd -- "$(dirname -- "$tickmarkr_entry")/../.." 2>/dev/null && pwd) || root=""
|
|
105
|
+
source="$root/dist/adapters/types.js"
|
|
106
|
+
if [ -n "$root" ] && [ -f "$source" ]; then
|
|
107
|
+
glyph=$(awk -F'"' -v adapter="$adapter" '$2 == adapter && $3 ~ /^[[:space:]]*:[[:space:]]*$/ { print $4; exit }' "$source")
|
|
108
|
+
if [ -n "$glyph" ]; then printf '%s\n' "$glyph"; return 0; fi
|
|
109
|
+
fi
|
|
110
|
+
fi
|
|
111
|
+
fi
|
|
112
|
+
return 1
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
ADAPTER=$(adapter_of)
|
|
116
|
+
if ! PROMPT_GLYPH=$(prompt_glyph_of "$ADAPTER"); then
|
|
117
|
+
echo "SEND_UNVERIFIED $TARGET — adapter '$ADAPTER' has no prompt glyph declaration; nothing was typed"
|
|
118
|
+
exit 1
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# The adapter-declared prompt line is the only state that discriminates submitted from sitting.
|
|
122
|
+
# tail -1 takes the LAST matching line: a wrapped draft's continuation lines carry no prompt glyph,
|
|
123
|
+
# so the last one is still that draft's FIRST rendered line, i.e. a PREFIX of the draft —
|
|
63
124
|
# callers compare prefixes, never whole sentences (OBS-396: a full-sentence match returns false on a
|
|
64
125
|
# message that arrived intact).
|
|
65
126
|
#
|
|
@@ -84,7 +145,11 @@ pane_of() {
|
|
|
84
145
|
prompt_line() {
|
|
85
146
|
herdr agent read "$TARGET" --source visible --lines 14 2>/dev/null \
|
|
86
147
|
| sed '/↑ to edit/{/steer/d;}' \
|
|
87
|
-
|
|
|
148
|
+
| awk -v glyph="$PROMPT_GLYPH" '
|
|
149
|
+
{ pos = index($0, glyph); if (!pos) next
|
|
150
|
+
prefix = substr($0, 1, pos - 1); gsub(/[[:space:]|]/, "", prefix); gsub(/│/, "", prefix)
|
|
151
|
+
if (prefix == "") print substr($0, pos + length(glyph)) }
|
|
152
|
+
' | tail -1 | sed 's/^[[:space:]]*//; s/[[:space:]│|]*$//'
|
|
88
153
|
}
|
|
89
154
|
|
|
90
155
|
# STAGED-QUEUE DISCRIMINATOR (OBS-552 addendum, captured 2026-08-19 on a live kimi seat). After its
|
|
@@ -114,7 +179,7 @@ is_ours() {
|
|
|
114
179
|
# wrapped in SGR dim; typed text renders default. A dim-wrapped line is rendering, not state.
|
|
115
180
|
is_ghost() {
|
|
116
181
|
herdr agent read "$TARGET" --source visible --lines 14 --format ansi 2>/dev/null \
|
|
117
|
-
| grep -F -- "
|
|
182
|
+
| grep -F -- "$PROMPT_GLYPH" | grep -F -- "$1" | head -1 | grep -q "$(printf '\033')\[2m"
|
|
118
183
|
}
|
|
119
184
|
|
|
120
185
|
S=$(status_of)
|
|
@@ -123,17 +188,22 @@ if [ -z "$S" ]; then
|
|
|
123
188
|
exit 1
|
|
124
189
|
fi
|
|
125
190
|
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
191
|
+
# Probe BEFORE writing: `agent prompt` and `pane run` both append to a live draft and submit the
|
|
192
|
+
# concatenation. Give a human a short bounded window to finish or clear; if it remains, defer without
|
|
193
|
+
# touching the pane. Re-running the receipt is the retry, so SEND_DEFERRED remains safe indefinitely.
|
|
194
|
+
DRAFT_WAIT="${TKR_DRAFT_WAIT_S:-10}"
|
|
195
|
+
DRAFT_POLL="${TKR_DRAFT_POLL_S:-1}"
|
|
196
|
+
waited=0
|
|
197
|
+
while :; do
|
|
198
|
+
PRE=$(prompt_line)
|
|
199
|
+
if [ -z "$PRE" ] || is_ghost "$PRE"; then break; fi
|
|
200
|
+
if [ "$waited" -ge "$DRAFT_WAIT" ] 2>/dev/null; then
|
|
201
|
+
echo "SEND_DEFERRED $TARGET — draft present; nothing was typed: $PRE"
|
|
202
|
+
exit 2
|
|
203
|
+
fi
|
|
204
|
+
sleep "$DRAFT_POLL"
|
|
205
|
+
waited=$((waited + DRAFT_POLL))
|
|
206
|
+
done
|
|
137
207
|
|
|
138
208
|
QUEUED=""
|
|
139
209
|
if [ "$S" = "working" ]; then
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Wake the overseer when spawned seats have actually DELIVERED.
|
|
3
3
|
#
|
|
4
|
-
# watch-artifacts.sh <marker> <cap-seconds> <poll-seconds> <file>...
|
|
4
|
+
# watch-artifacts.sh [--changed-from <sha|mtime|now>] <marker> <cap-seconds> <poll-seconds> <file>...
|
|
5
5
|
#
|
|
6
6
|
# Completion is the ARTIFACT plus its TERMINAL MARKER — never an agent's `done`, which is turn end and
|
|
7
7
|
# fires the moment a seat finishes acknowledging you. Never file existence alone either: a seat killed
|
|
@@ -26,12 +26,29 @@ set -u
|
|
|
26
26
|
# arm crashed before printing its reason — a watcher that dies without a wake reason is the exact failure
|
|
27
27
|
# this script exists to prevent.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
CHANGED_FROM=""
|
|
30
|
+
if [ "${1:-}" = "--changed-from" ]; then
|
|
31
|
+
CHANGED_FROM="${2:?--changed-from requires a sha, prior mtime, or 'now'}"
|
|
32
|
+
shift 2
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
MARKER="${1:?usage: watch-artifacts.sh [--changed-from <sha|mtime|now>] <marker> <cap-seconds> <poll-seconds> <file>...}"
|
|
30
36
|
CAP="${2:?}"
|
|
31
37
|
POLL="${3:?}"
|
|
32
38
|
shift 3
|
|
33
39
|
[ "$#" -gt 0 ] || { echo "watch-artifacts: no files given" >&2; exit 2; }
|
|
34
40
|
|
|
41
|
+
# OBS-848: ownership is a pid-file fact, never an argv/path pattern. Two tiers intentionally watch
|
|
42
|
+
# the same artifacts, so the journal/file argument cannot identify which watcher a seat may retire.
|
|
43
|
+
PID_DIR="${TKR_STATE_DIR:-.tickmarkr}/overseer/pids"
|
|
44
|
+
PID_SEAT=$(printf '%s' "${TKR_ARMING_SEAT:-unattributed}" | sed 's/[^A-Za-z0-9_-]/_/g')
|
|
45
|
+
PID_FILE="$PID_DIR/${PID_SEAT}-watch-artifacts-$$.pid"
|
|
46
|
+
mkdir -p "$PID_DIR" && (umask 077; printf '%s\n' "$$" > "$PID_FILE") || {
|
|
47
|
+
echo "watch-artifacts: cannot record pid under $PID_DIR" >&2; exit 73;
|
|
48
|
+
}
|
|
49
|
+
clear_pid() { rm -f "$PID_FILE"; }
|
|
50
|
+
trap clear_pid EXIT
|
|
51
|
+
|
|
35
52
|
# Cap BELOW the host's background-job kill so every arm ends by PRINTING something. A job killed at the
|
|
36
53
|
# limit carries no wake reason and is indistinguishable from a real wake until you read the output —
|
|
37
54
|
# OBS-325, re-earned by a third watcher that had not been capped because the fix was applied only to the
|
|
@@ -74,6 +91,22 @@ END=$((SECONDS + CAP))
|
|
|
74
91
|
# RUNTIME, where the arithmetic then errors, `done_file` returns 1 forever, and the watcher never wakes.
|
|
75
92
|
# Caught by the drill below, not by the syntax check. A syntax check is not a positive control.
|
|
76
93
|
PREV=()
|
|
94
|
+
BASELINE=()
|
|
95
|
+
i=0
|
|
96
|
+
for f in "$@"; do
|
|
97
|
+
if [ -n "$CHANGED_FROM" ]; then
|
|
98
|
+
# A SHA is an explicit single-artifact baseline. Historical callers recorded mtimes; retain that
|
|
99
|
+
# spelling as the arming signal but capture content now, because equal-length rewrites and touches
|
|
100
|
+
# prove that mtime is not the property this watcher claims to read.
|
|
101
|
+
if printf '%s' "$CHANGED_FROM" | grep -qE '^[0-9a-fA-F]{40}$' && [ "$#" -eq 1 ]; then
|
|
102
|
+
BASELINE[$i]=$(printf '%s' "$CHANGED_FROM" | tr 'A-F' 'a-f')
|
|
103
|
+
else
|
|
104
|
+
BASELINE[$i]=$(shasum -a 1 "$f" 2>/dev/null | cut -d' ' -f1)
|
|
105
|
+
fi
|
|
106
|
+
fi
|
|
107
|
+
i=$((i + 1))
|
|
108
|
+
done
|
|
109
|
+
|
|
77
110
|
done_file() { # $1 = index into "$@", $2 = path
|
|
78
111
|
local i="$1" f="$2" sig
|
|
79
112
|
[ -s "$f" ] || return 1
|
|
@@ -85,6 +118,7 @@ done_file() { # $1 = index into "$@", $2 = path
|
|
|
85
118
|
# catch. Hashing 48KB per poll costs nothing.
|
|
86
119
|
sig=$(shasum -a 1 "$f" 2>/dev/null | cut -d' ' -f1)
|
|
87
120
|
[ -n "$sig" ] || return 1
|
|
121
|
+
if [ -n "$CHANGED_FROM" ] && [ "${BASELINE[$i]:-}" = "$sig" ]; then return 1; fi
|
|
88
122
|
if [ "${PREV[$i]:-}" = "$sig" ]; then return 0; fi
|
|
89
123
|
PREV[$i]="$sig" # marked but still moving — hold it one more poll
|
|
90
124
|
return 1
|
|
@@ -21,8 +21,16 @@ LOAD_CEIL="${2:-24}"
|
|
|
21
21
|
POLL="${3:-45}"
|
|
22
22
|
CAP="${4:-14400}"
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
:
|
|
24
|
+
# OBS-848: the arming seat owns this pid, even when a partner watches the same journal. Never derive
|
|
25
|
+
# retirement ownership from the script or journal argv: both are deliberately shared across tiers.
|
|
26
|
+
PID_DIR="${TKR_STATE_DIR:-.tickmarkr}/overseer/pids"
|
|
27
|
+
PID_SEAT=$(printf '%s' "${TKR_ARMING_SEAT:-unattributed}" | sed 's/[^A-Za-z0-9_-]/_/g')
|
|
28
|
+
PID_FILE="$PID_DIR/${PID_SEAT}-watch-contamination-$$.pid"
|
|
29
|
+
mkdir -p "$PID_DIR" && (umask 077; printf '%s\n' "$$" > "$PID_FILE") || {
|
|
30
|
+
echo "watch-contamination: cannot record pid under $PID_DIR" >&2; exit 73;
|
|
31
|
+
}
|
|
32
|
+
clear_pid() { rm -f "$PID_FILE"; }
|
|
33
|
+
trap clear_pid EXIT
|
|
26
34
|
|
|
27
35
|
# Fingerprints of a harness collapse rather than a real regression. Deliberately narrow: a genuine
|
|
28
36
|
# assertion failure must NOT match, or the watcher launders real defects into "infra".
|
|
@@ -35,11 +43,8 @@ load1() { uptime | sed 's/.*load averages*: *//' | awk '{print $1}' | tr -d ',';
|
|
|
35
43
|
vitest_n() { ps -axo comm,command 2>/dev/null | grep -Ec 'node_modules/(\.bin/)?[v]itest|[v]itest/dist/|\([v]itest [0-9]+\)'; }
|
|
36
44
|
|
|
37
45
|
# Seed the seen-set so we wake on what happens NEXT, not on history already ruled on.
|
|
38
|
-
if [ -f "$J" ]; then
|
|
39
|
-
|
|
40
|
-
else
|
|
41
|
-
echo 0 > "$STATE"
|
|
42
|
-
fi
|
|
46
|
+
if [ -f "$J" ]; then seen=$(grep -c '' "$J" 2>/dev/null); else seen=0; fi
|
|
47
|
+
[ -n "$seen" ] || seen=0
|
|
43
48
|
|
|
44
49
|
elapsed=0
|
|
45
50
|
while [ "$elapsed" -lt "$CAP" ]; do
|
|
@@ -54,18 +59,34 @@ while [ "$elapsed" -lt "$CAP" ]; do
|
|
|
54
59
|
Li=$(printf %s "$L" | sed "s/[^0-9].*//"); [ -z "$Li" ] && Li=0
|
|
55
60
|
|
|
56
61
|
if [ -f "$J" ]; then
|
|
57
|
-
seen=$(cat "$STATE" 2>/dev/null); [ -z "$seen" ] && seen=0
|
|
58
62
|
now=$(grep -c '' "$J" 2>/dev/null); [ -z "$now" ] && now=0
|
|
59
63
|
if [ "$now" -gt "$seen" ]; then
|
|
60
64
|
newrows=$(tail -n "$((now - seen))" "$J" 2>/dev/null)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
seen="$now"
|
|
66
|
+
# A fingerprint in arbitrary JSON details is prose, not an occurrence. Parse the event shape,
|
|
67
|
+
# admit only runner gates, and stop before the baseline classifier's secondary echo list. Review
|
|
68
|
+
# findings and acceptance prose are deliberately outside this instrument's claim.
|
|
69
|
+
hit=$(printf '%s\n' "$newrows" | python3 -c '
|
|
70
|
+
import json, re, sys
|
|
71
|
+
infra = re.compile(sys.argv[1], re.I)
|
|
72
|
+
for raw in sys.stdin:
|
|
73
|
+
try: row = json.loads(raw)
|
|
74
|
+
except Exception: continue
|
|
75
|
+
data = row.get("data") or {}
|
|
76
|
+
gate = data.get("gate")
|
|
77
|
+
if row.get("event") != "gate-result" or data.get("pass") is not False or gate not in {"test", "build", "lint"}: continue
|
|
78
|
+
details = str(data.get("details") or "")
|
|
79
|
+
runner = re.split(r"(?im)^new failure fingerprints vs baseline \(secondary\):[ \t]*$", details, maxsplit=1)[0]
|
|
80
|
+
match = infra.search(runner)
|
|
81
|
+
if match:
|
|
82
|
+
end = runner.find("\n", match.end())
|
|
83
|
+
line = runner[runner.rfind("\n", 0, match.start()) + 1:end if end >= 0 else len(runner)]
|
|
84
|
+
print("%s\t%s\t%s" % (row.get("taskId", ""), gate, line))
|
|
85
|
+
break
|
|
86
|
+
' "$INFRA_RE")
|
|
66
87
|
if [ -n "$hit" ]; then
|
|
67
|
-
task=$(printf '%s' "$hit" |
|
|
68
|
-
gate=$(printf '%s' "$hit" |
|
|
88
|
+
task=$(printf '%s' "$hit" | cut -f1)
|
|
89
|
+
gate=$(printf '%s' "$hit" | cut -f2)
|
|
69
90
|
echo "CONTAMINATED_VERDICT task=$task gate=$gate load=$L vitest=$V"
|
|
70
91
|
echo " an infra fingerprint appeared in a FAILED gate â this red is not evidence about the diff"
|
|
71
92
|
echo " ruling owed: is the attempt chargeable? (OBS-426: infra failures are not)"
|
|
@@ -63,6 +63,15 @@ case "$ROLE" in
|
|
|
63
63
|
esac
|
|
64
64
|
TIER="${ROLE}-context"
|
|
65
65
|
|
|
66
|
+
# A shared target does not identify its watcher owner. The arming seat records this exact pid under
|
|
67
|
+
# the repository state dir; retirement reads that file and never matches a script/target name pattern.
|
|
68
|
+
PID_DIR="${TKR_STATE_DIR:-.tickmarkr}/overseer/pids"
|
|
69
|
+
PID_SEAT=$(printf '%s' "${TKR_ARMING_SEAT:-unattributed}" | sed 's/[^A-Za-z0-9_-]/_/g')
|
|
70
|
+
PID_FILE="$PID_DIR/${PID_SEAT}-watch-context-$$.pid"
|
|
71
|
+
mkdir -p "$PID_DIR" && (umask 077; printf '%s\n' "$$" > "$PID_FILE") || {
|
|
72
|
+
echo "watch-context: cannot record pid under $PID_DIR" >&2; exit 73;
|
|
73
|
+
}
|
|
74
|
+
clear_pid() { rm -f "$PID_FILE"; }
|
|
66
75
|
|
|
67
76
|
# The supervision beat interval (SUPERVISION_BEAT_MS = 10s). The loop ticks at the beat cadence or the
|
|
68
77
|
# caller's poll, whichever is SHORTER. Every successful screen read beats; its result is either a
|
|
@@ -100,7 +109,8 @@ beat() {
|
|
|
100
109
|
stand_down() { tickmarkr beat "$TIER" --stand-down --seat "$SEAT" >/dev/null 2>&1; return 0; }
|
|
101
110
|
# EVERY terminal exit — act, unsafe-act, cap — leaves through here, so none of them can forget to
|
|
102
111
|
# record the hand-off. A killed watcher never runs it, which is the one case that must read STALE.
|
|
103
|
-
|
|
112
|
+
cleanup() { stand_down; clear_pid; }
|
|
113
|
+
trap cleanup EXIT
|
|
104
114
|
|
|
105
115
|
# ── WHERE THE NUMBER COMES FROM, and this is the whole 2.1.7-series lesson ────────────────────────────
|
|
106
116
|
# The shipped version read a TERMINAL RENDERING and nothing else. Every context-measurement failure of
|
|
@@ -189,15 +199,31 @@ print(int(round(fill * 100.0 / window)))
|
|
|
189
199
|
' "$CTX_JSONL" "$CTX_WINDOW" 2>/dev/null
|
|
190
200
|
}
|
|
191
201
|
|
|
192
|
-
# The banner path stays as the FALLBACK,
|
|
193
|
-
#
|
|
202
|
+
# The banner path stays as the FALLBACK, and it now selects by POSITION (OBS-865). A seat's statusline
|
|
203
|
+
# renders BELOW the input box's horizontal rule, so the only rows that can carry THIS seat's fill are the
|
|
204
|
+
# rows after the LAST rule line in the window. A tip, a transcript row, or a decoy quoting another seat's
|
|
205
|
+
# percentage sits ABOVE that rule and can no longer be chosen. The previous selector took the last
|
|
206
|
+
# vendor-word line ANYWHERE in the window: on a Fable seat that was `Tip: Ask Claude ...` (UNREADABLE,
|
|
207
|
+
# duty silently dead), and a quoted `opus 12%` would have been read as this seat's fill. The model
|
|
208
|
+
# vocabulary survives only as a SANITY CHECK on the already-chosen row, never as the selector (OBS-780).
|
|
194
209
|
banner_pct() {
|
|
195
210
|
local screen banner pct
|
|
196
211
|
screen=$(herdr agent read "$TARGET" --source visible --lines 8 2>/dev/null) || return 1
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
212
|
+
# The rule glyph is passed IN, never written as an escape: this host's awk (BWK 20200816) does not
|
|
213
|
+
# honour \xNN, and an unsupported escape matches nothing — the selector would find no rule line and
|
|
214
|
+
# report UNREADABLE on every real pane while staying green against any fixture that skipped awk.
|
|
215
|
+
banner=$(printf '%s\n' "$screen" | awk -v rule='─' '
|
|
216
|
+
{ line[NR] = $0
|
|
217
|
+
bare = $0; gsub(/[[:space:]]/, "", bare)
|
|
218
|
+
stripped = bare; gsub(rule, "", stripped)
|
|
219
|
+
if (bare != "" && stripped == "") last = NR }
|
|
220
|
+
END { if (last) for (i = last + 1; i <= NR; i++) if (line[i] ~ /[0-9]+%/) print line[i] }
|
|
221
|
+
' | tail -1)
|
|
222
|
+
# No rule line in the window, or no percentage below it: say so out loud rather than guess.
|
|
200
223
|
[ -n "$banner" ] || { printf 'UNREADABLE\n'; return 0; }
|
|
224
|
+
printf '%s\n' "$banner" |
|
|
225
|
+
grep -Eqi '(^|[^[:alnum:]])(claude|opus|sonnet|haiku|fable|gpt|gemini|glm|kimi|grok|composer|openai|zai)[[:alnum:]_./-]*([[:space:]]|$)' ||
|
|
226
|
+
{ printf 'UNREADABLE\n'; return 0; }
|
|
201
227
|
pct=$(printf '%s\n' "$banner" | grep -oE '[0-9]+%' | tail -1 | tr -d '%')
|
|
202
228
|
[ -n "$pct" ] && printf '%s\n' "$pct" || printf 'UNREADABLE\n'
|
|
203
229
|
}
|
|
@@ -224,7 +250,7 @@ act_on() {
|
|
|
224
250
|
if [ "${TKR_AUTO_CLEAR:-0}" = "1" ] && { [ "$ROLE" = "orchestrator" ] || [ "$ROLE" = "overseer" ]; }; then
|
|
225
251
|
herdr agent prompt "$TARGET" "/clear" >/dev/null 2>&1
|
|
226
252
|
sleep "$SETTLE"
|
|
227
|
-
herdr agent prompt "$TARGET" "Read ${REBRIEF} and continue exactly where it says. Your context was cleared at ${P}% against that handoff; it is current as of $(date '+%H:%M'). Do not reconstruct from memory — everything you need is on disk." >/dev/null 2>&1
|
|
253
|
+
herdr agent prompt "$TARGET" "Read ${REBRIEF} and continue exactly where it says. Your context was cleared at ${P}% against that handoff; it is current as of $(date '+%H:%M'). The same-process clear kept every background task alive: kill only this seat's recorded watcher pids first, verify the process table twice, then re-arm. Do not reconstruct from memory — everything you need is on disk." >/dev/null 2>&1
|
|
228
254
|
echo "CONTEXT_CLEARED $TARGET at ${P}% — handoff fresh, re-briefed from ${REBRIEF}"
|
|
229
255
|
exit 0
|
|
230
256
|
fi
|