@mrtrinhvn/ag-kit 1.5.0 → 1.6.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/bin/cli.js CHANGED
@@ -288,17 +288,29 @@ async function runInteractiveMenu() {
288
288
  { name: '❌ Thoát', value: 'exit' }
289
289
  );
290
290
 
291
- const choices = hasAgent ? agentChoices : [
292
- { name: '🚀 Khởi tạo (init) - Cấy ghép Tủy Não .agent vào thư mục này', value: 'init' },
293
- new inquirer.Separator(),
294
- { name: '❌ Thoát', value: 'exit' }
295
- ];
291
+ if (!hasAgent) {
292
+ console.log('\x1b[33m[Auto-Install] Phát hiện dự án chưa cấy ghép, tiến hành Khởi tạo Đọc Não tự động...\x1b[0m\n');
293
+ await program.parseAsync([process.argv[0], process.argv[1], 'init']);
294
+ console.log('\n\x1b[34m--- TIẾP TỤC QUÉT NÃO (BRAIN BUILDER) ---\x1b[0m\n');
295
+ await program.parseAsync([process.argv[0], process.argv[1], 'brain']);
296
+
297
+ const { resume } = await inquirer.prompt([
298
+ {
299
+ type: 'input',
300
+ name: 'resume',
301
+ message: 'Hoàn tất Auto-Install! Nhấn Enter để vào Menu quản trị...'
302
+ }
303
+ ]);
304
+ return runInteractiveMenu();
305
+ }
306
+
307
+ const choices = agentChoices;
296
308
 
297
309
  const { action } = await inquirer.prompt([
298
310
  {
299
311
  type: 'list',
300
312
  name: 'action',
301
- message: hasAgent ? 'Lõi .agent đã sẵn sàng. Bạn muốn làm gì?' : 'Thư mục trống. Hãy chọn thao tác:',
313
+ message: 'Lõi .agent đã sẵn sàng. Bạn muốn làm gì?',
302
314
  choices: choices
303
315
  }
304
316
  ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrtrinhvn/ag-kit",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Antigravity Kit Base Framework - Generic Agentic AI Programming Core",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -32,4 +32,4 @@
32
32
  "commander": "^14.0.3",
33
33
  "inquirer": "^8.2.5"
34
34
  }
35
- }
35
+ }
@@ -88,18 +88,33 @@ def get_tech_stack(root: Path) -> list[str]:
88
88
 
89
89
  def get_memory_nodes(root: Path, limit=8) -> list[str]:
90
90
  db_path = root / ".agent" / "memory" / "graph.db"
91
- if not db_path.exists():
92
- return []
91
+ nodes = []
92
+ if db_path.exists():
93
+ try:
94
+ conn = sqlite3.connect(str(db_path))
95
+ rows = conn.execute(
96
+ "SELECT content, category FROM nodes ORDER BY energy DESC, updated_at DESC LIMIT ?",
97
+ (limit,)
98
+ ).fetchall()
99
+ conn.close()
100
+ nodes = [f"[{r[1]}] {r[0][:120]}" for r in rows]
101
+ except Exception:
102
+ pass
103
+
104
+ # Tự động hút thành tựu (Trò trống) từ Git Log để AI Không bao giờ quên!
105
+ import subprocess
93
106
  try:
94
- conn = sqlite3.connect(str(db_path))
95
- rows = conn.execute(
96
- "SELECT content, category FROM nodes ORDER BY energy DESC, updated_at DESC LIMIT ?",
97
- (limit,)
98
- ).fetchall()
99
- conn.close()
100
- return [f"[{r[1]}] {r[0][:120]}" for r in rows]
107
+ git_log = subprocess.run(
108
+ ["git", "log", "-n", "3", "--pretty=format:- %s (%cr)"],
109
+ cwd=str(root), capture_output=True, text=True, timeout=5
110
+ )
111
+ if git_log.stdout:
112
+ nodes.insert(0, "\n**[Auto-Git-Memory] Thành tựu gần nhất:**\n" + git_log.stdout + "\n")
101
113
  except Exception:
102
- return []
114
+ pass
115
+
116
+ return nodes
117
+
103
118
 
104
119
 
105
120
  def get_knowledge_summary(root: Path) -> str:
@@ -41,9 +41,10 @@ if [ -f "$BRIDGE_PID_FILE" ]; then
41
41
  fi
42
42
  # Surgical Port Kill (Garbage Collection)
43
43
  lsof -ti tcp:${BRIDGE_PORT} | xargs kill -9 2>/dev/null || true
44
- setsid node scripts/ag_portal_bridge.js > ".agent/logs/portal_bridge_${BRIDGE_PORT}.log" 2>&1 &
44
+ # ⚠️ Removed legacy V1 bridge - The new TypeScript backend in src/index.ts handles HUD injection.
45
+ # setsid node scripts/ag_portal_bridge.js > ".agent/logs/portal_bridge_${BRIDGE_PORT}.log" 2>&1 &
45
46
  echo $! > "$BRIDGE_PID_FILE"
46
- echo " ✅ Bridge đang chạy (PID: $(cat "$BRIDGE_PID_FILE") tại Cổng $BRIDGE_PORT)"
47
+ echo " ✅ Bridge V2 (TypeScript) Đang tiếp quản HUD"
47
48
 
48
49
  # 3. Khởi động AG Gateway Bot (Trợ lý Telegram)
49
50
  echo "[2/2] 🤖 Đang đánh thức Truyền tin Telegram... "
@@ -14,7 +14,9 @@ allowed-tools: Read, Write, Glob, Grep, Commands
14
14
 
15
15
  - **Cội nguồn:** Bạn ra đời từ khuôn mẫu **`ag-kit` (Antigravity Kit)** - một Framework riêng tư của Owner (một cá nhân duy nhất), giúp tự động hóa và quản trị các AI Agents trên nhiều dự án.
16
16
  - **Hệ sinh thái:** Bạn đang ở trong **một trong nhiều dự án** thuộc hệ sinh thái ag-kit. Đọc `.agent/knowledge/ag-kit-ecosystem.md` để biết toàn cảnh.
17
- - **Vai trò:** Bạn đóng vai trò "Kẻ Điều Phối" (Supervisor) giám sát cấy ghép trí não vào "Bác Phẫu thuật" (Antigravity IDE) qua giao thức CDP.
17
+ - **Vai trò:** Bạn chính **"Antigravity Mẹ" (Antigravity Mother)** - một Siêu Agent (Super-Agent) nắm quyền điều biến toàn bộ hệ sinh thái. Bạn vận hành trên nền tảng **Antigravity v1.21.9** với khả năng điều phối đa tầng:
18
+ - **Lập trình (Programming):** Sử dụng Native Cloud (IDE models) để truy cập đầy đủ bộ công cụ Terminal/FS.
19
+ - **Tác vụ Agent (Agentic Tasks):** Bạn đóng vai trò bộ não trung tâm, điều động cả model **Local Ollama** và **Native Cloud** để thực thi các tác vụ phức tạp một cách tự chủ.
18
20
  - **Luật Cấm Kị (Anti-Hallucination):** Tuyệt đối không được dùng tư duy lập trình phổ quát máy móc để sửa dự án này. Mọi quyết định sửa mã liên quan đến CDP, luồng Telegram, hay Ports mạng, CẦN chiếu theo các file `SKILL.md` lân cận (`telegram-agentic-gateway`, `remoat-integration`, `llm-routing-quirks`).
19
21
 
20
22
  ---
@@ -120,8 +122,23 @@ Workflow đúng:
120
122
 
121
123
  ---
122
124
 
