glink-engine 0.3.0__py3-none-any.whl

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.
bus/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Glink Bus 包 — 共享工具与总线模块"""
3
+
4
+ import re
5
+
6
+ # ── 项目名白名单:仅允许字母、数字、下划线、连字符(防 path traversal)──
7
+ _PROJECT_NAME_RE = re.compile(r"[^\w\-]")
8
+
9
+
10
+ def sanitize_project_name(project_name: str) -> str:
11
+ """过滤项目名,防止 path traversal(仅保留 [\\w\\-])"""
12
+ return _PROJECT_NAME_RE.sub("", project_name or "")
bus/agent_client.py ADDED
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ agent_client — Glink 共享的 Agent 通讯与工作流加载模块
4
+
5
+ 由 glink.py(一次性调度引擎)和 glink-daemon.py(带断点续跑的守护进程)共享。
6
+
7
+ 导出:
8
+ - AGENT_PORTS: Agent 名称 → HTTP 端口的统一映射
9
+ - call_agent(): HTTP 调用 Agent 的 /ask 接口
10
+ - load_workflow(): 从 workflows/ 或 bus/projects/ 加载 yaml 工作流
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import os
17
+ import sys
18
+ import urllib.error
19
+ import urllib.request
20
+ from typing import Any
21
+
22
+ import yaml
23
+
24
+ # ── Agent 端口映射(唯一真源)────────────────────────────
25
+ # 同一端口可有多个别名(如 标准版/扎古、代码臂/Forge/forge)
26
+ AGENT_PORTS: dict[str, int] = {
27
+ "标准版": 8420,
28
+ "扎古": 8420,
29
+ "重锤": 8431,
30
+ "绘墨": 8432,
31
+ "大黄蜂": 8434,
32
+ "Laser": 8435,
33
+ "代码臂": 8436,
34
+ "Forge": 8436,
35
+ "forge": 8436,
36
+ }
37
+
38
+ DEFAULT_AGENT_PORT = 8420
39
+ DEFAULT_TIMEOUT = 600
40
+
41
+ # ── 项目名白名单(防 path traversal,从 bus/__init__.py 导入)──
42
+ from . import sanitize_project_name
43
+
44
+ _sanitize_project_name = sanitize_project_name # 兼容别名
45
+
46
+
47
+ # ── HTTP 调用 Agent ─────────────────────────────────────
48
+ def call_agent(
49
+ agent: str,
50
+ task: str,
51
+ port: int | None = None,
52
+ timeout: int = DEFAULT_TIMEOUT,
53
+ parse_reply: bool = True,
54
+ ) -> dict[str, Any]:
55
+ """HTTP 调用 agent 的 /ask 接口。
56
+
57
+ Args:
58
+ agent: Agent 名称(如 "重锤"、"Forge")
59
+ task: 发送给 Agent 的任务描述
60
+ port: 显式端口;不传则查 AGENT_PORTS
61
+ timeout: 请求超时秒数
62
+ parse_reply: True=尝试解析 JSON 取 reply 字段;False=直接返回原始响应
63
+
64
+ Returns:
65
+ {"status": "ok", "output": "<reply 或原始响应前500字>"}
66
+ {"status": "failed", "error": "<错误描述>"}
67
+ """
68
+ if port is None:
69
+ port = AGENT_PORTS.get(agent, DEFAULT_AGENT_PORT)
70
+
71
+ url = f"http://127.0.0.1:{port}/ask"
72
+ payload = json.dumps({"message": task, "session": True}).encode()
73
+ req = urllib.request.Request(url, data=payload, headers={"Content-Type": "application/json"})
74
+
75
+ max_response_size = 1 * 1024 * 1024 # 1 MB
76
+ chunk_size = 64 * 1024 # 64 KB
77
+
78
+ try:
79
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
80
+ chunks = []
81
+ total = 0
82
+ while True:
83
+ chunk = resp.read(chunk_size)
84
+ if not chunk:
85
+ break
86
+ total += len(chunk)
87
+ if total > max_response_size:
88
+ max_read = max_response_size - (total - len(chunk))
89
+ if max_read > 0:
90
+ chunks.append(chunk[:max_read])
91
+ while resp.read(chunk_size):
92
+ pass
93
+ body = b"".join(chunks).decode()
94
+ body = body[:max_response_size] + "\n\n[TRUNCATED: Response exceeded 1MB limit]"
95
+ break
96
+ chunks.append(chunk)
97
+ else:
98
+ body = b"".join(chunks).decode()
99
+
100
+ if parse_reply:
101
+ try:
102
+ output = json.loads(body).get("reply", body[:500])
103
+ except json.JSONDecodeError:
104
+ output = body[:500]
105
+ else:
106
+ output = body[:500]
107
+ return {"status": "ok", "output": output}
108
+ except urllib.error.HTTPError as e:
109
+ body = e.read().decode()[:200]
110
+ return {"status": "failed", "error": f"HTTP {e.code}: {body}"}
111
+ except Exception as e:
112
+ return {"status": "failed", "error": str(e)}
113
+
114
+
115
+ # ── 工作流加载 ───────────────────────────────────────────
116
+ def load_workflow(project_name: str, base_dir: str | None = None) -> dict[str, Any]:
117
+ """加载工作流 YAML,先查 workflows/,再查 bus/projects/。
118
+
119
+ Args:
120
+ project_name: 项目名(会被白名单过滤)
121
+ base_dir: Glink 根目录;不传则用本文件所在目录的父级
122
+
123
+ Returns:
124
+ 解析后的工作流字典
125
+
126
+ Raises:
127
+ SystemExit(1): 找不到工作流文件
128
+ """
129
+ if base_dir is None:
130
+ # 本文件位于 <glink>/bus/agent_client.py,父级 = glink 根
131
+ base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
132
+
133
+ workflows_dir = os.path.join(base_dir, "workflows")
134
+ bus_projects_dir = os.path.join(base_dir, "bus", "projects")
135
+
136
+ safe_name = _sanitize_project_name(project_name)
137
+ candidates = [
138
+ os.path.join(workflows_dir, f"{safe_name}.yaml"),
139
+ os.path.join(bus_projects_dir, f"{safe_name}.yaml"),
140
+ ]
141
+
142
+ for path in candidates:
143
+ if os.path.exists(path):
144
+ with open(path, encoding="utf-8") as f:
145
+ return yaml.safe_load(f)
146
+
147
+ print(f"❌ 找不到工作流: {safe_name}", file=sys.stderr)
148
+ sys.exit(1)
bus/docs/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # Main Bus — Shared Project Timeline
2
+
3
+ > All agents read/write project-level shared memory through this module.
4
+
5
+ ## Overview
6
+
7
+ Main Bus is the core communication layer for **Glink** multi-agent orchestration.
8
+ It's a **JSONL file system** — one `.jsonl` file per project, recording all events.
9
+ Append-only, never overwritten.
10
+
11
+ ## Event Types
12
+
13
+ | Type | Meaning | Triggered By |
14
+ |------|---------|-------------|
15
+ | `task.created` | Task created | Glink orchestrator |
16
+ | `task.assigned` | Task assigned | Glink orchestrator |
17
+ | `task.started` | Task execution started | Execution agent |
18
+ | `task.completed` | Task completed successfully | Execution agent |
19
+ | `task.failed` | Task failed | Execution agent |
20
+ | `task.log` | Execution log entry | Execution agent |
21
+ | `project.update` | Project status changed | Glink orchestrator |
22
+
23
+ ## API Reference
24
+
25
+ ### Python API
26
+
27
+ ```python
28
+ import main_bus
29
+
30
+ # Write an event
31
+ main_bus.write("myproject", "task.started", "agent-5", {"title": "Testing"})
32
+
33
+ # Read recent 20 events
34
+ events = main_bus.read("myproject", limit=20)
35
+
36
+ # Get latest event
37
+ ev = main_bus.latest("myproject", "task.completed")
38
+
39
+ # Get project status
40
+ s = main_bus.status("myproject")
41
+ print(s["tasks_completed"], "/", s["total_events"])
42
+ ```
43
+
44
+ ### CLI
45
+
46
+ ```bash
47
+ # Write
48
+ GLINK_PROJECT=hello-world python3 main_bus.py write task.started agent-5 '{"title":"Testing"}'
49
+
50
+ # Read (last 20)
51
+ GLINK_PROJECT=hello-world python3 main_bus.py read 20
52
+
53
+ # Status
54
+ GLINK_PROJECT=hello-world python3 main_bus.py status
55
+ ```
56
+
57
+ ## Data Storage
58
+
59
+ - Project files: `projects/{project_name}.jsonl`
60
+ - Format: One JSON object per line
61
+ - Event structure: `{ts, type, agent, data, stage}`
62
+
63
+ ## Concurrency Safety
64
+
65
+ - File-locked writes via `fcntl.flock` (exclusive lock)
66
+ - `n` parameter validated (positive integer, max 1000)
67
+ - Empty project event lists return `[]` gracefully
bus/main_bus.py ADDED
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Main Bus — 共享项目时间线
4
+ 所有 agent 通过此文件读写项目级共享记忆
5
+
6
+ 存储格式:JSONL(每行一个事件)
7
+ 事件类型:
8
+ - task.created: 任务创建
9
+ - task.assigned: 任务分派
10
+ - task.started: 任务开始执行
11
+ - task.completed: 任务完成
12
+ - task.failed: 任务失败
13
+ - task.log: 执行过程中的日志
14
+ - project.update: 项目状态更新
15
+ """
16
+
17
+ import fcntl
18
+ import json
19
+ import os
20
+ import sys
21
+ from datetime import datetime
22
+
23
+ from . import sanitize_project_name
24
+
25
+ BUS_DIR = os.path.dirname(os.path.abspath(__file__))
26
+
27
+ # 兼容别名(从 bus/__init__.py 导入)
28
+ _sanitize_project_name = sanitize_project_name
29
+
30
+
31
+ def bus_path(project_name: str) -> str:
32
+ """获取项目总线文件路径"""
33
+ safe_name = _sanitize_project_name(project_name)
34
+ projects_dir = os.path.join(BUS_DIR, "projects")
35
+ os.makedirs(projects_dir, exist_ok=True)
36
+ return os.path.join(projects_dir, f"{safe_name}.jsonl")
37
+
38
+
39
+ def write(project_name: str, event_type: str, agent: str, data, stage: str = ""):
40
+ """写入一条事件到 Main Bus(带文件锁,并发安全)"""
41
+ path = bus_path(project_name)
42
+ entry = {
43
+ "ts": datetime.now().isoformat(),
44
+ "type": event_type,
45
+ "agent": agent,
46
+ "data": data,
47
+ "stage": stage,
48
+ }
49
+ try:
50
+ with open(path, "a", encoding="utf-8") as f:
51
+ fcntl.flock(f.fileno(), fcntl.LOCK_EX)
52
+ try:
53
+ f.write(json.dumps(entry, ensure_ascii=False) + "\n")
54
+ f.flush()
55
+ finally:
56
+ fcntl.flock(f.fileno(), fcntl.LOCK_UN)
57
+ except OSError as e:
58
+ # 写入失败不抛出,记录到 stderr,避免拖垮调用方
59
+ sys.stderr.write(f"[main_bus.write] IOError on {path}: {e}\n")
60
+ return None
61
+ return entry
62
+
63
+
64
+ def read(project_name: str, limit: int = 20, since_type: str = None):
65
+ """读取 Main Bus 最近的事件
66
+
67
+ TODO: 当前实现为 O(n) 扫描整个文件。对于长期运行项目,
68
+
69
+ 事件可达数万行。优化方向:
70
+ - 维护文件行数偏移量索引(如 .idx 文件缓存行号→offset 映射)
71
+ - 或使用环形缓冲区缓存最近 N 条事件
72
+ - 或改用 SQLite / 结构化存储
73
+ """
74
+ path = bus_path(project_name)
75
+ if not os.path.exists(path):
76
+ return []
77
+
78
+ # BUG-02: 负数或零 n 参数校验(2026-05-25 Forge 发现)
79
+ if limit <= 0:
80
+ limit = 20
81
+
82
+ entries = []
83
+ with open(path, encoding="utf-8") as f:
84
+ for line in f:
85
+ line = line.strip()
86
+ if not line:
87
+ continue
88
+ try:
89
+ entry = json.loads(line)
90
+ if since_type is None or entry["type"] == since_type:
91
+ entries.append(entry)
92
+ except json.JSONDecodeError:
93
+ continue
94
+
95
+ return entries[-limit:]
96
+
97
+
98
+ def latest(project_name: str, event_type: str = None, agent: str = None):
99
+ """获取最新的一条事件"""
100
+ entries = read(project_name, limit=100)
101
+ for e in reversed(entries):
102
+ if event_type and e["type"] != event_type:
103
+ continue
104
+ if agent and e["agent"] != agent:
105
+ continue
106
+ return e
107
+ return None
108
+
109
+
110
+ def status(project_name: str) -> dict:
111
+ """获取项目当前状态总结"""
112
+ entries = read(project_name, limit=1000)
113
+
114
+ tasks = [e for e in entries if e["type"].startswith("task.")]
115
+ completed = [t for t in tasks if t["type"] == "task.completed"]
116
+ failed = [t for t in tasks if t["type"] == "task.failed"]
117
+ started = [t for t in tasks if t["type"] == "task.started"]
118
+
119
+ return {
120
+ "project": project_name,
121
+ "total_events": len(entries),
122
+ "tasks_created": len([t for t in tasks if t["type"] == "task.created"]),
123
+ "tasks_started": len(started),
124
+ "tasks_completed": len(completed),
125
+ "tasks_failed": len(failed),
126
+ "agents_involved": list(set(e["agent"] for e in entries)),
127
+ "stages": list(set(e.get("stage", "") for e in entries if e.get("stage"))),
128
+ }
129
+
130
+
131
+ def cli() -> None:
132
+ """命令行入口"""
133
+ if len(sys.argv) < 2:
134
+ print("用法: python3 main-bus.py <命令> [参数...]")
135
+ print("命令: write, read, status, latest")
136
+ sys.exit(1)
137
+
138
+ cmd = sys.argv[1]
139
+ project = os.environ.get("GLINK_PROJECT", "testglink")
140
+
141
+ if cmd == "write":
142
+ if len(sys.argv) < 5:
143
+ print("用法: write <event_type> <agent> <data_json>")
144
+ sys.exit(1)
145
+ entry = write(project, sys.argv[2], sys.argv[3], json.loads(sys.argv[4]))
146
+ print(json.dumps(entry, ensure_ascii=False))
147
+
148
+ elif cmd == "read":
149
+ entries = read(project, limit=int(sys.argv[2]) if len(sys.argv) > 2 else 20)
150
+ for e in entries:
151
+ print(
152
+ f"[{e['ts'][:19]}] {e['type']:20s} | {e['agent']:10s} | {json.dumps(e['data'], ensure_ascii=False)[:80]}"
153
+ )
154
+
155
+ elif cmd == "status":
156
+ s = status(project)
157
+ print(f"项目: {s['project']}")
158
+ print(f"总事件: {s['total_events']}")
159
+ print(
160
+ f"任务: 创建{s['tasks_created']} / 开始{s['tasks_started']} / 完成{s['tasks_completed']} / 失败{s['tasks_failed']}"
161
+ )
162
+ print(f"参与Agent: {', '.join(s['agents_involved'])}")
163
+ print(f"阶段: {', '.join(s['stages'])}")
164
+
165
+ elif cmd == "latest":
166
+ entry = latest(
167
+ project,
168
+ event_type=sys.argv[2] if len(sys.argv) > 2 else None,
169
+ agent=sys.argv[3] if len(sys.argv) > 3 else None,
170
+ )
171
+ if entry:
172
+ print(json.dumps(entry, ensure_ascii=False))
173
+ else:
174
+ print("null")
175
+
176
+
177
+ if __name__ == "__main__":
178
+ cli()
@@ -0,0 +1,24 @@
1
+ {"ts": "2026-05-24T19:54:51.215516", "type": "project.update", "agent": "glink", "data": {"action": "started", "title": "sandbox-builder", "goal": "", "total_steps": 9}, "stage": ""}
2
+ {"ts": "2026-05-24T19:54:51.223930", "type": "task.started", "agent": "标准版", "data": {"title": "Step 1", "stage": "step-1", "step_index": 0, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-1"}
3
+ {"ts": "2026-05-24T19:56:21.217948", "type": "task.completed", "agent": "标准版", "data": {"title": "Step 1", "output_preview": "已生成 → `output/threejs-starter.html`(208 行,零依赖,直接浏览器打开即可)。\n\n---\n\n## 模板拆解\n\n整个文件分 9 个区块,按你要求的 **场景 → 相机 → 光照 → 渲染循环** 组织:\n\n### 1. 渲染器\n```\nWebGLRenderer + antialias + PCFSoftShadowMap + ACESFilmicToneMapp", "stage": "step-1", "step_index": 0, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-1"}
4
+ {"ts": "2026-05-24T19:56:22.235476", "type": "task.started", "agent": "标准版", "data": {"title": "Step 2", "stage": "step-2", "step_index": 1, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-2"}
5
+ {"ts": "2026-05-24T19:58:26.221190", "type": "task.completed", "agent": "标准版", "data": {"title": "Step 2", "output_preview": "Notblox 用了 ECS 架构,不好直接参考。sandbox-builder 目前只有 Step1 的基础场景,正好缺方块放置系统。让我直接给你写完整的实现方案。\n\n---\n\n## 方块放置系统:Raycasting + 网格吸附 — 完整方案\n\n核心流程分三步:**射线检测 → 面法线确定放置方向 → 网格吸附计算目标坐标**。\n\n### 1. Raycaster 初始化\n\n```js\n//", "stage": "step-2", "step_index": 1, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-2"}
6
+ {"ts": "2026-05-24T19:58:27.237772", "type": "task.started", "agent": "标准版", "data": {"title": "Step 3", "stage": "step-3", "step_index": 2, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-3"}
7
+ {"ts": "2026-05-24T20:01:35.390309", "type": "task.completed", "agent": "标准版", "data": {"title": "Step 3", "output_preview": "好,工具调用已到第 9 次,进入整理阶段。以下是基于搜索结果整理的完整方案。\n\n---\n\n## 材质贴图库 + ComfyUI 生成配置\n\n### 🎯 核心方案:StableMaterials → MatForger → ComfyUI_PBR_Maker\n\n这是一条完整的管线,从学术论文到可用的 ComfyUI custom node:\n\n| 层级 | 项目 | 说明 |\n|---|---|--", "stage": "step-3", "step_index": 2, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-3"}
8
+ {"ts": "2026-05-24T20:01:36.407667", "type": "task.started", "agent": "标准版", "data": {"title": "Step 4", "stage": "step-4", "step_index": 3, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-4"}
9
+ {"ts": "2026-05-24T20:02:31.819067", "type": "task.completed", "agent": "标准版", "data": {"title": "Step 4", "output_preview": "好的,我在 Mac Studio 本地环境中。Portal 页面在 ECS 上(/root/opprime/portal/opprime/index.html),不在本地。让我先确认几个关键信息:你指的物理系统,我理解是三种核心能力——**重力模拟**(物体受重力下落)、**碰撞检测**(物体之间的接触判定)、**叠放稳定性**(物体堆叠后不穿模、不抖动)。\n\n在 Three.js 场景中实现这套", "stage": "step-4", "step_index": 3, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-4"}
10
+ {"ts": "2026-05-24T20:02:32.831976", "type": "task.started", "agent": "标准版", "data": {"title": "Step 5", "stage": "step-5", "step_index": 4, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-5"}
11
+ {"ts": "2026-05-24T20:02:51.502496", "type": "task.completed", "agent": "标准版", "data": {"title": "Step 5", "output_preview": "我查了一下,你目前的知识库和记忆里没有现存游戏 UI 项目。你的提问比较简洁,我想确认一下你的意图——\n\n你希望我做什么?\n\n1. **从零搭建**一个游戏 UI(例如用 HTML/CSS/JS 或 React),包含材质工具栏、分数面板和操作提示?\n2. 这是对**已有项目的补充或修改**?如果是,请告诉我项目的位置。\n3. 还是你想让我帮你**设计/规划**这个 UI 的信息架构和交互方案?\n", "stage": "step-5", "step_index": 4, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-5"}
12
+ {"ts": "2026-05-24T20:08:19.375196", "type": "project.update", "agent": "glink", "data": {"action": "started", "total_steps": 10}, "stage": ""}
13
+ {"ts": "2026-05-24T23:50:58.717705", "type": "project.update", "agent": "glink", "data": {"action": "started", "total_steps": 10}, "stage": ""}
14
+ {"ts": "2026-05-24T23:53:20.383653", "type": "task.started", "agent": "绘墨", "data": {"title": "开始菜单 + 结束画面", "stage": "step-6", "step_index": 5, "planned_agent": "绘墨", "fallback_from": null}, "stage": "step-6"}
15
+ {"ts": "2026-05-24T23:57:48.902104", "type": "task.completed", "agent": "绘墨", "data": {"title": "开始菜单 + 结束画面", "output_preview": "<||DSML||tool_calls>\n<||DSML||invoke name=\"exec_command\">\n<||DSML||parameter name=\"command\" string=\"true\">grep -n \"keydown\\|btnQuitToMenu\\|console.log.*G 键\\|按 G 键\" /Users/gary/opprime/output/threejs-s", "stage": "step-6", "step_index": 5, "planned_agent": "绘墨", "fallback_from": null}, "stage": "step-6"}
16
+ {"ts": "2026-05-24T23:57:49.917739", "type": "task.started", "agent": "大黄蜂", "data": {"title": "保存/读取系统", "stage": "step-7", "step_index": 6, "planned_agent": "大黄蜂", "fallback_from": null}, "stage": "step-7"}
17
+ {"ts": "2026-05-24T23:58:57.104673", "type": "task.completed", "agent": "大黄蜂", "data": {"title": "保存/读取系统", "output_preview": "<||DSML||tool_calls>\n<||DSML||invoke name=\"exec_command\">\n<||DSML||parameter name=\"command\" string=\"true\">sed -n '1160,1399p' /Users/gary/opprime/glink/projects/sandbox-builder/sandbox-builder-step5.h", "stage": "step-7", "step_index": 6, "planned_agent": "大黄蜂", "fallback_from": null}, "stage": "step-7"}
18
+ {"ts": "2026-05-24T23:58:58.118557", "type": "task.started", "agent": "大黄蜂", "data": {"title": "计分系统 + 成就", "stage": "step-8", "step_index": 7, "planned_agent": "大黄蜂", "fallback_from": null}, "stage": "step-8"}
19
+ {"ts": "2026-05-25T00:02:15.725214", "type": "task.completed", "agent": "大黄蜂", "data": {"title": "计分系统 + 成就", "output_preview": "做最终验证 — 确认 HTML 完整性和无语法破坏。\n\n<||DSML||tool_calls>\n<||DSML||invoke name=\"exec_command\">\n<||DSML||parameter name=\"command\" string=\"true\">python3 -c \"\nhtml = open('/Users/gary/opprime/glink/projects/sandb", "stage": "step-8", "step_index": 7, "planned_agent": "大黄蜂", "fallback_from": null}, "stage": "step-8"}
20
+ {"ts": "2026-05-25T00:02:16.735866", "type": "task.started", "agent": "Laser", "data": {"title": "测试验证", "stage": "step-9", "step_index": 8, "planned_agent": "Laser", "fallback_from": null}, "stage": "step-9"}
21
+ {"ts": "2026-05-25T00:06:12.229519", "type": "task.completed", "agent": "Laser", "data": {"title": "测试验证", "output_preview": "全部数据就绪。现在生成完整的测试报告。\n\n---\n\n# 🔬 全流程测试报告\n\n> 测试时间:2026-05-25 00:02 \n> 测试范围:5 个 HTML 文件 + 1 个 JS 库 \n> 测试维度:CDN 可达性 / HTML 语法 / 关键代码段 / 功能覆盖率\n\n---\n\n## 📊 一、测试覆盖总览\n\n| 文件 | 大小 | 行数 | CSS/JS比 | 函数数 | 评定 |\n|--", "stage": "step-9", "step_index": 8, "planned_agent": "Laser", "fallback_from": null}, "stage": "step-9"}
22
+ {"ts": "2026-05-25T00:06:13.241127", "type": "task.started", "agent": "标准版", "data": {"title": "最终代码质检", "stage": "step-10", "step_index": 9, "planned_agent": "forge", "fallback_from": "forge"}, "stage": "step-10"}
23
+ {"ts": "2026-05-25T00:08:47.650009", "type": "task.completed", "agent": "标准版", "data": {"title": "最终代码质检", "output_preview": "所有黑盒测试通过。现在汇总最终的完整审查报告。\n\n---\n\n# 🔬 Forge 最终版 · 完整代码审查报告\n\n> **审查时间**:2026-05-25 00:06 \n> **审查范围**:Glink v0.5 全部 5 个 Python 源文件 \n> **审查方式**:forge_verify() 六项白盒 + qa_double_check 双战甲 + 黑盒实测\n\n---\n\n## 📊 一", "stage": "step-10", "step_index": 9, "planned_agent": "forge", "fallback_from": "forge"}, "stage": "step-10"}
24
+ {"ts": "2026-05-25T00:08:48.660807", "type": "project.update", "agent": "glink", "data": {"action": "completed", "total_steps": 10}, "stage": ""}
@@ -0,0 +1,21 @@
1
+ {"ts": "2026-05-24T19:54:51.215516", "type": "project.update", "agent": "glink", "data": {"action": "started", "title": "sandbox-builder", "goal": "", "total_steps": 9}, "stage": ""}
2
+ {"ts": "2026-05-24T19:54:51.223930", "type": "task.started", "agent": "标准版", "data": {"title": "Step 1", "stage": "step-1", "step_index": 0, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-1"}
3
+ {"ts": "2026-05-24T19:56:21.217948", "type": "task.completed", "agent": "标准版", "data": {"title": "Step 1", "output_preview": "已生成 → `output/threejs-starter.html`(208 行,零依赖,直接浏览器打开即可)。\n\n---\n\n## 模板拆解\n\n整个文件分 9 个区块,按你要求的 **场景 → 相机 → 光照 → 渲染循环** 组织:\n\n### 1. 渲染器\n```\nWebGLRenderer + antialias + PCFSoftShadowMap + ACESFilmicToneMapp", "stage": "step-1", "step_index": 0, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-1"}
4
+ {"ts": "2026-05-24T19:56:22.235476", "type": "task.started", "agent": "标准版", "data": {"title": "Step 2", "stage": "step-2", "step_index": 1, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-2"}
5
+ {"ts": "2026-05-24T19:58:26.221190", "type": "task.completed", "agent": "标准版", "data": {"title": "Step 2", "output_preview": "Notblox 用了 ECS 架构,不好直接参考。sandbox-builder 目前只有 Step1 的基础场景,正好缺方块放置系统。让我直接给你写完整的实现方案。\n\n---\n\n## 方块放置系统:Raycasting + 网格吸附 — 完整方案\n\n核心流程分三步:**射线检测 → 面法线确定放置方向 → 网格吸附计算目标坐标**。\n\n### 1. Raycaster 初始化\n\n```js\n//", "stage": "step-2", "step_index": 1, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-2"}
6
+ {"ts": "2026-05-24T19:58:27.237772", "type": "task.started", "agent": "标准版", "data": {"title": "Step 3", "stage": "step-3", "step_index": 2, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-3"}
7
+ {"ts": "2026-05-24T20:01:35.390309", "type": "task.completed", "agent": "标准版", "data": {"title": "Step 3", "output_preview": "好,工具调用已到第 9 次,进入整理阶段。以下是基于搜索结果整理的完整方案。\n\n---\n\n## 材质贴图库 + ComfyUI 生成配置\n\n### 🎯 核心方案:StableMaterials → MatForger → ComfyUI_PBR_Maker\n\n这是一条完整的管线,从学术论文到可用的 ComfyUI custom node:\n\n| 层级 | 项目 | 说明 |\n|---|---|--", "stage": "step-3", "step_index": 2, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-3"}
8
+ {"ts": "2026-05-24T20:01:36.407667", "type": "task.started", "agent": "标准版", "data": {"title": "Step 4", "stage": "step-4", "step_index": 3, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-4"}
9
+ {"ts": "2026-05-24T20:02:31.819067", "type": "task.completed", "agent": "标准版", "data": {"title": "Step 4", "output_preview": "好的,我在 Mac Studio 本地环境中。Portal 页面在 ECS 上(/root/opprime/portal/opprime/index.html),不在本地。让我先确认几个关键信息:你指的物理系统,我理解是三种核心能力——**重力模拟**(物体受重力下落)、**碰撞检测**(物体之间的接触判定)、**叠放稳定性**(物体堆叠后不穿模、不抖动)。\n\n在 Three.js 场景中实现这套", "stage": "step-4", "step_index": 3, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-4"}
10
+ {"ts": "2026-05-24T20:02:32.831976", "type": "task.started", "agent": "标准版", "data": {"title": "Step 5", "stage": "step-5", "step_index": 4, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-5"}
11
+ {"ts": "2026-05-24T20:02:51.502496", "type": "task.completed", "agent": "标准版", "data": {"title": "Step 5", "output_preview": "我查了一下,你目前的知识库和记忆里没有现存游戏 UI 项目。你的提问比较简洁,我想确认一下你的意图——\n\n你希望我做什么?\n\n1. **从零搭建**一个游戏 UI(例如用 HTML/CSS/JS 或 React),包含材质工具栏、分数面板和操作提示?\n2. 这是对**已有项目的补充或修改**?如果是,请告诉我项目的位置。\n3. 还是你想让我帮你**设计/规划**这个 UI 的信息架构和交互方案?\n", "stage": "step-5", "step_index": 4, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-5"}
12
+ {"ts": "2026-05-24T20:02:52.512642", "type": "task.started", "agent": "标准版", "data": {"title": "Step 6", "stage": "step-6", "step_index": 5, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-6"}
13
+ {"ts": "2026-05-24T20:03:53.150917", "type": "task.completed", "agent": "标准版", "data": {"title": "Step 6", "output_preview": "主人,我在项目里找到了今天刚创建的 `xiaoyou-frontend-lab`(一个 TypeScript 前端项目框架,目前只有 lint 工具配置,还没有源码),但没找到任何与\"游戏\"相关的现有代码或近期提交记录。\n\n我需要确认几个信息:\n\n1. **这是什么游戏?** — 什么类型(平台跳跃、射击、卡牌、RPG...)?有没有设计稿或之前的讨论?\n2. **放在哪个项目里?** — 是 `", "stage": "step-6", "step_index": 5, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-6"}
14
+ {"ts": "2026-05-24T20:03:54.165561", "type": "task.started", "agent": "标准版", "data": {"title": "Step 7", "stage": "step-7", "step_index": 6, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-7"}
15
+ {"ts": "2026-05-24T20:06:37.247183", "type": "task.completed", "agent": "标准版", "data": {"title": "Step 7", "output_preview": "**反思**:两个文件写入成功,portal.py 的静态文件路由会自动映射 `/save-slots/*` → `static/save-slots/*`,无需改路由。一切就绪。\n\n---\n\n## ✅ 完成:localStorage 3 槽位保存/读取系统\n\n### 📁 文件结构\n\n```\nstatic/save-slots/\n├── save-system.js (5.8KB) 核心 J", "stage": "step-7", "step_index": 6, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-7"}
16
+ {"ts": "2026-05-24T20:06:38.260151", "type": "task.started", "agent": "标准版", "data": {"title": "Step 8", "stage": "step-8", "step_index": 7, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-8"}
17
+ {"ts": "2026-05-24T20:07:12.105488", "type": "task.completed", "agent": "标准版", "data": {"title": "Step 8", "output_preview": "羽非,我目前在工作目录里没有找到明确的\"计分系统/成就徽章/游戏循环\"相关项目。不过我发现了一些可能相关的线索:\n\n- **notblox**:`learn-github-sources/notblox/front/game/` 下有一个 ECS 游戏框架(Player、Cube、HUD 等)\n- **ideaspace-vr**:一个 VR 空间项目\n\n但我不确定你说的是哪个项目。需要你帮我明确", "stage": "step-8", "step_index": 7, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-8"}
18
+ {"ts": "2026-05-24T20:07:13.119301", "type": "task.started", "agent": "标准版", "data": {"title": "Step 9", "stage": "step-9", "step_index": 8, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-9"}
19
+ {"ts": "2026-05-24T20:08:18.371348", "type": "task.completed", "agent": "标准版", "data": {"title": "Step 9", "output_preview": "**第10次调用 — 停止,进入汇报阶段。**\n\n好消息!之前 lsof 是权限问题误报,curl 确认 8420 健康检查返回 200。\n\n---\n\n## 🔬 Opprime 全流程测试报告\n\n### 🟢 正常 (7/9)\n\n| 维度 | 状态 | 实测数据 |\n|------|------|----------|\n| **标准版服务** | ✅ | `GET /health` → 200,`{", "stage": "step-9", "step_index": 8, "planned_agent": "标准版", "fallback_from": null}, "stage": "step-9"}
20
+ {"ts": "2026-05-24T20:08:19.375196", "type": "project.update", "agent": "glink", "data": {"action": "completed", "total_steps": 9}, "stage": ""}
21
+ {"ts": "2026-05-24T23:34:35.686034", "type": "task.started", "agent": "绘墨", "data": {"title": "开始菜单 + 结束画面", "stage": "step-6", "step_index": 5, "planned_agent": "绘墨", "fallback_from": null}, "stage": "step-6"}
@@ -0,0 +1,165 @@
1
+ # 诺保科销售拜访系统 — 需求交叉分析报告
2
+
3
+ > 分析来源:
4
+ > - 需求文档原文:`/Users/gary/.qclaw/workspace/projects/nuobaoke-crm/需求文档原文.md`
5
+ > - 客户确认工作清单:`/Users/gary/Projects/nuoboke-project/客户确认-工作清单.md`
6
+ > 分析日期:2026-05-24
7
+
8
+ ---
9
+
10
+ ## 一、项目全貌
11
+
12
+ ### 1.1 系统定位
13
+ 诺保科销售拜访微信小程序 + Web 管理后台,面向诺保科(种植牙/医疗器械)的销售团队管理场景。
14
+
15
+ ### 1.2 技术栈(需求文档约定)
16
+ | 层 | 技术 |
17
+ |:---|:---|
18
+ | 后端 | PHP Laravel 8.* |
19
+ | 前端(小程序) | 微信小程序原生 |
20
+ | 前端(管理后台) | layuimini(layui 2.x) |
21
+ | 数据库 | MySQL(同一实例,前后端共用) |
22
+ | 鉴权 | JWT(小程序 + 管理后台同密钥,通过路由/中间件隔离) |
23
+ | 定位 | 高德地图 |
24
+ | 管理后台入口 | 浏览器访问,独立于微信生态 |
25
+
26
+ ### 1.3 角色体系(需求文档原文)
27
+ | 角色 | 职责 |
28
+ |:---|:---|
29
+ | 销售 | 完成每月拜访任务,提交拜访记录、签到签出 |
30
+ | 地区经理 | 完成拜访任务,参阅手下员工每月完成进度 |
31
+ | 市场部 | 举办学术会议,会后发起指定任务给销售完成客户回访 |
32
+ | SFE 主管 | 对预警销量下滑的销售发起指定拜访任务 |
33
+ | 管理员(admin) | 电脑端后台管理,总账号 admin / NobelCRM |
34
+
35
+ > ⚠️ **注意**:需求文档定义了 5 种角色,而工作清单中仅区分"管理员 vs 小程序用户"。地区经理、市场部、SFE 主管的差异化权限在工作清单中未体现。
36
+
37
+ ### 1.4 模块全景图
38
+
39
+ ```
40
+ 诺保科销售拜访系统
41
+ ├── 小程序端(9 个版块)
42
+ │ ├── ① 客户拜访 ← 核心,签到/签出/日历
43
+ │ ├── ② 手术跟台 ← 列表页已有,缺完整 CRUD
44
+ │ ├── ③ 会议举办 ← 列表页已有,缺完整 CRUD
45
+ │ ├── ④ 新开发医院 ← ⚠️ 工作清单未提及!
46
+ │ ├── ⑤ 休假 ← 列表+创建已有,缺首页集成
47
+ │ ├── ⑥ 拜访计划 ← 需求文档标注"暂不开发"
48
+ │ ├── ⑦ 我的派单任务 ← 列表页已有,缺详情+完成
49
+ │ ├── ⑧ 医院 ← 列表已有,详情不完整
50
+ │ └── ⑨ 医生 ← 列表已有,缺创建页
51
+
52
+ └── Web 管理后台(layuimini)
53
+ ├── 登录 ← A13 必选(工作清单覆盖)
54
+ ├── 控制台首页 ← A14 推荐(工作清单覆盖)
55
+ ├── 销售管理 ← A15 必选(部分覆盖,缺 Excel 导入/字段粒度)
56
+ ├── 医院管理 ← A19 可选(工作清单覆盖)
57
+ ├── 医生库管理 ← ⚠️ 工作清单完全未覆盖!
58
+ ├── 拜访记录管理 ← A16 必选(部分覆盖,缺子模块区分)
59
+ ├── 审批管理 ← A17 推荐(工作清单覆盖)
60
+ ├── 日报统计 ← A18 推荐(工作清单覆盖)
61
+ └── 系统设置 ← A20 可选(工作清单覆盖)
62
+ ```
63
+
64
+ ---
65
+
66
+ ## 二、补全范围分析
67
+
68
+ ### 2.1 工作清单已覆盖的补全项(29 项)
69
+
70
+ | 模块 | 项数 | 内容 |
71
+ |:---|:---:|:---|
72
+ | 🅰 后台管理 API | 12 | A1-A12:登录/用户CRUD/拜访查看/医院CRUD/统计 |
73
+ | 🅰 后台管理前端 | 8 | A13-A20:登录/首页/用户/拜访/审批/日报/医院/设置 |
74
+ | 🅱 权限分离 | 4 | B1-B4:admin guard / 独立登录 / 小程序移除管理 / web 独立登录 |
75
+ | 🅲 小程序补全 | 5 | C1-C5:手术跟台/会议举办/休假/派单任务/医院医生 |
76
+
77
+ ### 2.2 范围遗漏(需求文档有,工作清单无)
78
+
79
+ 以下 6 项在需求文档原文中明确要求,但工作清单完全未覆盖:
80
+
81
+ | # | 遗漏项 | 需求文档出处 | 严重程度 | 建议 |
82
+ |:--|:------|:------------|:--------:|:-----|
83
+ | 🔴 R1 | **后台-医生库管理** | 需求文档"医生库"章节:搜索/筛选/展示/拜访记录按钮/Excel 导出 | **高** | 新增约 3 个 API + 1 个前端页面 |
84
+ | 🔴 R2 | **小程序-新开发医院模块** | 需求文档第 ④ 版块,与客户拜访同级的独立版块 | **高** | 参照客户拜访模块,含完整 CRUD + 签到逻辑 |
85
+ | 🔴 R3 | **后台-Excel 导入导出** | 销售 Excel 导入、医院 Excel 导入、医生库 Excel 导出 | **高** | 3 个导入导出功能,约 2 天工作量 |
86
+ | 🟡 R4 | **后台-拜访记录子模块区分** | 需求文档要求区分"客户拜访/手术跟台/会议举办/新开发医院" | **中** | 当前工作清单只说"全量拜访查看",需细化 |
87
+ | 🟡 R5 | **小程序-日历管理** | 需求文档:"提交后跳转至日历管理页,定位到创建的拜访日期" | **中** | 拜访/跟台/会议共用的日历视图 |
88
+ | 🟡 R6 | **多角色权限粒度** | 需求文档定义了销售/地区经理/市场部/SFE主管 4 种业务角色 | **中** | 当前仅区分"管理员 vs 用户",缺少业务角色权限 |
89
+
90
+ ### 2.3 工作清单覆盖但粒度不足的项
91
+
92
+ | # | 项目 | 工作清单描述 | 需求文档要求 | 差距 |
93
+ |:--|:-----|:-----------|:-----------|:-----|
94
+ | G1 | 后台用户管理 | "用户列表/新增/编辑/启用禁用" | 需包含:销售编码、大区、省份、城市、邮箱、手机、职级、上级领导、上级领导编码、负责医院编号、负责医院名称、状态 | 字段粒度差 10+ 字段 |
95
+ | G2 | 后台医院管理 | "医院/医生基础数据管理(CRUD)" | 需包含:医院编码、名称、地址、大类小类、连锁大类小类、标准注册名称、社会码、启用状态 + 医生按钮入口 | 缺字段定义 + 医生入口 |
96
+ | G3 | 小程序签到签出 | 未提及 | 详细逻辑:拜访前1小时开放签到、1公里内、20分钟后开放签出、19:00关闭、补签按钮 | 已有功能但工作清单未列入补全范围 |
97
+
98
+ ---
99
+
100
+ ## 三、优先级排序(建议交付顺序)
101
+
102
+ ### 🔴 P0 — 安全底座 + 管理入口(阻塞后续所有工作)
103
+ | # | 项目 | 来源 | 工作量 | 理由 |
104
+ |:--|:-----|:----|:------:|:-----|
105
+ | P0-1 | 🅱 权限分离(B1-B4) | 工作清单 | 0.5天 | 不隔离权限=管理员功能白做 |
106
+ | P0-2 | 🅰 管理后台登录(A1+A13) | 工作清单 | 含在 P0-1 | 管理后台入口 |
107
+
108
+ ### 🔴 P1 — 管理后台核心 CRUD(管理员必须能干活)
109
+ | # | 项目 | 来源 | 工作量 | 理由 |
110
+ |:--|:-----|:----|:------:|:-----|
111
+ | P1-1 | 🅰 用户管理(A2-A5+A15) | 工作清单 | 1.5天 | 最核心的管理能力 |
112
+ | P1-2 | 🅰 拜访管理(A6+A16) | 工作清单 | 1天 | 核心业务数据查看 |
113
+ | P1-3 | 🔴 后台-医生库管理(R1) | **遗漏补全** | 1天 | 需求文档明确要求,缺则不完整 |
114
+ | P1-4 | 🔴 后台-子模块区分(R4) | **粒度补全** | 0.5天 | 客户拜访/手术跟台/会议/新开发需分开查看 |
115
+
116
+ ### 🟡 P2 — 管理后台完整闭环
117
+ | # | 项目 | 来源 | 工作量 | 理由 |
118
+ |:--|:-----|:----|:------:|:-----|
119
+ | P2-1 | 🅰 审批管理(A17) | 工作清单 | 0.5天 | 已有接口,配界面 |
120
+ | P2-2 | 🅰 日报统计(A18) | 工作清单 | 0.5天 | 已有接口,配界面 |
121
+ | P2-3 | 🅰 医院管理(A8-A11+A19) | 工作清单 | 1天 | 基础数据管理 |
122
+ | P2-4 | 🔴 Excel 导入导出(R3) | **遗漏补全** | 2天 | 大批量数据操作必需 |
123
+
124
+ ### 🟡 P3 — 小程序功能补全
125
+ | # | 项目 | 来源 | 工作量 | 理由 |
126
+ |:--|:-----|:----|:------:|:-----|
127
+ | P3-1 | 🅲 手术跟台(C1) | 工作清单 | 0.5天 | 中工作量 |
128
+ | P3-2 | 🅲 会议举办(C2) | 工作清单 | 0.5天 | 中工作量 |
129
+ | P3-3 | 🅲 休假申请(C3) | 工作清单 | 0.2天 | 小工作量 |
130
+ | P3-4 | 🅲 派单任务(C4) | 工作清单 | 0.2天 | 小工作量 |
131
+ | P3-5 | 🅲 医院/医生(C5) | 工作清单 | 0.3天 | 中工作量 |
132
+ | P3-6 | 🔴 新开发医院模块(R2) | **遗漏补全** | 1天 | 小程序第④版块,完全缺失 |
133
+ | P3-7 | 🔴 日历管理(R5) | **遗漏补全** | 1天 | 拜访/跟台/会议共用日历 |
134
+
135
+ ### 🟢 P4 — 锦上添花
136
+ | # | 项目 | 来源 | 工作量 | 理由 |
137
+ |:--|:-----|:----|:------:|:-----|
138
+ | P4-1 | 🅰 统计概览(A12+A14) | 工作清单 | 0.5天 | 仪表盘,好看但不影响业务 |
139
+ | P4-2 | 🅰 系统设置(A20) | 工作清单 | 0.5天 | 打卡半径等可先硬编码 |
140
+ | P4-3 | 🟡 多角色权限(R6) | **粒度补全** | 1天 | 地区经理/市场部/SFE的差异化权限 |
141
+
142
+ ---
143
+
144
+ ## 四、工作量重新估算
145
+
146
+ | 阶段 | 原估算(工作清单) | 修正估算(含遗漏) | 差异 |
147
+ |:---|:---:|:---:|:---:|
148
+ | 管理后台 | 4-6 天 | 7-9 天 | +2~3 天(医生库+Excel导入导出+子模块区分) |
149
+ | 权限分离 | 0.5 天 | 0.5 天 | 持平 |
150
+ | 小程序补全 | 1.5 天 | 3.5 天 | +2 天(新开发医院+日历管理) |
151
+ | **合计** | **6-8 天** | **11-13 天** | **+5 天** |
152
+
153
+ ---
154
+
155
+ ## 五、关键建议
156
+
157
+ 1. **立即与客户确认 6 项遗漏**(R1-R6),避免交付后才发现缺功能。
158
+ 2. **P0 权限分离不可跳过**,这是管理后台能安全上线的前置条件。
159
+ 3. **后台-医生库管理(R1)优先级应上调至 P1**,因为医生数据是拜访记录的核心关联实体,缺失会导致后台拜访查看功能不完整。
160
+ 4. **小程序-新开发医院(R2)是完整业务闭环的必需模块**,需求文档将其列为与"客户拜访"并列的第④版块,不应遗漏。
161
+ 5. 需求文档中"拜访计划"标注为"暂不开发",本次可不纳入范围。
162
+
163
+ ---
164
+
165
+ > 本分析基于两份文档的交叉比对。建议将此报告与客户确认工作清单一并发送,供客户逐项确认优先级。