coze_lab 0.1.16 → 0.1.17

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/index.js CHANGED
@@ -4572,6 +4572,7 @@ function writeCodexHook(token, workspaceId, pythonCmd, codexHome, cloud) {
4572
4572
  if (!cloud) {
4573
4573
  envLines.push(`COZELOOP_API_TOKEN=${token}`);
4574
4574
  }
4575
+ envLines.push(`CODEX_HOME=${home}`);
4575
4576
  envLines.push(`COZELOOP_HOOK_LOG=${logFile}`);
4576
4577
  envLines.push('TRACE_TO_COZELOOP=true');
4577
4578
  // PPE 泳道:cozeloop SDK 读这两个环境变量,自动注入 x-tt-env / x-use-ppe header
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coze_lab",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Configure local AI agents (Claude Code, Codex, OpenClaw) to report traces to CozeLoop",
5
5
  "keywords": [
6
6
  "cozeloop",
@@ -366,6 +366,101 @@ def read_rollout_messages(transcript_path: str, start_line: int = 0) -> List[Dic
366
366
  return entries
367
367
 
368
368
 
369
+ def _add_unique_path(paths: List[Path], p: Optional[Path]):
370
+ if not p:
371
+ return
372
+ try:
373
+ resolved = p.expanduser().resolve()
374
+ except Exception:
375
+ return
376
+ if resolved not in paths:
377
+ paths.append(resolved)
378
+
379
+
380
+ def _candidate_codex_homes() -> List[Path]:
381
+ homes: List[Path] = []
382
+ _add_unique_path(homes, Path(os.environ["CODEX_HOME"]) if os.environ.get("CODEX_HOME") else None)
383
+
384
+ log_path = _log_file_path()
385
+ if log_path:
386
+ try:
387
+ log_parent = Path(log_path).expanduser().resolve().parent
388
+ if log_parent.name == "hooks":
389
+ _add_unique_path(homes, log_parent.parent)
390
+ except Exception:
391
+ pass
392
+
393
+ _add_unique_path(homes, Path.home() / ".codex")
394
+
395
+ agents_root = Path.home() / ".coze" / "agents"
396
+ try:
397
+ if agents_root.exists():
398
+ for child in agents_root.iterdir():
399
+ _add_unique_path(homes, child / "codex-home")
400
+ except Exception:
401
+ pass
402
+
403
+ return homes
404
+
405
+
406
+ def _latest_file(paths: List[Path]) -> Optional[Path]:
407
+ existing = []
408
+ for p in paths:
409
+ try:
410
+ if p.is_file():
411
+ existing.append(p)
412
+ except Exception:
413
+ pass
414
+ if not existing:
415
+ return None
416
+ return max(existing, key=lambda p: p.stat().st_mtime)
417
+
418
+
419
+ def find_latest_transcript() -> Optional[str]:
420
+ """Best-effort fallback when Codex does not pass hook stdin."""
421
+ candidates: List[Path] = []
422
+ for codex_home in _candidate_codex_homes():
423
+ sessions_dir = codex_home / "sessions"
424
+ if not sessions_dir.exists():
425
+ continue
426
+ try:
427
+ candidates.extend(sessions_dir.rglob("rollout-*.jsonl"))
428
+ except Exception as e:
429
+ hook_log(f"fallback scan failed dir={sessions_dir} error={repr(e)}")
430
+
431
+ latest = _latest_file(candidates)
432
+ if latest:
433
+ return str(latest)
434
+
435
+ for codex_home in _candidate_codex_homes():
436
+ sessions_dir = codex_home / "sessions"
437
+ if not sessions_dir.exists():
438
+ continue
439
+ try:
440
+ candidates.extend(sessions_dir.rglob("*.jsonl"))
441
+ except Exception:
442
+ pass
443
+
444
+ latest = _latest_file(candidates)
445
+ return str(latest) if latest else None
446
+
447
+
448
+ def recover_hook_input(reason: str) -> Optional[Dict[str, Any]]:
449
+ transcript_path = find_latest_transcript()
450
+ if not transcript_path:
451
+ hook_log(f"fallback failed reason={reason} no transcript found")
452
+ print(f"[CozeLoop] Hook input missing ({reason}); no Codex transcript found.", file=sys.stderr)
453
+ return None
454
+ hook_log(f"fallback transcript reason={reason} path={transcript_path}")
455
+ print(f"[CozeLoop] Hook input missing ({reason}); fallback transcript: {transcript_path}", file=sys.stderr)
456
+ return {
457
+ "hook_event_name": "Stop",
458
+ "session_id": "",
459
+ "transcript_path": transcript_path,
460
+ "input_fallback": reason,
461
+ }
462
+
463
+
369
464
  def parse_session_meta(entries: List[Dict[str, Any]]) -> Dict[str, Any]:
370
465
  """Extract session identity from session_meta entry."""
371
466
  result = {
@@ -1076,28 +1171,41 @@ def main():
1076
1171
  try:
1077
1172
  raw_input = sys.stdin.read().strip()
1078
1173
  if not raw_input:
1079
- hook_log("skip no stdin")
1174
+ hook_log("stdin empty, trying fallback")
1080
1175
  debug_log("No input received from stdin")
1081
- return
1082
- hook_input = json.loads(raw_input)
1176
+ hook_input = recover_hook_input("empty_stdin")
1177
+ if not hook_input:
1178
+ return
1179
+ else:
1180
+ hook_input = json.loads(raw_input)
1083
1181
  except Exception as e:
1084
- hook_log(f"skip stdin parse error={repr(e)}")
1182
+ hook_log(f"stdin parse error={repr(e)}, trying fallback")
1085
1183
  debug_log(f"Error reading hook input from stdin: {e}")
1086
- return
1184
+ hook_input = recover_hook_input("stdin_parse_error")
1185
+ if not hook_input:
1186
+ return
1087
1187
 
1088
1188
  debug_log(f"Hook input: {json.dumps(hook_input, ensure_ascii=False)}")
1089
1189
 
1090
1190
  # Get transcript path
1091
1191
  transcript_path = hook_input.get("transcript_path")
1092
1192
  if not transcript_path:
1093
- hook_log("skip missing transcript_path")
1193
+ hook_log("missing transcript_path, trying fallback")
1094
1194
  debug_log("No transcript_path in hook input")
1095
- return
1195
+ recovered = recover_hook_input("missing_transcript_path")
1196
+ if not recovered:
1197
+ return
1198
+ hook_input = recovered
1199
+ transcript_path = hook_input.get("transcript_path")
1096
1200
 
1097
1201
  if not os.path.exists(transcript_path):
1098
- hook_log(f"skip transcript not found path={transcript_path}")
1202
+ hook_log(f"transcript not found path={transcript_path}, trying fallback")
1099
1203
  debug_log(f"Transcript file not found: {transcript_path}")
1100
- return
1204
+ recovered = recover_hook_input("transcript_not_found")
1205
+ if not recovered:
1206
+ return
1207
+ hook_input = recovered
1208
+ transcript_path = hook_input.get("transcript_path")
1101
1209
 
1102
1210
  # Load state
1103
1211
  state_file = get_state_file_path(transcript_path)