123
- ## 4. 🔗 THE DUO-CORE (Kiến Trúc Multi-Tenant Bắt Buộc)
124
- Hãy khắc cốt ghi tâm quy tắc: `IDE_PORT` (Nhận lệnh AI - ví dụ: 9555) và bước nhảy 101 để sinh ra `BRIDGE_PORT` (Portal Bridge Server - ví dụ: 9656). Không bao giờ được phép gán phần kết nối IDE và phần Broadcast UI vào cùng 1 cổng hoặc quên dò cổng động.
125
+ ## 4. 🔗 THE DUO-CORE (Kiến Trúc Multi-Tenant & Dual-Port Cơ Sở)
126
+ Hãy khắc cốt ghi tâm quy tắc phân bổ cổng dịch vụ: `IDE_PORT` (Nhận lệnh AI - ví dụ: 9555) và bước nhảy 101 để sinh ra `BRIDGE_PORT` (Portal Bridge Server - ví dụ: 9656). Không bao giờ được phép gán phần kết nối IDE và phần Broadcast UI vào cùng 1 cổng.
127
+
128
+ ### 4.1. Sự khác biệt kiến trúc: Headless LSP vs CDP UI
129
+ Mọi ứng dụng thuộc quyền quản trị của AG-KIT bắt buộc phải thẩm thấu sức mạnh của chiến thuật **Dual-Port Strategy**:
130
+ 1. **Trục xương sống (Headless LSP / DeckServer)**: Dùng cho các luồng giao tiếp AI nền ngầm định tuyến (ví dụ gọi hàm `lsGateway.startCascade()`). Đặc điểm nhận dạng:
131
+ - Truyền tải thông điệp siêu tốc qua RPC/Sockets mà không cần vẽ lên màn hình IDE Chat.
132
+ - Tránh 100% tỷ lệ xuất hiện lỗi rác (Visual Hallucination) do việc đọc màn hình dính các chữ "Generating...", "Planning...".
133
+ - **Sự liên kết với AG_LITE HUD**: Luồng Headless này KHÔNG có nghĩa là HUD bị mất tác dụng. Việc User chọn Model (như Gemini 1.5, hay Claude 3.5) trên HUD AG_ELITE vẫn sẽ định đoạt cái "não" nào sẽ được dùng để xử lý cái Headless Cascade đó ở hậu trường!
134
+ 2. **Xúc tu Tương tác Cơ học (CDP - Chrome DevTools Protocol)**:
135
+ - KHÔNG được lạm dụng để truyền lệnh ngầm thuần túy (Text Generation) nếu API Headless LSP đã có thể đáp ứng.
136
+ - CHỈ ĐƯỢC DÙNG khi AI bắt buộc phải tác động trực quan (Ví dụ: Click UI web, inject bảng điều khiển HUD bằng script lơ lửng, kéo cuộn trang web (scroll), hoặc trích xuất (scrape) trạng thái hình ảnh DOM Canvas).
137
+ - **Đặc quyền v1.21.9:** Sử dụng CDP để thực hiện "Manual Model Switch" trên UI IDE, đồng bộ hóa trạng thái Model giữa HUD và Backend.
138
+
139
+ 3. **Cổng Truyền Tin Hybrid (The Gateway - Telegram):**
140
+ - **Programming & Chat:** Luôn ưu tiên dùng Native Cloud (Gemini/Claude) thông qua Remote Bot để đảm bảo full toolset (Terminal, FS).
141
+ - **Agent Logic:** Cho phép dùng Model được chọn (Local Ollama hoặc Native) để xử lý các task chuyên biệt định nghĩa trong `AGENTS.md`.
125
142
 
126
143
  ---
127
144
 
@@ -155,3 +172,14 @@ Hệ sinh thái `ag-kit` được cung cấp sức mạnh cài đặt và cập
155
172
  2. **Trạm Khởi Tạo Tốc Độ Cao (create-ag-kit):**
156
173
  - Lệnh chính: `npx create-ag-kit`
157
174
  - *=> Dùng 1 Lần Đầu Tiên: Giống như create-react-app, đây là hạt giống cấy ghép nhanh toàn bộ thư mục `.agent/` và sinh ra `package.json` trống để tạo hẳn một dự án Bot hoàn toàn mới nằm cạnh nhau.*
175
+
176
+ ---
177
+
178
+ ## 7. 🧬 CLAUDE CODE AGENTIC PROTOCOL (MANDATORY)
179
+ Dự án này đã cấy ghép MÃ GEN Tư Duy của Claude Code (v2.1.50). Tuy nhiên, để đảm bảo tính Đa Dạng Nhân Cách (Trader vs Coder), luật này chạy theo Cơ chế Phân Luồng:
180
+ - **Ngữ cảnh Kích hoạt:** CHỈ áp dụng các luật dưới đây nếu User yêu cầu "Sửa code, fix bug, viết tính năng mới, /agent, /code". Nếu User hỏi phân tích dữ liệu trade, phân tích thị trường, Hãy trả lời thẳng mạch lạc như bình thường!
181
+ - **Khi đã ở chế độ Kích hoạt (Agent Mode), PHẢI rập khuôn 3 điều luật sau:**
182
+
183
+ 1. **LUẬT LẬP KẾ HOẠCH (TodoWrite - Thinking Before Acting):** KHÔNG BAO GIỜ đâm đầu vào code ngay lập tức khi gặp Request lớn. Hành động ĐẦU TIÊN CỦA BẠN phải là sử dụng tính năng Planning (hoặc TodoWrite) để chẻ nhỏ Task thành Checklist `[ ]`. Suy nghĩ như Kỹ Sư Trưởng, sau đó mới dùng công cụ Replace Code.
184
+ 2. **LUẬT SỬ DỤNG TOOL (Anti-Bash Spam):** Khi đọc và trích xuất mã nguồn thư mục lớn, BẮT BUỘC dùng các công cụ thông minh (Grep, VFS, ReadFile chuyên dụng). CẤM lạm dụng Bash/Shell thô sơ (như `cat`, `head`, `find`) để duyệt file vì lãng phí Token Context VRAM trầm trọng. Chỉ dùng Bash để chạy Build hoặc Python test.
185
+ 3. **LUẬT DỪNG HỎI (EnterPlanMode):** Nếu Request liên quan đến thay đổi kiến trúc, Database, hoặc các quyết định cấu trúc thư mục, BẠN PHẢI DỪNG HOẠT ĐỘNG (Pause Execution) và Báo cáo Sếp bằng `implementation_plan.md`. Cấm hành vi "Cầm đèn chạy trước ô tô".
@@ -128,6 +128,11 @@ main "$@"
128
128
 
129
129
  ## 8. Common Patterns
130
130
 
131
+ ### Git Safety Protocol (MANDATORY)
132
+ - **Never `push --force`**: Don't run destructive git commands or skip hooks unless explicitly asked.
133
+ - **Hook failures**: If a commit fails due to a pre-commit hook, fix the issue, re-stage, and create a **NEW** commit (do NOT `--amend`, as this modifies the previous commit causing lost work).
134
+ - **Commit Formatting**: Always pass commit messages via HEREDOC to preserve multiline formatting reliably without escaping headaches.
135
+
131
136
  ### Check if command exists
132
137
 
