Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions deploy/docker-compose.work.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ x-probe-proxy: &probe-proxy
HTTP_PROXY: ${HOOKPROBE_EGRESS_PROXY:-http://egress-proxy:8888}
HTTPS_PROXY: ${HOOKPROBE_EGRESS_PROXY:-http://egress-proxy:8888}
NO_PROXY: ${HOOKPROBE_NO_PROXY:-127.0.0.1,localhost,host.docker.internal,hookrelay,sink,egress-proxy,mcp-gate,watch-signer}
# No instance metadata service exists on the probes' network, and their AWS
# credentials are files. Without this, an AWS CLI call whose profile names no
# region asks 169.254.169.254 for one through the proxy above, and each ask
# lands in the egress ledger as a refusal that is nobody's attempt to leave —
# a second deployment running copies of these files saw six in one session.
# The CLI now says "specify a region" instead; the posture check skips the
# metadata question for the same reason.
AWS_EC2_METADATA_DISABLED: "true"

services:
hookrelay:
Expand Down
35 changes: 35 additions & 0 deletions deploy/watch/tests/test_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,38 @@ def test_a_bot_that_says_so_in_its_name_never_reaches_the_digest(monkeypatch):
)
found = watch_scan.scan_chat(chat, {}, watch_scan.when("2026-10-27T02:10:00Z"), [])
assert [(name, [who for _, who, _ in msgs]) for name, msgs in found] == [("ops room", ["Talbot"])]


class _Feeds:
"""A chat service with the given feeds; records every search it is asked."""

def __init__(self, feeds):
self.feeds, self.asked = feeds, []

def call(self, tool, **args):
if tool == "list_folder_feeds":
return {"feeds": self.feeds}
assert tool == "search_chat_records", tool
self.asked.append(args)
return {"messages": []}


def test_a_stale_cursor_asks_for_twice_as_much_up_to_the_servers_ceiling(monkeypatch):
"""A Monday's first round: the weekend group's cursor is Friday's evening."""
now = watch_scan.when("2026-11-02T01:40:00Z")
feeds = [
{"name": "weekend group", "chat_type_label": "group chat", "last_message_send_at": "2026-11-01T07:00:00Z"},
{"name": "weekday group", "chat_type_label": "group chat", "last_message_send_at": "2026-11-02T01:30:00Z"},
]
cursors = {
"weekend group": watch_scan.when("2026-10-30T11:20:00Z"),
"weekday group": watch_scan.when("2026-11-02T01:00:00Z"),
}
monkeypatch.setattr(watch_scan, "PER_CHAT", 60)
chat = _Feeds(feeds)
watch_scan.scan_chat(chat, dict(cursors), now, [])
assert {a["contact_name"]: a["limit"] for a in chat.asked} == {"weekend group": 120, "weekday group": 60}
monkeypatch.setattr(watch_scan, "PER_CHAT", 150)
chat = _Feeds(feeds)
watch_scan.scan_chat(chat, dict(cursors), now, [])
assert {a["contact_name"]: a["limit"] for a in chat.asked} == {"weekend group": 200, "weekday group": 150}
13 changes: 10 additions & 3 deletions deploy/watch/watch_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@
# 60 而不是 12:截断要罕见到算异常。上限是 200,一个 20 分钟的窗口里 60 条已经
# 是刷屏了。真截断了下面会明说,因为结果是新的在前,被丢掉的是更早的那些。
PER_CHAT = int(os.environ.get("WATCH_MAX_PER_CHAT") or 60)
# 游标比这还旧的会话(周末之后、长假之后、刚出现的会话)积压的不是一轮的量:
# 按常量取,取满了就把更早的丢掉。另一套跑着这些文件副本的部署,在两个周一的首轮
# 各有两个会话「一次取满 60 条」。这种会话单次多取一倍,仍不超过服务端对 limit 的
# 上限;工作日的常规轮次不变。
STALE_CURSOR_SECONDS = 36 * 3600
CHAT_LIMIT_MAX = 200 # search_chat_records 的 limit 上限,超过会被整个调用拒掉
# 服务端对这个参数的上限就是 100,传 200 会被整个调用拒掉——而一次被拒的
# 列表调用意味着这一轮完全没看聊天。
FEED_LIMIT = int(os.environ.get("WATCH_FEED_LIMIT") or 100)
Expand Down Expand Up @@ -396,13 +402,14 @@ def scan_chat(
continue
# 到期放行。取完(或取失败走 unreachable)都在下面清 pending。

limit = PER_CHAT if now - base < STALE_CURSOR_SECONDS else min(PER_CHAT * 2, CHAT_LIMIT_MAX)
args = {
"contact_name": name,
# from_time 是闭区间。原样传游标会把「设定这个游标的那条消息」再取
# 一遍——2026-09-04 就是这么把一条五小时前的 @All 当成新消息发出去的。
"from_time": str(int(base) + 1),
"to_time": str(int(now)),
"limit": PER_CHAT,
"limit": limit,
}
if feed.get("chat_type_label") == "group chat":
args["search_group"] = True
Expand Down Expand Up @@ -467,9 +474,9 @@ def scan_chat(
unreachable.pop(name, None) # 又够得着了;下次再断会重新说一次
if pending is not None:
pending.pop(name, None) # 攒的一批已经端出去了;下一批重新起算
if len(got) >= PER_CHAT:
if len(got) >= limit:
notes.append(
f"{name}: 一次取满 {PER_CHAT} 条,更早的可能没取到(结果是新的在前,游标仍推进到最新,否则会一直重报)"
f"{name}: 一次取满 {limit} 条,更早的可能没取到(结果是新的在前,游标仍推进到最新,否则会一直重报)"
)
if picked:
findings.append((name, picked))
Expand Down
2 changes: 1 addition & 1 deletion hookjudge/requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ librt==0.16.0
license-expression==30.4.4
markdown-it-py==4.2.0
mdurl==0.1.2
msgpack==1.2.1
msgpack==1.2.4
mypy==2.4.0
mypy_extensions==1.1.0
opentelemetry-api==1.45.1
Expand Down
2 changes: 1 addition & 1 deletion hookprobe/requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ markdown-it-py==4.2.0
mcp==2.3.0
mcp-types==2.3.0
mdurl==0.1.2
msgpack==1.2.1
msgpack==1.2.4
mypy==2.4.0
mypy_extensions==1.1.0
opentelemetry-api==1.45.1
Expand Down
2 changes: 1 addition & 1 deletion hookrelay/requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ librt==0.16.0
license-expression==30.4.4
markdown-it-py==4.2.0
mdurl==0.1.2
msgpack==1.2.1
msgpack==1.2.4
mypy==2.4.0
mypy_extensions==1.1.0
opentelemetry-api==1.45.1
Expand Down
Loading