claude-controller 0.1.2 → 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.
Files changed (71) hide show
  1. package/README.md +2 -2
  2. package/bin/autoloop.sh +382 -0
  3. package/bin/ctl +1189 -0
  4. package/bin/native-app.py +6 -3
  5. package/bin/watchdog.sh +357 -0
  6. package/cognitive/__init__.py +14 -0
  7. package/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  8. package/cognitive/__pycache__/dispatcher.cpython-314.pyc +0 -0
  9. package/cognitive/__pycache__/evaluator.cpython-314.pyc +0 -0
  10. package/cognitive/__pycache__/goal_engine.cpython-314.pyc +0 -0
  11. package/cognitive/__pycache__/learning.cpython-314.pyc +0 -0
  12. package/cognitive/__pycache__/orchestrator.cpython-314.pyc +0 -0
  13. package/cognitive/__pycache__/planner.cpython-314.pyc +0 -0
  14. package/cognitive/dispatcher.py +192 -0
  15. package/cognitive/evaluator.py +289 -0
  16. package/cognitive/goal_engine.py +232 -0
  17. package/cognitive/learning.py +189 -0
  18. package/cognitive/orchestrator.py +303 -0
  19. package/cognitive/planner.py +207 -0
  20. package/cognitive/prompts/analyst.md +31 -0
  21. package/cognitive/prompts/coder.md +22 -0
  22. package/cognitive/prompts/reviewer.md +33 -0
  23. package/cognitive/prompts/tester.md +21 -0
  24. package/cognitive/prompts/writer.md +25 -0
  25. package/config.sh +6 -1
  26. package/dag/__init__.py +5 -0
  27. package/dag/__pycache__/__init__.cpython-314.pyc +0 -0
  28. package/dag/__pycache__/graph.cpython-314.pyc +0 -0
  29. package/dag/graph.py +222 -0
  30. package/lib/jobs.sh +12 -1
  31. package/package.json +11 -5
  32. package/postinstall.sh +1 -1
  33. package/service/controller.sh +43 -11
  34. package/web/audit.py +122 -0
  35. package/web/checkpoint.py +80 -0
  36. package/web/config.py +2 -5
  37. package/web/handler.py +634 -473
  38. package/web/handler_fs.py +153 -0
  39. package/web/handler_goals.py +203 -0
  40. package/web/handler_jobs.py +372 -0
  41. package/web/handler_memory.py +203 -0
  42. package/web/handler_sessions.py +132 -0
  43. package/web/jobs.py +585 -13
  44. package/web/personas.py +419 -0
  45. package/web/pipeline.py +981 -0
  46. package/web/presets.py +506 -0
  47. package/web/projects.py +246 -0
  48. package/web/static/api.js +141 -0
  49. package/web/static/app.js +25 -1937
  50. package/web/static/attachments.js +144 -0
  51. package/web/static/base.css +497 -0
  52. package/web/static/context.js +204 -0
  53. package/web/static/dirs.js +246 -0
  54. package/web/static/form.css +763 -0
  55. package/web/static/goals.css +363 -0
  56. package/web/static/goals.js +300 -0
  57. package/web/static/i18n.js +625 -0
  58. package/web/static/index.html +215 -13
  59. package/web/static/{styles.css → jobs.css} +746 -1141
  60. package/web/static/jobs.js +1270 -0
  61. package/web/static/memoryview.js +117 -0
  62. package/web/static/personas.js +228 -0
  63. package/web/static/pipeline.css +338 -0
  64. package/web/static/pipelines.js +487 -0
  65. package/web/static/presets.js +244 -0
  66. package/web/static/send.js +135 -0
  67. package/web/static/settings-style.css +291 -0
  68. package/web/static/settings.js +81 -0
  69. package/web/static/stream.js +534 -0
  70. package/web/static/utils.js +131 -0
  71. package/web/webhook.py +210 -0
