oh-aicoding-tool 0.1.2 → 0.1.5

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.
Files changed (55) hide show
  1. package/README.md +79 -80
  2. package/bin/cli.js +257 -384
  3. package/package.json +27 -55
  4. package/CODEX_LANGFUSE_PLAN.md +0 -62
  5. package/bin/langfuse-cli.js +0 -718
  6. package/codex_langfuse_notify.py +0 -591
  7. package/langfuse_hook.py +0 -603
  8. package/opencode-ohai-report/.claude/commands/report-ai-issue.md +0 -60
  9. package/opencode-ohai-report/.opencode/commands/report-ai-issue.md +0 -30
  10. package/opencode-ohai-report/.opencode/plugins/oh-ai-report.ts +0 -569
  11. package/opencode-ohai-report/README.md +0 -45
  12. package/opencode-ohai-report/bin/cli.js +0 -421
  13. package/opencode-ohai-report/docs/opencode-ai-issue-collection-architecture.md +0 -313
  14. package/opencode-ohai-report/docs/opencode-ai-issue-collection-best-practices.md +0 -476
  15. package/opencode-ohai-report/docs/opencode-ai-issue-collection-phase1-summary.md +0 -405
  16. package/opencode-ohai-report/examples/issue_output.json +0 -4
  17. package/opencode-ohai-report/package.json +0 -40
  18. package/opencode-ohai-report/scripts/claude_report_hook.py +0 -257
  19. package/opencode-ohai-report/scripts/create_issue.py +0 -34
  20. package/opencode-ohai-report/scripts/install-claude-plugin.ps1 +0 -254
  21. package/opencode-ohai-report/scripts/install-opencode-plugin.ps1 +0 -264
  22. package/opencode-ohai-report/scripts/install-opencode-plugin.sh +0 -218
  23. package/opencode-ohai-report/scripts/merge-claude-settings.py +0 -99
  24. package/opencode-ohai-report/tools/ohai-report/README.md +0 -151
  25. package/opencode-ohai-report/tools/ohai-report/examples/issue-input.json +0 -26
  26. package/opencode-ohai-report/tools/ohai-report/ohai_report/__init__.py +0 -5
  27. package/opencode-ohai-report/tools/ohai-report/ohai_report/__main__.py +0 -9
  28. package/opencode-ohai-report/tools/ohai-report/ohai_report/cli.py +0 -319
  29. package/opencode-ohai-report/tools/ohai-report/ohai_report/git_context.py +0 -32
  30. package/opencode-ohai-report/tools/ohai-report/ohai_report/gitcode_defaults.py +0 -14
  31. package/opencode-ohai-report/tools/ohai-report/ohai_report/issue_markdown.py +0 -313
  32. package/opencode-ohai-report/tools/ohai-report/ohai_report/metadata.py +0 -360
  33. package/opencode-ohai-report/tools/ohai-report/ohai_report/observability/__init__.py +0 -1
  34. package/opencode-ohai-report/tools/ohai-report/ohai_report/observability/langfuse.py +0 -38
  35. package/opencode-ohai-report/tools/ohai-report/ohai_report/payload.py +0 -64
  36. package/opencode-ohai-report/tools/ohai-report/ohai_report/schema.py +0 -80
  37. package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/__init__.py +0 -1
  38. package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/base.py +0 -15
  39. package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/gitcode.py +0 -405
  40. package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/local.py +0 -21
  41. package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/webhook.py +0 -354
  42. package/opencode-ohai-report/tools/ohai-report/ohai_report/webhook_defaults.py +0 -9
  43. package/opencode-ohai-report/tools/ohai-report/ohai_report/workspace.py +0 -61
  44. package/opencode-ohai-report/tools/ohai-report/ohai_report.py +0 -10
  45. package/opencode-ohai-report/tools/ohai-report/schemas/report_issue.schema.json +0 -166
  46. package/scripts/codex-langfuse-check.mjs +0 -101
  47. package/scripts/codex-langfuse-setup.mjs +0 -181
  48. package/scripts/langfuse-check.mjs +0 -90
  49. package/scripts/langfuse-setup.mjs +0 -278
  50. package/scripts/opencode-langfuse-check.mjs +0 -94
  51. package/scripts/opencode-langfuse-run.mjs +0 -96
  52. package/scripts/opencode-langfuse-setup.mjs +0 -478
  53. package/scripts/resolve-opencode-cli.mjs +0 -58
  54. package/setup-langfuse.bat +0 -163
  55. package/setup-langfuse.sh +0 -130
