pi-multi-viewers 0.4.0 → 0.5.1
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/AGENTS.md +48 -14
- package/README.md +10 -6
- package/docs/design.md +75 -13
- package/docs/reviews/2026-09-14-e2e24-extension-policy-review.md +146 -0
- package/docs/reviews/2026-09-14-e2e25-doc-drift-review.md +188 -0
- package/docs/reviews/README.md +2 -0
- package/docs/test-methodology.md +33 -0
- package/meeting_fs.py +155 -2
- package/meeting_loop.py +69 -27
- package/mv_cli.py +1 -0
- package/observability.py +43 -2
- package/package.json +1 -1
- package/prompts/multi-viewers.md +1 -1
- package/spec_gen.py +11 -7
- package/start_discussion.py +16 -10
package/observability.py
CHANGED
|
@@ -120,8 +120,6 @@ def check_status(base):
|
|
|
120
120
|
return "not-exists"
|
|
121
121
|
# 读路径统一走 fs.run_git(quotepath 加固单点;run_cmd 只做一次性
|
|
122
122
|
# 环境命令——init/clone/config/push)
|
|
123
|
-
# 读路径统一走 fs.run_git(quotepath 加固单点;run_cmd 只做一次性
|
|
124
|
-
# 环境命令——init/clone/config/push)
|
|
125
123
|
agents = meeting_fs.read_protocol(bare).get("participants", [])
|
|
126
124
|
# "收尾完成"判据**单源** = human_viewer.is_finished(concluded 且
|
|
127
125
|
# HEAD:result.md 有效)——与 viewer 的 done 同一判据(§3.5-P5:此前
|
|
@@ -327,6 +325,9 @@ def build_report(base):
|
|
|
327
325
|
f"{_dur(d['max_ms'] // 1000)} | rc≠0 {d['fails']} 次")
|
|
328
326
|
|
|
329
327
|
# ---- LLM 运行事实(session 文档化字段;流式预过滤,不整文件解析) ----
|
|
328
|
+
# ---- 扩展策略(声明 vs 生效)----
|
|
329
|
+
_report_extension_line(base, out)
|
|
330
|
+
|
|
330
331
|
# ---- 终止原因(只报事实与计数)----
|
|
331
332
|
_report_termination(base, agents, out)
|
|
332
333
|
|
|
@@ -618,6 +619,46 @@ def _report_termination(base, agents, out):
|
|
|
618
619
|
+ (" | result.md 已提交" if has_result else ""))
|
|
619
620
|
|
|
620
621
|
|
|
622
|
+
def _report_extension_line(base, out):
|
|
623
|
+
"""扩展策略:**声明 vs 生效**(+ 降级原因)——与"档位:声明 max | 生效 max"同型。
|
|
624
|
+
|
|
625
|
+
取数(只读已有家,零新增记录):
|
|
626
|
+
· 声明 = `protocol.json.extensionPolicy`(无该字段的旧产物 → n/a)
|
|
627
|
+
· 生效 = loop log 的**登记行**(首唤打一次):
|
|
628
|
+
`扩展策略: 声明=<x> 生效=<y> strict=<0|1>[ 降级原因=<reason>]`
|
|
629
|
+
为什么需要它:降级本来是**可见的**(loop log 一行),但产品面(报告)看不到
|
|
630
|
+
——2026-09-14 为确认"MC 生效没有"花了好几轮探针 + DB 查询(e2e24 评审 E2)。
|
|
631
|
+
fail-open:读不到 → 显示 n/a,不报错。
|
|
632
|
+
"""
|
|
633
|
+
bare = meeting_fs.bare_of_base(base)
|
|
634
|
+
proto = meeting_fs.read_protocol(bare)
|
|
635
|
+
declared = proto.get("extensionPolicy")
|
|
636
|
+
eff = None
|
|
637
|
+
for f in sorted(glob.glob(os.path.join(base, "loop-*.log"))):
|
|
638
|
+
try:
|
|
639
|
+
txt = open(f, encoding="utf-8", errors="replace").read()
|
|
640
|
+
except OSError:
|
|
641
|
+
continue
|
|
642
|
+
m = re.search(r"扩展策略: 声明=(\S+) 生效=(\S+) strict=(\d)"
|
|
643
|
+
r"(?: 降级原因=(.*))?", txt)
|
|
644
|
+
if m:
|
|
645
|
+
eff = m
|
|
646
|
+
break
|
|
647
|
+
if not declared and not eff:
|
|
648
|
+
out.append("扩展策略:n/a(协议无字段、日志无登记行——多为 2026-09-14 前的产物)")
|
|
649
|
+
return
|
|
650
|
+
if eff:
|
|
651
|
+
d, e, strict, reason = eff.group(1), eff.group(2), eff.group(3), eff.group(4)
|
|
652
|
+
line = f"扩展策略:声明 {d} | 生效 {e}(strict={strict}"
|
|
653
|
+
line += f",降级:{reason.strip()}" if reason else ""
|
|
654
|
+
line += ")"
|
|
655
|
+
if d != e:
|
|
656
|
+
line += " ⚠ 生效≠声明(降级:ctx_search 不可用)"
|
|
657
|
+
out.append(line)
|
|
658
|
+
else:
|
|
659
|
+
out.append(f"扩展策略:声明 {declared} | 生效 n/a(日志中无登记行)")
|
|
660
|
+
|
|
661
|
+
|
|
621
662
|
def _report_wake_fields(base):
|
|
622
663
|
"""解析 loop-*.log 的登记字段(`elapsed_ms` / `rc`)。
|
|
623
664
|
|
package/package.json
CHANGED
package/prompts/multi-viewers.md
CHANGED
package/spec_gen.py
CHANGED
|
@@ -17,8 +17,7 @@ import sys
|
|
|
17
17
|
|
|
18
18
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
19
19
|
TPL_DIR = os.path.join(HERE, "templates")
|
|
20
|
-
|
|
21
|
-
os.path.expanduser("~/.pi/agent"))
|
|
20
|
+
# pi agent 目录的**单一实现**在 meeting_fs.pi_agent_dir()(F9:此前两处各拼一次)
|
|
22
21
|
MAX_AGENT_NAME_LEN = 32
|
|
23
22
|
|
|
24
23
|
import meeting_fs
|
|
@@ -56,7 +55,7 @@ def _default_model():
|
|
|
56
55
|
获取失败(pi 不可用/无 settings)→ 返回 None。
|
|
57
56
|
"""
|
|
58
57
|
try:
|
|
59
|
-
with open(os.path.join(
|
|
58
|
+
with open(os.path.join(meeting_fs.pi_agent_dir(), "settings.json")) as f:
|
|
60
59
|
cfg = json.load(f)
|
|
61
60
|
provider = cfg.get("defaultProvider") or ""
|
|
62
61
|
model = cfg.get("defaultModel") or ""
|
|
@@ -123,7 +122,7 @@ def pi_sessions_dir(cwd):
|
|
|
123
122
|
AGENTS.md 明言"编码错一根横线 = 静默解析不到"——风险点不应复制。
|
|
124
123
|
"""
|
|
125
124
|
enc = "--" + cwd.strip("/").replace("/", "-") + "--"
|
|
126
|
-
return os.path.join(
|
|
125
|
+
return os.path.join(meeting_fs.pi_agent_dir(), "sessions", enc)
|
|
127
126
|
|
|
128
127
|
|
|
129
128
|
def current_session_file():
|
|
@@ -370,7 +369,8 @@ def gen_question(topic, stances, background, questions):
|
|
|
370
369
|
return "\n".join(lines)
|
|
371
370
|
|
|
372
371
|
|
|
373
|
-
def gen_protocol(topic, participants, max_meeting, max_rr,
|
|
372
|
+
def gen_protocol(topic, participants, max_meeting, max_rr,
|
|
373
|
+
extension_policy=meeting_fs.DEFAULT_EXTENSION_POLICY,
|
|
374
374
|
result_writer=None,
|
|
375
375
|
stall_timeout=DEFAULT_STALL_TIMEOUT,
|
|
376
376
|
fork_source=None, fork_cwd=None,
|
|
@@ -388,8 +388,12 @@ def gen_protocol(topic, participants, max_meeting, max_rr, extensions=False,
|
|
|
388
388
|
"stallTimeoutSeconds": stall_timeout,
|
|
389
389
|
"commitPolicy": "one-message-per-commit",
|
|
390
390
|
}
|
|
391
|
-
#
|
|
392
|
-
|
|
391
|
+
# 显式写出扩展策略(值域与默认值的家在 meeting_fs;此处只写选定值)。
|
|
392
|
+
# 非法值就地报错(不落盘半成品)——与 forkMode 同款:配置错误不进运行时
|
|
393
|
+
if extension_policy not in meeting_fs.EXTENSION_POLICIES:
|
|
394
|
+
raise ValueError(f"非法 extension_policy: {extension_policy!r}"
|
|
395
|
+
f"(合法值: {'/'.join(meeting_fs.EXTENSION_POLICIES)})")
|
|
396
|
+
proto["extensionPolicy"] = extension_policy
|
|
393
397
|
if fork_source:
|
|
394
398
|
# fork 模式(多视角):首唤挂载主 session + cwd=主项目
|
|
395
399
|
# forkMode 取值域与默认值的定义在 meeting_fs(FORK_MODES /
|
package/start_discussion.py
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
用法:
|
|
5
5
|
python3 start_discussion.py --dir mymeet --topic "主题" --agents a,b \
|
|
6
|
-
[--stances '{"a": "立场1", "b": "立场2"}'] [--start]
|
|
6
|
+
[--stances '{"a": "立场1", "b": "立场2"}'] [--start] \
|
|
7
|
+
[--extension-policy none|mc-tools|all] \
|
|
7
8
|
[--models '{"a": "provider/model"}'] [--max-meeting 10] [--max-rr 7]
|
|
8
9
|
|
|
9
10
|
复杂内容用 spec 规格目录(设计 16,与 CLI 内容参数互斥):
|
|
@@ -350,7 +351,7 @@ def setup_environment(args, participants, base, spec_dir=None,
|
|
|
350
351
|
# 共享配置(work-a 提交,setup commit 进 bare)
|
|
351
352
|
with open(os.path.join(wa, "protocol.json"), "w") as f:
|
|
352
353
|
json.dump(gen_protocol(spec_topic or args.topic, participants, args.max_meeting,
|
|
353
|
-
args.max_rr, args.
|
|
354
|
+
args.max_rr, args.extension_policy, args.result_writer,
|
|
354
355
|
args.stall_timeout,
|
|
355
356
|
fork_source=getattr(args, "fork_source", None),
|
|
356
357
|
fork_cwd=os.getcwd(),
|
|
@@ -420,7 +421,7 @@ def setup_environment(args, participants, base, spec_dir=None,
|
|
|
420
421
|
print(f"[setup] 环境就绪: {base}({len(participants)} agents: {', '.join(participants)})")
|
|
421
422
|
print(f"[setup] resultWriter={rw}, maxMeeting={args.max_meeting}, maxRR={args.max_rr}, "
|
|
422
423
|
f"立场={'有' if (args.stances or spec_dir) else '无'}, "
|
|
423
|
-
f"
|
|
424
|
+
f"extensionPolicy={args.extension_policy}")
|
|
424
425
|
|
|
425
426
|
|
|
426
427
|
def _preserve_result_md(base):
|
|
@@ -512,7 +513,7 @@ from spec_gen import ( # noqa: F401
|
|
|
512
513
|
viewer_set_error, gen_agents_md, gen_protocol,
|
|
513
514
|
pi_sessions_dir, current_session_file, _join_model_ref,
|
|
514
515
|
_default_model, _detect_pi_model_thinking, resolve_fork_source,
|
|
515
|
-
|
|
516
|
+
MAX_AGENT_NAME_LEN,
|
|
516
517
|
)
|
|
517
518
|
from observability import ( # noqa: F401
|
|
518
519
|
_loop_pids, _loops_alive, check_status, build_report,
|
|
@@ -550,9 +551,13 @@ def main():
|
|
|
550
551
|
"protocol.json,各 agent 首唤由本地生成 fork "
|
|
551
552
|
"源挂载主上下文;不传 = 从主 pi 环境自动解析"
|
|
552
553
|
"(PI_SESSION_ID;解析失败明确报错)")
|
|
553
|
-
parser.add_argument(
|
|
554
|
-
|
|
555
|
-
|
|
554
|
+
parser.add_argument(
|
|
555
|
+
"--extension-policy",
|
|
556
|
+
choices=list(meeting_fs.EXTENSION_POLICIES),
|
|
557
|
+
default=meeting_fs.DEFAULT_EXTENSION_POLICY,
|
|
558
|
+
help="agents 的扩展策略:mc-tools=默认,只要 MC 的只读检索工具 "
|
|
559
|
+
"ctx_search(缺 MC 时可见降级为零扩展);none=零扩展(零依赖);"
|
|
560
|
+
"all=走 pi 默认发现")
|
|
556
561
|
parser.add_argument("--start", action="store_true", help="创建后启动讨论")
|
|
557
562
|
parser.add_argument("--skip-setup", action="store_true",
|
|
558
563
|
help="跳过环境生成,只启动已有环境(需 --dir)")
|
|
@@ -704,12 +709,13 @@ def main():
|
|
|
704
709
|
workdir = os.path.join(base, f"work-{p}")
|
|
705
710
|
cmd = [sys.executable, os.path.join(base, "meeting_loop.py"),
|
|
706
711
|
workdir, p]
|
|
707
|
-
|
|
708
|
-
|
|
712
|
+
# 扩展策略**不转发**:协议字段(gen_protocol 恒写)是唯一权威,
|
|
713
|
+
# 转发只会多一条通道(S3)。例外语义已写进 decision 20:旧协议
|
|
714
|
+
# 目录(无该字段)按默认档启动。
|
|
709
715
|
# 配额(max-meeting/max-rr/stall-timeout)是环境属性:创建时
|
|
710
716
|
# 固化在 protocol.json,启动继承(loop 读 protocol 优先)。
|
|
711
717
|
# 不传 CLI —— 避免无条件覆盖 protocol.json 的固化值
|
|
712
|
-
# (审核 C1:配额单一事实源;与
|
|
718
|
+
# (审核 C1:配额单一事实源;与 extension-policy 处理一致)
|
|
713
719
|
with open(os.path.join(base, f"loop-{p}.log"), "w") as f:
|
|
714
720
|
procs.append(subprocess.Popen(cmd, stdout=f,
|
|
715
721
|
stderr=subprocess.STDOUT,
|