pi-multi-viewers 0.2.1 → 0.3.0

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/scripts/mv.sh CHANGED
@@ -1,325 +1,17 @@
1
1
  #!/usr/bin/env bash
2
2
  #
3
- # pi-multi-viewers wrapper——多视角协同分析(fork 主 session + meeting 协议)
3
+ # mv.sh —— 多视角协同分析的**稳定入口**(shim)。
4
4
  #
5
- # 用法:
6
- # ./scripts/mv.sh --prepare "<主题>" [--agents "a,b,c"|4]
7
- # ./scripts/mv.sh --start <spec目录> # 可选 --fork-mode compaction|budget|full
8
- # ./scripts/mv.sh --status <dir>
9
- # ./scripts/mv.sh --report <dir>
10
- # ./scripts/mv.sh --wait <dir>
11
- # ./scripts/mv.sh --cleanup <dir>
12
- # ./scripts/mv.sh --view <dir> [--since <ref>] # human-viewer 封装
13
- # ./scripts/mv.sh --say <dir> "<文本>" # human-sayer 封装
5
+ # 逻辑在 mv_cli.py(见该文件首注释:为什么这层从 364 行 bash 收敛为 Python——
6
+ # bash 陷阱产出的真实 bug "零行覆盖"的盲区)。本文件只做一件事:找到仓库
7
+ # 根、把展示名($0 语义)与参数交给 mv_cli.py。
8
+ #
9
+ # **路径不能变**:prompt / README / 用户习惯都引用 scripts/mv.sh
14
10
  set -u
15
-
16
11
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
12
  ROOT_DIR="$(dirname "$SCRIPT_DIR")"
