@vibelord/lanes 1.0.0 → 1.0.2
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 +92 -7
- package/payload/lanes/VERSION +1 -1
- package/payload/lanes/bin/lanes +928 -150
- package/payload/lanes/client/ghostty/common.conf +3 -0
- package/payload/lanes/client/ghostty/lanes-palette-light.conf +25 -0
- package/payload/lanes/client/ghostty/lanes-palette.conf +25 -0
- package/payload/lanes/client/ghostty/omarchy.conf +3 -2
- package/payload/lanes/client/lanes-launch +62 -5
- package/payload/lanes/client/lanes-window +17 -1
- package/payload/lanes/container/env.sh +8 -0
- package/payload/lanes/container/hooks/claude-settings.json +10 -0
- package/payload/lanes/container/hooks/codex-hooks.json +22 -0
- package/payload/lanes/container/lane +326 -25
- package/payload/lanes/container/lane-signal +173 -0
- package/payload/lanes/docs/ARCHITECTURE.md +158 -6
- package/payload/lanes/docs/DESIGN.md +250 -0
- package/payload/lanes/docs/INSTALL.md +39 -0
- package/payload/lanes/docs/RUNBOOK.md +507 -24
- package/payload/lanes/docs/SHARING.md +1 -1
- package/payload/lanes/holders/exec.sh +2 -5
- package/payload/lanes/holders/ssh.sh +18 -0
- package/payload/lanes/holders.lock +3 -0
- package/payload/lanes/install/enable-phone.sh +15 -6
- package/payload/lanes/install/enable-web.sh +19 -6
- package/payload/lanes/install/host-install.sh +10 -1
- package/payload/lanes/install/services.py +30 -3
- package/payload/lanes/install.sh +108 -3
- package/payload/lanes/libexec/agent_state.py +246 -0
- package/payload/lanes/libexec/connection.py +178 -0
- package/payload/lanes/libexec/cursor_sync.py +147 -6
- package/payload/lanes/libexec/device_sync.py +134 -0
- package/payload/lanes/libexec/economy_poll.py +685 -0
- package/payload/lanes/libexec/guard.py +27 -1
- package/payload/lanes/libexec/lanes_lib.py +436 -36
- package/payload/lanes/libexec/notify.py +317 -0
- package/payload/lanes/libexec/registrations.py +148 -0
- package/payload/lanes/libexec/session_actions.py +53 -22
- package/payload/lanes/libexec/sidebar.py +1190 -186
- package/payload/lanes/libexec/statusd.py +352 -90
- package/payload/lanes/libexec/tokens.py +310 -0
- package/payload/lanes/libexec/tokens.sh +11 -0
- package/payload/lanes/libexec/view.sh +22 -11
- package/payload/lanes/systemd/lanes-economy.service +20 -0
- package/payload/lanes/systemd/lanes-notify.service +19 -0
- package/payload/lanes/systemd/lanes-sync.service +3 -0
- package/payload/lanes/web/README.md +96 -2
- package/payload/lanes/web/browser.css +22 -0
- package/payload/lanes/web/browser.js +73 -0
- package/payload/lanes/web/index.html +2 -1
- package/payload/lanes/web/mobile.css +108 -221
- package/payload/lanes/web/mobile.js +105 -55
- package/payload/lanes/workspaces.example/README.md +9 -3
- package/payload/lanes/zellij/config.kdl +128 -19
- package/payload/manifest.json +7 -3
- package/payload/lanes/libexec/legacy_sessions.py +0 -130
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
"""Track pre-persistence Docker shells without pretending their lost Docker connection is reattachable."""
|
|
2
|
-
import json
|
|
3
|
-
import os
|
|
4
|
-
import select
|
|
5
|
-
import signal
|
|
6
|
-
import sys
|
|
7
|
-
import time
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
import lanes_lib as L
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def process(pid):
|
|
14
|
-
root = Path(L.proc_root()) / str(pid)
|
|
15
|
-
stat = (root / 'stat').read_text().rsplit(') ', 1)[1].split()
|
|
16
|
-
return {'pid': int(pid), 'ppid': int(stat[1]), 'sid': int(stat[3]), 'tty': stat[4], 'start': stat[19],
|
|
17
|
-
'uid': root.stat().st_uid, 'cgroup': (root / 'cgroup').read_text(),
|
|
18
|
-
'cwd': os.readlink(root / 'cwd'), 'command': (root / 'comm').read_text().strip()}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def observe():
|
|
22
|
-
lanes, groups = {}, {}
|
|
23
|
-
for path in (L.STATE / 'legacy-sessions').glob('*.json'):
|
|
24
|
-
entry = L.read_json(path) or {}
|
|
25
|
-
identity = entry.get('legacy') or {}
|
|
26
|
-
try:
|
|
27
|
-
actual = process(identity['pid'])
|
|
28
|
-
if actual['start'] != identity['start']:
|
|
29
|
-
continue
|
|
30
|
-
state = {'state': 'quiet', 'pid': actual['pid'], 'cwd': actual['cwd'],
|
|
31
|
-
'command': actual['command'], 'legacy': True}
|
|
32
|
-
except (FileNotFoundError, ProcessLookupError, KeyError):
|
|
33
|
-
continue
|
|
34
|
-
except OSError:
|
|
35
|
-
state = {'state': 'unknown', 'observed': 'error', 'legacy': True}
|
|
36
|
-
lanes[entry['id']] = state
|
|
37
|
-
groups.setdefault(entry['workspace'], []).append(entry)
|
|
38
|
-
L.atomic_write(L.STATE / 'resolve.dynamic' / entry['id'],
|
|
39
|
-
'\n'.join(['exec', entry['container'], 'legacy', entry['id'], entry['name']]) + '\n')
|
|
40
|
-
return lanes, groups
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def close(entry):
|
|
44
|
-
identity = entry.get('legacy') or {}
|
|
45
|
-
recorded = L.read_json(L.STATE / 'legacy-sessions' / (entry['id'] + '.json')) or {}
|
|
46
|
-
if recorded.get('legacy') != identity:
|
|
47
|
-
raise L.LanesError('Legacy shell identity changed; nothing was closed')
|
|
48
|
-
try:
|
|
49
|
-
root = process(identity['pid'])
|
|
50
|
-
except (OSError, KeyError):
|
|
51
|
-
raise L.LanesError('Legacy shell could not be checked; nothing was closed')
|
|
52
|
-
if root['start'] != identity['start'] or root['uid'] != os.getuid():
|
|
53
|
-
raise L.LanesError('Legacy shell changed; nothing was closed')
|
|
54
|
-
members = {}
|
|
55
|
-
for path in Path(L.proc_root()).iterdir():
|
|
56
|
-
if not path.name.isdigit():
|
|
57
|
-
continue
|
|
58
|
-
try:
|
|
59
|
-
candidate = process(path.name)
|
|
60
|
-
if candidate['uid'] == root['uid'] and candidate['cgroup'] == root['cgroup']:
|
|
61
|
-
members[candidate['pid']] = candidate
|
|
62
|
-
except OSError:
|
|
63
|
-
continue
|
|
64
|
-
# The root may have exited and its PID been reused while we scanned /proc.
|
|
65
|
-
# Keep the recorded identity authoritative rather than adopting the scan's replacement.
|
|
66
|
-
if members.get(root['pid'], {}).get('start') != root['start']:
|
|
67
|
-
raise L.LanesError('Legacy shell changed or could not be checked; nothing was closed')
|
|
68
|
-
chosen = {root['pid']} | {pid for pid, p in members.items()
|
|
69
|
-
if p['sid'] == root['sid'] and p['tty'] == root['tty'] and root['tty'] != '0'}
|
|
70
|
-
while True:
|
|
71
|
-
children = {pid for pid, p in members.items() if p['ppid'] in chosen}
|
|
72
|
-
if children <= chosen:
|
|
73
|
-
break
|
|
74
|
-
chosen.update(children)
|
|
75
|
-
handles, root_verified = [], False
|
|
76
|
-
try:
|
|
77
|
-
for pid in chosen:
|
|
78
|
-
fd = None
|
|
79
|
-
try:
|
|
80
|
-
fd = os.pidfd_open(pid)
|
|
81
|
-
if process(pid)['start'] != members[pid]['start']:
|
|
82
|
-
os.close(fd)
|
|
83
|
-
continue
|
|
84
|
-
handles.append(fd)
|
|
85
|
-
if pid == root['pid']:
|
|
86
|
-
root_verified = True
|
|
87
|
-
except (OSError, KeyError):
|
|
88
|
-
if fd is not None:
|
|
89
|
-
os.close(fd)
|
|
90
|
-
continue
|
|
91
|
-
if not root_verified:
|
|
92
|
-
raise L.LanesError('Legacy shell could not be checked; nothing was closed')
|
|
93
|
-
# pidfds remain attached to these processes even if the kernel recycles their numeric pids.
|
|
94
|
-
for fd in handles:
|
|
95
|
-
try:
|
|
96
|
-
signal.pidfd_send_signal(fd, signal.SIGHUP)
|
|
97
|
-
except ProcessLookupError:
|
|
98
|
-
pass
|
|
99
|
-
remaining, deadline = set(handles), time.monotonic() + 2
|
|
100
|
-
while remaining and time.monotonic() < deadline:
|
|
101
|
-
ready, _, _ = select.select(list(remaining), [], [], 0.1)
|
|
102
|
-
remaining.difference_update(ready)
|
|
103
|
-
for fd in remaining:
|
|
104
|
-
try:
|
|
105
|
-
signal.pidfd_send_signal(fd, signal.SIGKILL)
|
|
106
|
-
except ProcessLookupError:
|
|
107
|
-
pass
|
|
108
|
-
finally:
|
|
109
|
-
for fd in handles:
|
|
110
|
-
os.close(fd)
|
|
111
|
-
(L.STATE / 'legacy-sessions' / (entry['id'] + '.json')).unlink(missing_ok=True)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def view(container, lane_id):
|
|
115
|
-
L._require(L.IDENT, lane_id, 'legacy shell')
|
|
116
|
-
entry = L.read_json(L.STATE / 'legacy-sessions' / (lane_id + '.json')) or {}
|
|
117
|
-
if entry.get('container') != container:
|
|
118
|
-
raise L.LanesError('Legacy shell no longer exists')
|
|
119
|
-
print('\033[2J\033[H\n ' + entry['name'] + '\n', flush=True)
|
|
120
|
-
print(' This shell predates persistent terminals. Its original Docker connection cannot be reattached.\n'
|
|
121
|
-
' Its shell and running jobs are preserved. Use × in Unregistered to terminate them.\n'
|
|
122
|
-
' New + creates a shell you can leave and reopen.\n', flush=True)
|
|
123
|
-
print(' Container: ' + container + '\n Process: ' + str(entry['legacy']['pid']), flush=True)
|
|
124
|
-
while True:
|
|
125
|
-
time.sleep(30)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if __name__ == '__main__':
|
|
129
|
-
if len(sys.argv) == 4 and sys.argv[1] == 'view':
|
|
130
|
-
view(sys.argv[2], sys.argv[3])
|