diff --git a/CONTEXT.md b/CONTEXT.md index 61451164..5b02f350 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -2,6 +2,8 @@ ระบบจัดการเอกสารงานก่อสร้าง (DMS) สำหรับโครงการ LCBP3 — เน้นการควบคุม Correspondence, RFA, Transmittal, Drawing พร้อมผู้ช่วย AI แบบ on-premises ที่ทำงานภายใต้ Workflow Engine กลางและขอบเขต AI ที่เข้มงวด (ADR-023A) +> **Agent/ tooling context:** สำหรับ Hermes Agent, Telegram Bridge, และ DevOps tooling → ดู [`specs/06-Decision-Records/CONTEXT-ADR-031.md`](specs/06-Decision-Records/CONTEXT-ADR-031.md) + ## Language ### Documents @@ -211,16 +213,16 @@ User Chat → Intent Router (ยังไม่มี) → Server-side Intent ## System readiness summary (resolved) -| Component | สถานะ | หมายเหตุ | -| ----------------------- | --------------- | ----------------------------------------------------------------------------------------- | -| **Infrastructure** | ✅ พร้อม | NestJS + Next.js + MariaDB + Redis + Elasticsearch | -| **Workflow Engine** | ✅ พร้อม | DSL-based, ADR-001/021 | -| **AI Boundary** | ✅ พร้อม | ADR-023A — Ollama isolation, no direct DB access | -| **RAG Pipeline** | 🟡 บางส่วน | Qdrant service มีใน code, ต้องตรวจสอบ deployment | -| **Intent Router** | 🟡 ADR Accepted | ADR-024 Accepted — Intent Classifier (Pattern→LLM Fallback) รอ implementation | -| **AI Tool Layer** | 🟡 ADR Accepted | ADR-025 Accepted — Tool Layer Architecture รอ implementation | -| **Document Chat UI** | 🟡 ADR Accepted | ADR-026 Accepted — Side-panel Chat UI รอ implementation | -| **AI Admin Console** | 🟡 ADR Accepted | ADR-027 Accepted — Dynamic Control Panel รอ implementation | +| Component | สถานะ | หมายเหตุ | +| ----------------------- | --------------- | -------------------------------------------------------------------------------------------- | +| **Infrastructure** | ✅ พร้อม | NestJS + Next.js + MariaDB + Redis + Elasticsearch | +| **Workflow Engine** | ✅ พร้อม | DSL-based, ADR-001/021 | +| **AI Boundary** | ✅ พร้อม | ADR-023A — Ollama isolation, no direct DB access | +| **RAG Pipeline** | 🟡 บางส่วน | Qdrant service มีใน code, ต้องตรวจสอบ deployment | +| **Intent Router** | 🟡 ADR Accepted | ADR-024 Accepted — Intent Classifier (Pattern→LLM Fallback) รอ implementation | +| **AI Tool Layer** | 🟡 ADR Accepted | ADR-025 Accepted — Tool Layer Architecture รอ implementation | +| **Document Chat UI** | 🟡 ADR Accepted | ADR-026 Accepted — Side-panel Chat UI รอ implementation | +| **AI Admin Console** | 🟡 ADR Accepted | ADR-027 Accepted — Dynamic Control Panel รอ implementation | | **Dynamic Prompt Mgmt** | ✅ พร้อม | ADR-029 Active — พัฒนาเสร็จสมบูรณ์ทั้ง Entity, API, Sandbox Runner, Cache และ UI Playgrounds | ## Flagged ambiguities diff --git a/frontend/components/admin/ai/OcrSandboxPromptManager.tsx b/frontend/components/admin/ai/OcrSandboxPromptManager.tsx index a21241fd..30625277 100644 --- a/frontend/components/admin/ai/OcrSandboxPromptManager.tsx +++ b/frontend/components/admin/ai/OcrSandboxPromptManager.tsx @@ -5,6 +5,7 @@ // - 2026-05-25: Refactored sandbox polling to useSandboxRun hook (Obs #2 fix) // - 2026-05-26: เพิ่มการตรวจสอบ versionsQuery.data แบบทนทานเพื่อป้องกัน Error N.find is not a function ในกรณีที่ API ส่งข้อมูลแบบ wrapped object มา // - 2026-05-29: เพิ่ม OCR Raw Text section ในผล sandbox +// - 2026-05-29: ปรับปรุงการโหลด Active Prompt ให้ทนทานต่อ race conditions และรูปแบบประเภทข้อมูลที่ส่งมาจาก API (boolean, number, string) 'use client'; import React, { useState, useEffect } from 'react'; @@ -52,8 +53,11 @@ export default function OcrSandboxPromptManager() { : (versionsData && typeof versionsData === 'object' && 'data' in versionsData && Array.isArray((versionsData as { data: unknown }).data)) ? (versionsData as { data: AiPrompt[] }).data : []; - const activePrompt = versions.find((v) => Boolean(v.isActive)); + const activePrompt = versions.find( + (v) => v.isActive === true || (v.isActive as unknown) === 1 || (v.isActive as unknown) === '1' + ); const [templateText, setTemplateText] = useState(''); + const [hasLoadedActivePrompt, setHasLoadedActivePrompt] = useState(false); const [ocrFile, setOcrFile] = useState(null); const [manualNote, setManualNote] = useState(''); const [activeTab, setActiveTab] = useState<'editor' | 'sandbox'>('editor'); @@ -64,11 +68,11 @@ export default function OcrSandboxPromptManager() { toast.success(t('ai.prompt.sandboxSuccess')); }); useEffect(() => { - if (activePrompt && !templateText) { + if (activePrompt && !hasLoadedActivePrompt) { setTemplateText(activePrompt.template); + setHasLoadedActivePrompt(true); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activePrompt]); + }, [activePrompt, hasLoadedActivePrompt]); const handleSaveVersion = async () => { if (!templateText.includes('{{ocr_text}}')) { toast.error(t('ai.prompt.placeholderError')); diff --git a/specs/06-Decision-Records/ADR-031-hermes-agent-telegram-devops-bridge.md b/specs/06-Decision-Records/ADR-031-hermes-agent-telegram-devops-bridge.md index 7b067c19..e5c7f9fc 100644 --- a/specs/06-Decision-Records/ADR-031-hermes-agent-telegram-devops-bridge.md +++ b/specs/06-Decision-Records/ADR-031-hermes-agent-telegram-devops-bridge.md @@ -298,7 +298,7 @@ Hermes Telegram commands ต้องแบ่งสิทธิ์ตามผ | **Read-only** | `/status`, `/ci_status`, `/repo_summary`, `/audit_summary` | ทำได้ทันทีสำหรับผู้ใช้ใน allowlist | | **Write with confirmation** | สร้าง branch, เปิด issue/PR, trigger CI, schedule audit | ต้องมี explicit confirmation ใน Telegram และบันทึก `transactionId` ใน `hermes_operations_log` | | **Forbidden from Telegram** | push ไป `main`/`master`, production deploy, schema migration execution, destructive git/file commands, direct DB writes, storage delete | ห้ามทำผ่าน Telegram ทุกกรณี ต้องใช้ workflow ที่มี human review และ approval แยกต่างหาก | - + ตัวอย่างคำสั่งที่ปลอดภัยควรเป็น “เตรียม branch/PR proposal สำหรับ fix/ci-pnpm-cache” ไม่ใช่ “สร้าง branch แล้ว push ให้เลย” #### Future DMS Telegram Module (Out of Scope) @@ -810,3 +810,5 @@ ADR-031 ต้อง rollout แบบเป็น stage เพื่อลด |------|---------|---------| | 2026-05-28 | 1.0.0 | Initial ADR creation - Merged from CONTEXT-ADR-031 and CONTEXT-ADR-031-Added | | 2026-05-28 | 1.1.0 | Added sections 4–6 from CONTEXT-ADR-031-Added-2: Hermes Interface Modes, agy+Hermes MCP Integration, Deploy Prerequisites; fixed port conflict (hermes proxy :8766, not :8765) | +| 2026-05-29 | 1.1.1 | Aligned with CONTEXT-ADR-031.md grill-with-docs: fixed monorepo structure (flat layout), corrected file paths, updated repo URL to git.np-dms.work, added AGENTS.md v1.9.7 reference | +| 2026-05-29 | 1.1.2 | Linked root CONTEXT.md with specs/CONTEXT-ADR-031.md; fixed setup-context.sh paths; updated Windsurf/agy/Hermes symlink targets | diff --git a/specs/06-Decision-Records/CONTEXT-ADR-031.md b/specs/06-Decision-Records/CONTEXT-ADR-031.md new file mode 100644 index 00000000..d8ad0a29 --- /dev/null +++ b/specs/06-Decision-Records/CONTEXT-ADR-031.md @@ -0,0 +1,344 @@ +# LCBP3 DMS — Agent Context (ADR-031) +# Single source of truth สำหรับทุก agent tool (Hermes, agy, Windsurf) +# +# ที่อยู่ไฟล์: specs/06-Decision-Records/CONTEXT-ADR-031.md +# +# Tool ที่อ่านไฟล์นี้: +# Hermes → /volume1/docker/hermes/config/MEMORY.md (symlink หรือ copy) +# agy → ~/.gemini/antigravity-cli/settings.json > contextFiles[] +# Windsurf → .windsurfrules > @import specs/06-Decision-Records/CONTEXT-ADR-031.md +# +# Domain context (Correspondence, RFA, AI Architecture): ดู CONTEXT.md ที่ root +# +# แก้ไขที่ไฟล์นี้ที่เดียว — tools ทั้งหมดได้รับ context เดียวกัน +# Last updated: 2026-05-29 (grill-with-docs: fixed paths, linked to root CONTEXT.md) + +--- + +## Project identity + +| Key | Value | +|---|---| +| Project | LCBP3 DMS — Laem Chabang Basin Phase 3 Document Management System | +| Domain | Construction project document management | +| Owner | เป้ | +| Repo | Gitea: `https://git.np-dms.work/np-dms/lcbp3` (internal) | +| Primary IDE | Windsurf (Cascade + MCP) | +| Agent stack | Hermes (ASUSTOR) · agy CLI · Codex CLI | + +--- + +## Monorepo structure + +``` +lcbp3/ +├── backend/ NestJS + TypeORM (port 3001) +├── frontend/ Next.js 14 App Router (port 3000) +├── specs/ ADRs, specs, data dictionary +├── .agents/ Agent skills and rules +├── .gitea/workflows/ CI/CD pipeline definitions +├── .windsurf/ Windsurf workflows and rules +├── memory/ Agent memory +└── CONTEXT.md ← this file +``` + +--- + +## Stack + +| Layer | Technology | Notes | +|---|---|---| +| Backend | NestJS + TypeScript | strict mode | +| ORM | TypeORM + MariaDB | migrations only, no sync | +| Queue | BullMQ + Redis | async ingestion, OCR jobs | +| Frontend | Next.js 14 App Router | shadcn/ui, Tailwind | +| Auth | JWT + RBAC | roles: ADMIN / PM / ENGINEER / VIEWER | +| File storage | MinIO | no local disk uploads | +| Embeddings | Ollama · nomic-embed-text | local on QNAP | +| Generation | Typhoon API · typhoon-v2.1-12b-instruct | Thai-first LLM | +| Vector store | Qdrant | Docker sidecar on QNAP, port 6333 | +| CI/CD | Gitea Actions → ASUSTOR runners | | +| Package manager | pnpm workspaces | | + +--- + +## Infrastructure + +| Service | Host | Port | +|---|---|---| +| Gitea | QNAP NAS | 3000 | +| MariaDB (dev) | QNAP NAS | 3306 | +| Redis | QNAP NAS | 6379 | +| Qdrant | QNAP NAS | 6333 | +| MinIO | QNAP NAS | 9000 | +| Ollama | QNAP NAS | 11434 | +| Hermes Agent | ASUSTOR NAS | 8765 (proxy) | +| Gitea Actions runners | ASUSTOR NAS | — | +| Nginx Proxy Manager | QNAP NAS | 80 / 443 | +| n8n | QNAP NAS | 5678 | + +--- + +## Active modules + +| Module | Status | Notes | +|---|---|---| +| Correspondence | 🟡 In progress | revision workflow, 4-step wizard | +| Document | 🟢 Active | core DMS, file upload | +| User / Auth | 🟢 Active | JWT, RBAC | +| RAG pipeline | 🟡 In progress | BullMQ ingestion, Qdrant, EasyOCR | +| Notification | 🔴 Planned | — | +| Report | 🔴 Planned | — | + +--- + +## Key file paths + +| ต้องการ | Path | +|---|---| +| DB entities | `backend/src/database/entities/` | +| Migrations | `backend/src/database/migrations/` | +| NestJS modules | `backend/src/modules/` | +| Next.js pages | `frontend/app/(dashboard)/` | +| API hooks | `frontend/lib/api/` | +| Shared types | `frontend/types/` | +| RBAC guard | `backend/src/common/auth/` | +| CI workflow | `.gitea/workflows/ci-deploy.yml` | +| ADR docs | `specs/06-Decision-Records/` | + +--- + +## Code conventions + +- TypeScript strict — ห้าม `any`, ห้าม non-null assertion (`!.`) บน DB results +- API response envelope: `{ data, meta, error }` +- Thai + English field support บน document metadata +- File upload → MinIO เท่านั้น (ห้าม local disk) +- async ทุกตัวต้อง try/catch หรือ `.catch()` +- Commit format: `type(scope): description` + - เช่น `feat(correspondence): add revision workflow` + - เช่น `fix(ci): fix pnpm cache mount path` + +--- + +## Rule enforcement tiers + +### 🔴 Hard — ห้ามทำโดยเด็ดขาด +- commit ตรง `main` / `develop` โดยไม่ผ่าน PR +- รัน DB migration โดยไม่ได้รับ human approval +- ลบไฟล์ — ให้ย้ายไป `_archive/.` แทน +- เพิ่ม npm package ที่ root `package.json` โดยไม่แจ้ง +- expose `.env` values ใน log / console / test fixtures + +### 🟡 Flag — แจ้งก่อนดำเนินการ +- แก้ไข `docker-compose.yml` หรือ `.gitea/workflows/` +- เพิ่มหรือแก้ DB schema (ต้องมี migration file คู่กัน) +- เพิ่ม npm package ใหม่ (ต้องบอก package name + justification) +- แก้ไข shared `packages/*` ที่กระทบหลาย apps + +### 🟢 Suggest — แนะนำแต่ดำเนินการได้ +- code style, naming convention +- test coverage +- refactoring ที่ไม่เปลี่ยน public API + +--- + +## Before any code task + +1. อ่าน entity files ที่เกี่ยวข้องใน `backend/src/database/entities/` +2. ตรวจ migrations ที่มีอยู่ใน `backend/src/database/migrations/` +3. ตรวจว่า module มีอยู่แล้วก่อน scaffold ใหม่ +4. อ่าน ADR ที่เกี่ยวข้องใน `specs/06-Decision-Records/` + +--- + +## When blocked + +หยุดทันที และ output: + +``` +⚠️ BLOCKED: [คำถาม] +Assumption ที่ตั้งใจจะใช้: [assumption] +รอ human input ก่อนดำเนินการต่อ +``` + +ห้าม guess business logic, permission rules, หรือ DB schema + +--- + +## Agent-specific notes + +### Hermes (ASUSTOR) +- Terminal backend: SSH → ASUSTOR itself +- Working directory: `/volume1/gitea-repos/lcbp3-dms` +- Scheduled: CI audit daily 08:00 → report ผ่าน Telegram +- Skills autoload: `dms-context`, `gitea-watch`, `rag-ops` + +### agy CLI +- Model: Gemini 3.5 Flash (orchestration) +- Hermes เป็น MCP server → `hermes-memory` tool +- Max parallel subagents: 3 +- Delegate bash/git execution → Hermes via MCP + +### Windsurf / Cascade +- MCP: MariaDB (schema lookup), Gitea (PR/issue) +- Rules enforced via `AGENTS.md` v1.9.7 (master), synced to `.windsurfrules` และ `.agents/rules/` +- Primary tool สำหรับ active coding + +## Setup context +#!/usr/bin/env bash +# setup-context.sh — wire CONTEXT-ADR-031.md ไปยัง tools ทุกตัว +# +# รันจาก: specs/06-Decision-Records/CONTEXT-ADR-031.md (ดูส่วนท้ายไฟล์) +# รันบน: laptop (สำหรับ agy + Windsurf) และ ASUSTOR (สำหรับ Hermes) +# +# Usage: +# cd specs/06-Decision-Records && bash CONTEXT-ADR-031.sh laptop +# cd specs/06-Decision-Records && bash CONTEXT-ADR-031.sh asustor +# cd specs/06-Decision-Records && bash CONTEXT-ADR-031.sh all + +set -e + +REPO_ROOT="$(cd "$(dirname "$0")" && cd ../.. && pwd)" +CONTEXT_FILE="$REPO_ROOT/specs/06-Decision-Records/CONTEXT-ADR-031.md" + +# ── helper ────────────────────────────────────────────────── +info() { echo " ✅ $*"; } +warn() { echo " ⚠️ $*"; } +section() { echo; echo "▶ $*"; } + +# ── laptop: agy ───────────────────────────────────────────── +setup_agy() { + section "agy CLI" + + AGY_DIR="$HOME/.gemini/antigravity-cli" + AGY_GLOBAL_DIR="$HOME/.gemini/config" + + mkdir -p "$AGY_DIR" "$AGY_GLOBAL_DIR" + + # symlink CONTEXT-ADR-031.md → agy context directory + AGY_CONTEXT_LINK="$AGY_DIR/DMS_CONTEXT_ADR031.md" + ln -sf "$CONTEXT_FILE" "$AGY_CONTEXT_LINK" + info "symlink: $AGY_CONTEXT_LINK → $CONTEXT_FILE" + + # patch settings.json: เพิ่ม contextFiles entry + SETTINGS="$AGY_DIR/settings.json" + if [ ! -f "$SETTINGS" ]; then + cat > "$SETTINGS" <<'EOF' +{ + "model": "gemini-3.5-flash-high", + "theme": "Default", + "autoAccept": false, + "contextFiles": [], + "permissions": { + "defaultMode": "suggest", + "autoApprove": [ + "read_file", + "list_directory", + "mcp_hermes-memory_recall", + "mcp_mariadb_query" + ] + }, + "subagents": { "maxParallel": 3 } +} +EOF + info "สร้าง settings.json ใหม่" + fi + + # เพิ่ม contextFiles entry ถ้ายังไม่มี + if ! grep -q "DMS_CONTEXT_ADR031" "$SETTINGS"; then + # ใช้ python เพราะ jq อาจไม่ติดตั้ง + python3 - "$SETTINGS" "$AGY_CONTEXT_LINK" <<'PYEOF' +import json, sys +settings_path, context_path = sys.argv[1], sys.argv[2] +with open(settings_path) as f: + s = json.load(f) +if "contextFiles" not in s: + s["contextFiles"] = [] +if context_path not in s["contextFiles"]: + s["contextFiles"].append(context_path) +with open(settings_path, "w") as f: + json.dump(s, f, indent=2, ensure_ascii=False) +print(f" ✅ เพิ่ม contextFiles: {context_path}") +PYEOF + else + info "contextFiles มีอยู่แล้วใน settings.json" + fi +} + +# ── laptop: Windsurf ───────────────────────────────────────── +setup_windsurf() { + section "Windsurf .windsurfrules" + + RULES="$REPO_ROOT/.windsurfrules" + IMPORT_LINE="@import specs/06-Decision-Records/CONTEXT-ADR-031.md" + + if [ ! -f "$RULES" ]; then + warn ".windsurfrules ไม่พบ — ข้ามขั้นตอนนี้" + return + fi + + if grep -q "@import specs/06-Decision-Records/CONTEXT-ADR-031.md" "$RULES"; then + info "@import specs/06-Decision-Records/CONTEXT-ADR-031.md มีอยู่แล้วใน .windsurfrules" + else + # เพิ่ม import ที่บรรทัดแรก + TMPFILE=$(mktemp) + echo "$IMPORT_LINE" | cat - "$RULES" > "$TMPFILE" + mv "$TMPFILE" "$RULES" + info "เพิ่ม @import specs/06-Decision-Records/CONTEXT-ADR-031.md ที่บรรทัดแรกของ .windsurfrules" + fi +} + +# ── ASUSTOR: Hermes ────────────────────────────────────────── +setup_hermes() { + section "Hermes (ASUSTOR)" + + HERMES_CONFIG_DIR="/volume1/docker/hermes/config" + + if [ ! -d "$HERMES_CONFIG_DIR" ]; then + warn "$HERMES_CONFIG_DIR ไม่พบ — รัน script นี้บน ASUSTOR หรือสร้าง folder ก่อน" + return + fi + + # symlink CONTEXT-ADR-031.md → MEMORY.md ที่ Hermes อ่าน + MEMORY_LINK="$HERMES_CONFIG_DIR/MEMORY.md" + ln -sf "$CONTEXT_FILE" "$MEMORY_LINK" + info "symlink: $MEMORY_LINK → $CONTEXT_FILE (specs/06-Decision-Records/CONTEXT-ADR-031.md)" + + # reload Hermes config (ถ้า container รันอยู่) + if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^hermes$"; then + docker exec hermes hermes reload 2>/dev/null && info "Hermes reloaded" || warn "ไม่สามารถ reload Hermes — รีสตาร์ท container แทน" + else + warn "Hermes container ไม่ได้รัน — symlink พร้อมแล้ว จะมีผลตอน restart" + fi +} + +# ── main ──────────────────────────────────────────────────── +TARGET="${1:-all}" + +case "$TARGET" in + laptop) + setup_agy + setup_windsurf + ;; + asustor) + setup_hermes + ;; + all) + setup_agy + setup_windsurf + setup_hermes + ;; + *) + echo "Usage: $0 [laptop|asustor|all]" + exit 1 + ;; +esac + +echo +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo " ✅ CONTEXT-ADR-031.md wired สำเร็จ" +echo " แก้ agent context → แก้ที่ specs/06-Decision-Records/CONTEXT-ADR-031.md" +echo " แก้ domain context → แก้ที่ CONTEXT.md (root)" +echo " tools ทุกตัวได้รับ update อัตโนมัติ" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"