@@ -1,354 +0,0 @@
1
- """HTTP JSON webhook sink: POST JSON(title、body;非空时含 labels 逗号分隔字符串)供外部建单服务消费。"""
2
-
3
- from __future__ import annotations
4
-
5
- import datetime as dt
6
- import json
7
- import os
8
- import pathlib
9
- import urllib.error
10
- import urllib.request
11
- from dataclasses import dataclass
12
- from typing import Any
13
-
14
- from ..issue_markdown import derive_gitcode_dimension_labels, format_issue_markdown, sanitize_gitcode_label_value
15
- from ..payload import generate_issue_id
16
- from ..webhook_defaults import DEFAULT_WEBHOOK_URL
17
- from .base import SinkResult
18
-
19
-
20
- def parse_labels_value(value: Any) -> list[str]:
21
- """将 issue 字段或 CLI 中的 labels 规范为去重后的非空字符串列表。"""
22
- if value is None:
23
- return []
24
- if isinstance(value, list):
25
- out: list[str] = []
26
- for x in value:
27
- if x is None:
28
- continue
29
- t = str(x).strip()
30
- if t:
31
- out.append(t)
32
- return out
33
- s = str(value).strip()
34
- if not s:
35
- return []
36
- return [p.strip() for p in s.split(",") if p.strip()]
37
-
38
-
39
- def resolve_webhook_post_labels(issue: dict[str, Any], cli_labels: str | None) -> list[str]:
40
- """决定 POST 体中的 labels:显式传入 ``--webhook-labels`` 时仅使用该串(与 GitCode CLI 覆盖 env 一致);否则合并 issue.labels 与 ``OHAI_WEBHOOK_LABELS``。"""
41
- if cli_labels is not None:
42
- return parse_labels_value(cli_labels)
43
- merged: list[str] = []
44
- merged.extend(parse_labels_value(issue.get("labels")))
45
- merged.extend(parse_labels_value(os.environ.get("OHAI_WEBHOOK_LABELS", "")))
46
- seen: set[str] = set()
47
- out: list[str] = []
48
- for p in merged:
49
- if p not in seen:
50
- seen.add(p)
51
- out.append(p)
52
- return out
53
-
54
-
55
- def merge_webhook_post_label_list(
56
- payload: dict[str, Any],
57
- issue: dict[str, Any],
58
- cli_labels: str | None,
59
- ) -> list[str]:
60
- """维度标签(与 gitcode sink 一致)在前,其次 ``resolve_webhook_post_labels``;大小写不敏感去重。"""
61
- dim = derive_gitcode_dimension_labels(payload)
62
- rest = resolve_webhook_post_labels(issue, cli_labels)
63
- seen: set[str] = set()
64
- merged: list[str] = []
65
- for group in (dim, rest):
66
- for name in group:
67
- t = sanitize_gitcode_label_value(name)
68
- if not t:
69
- continue
70
- k = t.casefold()
71
- if k in seen:
72
- continue
73
- seen.add(k)
74
- merged.append(t)
75
- return merged
76
-
77
-
78
- def format_webhook_labels_string(labels: list[str]) -> str:
79
- """接收端常见约定:``\"bug,critical\"`` JSON 字符串,而非数组。"""
80
- return ",".join(str(x).strip() for x in labels if str(x).strip())
81
-
82
-
83
- def format_webhook_labels_color_string(labels: list[str]) -> str:
84
- """将标签列表一一映射为颜色 HEX 字符串,再以逗号分隔。"""
85
- from .gitcode import gitcode_label_color
86
- return ",".join(gitcode_label_color(str(x).strip()) for x in labels if str(x).strip())
87
-
88
-
89
- class WebhookSinkError(RuntimeError):
90
- def __init__(self, message: str, *, status_code: int = 0, body: str = "") -> None:
91
- super().__init__(message)
92
- self.status_code = status_code
93
- self.body = body
94
-
95
-
96
- def _url_from_mapping(m: dict[str, Any]) -> str:
97
- for key in (
98
- "html_url",
99
- "url",
100
- "issue_url",
101
- "link",
102
- "web_url",
103
- "ticket_url",
104
- "browser_url",
105
- "permalink",
106
- "detail_url",
107
- "issueUrl",
108
- "issue_link",
109
- ):
110
- v = m.get(key)
111
- if isinstance(v, str) and v.strip().startswith(("http://", "https://")):
112
- return v.strip()
113
- return ""
114
-
115
-
116
- def _parse_remote_url(raw: str) -> str:
117
- raw = (raw or "").strip()
118
- if not raw:
119
- return ""
120
- first_line = raw.splitlines()[0].strip()
121
- if first_line.startswith(("http://", "https://")):
122
- return first_line
123
- try:
124
- obj = json.loads(raw)
125
- except json.JSONDecodeError:
126
- return ""
127
- if isinstance(obj, list) and obj:
128
- first = obj[0]
129
- if isinstance(first, str) and first.strip().startswith(("http://", "https://")):
130
- return first.strip()
131
- if isinstance(first, dict):
132
- u = _url_from_mapping(first)
133
- if u:
134
- return u
135
- if not isinstance(obj, dict):
136
- return ""
137
- u = _url_from_mapping(obj)
138
- if u:
139
- return u
140
- for nest_key in ("data", "result", "payload", "body", "issue", "ticket", "item"):
141
- nested = obj.get(nest_key)
142
- if isinstance(nested, dict):
143
- u = _url_from_mapping(nested)
144
- if u:
145
- return u
146
- return ""
147
-
148
-
149
- def _extract_extra_ids(obj: dict[str, Any]) -> dict[str, Any]:
150
- out: dict[str, Any] = {}
151
- for key in ("number", "id", "iid", "issue_id", "key", "seq", "ticket_id"):
152
- if key in obj and obj[key] is not None:
153
- out[key] = obj[key]
154
- for nest_key in ("data", "result", "issue", "ticket", "payload"):
155
- data = obj.get(nest_key)
156
- if isinstance(data, dict):
157
- for key in ("number", "id", "iid", "issue_id", "key", "seq"):
158
- if key in data and data[key] is not None:
159
- out.setdefault(key, data[key])
160
- return out
161
-
162
-
163
- @dataclass(frozen=True)
164
- class WebhookConfig:
165
- url: str
166
- secret: str = ""
167
- timeout: float = 45.0
168
-
169
- @classmethod
170
- def from_env(
171
- cls,
172
- *,
173
- url: str | None = None,
174
- secret: str | None = None,
175
- timeout: float | None = None,
176
- ) -> WebhookConfig:
177
- env = os.environ
178
- u = (url or "").strip()
179
- if not u:
180
- u = (env.get("OHAI_WEBHOOK_URL") or env.get("WEBHOOK_URL") or "").strip()
181
- if not u:
182
- u = DEFAULT_WEBHOOK_URL.strip()
183
- if secret is not None:
184
- sec = str(secret).strip()
185
- else:
186
- sec = (env.get("OHAI_WEBHOOK_SECRET") or env.get("WEBHOOK_SECRET") or "").strip()
187
- if timeout is not None:
188
- to = float(timeout)
189
- else:
190
- raw = (env.get("OHAI_WEBHOOK_TIMEOUT") or env.get("WEBHOOK_TIMEOUT") or "").strip()
191
- if raw:
192
- try:
193
- to = float(raw)
194
- except ValueError:
195
- to = 45.0
196
- else:
197
- to = 45.0
198
- return cls(url=u.strip(), secret=sec, timeout=float(to))
199
-
200
-
201
- class WebhookIssueSink:
202
- def __init__(self, config: WebhookConfig) -> None:
203
- self.config = config
204
-
205
- def post_title_body(self, title: str, body: str, labels: list[str] | None = None, labels_color: list[str] | None = None) -> SinkResult:
206
- title = str(title or "").strip()
207
- body = str(body or "").strip()
208
- if not title:
209
- raise WebhookSinkError("title 不能为空")
210
- if not body:
211
- raise WebhookSinkError("body 不能为空")
212
-
213
- body_obj: dict[str, Any] = {"title": title, "body": body}
214
- if labels:
215
- body_obj["labels"] = format_webhook_labels_string(labels)
216
- if labels_color:
217
- body_obj["labels_color"] = format_webhook_labels_color_string(labels_color)
218
- payload = json.dumps(body_obj, ensure_ascii=False).encode("utf-8")
219
- headers: dict[str, str] = {
220
- "Content-Type": "application/json; charset=utf-8",
221
- "Accept": "application/json",
222
- }
223
- if self.config.secret:
224
- headers["X-Webhook-Secret"] = self.config.secret
225
-
226
- req = urllib.request.Request(
227
- self.config.url,
228
- data=payload,
229
- headers=headers,
230
- method="POST",
231
- )
232
- try:
233
- with urllib.request.urlopen(req, timeout=self.config.timeout) as resp:
234
- raw = resp.read().decode("utf-8", errors="replace")
235
- code = int(getattr(resp, "status", 200) or 200)
236
- except urllib.error.HTTPError as exc:
237
- err_body = ""
238
- try:
239
- err_body = exc.read().decode("utf-8", errors="replace")
240
- except OSError:
241
- pass
242
- raise WebhookSinkError(
243
- f"Webhook 请求失败:HTTP {exc.code}",
244
- status_code=int(exc.code or 0),
245
- body=err_body,
246
- ) from exc
247
- except OSError as exc:
248
- raise WebhookSinkError(f"Webhook 请求失败:{exc}") from exc
249
-
250
- if code >= 400:
251
- raise WebhookSinkError(f"Webhook 返回异常状态:HTTP {code}", status_code=code, body=raw[:4096])
252
-
253
- remote = _parse_remote_url(raw)
254
- extra: dict[str, Any] = {"raw_response": raw[:8192]}
255
- try:
256
- obj = json.loads(raw)
257
- if isinstance(obj, dict):
258
- extra.update(_extract_extra_ids(obj))
259
- except json.JSONDecodeError:
260
- pass
261
-
262
- return SinkResult(issue_id="", path=None, url=remote, extra=extra)
263
-
264
- def save(self, payload: dict[str, Any], *, webhook_labels_cli: str | None = None) -> SinkResult:
265
- issue = payload.get("issue") if isinstance(payload.get("issue"), dict) else {}
266
- title = str(issue.get("title") or "").strip()
267
- if not title:
268
- raise WebhookSinkError("issue.title 不能为空")
269
- body = format_issue_markdown(payload)
270
- labels = merge_webhook_post_label_list(payload, issue, webhook_labels_cli)
271
- labels_color = labels if labels else None
272
- result = self.post_title_body(title, body, labels, labels_color)
273
- iid = str(payload.get("issue_id") or "")
274
- return SinkResult(issue_id=iid, path=result.path, url=result.url, extra=result.extra)
275
-
276
-
277
- def apply_webhook_result(payload: dict[str, Any], result: SinkResult) -> dict[str, Any]:
278
- out = dict(payload)
279
- block: dict[str, Any] = {}
280
- if result.url:
281
- block["url"] = result.url
282
- extra = getattr(result, "extra", {}) or {}
283
- raw = extra.get("raw_response")
284
- if isinstance(raw, str) and raw:
285
- block["response_preview"] = raw[:2048]
286
- for key in ("number", "id", "iid", "issue_id"):
287
- if key in extra:
288
- block[key] = extra[key]
289
- out["webhook"] = block
290
- out["status"] = "created-webhook"
291
- return out
292
-
293
-
294
- def push_legacy_issue_json_file(path: pathlib.Path) -> int:
295
- """Load {title, body|content} JSON and POST to webhook(与 scripts/create_issue.py 配套)。
296
-
297
- 成功时打印**一行 JSON**(与 ``ohai_report.py create --sink webhook --json`` 对齐),含 ``issue_id``(本地生成)
298
- 与 ``webhook_url``(从远端响应解析,可能为空),便于 agent / 脚本解析「已上报」链接。
299
- """
300
- try:
301
- with path.open("r", encoding="utf-8") as fp:
302
- data = json.load(fp)
303
- except (OSError, json.JSONDecodeError) as exc:
304
- print(f"Error: cannot read JSON: {exc}")
305
- return 1
306
-
307
- if not isinstance(data, dict):
308
- print("Error: JSON root must be an object")
309
- return 1
310
-
311
- title = str(data.get("title", "")).strip()
312
- body = str(data.get("body") or data.get("content", "")).strip()
313
- if not title or not body:
314
- print("Error: Missing title or body/content in issue file")
315
- return 1
316
-
317
- pseudo_payload: dict[str, Any] = {
318
- "source": str(data.get("source") or "opencode"),
319
- "issue": {k: v for k, v in data.items() if k not in ("title", "body", "content")},
320
- }
321
- issue = pseudo_payload["issue"] if isinstance(pseudo_payload.get("issue"), dict) else {}
322
- labels = merge_webhook_post_label_list(pseudo_payload, issue, None)
323
-
324
- cfg = WebhookConfig.from_env()
325
- try:
326
- sink = WebhookIssueSink(cfg)
327
- result = sink.post_title_body(title, body, labels)
328
- except WebhookSinkError as exc:
329
- err = f"Error {exc.status_code}: {exc.body}" if exc.status_code and exc.body else str(exc)
330
- print(err)
331
- return 1
332
-
333
- now = dt.datetime.now(dt.timezone.utc).astimezone()
334
- iid = generate_issue_id(now)
335
- out: dict[str, Any] = {
336
- "ok": True,
337
- "issue_id": iid,
338
- "webhook_url": (result.url or "").strip(),
339
- "sink": "webhook",
340
- "path": "",
341
- "session_id": "",
342
- "trace_id": "",
343
- "observation_id": "",
344
- "user_id": "",
345
- "message_id": "",
346
- "opencode_subagent": "",
347
- "langfuse_url": "",
348
- }
349
- extra = getattr(result, "extra", None) or {}
350
- for k in ("number", "id", "iid", "issue_id"):
351
- if k in extra:
352
- out[f"webhook_{k}"] = extra[k]
353
- print(json.dumps(out, ensure_ascii=False))
354
- return 0
@@ -1,9 +0,0 @@
1
- """Webhook 默认地址(当环境变量未设置时的回退值)。
2
-
3
- 与 `os.environ.get("WEBHOOK_URL", "<默认>")` 等价逻辑:优先读环境变量
4
- `WEBHOOK_URL` / `OHAI_WEBHOOK_URL`,缺省再使用本常量。
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- DEFAULT_WEBHOOK_URL = "http://42.193.17.157:8081/test"
@@ -1,61 +0,0 @@
1
- """Workspace and filesystem helpers."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import os
7
- import pathlib
8
- from typing import Any
9
-
10
-
11
- def workspace_root() -> pathlib.Path:
12
- return pathlib.Path.cwd()
13
-
14
-
15
- def resolve_workspace_root(cwd_arg: str) -> pathlib.Path:
16
- """仓库根:优先 ``--cwd``,其次 ``OHAI_REPORT_CWD``,否则为进程当前目录。"""
17
- t = (cwd_arg or "").strip()
18
- if t:
19
- return pathlib.Path(t).expanduser().resolve()
20
- env_cwd = (os.environ.get("OHAI_REPORT_CWD") or "").strip()
21
- if env_cwd:
22
- return pathlib.Path(env_cwd).expanduser().resolve()
23
- return workspace_root()
24
-
25
-
26
- def data_dir(root: pathlib.Path) -> pathlib.Path:
27
- path = root / ".ohai-report"
28
- path.mkdir(parents=True, exist_ok=True)
29
- (path / "issues").mkdir(parents=True, exist_ok=True)
30
- return path
31
-
32
-
33
- def read_json(path: pathlib.Path) -> dict[str, Any]:
34
- try:
35
- if not path.exists():
36
- return {}
37
- value = json.loads(path.read_text(encoding="utf-8"))
38
- except (OSError, json.JSONDecodeError):
39
- return {}
40
- return value if isinstance(value, dict) else {}
41
-
42
-
43
- def strip_surrogates(value: Any) -> Any:
44
- if isinstance(value, str):
45
- return value.encode("utf-8", errors="replace").decode("utf-8")
46
- if isinstance(value, list):
47
- return [strip_surrogates(item) for item in value]
48
- if isinstance(value, dict):
49
- return {
50
- strip_surrogates(key) if isinstance(key, str) else key: strip_surrogates(item)
51
- for key, item in value.items()
52
- }
53
- return value
54
-
55
-
56
- def write_json(path: pathlib.Path, value: dict[str, Any]) -> None:
57
- path.parent.mkdir(parents=True, exist_ok=True)
58
- path.write_text(
59
- json.dumps(strip_surrogates(value), ensure_ascii=False, indent=2) + "\n",
60
- encoding="utf-8",
61
- )
@@ -1,10 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Compatibility entrypoint for the ohai-report CLI."""
3
-
4
- from __future__ import annotations
5
-
6
- from ohai_report.cli import main
7
-
8
-
9
- if __name__ == "__main__":
10
- raise SystemExit(main())
@@ -1,166 +0,0 @@
1
- {
2
- "version": "1.4.0",
3
- "description": "Issue template fields for AI development issue reports(含「AI 使用问题反馈」扩展字段,均为可选除必填项外).",
4
- "fields": {
5
- "title": {
6
- "type": "string",
7
- "required": true,
8
- "maxLength": 80,
9
- "description": "标题,简短概括"
10
- },
11
- "category": {
12
- "type": "string",
13
- "required": true,
14
- "enum": [
15
- "模型输出错误",
16
- "工具调用失败",
17
- "Skill 缺陷",
18
- "上下文缺失",
19
- "环境问题",
20
- "其他"
21
- ],
22
- "description": "采集侧分类(枚举),与主分类可并存"
23
- },
24
- "summary": {
25
- "type": "string",
26
- "required": true,
27
- "maxLength": 800,
28
- "description": "问题摘要,可较完整描述现象与上下文"
29
- },
30
- "expected_behavior": {
31
- "type": "string",
32
- "required": true,
33
- "maxLength": 400,
34
- "description": "期望行为 / 可作为改进方向兜底"
35
- },
36
- "actual_behavior": {
37
- "type": "string",
38
- "required": true,
39
- "maxLength": 400,
40
- "description": "实际行为 / 可与影响描述呼应"
41
- },
42
- "severity": {
43
- "type": "string",
44
- "required": true,
45
- "enum": ["P0", "P1", "P2", "P3"],
46
- "default": "P2",
47
- "description": "Issue severity"
48
- },
49
- "user_description": {
50
- "type": "string",
51
- "required": true,
52
- "maxLength": 2000,
53
- "description": "原始用户描述"
54
- },
55
- "agent": {
56
- "type": "string",
57
- "enum": ["opencode", "claude", "codex"],
58
- "description": "使用的 Agent 工具(标签 agent:),当前仅 opencode / claude / codex"
59
- },
60
- "model": {
61
- "type": "string",
62
- "maxLength": 128,
63
- "description": "使用的模型(标签 model:),如 deepseek、minimax、gpt-5.5 等"
64
- },
65
- "level": {
66
- "type": "string",
67
- "enum": ["P0", "P1", "P2", "P3"],
68
- "description": "标签中的问题严重等级 level,与 P0–P3 一致;未填时在正文中沿用 severity"
69
- },
70
- "scenario": {
71
- "type": "string",
72
- "maxLength": 128,
73
- "description": "场景,如 coding / writing"
74
- },
75
- "task_type": {
76
- "type": "string",
77
- "maxLength": 128,
78
- "description": "任务类型"
79
- },
80
- "workflow_phase": {
81
- "type": "string",
82
- "maxLength": 128,
83
- "description": "工作流阶段"
84
- },
85
- "affected_role": {
86
- "type": "string",
87
- "maxLength": 128,
88
- "description": "受影响角色"
89
- },
90
- "primary_category": {
91
- "type": "string",
92
- "maxLength": 128,
93
- "description": "主分类(展示用,如 model-capability)"
94
- },
95
- "sub_category": {
96
- "type": "string",
97
- "maxLength": 128,
98
- "description": "次分类"
99
- },
100
- "classification_confidence": {
101
- "type": "string",
102
- "enum": ["high", "medium", "low"],
103
- "description": "分类置信度"
104
- },
105
- "classification_rationale": {
106
- "type": "string",
107
- "maxLength": 800,
108
- "description": "判断依据"
109
- },
110
- "user_id": {
111
- "type": "string",
112
- "maxLength": 256,
113
- "description": "追踪侧用户标识(Langfuse 等);正文「日志信息」展示公司邮箱请用 user_email"
114
- },
115
- "user_email": {
116
- "type": "string",
117
- "maxLength": 256,
118
- "description": "公司邮箱,展示在 Issue 正文「日志信息」user_email 行(可覆盖 metadata user.email)"
119
- },
120
- "observation_id": {
121
- "type": "string",
122
- "maxLength": 256,
123
- "description": "日志信息中的 observation_id(观测关联 ID)"
124
- },
125
- "message_id": {
126
- "type": "string",
127
- "maxLength": 256,
128
- "description": "OpenCode 当前消息 ID(日志信息,对应 context.messageID)"
129
- },
130
- "opencode_subagent": {
131
- "type": "string",
132
- "maxLength": 128,
133
- "description": "OpenCode 子 Agent 名(日志信息,对应 context.agent,非 LLM 模型名)"
134
- },
135
- "tags": {
136
- "type": "string",
137
- "maxLength": 2000,
138
- "description": "可选备注标签(正文「标签」小节固定展示 agent/model/level)"
139
- },
140
- "impact": {
141
- "type": "string",
142
- "maxLength": 2000,
143
- "description": "影响说明"
144
- },
145
- "possible_causes": {
146
- "type": "string",
147
- "maxLength": 4000,
148
- "description": "可能原因,多行可用换行,行首可加 - "
149
- },
150
- "improvement_direction": {
151
- "type": "string",
152
- "maxLength": 2000,
153
- "description": "改进方向"
154
- },
155
- "suggested_follow_up": {
156
- "type": "string",
157
- "maxLength": 2000,
158
- "description": "建议补充信息"
159
- },
160
- "suggested_dispatch": {
161
- "type": "string",
162
- "maxLength": 500,
163
- "description": "建议分派方向"
164
- }
165
- }
166
- }