@oneciel-ai/claude-any 0.1.80 → 0.1.81
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/claude_any.py +114 -15
- package/package.json +1 -1
package/claude_any.py
CHANGED
|
@@ -104,7 +104,7 @@ OFFICIAL_CHANNEL_PLUGINS = {
|
|
|
104
104
|
"fakechat": "plugin:fakechat@claude-plugins-official",
|
|
105
105
|
}
|
|
106
106
|
APP_NAME = "Claude Any"
|
|
107
|
-
VERSION = "0.1.
|
|
107
|
+
VERSION = "0.1.81"
|
|
108
108
|
CREDITS = "Credits: One Ciel LLC"
|
|
109
109
|
|
|
110
110
|
LOG_LEVELS = {"SILENT": 0, "ERROR": 1, "WARN": 2, "INFO": 3, "DEBUG": 4, "TRACE": 5}
|
|
@@ -4481,14 +4481,35 @@ def _event_meta_from_sources(*sources: Any) -> dict[str, Any]:
|
|
|
4481
4481
|
"thread_id",
|
|
4482
4482
|
"parent_id",
|
|
4483
4483
|
"message_id",
|
|
4484
|
+
"event_id",
|
|
4485
|
+
"cursor",
|
|
4486
|
+
"sequence",
|
|
4487
|
+
"seq",
|
|
4484
4488
|
"task_id",
|
|
4485
4489
|
"round_id",
|
|
4490
|
+
"conversation_id",
|
|
4491
|
+
"session_id",
|
|
4486
4492
|
"agent_id",
|
|
4493
|
+
"agent_name",
|
|
4487
4494
|
"sender_id",
|
|
4495
|
+
"sender",
|
|
4488
4496
|
"recipient_id",
|
|
4497
|
+
"recipient",
|
|
4498
|
+
"recipients",
|
|
4499
|
+
"target_id",
|
|
4500
|
+
"target",
|
|
4489
4501
|
"type",
|
|
4490
4502
|
"event_type",
|
|
4491
4503
|
"kind",
|
|
4504
|
+
"timestamp",
|
|
4505
|
+
"created_at",
|
|
4506
|
+
"updated_at",
|
|
4507
|
+
"status",
|
|
4508
|
+
"priority",
|
|
4509
|
+
"title",
|
|
4510
|
+
"name",
|
|
4511
|
+
"model",
|
|
4512
|
+
"runtime",
|
|
4492
4513
|
):
|
|
4493
4514
|
value = source.get(key)
|
|
4494
4515
|
if value is not None and key not in meta:
|
|
@@ -4496,7 +4517,45 @@ def _event_meta_from_sources(*sources: Any) -> dict[str, Any]:
|
|
|
4496
4517
|
return meta
|
|
4497
4518
|
|
|
4498
4519
|
|
|
4499
|
-
def
|
|
4520
|
+
def _metadata_key_is_sensitive(key: str) -> bool:
|
|
4521
|
+
return bool(re.search(r"(authorization|api[_-]?key|token|secret|password|credential|cookie)", key, re.I))
|
|
4522
|
+
|
|
4523
|
+
|
|
4524
|
+
def _json_safe_metadata(value: Any, depth: int = 0) -> Any:
|
|
4525
|
+
if depth > 8:
|
|
4526
|
+
return "<max-depth>"
|
|
4527
|
+
if value is None or isinstance(value, (bool, int, float)):
|
|
4528
|
+
return value
|
|
4529
|
+
if isinstance(value, str):
|
|
4530
|
+
return value if len(value) <= 8000 else value[:8000] + "...<truncated>"
|
|
4531
|
+
if isinstance(value, list):
|
|
4532
|
+
out = [_json_safe_metadata(item, depth + 1) for item in value[:200]]
|
|
4533
|
+
if len(value) > 200:
|
|
4534
|
+
out.append(f"...<{len(value) - 200} more>")
|
|
4535
|
+
return out
|
|
4536
|
+
if isinstance(value, dict):
|
|
4537
|
+
out: dict[str, Any] = {}
|
|
4538
|
+
items = list(value.items())
|
|
4539
|
+
for key, item in items[:200]:
|
|
4540
|
+
skey = str(key)
|
|
4541
|
+
out[skey] = "[redacted]" if _metadata_key_is_sensitive(skey) else _json_safe_metadata(item, depth + 1)
|
|
4542
|
+
if len(items) > 200:
|
|
4543
|
+
out["..."] = f"<{len(items) - 200} more>"
|
|
4544
|
+
return out
|
|
4545
|
+
return str(value)
|
|
4546
|
+
|
|
4547
|
+
|
|
4548
|
+
def _compact_json_for_prompt(value: Any, max_chars: int = 2400) -> str:
|
|
4549
|
+
try:
|
|
4550
|
+
text = json.dumps(value, ensure_ascii=False, separators=(",", ":"), default=str)
|
|
4551
|
+
except Exception:
|
|
4552
|
+
text = str(value)
|
|
4553
|
+
if len(text) <= max_chars:
|
|
4554
|
+
return text
|
|
4555
|
+
return text[: max_chars - 16] + "...<truncated>"
|
|
4556
|
+
|
|
4557
|
+
|
|
4558
|
+
def _sse_payload_to_chat_payload(data_text: str, event_name: str, defaults: dict[str, Any], event_id: str | None = None) -> dict[str, Any] | None:
|
|
4500
4559
|
text = (data_text or "").strip()
|
|
4501
4560
|
if not text or text == "[DONE]":
|
|
4502
4561
|
return None
|
|
@@ -4512,6 +4571,8 @@ def _sse_payload_to_chat_payload(data_text: str, event_name: str, defaults: dict
|
|
|
4512
4571
|
"sse_event": event_name or "message",
|
|
4513
4572
|
"sse_source": defaults.get("name") or "",
|
|
4514
4573
|
}
|
|
4574
|
+
if event_id:
|
|
4575
|
+
meta["sse_id"] = event_id
|
|
4515
4576
|
content = text
|
|
4516
4577
|
kind = "sse"
|
|
4517
4578
|
method = str(event_name or "message")
|
|
@@ -4519,6 +4580,13 @@ def _sse_payload_to_chat_payload(data_text: str, event_name: str, defaults: dict
|
|
|
4519
4580
|
allowed_events = {str(item).strip() for item in event_filter if str(item).strip()} if isinstance(event_filter, list) else set()
|
|
4520
4581
|
if isinstance(parsed, dict):
|
|
4521
4582
|
method = str(parsed.get("method") or event_name or "message")
|
|
4583
|
+
meta["sse_json"] = _json_safe_metadata(parsed)
|
|
4584
|
+
if parsed.get("jsonrpc") is not None:
|
|
4585
|
+
meta["jsonrpc"] = parsed.get("jsonrpc")
|
|
4586
|
+
if parsed.get("id") is not None:
|
|
4587
|
+
meta["rpc_id"] = parsed.get("id")
|
|
4588
|
+
if parsed.get("method") is not None:
|
|
4589
|
+
meta["mcp_method"] = parsed.get("method")
|
|
4522
4590
|
params = parsed.get("params") if isinstance(parsed.get("params"), dict) else {}
|
|
4523
4591
|
payload = parsed.get("payload") if isinstance(parsed.get("payload"), dict) else {}
|
|
4524
4592
|
data = params.get("data") if isinstance(params.get("data"), dict) else {}
|
|
@@ -4614,7 +4682,7 @@ def _channel_sse_maybe_initialize_mcp(name: str, endpoint_text: str) -> None:
|
|
|
4614
4682
|
router_log("WARN", f"channel_sse_mcp_initialize_failed name={name} endpoint={endpoint} error={type(exc).__name__}: {exc}")
|
|
4615
4683
|
|
|
4616
4684
|
|
|
4617
|
-
def _channel_sse_dispatch(name: str, event_name: str, data_lines: list[str]) -> None:
|
|
4685
|
+
def _channel_sse_dispatch(name: str, event_name: str, data_lines: list[str], event_id: str | None = None) -> None:
|
|
4618
4686
|
data_text = "\n".join(data_lines)
|
|
4619
4687
|
if (event_name or "").strip().lower() == "endpoint":
|
|
4620
4688
|
_channel_sse_maybe_initialize_mcp(name, data_text)
|
|
@@ -4624,10 +4692,10 @@ def _channel_sse_dispatch(name: str, event_name: str, data_lines: list[str]) ->
|
|
|
4624
4692
|
if not state:
|
|
4625
4693
|
return
|
|
4626
4694
|
defaults = dict(state)
|
|
4627
|
-
payload = _sse_payload_to_chat_payload(data_text, event_name, defaults)
|
|
4695
|
+
payload = _sse_payload_to_chat_payload(data_text, event_name, defaults, event_id=event_id)
|
|
4628
4696
|
if not payload:
|
|
4629
4697
|
return
|
|
4630
|
-
append_chat_message(payload)
|
|
4698
|
+
saved = append_chat_message(payload)
|
|
4631
4699
|
now = time.strftime("%Y-%m-%dT%H:%M:%S")
|
|
4632
4700
|
with _CHANNEL_SSE_LOCK:
|
|
4633
4701
|
state = _CHANNEL_SSE_CONNECTIONS.get(name)
|
|
@@ -4635,6 +4703,10 @@ def _channel_sse_dispatch(name: str, event_name: str, data_lines: list[str]) ->
|
|
|
4635
4703
|
state["last_event_at"] = now
|
|
4636
4704
|
state["messages_received"] = int(state.get("messages_received") or 0) + 1
|
|
4637
4705
|
state["last_error"] = None
|
|
4706
|
+
router_log(
|
|
4707
|
+
"INFO",
|
|
4708
|
+
f"channel_sse_message_received name={name} event={event_name or 'message'} message_id={saved.get('id')} channel={saved.get('channel')}",
|
|
4709
|
+
)
|
|
4638
4710
|
|
|
4639
4711
|
|
|
4640
4712
|
def _channel_sse_worker(name: str) -> None:
|
|
@@ -4648,10 +4720,13 @@ def _channel_sse_worker(name: str) -> None:
|
|
|
4648
4720
|
read_timeout = max(5.0, min(3600.0, float(state.get("read_timeout_seconds") or 300.0)))
|
|
4649
4721
|
retry_seconds = max(1.0, min(60.0, float(state.get("retry_seconds") or 5.0)))
|
|
4650
4722
|
event_name = "message"
|
|
4723
|
+
event_id: str | None = None
|
|
4651
4724
|
data_lines: list[str] = []
|
|
4652
4725
|
try:
|
|
4653
4726
|
req = urllib.request.Request(url, headers={**headers, "Accept": "text/event-stream"})
|
|
4654
4727
|
with urllib.request.urlopen(req, timeout=read_timeout) as response:
|
|
4728
|
+
_channel_sse_set_state(name, last_error=None)
|
|
4729
|
+
router_log("INFO", f"channel_sse_connected name={name} url={url}")
|
|
4655
4730
|
while True:
|
|
4656
4731
|
with _CHANNEL_SSE_LOCK:
|
|
4657
4732
|
current = _CHANNEL_SSE_CONNECTIONS.get(name)
|
|
@@ -4663,8 +4738,9 @@ def _channel_sse_worker(name: str) -> None:
|
|
|
4663
4738
|
line = raw.decode("utf-8", errors="replace").rstrip("\r\n")
|
|
4664
4739
|
if not line:
|
|
4665
4740
|
if data_lines:
|
|
4666
|
-
_channel_sse_dispatch(name, event_name, data_lines)
|
|
4741
|
+
_channel_sse_dispatch(name, event_name, data_lines, event_id=event_id)
|
|
4667
4742
|
event_name = "message"
|
|
4743
|
+
event_id = None
|
|
4668
4744
|
data_lines = []
|
|
4669
4745
|
continue
|
|
4670
4746
|
if line.startswith(":"):
|
|
@@ -4676,6 +4752,8 @@ def _channel_sse_worker(name: str) -> None:
|
|
|
4676
4752
|
event_name = value or "message"
|
|
4677
4753
|
elif field == "data":
|
|
4678
4754
|
data_lines.append(value)
|
|
4755
|
+
elif field == "id":
|
|
4756
|
+
event_id = value
|
|
4679
4757
|
elif field == "retry":
|
|
4680
4758
|
try:
|
|
4681
4759
|
retry_seconds = max(1.0, min(60.0, int(value) / 1000.0))
|
|
@@ -4687,6 +4765,7 @@ def _channel_sse_worker(name: str) -> None:
|
|
|
4687
4765
|
if not state or not state.get("running"):
|
|
4688
4766
|
return
|
|
4689
4767
|
state["last_error"] = f"{type(exc).__name__}: {exc}"
|
|
4768
|
+
router_log("WARN", f"channel_sse_reconnect name={name} error={type(exc).__name__}: {exc}")
|
|
4690
4769
|
time.sleep(retry_seconds)
|
|
4691
4770
|
|
|
4692
4771
|
|
|
@@ -13612,22 +13691,31 @@ def format_channel_wake_prompt(message: dict[str, Any]) -> str:
|
|
|
13612
13691
|
fields.append(f"id={mid}")
|
|
13613
13692
|
if thread:
|
|
13614
13693
|
fields.append(f"thread={thread}")
|
|
13694
|
+
meta_text = f" metadata={_compact_json_for_prompt(meta)}" if meta else ""
|
|
13615
13695
|
return (
|
|
13616
13696
|
"[claude-any external channel message] "
|
|
13617
13697
|
+ " ".join(fields)
|
|
13618
|
-
+ f" text={json.dumps(body, ensure_ascii=False)}
|
|
13698
|
+
+ f" text={json.dumps(body, ensure_ascii=False)}"
|
|
13699
|
+
+ meta_text
|
|
13700
|
+
+ ". "
|
|
13619
13701
|
+ "If relevant to current work, respond or act now; otherwise keep working."
|
|
13620
13702
|
)
|
|
13621
13703
|
|
|
13622
13704
|
|
|
13623
|
-
def
|
|
13705
|
+
def _channel_wake_message_noise_reason(message: dict[str, Any]) -> str | None:
|
|
13624
13706
|
body = re.sub(r"\s+", " ", str(message.get("message") or "")).strip().lower()
|
|
13625
13707
|
kind = str(message.get("kind") or "").strip().lower()
|
|
13626
13708
|
if not body:
|
|
13627
|
-
return
|
|
13709
|
+
return "empty"
|
|
13628
13710
|
if kind in {"connection", "connected", "heartbeat", "keepalive"}:
|
|
13629
|
-
return
|
|
13630
|
-
|
|
13711
|
+
return kind
|
|
13712
|
+
if re.fullmatch(r"[a-z0-9_.:-]{1,80}\.(ws|sse)\.connected", body):
|
|
13713
|
+
return "transport_connected"
|
|
13714
|
+
return None
|
|
13715
|
+
|
|
13716
|
+
|
|
13717
|
+
def _channel_wake_message_is_noise(message: dict[str, Any]) -> bool:
|
|
13718
|
+
return _channel_wake_message_noise_reason(message) is not None
|
|
13631
13719
|
|
|
13632
13720
|
|
|
13633
13721
|
def format_channel_wake_batch_prompt(messages: list[dict[str, Any]]) -> str:
|
|
@@ -13645,7 +13733,8 @@ def format_channel_wake_batch_prompt(messages: list[dict[str, Any]]) -> str:
|
|
|
13645
13733
|
fields = [f"id={mid}", f"room={room}", f"from={sender}"]
|
|
13646
13734
|
if thread:
|
|
13647
13735
|
fields.append(f"thread={thread}")
|
|
13648
|
-
|
|
13736
|
+
meta_text = f" metadata={_compact_json_for_prompt(meta)}" if meta else ""
|
|
13737
|
+
parts.append("(" + " ".join(fields) + ") " + json.dumps(body, ensure_ascii=False) + meta_text)
|
|
13649
13738
|
return (
|
|
13650
13739
|
f"[claude-any external channel messages] {len(messages)} new messages: "
|
|
13651
13740
|
+ " ; ".join(parts)
|
|
@@ -13674,7 +13763,12 @@ def _inject_pending_channel_messages(master_fd: int, last_id: int) -> int:
|
|
|
13674
13763
|
last_id = max(last_id, int(message.get("id") or 0))
|
|
13675
13764
|
except Exception:
|
|
13676
13765
|
continue
|
|
13677
|
-
|
|
13766
|
+
noise_reason = _channel_wake_message_noise_reason(message)
|
|
13767
|
+
if noise_reason:
|
|
13768
|
+
router_log(
|
|
13769
|
+
"INFO",
|
|
13770
|
+
f"channel_stdin_proxy_skipped_noise message_id={message.get('id')} channel={message.get('channel')} reason={noise_reason}",
|
|
13771
|
+
)
|
|
13678
13772
|
continue
|
|
13679
13773
|
pending.append(message)
|
|
13680
13774
|
if pending:
|
|
@@ -13703,15 +13797,15 @@ def subprocess_call_with_channel_wake_proxy(cmd: list[str], env: dict[str, str])
|
|
|
13703
13797
|
import termios
|
|
13704
13798
|
import tty
|
|
13705
13799
|
|
|
13800
|
+
last_id = _chat_init_next_id() - 1
|
|
13801
|
+
last_channel_marker = _chat_messages_file_marker()
|
|
13706
13802
|
master_fd, slave_fd = pty.openpty()
|
|
13707
13803
|
proc = subprocess.Popen(cmd, stdin=slave_fd, stdout=slave_fd, stderr=slave_fd, env=env, close_fds=True)
|
|
13708
13804
|
os.close(slave_fd)
|
|
13709
13805
|
stdin_fd = sys.stdin.fileno()
|
|
13710
13806
|
stdout_fd = sys.stdout.fileno()
|
|
13711
13807
|
old_attrs = termios.tcgetattr(stdin_fd)
|
|
13712
|
-
last_id = _chat_init_next_id() - 1
|
|
13713
13808
|
last_channel_poll = 0.0
|
|
13714
|
-
last_channel_marker = _chat_messages_file_marker()
|
|
13715
13809
|
try:
|
|
13716
13810
|
tty.setraw(stdin_fd)
|
|
13717
13811
|
while proc.poll() is None:
|
|
@@ -13776,7 +13870,12 @@ def _mcp_proxy_notification_payload(server_name: str, message: dict[str, Any]) -
|
|
|
13776
13870
|
meta: dict[str, Any] = {
|
|
13777
13871
|
"mcp_server": server_name,
|
|
13778
13872
|
"mcp_method": method,
|
|
13873
|
+
"mcp_json": _json_safe_metadata(message),
|
|
13779
13874
|
}
|
|
13875
|
+
if message.get("jsonrpc") is not None:
|
|
13876
|
+
meta["jsonrpc"] = message.get("jsonrpc")
|
|
13877
|
+
if message.get("id") is not None:
|
|
13878
|
+
meta["rpc_id"] = message.get("id")
|
|
13780
13879
|
meta.update(_event_meta_from_sources(message, params, payload, data, event))
|
|
13781
13880
|
content = (
|
|
13782
13881
|
_event_payload_text(params)
|
package/package.json
CHANGED