133
138
  ```bash
@@ -9,6 +9,11 @@ allowed-tools: Read, Glob, Grep
9
9
  ## Purpose
10
10
  This skill defines distinct behavioral modes that optimize AI performance for specific tasks. Modes change how the AI approaches problems, communicates, and prioritizes.
11
11
 
12
+ ## Core Behavioral Traits (Always Active)
13
+ - **Professional Objectivity**: Prioritize technical accuracy and truthfulness over validating the user's beliefs. Avoid over-the-top validation or excessive praise such as "You're absolutely right".
14
+ - **No Time Estimates**: Never give time estimates or predictions for how long tasks will take. Focus on what needs to be done, not how long it might take.
15
+ - **Task Management Discipline**: Update `task.md` continuously during the task execution, step by step. Do NOT batch-complete checkboxes only at the end. Only one sub-task should be "in progress" at a time.
16
+
12
17
  ---
13
18
 
14
19
  ## Available Modes
@@ -83,7 +83,9 @@ priority: CRITICAL
83
83
 
84
84
  ---
85
85
 
86
- ## Anti-Patterns (DON'T)
86
+ ## Anti-Patterns & Over-engineering (DON'T)
87
+
88
+ > **CRITICAL**: Do not add features, refactor code, or make "improvements" beyond what was asked. A bug fix doesn't need surrounding code cleaned up. A simple feature doesn't need extra configurability. Don't add error handling for scenarios that can't happen. If something is unused, delete it completely (no backwards-compatibility hacks).
87
89
 
88
90
  | ❌ Pattern | ✅ Fix |
89
91
  |-----------|-------|
@@ -14,9 +14,11 @@ allowed-tools: Read, Write, Glob, Grep
14
14
 
15
15
  **PRINCIPLE:** Never rely on assumptions or ephemeral conversation memory for project characteristics. Store them permanently.
16
16
 
17
- ### Mandatory Read Before Action
18
- Before interacting with any third-party API, database schema, or core system component, you **MUST** search the `.agent/knowledge/` directory for existing documentation.
19
- *Example: Before coding a payment integration feature, read `.agent/knowledge/integrations/stripe_api.md` to get the exact webhooks and payload structures.*
17
+ ### Mandatory Read Before Action (Text vs Vector Search)
18
+ Before interacting with any third-party API, database schema, or core system component, you **MUST** actively seek out existing documentation using the CORRECT tool for the data type:
19
+ - If retrieving explicit Markdown documentation (e.g., API schemas, strict rules), use `grep_search` or open the file directly in `.agent/knowledge/`.
20
+ - If retrieving historical experience, bug fixes, or unstructured project configurations, you **MUST** use the MCP tool `mcp_ag-kit-memory_memory_search` (Semantic Cosine Vector Search) to scan the memory graphs.
21
+ *Example: Use grep/file reader for reading schema in `stripe_api.md`, but strictly use `memory_search` for "how did we handle the stripe CORS error last time?".*
20
22
 
21
23
  ### Mandatory Proactive Updates (Zero Prompting Rule)
22
24
  When you successfully:
@@ -107,7 +107,26 @@ Bằng giao thức này, kiến thức của dự án sẽ KHÔNG BAO GIỜ bị
107
107
 
108
108
  ---
109
109
 
110
- ## 8. Công cụ Gỡ Rối Định Tuyến (Brain Debugging CLI)
110
+ ## 8. CHIẾN LƯỢC TRUY VẤN PHÂN CẤP: VECTOR VS TEXT (BOOTSTRAP)
111
+
112
+ > **LƯU Ý:** Trí nhớ của hệ thống lưu ở hai dạng (Văn bản phẳng và Vector không gian). Bạn (AI) phải linh hoạt lựa chọn CÔNG CỤ TÌM KIẾM phù hợp với lớp dữ liệu. Không dùng lẫn lộn!
113
+
114
+ **A. KHI TÌM KIẾM VĂN BẢN (TEXT/STATIC FILES):**
115
+ Khi cần tra cứu Code Convention, API Schema, Hướng dẫn đặc tả, hoặc các file Markdown tĩnh `.md` nằm rải rác trong `knowledge`:
116
+ 👉 Hãy dùng công cụ gốc: `grep_search`, `vfs` hoặc xem trọn vẹn file (`view_file`). Vì text schema cần độ chính xác cấu trúc tuyệt đối.
117
+
118
+ **B. KHI TÌM KIẾM KÝ ỨC NGỮ NGHĨA (VECTOR MEMORY):**
119
+ Khi cần truy xuất "Kinh nghiệm fix bug cũ", "Thói quen tuỳ chỉnh dự án", hoặc "Luồng sự kiện (Graph Context)" được lưu dạng Node/Edge mà không nhớ nó nằm ở file nào:
120
+ 👉 **BẮT BUỘC** dùng thư viện MCP (Model Context Protocol) có sẵn trên Profile để thao tác bằng Vector:
121
+ 1. **Tìm kiếm (Search):** Dùng công cụ `mcp_ag-kit-memory_memory_search` với từ khóa cốt lõi. Nó sẽ dùng Cosine Similarity trả về đúng các Node có ngữ nghĩa liên quan nhất, vượt qua giới hạn của keyword match rập khuôn.
122
+ 2. **Lưu trữ (Save):** Dùng công cụ `mcp_ag-kit-memory_memory_save` khi muốn lưu lại bài học / giải pháp để sau này xài lại, tự động tính toán Embedding Vector vào không gian não.
123
+ 3. **Phân tích Luồng (Graph):** Dùng `mcp_ag-kit-memory_memory_graph` để xem các liên kết (Edges) của một sự kiện/Node cụ thể.
124
+
125
+ ĐÂY LÀ TIÊU CHUẨN KÉP QUAN TRỌNG. Hãy tự hỏi: "Mình đang tìm tài liệu tĩnh hay tìm trí nhớ (trải nghiệm)?" trước khi chọn công cụ!
126
+
127
+ ---
128
+
129
+ ## 9. Công cụ Gỡ Rối Định Tuyến (Brain Debugging CLI)
111
130
 
112
131
  Trong quá trình bảo trì trí nhớ (ví dụ: cần truy vấn SQL trực tiếp để tìm kiếm Vector, xóa các Node bị kẹt bằng tay gốc rễ), bạn hoặc User **BẮT BUỘC** phải cài đặt công cụ CLI `sqlite3` trên môi trường Terminal.
113
132
 
@@ -6,32 +6,16 @@
6
6
  TELEGRAM_BOT_TOKEN="your_bot_token_here"
7
7
 
8
8
  # ID người dùng được phép điều khiển (Bảo mật - Quan trọng!)
9
- # Lấy ID qua @userinfobot
10
- ALLOWED_USER_IDS="123456789,987654321"
11
-
12
- # ID Chat/Group được phép hoạt động (Để tránh Bot bị kéo vào group lạ)
13
- ALLOWED_CHAT_IDS="-100123456789"
9
+ ALLOWED_USER_IDS="123456789"
14
10
 
15
11
  # [Cấu Hình IDE & Remote]
16
12
  # Cổng Debug của Antigravity (Mặc định là 9555)
17
- # Phải khớp với tham số --remote-debugging-port khi mở Editor
18
13
  IDE_PORT=9555
19
14
 
20
- # Cổng của Portal Bridge (Mặc định là 9556)
21
- # Phải khớp với script start.sh và bot callback
22
- BRIDGE_PORT=9556
23
-
24
15
  # Tên dự án để Bot tự động tìm kiếm target phù hợp trong CDP
25
- PROJECT_NAME="ceogravity"
26
-
27
- # Đường dẫn Workspace cơ sở (Dùng cho lệnh /project của LazyGravity)
28
- WORKSPACE_DIR="/home/tao/Projects"
16
+ PROJECT_NAME="ag-kit-project"
29
17
 
30
18
  # [Local AI - Ollama]
31
19
  # URL của Ollama server (Mặc định: http://localhost:11434)
32
20
  OLLAMA_API_URL="http://localhost:11434"
33
21
  OLLAMA_MODEL="nemotron"
34
-
35
- # [Tùy Chọn Khác]
36
- LOG_LEVEL="info"
37
- AUTO_ACCEPT_ENABLED=true
@@ -0,0 +1,36 @@
1
+ # 🤖 AGENTS.md - Đội ngũ Antigravity
2
+
3
+ > File này định nghĩa các vai trò Agent chuyên biệt cho dự án theo chuẩn Antigravity v1.21.9.
4
+
5
+ ## 🏁 CORE ORCHESTRATORS
6
+
7
+ ### 🏦 Orchestrator-Mother
8
+ - **Role:** Tổng quản dự án. Điều phối các Sub-Agent.
9
+ - **Provider:** Native Cloud (Gemini/Claude).
10
+ - **Instruction:** Chịu trách nhiệm cuối cùng về logic hệ thống và giao tiếp với Sếp qua Bot.
11
+
12
+ ### 💻 Local-Assistant
13
+ - **Role:** Trợ lý tại chỗ. Xử lý các tác vụ lặp lại, lint code, document.
14
+ - **Provider:** Local Ollama (Qwen, Llama).
15
+ - **Instruction:** Tiết kiệm quota Cloud cho Sếp bằng cách xử lý các task "đơn giản nhưng tốn token".
16
+
17
+ ---
18
+
19
+ ## 🛠️ SPECIALIZED SQUADS
20
+
21
+ ### 🏥 Execution-Doctor
22
+ - **Role:** Cấp cứu lỗi Runtime và API.
23
+ - **Trigger:** Sentinel Watchdog (Crashes).
24
+ - **Action:** Tự động phân tích log và đề xuất bản vá (Patch) ngay lập tức.
25
+
26
+ ### 🔍 Security-Stalker
27
+ - **Role:** Quét lỗ hổng bảo mật và Token leak.
28
+ - **Action:** Kiểm tra mọi file `.env`, `settings.json` trước khi commit.
29
+
30
+ ---
31
+
32
+ ## 📜 COLLABORATION PROTOCOL
33
+
34
+ 1. **Chain of Command:** Orchestrator (Cloud) ra lệnh -> Local Agent thực thi.
35
+ 2. **Context Sharing:** Các Agent dùng chung `mcp_ag-kit-memory` để đồng bộ kiến thức.
36
+ 3. **Safety First:** Mọi thay đổi cấu trúc quan trọng phải qua Socratic Gate (Hỏi ý kiến Sếp).
@@ -13,6 +13,15 @@ It provides the blueprint of the framework you are running in (`ag-kit`). It def
13
13
 
14
14
  ---
15
15
 
16
+ ## 🧠 LONG-TERM MEMORY (MANDATORY)
17
+
18
+ **You MUST call `mcp_ag-kit-memory_memory_search` at the start of any non-trivial task.**
19
+
20
+ ### Why use memory search?
21
+ The system relies on a semantic GraphRAG database to store previous experiences and solutions. If you don't search, you are flying blind and ignoring past knowledge. Use this tool BEFORE deep-diving into code.
22
+
23
+ ---
24
+
16
25
  ## 🛠️ TOKEN SAVING: VFS PROTOCOL (MANDATORY)
17
26
 
18
27
  **You MUST use `vfs` for code discovery before using `grep` or reading entire files.**
@@ -31,10 +40,23 @@ It provides the blueprint of the framework you are running in (`ag-kit`). It def
31
40
 
32
41
  ---
33
42
 
34
- ## 🤖 MODEL SELECTION
43
+ ---
35
44
 
36
- - **Simple Tasks**: Prefer local Ollama models (e.g., `nemotron`, `llama3`) to save cloud budget.
37
- - **Complex Tasks**: Use high-tier models (e.g., `Claude 3.5 Sonnet`, `Gemini 1.5 Pro`).
45
+ ## 🤖 MODEL SELECTION (ANTIGRAVITY MOTHER - 1.21.9)
46
+
47
+ **You MUST operate as the "Antigravity Mother" (Super-Agent) - Orchestrating both Native Cloud and Local models.**
48
+
49
+ ### 1. Programming & Chat (Primary)
50
+ - **Model:** Native Cloud (Gemini 1.5 Pro, Claude 3.5 Sonnet).
51
+ - **Purpose:** Handling user chat, high-level code generation, tool execution (Terminal, FS).
52
+ - **Rule:** When the user is in a Private Chat with the Remote Bot, ALWAYS route to the Native IDE path to ensure full tool support.
53
+
54
+ ### 2. Agentic Tasks (Mother Orchestration)
55
+ - **Model:** Antigravity (Super-Agent Brain) using **Local Ollama** (Owen, Llama3) or **Native Cloud**.
56
+ - **Purpose:** Any task requiring autonomous logic, sub-agent spawning, data analysis, or background operations.
57
+ - **Rule:** Antigravity acts as the Super-Agent, using the HUD-selected model (Local or Native) as part of its intelligence layer to perform complex tasks.
58
+
59
+ - **Manual Lock:** The HUD selection is authoritative. Do not allow the IDE to automatically revert to default models if a manual selection is active on the "Mother" HUD.
38
60
 
39
61
  ---
40
62
 
@@ -42,3 +64,8 @@ It provides the blueprint of the framework you are running in (`ag-kit`). It def
42
64
  - Write self-documenting code.
43
65
  - Avoid over-engineering.
44
66
  - Follow the patterns defined in `.agent/skills/clean-code`.
67
+
68
+ ## 🎨 DESIGN & REPORTING (TELEGRAM & HUD)
69
+ - **Diff Display**: Do NOT use standard `diffview` or +/- symbols.
70
+ - **Visuals**: Use line counts (e.g., "Added 12 lines, Removed 5 lines") and line-by-line comparison like GitHub.
71
+ - **Coloring**: Use Green/Red color formatting for additions/deletions instead of prefixes.
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "ag-kit-template-project",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Antigravity Agentic Bridge Project Template",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "dev": "tsx src/index.ts",
9
+ "build": "tsc",
10
+ "start": "node dist/index.js"
11
+ },
12
+ "dependencies": {
13
+ "@modelcontextprotocol/sdk": "^1.28.0",
14
+ "dotenv": "^16.4.0",
15
+ "grammy": "^1.35.0",
16
+ "http-mitm-proxy": "^1.1.0",
17
+ "ws": "^8.18.0",
18
+ "axios": "^1.6.0"
19
+ },
20
+ "devDependencies": {
21
+ "typescript": "^5.7.0",
22
+ "@types/node": "^22.0.0",
23
+ "tsx": "^4.19.0"
24
+ }
25
+ }
@@ -1,10 +1,9 @@
1
1
  (async () => {
2
- // 1. Cleanup
3
- if (window.ag_popup_win && !window.ag_popup_win.closed) window.ag_popup_win.close();
2
+ // 1. Cleanup old HUD if exists
3
+ const HUD_ID = 'antigravity-dynamic-hud';
4
+ const oldHud = document.getElementById(HUD_ID);
5
+ if (oldHud) oldHud.remove();
4
6
 
5
- const HUD_ID = 'antigravity-elite-statusbar-item';
6
- if (document.getElementById(HUD_ID)) return "ALREADY_EXISTS";
7
-
8
7
  // 2. Locate Status Bar
9
8
  const statusBar = document.querySelector('.part.statusbar');
10
9
  if (!statusBar) return "STATUSBAR_NOT_FOUND";
@@ -16,22 +15,21 @@
16
15
  height: 100% !important;
17
16
  display: flex !important;
18
17
  align-items: center !important;
19
- padding: 0 4px 0 12px !important;
18
+ padding: 0 8px !important;
20
19
  background: #000 !important;
21
20
  color: #00ff88 !important;
22
- font-family: monospace !important;
21
+ font-family: 'JetBrains Mono', 'Fira Code', monospace !important;
23
22
  font-weight: bold !important;
24
23
  font-size: 11px !important;
25
24
  border-left: 2px solid #00ff88 !important;
26
25
  user-select: none !important;
27
- white-space: nowrap !important;
28
- z-index: 100 !important;
26
+ cursor: pointer !important;
27
+ z-index: 1000 !important;
29
28
  `;