18
- PYTHON="${PYTHON:-python3}"
19
- START_DISCUSSION="$ROOT_DIR/start_discussion.py"
20
- HUMAN_VIEWER="$ROOT_DIR/human_viewer.py"
21
- HUMAN_SAYER="$ROOT_DIR/human_sayer.py"
22
-
23
-
24
- usage() {
25
- cat <<'USAGE_EOF'
26
- 用法:
27
- $0 --prepare "<问题>" [--background "<背景>"] [--agents "a,b,c"|4]
28
- $0 --start <spec目录> [--fork-mode compaction|budget|full]
29
- $0 --status <dir>
30
- $0 --report <dir>
31
- $0 --wait <dir>
32
- $0 --cleanup <dir>
33
- $0 --view <dir> [--since <ref>]
34
- $0 --say <dir> "<文本>"
35
-
36
- 默认参数:
37
- agents=a,b,c max-meeting=10 max-rr=7 # 配额默认值的权威在 python argparse(wrapper 不传)
38
-
39
- --agents: 逗号分隔名称列表(如 "x,y")或纯数字(如 4 → 生成 a..d);
40
- human 是保留名,不能作为参与者
41
-
42
- human 通道:
43
- --view: 增量查看分析进展(--since 之后的新消息+状态;末尾输出 HEAD=<hash>,主 pi 记录作下轮 --since)
44
- --say: 插话(写一条 human 消息,agents 可见并可回应)
45
- USAGE_EOF
46
- }
47
-
48
- fail() {
49
- echo "错误: $*" >&2
50
- exit 1
51
- }
52
-
53
-
54
-
55
- require_dir() {
56
- local dir="$1"
57
- [ -n "$dir" ] || fail "缺少目录参数"
58
- [ -d "$dir" ] || fail "目录不存在: $dir"
59
- }
60
-
61
- # 目录参数规范化:裸名(无路径符)会被 start_discussion 加 discussion-
62
- # 前缀导致找错目录(实测 2026-09-03)——所有消费命令入口统一转绝对路径
63
- normalize_dir() {
64
- readlink -f "$1"
65
- }
66
-
67
- cmd_status() {
68
- local dir
69
- dir="$(normalize_dir "$1")"
70
- require_dir "$dir"
71
- "$PYTHON" "$START_DISCUSSION" --dir "$dir" --status
72
- }
73
-
74
- cmd_report() {
75
- local dir
76
- dir="$(normalize_dir "$1")"
77
- require_dir "$dir"
78
- "$PYTHON" "$START_DISCUSSION" --dir "$dir" --report
79
- }
80
-
81
- cmd_wait() {
82
- local dir
83
- dir="$(normalize_dir "$1")"
84
- require_dir "$dir"
85
- "$PYTHON" "$START_DISCUSSION" --dir "$dir" --wait
86
- }
87
-
88
- cmd_cleanup() {
89
- local dir
90
- dir="$(normalize_dir "$1")"
91
- require_dir "$dir"
92
- "$PYTHON" "$START_DISCUSSION" --dir "$dir" --cleanup
93
- }
94
-
95
- cmd_view() {
96
- local dir since=""
97
- dir="$(normalize_dir "$1")"
98
- shift
99
- while [ "$#" -gt 0 ]; do
100
- case "$1" in
101
- --since)
102
- [ "$#" -ge 2 ] || fail "--since 需要一个值"
103
- since="$2"
104
- shift 2
105
- ;;
106
- *)
107
- fail "未知参数: $1(--view 只接受 --since)"
108
- ;;
109
- esac
110
- done
111
- require_dir "$dir"
112
- [ -d "$dir/repo.git" ] || fail "分析不存在: $dir(无 repo.git)"
113
- if [ -n "$since" ]; then
114
- "$PYTHON" "$HUMAN_VIEWER" "$dir" --since "$since"
115
- else
116
- "$PYTHON" "$HUMAN_VIEWER" "$dir"
117
- fi
118
- # HEAD 游标(主 pi 记录作下轮 --since;无状态,不写游标文件——
119
- # 与 --follow 的 .viewer-cursor 互不干扰)
120
- echo "HEAD=$(git -C "$dir/repo.git" rev-parse HEAD)"
121
- }
122
-
123
- cmd_say() {
124
- local dir text="${2:-}"
125
- dir="$(normalize_dir "$1")"
126
- require_dir "$dir"
127
- [ -d "$dir/work-human" ] || fail "分析缺少 work-human: $dir"
128
- [ -n "$text" ] || fail "插话文本不能为空"
129
- "$PYTHON" "$HUMAN_SAYER" "$dir" "$text"
130
- }
131
-
132
- # 定位主 pi 的 session 文件:优先 PI_SESSION_FILE,否则用当前 cwd 编码路径查找
133
- # 读取主 pi 的 model/thinking(用户 2026-08-31:aft 不再替换 bash 后
134
- # 环境变量可用且是当前生效值——优先环境变量,session 文件解析仅为兜底)
135
- cmd_prepare() {
136
- local topic="" background="" agents_list=""
137
- if [ "$#" -lt 1 ]; then
138
- usage >&2
139
- exit 2
140
- fi
141
- topic="$1"
142
- shift
143
- while [ "$#" -gt 0 ]; do
144
- case "$1" in
145
- --background)
146
- [ "$#" -ge 2 ] || fail "--background 需要一个值"
147
- background="$2"
148
- shift 2
149
- ;;
150
- --agents)
151
- [ "$#" -ge 2 ] || fail "--agents 需要一个值(逗号分隔名称列表或数字)"
152
- agents_list="$2"
153
- shift 2
154
- ;;
155
- -h|--help)
156
- usage
157
- exit 0
158
- ;;
159
- *)
160
- fail "未知参数: $1(--prepare 只接受 <问题>、--background、--agents)"
161
- ;;
162
- esac
163
- done
164
- [ -n "$topic" ] || fail "问题不能为空"
165
-
166
- # agents 解析(数字展开/human/名字校验)全部收归 python
167
- # _parse_agents(2026-09-09 结构收敛:bash = 调度与展示)
168
- # viewers 模式(无 --agents):校验 + 快照已全部移入 python
169
- # _snapshot_viewers(铁律 1 职责边界;用户 2026-09-09:不合规根本
170
- # 不应该开始 spec-gen,校验在骨架生成之前失败)
171
- local snapshot_mode=0
172
- [ -z "$agents_list" ] && snapshot_mode=1
173
-
174
- local stamp
175
- stamp="$(date +%Y%m%d-%H%M%S)"
176
- local spec_dir="$PWD/mv-spec-${stamp}"
177
- # (目录不预创建——python 校验失败 = 零产物:预创建会让"不合规
178
- # 零产物"失效,实测 2026-09-09;成功时 gen_spec_skeleton 自建)
179
-
180
- # 骨架生成 = start_discussion --spec-gen(唯一实现):agents/ 三态
181
- # (显式占位骨架 / viewers 校验+快照 / 无)全部在 python 侧,bash
182
- # 与 CLI 直用产出一致;viewers 校验失败时 python 退出非 0(不合规
183
- # 根本不应该开始 spec-gen)
184
- local skeleton_args=(--spec-gen "$spec_dir" --topic "$topic")
185
- [ -n "$background" ] && skeleton_args+=(--background "$background")
186
- if [ "$snapshot_mode" -eq 0 ]; then
187
- skeleton_args+=(--agents "$agents_list")
188
- fi
189
- "$PYTHON" "$START_DISCUSSION" "${skeleton_args[@]}" || {
190
- fail "spec 骨架生成失败(start_discussion --spec-gen,见上方错误)"
191
- }
192
-
193
- cat <<OUTPUT_EOF
194
- 已生成分析 spec:
195
- $spec_dir
196
-
197
- 请查看/编辑该目录,补充背景、各 agent 视角等。
198
- 编辑完成后,告诉我"继续",我会自动启动分析。
199
- OUTPUT_EOF
200
- }
201
-
202
- cmd_start() {
203
- local spec_dir="$1"
204
- shift # 余参 = 透传给 python 的选项(如 --fork-mode X)
205
- require_dir "$spec_dir"
206
- [ -f "$spec_dir/question.md" ] || fail "spec 缺少 question.md: $spec_dir"
207
-
208
- local session_id="${PI_SESSION_ID:-}"
209
- local stamp
210
- stamp="$(date +%Y%m%d-%H%M%S)"
211
- # 目录前缀 mv-:与 pi-agents-helper 的 discuss-<sid>-* **命名空间隔离**
212
- # ——两个系统的 extension 都按"同 sid 最新目录"发现目标,共用前缀会
213
- # 在同 session 并发两套时互相插错(且 human 消息格式兼容 → 静默写错)。
214
- # 前缀也是产品语言的统一(本项目用"分析",不再沿用"discuss")。
215
- local dir_name="mv"
216
- if [ -n "$session_id" ]; then
217
- dir_name="${dir_name}-${session_id}"
218
- fi
219
- dir_name="${dir_name}-${stamp}"
220
- local dir_path="$PWD/$dir_name"
221
-
222
- # fork 源解析已收归 python(resolve_fork_source:env + sessions 编码
223
- # glob,2026-09-09 结构收敛;解析失败 python 内部报错退出)
224
- # 第 1 步:创建分析环境(不启动;fork 源自动解析)
225
- # 配额(max-meeting/max-rr)是环境属性:唯一默认在 python argparse
226
- # (10/7),创建时固化 protocol.json——wrapper 不传(W1 双源分叉修复:
227
- # 此前 wrapper 5 与 python 7 不一致,两条入口 RR 上限差 40%)
228
- # 余参哑转发(P0,e2e10 评审:wrapper 不得静默丢弃参数——值识别/默认值
229
- # 的唯一家在 python;本层只转发)
230
- if ! "$PYTHON" "$START_DISCUSSION" --dir "$dir_path" --spec "$spec_dir" "$@"; then
231
- fail "环境创建失败,请查看上方输出"
232
- fi
233
-
234
- # spec 已被消费:只删**本工具生成的形态**(mv-spec-*)——用户自建的
235
- # spec 目录不动(可能是有价值的视角快照)。删除前提示一行(此前静默
236
- # 删除:spec 是视角任务书/背景/models 在用户侧的唯一副本,删了不可恢复)
237
- case "$(basename "$spec_dir")" in
238
- mv-spec-*)
239
- echo "[start] spec 已消费,删除(本工具生成形态): $spec_dir"
240
- rm -rf "$spec_dir"
241
- ;;
242
- *)
243
- echo "[start] spec 已消费(保留未删——非 mv-spec-* 形态): $spec_dir"
244
- ;;
245
- esac
246
-
247
- # 第 3 步:启动已有环境
248
- if ! "$PYTHON" "$START_DISCUSSION" --dir "$dir_path" --skip-setup --start; then
249
- fail "启动失败,请查看上方输出"
250
- fi
251
-
252
- cat <<OUTPUT_EOF
253
- 多视角分析已启动
254
- 目录: $dir_path
255
-
256
- 观看分析(复制执行,不进 LLM;Ctrl-C 中断后可插话再续看):
257
- !!python3 "$ROOT_DIR/human_viewer.py" $dir_path --follow
258
-
259
- 查看进展: $0 --view $dir_path
260
- 插话: $0 --say $dir_path "<文本>"
261
- 查看状态: $0 --status $dir_path
262
- 等待完成: $0 --wait $dir_path
263
- 清理: $0 --cleanup $dir_path
264
-
265
- 说明:
266
- - human 通道:--view 增量查看(主 pi 记录末尾 HEAD 作下轮 --since);
267
- --say 插话(agents 可见并可回应;已冻结 agent 不响应)
268
- - 完成后 result.md 自动保存到固定位置:$dir_path-result.md(与分析目录同级——resultWriter loop 退出时保存;cleanup 也会保存)
269
- - 读取 result.md 摘要后请执行 --cleanup 清理分析目录
270
- OUTPUT_EOF
271
- }
272
-
273
- if [ "$#" -ge 1 ]; then
274
- case "$1" in
275
- --prepare)
276
- shift
277
- cmd_prepare "$@"
278
- exit $?
279
- ;;
280
- --start)
281
- [ "$#" -ge 2 ] || fail "--start 需要 spec 目录参数"
282
- shift
283
- cmd_start "$@"
284
- exit $?
285
- ;;
286
- --status)
287
- [ "$#" -ge 2 ] || fail "--status 需要分析目录参数"
288
- cmd_status "$2"
289
- exit $?
290
- ;;
291
- --report)
292
- [ "$#" -ge 2 ] || fail "--report 需要分析目录参数"
293
- cmd_report "$2"
294
- exit $?
295
- ;;
296
- --wait)
297
- [ "$#" -ge 2 ] || fail "--wait 需要分析目录参数"
298
- cmd_wait "$2"
299
- exit $?
300
- ;;
301
- --cleanup)
302
- [ "$#" -ge 2 ] || fail "--cleanup 需要分析目录参数"
303
- cmd_cleanup "$2"
304
- exit $?
305
- ;;
306
- --view)
307
- [ "$#" -ge 2 ] || fail "--view 需要分析目录参数"
308
- shift
309
- cmd_view "$@"
310
- exit $?
311
- ;;
312
- --say)
313
- [ "$#" -ge 3 ] || fail "--say 需要分析目录和文本参数"
314
- cmd_say "$2" "$3"
315
- exit $?
316
- ;;
317
- -h|--help)
318
- usage
319
- exit 0
320
- ;;
321
- esac
322
- fi
323
13
 
