ring-cli 0.11.1__tar.gz → 0.11.2__tar.gz
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.
- {ring_cli-0.11.1 → ring_cli-0.11.2}/PKG-INFO +4 -1
- {ring_cli-0.11.1 → ring_cli-0.11.2}/README.en.md +3 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/pyproject.toml +1 -1
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/registry.py +47 -7
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/sources/__init__.py +5 -0
- ring_cli-0.11.2/src/ring/sources/claude_code.py +46 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/tui.py +29 -0
- ring_cli-0.11.1/src/ring/sources/claude_code.py +0 -23
- {ring_cli-0.11.1 → ring_cli-0.11.2}/LICENSE +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/__init__.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/__main__.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/cli.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/commands/__init__.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/commands/_args.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/commands/completion.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/commands/digest.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/commands/doctor.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/commands/focus.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/commands/gc.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/commands/hook.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/commands/stats.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/config.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/focus/__init__.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/focus/applescript.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/focus/base.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/focus/iterm2.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/focus/linux_wm.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/focus/neovim.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/focus/terminal.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/focus/tmux.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/gc.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/hook.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/hook_protocol.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/i18n.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/ipc.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/labels.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/locale/en/LC_MESSAGES/ring.mo +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/locale/en/LC_MESSAGES/ring.po +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/locale/ring.pot +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/notify/__init__.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/notify/base.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/notify/command.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/notify/notify_send.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/notify/ntfy.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/notify/osascript_notifier.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/notify/terminal_notifier.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/notify/webhook.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/osascript.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/permission.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/plugins.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/sources/base.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/sources/codex.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/sources/hook_registry.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/stats.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/transcript.py +0 -0
- {ring_cli-0.11.1 → ring_cli-0.11.2}/src/ring/watcher.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ring-cli
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.2
|
|
4
4
|
Summary: RiNG — Realtime Instance Notification Grid for active agent CLI sessions.
|
|
5
5
|
Keywords: claude-code,codex,tui,dashboard,session,monitor,rich
|
|
6
6
|
Author: Wei Lee
|
|
@@ -157,6 +157,7 @@ Completes subcommands, flags, and `config set` keys; `ring focus` prompts for a
|
|
|
157
157
|
Use `↑/↓` to select, `Enter` / `Space` to jump, `p` to reply to a permission request in place, `n` to name a session, `a` to toggle ended sessions, `dd` to hide a session (it reappears automatically once it has new activity), `r` to refresh, and `q` to quit.
|
|
158
158
|
If you have vim muscle memory like I do, `j/k` move up/down and `g/G` jump to the first/last row.
|
|
159
159
|
When the selected row is 🔴 waiting, a line under the table shows **what it is concretely waiting for** (the command to run, the question asked; hook mode only).
|
|
160
|
+
Claude Code background agents carry a `⚙` badge. They have no terminal to jump to, so selecting one shows a `claude --resume` hint; completed agents are folded into ended sessions by default and remain available via `a`.
|
|
160
161
|
- Otherwise: Rich polling; without Rich, plain text.
|
|
161
162
|
|
|
162
163
|
### Jump To A Session
|
|
@@ -181,6 +182,8 @@ ones with a "from the … agent" header), and lists the numbered options verbati
|
|
|
181
182
|
After you pick one, RiNG **captures the screen again** to confirm the dialog is still there and
|
|
182
183
|
unchanged (it may have been answered while you were deciding), only then sends that single
|
|
183
184
|
digit, and re-checks that the dialog actually disappeared.
|
|
185
|
+
Once the reply is verified, the TUI immediately clears that waiting revision. Only a newer hook
|
|
186
|
+
event, such as another permission request, can mark the session as waiting again.
|
|
184
187
|
|
|
185
188
|
- **Sessions inside tmux**: reads the screen with `tmux capture-pane` and sends keys with
|
|
186
189
|
`tmux send-keys`.
|
|
@@ -132,6 +132,7 @@ Completes subcommands, flags, and `config set` keys; `ring focus` prompts for a
|
|
|
132
132
|
Use `↑/↓` to select, `Enter` / `Space` to jump, `p` to reply to a permission request in place, `n` to name a session, `a` to toggle ended sessions, `dd` to hide a session (it reappears automatically once it has new activity), `r` to refresh, and `q` to quit.
|
|
133
133
|
If you have vim muscle memory like I do, `j/k` move up/down and `g/G` jump to the first/last row.
|
|
134
134
|
When the selected row is 🔴 waiting, a line under the table shows **what it is concretely waiting for** (the command to run, the question asked; hook mode only).
|
|
135
|
+
Claude Code background agents carry a `⚙` badge. They have no terminal to jump to, so selecting one shows a `claude --resume` hint; completed agents are folded into ended sessions by default and remain available via `a`.
|
|
135
136
|
- Otherwise: Rich polling; without Rich, plain text.
|
|
136
137
|
|
|
137
138
|
### Jump To A Session
|
|
@@ -156,6 +157,8 @@ ones with a "from the … agent" header), and lists the numbered options verbati
|
|
|
156
157
|
After you pick one, RiNG **captures the screen again** to confirm the dialog is still there and
|
|
157
158
|
unchanged (it may have been answered while you were deciding), only then sends that single
|
|
158
159
|
digit, and re-checks that the dialog actually disappeared.
|
|
160
|
+
Once the reply is verified, the TUI immediately clears that waiting revision. Only a newer hook
|
|
161
|
+
event, such as another permission request, can mark the session as waiting again.
|
|
159
162
|
|
|
160
163
|
- **Sessions inside tmux**: reads the screen with `tmux capture-pane` and sends keys with
|
|
161
164
|
`tmux send-keys`.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# import 的 module(見 [tool.uv.build-backend] module-name)與 CLI 指令仍是 `ring`。
|
|
3
3
|
[project]
|
|
4
4
|
name = "ring-cli"
|
|
5
|
-
version = "0.11.
|
|
5
|
+
version = "0.11.2"
|
|
6
6
|
description = "RiNG — Realtime Instance Notification Grid for active agent CLI sessions."
|
|
7
7
|
readme = "README.en.md"
|
|
8
8
|
requires-python = ">=3.13"
|
|
@@ -633,6 +633,24 @@ def running_claude_pids() -> list[int]:
|
|
|
633
633
|
return pids
|
|
634
634
|
|
|
635
635
|
|
|
636
|
+
def running_foreground_claude_pids() -> list[int]:
|
|
637
|
+
"""目前仍有可聚焦終端的 Claude session pid。
|
|
638
|
+
|
|
639
|
+
``running_claude_pids`` 也包含 agents mode 的背景 session,因為 scan 需要靠它們
|
|
640
|
+
找到對應 transcript;但 hook registry 不能拿背景 agent 的 cwd/數量替同專案的
|
|
641
|
+
舊前景 row 證明存活,否則一個 agent 就可能讓 crash 數小時的 session 繼續顯示。
|
|
642
|
+
"""
|
|
643
|
+
bg_ids = background_agent_session_ids()
|
|
644
|
+
if not bg_ids:
|
|
645
|
+
return running_claude_pids()
|
|
646
|
+
args_by_pid = {pid: args for pid, args, _is_bg_host in _parse_ps_claude_lines(_ps_claude_snapshot())}
|
|
647
|
+
return [
|
|
648
|
+
pid
|
|
649
|
+
for pid in running_claude_pids()
|
|
650
|
+
if (session_id := _arg_session_id(args_by_pid.get(pid, ""))) is None or session_id not in bg_ids
|
|
651
|
+
]
|
|
652
|
+
|
|
653
|
+
|
|
636
654
|
def background_agent_session_ids() -> set[str]:
|
|
637
655
|
"""所有背景 agent(``--bg-pty-host`` 承載且已載入真 session)的 session-id 集合。
|
|
638
656
|
|
|
@@ -686,13 +704,16 @@ def running_codex_pids() -> list[int]:
|
|
|
686
704
|
if 0.0 <= now - _codex_pids_cache[0] <= _SUBPROCESS_CACHE_TTL:
|
|
687
705
|
return _codex_pids_cache[1]
|
|
688
706
|
try:
|
|
689
|
-
out = subprocess.run(["ps", "-Ao", "pid
|
|
707
|
+
out = subprocess.run(["ps", "-Ao", "pid=,comm=,args="], capture_output=True, text=True, timeout=3).stdout
|
|
690
708
|
except (OSError, subprocess.SubprocessError):
|
|
691
709
|
out = ""
|
|
692
710
|
pids: list[int] = []
|
|
693
|
-
for line in out.splitlines()
|
|
694
|
-
parts = line.split(None,
|
|
695
|
-
if len(parts)
|
|
711
|
+
for line in out.splitlines():
|
|
712
|
+
parts = line.split(None, 2)
|
|
713
|
+
if len(parts) >= 2 and os.path.basename(parts[1].strip()) == "codex":
|
|
714
|
+
args = parts[2] if len(parts) == 3 else ""
|
|
715
|
+
if _is_codex_internal_process(args):
|
|
716
|
+
continue
|
|
696
717
|
try:
|
|
697
718
|
pids.append(int(parts[0]))
|
|
698
719
|
except ValueError:
|
|
@@ -701,6 +722,18 @@ def running_codex_pids() -> list[int]:
|
|
|
701
722
|
return pids
|
|
702
723
|
|
|
703
724
|
|
|
725
|
+
def _is_codex_internal_process(args: str) -> bool:
|
|
726
|
+
"""Codex app 為工具呼叫啟動的同名內部 process,不是獨立互動 session。"""
|
|
727
|
+
tokens = args.split()
|
|
728
|
+
if not tokens:
|
|
729
|
+
return False
|
|
730
|
+
try:
|
|
731
|
+
command_index = next(i for i, token in enumerate(tokens) if os.path.basename(token) == "codex")
|
|
732
|
+
except StopIteration:
|
|
733
|
+
return False
|
|
734
|
+
return command_index + 1 < len(tokens) and tokens[command_index + 1] in {"app-server", "sandbox"}
|
|
735
|
+
|
|
736
|
+
|
|
704
737
|
def running_agent_pids() -> list[int]:
|
|
705
738
|
"""所有內建來源看得到的 live agent CLI 行程。"""
|
|
706
739
|
return [*running_claude_pids(), *running_codex_pids()]
|
|
@@ -776,14 +809,15 @@ def _pid_tty(pid: int) -> str:
|
|
|
776
809
|
|
|
777
810
|
|
|
778
811
|
def _claude_procs() -> list[tuple[str, str]]:
|
|
779
|
-
"""
|
|
812
|
+
"""每個可聚焦的前景 Claude:(cwd, tty)。cwd 判活躍/分流,tty 給終端跳轉。
|
|
780
813
|
|
|
781
|
-
|
|
814
|
+
背景 agent 不在這裡按 cwd 配對;它們由 source 依 process args 的 session id 精準
|
|
815
|
+
認領,避免背景 process 的啟動 cwd 讓同專案舊 transcript 誤判成仍在場。
|
|
782
816
|
同一個 cwd 可能同時開好幾個 session,所以之後在 cwd 群組裡只有 mtime 最新的
|
|
783
817
|
這幾個算活著,其餘同專案的舊 session=已離場。
|
|
784
818
|
"""
|
|
785
819
|
procs: list[tuple[str, str]] = []
|
|
786
|
-
for pid in
|
|
820
|
+
for pid in running_foreground_claude_pids():
|
|
787
821
|
cwd = _pid_cwd(pid)
|
|
788
822
|
if cwd:
|
|
789
823
|
procs.append((cwd, _pid_tty(pid)))
|
|
@@ -1153,11 +1187,17 @@ def _hook_sessions(
|
|
|
1153
1187
|
proc_ttys.setdefault(key, set()).add(tty)
|
|
1154
1188
|
proc_cwds_by_provider.setdefault(pk, []).append(real_cwd)
|
|
1155
1189
|
|
|
1190
|
+
# 背景 agent 的 process 沒有可聚焦終端,不能拿來替同 cwd 的舊前景 hook row
|
|
1191
|
+
# 證明存活;它只精準認領 args 裡明載的 session id。Claude scan 仍使用包含背景
|
|
1192
|
+
# agent 的 session-id 配對,因此沒有 hook row 的 agent 也不會從看板消失。
|
|
1193
|
+
background_ids = background_agent_session_ids()
|
|
1156
1194
|
rows_by_key: dict[tuple[str, str], list[Session]] = {}
|
|
1157
1195
|
for s in out:
|
|
1158
1196
|
pk = _canonical_provider(s.provider)
|
|
1159
1197
|
if pk not in _PROVIDER_PROCS:
|
|
1160
1198
|
continue # 沒有 proc 偵測器 → 無法驗活性 → fail-open,交給 SessionEnd
|
|
1199
|
+
if pk == "claude-code" and s.session_id in background_ids:
|
|
1200
|
+
continue
|
|
1161
1201
|
rows_by_key.setdefault((pk, _real(s.cwd)), []).append(s)
|
|
1162
1202
|
|
|
1163
1203
|
for key, rows in rows_by_key.items():
|
|
@@ -60,6 +60,11 @@ def discover_sessions() -> list[Session]:
|
|
|
60
60
|
for s in found:
|
|
61
61
|
if s.session_id in bg_agent_ids:
|
|
62
62
|
s.kind = "agent"
|
|
63
|
+
# 背景 agent 跑完後不會像前景 session 一樣停在終端等下一輪輸入;它只會由
|
|
64
|
+
# daemon 暫留 process。繼續顯示成「跑完停著」既無法跳轉也無事可處理,反而
|
|
65
|
+
# 把已完成工作混進在場清單。歸入 ENDED 後預設收起,`--all` / TUI 的 a 仍可查。
|
|
66
|
+
if s.status is Status.IDLE:
|
|
67
|
+
s.status = Status.ENDED
|
|
63
68
|
|
|
64
69
|
bound_targets = registry._tmux_pane_targets()
|
|
65
70
|
process_targets = registry._tmux_process_tree_targets(found)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Claude Code:掃 ``~/.claude/projects`` 的 JSONL。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
import ring.registry as registry
|
|
8
|
+
from ring.registry import Session
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _activate_background_agents(sessions: list[Session], agent_ids: set[str]) -> None:
|
|
12
|
+
"""只依明確 session id 恢復仍有 process 的背景 agent,不用它們的 cwd 猜前景活性。"""
|
|
13
|
+
now = time.time()
|
|
14
|
+
for session in sessions:
|
|
15
|
+
if session.session_id not in agent_ids:
|
|
16
|
+
continue
|
|
17
|
+
idle = now - session.last_active
|
|
18
|
+
session.status = registry._scan_status(idle)
|
|
19
|
+
session.status = registry._apply_waiting(
|
|
20
|
+
session.status,
|
|
21
|
+
idle,
|
|
22
|
+
session._tail_kind,
|
|
23
|
+
registry.WAITING_WINDOW_SECONDS,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ClaudeCodeSource:
|
|
28
|
+
name = "claude-code"
|
|
29
|
+
|
|
30
|
+
def discover(self) -> list[Session]:
|
|
31
|
+
# 背景 agent 的 process cwd 可能仍是 daemon/啟動目錄,不能拿來按 cwd 猜哪些
|
|
32
|
+
# 前景 transcript 活著。前景先只用可聚焦 process 判活,背景再按 session id 認領。
|
|
33
|
+
procs = registry._claude_procs()
|
|
34
|
+
agent_ids = registry.background_agent_session_ids()
|
|
35
|
+
merged: dict[str, Session] = {}
|
|
36
|
+
scanned = registry._scan_sessions(procs)
|
|
37
|
+
_activate_background_agents(scanned, agent_ids)
|
|
38
|
+
for s in scanned:
|
|
39
|
+
merged.setdefault(s.session_id, s)
|
|
40
|
+
existing = list(merged.values())
|
|
41
|
+
for s in registry._synthetic_sessions(procs, existing):
|
|
42
|
+
merged.setdefault(s.session_id, s) # 合成列只填無 row 的 cwd
|
|
43
|
+
return list(merged.values())
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
source = ClaudeCodeSource()
|
|
@@ -197,6 +197,9 @@ class RingApp(App[None]):
|
|
|
197
197
|
self._own_tty: str = self._detect_own_tty()
|
|
198
198
|
self._delete_armed_sid: str | None = None
|
|
199
199
|
self._delete_armed_until: float = 0.0
|
|
200
|
+
# p 已確認終端對話框消失,但 provider 尚未送後續 hook 時,別讓同一筆舊
|
|
201
|
+
# PermissionRequest 又把列拉回 WAITING。value 是已回覆那筆 row 的 last_active revision。
|
|
202
|
+
self._permission_acks: dict[str, float] = {}
|
|
200
203
|
|
|
201
204
|
@staticmethod
|
|
202
205
|
def _detect_own_tty() -> str:
|
|
@@ -345,6 +348,7 @@ class RingApp(App[None]):
|
|
|
345
348
|
# 退回去跳 session 自己的終端(scan 模式常沒 tty → 跳轉失敗)。
|
|
346
349
|
write_tui_presence()
|
|
347
350
|
self._sessions = board(self._show_all)
|
|
351
|
+
self._apply_permission_acks()
|
|
348
352
|
table = self.query_one(DataTable)
|
|
349
353
|
# cursor 快照要在「任何」clear 之前取:clear(columns=True) 會把 cursor_row reset 成 0。
|
|
350
354
|
cursor = table.cursor_row
|
|
@@ -389,6 +393,30 @@ class RingApp(App[None]):
|
|
|
389
393
|
self._update_detail()
|
|
390
394
|
self._poll_focus_request()
|
|
391
395
|
|
|
396
|
+
def _apply_permission_acks(self) -> None:
|
|
397
|
+
"""以終端回覆結果蓋過同一 revision 的 stale WAITING,直到 provider 送來新事件。"""
|
|
398
|
+
present_ids = {s.session_id for s in self._sessions}
|
|
399
|
+
for session_id in set(self._permission_acks) - present_ids:
|
|
400
|
+
del self._permission_acks[session_id]
|
|
401
|
+
|
|
402
|
+
for s in self._sessions:
|
|
403
|
+
acknowledged_revision = self._permission_acks.get(s.session_id)
|
|
404
|
+
if acknowledged_revision is None:
|
|
405
|
+
continue
|
|
406
|
+
if s.last_active > acknowledged_revision:
|
|
407
|
+
# 新 hook event(可能是下一個權限請求)已到,重新採信 provider 狀態。
|
|
408
|
+
del self._permission_acks[s.session_id]
|
|
409
|
+
continue
|
|
410
|
+
if s.status is Status.WAITING:
|
|
411
|
+
# 終端畫面已證實原對話框消失;provider 尚未回報後續狀態時先視為處理中。
|
|
412
|
+
s.status = Status.WORKING
|
|
413
|
+
s.waiting_kind = ""
|
|
414
|
+
s.waiting_detail = ""
|
|
415
|
+
else:
|
|
416
|
+
# provider 已用同 revision 清掉 WAITING,不再需要本地 acknowledgment。
|
|
417
|
+
del self._permission_acks[s.session_id]
|
|
418
|
+
self._sessions.sort(key=lambda s: (s.status.rank, s.idle_for))
|
|
419
|
+
|
|
392
420
|
def _update_detail(self) -> None:
|
|
393
421
|
"""detail 列:選中的 session 在 🔴 等什麼(hook 有給具體內容才顯示)。
|
|
394
422
|
|
|
@@ -541,6 +569,7 @@ class RingApp(App[None]):
|
|
|
541
569
|
if outcome is permission.ReplyOutcome.OK:
|
|
542
570
|
if s.session_id == self._focused_sid:
|
|
543
571
|
self._focused_sid = None # 已就地回覆,解除通知標記
|
|
572
|
+
self._permission_acks[s.session_id] = s.last_active
|
|
544
573
|
self._toast(_("→ {project}:已回覆權限({option})", project=name, option=option), ok=True)
|
|
545
574
|
self._reload()
|
|
546
575
|
return
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"""Claude Code:掃 ``~/.claude/projects`` 的 JSONL。"""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import ring.registry as registry
|
|
6
|
-
from ring.registry import Session
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class ClaudeCodeSource:
|
|
10
|
-
name = "claude-code"
|
|
11
|
-
|
|
12
|
-
def discover(self) -> list[Session]:
|
|
13
|
-
procs = registry._claude_procs()
|
|
14
|
-
merged: dict[str, Session] = {}
|
|
15
|
-
for s in registry._scan_sessions(procs):
|
|
16
|
-
merged.setdefault(s.session_id, s)
|
|
17
|
-
existing = list(merged.values())
|
|
18
|
-
for s in registry._synthetic_sessions(procs, existing):
|
|
19
|
-
merged.setdefault(s.session_id, s) # 合成列只填無 row 的 cwd
|
|
20
|
-
return list(merged.values())
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
source = ClaudeCodeSource()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|