@vibelord/lanes 1.5.1 → 1.6.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/package.json +1 -1
- package/payload/lanes/README.md +1 -1
- package/payload/lanes/VERSION +1 -1
- package/payload/lanes/bin/lanes +104 -22
- package/payload/lanes/client/ghostty/common.conf +4 -0
- package/payload/lanes/client/ghostty/lanes-themed-window +73 -0
- package/payload/lanes/client/ghostty/omarchy.conf +4 -2
- package/payload/lanes/container/hooks/muse-hooks.json +13 -0
- package/payload/lanes/container/hooks/zcode-hooks.json +9 -0
- package/payload/lanes/container/lane +7 -4
- package/payload/lanes/container/lane-mcp +13 -5
- package/payload/lanes/container/lane-signal +88 -21
- package/payload/lanes/container/lane_core.py +143 -19
- package/payload/lanes/container/throttle-pulse +9 -6
- package/payload/lanes/docs/ARCHITECTURE.md +41 -11
- package/payload/lanes/docs/INSTALL.md +10 -6
- package/payload/lanes/docs/RUNBOOK.md +115 -15
- package/payload/lanes/docs/USERGUIDE.md +77 -28
- package/payload/lanes/install.sh +2 -1
- package/payload/lanes/libexec/agent_state.py +90 -14
- package/payload/lanes/libexec/harness_muse.py +531 -0
- package/payload/lanes/libexec/lanes_lib.py +3 -2
- package/payload/lanes/libexec/sidebar.py +9 -6
- package/payload/lanes/libexec/statusd.py +16 -6
- package/payload/manifest.json +4 -3
|
@@ -49,6 +49,13 @@ then, after the turn's `agent.message.appended` dump, `agent.turn.ended{outcome:
|
|
|
49
49
|
`interaction.request{kind: approval}` answered by `interaction.resolved` of the same id (a `question` is the other kind,
|
|
50
50
|
and a cancelled turn can leave either unanswered); state.json drops `lastTurnReason` when a turn starts and sets it
|
|
51
51
|
(`completed|failed|cancelled`) when one ends.
|
|
52
|
+
|
|
53
|
+
W13 (spec `docs/superpowers/specs/2026-10-16-lanes-harness-observers-muse-zcode-design.md`): Muse Code and ZCode are
|
|
54
|
+
observed harnesses too (HARNESS_COMMS, MUSE_COMM_PREFIX). Muse Code's own files are read by libexec/harness_muse.py
|
|
55
|
+
through `muse_sessions` (its session log, joined to the pane's Muse pid by the `.session.lock` the process holds open);
|
|
56
|
+
its spinner title `⠙ <workspace>` was published `codex / working` before W13, because the walk did not know Muse and
|
|
57
|
+
the Codex title rule reads a spinner on an `unknown` pane (DEF-2030). ZCode keeps its sessions in SQLite and draws no
|
|
58
|
+
title and no bell: it is observed by its process (`zcode` / `idle`) and its hooks only.
|
|
52
59
|
"""
|
|
53
60
|
import importlib.util
|
|
54
61
|
import json
|
|
@@ -75,8 +82,15 @@ DWELL = {'needs': 8, 'working': 5, 'done': 5, None: 0}
|
|
|
75
82
|
GONE_DWELL = 5
|
|
76
83
|
|
|
77
84
|
# W12 harness identity (spec §2). The comm (/proc/<pid>/comm, at most 15 bytes) of each harness, and what it is called.
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
# W13 (spec 2026-10-16 §2): ZCode's CLI sets process.title `zcode-cli` (its comm); Muse Code's launcher is a script named
|
|
86
|
+
# `muse` that execs `muse-bin-<version>` (MEASURED 2026-10-16, muse 1.4.10: comm `muse-bin-1.4.10`, the 15-byte cut of
|
|
87
|
+
# the file `muse-bin-1.4.10-R6427.1`), so Muse is also a comm that starts with MUSE_COMM_PREFIX, or an executable whose
|
|
88
|
+
# file name does (MUSE_EXE_PREFIX). lane-signal's and lane_core's process_harness apply the same rule (pinned equal by
|
|
89
|
+
# test_agent_state_harness.ThreeWalksAgree).
|
|
90
|
+
HARNESS_COMMS = {'claude': 'claude', 'codex': 'codex', 'kimi': 'kimi', 'kimi-code': 'kimi', 'grok': 'grok',
|
|
91
|
+
'muse': 'muse', 'zcode-cli': 'zcode'}
|
|
92
|
+
MUSE_COMM_PREFIX = MUSE_EXE_PREFIX = 'muse-bin-'
|
|
93
|
+
AGENT_HARNESSES = ('claude', 'codex', 'kimi', 'grok', 'grok-cli', 'muse', 'zcode')
|
|
80
94
|
HARNESSES = AGENT_HARNESSES + ('shell', 'unknown') # the published `agent.harness` enum (DM-LANES-STATUS)
|
|
81
95
|
HARNESS_DEPTH = 4 # levels below the pane's root process walked for a harness
|
|
82
96
|
HARNESS_FANOUT = 64 # processes examined per pane at most: a fork bomb under a pane costs a bounded read
|
|
@@ -220,15 +234,22 @@ def _named(proc_root, pid, comm):
|
|
|
220
234
|
|
|
221
235
|
|
|
222
236
|
def _harness_of(proc_root, pid, comm):
|
|
223
|
-
"""The harness process `pid` (whose comm is `comm`) is, or None: a comm in HARNESS_COMMS (`_named`),
|
|
224
|
-
process that is not a shell —
|
|
237
|
+
"""The harness process `pid` (whose comm is `comm`) is, or None: a comm in HARNESS_COMMS (`_named`), a comm that
|
|
238
|
+
starts with MUSE_COMM_PREFIX (Muse Code, W13), else — for a process that is not a shell — by its executable: native
|
|
239
|
+
Claude Code (CLAUDE_EXE, DEF-1993) or a Muse binary (a file name starting with MUSE_EXE_PREFIX). A shell's exe is its
|
|
225
240
|
shell, so shells cost no readlink."""
|
|
226
241
|
if comm in HARNESS_COMMS:
|
|
227
242
|
return _named(proc_root, pid, comm)
|
|
228
243
|
if comm is None or comm.lstrip('-') in SHELL_COMMANDS:
|
|
229
244
|
return None
|
|
245
|
+
if comm.startswith(MUSE_COMM_PREFIX):
|
|
246
|
+
return 'muse'
|
|
230
247
|
exe = _exe(proc_root, pid)
|
|
231
|
-
|
|
248
|
+
if exe is None:
|
|
249
|
+
return None
|
|
250
|
+
if CLAUDE_EXE in exe:
|
|
251
|
+
return 'claude'
|
|
252
|
+
return 'muse' if exe.rpartition('/')[2].startswith(MUSE_EXE_PREFIX) else None
|
|
232
253
|
|
|
233
254
|
|
|
234
255
|
def harness_walk(proc_root, pid, kids=None):
|
|
@@ -776,10 +797,60 @@ def grok_title(pane_row, bell, since):
|
|
|
776
797
|
'pid': max(pid, 0), 'since': since, 'source': 'grok-title'}
|
|
777
798
|
|
|
778
799
|
|
|
779
|
-
def
|
|
780
|
-
"""
|
|
781
|
-
|
|
782
|
-
|
|
800
|
+
def _harness_muse():
|
|
801
|
+
"""libexec/harness_muse.py (W13) when it ships, else None: looked up like `_harness_grok`."""
|
|
802
|
+
module = sys.modules.get('harness_muse')
|
|
803
|
+
if module is None:
|
|
804
|
+
if importlib.util.find_spec('harness_muse') is None:
|
|
805
|
+
return None
|
|
806
|
+
import harness_muse as module
|
|
807
|
+
return module
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
def muse_sessions(root, panes, *, home=CONTAINER_HOME, now=None):
|
|
811
|
+
"""The Muse Code observer (W13): `harness_muse.muse_sessions(root, panes, home=home, now=now)` -> {pane key:
|
|
812
|
+
verdict}, or {} when the module does not ship. `root` the container's root as the host sees it, `panes` {pane key:
|
|
813
|
+
the pane's Muse pid} (statusd: the panes whose walk found `muse`). Kept: verdicts whose status is a vocabulary word."""
|
|
814
|
+
if not panes:
|
|
815
|
+
return {}
|
|
816
|
+
module = _harness_muse()
|
|
817
|
+
if module is None:
|
|
818
|
+
return {}
|
|
819
|
+
got = module.muse_sessions(root, panes, home=home, now=now)
|
|
820
|
+
if not isinstance(got, dict):
|
|
821
|
+
return {}
|
|
822
|
+
return {key: dict(verdict) for key, verdict in got.items()
|
|
823
|
+
if isinstance(verdict, dict) and verdict.get('status') in STATUSES}
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
def muse_title(pane_row, since):
|
|
827
|
+
"""merge's Muse title rung for a pane observed running Muse Code that no observer claimed:
|
|
828
|
+
`harness_muse.parse_muse_title` of its title (`⠙ <workspace>` working, any other title idle), shaped as a title
|
|
829
|
+
verdict (`source: muse-title`), or None. Muse rings no bell (MEASURED 2026-10-16), so the bell never makes it done."""
|
|
830
|
+
module = _harness_muse()
|
|
831
|
+
parse = getattr(module, 'parse_muse_title', None) if module is not None else None
|
|
832
|
+
parsed = parse(pane_row.get('pane_title')) if parse is not None else None
|
|
833
|
+
if not isinstance(parsed, dict) or parsed.get('status') not in STATUSES:
|
|
834
|
+
return None
|
|
835
|
+
try:
|
|
836
|
+
pid = int(pane_row.get('harness_pid') or 0)
|
|
837
|
+
except (TypeError, ValueError):
|
|
838
|
+
pid = 0
|
|
839
|
+
return {'harness': 'muse', 'status': parsed['status'], 'name': str(parsed.get('name') or ''), 'session_id': '',
|
|
840
|
+
'pid': max(pid, 0), 'since': since, 'source': 'muse-title'}
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
# Harnesses that ring no bell (MEASURED 2026-10-16: Muse Code 1.4.10 none; ZCode has no BEL path in its source): a bell
|
|
844
|
+
# flag on their pane came from something else (a tool's output), so it never turns their verdict into `done`.
|
|
845
|
+
BELL_LESS = ('muse', 'zcode')
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
def merge(pane_row, claude_by_pane, sidecar, kimi_by_pane=None, grok_by_pane=None, muse_by_pane=None):
|
|
849
|
+
"""Precedence: hook sidecar (event-driven, every harness) > Claude session file > Kimi session file > Muse session
|
|
850
|
+
file (`muse_sessions`, W13) > Grok observer (`grok_sessions`: its session file, else the title with its blink
|
|
851
|
+
memory) > the Grok title (`grok_title`, a pane observed as `grok` nothing above claimed) > the Muse title
|
|
852
|
+
(`muse_title`, a pane observed as `muse`) > Codex title > the observed harness itself > the bell (never for a
|
|
853
|
+
BELL_LESS harness).
|
|
783
854
|
Any Codex title — idle, spinner or Action Required, bell or not — over a bare-shell pane whose shell has no
|
|
784
855
|
child process (`pane_has_child is False`, statusd's row key) publishes `shell`: the harness ended and tmux kept
|
|
785
856
|
its last title, so no ghost idle/done/working/needs_approval. Unknown liveness leaves the title in charge.
|
|
@@ -794,22 +865,23 @@ def merge(pane_row, claude_by_pane, sidecar, kimi_by_pane=None, grok_by_pane=Non
|
|
|
794
865
|
nothing above claimed is that harness, `idle`, `done` with the bell (`source: process`, `pid` the harness pid statusd
|
|
795
866
|
found). Returns None when nothing says an agent is here — the sidebar then draws a plain shell."""
|
|
796
867
|
observed = pane_row.get('harness')
|
|
797
|
-
verdict = _verdict(pane_row, claude_by_pane, sidecar, kimi_by_pane or {}, grok_by_pane or {},
|
|
868
|
+
verdict = _verdict(pane_row, claude_by_pane, sidecar, kimi_by_pane or {}, grok_by_pane or {}, muse_by_pane or {},
|
|
869
|
+
observed)
|
|
798
870
|
if verdict is not None and observed in AGENT_HARNESSES:
|
|
799
871
|
verdict['harness'] = observed
|
|
800
872
|
return verdict
|
|
801
873
|
|
|
802
874
|
|
|
803
|
-
def _verdict(pane_row, claude_by_pane, sidecar, kimi_by_pane, grok_by_pane, observed):
|
|
875
|
+
def _verdict(pane_row, claude_by_pane, sidecar, kimi_by_pane, grok_by_pane, muse_by_pane, observed):
|
|
804
876
|
if sidecar and sidecar.get('status') in STATUSES:
|
|
805
877
|
return dict(sidecar, source=sidecar.get('source') or 'hook')
|
|
806
878
|
key = pane_key(pane_row)
|
|
807
879
|
if key in claude_by_pane:
|
|
808
880
|
return dict(claude_by_pane[key])
|
|
809
|
-
bell = pane_row.get('window_bell_flag') == '1'
|
|
881
|
+
bell = pane_row.get('window_bell_flag') == '1' and observed not in BELL_LESS
|
|
810
882
|
# The Kimi observer leaves the bell to merge; the Grok observer applies it itself (harness_grok: only a completed
|
|
811
|
-
# turn or an idle title is `done` — a cancelled or interrupted turn stays needs_input, spec §4).
|
|
812
|
-
for index, rings in ((kimi_by_pane, True), (grok_by_pane, False)):
|
|
883
|
+
# turn or an idle title is `done` — a cancelled or interrupted turn stays needs_input, spec §4); Muse rings none.
|
|
884
|
+
for index, rings in ((kimi_by_pane, True), (muse_by_pane, False), (grok_by_pane, False)):
|
|
813
885
|
found = index.get(key)
|
|
814
886
|
if isinstance(found, dict) and found.get('status') == GROK_UNKNOWN:
|
|
815
887
|
return None # seen, not joinable (Grok's leader mode): no guess below
|
|
@@ -826,6 +898,10 @@ def _verdict(pane_row, claude_by_pane, sidecar, kimi_by_pane, grok_by_pane, obse
|
|
|
826
898
|
found = grok_title(pane_row, bell, activity)
|
|
827
899
|
if found:
|
|
828
900
|
return found
|
|
901
|
+
if observed == 'muse':
|
|
902
|
+
found = muse_title(pane_row, activity)
|
|
903
|
+
if found:
|
|
904
|
+
return found
|
|
829
905
|
title = parse_codex_title(pane_row.get('pane_title'), observed)
|
|
830
906
|
if title:
|
|
831
907
|
if _harness_ended(pane_row):
|