324
- usage >&2
325
- exit 2
14
+ # MV_INVOKED_AS:usage 与启动块里展示用户实际敲的那条命令(沿用 bash 的
15
+ # $0 语义;直接跑 mv_cli.py 时它会退回默认名)
16
+ MV_INVOKED_AS="${MV_INVOKED_AS:-$0}" \
17
+ exec "${PYTHON:-python3}" "$ROOT_DIR/mv_cli.py" "$@"
@@ -33,6 +33,7 @@ import subprocess
33
33
  import sys
34
34
  import time
35
35
 
36
+ import human_viewer
36
37
  import meeting_fs
37
38
  import observability
38
39
  import spec_gen
@@ -601,7 +602,12 @@ def main():
601
602
  cleanup_discussion(base)
602
603
  return
603
604
  if args.status:
604
- print(f"[status] {check_status(base)}")
605
+ st = check_status(base)
606
+ print(f"[status] {st}")
607
+ if st == "done":
608
+ # 产物路径一并给出:目录可省略后(自动发现),调用方**无法**
609
+ # 自己拼出 `<目录>-result.md`——路径由机制提供,不经 LLM 记忆
610
+ print(f"[result] {meeting_fs.result_path(base)}")
605
611
  return
606
612
  if args.report:
607
613
  for line in build_report(base):