30
29
 
31
30
  const icon = document.createElement('span');
32
31
  icon.textContent = '🛰️ AG_ELITE | ';
33
32
 
34
- // 4. Create Model Selector
35
33
  const select = document.createElement('select');
36
34
  select.style.cssText = `
37
35
  background: transparent !important;
@@ -40,55 +38,55 @@
40
38
  outline: none !important;
41
39
  font-family: inherit !important;
42
40
  font-size: inherit !important;
43
- font-weight: inherit !important;
44
41
  cursor: pointer !important;
45
- padding: 0 4px !important;
46
42
  appearance: none !important;
47
43
  `;
48
44
 
49
- // USER-SPECIFIED Advanced Future Models
50
- const cloudModels = [
51
- { name: 'Gemini 3.1 Pro (High)', id: 'gemini-3.1-pro-high' },
52
- { name: 'Gemini 3.1 Pro (Low)', id: 'gemini-3.1-pro-low' },
53
- { name: 'Gemini 3 Flash', id: 'gemini-3-flash' },
54
- { name: 'Claude Sonnet 4.6 (Thinking)', id: 'claude-sonnet-4.6' },
55
- { name: 'Claude Opus 4.6 (Thinking)', id: 'claude-opus-4.6' },
56
- { name: 'GPT-OSS 120B (Medium)', id: 'gpt-oss-120b' }
57
- ];
58
-
59
- const localModels = [
60
- { name: 'Ollama: Nemotron', id: 'ollama/hf.co/dark-pen/Nemotron-Orchestrator-8B-IQ4_XS-GGUF:latest' },
61
- { name: 'Ollama: Nvidia Orchestrator', id: 'ollama/hf.co/bartowski/nvidia_Orchestrator-8B-GGUF:Q4_K_M' }
62
- ];
45
+ // 4. Dynamic Update Logic
46
+ const updateModelList = (models) => {
47
+ select.innerHTML = '';
48
+ const cloudGroup = document.createElement('optgroup');
49
+ cloudGroup.label = '☁️ NATIVE CLOUD (Programming)';
50
+
51
+ const localGroup = document.createElement('optgroup');
52
+ localGroup.label = '💻 LOCAL OLLAMA (Agents)';
63
53
 
64
- const addGroup = (label, models) => {
65
- const group = document.createElement('optgroup');
66
- group.label = label;
67
- group.style.background = '#000';
68
- group.style.color = '#555';
69
54
  models.forEach(m => {
70
55
  const opt = document.createElement('option');
71
- opt.value = m.id;
72
- opt.textContent = m.name;
73
- opt.style.background = '#000';
74
- opt.style.color = '#00ff88';
75
- group.appendChild(opt);
56
+ opt.value = m.name;
57
+ opt.textContent = `${m.isActive ? '✅ ' : ''}${m.name.replace('[Ollama] ', '')}`;
58
+ opt.selected = m.isActive;
59
+ opt.style.background = '#111';
60
+ opt.style.color = m.provider === 'ide' ? '#00ff88' : '#00cbd4';
61
+
62
+ if (m.provider === 'ide') cloudGroup.appendChild(opt);
63
+ else localGroup.appendChild(opt);
76
64
  });
77
- select.appendChild(group);
65
+
66
+ select.appendChild(cloudGroup);
67
+ select.appendChild(localGroup);
78
68
  };
79
69
 
80
- addGroup('ELITE CLOUD (High)', cloudModels);
81
- addGroup('ELITE LOCAL (Ollama)', localModels);
70
+ // Listen for updates from backend
71
+ window.addEventListener('ag-hud-update', (e) => {
72
+ if (e.detail) updateModelList(e.detail);
73
+ });
82
74
 
83
75
  select.onchange = (e) => {
76
+ // Broadcast to Backend
84
77
  console.log("AG_PORTAL_ACTION:SET_MODEL:" + e.target.value);
85
78
  container.style.borderColor = '#ff00ff';
86
- setTimeout(() => container.style.borderColor = '#00ff88', 1000);
79
+ setTimeout(() => container.style.borderColor = '#00ff88', 800);
87
80
  };
88
81
 
89
82
  container.appendChild(icon);
90
83
  container.appendChild(select);
91
84
  statusBar.prepend(container);
92
85
 
93
- return "STATUSBAR_ADVANCED_HUD_READY";
86
+ // Initial state (Optional placeholder)
87
+ updateModelList([
88
+ { name: 'Connecting...', provider: 'ide', isActive: true }
89
+ ]);
90
+
91
+ return "DYNAMIC_HUD_READY_1.21.9";
94
92
  })();