@@ -0,0 +1,132 @@
1
+ """
2
+ Session 목록 HTTP 핸들러 Mixin
3
+
4
+ Claude Code 네이티브 세션 + history.log + job meta 파일을 통합하여 세션 목록을 제공한다.
5
+ """
6
+
7
+ import json
8
+ import os
9
+
10
+ from config import LOGS_DIR, SESSIONS_DIR, CLAUDE_PROJECTS_DIR
11
+ from utils import parse_meta_file, cwd_to_project_dir, scan_claude_sessions
12
+
13
+
14
+ class SessionHandlerMixin:
15
+
16
+ def _handle_sessions(self, filter_cwd=None):
17
+ seen = {}
18
+
19
+ # 0) Claude Code 네이티브 세션 스캔
20
+ if filter_cwd:
21
+ proj_name = cwd_to_project_dir(filter_cwd)
22
+ project_dirs = [CLAUDE_PROJECTS_DIR / proj_name]
23
+ else:
24
+ if CLAUDE_PROJECTS_DIR.exists():
25
+ all_dirs = sorted(
26
+ (d for d in CLAUDE_PROJECTS_DIR.iterdir() if d.is_dir()),
27
+ key=lambda d: d.stat().st_mtime,
28
+ reverse=True,
29
+ )
30
+ project_dirs = all_dirs[:15]
31
+ else:
32
+ project_dirs = []
33
+
34
+ for pd in project_dirs:
35
+ native = scan_claude_sessions(pd, limit=60)
36
+ for sid, info in native.items():
37
+ if sid not in seen:
38
+ seen[sid] = info
39
+
40
+ # 1) Job meta 파일에서 보강
41
+ if LOGS_DIR.exists():
42
+ meta_files = sorted(
43
+ LOGS_DIR.glob("job_*.meta"),
44
+ key=lambda f: int(f.stem.split("_")[1]),
45
+ reverse=True,
46
+ )
47
+ for mf in meta_files:
48
+ meta = parse_meta_file(mf)
49
+ if not meta:
50
+ continue
51
+ sid = meta.get("SESSION_ID", "").strip()
52
+ if not sid:
53
+ continue
54
+
55
+ status = meta.get("STATUS", "unknown")
56
+ if status == "running" and meta.get("PID"):
57
+ try:
58
+ os.kill(int(meta["PID"]), 0)
59
+ except (ProcessLookupError, ValueError, OSError):
60
+ status = "done"
61
+
62
+ job_id = meta.get("JOB_ID", "")
63
+ cost_usd = None
64
+ if status in ("done", "failed"):
65
+ out_file = LOGS_DIR / f"job_{job_id}.out"
66
+ if out_file.exists():
67
+ try:
68
+ for line in open(out_file, "r"):
69
+ try:
70
+ obj = json.loads(line.strip())
71
+ if obj.get("type") == "result":
72
+ cost_usd = obj.get("total_cost_usd")
73
+ except json.JSONDecodeError:
74
+ continue
75
+ except OSError:
76
+ pass
77
+
78
+ entry = {
79
+ "session_id": sid,
80
+ "job_id": job_id,
81
+ "prompt": meta.get("PROMPT", ""),
82
+ "timestamp": meta.get("CREATED_AT", ""),
83
+ "status": status,
84
+ "cwd": meta.get("CWD", ""),
85
+ "cost_usd": cost_usd,
86
+ "slug": "",
87
+ }
88
+
89
+ if sid not in seen:
90
+ seen[sid] = entry
91
+ else:
92
+ existing = seen[sid]
93
+ if existing.get("job_id") is None:
94
+ existing.update({"job_id": job_id, "status": status, "cost_usd": cost_usd})
95
+ else:
96
+ try:
97
+ if int(job_id) > int(existing.get("job_id", 0)):
98
+ seen[sid] = entry
99
+ except (ValueError, TypeError):
100
+ pass
101
+
102
+ # 2) history.log 보충
103
+ history_file = SESSIONS_DIR / "history.log"
104
+ if history_file.exists():
105
+ try:
106
+ for line in history_file.read_text("utf-8").strip().split("\n"):
107
+ parts = line.split("|", 2)
108
+ if len(parts) >= 2:
109
+ ts, sid = parts[0].strip(), parts[1].strip()
110
+ if not sid:
111
+ continue
112
+ prompt = parts[2].strip() if len(parts) > 2 else ""
113
+ if sid not in seen:
114
+ seen[sid] = {
115
+ "session_id": sid, "job_id": None,
116
+ "prompt": prompt, "timestamp": ts,
117
+ "status": "done", "cwd": None,
118
+ "cost_usd": None, "slug": "",
119
+ }
120
+ except OSError:
121
+ pass
122
+
123
+ # cwd 필터 적용
124
+ if filter_cwd:
125
+ norm = os.path.normpath(filter_cwd)
126
+ seen = {
127
+ sid: s for sid, s in seen.items()
128
+ if s.get("cwd") and os.path.normpath(s["cwd"]) == norm
129
+ }
130
+
131
+ sessions = sorted(seen.values(), key=lambda s: s.get("timestamp") or "", reverse=True)
132
+ self._json_response(sessions[:50])