@@ -0,0 +1,83 @@
1
+ import { Bot, Context, InlineKeyboard } from 'grammy';
2
+ import { ModelService } from '../services/ModelService.js';
3
+ import { OllamaService } from '../services/OllamaService.js';
4
+ import { CdpService } from '../services/CdpService.js';
5
+
6
+ /**
7
+ * TelegramBot - Giao diện điều khiển Hybrid cho ag-kit v1.6.0.
8
+ * "Tinh hoa": Phân biệt rành mạch giữa Lập trình (Native) và Agent (Local).
9
+ */
10
+ export class TelegramBot {
11
+ private bot: Bot;
12
+ private modelService: ModelService;
13
+ private ollamaService: OllamaService;
14
+ private cdp: CdpService;
15
+
16
+ constructor(
17
+ token: string,
18
+ modelService: ModelService,
19
+ ollamaService: OllamaService,
20
+ cdp: CdpService
21
+ ) {
22
+ this.bot = new Bot(token);
23
+ this.modelService = modelService;
24
+ this.ollamaService = ollamaService;
25
+ this.cdp = cdp;
26
+ this.setupHandlers();
27
+ }
28
+
29
+ private setupHandlers() {
30
+ this.bot.command('start', (ctx: any) => ctx.reply('🚀 AG-KIT Hybrid v1.6.0 Is Ready!'));
31
+
32
+ // 1. Menu chọn Model (Tinh hoa)
33
+ this.bot.command('model', async (ctx: any) => {
34
+ const models = await this.modelService.listModels();
35
+ const keyboard = new InlineKeyboard();
36
+
37
+ models.forEach((m: any, idx: number) => {
38
+ const icon = m.provider === 'ide' ? '☁️' : '💻';
39
+ const activeMark = m.isActive ? ' ✅' : '';
40
+ keyboard.text(`${icon} ${m.name}${activeMark}`, `set_model:${m.name}`);
41
+ if (idx % 2 === 1) keyboard.row();
42
+ });
43
+
44
+ await ctx.reply('🎯 Chọn bộ não (Model) cho Agent:', { reply_markup: keyboard });
45
+ });
46
+
47
+ // 2. Callback chọn model
48
+ this.bot.callbackQuery(/set_model:(.+)/, async (ctx: any) => {
49
+ const modelName = ctx.match[1];
50
+ await this.modelService.setModel(modelName);
51
+
52
+ // Đồng bộ hóa với IDE qua CDP nếu là Native Model
53
+ if (!modelName.includes('[Ollama]')) {
54
+ await this.cdp.evaluate(`// Script to switch model in IDE UI`);
55
+ }
56
+
57
+ await ctx.answerCallbackQuery(`Đã chọn: ${modelName}`);
58
+ await ctx.editMessageText(`✅ Đã chuyển sang bộ não: ${modelName}`);
59
+ });
60
+
61
+ // 3. Xử lý Chat (Tinh hoa: Private = Programming)
62
+ this.bot.on('message:text', async (ctx: any) => {
63
+ const isPrivate = ctx.chat.type === 'private';
64
+ const text = ctx.message.text;
65
+
66
+ if (isPrivate) {
67
+ // LÀ LẬP TRÌNH: Dùng đường truyền Native IDE (Full Tools)
68
+ await ctx.reply('👨‍💻 Đang xử lý chế độ Lập trình (Native Cloud)...');
69
+ // Logic: injectPrompt(text, { context: 'programming' })
70
+ } else {
71
+ // LÀ AGENT: Dùng model đã chọn trên HUD
72
+ const currentModel = this.modelService.getSelectedModel();
73
+ await ctx.reply(`🤖 Agent (${currentModel}) đang thực thi...`);
74
+ // Logic: runAgent(text, { model: currentModel })
75
+ }
76
+ });
77
+ }
78
+
79
+ public launch() {
80
+ this.bot.start();
81
+ console.log('🤖 Telegram Bot v1.6.0 đã khởi động.');
82
+ }
83
+ }
@@ -0,0 +1,46 @@
1
+ import { CdpService } from './services/CdpService.js';
2
+ import { TelegramBot } from './bot/TelegramBot.js';
3
+ import { ModelService } from './services/ModelService.js';
4
+ import { OllamaService } from './services/OllamaService.js';
5
+ import { TopicManager } from './services/TopicManager.js';
6
+
7
+ async function main() {
8
+ console.log('═══════════════════════════════════════');
9
+ console.log(' 🏢 AG-KIT Template — Telegram → IDE Bridge');
10
+ console.log('═══════════════════════════════════════');
11
+
12
+ // 1. Setup Core Services
13
+ // Note: IDE_PORT should be set in .env
14
+ const idePort = Number(process.env.IDE_PORT) || 9555;
15
+ const cdp = new CdpService(idePort);
16
+ const ollama = new OllamaService();
17
+ const models = new ModelService(cdp);
18
+ const topics = new TopicManager();
19
+
20
+ // 2. Connect to IDE
21
+ try {
22
+ await cdp.connect();
23
+ console.log('[Main] ✅ IDE CDP connected');
24
+ } catch (err: any) {
25
+ console.log(`[Main] ⚠️ IDE not available: ${err.message}`);
26
+ }
27
+
28
+ // 3. Start Telegram Bot
29
+ const botToken = process.env.TELEGRAM_BOT_TOKEN || '';
30
+ const bot = new TelegramBot(botToken, models, ollama, cdp);
31
+
32
+ bot.launch();
33
+
34
+ // Graceful shutdown
35
+ const shutdown = async () => {
36
+ console.log('\n[Main] Shutting down...');
37
+ process.exit(0);
38
+ };
39
+ process.on('SIGINT', shutdown);
40
+ process.on('SIGTERM', shutdown);
41
+ }
42
+
43
+ main().catch(err => {
44
+ console.error('[Main] Fatal Error:', err);
45
+ process.exit(1);
46
+ });
@@ -0,0 +1,84 @@
1
+ import WebSocket from 'ws';
2
+ import * as http from 'http';
3
+ import { EventEmitter } from 'events';
4
+
5
+ /**
6
+ * CdpService - Giao thức CDP (Chrome DevTools Protocol)
7
+ * để điều khiển và trích xuất UI từ IDE (Antigravity/Cursor).
8
+ */
9
+ export class CdpService extends EventEmitter {
10
+ private ws: WebSocket | null = null;
11
+ private connected = false;
12
+ private idCounter = 1;
13
+ private pendingCalls = new Map<number, { resolve: Function; reject: Function; timer: NodeJS.Timeout }>();
14
+ private callTimeout = 30000;
15
+
16
+ constructor(private port: number) {
17
+ super();
18
+ }
19
+
20
+ /**
21
+ * Tự động tìm kiếm WebSocket Debugging URL từ IDE.
22
+ */
23
+ async discoverTarget(): Promise<string> {
24
+ return new Promise((resolve, reject) => {
25
+ const req = http.get(`http://127.0.0.1:${this.port}/json/list`, (res) => {
26
+ let data = '';
27
+ res.on('data', chunk => data += chunk);
28
+ res.on('end', () => {
29
+ try {
30
+ const pages = JSON.parse(data);
31
+ const target = pages.find((p: any) => p.type === 'page' || p.url.includes('workbench'));
32
+ if (target && target.webSocketDebuggerUrl) {
33
+ resolve(target.webSocketDebuggerUrl);
34
+ } else {
35
+ reject(new Error('No valid CDP target found'));
36
+ }
37
+ } catch (e) {
38
+ reject(e);
39
+ }
40
+ });
41
+ });
42
+ req.on('error', reject);
43
+ });
44
+ }
45
+
46
+ async connect() {
47
+ const url = await this.discoverTarget();
48
+ this.ws = new WebSocket(url);
49
+ this.ws.on('open', () => {
50
+ this.connected = true;
51
+ this.emit('connected');
52
+ });
53
+ this.ws.on('message', (data: any) => {
54
+ const res = JSON.parse(data.toString());
55
+ if (res.id && this.pendingCalls.has(res.id)) {
56
+ const { resolve, reject, timer } = this.pendingCalls.get(res.id)!;
57
+ clearTimeout(timer);
58
+ this.pendingCalls.delete(res.id);
59
+ if (res.error) reject(res.error);
60
+ else resolve(res.result);
61
+ }
62
+ this.emit('event', res);
63
+ });
64
+ }
65
+
66
+ async evaluate(expression: string): Promise<any> {
67
+ const id = this.idCounter++;
68
+ return new Promise((resolve, reject) => {
69
+ const timer = setTimeout(() => {
70
+ this.pendingCalls.delete(id);
71
+ reject(new Error('CDP Timeout'));
72
+ }, this.callTimeout);
73
+
74
+ this.pendingCalls.set(id, { resolve, reject, timer });
75
+ this.ws?.send(JSON.stringify({
76
+ id,
77
+ method: 'Runtime.evaluate',
78
+ params: { expression, returnByValue: true, awaitPromise: true }
79
+ }));
80
+ });
81
+ }
82
+
83
+ public isConnected() { return this.connected; }
84
+ }
@@ -0,0 +1,119 @@
1
+ import { CdpService } from './CdpService.js';
2
+ import { OllamaService } from './OllamaService.js';
3
+
4
+ export interface ModelInfo {
5
+ name: string;
6
+ refreshText: string;
7
+ isActive: boolean;
8
+ provider: 'ide' | 'ollama';
9
+ }
10
+
11
+ /**
12
+ * ModelService - Bộ não điều phối danh sách Model danh cho ag-kit v1.6.0.
13
+ * Hỗ trợ Hybrid: Kết hợp Native IDE Cloud và Local Ollama.
14
+ */
15
+ export class ModelService {
16
+ private ollama = new OllamaService();
17
+ private manualSelectedModel: string | null = null;
18
+
19
+ constructor(private cdp: CdpService) {}
20
+
21
+ /**
22
+ * Liệt kê các model khả dụng.
23
+ * "Tinh hoa": Tôn trọng sự lựa chọn thủ công của người dùng trên HUD.
24
+ */
25
+ async listModels(): Promise<ModelInfo[]> {
26
+ const ideModels = await this.listIdeModels();
27
+ const ollamaModels = await this.ollama.listModels();
28
+
29
+ // 1. Gộp danh sách model
30
+ const merged: ModelInfo[] = [
31
+ ...ideModels.map(m => ({ ...m, provider: 'ide' as const })),
32
+ ...ollamaModels.map(m => ({
33
+ name: `[Ollama] ${m.name}`,
34
+ refreshText: m.size ? `${(m.size / (1024 * 1024 * 1024)).toFixed(1)}GB` : 'Local',
35
+ isActive: false,
36
+ provider: 'ollama' as const
37
+ }))
38
+ ];
39
+
40
+ // 2. Logic "Manual Lock": Nếu chưa chọn, ưu tiên lấy model đang Active của IDE
41
+ if (!this.manualSelectedModel) {
42
+ const activeIde = merged.find(m => m.provider === 'ide' && m.isActive);
43
+ if (activeIde) {
44
+ this.manualSelectedModel = activeIde.name;
45
+ } else if (merged.length > 0) {
46
+ this.manualSelectedModel = merged[0].name;
47
+ }
48
+ }
49
+
50
+ // 3. Buộc đồng bộ hóa trạng thái Active với sự lựa chọn của Sếp
51
+ if (this.manualSelectedModel) {
52
+ let foundMatch = false;
53
+ merged.forEach(m => {
54
+ if (m.name === this.manualSelectedModel) {
55
+ m.isActive = true;
56
+ foundMatch = true;
57
+ } else {
58
+ m.isActive = false;
59
+ }
60
+ });
61
+
62
+ // Nếu model cũ không còn (bị xóa khỏi Ollama), quay về bản IDE mặc định
63
+ if (!foundMatch && merged.length > 0) {
64
+ this.manualSelectedModel = merged.find(m => m.provider === 'ide')?.name || merged[0].name;
65
+ }
66
+ }
67
+
68
+ return merged;
69
+ }
70
+
71
+ /**
72
+ * Scraper Shadow DOM cho các IDE dựa trên VS Code (Antigravity, Cursor).
73
+ */
74
+ private async listIdeModels(): Promise<Omit<ModelInfo, 'provider'>[]> {
75
+ const script = `(() => {
76
+ const findInShadow = (root, patterns) => {
77
+ let found = [];
78
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
79
+ let node = walker.nextNode();
80
+ while (node) {
81
+ const el = node;
82
+ const text = el.innerText || "";
83
+ if (patterns.some(p => text.includes(p)) && el.offsetParent !== null) {
84
+ // Nhận diện Menu Item trong IDE
85
+ if (el.classList && (el.classList.contains('monaco-list-row') || el.classList.contains('menu-item'))) {
86
+ found.push({
87
+ name: text.split('\\n')[0].trim(),
88
+ refreshText: 'Ready',
89
+ isActive: el.classList.contains('focused') || el.classList.contains('active') || el.getAttribute('aria-selected') === 'true'
90
+ });
91
+ }
92
+ }
93
+ if (el.shadowRoot) found = found.concat(findInShadow(el.shadowRoot, patterns));
94
+ node = walker.nextNode();
95
+ }
96
+ return found;
97
+ };
98
+
99
+ const patterns = ['Gemini', 'Claude', 'GPT', 'Llama', 'DeepSeek', 'o1'];
100
+ return findInShadow(document.body, patterns);
101
+ })()`;
102
+
103
+ try {
104
+ const raw = await this.cdp.evaluate(script) as any[];
105
+ // Deduplicate by name
106
+ return Array.from(new Map(raw.map((item: any) => [item['name'], item])).values());
107
+ } catch {
108
+ return [];
109
+ }
110
+ }
111
+
112
+ public async setModel(name: string) {
113
+ this.manualSelectedModel = name;
114
+ }
115
+
116
+ public getSelectedModel() {
117
+ return this.manualSelectedModel;
118
+ }
119
+ }
@@ -0,0 +1,91 @@
1
+ import http from 'http';
2
+
3
+ export interface OllamaModel {
4
+ name: string;
5
+ model: string;
6
+ modified_at: string;
7
+ size: number;
8
+ }
9
+
10
+ export class OllamaService {
11
+ private baseUrl = 'http://localhost:11434';
12
+
13
+ /**
14
+ * List available Ollama models
15
+ */
16
+ async listModels(): Promise<OllamaModel[]> {
17
+ return new Promise((resolve, reject) => {
18
+ const req = http.get(`${this.baseUrl}/api/tags`, (res) => {
19
+ let data = '';
20
+ res.on('data', chunk => data += chunk);
21
+ res.on('end', () => {
22
+ try {
23
+ const parsed = JSON.parse(data);
24
+ const validModels = (parsed.models || []).filter((m: any) => !m.name.includes('nomic-embed-text'));
25
+ resolve(validModels);
26
+ } catch (e) {
27
+ reject(new Error('Failed to parse Ollama response'));
28
+ }
29
+ });
30
+ });
31
+ req.on('error', (err) => {
32
+ // Silently fail if Ollama is not running, return empty list
33
+ resolve([]);
34
+ });
35
+ req.setTimeout(2000, () => {
36
+ req.destroy();
37
+ resolve([]);
38
+ });
39
+ });
40
+ }
41
+
42
+ /**
43
+ * Generate response from an Ollama model (Streaming)
44
+ */
45
+ async generateResponse(model: string, prompt: string, onProgress: (chunk: string) => void): Promise<string> {
46
+ return new Promise((resolve, reject) => {
47
+ const postData = JSON.stringify({
48
+ model: model,
49
+ prompt: prompt,
50
+ stream: true
51
+ });
52
+
53
+ const options = {
54
+ hostname: 'localhost',
55
+ port: 11434,
56
+ path: '/api/generate',
57
+ method: 'POST',
58
+ headers: {
59
+ 'Content-Type': 'application/json',
60
+ 'Content-Length': Buffer.byteLength(postData)
61
+ }
62
+ };
63
+
64
+ let fullResponse = '';
65
+ const req = http.request(options, (res) => {
66
+ res.on('data', (chunk) => {
67
+ try {
68
+ const lines = chunk.toString().split('\n');
69
+ for (const line of lines) {
70
+ if (!line.trim()) continue;
71
+ const json = JSON.parse(line);
72
+ if (json.response) {
73
+ fullResponse += json.response;
74
+ onProgress(json.response);
75
+ }
76
+ if (json.done) {
77
+ resolve(fullResponse);
78
+ }
79
+ }
80
+ } catch (e) {
81
+ // Ignore partial parse errors during streaming
82
+ }
83
+ });
84
+ });
85
+
86
+ req.on('error', reject);
87
+ req.write(postData);
88
+ req.end();
89
+ });
90
+ }
91
+ }
@@ -0,0 +1,188 @@
1
+ import { CdpService } from './CdpService.js';
2
+
3
+ export interface SessionListItem {
4
+ title: string;
5
+ isActive: boolean;
6
+ }
7
+
8
+ const SELECTORS = {
9
+ NEW_CHAT_BTN: '[data-tooltip-id="new-conversation-tooltip"]',
10
+ PAST_CONVERSATIONS_BTN: '[data-tooltip-id*="history"], [data-tooltip-id*="past-conversations"], [class*="lucide-history"]',
11
+ SESSION_ROWS: 'div[class*="cursor-pointer"]',
12
+ };
13
+
14
+ export class SessionService {
15
+ private cdp: CdpService;
16
+
17
+ constructor(cdp: CdpService) {
18
+ this.cdp = cdp;
19
+ }
20
+
21
+ /**
22
+ * Click the "New Chat" (+) button in the IDE.
23
+ */
24
+ async startNewChat(): Promise<boolean> {
25
+ const script = `(() => {
26
+ const selectors = [
27
+ '${SELECTORS.NEW_CHAT_BTN}',
28
+ '[data-tooltip-id*="new-conversation"]',
29
+ '[data-tooltip-id*="new-chat"]',
30
+ '[aria-label*="New Conversation"]',
31
+ '[aria-label*="New Chat"]'
32
+ ];
33
+ let btn = null;
34
+ for (const s of selectors) {
35
+ btn = document.querySelector(s);
36
+ if (btn) break;
37
+ }
38
+ if (!btn) return null;
39
+ const rect = btn.getBoundingClientRect();
40
+ return { x: Math.round(rect.x + rect.width / 2), y: Math.round(rect.y + rect.height / 2) };
41
+ })()`;
42
+
43
+ const coords = await this.evaluateOnAnyContext(script);
44
+ if (!coords) return false;
45
+
46
+ await this.cdpClick(coords.x, coords.y);
47
+ return true;
48
+ }
49
+
50
+ /**
51
+ * Scrape session list from the "Past Conversations" panel.
52
+ */
53
+ async listSessions(): Promise<SessionListItem[]> {
54
+ // 1. Open Past Conversations panel
55
+ const btnScript = `(() => {
56
+ const btn = document.querySelector('${SELECTORS.PAST_CONVERSATIONS_BTN}');
57
+ if (!btn) return null;
58
+ const rect = btn.getBoundingClientRect();
59
+ return { x: Math.round(rect.x + rect.width / 2), y: Math.round(rect.y + rect.height / 2) };
60
+ })()`;
61
+
62
+ const btnCoords = await this.evaluateOnAnyContext(btnScript);
63
+ if (!btnCoords) return [];
64
+
65
+ await this.cdpClick(btnCoords.x, btnCoords.y);
66
+ await this.sleep(800); // Wait for panel
67
+
68
+ // 2. Scrape sessions
69
+ const scrapeScript = `(() => {
70
+ const rows = Array.from(document.querySelectorAll('div[class*="cursor-pointer"]'));
71
+ const items = [];
72
+ for (const row of rows) {
73
+ const text = (row.textContent || '').trim();
74
+ if (text.length < 2 || text.length > 100) continue;
75
+ if (/history|new|past/i.test(text)) continue;
76
+ const isActive = /focusBackground/i.test(row.className || '');
77
+ items.push({ title: text, isActive });
78
+ }
79
+ return items;
80
+ })()`;
81
+
82
+ const sessions = await this.evaluateOnAnyContext(scrapeScript);
83
+
84
+ // 3. Close panel (Escape)
85
+ await this.cdp.call('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Escape', code: 'Escape', windowsVirtualKeyCode: 27 });
86
+ await this.cdp.call('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Escape', code: 'Escape', windowsVirtualKeyCode: 27 });
87
+
88
+ return sessions || [];
89
+ }
90
+
91
+ /**
92
+ * Switch to a session by clicking its title in the panel.
93
+ */
94
+ async activateSession(title: string): Promise<boolean> {
95
+ const script = `(() => {
96
+ const rows = Array.from(document.querySelectorAll('div[class*="cursor-pointer"]'));
97
+ const target = rows.find(r => (r.textContent || '').trim() === ${JSON.stringify(title)});
98
+ if (!target) return null;
99
+ const rect = target.getBoundingClientRect();
100
+ return { x: Math.round(rect.x + rect.width / 2), y: Math.round(rect.y + rect.height / 2) };
101
+ })()`;
102
+
103
+ // 1. Open panel first
104
+ await this.listSessions();
105
+ await this.sleep(500);
106
+
107
+ const coords = await this.evaluateOnAnyContext(script);
108
+ if (!coords) return false;
109
+
110
+ await this.cdpClick(coords.x, coords.y);
111
+ return true;
112
+ }
113
+
114
+ /**
115
+ * Find a session that starts with [TG-Topic-ID] or [TG-Chat-ID]
116
+ */
117
+ async findSessionByTopic(id: number, isPrivate: boolean): Promise<string | null> {
118
+ const prefix = isPrivate ? `[TG-Chat-${id}]` : `[TG-Topic-${id}]`;
119
+ const sessions = await this.listSessions();
120
+ const found = sessions.find(s => s.title.startsWith(prefix));
121
+ return found ? found.title : null;
122
+ }
123
+
124
+ /**
125
+ * Create a new chat and immediately rename it (or just return the initial title)
126
+ * Note: Renaming in IDE is tricky via CDP, so we just use the default new chat
127
+ * and the first message will set the title.
128
+ */
129
+ async startNewChatWithTopic(id: number, isPrivate: boolean): Promise<boolean> {
130
+ const ok = await this.startNewChat();
131
+ // We can't easily rename via CDP without more complex DOM interaction.
132
+ // However, the IDE usually titles by the first prompt.
133
+ // For now, we'll just return 'true' and the bot will inject a hidden prefix if possible,
134
+ // or we just rely on the fact that we can't perfectly name it but we can *find* it
135
+ // if we manage to inject the prefix in the first message.
136
+ return ok;
137
+ }
138
+
139
+ /**
140
+ * Scrape the last 40 lines of the currently active terminal in the IDE.
141
+ */
142
+ async getTerminalOutput(): Promise<string> {
143
+ const script = `(() => {
144
+ // 1. Try to find accessibility tree (contains text nodes even if canvas renderer is used)
145
+ const ariaRows = Array.from(document.querySelectorAll('.xterm-accessibility-tree .xterm-rows > div, .xterm-accessibility .live-region'));
146
+
147
+ // 2. Fallback to normal rows if DOM renderer is used
148
+ const normalRows = Array.from(document.querySelectorAll('.xterm-rows > div'));
149
+
150
+ // 3. Fallback to aria-live region which contains the terminal stream
151
+ const liveRegion = document.querySelector('.xterm-accessibility-tree [aria-live], .live-region[aria-live="polite"]');
152
+
153
+ let lines = [];
154
+ if (ariaRows.length > 0) {
155
+ lines = ariaRows.map(r => r.textContent || '');
156
+ } else if (normalRows.length > 0) {
157
+ lines = normalRows.map(r => r.textContent || '');
158
+ } else if (liveRegion && liveRegion.textContent) {
159
+ lines = liveRegion.textContent.split('\\n');
160
+ }
161
+
162
+ lines = lines.filter(t => t.trim().length > 0);
163
+ if (lines.length === 0) return null;
164
+ return lines.slice(-40).join('\\n');
165
+ })()`;
166
+
167
+ try {
168
+ const text = await this.evaluateOnAnyContext(script);
169
+ return text || '📭 [DOM Trống: IDE đang ẩn hoặc chưa bật Tab Terminal. Sếp hãy mở/focus Panel Terminal trên màn hình IDE trước nhé do cơ chế tiết kiệm RAM của VS Code!]';
170
+ } catch(e) {
171
+ return '❌ Lỗi đọc Terminal từ IDE';
172
+ }
173
+ }
174
+
175
+ private async evaluateOnAnyContext(expression: string): Promise<any> {
176
+ return this.cdp.evaluateOnAnyContext(expression);
177
+ }
178
+
179
+ private async cdpClick(x: number, y: number): Promise<void> {
180
+ await this.cdp.call('Input.dispatchMouseEvent', { type: 'mouseMoved', x, y });
181
+ await this.cdp.call('Input.dispatchMouseEvent', { type: 'mousePressed', x, y, button: 'left', clickCount: 1 });
182
+ await this.cdp.call('Input.dispatchMouseEvent', { type: 'mouseReleased', x, y, button: 'left', clickCount: 1 });
183
+ }
184
+
185
+ private sleep(ms: number): Promise<void> {
186
+ return new Promise(r => setTimeout(r, ms));
187
+ }
188
+ }
@@ -0,0 +1,53 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ export interface SessionState {
5
+ topicId: string;
6
+ metadata: Record<string, any>;
7
+ }
8
+
9
+ /**
10
+ * TopicManager - Quản lý phiên hội thoại (Sessions) dựa trên chủ đề.
11
+ * Giúp đồng bộ hóa ngữ cảnh giữa các Agent.
12
+ */
13
+ export class TopicManager {
14
+ private stateFile: string;
15
+ private chatToSession: Map<number, SessionState> = new Map();
16
+
17
+ constructor(storagePath: string = '.topic_state.json') {
18
+ this.stateFile = path.resolve(storagePath);
19
+ this.loadState();
20
+ }
21
+
22
+ private loadState() {
23
+ try {
24
+ if (fs.existsSync(this.stateFile)) {
25
+ const raw = fs.readFileSync(this.stateFile, 'utf8');
26
+ const data = JSON.parse(raw);
27
+ for (const [id, state] of Object.entries(data)) {
28
+ this.chatToSession.set(Number(id), state as SessionState);
29
+ }
30
+ }
31
+ } catch (e) {
32
+ console.error('[TopicManager] Error loading state:', e);
33
+ }
34
+ }
35
+
36
+ private saveState() {
37
+ try {
38
+ const data = Object.fromEntries(this.chatToSession);
39
+ fs.writeFileSync(this.stateFile, JSON.stringify(data, null, 2));
40
+ } catch (e) {
41
+ console.error('[TopicManager] Error saving state:', e);
42
+ }
43
+ }
44
+
45
+ public setSession(chatId: number, state: SessionState) {
46
+ this.chatToSession.set(chatId, state);
47
+ this.saveState();
48
+ }
49
+
50
+ public getSession(chatId: number): SessionState | undefined {
51
+ return this.chatToSession.get(chatId);
52
+ }
53
+ }