---
title: "SotaAgents — Hướng dẫn sử dụng"
description: "Tất cả những gì bạn cần để trò chuyện với AI, sử dụng năng lực, xây dựng và cài đặt ứng dụng, và quản lý tổ chức."
url: "https://sotaagents.ai/vi/manual"
generated_by: "sotaagents-ldp"
docs_index: "https://sotaagents.ai/manual/llms.txt"
locale: "vi"
---

# SotaAgents — Hướng dẫn sử dụng

## Bắt đầu

Tìm hiểu về SotaAgents, nắm vững các khái niệm cốt lõi và đăng nhập lần đầu tiên.

### Tổng quan

SotaAgents là một không gian làm việc AI ưu tiên trò chuyện. Đăng nhập, chọn một workspace và đặt câu hỏi — trợ lý có thể khai thác tài liệu của nhóm, tạo tệp, tìm kiếm trên web và gọi các công cụ được kết nối. Màn hình chính hiển thị ô nhập liệu ở trung tâm.

![Màn hình chính SotaAgents với ô nhập ở giữa, workspace switcher và hội thoại gần đây ở sidebar](/manual/assets/product/chat-home-20260813.webp)
_Màn hình chính: một ô nhập, workspace và hội thoại gần đây ở bên trái._

Chủ sở hữu tổ chức và quản trị viên quản lý nền tảng từ **Admin Console** — một khu vực riêng biệt để tạo workspace, cài đặt ứng dụng, quản lý credit, xem nhật ký hoạt động và nhiều tính năng khác.

### Khái niệm cốt lõi

### Organization (Tổ chức)

Công ty của bạn. Quản lý thanh toán, thành viên và credit. Bạn có thể thuộc về một hoặc nhiều tổ chức.

### Workspace (Không gian làm việc)

Không gian làm việc cho một nhóm trong tổ chức. Quản lý các cuộc trò chuyện, tệp, kết nối tích hợp, server MCP và các ứng dụng đã cài đặt. Chuyển đổi workspace từ bộ chọn ở đầu thanh bên.

### Capability (Năng lực)

Phím tắt bạn thêm vào tin nhắn — Tạo hình ảnh, Tạo slide, Viết tài liệu, Tìm kiếm Web và hơn thế nữa. Nhập `/` trong khung chat để mở danh sách.

### Conversation (Cuộc trò chuyện)

Một luồng trò chuyện. Tất cả cuộc trò chuyện nằm ở thanh bên, được nhóm theo ngày. Bạn có thể sắp xếp chúng vào các Project.

### Project (Dự án)

Một nhóm các cuộc trò chuyện liên quan kèm theo hướng dẫn dùng chung, tệp ngữ cảnh và thành viên (tùy chọn). Được tạo từ trang Projects ở thanh bên.

### Artifact (Sản phẩm / Tệp kết quả)

Tệp mà trợ lý tạo ra hoặc trích dẫn — tệp .docx đã tạo, hình ảnh, nguồn PDF. Mở ở bảng điều khiển artifact bên cạnh ô chat.

### App (Ứng dụng)

Một tiện ích mở rộng có thể cài đặt (Knowledge Base, Office App, Web Search, CAD, Remagine) bổ sung các năng lực chuyên biệt cho tổ chức. Được cài đặt bởi quản trị viên từ App Store trong Admin Console.

### API Key

Thông tin xác thực cho tích hợp machine-to-machine đã được phê duyệt. Public Deployment tích hợp sẵn dùng guest session bị giới hạn, không đưa organization API key vào trình duyệt.

### Integration (Tích hợp)

Kết nối tới dịch vụ lưu trữ đám mây (Drive, OneDrive, SharePoint) do quản trị viên thiết lập. Cho phép trợ lý đọc tài liệu của nhóm.

### MCP server

Một plug-in công cụ do quản trị viên thiết lập. Khi được kết nối, trợ lý có thể sử dụng các công cụ đó ngay trong cuộc trò chuyện để thay bạn thực hiện thao tác.

### Đăng nhập

![Trang đăng nhập SotaAgents với bước nhập email và các lựa chọn đăng nhập mạng xã hội](/manual/assets/product/auth-login-20260813.webp)
_Bắt đầu bằng email; trang sẽ hiển thị các phương thức đăng nhập được phép cho tài khoản đó._

SotaAgents hỗ trợ bốn phương thức xác thực: **email + mật khẩu**, **Google SSO**, **Microsoft SSO**, và **Apple SSO**.

1. #### Mở trang đăng nhập

   Truy cập [https://app.sotaagents.ai/](https://app.sotaagents.ai/).

2. #### Chọn phương thức

   - **Email & mật khẩu** — nhập email và mật khẩu đã đăng ký, sau đó nhấn _Sign in_.
   - **Google SSO** — nhấn _Continue with Google_ và xác thực bằng tài khoản Google.
   - **Microsoft SSO** — nhấn _Continue with Microsoft_ và xác thực bằng tài khoản Microsoft.
   - **Apple SSO** — nhấn _Continue with Apple_ và xác thực bằng tài khoản Apple.

3. #### Tạo tài khoản

   Nếu chưa có tài khoản, nhấn _Register_, nhập tên, email và mật khẩu, rồi xác minh email. Đăng ký tạo tài khoản người dùng; bạn vẫn cần lời mời để tham gia tổ chức hoặc workspace chưa thuộc về mình.

4. #### Quên mật khẩu?

   Nhấn _Forgot password?_, nhập email và làm theo liên kết đặt lại mật khẩu. Không cần quản trị viên can thiệp.

5. #### Duy trì đăng nhập

   SotaAgents tự động duy trì phiên làm việc. Hãy _Sign out_ khi dùng xong thiết bị dùng chung.

> [!NOTE]
> Đăng nhập trên app native
>
> Bản desktop/mobile được hỗ trợ mở OAuth trong trình duyệt hệ thống và quay lại app bằng lượt trao đổi PKCE dùng một lần. Nếu callback lỗi, hết hạn hoặc đã dùng, hãy bắt đầu đăng nhập lại; khả năng hỗ trợ có thể khác nhau theo deployment.

> [!WARNING]
> Không thể đăng nhập?
>
> Hãy xác minh email, dùng đúng phương thức đăng ký và tuân thủ chính sách SSO. Nếu đăng nhập được nhưng không thấy tenant, hãy nhờ admin gửi lời mời tổ chức/workspace.

Sau khi xác thực, màn hình workspace xuất hiện. Dùng workspace selector ở đầu thanh bên để chuyển giữa các workspace.

### Cuộc trò chuyện đầu tiên

1. #### Chọn AI model, Speed & Reasoning Effort

   Chọn model AI, Speed và Reasoning Effort phù hợp với công việc:

   - **Reasoning Effort** — kiểm soát mức độ suy luận trước khi tạo câu trả lời. Mức càng cao thì câu trả lời càng chi tiết nhưng xử lý càng lâu hơn.
   - **Speed** — thay đổi cách định tuyến: `Standard` (mặc định) ưu tiên chất lượng; `Fast` ưu tiên thông lượng và không cộng phụ phí 1,5 lần.

   > [!NOTE]
   > **Khuyến nghị**Nếu không có yêu cầu đặc biệt, nên dùng cấu hình mặc định.

![Model selector mở trong composer, liệt kê các model Claude và Gemini khả dụng kèm hệ số credit](/manual/assets/product/chat-model-settings-20260813.webp)
_Mỗi model khả dụng hiển thị hệ số credit, nên bạn thấy chi phí trước khi chọn._

2. #### Nhập yêu cầu

   Nhập yêu cầu dưới dạng câu hỏi, hoặc một mô tả có cấu trúc nêu rõ **mục tiêu**, **bối cảnh**, và **định dạng đầu ra mong muốn**.

![Màn hình chính với ô nhập yêu cầu ở giữa](/manual/assets/product/chat-home-20260813.webp)
_Nhập yêu cầu ở đây. Các điều khiển bên dưới đi theo hội thoại._

3. #### Xem kết quả & tiếp tục

   Xem lại câu trả lời, yêu cầu chỉnh sửa nếu cần, sau đó tải tệp kết quả về hoặc tiếp tục cuộc trò chuyện để tinh chỉnh thêm.

![Hội thoại hiển thị câu hỏi, câu trả lời đã định dạng, số credit đã dùng và composer trả lời](/manual/assets/product/chat-conversation-20260813.webp)
_Câu trả lời đi kèm action riêng và chi phí credit. Trả lời tiếp trong cùng thread để tinh chỉnh._

4. #### Đính kèm tệp (nếu cần)

   Tệp chat được tải lên Core và gắn với cuộc trò chuyện; chúng không tự động được nhập vào Knowledge Base hay được bảo đảm chạy OCR.

   - **Định dạng:** hình ảnh, PDF và các tài liệu phổ biến mà uploader hiện chấp nhận
   - **Giới hạn:** 20 tệp mỗi tin nhắn, 100 tệp mỗi cuộc trò chuyện và 100 MB mỗi tệp

### Các năng lực hiện có (Capability)

Nền tảng có thể tạo hình ảnh, slide, tài liệu, bảng tính/PDF, vẽ sơ đồ, xây website, tìm web và gọi tool từ app đã bật. Khả năng thực tế phụ thuộc app và policy được cấp cho workspace. Slash directive chỉ áp dụng cho tin nhắn sắp gửi, không phải công tắc duy trì cho cả cuộc trò chuyện.

Với câu hỏi thông thường, trợ lý có thể chọn tool khả dụng cho lượt chạy đó. Knowledge Base và web search không phải mặc định được bảo đảm; hãy chọn capability hoặc nêu rõ nguồn khi bắt buộc.

| Năng lực | Sản phẩm trợ lý tạo ra |
| --- | --- |
| Search Knowledge Base | Truy vấn tài liệu đã được app Knowledge Base đang bật lập chỉ mục. Kết quả và citation phụ thuộc cấu hình app và lượt chạy hiện tại. |
| Search Web | Tra cứu câu trả lời trên internet trực tiếp và trích dẫn các nguồn tìm được. |
| Create Slides | Một bộ trình chiếu hoàn chỉnh từ một chủ đề hoặc dàn ý. Có thể chỉnh sửa trong trình biên tập slide trước khi xuất. |
| Write Doc | Tài liệu `.docx` — báo cáo, bản ghi nhớ, tóm tắt — có tiêu đề và định dạng. |
| Create Sheet | Bảng tính `.xlsx` với công thức, biểu đồ và định dạng. |
| Create PDF | Tệp PDF định dạng chuẩn — hợp đồng, trang dữ liệu, tài liệu tóm tắt 1 trang. |
| Generate Image | Hình ảnh từ mô tả văn bản — hình minh họa, mockup, banner. |
| Draw Diagram | Sơ đồ luồng hoặc sơ đồ quy trình từ mô tả của bạn. |
| Build Site | Một trang web nhỏ từ mô tả, xuất dưới dạng HTML. |

### Các năng lực hiện có

Năng lực hướng dẫn trợ lý loại đầu ra cần tạo. Nhập `/` trong ô chat để mở danh sách và chọn một năng lực; năng lực đó sẽ được thêm vào tin nhắn trước khi bạn gửi.

### Chi tiết từng capability

### Search Knowledge Base

Dùng khi câu trả lời phải dựa trên tài liệu đã được app Knowledge Base nhập và xử lý. Tìm kiếm, định dạng ingestion, OCR và citation phụ thuộc app/cấu hình. Lựa chọn chỉ áp dụng cho tin nhắn hiện tại; hãy chọn lại ở tin sau nếu cần ràng buộc nguồn rõ ràng. Web search cũng có thể được trợ lý chọn khi tool đó được cấp, vì vậy hãy nêu rõ phạm vi nguồn.

Tài liệu nội bộ

Tìm kiếm lai vector + toàn văn

Có trích dẫn

> [!NOTE]
> **Mẫu prompt**"Tìm thông tin về [chủ đề] trong tài liệu nội bộ của chúng tôi. Tóm tắt các điểm chính từ những phần liên quan nhất và nêu rõ tên tài liệu nguồn."

### Search Web

Tìm kiếm thông tin công khai, mới nhất trên Internet cho các nguồn ngoài dữ liệu nội bộ. Hỗ trợ tìm kiếm web thông thường, tìm kiếm tin tức, tìm kiếm hình ảnh, và trích xuất toàn bộ nội dung trang. Mọi câu trả lời đều trích dẫn liên kết nguồn.

Tìm kiếm Internet

Hỗ trợ tìm tin tức

Trích dẫn đầy đủ liên kết nguồn

> [!NOTE]
> **Mẫu prompt**"Tìm kiếm về [chủ đề và khoảng thời gian] [mục tiêu tìm kiếm]. Tóm tắt [nội dung cần đưa vào] từ [phạm vi nguồn] [phạm vi]. Trả về danh sách gạch đầu dòng ngắn gọn kèm số liệu chính và trích dẫn nguồn chính thức [định dạng đầu ra]."

### Tạo tài liệu (DOCX · Excel · PDF · PPTX)

Tạo tài liệu, bảng tính, PDF và bản trình chiếu từ yêu cầu bằng ngôn ngữ tự nhiên. Tệp được tạo bởi ứng dụng Office và chuyển sang PDF qua Gotenberg khi cần.

Bao gồm các năng lực **Write Doc**, **Create Sheet**, **Create PDF**, và **Create Slides**.

DOCX · EXCEL · PDF · PPTX

Tải về + Có thể chỉnh sửa

> [!NOTE]
> **DOCX**"Tạo tệp DOCX [định dạng] cho [mục đích tài liệu] [nội dung]. Trình bày chuyên nghiệp với cấu trúc Heading 1/2 rõ ràng, gạch đầu dòng, và bảng tóm tắt [cách trình bày]."

> [!NOTE]
> **EXCEL**"Tạo tệp Excel [định dạng] cho [mục đích tính toán/theo dõi] [nội dung]. Bao gồm công thức tự động tính [chỉ số], biểu đồ trực quan hóa [dữ liệu], và định dạng có điều kiện để làm nổi bật [điều kiện] [cách trình bày]."

> [!NOTE]
> **PDF**"Tạo tệp PDF [định dạng] cho [mục đích tài liệu] [nội dung]. Thiết kế gọn gàng, tối giản với [tông màu] phù hợp cho [đối tượng đọc] [cách trình bày]."

> [!NOTE]
> **PPTX (Slide)**"Tạo báo cáo [số lượng] slide về [chủ đề] [nội dung] cho [đối tượng]. Bao quát [các điểm chính], theo phong cách [trang trọng / hiện đại / tối giản] dùng [màu thương hiệu]. [phong cách]"

### Generate Image

Tạo hình minh họa từ mô tả bằng ngôn ngữ tự nhiên.

> [!NOTE]
> **Mẫu prompt**"Tạo hình ảnh về [chủ thể và bối cảnh] [nội dung], theo phong cách [nghệ thuật / ảnh chụp / render 3D] với [bảng màu và không khí] [phong cách], tỉ lệ khung hình [1:1 / 16:9] [tỉ lệ]."

### Draw Diagram

Tạo sơ đồ luồng / sơ đồ quy trình từ mô tả bằng ngôn ngữ tự nhiên.

> [!NOTE]
> **Mẫu prompt**"Vẽ sơ đồ quy trình cho [tên quy trình] [mục tiêu]. Bao gồm các bước từ [điểm bắt đầu] đến [điểm kết thúc] [nội dung]. Dùng sơ đồ luồng rõ ràng, chuyên nghiệp phù hợp cho [nơi sử dụng] [cách trình bày]."

### Build Site

Tạo website từ mô tả bằng ngôn ngữ tự nhiên.

Website chuyên nghiệp

Xuất HTML

> [!NOTE]
> **Mẫu prompt**"Xây dựng [loại trang, ví dụ: microsite/landing page] cho [mục đích] [mục tiêu]. Bao gồm [các phần, ví dụ: giới thiệu, lịch trình, FAQ, liên hệ] [nội dung]. Thiết kế tối giản, chuyên nghiệp, dễ điều hướng [cách trình bày]."

### Tài khoản & phiên làm việc

![Menu tài khoản mở từ avatar, hiển thị usage còn lại, ngôn ngữ, theme, tài liệu, settings, console và đăng xuất](/manual/assets/product/chat-account-menu-20260813.webp)
_Mọi thứ trong mục này nằm sau avatar ở góc dưới bên trái._

### Giao diện Sáng & Tối

Mở _Settings → General_ và đặt Appearance thành Light, Dark hoặc System. System dùng cài đặt giao diện của hệ điều hành.

### Giảm chuyển động (Reduced motion)

Nếu hệ điều hành của bạn bật chế độ giảm chuyển động, giao diện sẽ tự động tuân thủ — các hiệu ứng chuyển động được giảm tối đa.

### Cấu hình model & phiên làm việc

Chọn model, mức độ suy luận và tốc độ cho từng cuộc trò chuyện ngay tại bộ chọn model trong composer trên máy tính hoặc di động. Xem [Cấu hình model & phiên làm việc](/vi/manual/using-sotaagents/model-and-session-settings).

### Settings

Mở menu tài khoản ở cuối thanh bên rồi chọn _Settings_ để quản lý General, AI Agent, Memory, Security, Usage và Login history khi gateway hỗ trợ.

### Cài đặt General và AI Agent

Dùng thẻ _General_ để quản lý thông tin hồ sơ, Appearance, font chat và Motion.

![Account Settings ở thẻ General với menu Appearance gồm System, Light và Dark](/manual/assets/product/chat-settings-general-20260903.webp)
_General gom thông tin hồ sơ và tùy chọn hiển thị vào một nơi._

Dùng _Settings → AI Agent_ để chọn phím gửi, hành vi tin nhắn tiếp theo Queue hoặc Steer, thông báo hoàn tất lượt, custom instructions, Stats for nerds và hiển thị model context.

![Thẻ AI Agent hiển thị phím gửi, hành vi follow-up, thông báo và custom instructions](/manual/assets/product/chat-settings-ai-agent-20260903.webp)
_AI Agent tập trung hành vi hội thoại và các tùy chọn chẩn đoán._

Mở phần _Voice_ để chọn Voice, Style và Speed cho tính năng đọc to. Âm thanh được stream theo từng câu nên có thể bắt đầu trước khi toàn bộ câu trả lời hoàn tất.

![Cài đặt AI Agent Voice với các tùy chọn giọng, phong cách và tốc độ](/manual/assets/product/chat-settings-ai-agent-voice-20260903.webp)
_Cài đặt Voice áp dụng cho action Read aloud trên câu trả lời của trợ lý._

### Personal Memory

Mở _Settings → Memory_. _Personal memory_ tạm dừng hoặc tiếp tục toàn bộ tính năng mà không xóa dữ liệu. _Use memory in chats_ điều khiển việc dùng lại thông tin; _Learn from chats_ điều khiển việc học nền sau khi chat hoàn tất. Bạn có thể cho phép riêng các nhóm: phong cách giao tiếp, cách làm việc, công cụ, thông tin cá nhân và mục tiêu dài hạn. Tắt một nhóm chỉ ngăn lưu tự động mới, không xóa mục đã có. Incognito và phiên guest/public không dùng hoặc ghi Personal Memory.

![Cài đặt Personal Memory với điều khiển riêng cho dùng lại, học từ chat, chính sách theo nhóm và các thao tác quản lý, activity, import, export](/manual/assets/product/chat-memory-settings-20260813.webp)
_Dùng lại, học và quyền theo nhóm hoạt động độc lập; tạm dừng memory vẫn giữ các mục đã lưu._

Chọn _View and manage memory_ để xem, sửa, xác nhận mục cần review hoặc quên từng mục. Memory theo workspace vẫn riêng tư với tài khoản của bạn—nó chỉ liên quan trong workspace đó, không được chia sẻ với thành viên khác. _Memory activity_ ghi lại thay đổi gần đây; export tải JSON, còn import cho xem trước dữ liệu từ AI khác trước khi thêm.

![Trình quản lý Personal Memory riêng tư liệt kê các mục về giao tiếp, thông tin cá nhân và cách làm việc cùng nút sửa và quên](/manual/assets/product/chat-memory-items-20260813.webp)
_Mục đã lưu có thể dùng ở mọi workspace hoặc chỉ liên quan một workspace, nhưng luôn riêng tư với chủ tài khoản._

### Xác thực hai yếu tố và lịch sử đăng nhập

Trong _Settings → Security_, thiết lập 2FA bằng ứng dụng xác thực, lưu recovery codes và quản lý trusted devices. _Login history_ hiển thị các phiên do Core ghi nhận; đăng nhập OAuth native cũ chỉ xuất hiện nếu client và gateway cung cấp dữ liệu đó.

★. #### Xem hạn mức sử dụng

   Chọn **ảnh đại diện** ở **góc dưới bên trái** màn hình để xem hạn mức sử dụng còn lại và số dư credit.

↩. #### Đăng xuất

   Chọn menu **ảnh đại diện** ở góc dưới bên trái, sau đó chọn **Sign out** để kết thúc phiên làm việc.

## Sử dụng SotaAgents

Trò chuyện là trung tâm — nhập tin nhắn và trợ lý sẽ phản hồi, tìm kiếm, tạo tệp và gọi các công cụ khi cần. Bạn cũng có thể tổ chức các cuộc trò chuyện thành project, chia sẻ với đồng nghiệp và tùy chỉnh model cho từng cuộc trò chuyện.

### Bắt đầu trò chuyện

Trò chuyện luôn diễn ra bên trong một workspace, vì vậy bước đầu tiên là mở workspace. Mở **bộ chọn workspace** ở đầu thanh bên, chọn workspace bạn muốn làm việc, và màn hình chính sẽ tải với ô nhập liệu ở trung tâm hỏi _How can I help you today?_ — nhập câu hỏi của bạn tại đó để bắt đầu cuộc trò chuyện.

### Bố cục giao diện chat

![Bố cục chat gồm workspace selector, New Conversation, Search và Projects trên danh sách Recents, với composer ở giữa](/manual/assets/product/chat-home-20260813.webp)
_Thanh bên chứa workspace, tìm kiếm và hội thoại; phần còn lại là thread._

| Khu vực | Nội dung |
| --- | --- |
| Thanh bên (trái) | Bộ chọn workspace ở trên cùngNút New Conversation (Cuộc trò chuyện mới)Tìm kiếm (mở bảng lệnh để tìm cuộc trò chuyện)Projects (đi tới danh sách dự án của bạn)Các cuộc trò chuyện gần đâyMenu tài khoản ở dưới cùng |
| Giao diện Chat (giữa) | Khu vực cuộc trò chuyệnKhung nhập tin nhắn (chứa bộ chọn model, ô nhập văn bản, nút đính kèm **(+)**, các thẻ tệp và nút gửi/dừng)Nhập `/` để mở danh sách các năng lực |
| Bảng Artifact (phải) | Mở khi bạn nhấp vào một tệp được tạo hoặc một trích dẫnCho phép đọc, duyệt và tải tệp xuống |

### Gửi tin nhắn

1. #### Bắt đầu cuộc trò chuyện mới (tùy chọn)

   Nhấn _New Conversation_ trên thanh bên để bắt đầu lại.

2. #### Nhập và gửi

   Phím `Enter` để gửi, `Shift`+`Enter` để xuống dòng.

3. #### Theo dõi phản hồi trực tiếp

   Câu trả lời của trợ lý xuất hiện theo dạng luồng (stream). Nếu cần tìm kiếm, tạo tệp hoặc gọi công cụ, bạn sẽ thấy một _bước công cụ (tool step)_ xuất hiện trước khi trợ lý tiếp tục viết.

![Câu trả lời của trợ lý đang stream vào thread với heading và bullet, phía trên composer trả lời](/manual/assets/product/chat-conversation-20260813.webp)
_Câu trả lời hiện dần khi được viết ra; bạn có thể đọc trong lúc nó hoàn tất._

4. #### Dừng phản hồi

   Trong khi trợ lý đang trả lời, nút gửi đổi thành nút _Stop_. Nhấp vào nút đó để dừng tạo nội dung.

![Hàng action dưới tin nhắn của trợ lý: retry, copy, đọc to và feedback, kèm số credit đã dùng](/manual/assets/product/chat-message-actions-20260813.webp)
_Mỗi câu trả lời có action riêng và chi phí credit._

### Trả lời đoạn trích, đọc to và tạo nhánh

Bôi chọn một đoạn trong câu trả lời của trợ lý rồi nhấn _Reply_. Đoạn trích được gắn phía trên composer để tin nhắn tiếp theo giữ đúng ngữ cảnh.

![Đoạn văn được chọn trong câu trả lời của trợ lý với action Reply để gắn vào composer](/manual/assets/product/chat-quote-reply-20260903.webp)
_Chỉ chọn phần muốn trao đổi rồi trả lời trực tiếp đoạn đó._

Mở menu _More_ của câu trả lời để dùng _Read aloud_ hoặc _Fork from this message_. Fork từ tin nhắn sẽ sao chép hội thoại đến đúng câu trả lời đó sang thread mới và giữ nguyên bản gốc.

![Menu More của tin nhắn trợ lý hiển thị Read aloud và Fork from this message](/manual/assets/product/chat-message-fork-menu-20260903.webp)
_Đọc to hoặc tạo nhánh tại đúng tin nhắn từ cùng một menu._

### Chỉnh sửa và hội thoại dài

Khi sửa một tin nhắn người dùng cũ, composer mở dưới lớp phủ tối và khóa cuộn. Nhấn `Esc` hoặc nhấp ra ngoài để thoát; nội dung đang sửa vẫn được giữ nếu bạn chuyển hội thoại rồi quay lại.

![Tin nhắn cũ đang được sửa trong composer dưới lớp phủ tối](/manual/assets/product/chat-message-edit-overlay-20260903.webp)
_Edit mode chiếm màn hình để điểm tạo nhánh luôn rõ ràng._

Khi hội thoại dài được tự động tóm tắt để giải phóng context, timeline hiển thị activity _Context automatically compacted_.

![Activity Context automatically compacted xuất hiện trong hội thoại](/manual/assets/product/chat-context-compaction-20260903.webp)
_Mỗi lần tự động nén context đều được ghi lại trên timeline._

### Tổ chức không hoạt động

Nếu gói của tổ chức không còn hoạt động, thông báo xuất hiện ngay trên composer. Nội dung cũ vẫn đọc được, nhưng gửi tin nhắn, tải tệp và thay đổi app bị khóa cho tới khi khôi phục gói.

![Composer hiển thị thông báo gói tổ chức đã bị hủy và thao tác gửi bị khóa](/manual/assets/product/chat-org-inactive-notice-20260903.webp)
_Thông báo tổ chức không hoạt động nằm cùng các control bị vô hiệu hóa._

> [!NOTE]
> Nếu gặp sự cố
>
> Một banner thông báo lỗi xuất hiện bên dưới tin nhắn kèm các nút _Retry_ (Thử lại) và _Dismiss_ (Bỏ qua).

### Cấu hình model & phiên làm việc

**Bộ chọn model** nằm trong composer trên cả máy tính và di động. Bộ chọn hiển thị model hiện tại và mở bảng tùy chọn để chọn model, mức suy luận và tốc độ cho các tin nhắn tiếp theo.

1. #### Chọn model

   Nhấp vào tên model để mở bộ chọn. Danh sách chỉ hiển thị model tổ chức đang được phép dùng. Owner hoặc Admin đủ quyền có thể gửi yêu cầu truy cập model tại _Console → Organization → Chat Models_.

![Model selector hiện tại hiển thị Claude và Gemini khả dụng cùng Reasoning effort và Speed](/manual/assets/product/chat-model-settings-latest-20260903.webp)
_Chat picker chỉ chứa model có thể chọn; yêu cầu truy cập nằm trong Console._

2. #### Cài đặt mức độ suy luận (Reasoning effort)

   Tùy chọn _Reasoning effort_ quy định mức độ suy luận của model trước khi trả lời — _None_, _Minimal_, _Low_, _Medium_, _High_, hoặc _X-High_. Mức suy luận cao hơn cho câu trả lời tốt hơn cho bài toán khó nhưng mất nhiều thời gian hơn và tiêu tốn nhiều credit hơn. Chỉ hiển thị với model có hỗ trợ.

3. #### Chọn tốc độ (Speed)

   _Standard_ ưu tiên chất lượng; _Fast_ ưu tiên route có thông lượng cao nhất. Credit được tính theo đúng chi phí do provider báo, không cộng phụ phí Fast 1,5 lần. Chỉ hiển thị với model có hỗ trợ.

### Thống kê lượt và model context

Bật _Stats for nerds_ và _Show model context_ trong _Settings → AI Agent_. Dòng dưới composer hiển thị số lượt, số bước, thời gian LLM, tool call và thời gian trung bình tới token đầu tiên. Context readout hiển thị mức dùng cửa sổ cùng phần system prompt, message, attachment, tool call và skill.

![Composer hiển thị thống kê thời gian từng lượt và bảng chi tiết mức dùng model context](/manual/assets/product/chat-turn-stats-model-context-20260903.webp)
_Thống kê thời gian và context là chẩn đoán tùy chọn trong AI Agent settings._

### Tải lên tệp đính kèm

Kéo thả tệp vào cuộc trò chuyện khi bạn muốn trợ lý đọc tệp đó trong cuộc trò chuyện — hợp đồng, bảng tính, ảnh chụp màn hình. Mỗi tin nhắn tối đa 20 tệp, mỗi cuộc trò chuyện tối đa 100 tệp, và mỗi tệp tối đa 100 MB.

1. #### Đính kèm

   **Kéo & thả** tệp vào khu vực nhập liệu, hoặc nhấp vào nút dấu cộng (**+**) để chọn tệp từ máy tính. Bạn cũng có thể dán trực tiếp hình ảnh hoặc tài liệu vào ô nhập.

2. #### Chờ tải lên

   Mỗi tệp hiển thị một thẻ kèm trạng thái tải lên. Xóa thẻ bằng nút _x_ trước khi gửi nếu cần. Các định dạng được hỗ trợ bao gồm hình ảnh, PDF và các tài liệu phổ biến.

3. #### Gửi tin nhắn

   Nhấn gửi. Core lưu tệp với cuộc trò chuyện để các lượt sau trong cùng thread có thể tham chiếu lại.

> [!NOTE]
> Tệp đính kèm khác Knowledge Base
>
> Tệp đính kèm là tệp thô của Core; hệ thống không tự nhập vào Knowledge Base hoặc tự OCR. Muốn tài liệu dùng chung và có thể tìm kiếm, hãy nhập riêng trong ứng dụng [Knowledge Base](/vi/manual/using-sotaagents/integrations) nếu workspace đã cài đặt và bật ứng dụng đó.

### Câu trả lời phong phú

Câu trả lời của trợ lý không chỉ là văn bản thuần túy. SotaAgents hiển thị câu trả lời với đầy đủ định dạng và có thể nhúng nội dung được tạo ngay trong câu phản hồi.

### Định dạng

- **Khối mã (Code blocks)** — tô màu cú pháp, hiển thị tên ngôn ngữ và nút _sao chép_.
- **Bảng (Tables)** — có đường viền và cuộn ngang khi bảng rộng.
- **Danh sách (Lists)** — dạng gạch đầu dòng và đánh số.
- **Liên kết (Links)** — mở trong thẻ mới.

### Nội dung được tạo bên trong câu trả lời

- **Hình ảnh được tạo** — hiển thị trực tiếp; nhấp vào để phóng to trong lightbox.
- **Tệp được tạo** — tài liệu, bảng tính, slide và PDF hiển thị dưới dạng khối mà bạn có thể mở trong [bảng artifact](/vi/manual/using-sotaagents/artifacts-panel) và tải xuống.
- **Kết quả tìm kiếm web** — khi trợ lý tìm kiếm trên web, kết quả hiển thị trực tiếp trước khi tổng hợp vào câu trả lời.
- **Công việc của subagent** — phần việc được giao xuất hiện dưới dạng chip có tên. Mở chip để xem transcript bền vững của subagent mà không bung các tin nội bộ vào timeline chính.

### Bảng điều khiển Artifact

Khi trợ lý tạo ra tệp — hoặc khi bạn mở một trích dẫn — tệp đó sẽ xuất hiện ở **bảng artifact** bên phải khu vực chat.

### Nội dung mở tại đây

| Loại | Nội dung hiển thị |
| --- | --- |
| Tài liệu được tạo | Tài liệu Word, bảng tính, slide và PDF do trợ lý tạo — được xem trước kèm nút tải xuống. |
| Tệp & Hình ảnh | PDF, hình ảnh, video, âm thanh, văn bản, markdown, HTML và CSV hiển thị trực tiếp trong bảng. |
| Xem trước trích dẫn | Tài liệu nguồn đằng sau trích dẫn, được mở tại chính trang được trích dẫn. |
| Transcript của subagent | Transcript chỉ đọc có tên cho công việc được giao, gồm nhiệm vụ, kết quả và trạng thái cuối; vẫn mở lại được sau khi tải lại lịch sử. |

1. #### Mở một artifact

   Nhấp vào tệp được tạo, huy hiệu trích dẫn hoặc chip subagent có tên. Bảng điều khiển mở ra bên phải.

![Chat có chip subagent Launch Readiness Analyst được mở thành transcript bền vững trong bảng artifact](/manual/assets/product/chat-subagent-artifact-20260813.webp)
_Công việc subagent nằm gọn trong thread chính và mở thành transcript bền vững trong bảng artifact._

2. #### Đọc & duyệt

   Cuộn tài liệu hoặc transcript subagent, chuyển trang PDF, phóng to ảnh, hoặc phát phương tiện — tùy loại artifact.

### Preview skill và tài liệu Office

Dùng breadcrumb phía trên Skill viewer để chọn tệp trong skill.

![Skill viewer mở menu breadcrumb chọn tệp ở phía trên panel](/manual/assets/product/chat-skill-viewer-breadcrumb-20260903.webp)
_Dùng breadcrumb để chuyển giữa các tệp skill mà vẫn giữ rộng vùng preview._

Tệp tải xuống giữ nguyên tên gốc. Preview Excel hiển thị ảnh, biểu đồ và shape nhúng. Preview form DOCX giữ nguyên bố cục; thao tác điền chỉ sửa body, không thay đổi header, footer hoặc hình học trang, và không biến dấu footnote thành field.

3. #### Tải xuống

   Sử dụng nút tải xuống để lưu tệp về máy tính.

### Trích dẫn & Nguồn

Khi trợ lý trả lời bằng tài liệu từ workspace hoặc trên web, trợ lý sẽ trích dẫn nguồn. Trích dẫn xuất hiện dưới dạng thẻ nhỏ trong câu trả lời, hiển thị tên tệp nguồn.

1. #### Nhận biết trích dẫn

   Tìm các thẻ nhỏ hiển thị tên tệp nguồn trong câu trả lời. Di chuột qua (hoặc chạm trên di động) để xem thẻ tóm tắt nhanh hiển thị tiêu đề nguồn, mục và đoạn trích dẫn ngắn.

2. #### Mở nguồn

   Nhấp vào thẻ trích dẫn để mở tài liệu nguồn trong bảng artifact. Tài liệu hiển thị bản xem trước đầy đủ — các tệp Office mở trong trình xem tài liệu, giúp bạn đọc nội dung gốc đúng ngữ cảnh.

### Lịch sử

Các cuộc trò chuyện thông thường đã tạo thành công được lưu trong lịch sử workspace. Phiên Incognito không được giữ trong History: Core có thể lưu tạm khi phiên còn hoạt động rồi xóa khi đóng hoặc hết hạn, tối đa 24 giờ. Yêu cầu bị guardrail chặn trước khi tạo hội thoại và một số lần tạo thất bại cũng không xuất hiện trong lịch sử.

1. #### Tìm cuộc trò chuyện

   Nhấp **Search** ở thanh bên (hoặc nhấn `⌘``K`) để mở command palette: nó liệt kê các cuộc trò chuyện gần đây và cho phép nhảy thẳng tới một cuộc. Bạn cũng có thể cuộn thanh bên mục _Recents_ và nhấp vào cuộc trò chuyện bất kỳ; cuộc đang xem được làm nổi bật.

![Command palette tìm kiếm liệt kê hội thoại gần đây và hành động gợi ý](/manual/assets/product/chat-search-palette-20260813.webp)
_Search mở một command palette đè lên trang: hội thoại gần đây trước, rồi các hành động như New Conversation và Projects._

2. #### Thay đổi cách nhóm

   Nhấp vào biểu tượng thanh trượt bên cạnh _Recents_ để mở menu _Group by_. Chọn _None_ (danh sách phẳng), _Date_ (Hôm nay, Hôm qua, Tuần này…), hoặc _Project_ (nhóm theo dự án).

3. #### Quản lý và tiếp tục

   Ghim tối đa 100 cuộc trò chuyện, đổi tên hoặc xóa từ menu ba chấm. Khi một phản hồi đang chạy, bạn có thể xếp hàng hoặc chỉnh sửa tin nhắn tiếp theo, dừng, thử lại hoặc sửa một tin nhắn cũ để tiếp tục từ điểm đó.

### Fork cuộc trò chuyện

Mở menu tùy chọn của hội thoại ở sidebar hoặc history bar rồi chọn _Fork_ để sao chép toàn bộ thread sang cuộc trò chuyện mới. Fork từ câu trả lời trợ lý sẽ tạo nhánh tại đúng điểm đó. Bản gốc không đổi; bản fork hoạt động độc lập và không có chip provenance.

Bản nháp composer được giữ theo route hội thoại khi bạn điều hướng. Bật _Incognito_ trước khi gửi nếu không muốn tạo mục lịch sử.

### Project

Project giúp bạn nhóm các cuộc trò chuyện liên quan dưới một tên chung, kèm theo các hướng dẫn tùy chọn và tệp ngữ cảnh dùng chung. Nhấp vào _Projects_ ở thanh bên để mở danh sách dự án.

### Tạo một project

1. #### Mở danh sách

   Nhấp vào _Projects_ ở thanh bên. Sử dụng các thẻ để chuyển giữa _Created by you_ (Do bạn tạo) và _Shared with you_ (Được chia sẻ với bạn). Sắp xếp theo lần sửa cuối, tên, hoặc ngày tạo.

2. #### Tạo mới

   Nhấn _New project_, nhập tên (bắt buộc) và mô tả (tùy chọn), sau đó xác nhận. Project xuất hiện ngay lập tức trong danh sách.

![Màn hình Projects liệt kê hai project, có tab cho project bạn tạo và được chia sẻ với bạn](/manual/assets/product/chat-projects-list-20260813.webp)
_Project mới xuất hiện trong danh sách ngay lập tức._

### Thêm và di chuyển cuộc trò chuyện

1. #### Thêm vào project

   Nhấp vào menu **…** của cuộc trò chuyện bất kỳ — ở thanh bên hoặc trong project — và chọn _Add to project_. Menu phụ liệt kê các project của bạn; bạn cũng có thể tạo project mới từ đó.

![Menu ba chấm trên hội thoại ở sidebar, submenu Add to project đang mở liệt kê các project](/manual/assets/product/chat-add-to-project-20260813.webp)
_Hội thoại bất kỳ đều có thể đưa vào project từ menu ba chấm._

2. #### Di chuyển hoặc xóa

   Tương tự từ menu **…**, chọn _Move to project_ để chuyển sang project khác (có hộp thoại xác nhận), hoặc _Remove from project_ để hủy liên kết mà không xóa cuộc trò chuyện.

### Bên trong một project

Mở một project sẽ hiển thị khung nhập để bắt đầu chat mới trong project, cùng danh sách các cuộc trò chuyện thuộc project. Thanh bên phải cho phép bạn:

![Trang chi tiết project hiển thị hội thoại, Instructions, tệp ngữ cảnh và thành viên](/manual/assets/product/chat-project-detail-20260813.webp)
_Bên trong project: hội thoại, hướng dẫn dùng chung, tệp ngữ cảnh và những người có quyền xem._

- **Instructions (Hướng dẫn)** — thiết lập hướng dẫn dùng chung áp dụng cho mọi cuộc chat được bắt đầu trong project.
- **Project context (Ngữ cảnh project)** — đính kèm tệp hoặc đoạn văn bản (tối đa 50 mục) khả dụng cho mọi cuộc chat trong project.
- **Members (Thành viên)** — mời các thành viên workspace vào project và quản lý quyền truy cập của họ.

### Đổi tên và xóa

Sử dụng menu **…** trên thẻ project hoặc ở trang chi tiết project để _Edit_ (đổi tên hoặc cập nhật mô tả) hoặc _Delete_ project. Việc xóa sẽ xóa project và các cuộc trò chuyện ngay lập tức. Thao tác này không thể hoàn tác.

### Chia sẻ

Bạn có thể chia sẻ một bản chụp (snapshot) cuộc trò chuyện với đồng nghiệp hoặc công khai qua liên kết. Liên kết chia sẻ ghi lại cuộc trò chuyện tại thời điểm tạo — các tin nhắn mới sẽ không bao gồm cho đến khi bạn cập nhật liên kết.

### Cài đặt đối tượng chia sẻ

| Đối tượng | Ai có thể xem |
| --- | --- |
| Workspace only | Thành viên trong workspace hiện tại của bạn. |
| Organization | Bất kỳ ai đã đăng nhập vào tổ chức của bạn. |
| Public link | Bất kỳ ai có liên kết, không cần đăng nhập. |

### Cách chia sẻ

1. #### Mở hộp thoại chia sẻ

   Nhấp vào biểu tượng chia sẻ ở phần tiêu đề cuộc trò chuyện (hoặc menu ba chấm → _Share_). Hộp thoại hiển thị đối tượng hiện tại và liên kết.

![Hộp thoại chia sẻ với công tắc bao gồm tool activity và các lựa chọn đối tượng: thành viên workspace, tổ chức, hoặc bất kỳ ai có link](/manual/assets/product/chat-share-dialog-20260813.webp)
_Hộp thoại này là nơi chọn đối tượng và tạo link._

2. #### Chọn đối tượng

   Chọn _Workspace only_, _Organization_, hoặc _Public link_. Liên kết công khai ai cũng có thể xem — không chia sẻ các cuộc trò chuyện nhạy cảm ra công khai.

3. #### Tùy chọn: kèm hoạt động công cụ

   Bật _Show tool activity_ để kèm theo tên các bước công cụ trong bản chia sẻ (nội dung chi tiết của công cụ không bao giờ hiển thị). Hộp thoại sẽ cảnh báo nếu cuộc trò chuyện chứa tệp đính kèm, artifact, nguồn tri thức hoặc thông tin nhạy cảm.

4. #### Sao chép và chia sẻ liên kết

   Sao chép liên kết và gửi đi. Người nhận có thể đọc bản chụp tại địa chỉ `/share/c/:token` mà không cần tham gia workspace của bạn.

5. #### Cập nhật hoặc thu hồi

   _Update link_ làm mới bản chụp để bao gồm các tin nhắn mới. _Revoke link_ thu hồi quyền truy cập ngay lập tức — bất kỳ ai truy cập URL cũ sẽ thấy trang hết hạn.

> [!NOTE]
> Tạo bản sao
>
> Người nhận liên kết chia sẻ có thể nhấp vào _Create a copy_ để sao chép bản chụp vào workspace của chính họ và tiếp tục cuộc trò chuyện từ đó.

### Tích hợp

Tích hợp tài liệu không phải một route bắt buộc của giao diện Core. Chúng thuộc ứng dụng **Knowledge Base** và chỉ xuất hiện khi tổ chức đã cài app, workspace đã bật app, và cấu hình/provider tương ứng khả dụng.

Phạm vi: ứng dụng Knowledge Base

Thiết lập: phụ thuộc vai trò và cấu hình

Provider: phụ thuộc triển khai

1. #### Mở Knowledge Base

   Mở app Knowledge Base từ các bề mặt app của workspace. Nếu không thấy app hoặc connector, hỏi admin kiểm tra trạng thái cài đặt, bật app và cấu hình.

2. #### Kết nối hoặc nhập nguồn

   Chọn connector mà tenant của bạn cung cấp — một số connector dùng Composio hoặc OAuth của provider. Danh sách nhà cung cấp và các bước ủy quyền có thể khác nhau theo triển khai.

3. #### Xác nhận indexing

   Chờ nguồn hoàn tất đồng bộ/xử lý trong app rồi kiểm tra trạng thái tài liệu. Không coi tệp chat đính kèm là tài liệu Knowledge Base đã index.

4. #### Yêu cầu truy xuất rõ ràng

   Trong chat, chọn capability Knowledge Base hoặc yêu cầu rõ nguồn cần dùng. Việc có connector không đảm bảo mọi câu hỏi sẽ tự động tìm kiếm nguồn đó.

> [!WARNING]
> Không giả định connector cố định
>
> Google Drive, OneDrive, SharePoint hoặc connector khác chỉ dùng được khi app và provider của tenant thực sự cung cấp. Kiểm tra giao diện hiện tại thay vì dựa vào danh sách tĩnh.

### Server MCP

Server MCP cung cấp các **công cụ (tools)** cho trợ lý. Trong khi các kết nối Tích hợp giúp trợ lý _đọc_ tài liệu, các server MCP giúp trợ lý _thực hiện_ công việc — tạo issue Linear, đăng bài trên Slack, cập nhật cơ sở dữ liệu Notion, chạy truy vấn trong API nội bộ. "MCP" là viết tắt của _Model Context Protocol_: một tiêu chuẩn mở để cung cấp công cụ cho các agent AI.

Nhà cung cấp phổ biến: Linear · Notion · Slack · GitHub · Asana

Thiết lập: Workspace admin

Sử dụng: Tất cả thành viên workspace

### So sánh Tích hợp vs MCP — điểm khác biệt

| Khía cạnh | Tích hợp (Integration) | Server MCP |
| --- | --- | --- |
| Chức năng | Đưa tài liệu của hệ thống vào kho tri thức. | Cung cấp công cụ để trợ lý thao tác trong hệ thống. |
| Trợ lý có thể… | _Đọc_ nội dung và trích dẫn trong câu trả lời. | _Thực hiện_ công việc — tạo, cập nhật, truy vấn dữ liệu trực tiếp. |
| Mức độ mới của dữ liệu | Bản chụp từ lần đồng bộ cuối; đồng bộ lại để cập nhật. | Trực tiếp — trợ lý gọi hệ thống tại thời điểm đó. |
| Khi nào nên dùng | Bạn muốn câu trả lời dựa trên tài liệu của nhóm. | Bạn muốn trợ lý thực hiện công việc, không chỉ trả lời. |

### Dành cho Workspace Admin — đăng ký server MCP

1. #### Mở MCP servers

   Từ console của workspace chọn _MCP Servers_. Màn hình liệt kê từng server kết nối với workspace này.

![Màn hình MCP Servers: danh sách server đã kết nối bên trái, bên phải là server đang chọn kèm trạng thái, transport, kiểu auth và danh sách tool](/manual/assets/product/ws-mcp-20260813.webp)
_Danh sách hiển thị mọi server đã đăng ký; chọn một server để xem transport, auth và các tool nó cung cấp._

2. #### Thêm server

   Nhấn _Add server_. Điền **Name (Tên)**, **URL** HTTPS của server, và **Auth method (Phương thức xác thực)** (OAuth cho ứng dụng SaaS, API key cho công cụ nội bộ, hoặc None).

![Hộp thoại thêm MCP server với tên, URL server, transport và, trong mục Advanced, phương thức xác thực cùng prompt hint](/manual/assets/product/ws-mcp-add-20260813.webp)
_Thêm server cần URL và transport; xác thực và prompt hint nằm trong Advanced._

3. #### Xác thực

   Đối với server OAuth, nhấn _Connect_ và chấp thuận phạm vi truy cập trên trình duyệt. Đối với server API-key, dán key vào biểu mẫu — key được mã hóa và không bao giờ hiển thị lại.

4. #### Kiểm tra và làm mới công cụ

   Sử dụng nút _Test_ để xác nhận kết nối. Sau đó nhấn _Refresh tools_ để tải danh mục công cụ của server vào workspace.

5. #### Cho phép công cụ theo workspace

   Các công cụ tự động khả dụng cho trợ lý ngay khi server được kết nối. Bạn có thể giới hạn các công cụ bằng cách tắt chúng tại đây. Những công cụ bị tắt sẽ ẩn đối với trợ lý.

### Các server MCP phổ biến

| Server | Thao tác trợ lý có thể thực hiện | Xác thực |
| --- | --- | --- |
| Linear | Tạo / đọc / cập nhật issue, bình luận và người xử lý | OAuth |
| Notion | Tìm kiếm trang, tạo trang, cập nhật dòng trong database | OAuth |
| Slack | Gửi tin nhắn, đọc kênh, tìm kiếm người dùng | OAuth |
| GitHub | Đọc PR, để lại bình luận, tìm kiếm mã nguồn, chạy workflow | OAuth hoặc PAT |
| Asana | Liệt kê công việc, tạo công việc, cập nhật trạng thái | OAuth |
| HTTP MCP nội bộ | Bất kỳ chức năng nào đội ngũ kỹ sư của bạn cung cấp | API key |

> [!WARNING]
> Cân nhắc kỹ các công cụ cho phép
>
> Trợ lý sẽ sử dụng bất kỳ công cụ nào bạn cung cấp khi thấy hữu ích. Không bật các công cụ có tính phá hủy (delete, drop, force-push) trừ khi vai trò của trợ lý thực sự cần thiết.

## Admin Console

Dành cho Chủ sở hữu và Quản trị viên tổ chức — quản lý vai trò, workspace, ứng dụng, thành viên, API key và credit.

### Vai trò & Phân quyền

Quyền truy cập hoạt động trên hai cấp độ. **Vai trò tổ chức (Organization role)** xác định những gì bạn có thể làm trên toàn bộ tài khoản — thanh toán, thành viên và workspace. **Vai trò workspace (Workspace role)** xác định những gì bạn có thể làm bên trong một workspace cụ thể. Một người có thể giữ các vai trò khác nhau ở các workspace khác nhau, và chủ sở hữu cũng như quản trị viên tổ chức tự động có quyền quản trị trong mọi workspace thuộc tổ chức đó.

![Bảng thành viên tổ chức liệt kê từng người kèm vai trò và trạng thái](/manual/assets/product/console-participants-20260813.webp)
_Members liệt kê mọi người trong tổ chức và vai trò họ giữ._

### Vai trò trong tổ chức

| Vai trò | Quyền hạn |
| --- | --- |
| Owner (Chủ sở hữu) | Quản lý thành viên tổ chức, workspace, API key, app, security và các thao tác credit-policy được nêu dưới đây. Vai trò Owner không tự cấp quyền thay đổi subscription hoặc dòng tiền của nền tảng. |
| Admin (Quản trị viên) | Quản lý thành viên, workspace, app và nhiều thiết lập credit-policy. API vẫn kiểm tra quyền cho từng màn hình và thao tác; Admin không đồng nghĩa với toàn quyền billing. |
| Member (Thành viên) | Sử dụng các workspace được thêm vào. |

### Quyền subscription và credit

| Thao tác | Vai trò được phép |
| --- | --- |
| Xem tổng quan credit tổ chức | Mọi thành viên tổ chức |
| Tạo/sửa/xóa/gán credit package và rolling user limit | Organization Owner hoặc Admin |
| Xem Guest Credit add-on và quản lý phân bổ guest theo workspace | Organization Owner hoặc Admin |
| Nâng/hạ/hủy gói; top-up hoặc refund | Chỉ bên vận hành SotaAgents |
| Cấu hình pool/seat và vòng đời Guest Credit add-on | Chỉ bên vận hành SotaAgents |

### Vai trò trong workspace

![Tab Participants của workspace liệt kê thành viên và vai trò trong workspace](/manual/assets/product/ws-participants-20260813.webp)
_Vai trò workspace được thiết lập riêng trên chính workspace đó._

| Vai trò | Quyền hạn |
| --- | --- |
| Workspace Admin (`WS_ADMIN`) | Quản lý cài đặt workspace, thành viên, ứng dụng, tích hợp, guardrail và server MCP. |
| Workspace Member (`WS_MEMBER`) | Sử dụng workspace và các capability khả dụng. Không đổi được thành viên, app hay cài đặt workspace. Giá trị Editor/Chatter cũ được compatibility-map về vai trò này. |

> [!NOTE]
> Chủ sở hữu & Admin tổ chức tự động có quyền quản trị workspace
>
> Nếu bạn là Chủ sở hữu hoặc Admin của tổ chức, bạn có quyền Workspace Admin ở mọi workspace trong tổ chức — không cần tự gửi lời mời cho chính mình.

### Tổng quan Console

Admin Console (`/console`) là nơi chủ sở hữu và quản trị viên tổ chức quản lý nền tảng. Nơi này tách biệt với giao diện chat workspace và yêu cầu vai trò admin hoặc owner của tổ chức để truy cập hầu hết các tính năng.

![Danh sách tổ chức trong Admin Console, hiển thị gói và vai trò của bạn](/manual/assets/product/console-orgs-20260813.webp)
_Console mở ở danh sách tổ chức; mỗi card hiển thị gói và vai trò của bạn._

![Trang cài đặt tổ chức với nhận diện và cấu hình quản trị](/manual/assets/product/console-settings-20260813.webp)
_Cài đặt tổ chức chứa nhận diện và các chính sách mà vai trò hiện tại được phép quản lý._

Console được tổ chức xoay quanh **Tổ chức (Organization)** của bạn. Từ màn hình chi tiết tổ chức, bạn có thể điều hướng đến:

- **Dashboard** — tổng quan người dùng, workspace, AI response và mức dùng credit/model
- **Workspaces** — tạo, quản lý và cấu hình các workspace
- **Apps** — cài đặt và quản lý các tiện ích mở rộng từ App Store
- **Participants** — quản lý thành viên tổ chức và vai trò
- **API Keys** — thông tin xác thực cho truy cập giữa các hệ thống (machine-to-machine)
- **Activity** — nhật ký hoạt động cho toàn bộ tổ chức
- **Credits** — sử dụng credit, phân bổ, cảnh báo, log và Guest Credits theo quyền
- **Chat Models** — kiểm soát các model khả dụng và đặt model mặc định cho tổ chức
- **Security** — cài đặt bảo mật cấp tổ chức
- **Settings** — tên tổ chức và cấu hình chung

### Bảng điều khiển

Bảng điều khiển của tổ chức cung cấp cho bạn bức tranh tổng thể theo thời gian thực về hoạt động trên nền tảng ngay trong console.

![Dashboard tổ chức với thẻ thành viên, workspace và AI response, biểu đồ dùng credit, credit theo người dùng và xu hướng](/manual/assets/product/console-dashboard-20260813.webp)
_Dashboard trả lời tổ chức đang làm gì: thành viên, workspace, response và credit đã đi đâu._

### Ba thẻ KPI

Total Users, Workspaces và AI Responses tóm tắt tổ chức hiện tại.

### Mức dùng credit

Biểu đồ theo thời gian và theo người dùng cho biết credit đang được tiêu thụ ở đâu.

### Mức dùng model

Bảng xếp hạng model cho biết các chat model được cấp quyền chiếm bao nhiêu mức sử dụng.

### Xu hướng

Các thẻ xu hướng người dùng và AI response thể hiện thay đổi trong khoảng thời gian chọn. Dashboard hiện tại không cam kết có activity feed gần đây.

### Workspace

Thẻ Workspaces liệt kê mọi workspace trong tổ chức. Từ đây bạn có thể tạo workspace mới hoặc nhấp vào một workspace hiện có để cấu hình.

![Tab Workspaces liệt kê các workspace của tổ chức kèm slug và ngày tạo](/manual/assets/product/console-workspaces-20260813.webp)
_Mỗi workspace là một context tách biệt với app, integration và lịch sử chat riêng._

Dùng nút _Grid / List_ phía trên danh sách để chuyển giữa card trực quan và các dòng gọn. Tùy chọn này chỉ thay đổi cách hiển thị.

![Trang Workspaces của tổ chức ở List view với nút chuyển Grid và List](/manual/assets/product/console-workspaces-list-20260903.webp)
_List view trình bày identity, slug, mô tả và ngày tạo của workspace theo các dòng gọn._

### Tạo workspace mới

1. #### Nhấn Create workspace

   Từ thẻ Workspaces, nhấn nút _Create workspace_. Nhập tên và mô tả (tùy chọn).

2. #### Cấu hình

   Sau khi tạo xong, mở workspace để quản lý Apps, MCP Servers, Participants, Guardrails, Activity, Feedback, Configure Embed và Settings theo vai trò.

### Các thẻ cấp Workspace

![Tab Apps của workspace hiển thị app demo đã cài ở tổ chức và control quyền truy cập theo workspace](/manual/assets/product/ws-apps-config-20260813.webp)
_App cài ở tổ chức mặc định enabled trong mọi workspace; tab này chứa override tắt hoặc bật lại theo workspace._

| Thẻ | Chức năng |
| --- | --- |
| MCP Servers | Kết nối và quản lý các server công cụ MCP cho workspace này. |
| Apps | Xem ứng dụng đã cài ở cấp tổ chức và tạo override bật/tắt cho workspace này; mặc định app được bật. |
| Participants | Quản lý thành viên trong phạm vi workspace và gán vai trò workspace. |
| Guardrails | Cấu hình và xem các biện pháp bảo vệ input và tool output. |
| Activity | Xem nhật ký hoạt động của workspace và xuất ra CSV. |
| Feedback | Xem lại phản hồi của người dùng được gửi từ các cuộc trò chuyện trong workspace. |
| Configure Embed | Cấu hình, preview, publish hoặc unpublish public guest deployment. |

![Trang Feedback của workspace liệt kê điểm đánh giá và bình luận từ các cuộc trò chuyện](/manual/assets/product/ws-feedback-20260813.webp)
_Feedback tập trung điểm đánh giá và bình luận để quản trị viên workspace xem xét._

### Public Deployment (Configure Embed)

Owner/Admin tổ chức có quyền workspace có thể đặt allowed origins, chọn chat model, tùy chỉnh giao diện và lead form tùy chọn, rồi preview bề mặt app/tool dành cho guest. Publish tạo public deployment; unpublish ngăn phiên guest mới mà không cần đặt organization API key trong trình duyệt.

- Public-chat boundary kiểm tra origin, session và IP.
- Guest conversation dùng phân bổ workspace từ Guest Credit pool riêng.
- Luôn preview các app/tool được cấp và cảnh báo phân bổ trước khi publish.

![Trang Configure Embed cho public deployment của workspace](/manual/assets/product/ws-embed-20260813.webp)
_Cấu hình, preview, publish và unpublish guest deployment tại một nơi._

> [!NOTE]
> Gợi ý thao tác nhanh
>
> Danh sách workspace hiển thị các gợi ý thao tác nhanh theo ngữ cảnh — ví dụ: _Add datasource_, _Invite member_, _Try assistant_ — giúp thực hiện công việc chỉ bằng một cú nhấp chuột.

### Cài đặt workspace

Từ màn hình chi tiết workspace, mở _Settings_ (ở cuối thanh bên workspace) để đổi tên hoặc xóa workspace. Việc xóa được thực hiện qua hộp thoại xác nhận — sau khi xóa, dữ liệu workspace không thể khôi phục.

![Trang cài đặt workspace với nhận diện, slug, mô tả, tag, system prompt và định dạng phản hồi](/manual/assets/product/ws-settings-20260813.webp)
_Cài đặt workspace mở trong một drawer, không phải một trang riêng._

### Cài đặt ứng dụng

Ứng dụng là các tiện ích mở rộng bổ sung năng lực chuyên biệt cho tổ chức. Quản trị viên quản lý chúng từ thẻ **Apps** trong Admin Console (_Chi tiết tổ chức → Apps_).

![App Store của tổ chức với các tab Installed, Catalog, Recovery và app contract-probe được seed local](/manual/assets/product/console-apps-20260813.webp)
_Catalog thật phụ thuộc deployment; ảnh local này dùng app contract-probe của TestStack._

> [!NOTE]
> Cài ở tổ chức, override theo workspace
>
> **Cấp tổ chức:** cài một exact app environment cho tổ chức. **Cấp workspace:** mọi workspace mặc định kế thừa trạng thái enabled; admin chỉ tạo override tắt cho workspace không được dùng app và có thể bật lại sau.

### Ví dụ ứng dụng

Catalog phụ thuộc deployment và tổ chức. Tên cùng số contribution dưới đây chỉ là ví dụ từ một artifact đã chụp, không phải inventory cố định; trang chi tiết app hiện tại mới là nguồn chuẩn.

| Ứng dụng | Tính năng bổ sung | Thành phần |
| --- | --- | --- |
| Knowledge Base | Kho tài liệu dùng chung với tìm kiếm lai (ngữ nghĩa + toàn văn) và trích dẫn nguồn. Tải tài liệu nội bộ lên để trợ lý trả lời dựa trên bằng chứng. Công cụ chính: `hybridSearch`, `searchDocuments`, `structuredQuery/Sql`, quản lý tệp. | 19 Công cụ · 1 Năng lực · 2 Slot UI |
| Office App | Tạo và chỉnh sửa tài liệu Word, bảng tính Excel, bài trình chiếu PowerPoint/PDF và dashboard trong môi trường sandbox. Bao gồm trình chỉnh sửa slide tự động kiểm tra chất lượng. | 11 Công cụ · 1 Năng lực · 1 Slot UI |
| Web Search | Tìm kiếm web công khai theo thời gian thực và trích xuất nội dung trang. Công cụ chính: `webSearch`, `fetchWebPage` (tối đa ~50 000 ký tự mỗi URL). | 3 Công cụ · 1 Năng lực · 1 Slot UI |
| CAD | Quy trình CAD/BIM cho ngành xây dựng — nạp tệp PDF/DWG/DXF/IFC, OCR theo bố cục, Hỏi & Đáp kèm trích dẫn trang, tự động đếm ký hiệu, tính diện tích và xem mô hình BIM tương tác. | 5 Công cụ · 2 Năng lực · 7 Slot UI |
| Remagine | Tạo video ngắn bằng AI: trợ lý viết mã Remotion (React/TSX) trong workspace từng video, xem trước trực tiếp qua esbuild và render MP4 qua Remotion Lambda. | 11 Công cụ · 1 Năng lực · 1 Slot UI |

### Giao diện App Store

Thẻ Apps có 4 chế độ xem:

- **All** — tất cả ứng dụng khả dụng cho tổ chức của bạn.
- **Installed** — các ứng dụng đang hoạt động.
- **Catalog** — danh mục ứng dụng bạn có thể thêm.
- **Recovery** — ứng dụng đã gỡ tạm thời. Ứng dụng đã xóa nằm ở đây trong 30 ngày trước khi bị xóa vĩnh viễn, bạn có thể khôi phục khi cần.

### Cài đặt một ứng dụng

1. #### Mở thẻ Catalog

   Truy cập _Console → Tổ chức của bạn → Apps → Catalog_.

2. #### Cài đặt

   Mở card của exact environment Development, Staging hoặc Production, xem version và contribution rồi nhấn _Install_. Environment được cài cho tổ chức và mọi workspace kế thừa trạng thái enabled.

![Chi tiết exact Production environment của app contract-probe local với version, trạng thái và surface overview](/manual/assets/product/console-app-detail-20260813.webp)
_Trang chi tiết luôn pin vào một exact environment và hiển thị trạng thái cài hiện tại._

3. #### Kiểm tra quyền theo workspace

   Đi tới màn hình chi tiết workspace → thẻ _Apps_. App mới cài mặc định đã enabled. Chỉ tắt ở workspace không được phép dùng app; bật lại để gỡ hạn chế đó.

![Tab Apps config của workspace hiển thị app demo đã cài và trạng thái quyền kế thừa](/manual/assets/product/ws-apps-config-20260813.webp)
_Không có override nghĩa là enabled; control tạo hoặc đổi override của workspace._

4. #### Gỡ cài đặt

   Nhấn _Uninstall_ ở thẻ Installed. Ứng dụng chuyển sang Recovery trong 30 ngày. Trong khoảng thời gian đó bạn có thể khôi phục lại. Sau 30 ngày ứng dụng sẽ bị xóa vĩnh viễn.

> [!WARNING]
> Mức tiêu thụ credit của ứng dụng
>
> Mỗi ứng dụng tiêu tốn credit khi sử dụng. Kiểm tra thẻ Credit Logs để xem mức tiêu thụ của từng ứng dụng và đặt cảnh báo credit ở cấp workspace nếu bạn cần giới hạn chi tiêu.

### Mời thành viên

Mời đồng nghiệp tại _Console → Tổ chức của bạn → Participants → Invite_.

1. #### Gửi lời mời

   Nhập email của người đó và chọn vai trò tổ chức (Member hoặc Admin). Tùy chọn thêm họ vào một workspace và thiết lập vai trò workspace cùng lúc.

![Panel mời trên trang thành viên với địa chỉ email đã nhập và vai trò đã chọn, sẵn sàng gửi](/manual/assets/product/console-invite-form-20260813.webp)
_Nhập địa chỉ, chọn vai trò trong tổ chức, gửi._

2. #### Người được mời chấp nhận

   Họ nhận được email kèm liên kết. Nhấp vào liên kết sẽ yêu cầu đăng nhập hoặc đăng ký, sau đó tự động tham gia vào tổ chức và bất kỳ workspace nào bạn đã chỉ định.

3. #### Gửi lại hoặc xóa

   Cho tới khi được chấp nhận, người được mời xuất hiện với trạng thái _Không hoạt động (Inactive)_ trong bảng Người tham gia. Mở menu thao tác của họ để sao chép liên kết mời, gửi lại lời mời hoặc xóa lời mời gửi nhầm.

![Bảng Người tham gia với menu thao tác của người được mời Không hoạt động đang mở, hiển thị Sao chép liên kết mời, Gửi lại lời mời và Xóa](/manual/assets/product/console-invite-pending-20260813.webp)
_Hàng Không hoạt động tồn tại cho tới khi lời mời được chấp nhận hoặc bị xóa; mọi thao tác với lời mời đang chờ đều nằm trong menu này._

### API key

API key xác thực các tích hợp machine-to-machine đã được phê duyệt. Public Deployment _Configure Embed_ tích hợp sẵn không dùng organization API key trong trình duyệt; nó đổi publish/embed identity lấy guest session bị giới hạn. Nếu chỉ dùng chat hoặc widget tích hợp sẵn, không tạo key cho mục đích đó.

1. #### Mở API Keys

   Truy cập _Console → Tổ chức của bạn → API Keys_ ở thanh bên trái.

![Màn hình API Keys liệt kê các key kèm public key, scope, trạng thái, lần dùng cuối và ngày tạo](/manual/assets/product/console-api-keys-20260813.webp)
_Mỗi key hiển thị scope và đã từng được dùng hay chưa._

2. #### Tạo key

   Nhấn _Create API Key_ (góc trên bên phải). Hộp thoại mở ra — nhập **Tên (Name)** tùy chọn (ví dụ: "Môi trường Production") để nhận biết sau này, sau đó nhấn _Create_.

![Hộp thoại Create API Key với tên đã nhập](/manual/assets/product/console-api-key-dialog-20260813.webp)
_Tên chỉ là nhãn cho bạn; nó không ảnh hưởng tới quyền của key._

3. #### Lưu ngay key và secret

   Màn hình "API Key created" **chỉ hiển thị duy nhất một lần**. Màn hình này hiển thị cả **API Key** (dạng: `sota_ek_…`) và **API Secret**. Hãy sao chép cả hai ngay bây giờ và lưu trữ an toàn — bạn không thể lấy lại secret sau khi đóng màn hình này.

![Hộp thoại hiện một lần sau khi tạo, với API key, secret đã che và cảnh báo secret sẽ không hiện lại](/manual/assets/product/console-api-key-created-20260813.webp)
_Secret chỉ hiện một lần. Hãy copy trước khi đóng hộp thoại._

4. #### Xác nhận và đóng

   Sau khi lưu, nhấn _I have saved the Secret_ để đóng hộp thoại.

5. #### Sử dụng key trong lời gọi API

   API key được dùng để nhúng SotaAgents vào các sản phẩm bên ngoài hoặc tích hợp với các hệ thống nội bộ. Công dụng chính là ký handshake cấp ticket — xem [Nhúng SotaAgents vào hệ thống nội bộ](/vi/manual/enterprise-integration/embedding-in-your-systems). Với widget công khai cho khách ẩn danh, hãy dùng _Workspace → Configure Embed_.

6. #### Vô hiệu hóa key

   Để ngừng sử dụng một key mà không xóa nó, nhấp vào nút _Actions_ trên dòng tương ứng và chọn _Disable_. Xác nhận trong hộp thoại. Key dừng hoạt động ngay lập tức và vẫn hiển thị ở trạng thái bị vô hiệu hóa trong danh sách.

> [!WARNING]
> Bảo mật API key
>
> Tuyệt đối không đưa API key vào mã nguồn (source control) hoặc chia sẻ trong tin nhắn. Hãy bảo vệ key như mật khẩu. Nếu key bị lộ, hãy vô hiệu hóa ngay và tạo key mới.

### Nhật ký hoạt động

Nhật ký hoạt động cung cấp cho quản trị viên hồ sơ đầy đủ về ai đã làm gì và vào lúc nào — ở cả cấp tổ chức và cấp từng workspace. Nhật ký chỉ đọc và có thể xuất ra CSV.

### Nội dung được ghi nhật ký

| Cột | Mô tả |
| --- | --- |
| Time (Thời gian) | Dấu thời gian diễn ra hành động. |
| Actor (Người thực hiện) | Người dùng hoặc hệ thống thực hiện hành động. |
| Action (Hành động) | Loại sự kiện: _create_ (tạo), _update_ (cập nhật), _delete_ (xóa), hoặc _execute_ (thực thi). |
| Target (Mục tiêu) | Đối tượng chịu tác động — ứng dụng, nguồn dữ liệu, tích hợp, thành viên, hoặc cuộc trò chuyện. |
| Details (Chi tiết) | Nhấp để mở rộng dữ liệu chi tiết của sự kiện, bao gồm ngữ cảnh cuộc trò chuyện và dữ liệu có cấu trúc. |

### Truy cập nhật ký

![Nhật ký hoạt động tổ chức với bộ lọc loại hành động, thống kê tổng quan và các dòng sự kiện](/manual/assets/product/console-activity-20260813.webp)
_Nhật ký cấp tổ chức bao quát mọi workspace; bộ lọc giúp thu hẹp theo loại hành động._

- **Cấp tổ chức** — truy cập _Console → Tổ chức của bạn → Activity_. Hiển thị tất cả sự kiện trên mọi workspace trong tổ chức.
- **Cấp workspace** — truy cập _Console → Tổ chức của bạn → Workspaces → [workspace] → Activity_. Chỉ nằm trong phạm vi workspace đó.

### Lọc và xuất dữ liệu

Sử dụng ô tìm kiếm, bộ lọc người thực hiện, bộ lọc hành động và bộ chọn khoảng thời gian để thu hẹp nhật ký. Nhấn _Export CSV_ để tải xuống chế độ xem hiện tại.

![Nhật ký hoạt động workspace với dải thống kê, biểu đồ sự kiện và các dòng theo hội thoại](/manual/assets/product/ws-activity-20260813.webp)
_Nhật ký workspace giới hạn cùng loại hồ sơ trong một workspace._

### Quản lý Credit

SotaAgents tính phí sử dụng theo _credit_ ở cấp tổ chức. Màn hình hiện có năm thẻ: Credit Allocation, Member Allocation, Credit Alerts, Credit Logs và Guest Credits khi vai trò/deployment được cấp quyền.

![Màn hình Credits ở tab Credit Allocation, hiển thị gói, cấu hình seat và đơn vị credit cơ bản](/manual/assets/product/console-credits-20260813.webp)
_Credit được quản lý cho cả tổ chức: pool, seat và các giới hạn._

### Credit Allocation (Phân bổ Credit)

Phần đầu thẻ hiển thị dải KPI tóm tắt tình trạng credit của tổ chức. Nội dung hiển thị tùy thuộc vào gói dịch vụ:

- **Business / Enterprise (dùng chung):** Tổng quỹ (Pool total), Đã dùng (Consumed), Còn lại (Remaining), và Số chỗ ngồi (Seats count). Thanh tiêu thụ hiển thị mức sử dụng so với hạn mức chung của tổ chức.
- **Các gói khác (theo chỗ ngồi):** Seats, Base unit (Đơn vị cơ sở), Billing cycle (Chu kỳ thanh toán), và Reset day (Ngày đặt lại).

Phía dưới dải KPI, danh sách thanh mức sử dụng hiển thị top 10 thành viên tiêu thụ nhiều nhất. Các thanh được mã hóa màu: xanh lá (trong hạn mức), hổ phách (sắp đạt giới hạn), đỏ (bị chặn / vượt hạn mức).

Các trường **Reset day** (ngày chốt sổ, từ 1–28) và **Seats** chỉ có thể chỉnh sửa bởi bên vận hành SotaAgents — Chủ sở hữu hoặc Admin tổ chức không thể tự sửa.

#### Credit packages (Gói credit - chỉ áp dụng Business và Enterprise)

Ở các gói Business và Enterprise, chủ sở hữu và admin tổ chức có thể tạo các gói credit có tên — mỗi gói được xác định bằng hệ số nhân áp dụng cho đơn vị cơ sở của tổ chức (đơn vị cơ sở phụ thuộc gói dịch vụ, không thể sửa). Một gói được đánh dấu là mặc định và tự động gán cho thành viên mới. Cột _Members_ cho biết số người đang dùng gói. Không thể xóa gói còn thành viên; hãy gán họ sang gói khác trước. Việc gán hiện tại vẫn giữ nguyên khi hủy kích hoạt gói.

![Bảng Credit Packages hiển thị hệ số, credit, số thành viên, trạng thái và nút xóa bị khóa với gói đang được gán](/manual/assets/product/console-credit-packages-members-20260903.webp)
_Cột Members cho biết vì sao một gói đang được gán không thể xóa._

#### Rolling 5-hour limit (Giới hạn trượt 5 giờ)

Có thể chỉnh sửa bởi chủ sở hữu và admin tổ chức. Đặt số credit tối đa mà một người dùng có thể tiêu thụ trong bất kỳ khoảng thời gian trượt 5 giờ nào. Để trống nếu không giới hạn. Khi thành viên chạm giới hạn, các yêu cầu tiếp theo sẽ bị chặn cho đến khi khoảng thời gian trượt dịch chuyển tiếp. Hỗ trợ trên tất cả các gói dịch vụ.

### Member Allocation (Phân bổ thành viên)

Bảng phân trang tất cả thành viên tổ chức hiển thị số credit đã dùng, credit được phân bổ, số dư còn lại và thanh tiến trình. Các dòng tô đỏ là vượt hạn mức (đã dùng vượt quá phân bổ).

![Tab Member Allocation hiển thị từng thành viên kèm mức dùng credit và số dư còn lại](/manual/assets/product/console-credits-allocation-20260813.webp)
_Member Allocation cho thấy mỗi người đã dùng bao nhiêu và còn lại bao nhiêu._

- **Tìm kiếm và lọc** theo tên, vai trò (Owner / Admin / Member) hoặc gói credit.
- **Gán gói credit** cho thành viên (Business và Enterprise, Chủ sở hữu hoặc Admin) — ghi đè gói mặc định cho người dùng đó.

![Member Allocation với bộ lọc gói credit đang mở phía trên bảng thành viên](/manual/assets/product/console-credit-package-filter-20260903.webp)
_Lọc theo package giúp xem nhanh mọi thành viên dùng cùng chính sách phân bổ._

### Credit Alerts (Cảnh báo Credit)

Cấu hình cảnh báo credit theo workspace để admin nhận email khi mức sử dụng vượt ngưỡng. Cảnh báo kích hoạt dựa trên khoảng thời gian trượt tùy chỉnh (1–24 giờ).

1. #### Chọn workspace

   Truy cập _Console → Tổ chức của bạn → Credits → Credit Alerts_ và chọn workspace cần cấu hình từ danh sách thả xuống.

2. #### Đặt khoảng thời gian theo dõi

   Nhập giá trị _Window (hours)_ từ 1 đến 24. Cảnh báo sẽ kiểm tra mức tiêu thụ credit trong khoảng thời gian trượt này.

3. #### Đặt ngưỡng

   Bật ngưỡng _Warning_ (Cảnh báo), ngưỡng _Critical_ (Nghiêm trọng), hoặc cả hai. Nhập số credit cho mỗi ngưỡng. Ngưỡng nghiêm trọng phải lớn hơn ngưỡng cảnh báo.

4. #### Thêm người nhận phụ (tùy chọn)

   Nhập các địa chỉ email bổ sung (phân cách bằng dấu phẩy hoặc xuống dòng, tối đa 10 email) để nhận thông báo ngoài các admin mặc định của workspace.

5. #### Lưu

   Nhấn _Save_. Email cảnh báo sẽ gửi đi khi workspace vượt quá ngưỡng trong khoảng thời gian đã cấu hình.

### Credit Logs (Nhật ký Credit)

Nhật ký ghi lại mọi giao dịch credit (chỉ thêm, không sửa xóa). Lọc theo khoảng thời gian (30 ngày / 90 ngày / tùy chỉnh), nhà cung cấp (chat, Knowledge Base, Office App, Web Search, CAD, Remagine, sandbox…), người dùng, hoặc tìm kiếm tự do. Xuất ra CSV. Chỉ hiển thị với Admin và Owner tổ chức.

Mỗi dòng ghi lại: dấu thời gian, người dùng, nhà cung cấp, số credit đã trừ, chi phí USD và số dư sau giao dịch.

### Guest Credits

Guest Credits là pool riêng cho Public Deployment. Owner/Admin có thể xem add-on và phân bổ credit cho workspace; bên vận hành SotaAgents quản lý vòng đời add-on và cấu hình pool. Khi hết phân bổ, guest session mới hoặc lượt chat có thể bị từ chối cho tới khi có credit.

### Model trò chuyện

Thẻ **Chat Models** (_Console → Tổ chức của bạn → Chat Models_) kiểm soát các AI model khả dụng trong toàn tổ chức và thiết lập model mặc định.

![Tab Chat Models hiển thị mặc định của tổ chức, số model đã chọn, bộ lọc provider và các dòng model kèm hệ số credit](/manual/assets/product/console-chat-models-20260813.webp)
_Chat Models quyết định tổ chức mở những model nào và model nào là mặc định._

### Khả dụng (Availability)

Dùng allow-list của tổ chức để kiểm soát model thành viên được chọn. Model bị giới hạn bởi nền tảng không xuất hiện trong chat picker. Owner/Admin đủ quyền có thể gửi hoặc hủy yêu cầu truy cập tại đây; chỉ grant được duyệt mới làm model khả dụng. Owner và Admin tổ chức nhận email khi quyền truy cập được cấp.

### Model mặc định tổ chức

Đánh dấu một model làm mặc định cho tổ chức — model này sẽ được chọn sẵn trong khung chat cho mọi thành viên chưa tự ghim một model khác.

> [!NOTE]
> Lưu trước khi rời đi
>
> Các thay đổi chỉ có hiệu lực sau khi bạn nhấn _Save_. Thanh lưu sẽ xuất hiện ở cuối trang khi có chỉnh sửa chưa lưu.

### Bảo mật

Thẻ **Security** cho phép vai trò được cấp cấu hình Enterprise SSO và **IP Access**. Tính năng vẫn chịu điều kiện gói và vai trò.

### IP Access

Thêm dải IPv4 CIDR tin cậy rồi bật policy IP của tổ chức. Request từ địa chỉ ngoài mọi rule đang hoạt động bị từ chối với `ORG_IP_DENIED`; workspace hiển thị thông báo chặn cố định. Hãy chuyển sang mạng được phép hoặc nhờ admin sửa rule.

> [!WARNING]
> Yêu cầu gói Enterprise
>
> Cấu hình hoặc bắt buộc SSO yêu cầu gói đăng ký Enterprise Cloud. Bạn vẫn có thể xóa nhà cung cấp hiện có hoặc tắt bắt buộc SSO trên mọi gói dịch vụ.

### Giao thức SSO

Chọn một trong hai giao thức:

| Giao thức | Thông tin cần cung cấp |
| --- | --- |
| OIDC | Issuer URL, Client ID, Client Secret. Tùy chọn Custom Discovery Endpoint (mặc định `issuer/.well-known/openid-configuration`). Sao chép _Callback URL_ hiển thị trên biểu mẫu vào phần allowed redirect URIs của IdP. |
| SAML | IdP Entry Point URL, IdP Issuer, và chứng chỉ IdP (PEM). Tùy chọn dán XML metadata của IdP. Sao chép _ACS URL_, _SP Entity ID_, và _SP Metadata URL_ trên biểu mẫu vào IdP của bạn. |

> [!NOTE]
> Bảo mật thông tin xác thực
>
> Client Secret (OIDC) và chứng chỉ (SAML) không bao giờ hiển thị lại sau khi lưu — biểu mẫu chỉ hiển thị thông báo đã được thiết lập hay chưa. Nhập lại giá trị mới để thay thế.

### Tên miền Email (Email domains)

Thêm các tên miền email tổ chức sử dụng (ví dụ: `company.com`). Tên miền phải được xác minh trước khi có thể bật bắt buộc SSO.

#### Phương thức xác minh

| Phương thức | Cách hoạt động |
| --- | --- |
| DNS TXT | Mã xác minh (token) được tạo khi bạn thêm tên miền. Tạo bản ghi DNS TXT tại `_sotaagents-sso-verify.<domain>` chứa token đó, sau đó nhấn _Verify DNS_. |
| Thủ công (Manual) | Bên vận hành SotaAgents sẽ xem xét và phê duyệt hoặc từ chối yêu cầu tên miền. |

### Kiểm tra SSO (Test SSO)

Nhấn **Test SSO** để thực hiện kiểm tra kết nối với IdP đã cấu hình. Kết quả hiển thị trực tiếp — quá trình kiểm tra kết nối phải thành công trước khi có thể bật bắt buộc SSO.

### Bắt buộc SSO (Enforce SSO)

Nút bật/tắt _Enforce SSO_ yêu cầu các thành viên phải xác thực qua IdP đã cấu hình. Nút này chỉ có thể bật khi:

- Đã lưu thông tin nhà cung cấp dịch vụ (IdP).
- Tối thiểu một tên miền đã được xác minh.
- Bài kiểm tra kết nối SSO thành công cho cấu hình hiện tại.

> [!WARNING]
> Bị khóa quyền truy cập?
>
> Nếu tính năng bắt buộc SSO đang bật nhưng IdP bị cấu hình sai, hãy liên hệ hỗ trợ. Bên vận hành SotaAgents có thể tắt bắt buộc SSO qua điểm cuối khôi phục để khôi phục quyền truy cập.

### Guardrails

Guardrails chặn prompt injection/jailbreak, che dữ liệu cá nhân và kiểm tra tool/document output. Mở trực tiếp mục **Guardrails** trong điều hướng workspace; chỉ vai trò được cấp quyền mới thấy.

### Guardrails kiểm tra những gì

Guardrail Hub có hai lớp do workspace điều khiển. Core còn áp dụng một lớp bảo vệ reasoning do nền tảng quản lý:

| Chọn mode ở mục | Kiểm tra cái gì | Ý nghĩa |
| --- | --- | --- |
| _User input enforcement_ | Tin nhắn của bạn | Nội dung người dùng nhập, kiểm tra trước khi trợ lý trả lời. Dữ liệu cá nhân (email, số điện thoại) cũng có thể được che ở đây. |
| _Tool output enforcement_ | Nội dung tool & tài liệu | Văn bản trợ lý lấy về từ tool, tệp tải lên, web hoặc kho tri thức — kiểm tra trước khi dùng. |
| _Model reasoning_ | Reasoning trước khi gửi tới trình duyệt hoặc lưu trữ | Lớp do nền tảng quản lý phát hiện và che secret/dữ liệu cá nhân trước khi reasoning được stream hoặc lưu. Đây không phải mode để workspace chọn. |

### Mỗi chế độ làm gì

Bạn chọn chế độ riêng cho _User input enforcement_ và _Tool output enforcement_. Đây là hành động của từng chế độ trong cả hai trường hợp:

| Chế độ | User input enforcement | Tool output enforcement |
| --- | --- | --- |
| Off | Không chạy các kiểm tra input do workspace chọn. | Không chạy các kiểm tra tool output do workspace chọn. |
| Observe (chỉ log) | Ghi lại vào log; không thay đổi hay chặn gì. | Ghi lại vào log; không thay đổi hay chặn gì. |
| Redact (che) | Che dữ liệu cá nhân (vd email) trước khi trợ lý đọc. | Che phần bị khớp nếu có thể, rồi cho nội dung tiếp tục. |
| Enforce (chặn) | Chặn tin nhắn rủi ro (vd toan tính jailbreak); dữ liệu cá nhân vẫn được che. | Thông thường sẽ giữ lại nội dung không an toàn và hiển thị thông báo. Kết quả tool của app Knowledge Base chỉ được áp dụng tối đa mức soft: có thể log hoặc redact, nhưng tài liệu đã lấy về không bị chặn. |

> [!NOTE]
> Bảo vệ của nền tảng vẫn áp dụng
>
> Công tắc master và mode _Off_ chỉ tắt các kiểm tra do workspace chọn. Guard bắt buộc của nền tảng vẫn có thể chạy và được ghi là _Enforced by the platform_ trong Hub.

### Bật guardrails cho một workspace

1. #### Mở Guardrail Hub

   Mở workspace trong Console và chọn _Guardrails_ từ sidebar. Nếu không thấy, vai trò hiện tại không có quyền xem.

![Tab Guardrails hiển thị lớp bắt buộc của nền tảng, che Model reasoning, User input ở Enforce, Tool result ở Redact và hai trên mười một guard được chọn](/manual/assets/product/ws-guardrails-20260813.webp)
_Hub tách bảo vệ bắt buộc của nền tảng khỏi control của workspace; ví dụ này enforce input, redact tool result và chọn 2/11 guard._

2. #### Bật công tắc master

   Bật _Enable guardrails for this workspace_. Công tắc này điều khiển các guard do workspace chọn; bảo vệ bắt buộc của nền tảng vẫn hoạt động.

3. #### Chọn mức độ nghiêm ngặt

   Đặt cách guardrails xử lý với _tin nhắn của bạn_ và với _nội dung tool trả về_ (Observe, Redact hoặc Enforce). Có dòng chú thích dưới mỗi lựa chọn giải thích ý nghĩa.

4. #### Chọn guard chạy

   Tick các guard muốn dùng cho workspace; tìm hoặc lọc để chọn. Không chỉnh gì thì giữ nguyên bộ mặc định được khuyến nghị. Một vài guard cần bạn nhập giá trị (danh sách từ cấm, tên đối thủ, hoặc chủ đề cho phép) — phải nhập thì mới lưu được.

5. #### Lưu

   Nhấn _Save_. Áp dụng ngay cho tin nhắn mới. Dùng _Export rules (CSV)_ để tải cấu hình hiện tại.

### Các guard có sẵn

Các guard ghi _Khuyến nghị_ được bật sẵn. Với ba guard cuối, bạn cung cấp danh sách để nó đối chiếu.

![Recent blocks liệt kê các sự kiện blocked, redacted, shadow và Model reasoning cùng stage, guard và preview đã che](/manual/assets/product/ws-guardrails-events-20260813.webp)
_Recent blocks ghi nhận phát hiện ở input, tool result và model reasoning mà không lộ văn bản nhạy cảm._

| Guard | Chống lại | Bạn cung cấp |
| --- | --- | --- |
| Jailbreak / Prompt Injection | Hành vi lừa hoặc chiếm quyền trợ lý, gồm cả lệnh ẩn giấu trong tài liệu | Không cần (Khuyến nghị) |
| Secrets & Credentials | API key, mật khẩu, token trong tin nhắn | Không cần (Khuyến nghị) |
| Personal Data (PII) | Email, số điện thoại và dữ liệu cá nhân khác (được che) | Không cần (Khuyến nghị) |
| Toxic Language | Ngôn từ độc hại, thù ghét hoặc quấy rối | Không cần |
| Profanity | Từ ngữ thô tục | Không cần |
| NSFW Text | Nội dung khiêu dâm / không phù hợp nơi làm việc | Không cần |
| Toxic Language (Đa ngôn ngữ) | Ngôn từ độc hại ở nhiều ngôn ngữ, gồm tiếng Việt và tiếng Nhật | Không cần |
| Gibberish / Nonsense | Nội dung nhảm, vô nghĩa | Không cần |
| Unusual / Manipulative Prompt | Câu lệnh thao túng hoặc social-engineering | Không cần |
| Banned Words | Những từ/cụm bạn không cho phép | Danh sách từ của bạn |
| Competitor Mentions | Nhắc tới đối thủ mà bạn liệt kê | Tên đối thủ |
| Restrict to Topics | Bất kỳ nội dung nào ngoài chủ đề bạn cho phép | Chủ đề cho phép |

### Ai có quyền xem và sửa guardrails

Guardrails chỉ dùng một quyền duy nhất: ai mở được Hub thì cũng sửa được. Không có chế độ chỉ xem — cấu hình, danh sách guard và log _Recent blocks_ đều thuộc cùng quyền quản trị workspace.

| Vai trò | Quyền với guardrails |
| --- | --- |
| Organization Owner | Có — ở mọi workspace của tổ chức |
| Organization Admin | Có — ở mọi workspace của tổ chức |
| Workspace Admin | Có — ở workspace mà họ quản trị |
| Workspace Member | Không — không thấy mục _Guardrails_ |
| Thành viên tổ chức không có quyền admin workspace | Không |

Organization Owner và Admin tự động có quyền admin ở mọi workspace, nên cấp một trong hai vai trò này là trao quyền guardrails cho toàn tổ chức. Nếu chỉ muốn giới hạn trong một workspace, hãy để họ là Organization Member và cấp Workspace Admin tại workspace đó. Nhân sự hỗ trợ nền tảng SotaAgents cũng có thể mở Hub khi trợ giúp bạn.

> [!NOTE]
> Ẩn menu không phải là cơ chế bảo vệ
>
> Việc không thấy mục trong sidebar chỉ phản ánh đúng quy tắc mà API áp dụng: request từ tài khoản không có quyền admin workspace sẽ bị từ chối, nên không thể xem hay sửa guardrails bằng đường khác.

### Guard nào do nền tảng bắt buộc

Trong bảng trên, hai guard là bắt buộc ở mức nền tảng theo mặc định. Chúng chạy trên mọi workspace kể cả khi công tắc master đang tắt hoặc mode đặt _Off_, và không có tickbox để bỏ chọn:

| Guard | Ai kiểm soát |
| --- | --- |
| Jailbreak / Prompt Injection | Nền tảng — luôn bật, gồm cả việc kiểm tra nội dung tool và tài liệu trả về |
| Secrets & Credentials | Nền tảng — luôn bật |
| Personal Data (PII) | Bạn — trừ trong model reasoning, nơi nền tảng tự che |
| Chín guard còn lại | Bạn — tự bật/tắt và chọn mode theo workspace |

Model reasoning cũng do nền tảng quản lý: secret và dữ liệu cá nhân luôn được che ở đó, bất kể bạn tick guard nào. Một dòng guard bắt buộc chạy theo mode của nền tảng, không theo mode workspace, nên nó có thể đang chặn trong khi workspace của bạn vẫn ở _Observe_.

Bản triển khai của bạn mới là nguồn quyết định cuối cùng, vì tập guard bắt buộc là thiết lập của operator. Hãy đọc trực tiếp trên Hub: mọi thứ trong mục _Enforced by the platform_ là ngoài tầm điều chỉnh của bạn, còn mọi thứ có tickbox là của bạn. Nếu không thấy mục đó, lớp nền tảng đang tắt trong bản triển khai này và cả 12 guard đều do workspace kiểm soát.

### Log guardrails được lưu bao lâu

_Recent blocks_ mặc định lưu sự kiện trong **30 ngày**, sau đó mỗi bản ghi tự động bị xóa. Khoảng thời gian này là thiết lập nền tảng, nên bản self-hosted hoặc dedicated có thể được cấu hình khác — nếu cần con số chính xác cho mục đích audit, hãy hỏi bên vận hành nền tảng.

Mỗi bản ghi lưu thời điểm, stage (user input, tool output hay model reasoning), điều đã xảy ra (chặn, che, chỉ ghi nhận, hoặc không đánh giá được), guard đã khớp và mode đang áp dụng. Bản thân nội dung bị gắn cờ không bao giờ được lưu: bản ghi chỉ giữ độ dài và một chuỗi fingerprint ngắn, kèm một đoạn trích đã che tối đa 280 ký tự. Với phát hiện dạng secret hoặc dữ liệu cá nhân, ngay cả đoạn trích đó cũng bị bỏ, nên log không thể làm lộ chính thứ mà guard vừa bắt được.

> [!NOTE]
> Hãy xem log là công cụ debug, không phải nơi lưu trữ lâu dài
>
> Nó trả lời "tin nhắn nào bị dừng, ở đâu, do guard nào" khi sự việc còn mới. Nếu cần giữ hoạt động guardrails lâu hơn thời hạn lưu, hãy sao chép phần cần thiết ra ngoài trước khi hết hạn. _Export rules (CSV)_ xuất cấu hình, không xuất log.

## Hướng dẫn Developer

Xây dựng, kiểm thử, deploy, release và đưa một SotaAgents app lên App Store theo một quy trình thống nhất.

### App làm được gì

SotaAgents app là một gói năng lực có version, dùng để mở rộng nền tảng trong tổ chức và workspace. Một app có thể cung cấp **tools** cho trợ lý gọi, **skills** hướng dẫn workflow, native UI, tool-result renderer, hooks, events, prompts và quyền truy cập dữ liệu có scope. Nền tảng quản lý identity, cài đặt, authorization, chọn environment, phục vụ assets và audit; app quản lý business logic và dữ liệu nghiệp vụ riêng.

### Vì sao cần app?

SotaAgents chỉ có một trợ lý, nhưng không trợ lý nào biết tiêu chuẩn bản vẽ CAD của bạn, quy trình rà soát pháp lý của bạn, hay API của hệ ERP công ty bạn đang chạy. Thay vì phình sản phẩm lõi cho từng lĩnh vực, nền tảng được thiết kế cố ý "chưa hoàn chỉnh": app là cách một team bổ sung phần năng lực còn thiếu, và nền tảng uốn theo nó. Cùng một shell phục vụ được app rà soát văn bản pháp luật, knowledge base, app sinh CAD, app tạo tài liệu office và app web search — chính vì không domain logic nào trong số đó nằm trong lõi.

Cụ thể, một app là một thư mục chứa `manifest.yaml` và — khi năng lực đó cần tính toán, credentials riêng, API bên ngoài hoặc dữ liệu nghiệp vụ lâu dài — một HTTP service do bạn tự host. Nền tảng không dò code của bạn để đoán ra điều gì: nó chỉ expose đúng những gì manifest khai báo, cho đúng organization, workspace và environment đã resolve, và từ chối mọi thứ không được khai báo.

Hai hệ quả. Trợ lý có thêm năng lực mà tự nó không thể có: một **tool** để truy vấn hoặc thay đổi hệ thống của bạn, một **skill** dạy nó quy trình của bạn, một **screen** render dữ liệu của bạn ngay trong workspace. Và team của bạn ship những năng lực đó theo nhịp release riêng, bằng ngôn ngữ và runtime riêng, không cần sửa gì trong SotaAgents.

> [!NOTE]
> App là đơn vị mở rộng, không phải một plugin script.
>
> Nó có version, được cài theo organization, bật theo workspace và resolve về đúng một environment ở mỗi request. Đó là điều khiến domain logic của bên thứ ba chạy an toàn cạnh tính năng first-party.

Tools

Backend action có input/output typed để trợ lý gọi.

Skills

Chỉ dẫn và workflow tái sử dụng cho trợ lý.

Native UI

Trang workspace, màn admin, inline view và artifact renderer.

App data

Storage/backend access được authorize và tách theo environment.

![App Store của tổ chức với app đã cài và app trong catalog](/manual/assets/developer/app-store-catalog.webp)
_App được tìm và cài ở cấp tổ chức, mặc định enabled trong workspace và hỗ trợ override rõ ràng theo workspace._

![Knowledge Base chạy dưới dạng native workspace page](/manual/assets/developer/app-workspace-native-ui.webp)
_Production app có thể cung cấp cả một trải nghiệm workspace nhưng vẫn dùng navigation, identity và access control của nền tảng._

![Tool result Web Search đã mở rộng trong conversation SotaAgents](/manual/assets/developer/web-search-tool-result.webp)
_Action của app hiển thị query, visual preview và source card ngay trong conversation._

![Presentation artifact của Office App đang mở cạnh conversation](/manual/assets/developer/slide-artifact.webp)
_Artifact slide bền vững mở ở panel workspace trong khi conversation gốc và artifact card vẫn nhìn thấy bên trái._

### App experience xuất hiện ở đâu?

| Manifest surface | Dùng khi | Host điển hình |
| --- | --- | --- |
| `page` | Workflow lâu dài có navigation và state. | Trang workspace hoặc admin. |
| `tool-view` | Render một tool call đã bind, gồm cả trạng thái input đang stream nếu bật. | Tool result trong conversation. |
| `message-part` | Render contribution đã bind ngay trong message. | Message của assistant hoặc user. |
| `artifact` | Output bền vững để mở lại, kiểm tra hoặc export. | Artifact panel. |
| `card` | Status hoặc entry content gọn do app sở hữu. | Workspace assistant card slot. |
| `composer-action` | Action gọn nằm cạnh các control của chat input. | `chat.composer.actions`. |
| `composer-panel` | UI theo ngữ cảnh có thể đọc và sửa atomically draft hiện tại. | `chat.composer.panel`. |

`useComposer` chỉ khả dụng trong `composer-panel`. `composer-action` là surface độc lập và không có quyền sửa draft trực tiếp.

> [!NOTE]
> Nền tảng luôn resolve một environment chính xác.
>
> Mọi tool, screen, asset và data request đều thuộc Development, Staging hoặc Production. Không có fallback ngầm từ environment này sang environment khác.

### Triết lý kiến trúc

SotaAgents chủ động tách **platform control plane** khỏi **app data plane**. Platform biết user, organization, workspace, installation, selected environment, exact artifact và quyền đã cấp. Backend của app hiểu domain: tìm tài liệu, tạo CAD, gọi API riêng hay áp dụng business rules.

![Sơ đồ request đi qua SotaAgents tới exact app environment](/manual/assets/developer/app-architecture.svg?v=2)
_Một lần quyết định authority bind request với exact artifact, UI bundle, backend và data theo environment._

### Tại sao app cần backend riêng?

- **Ownership độc lập:** ship business logic theo nhịp riêng mà không nhét domain code vào Core.
- **Security boundary:** giữ credentials và third-party integration ở server; chỉ nhận signed platform context.
- **Data boundary:** tự chọn database, retention, residency và scaling theo domain.
- **Operational isolation:** app scale, fail và recover mà không kéo theo app khác.

> [!WARNING]
> Backend riêng không có nghĩa là identity system riêng.
>
> Không tin actor/workspace/environment tùy ý từ browser. Hãy verify signed Sota request và dùng exact context platform đã chọn.

### Trợ lý gọi tool của bạn như thế nào?

Khai báo tool trong manifest mới là một nửa câu chuyện. Đây là những gì nền tảng làm với khai báo đó lúc runtime — phần khiến một app không chỉ là một web service được host.

1. #### Tool được đưa cho model

   Khi workspace resolve app surface, mỗi tool đã publish trở thành một function model gọi được, tên `app_<appId>_<toolName>`. Development và Staging chèn thêm environment (`app_my-app_stg_example`); Production bỏ qua phần đó nên tên model học được ở Production luôn gọn. Ký tự mà tên model-facing không mang được — kể cả dấu chấm trong tên kiểu `documents.query` — chuyển thành gạch dưới; tên dài quá 64 ký tự bị cắt và thêm hậu tố hash ngắn; hai app trùng tên thì thêm hậu tố số. Description model đọc là `description` trong manifest của bạn, nối thêm `App: <appId>. Runtime tool: <name>.`, còn `inputSchema` được đưa nguyên văn cho model làm parameter schema. Vì vậy description mơ hồ hay schema lỏng lẻo làm giảm chất lượng chọn tool ngay lập tức: đó là toàn bộ căn cứ để model quyết định.

2. #### Nền tảng resolve và authorize

   Trước khi có request nào rời SotaAgents, arguments được validate theo input schema của bạn, và nền tảng resolve organization, workspace, installation, environment cùng đúng một artifact áp dụng. Tool không được publish trong artifact đó, hoặc app chưa cài cho workspace đó, bị từ chối ngay tại đây — backend của bạn không hề bị gọi tới.

3. #### Một signed request tới backend của bạn

   Nền tảng gửi HTTP request server-to-server tới `service.baseUrl` đã resolve cho environment đang dùng, tại path bạn khai báo. Ở Development, chính request đó đi qua tunnel của `sota dev` về máy bạn. Browser không nằm trong đường đi này, nên backend của bạn không cần user truy cập được từ internet công cộng.

4. #### Backend verify, xử lý và trả JSON

   Verify token, làm phần việc nghiệp vụ, trả JSON. Response non-2xx, body không phải JSON hợp lệ, hoặc timeout đều bị coi là tool thất bại.

5. #### Kết quả quay lại conversation

   Nền tảng stream tool result vào conversation dưới dạng output part của tool, lưu lại, và nếu có UI contribution khai báo `surface: tool-view` với tool này trong `toolNames` thì render React module của bạn thay cho kết quả thô. Model nhìn thấy một bản JSON gọn của cùng kết quả đó.

### Request nền tảng gửi đi

Body là một envelope. Arguments của model nằm lồng ở `body.input` — đó là lý do route trong scaffold đọc `request.body.body.input` chứ không phải `request.body`.

HTTP

```
POST https://my-app.example.com/tools/example
content-type: application/json
authorization: Bearer <invocation JWT>
x-sota-core-token: <delegated Core token>   # chỉ khi app khai báo Core tool grants

{
  "requestId": "01K...",
  "organizationId": "6a66...",
  "workspaceId": "6a66...",
  "appId": "my-first-project-demo",
  "hook": { "type": "tool", "name": "example" },
  "body": {
    "input":   { "message": "hello" },
    "context": { "organizationId": "…", "workspaceId": "…", "userId": "…",
                 "conversationId": "…", "agentId": "…", "toolCallId": "…", "config": {} }
  }
}
```

> [!WARNING]
> Tin token, đừng tin envelope.
>
> Các field `organizationId`, `workspaceId` và `context` trong body chỉ để tiện log và debug. Mọi quyết định authorization phải lấy từ claims đã verify trong JWT, vì chỉ chúng mới được ký.

### Invocation token

Header `Authorization` mang một JWT ngắn hạn ký bằng EdDSA, mint riêng cho lần gọi này. Backend của bạn verify nó bằng public key của nền tảng, công bố tại `<core origin>/.well-known/jwks.json`. App không giữ private key của Sota và không bao giờ tự mint token.

| Thuộc tính | Giá trị |
| --- | --- |
| Algorithm / `typ` | `EdDSA` (Ed25519), header type `sota-invocation+jwt` |
| `iss` | `sota/invocation-token` |
| `aud` | `appId` của bạn — hãy từ chối token mint cho app khác |
| Thời hạn | 60 giây, chấp nhận lệch đồng hồ 30 giây |
| `oid` / `wid` | Organization và workspace của lần gọi |
| `sub` | User thực hiện, khi lần gọi có actor |
| `iid` | Compatibility install identity dạng opaque; không suy ra Development/Staging/Production từ prefix. |
| `ae` / `aei` / `ag` | Tên App Environment, environment identity và Backend Access Generation chính xác. Ba claim đi cùng nhau để bind quyền vào đúng backend đã resolve. |
| `aer` | Execution reference chính xác đã ký. Công việc persist/replay phải giữ reference này thay vì resolve artifact mới hơn. |
| `scp` | Các họ scope hiện tại gồm `tool:<name>`, `route:prompt:<name>`, `route:systemPrompt:<name>`, `event:<name>`, `route:artifact:<name>`, `route:resolver:<path>`, scope route lifecycle và `app:http`. Mỗi endpoint phải yêu cầu đúng scope chính xác được cấp cho bề mặt đó. |
| `rid` | `requestId` trong envelope, để đối chiếu log |

Mỗi route hãy yêu cầu scope hẹp nhất: handler `/tools/example` nên đòi `tool:example`, không rộng hơn. File `src/backend/sota-auth.ts` trong scaffold có sẵn toàn bộ bước kiểm tra — issuer, audience, algorithm, type, hạn dùng, tenant claims và scope — trong một middleware dùng lại được.

### Response phải trông như thế nào

Trả JSON HTTP bình thường. Không có wrapper riêng nào của Sota phải dựng.

| Kết quả | Nền tảng xử lý |
| --- | --- |
| 2xx kèm body JSON | Thành công. Body trở thành tool result. |
| Non-2xx | Thất bại. Nếu body là `{ "code": "…", "message": "…" }` — có thể kèm `details` và `hint` — đúng các giá trị đó được mang đi thay cho thông báo chung chung. Hãy trả status code thật thay vì body lỗi đội lốt thành công. |
| Body không phải JSON hợp lệ | Thất bại với `VALIDATION_ERROR`. |
| Không kịp trả lời trong deadline | Thất bại với `TIMEOUT`. |

`outputSchema` không được kiểm tra với response lúc runtime — nó được kiểm tra khi publish, nơi nó dùng để phát hiện breaking change giữa các version. Hãy tự validate output ở boundary; schema là contract bạn cam kết với consumer và renderer, không phải một lớp chặn lúc chạy.

Hai reserved key tùy chọn cho phép một kết quả phục vụ hai đối tượng. Đặt kết luận gọn cho model và payload đầy đủ cho renderer trong cùng một response, rồi dùng `_sota.modelOutput` để đưa cho model một bản chiếu hoàn toàn khác, hoặc `_sota.modelProjection.omitKeys` để loại bỏ các key chỉ dành cho renderer khỏi phần model đọc. Bản chiếu model-visible bị cắt ở khoảng 32.000 ký tự, nên hãy giữ nó ở mức kết luận, id, số đếm và citation thay vì tài liệu thô.

### Lifecycle & environment

![Sơ đồ lifecycle: live session development trở thành immutable Staging artifact, sota release promote đúng artifact đó lên Production, data partition tách riêng và visibility quản lý độc lập](/manual/assets/developer/app-lifecycle.svg?v=2)
_Development là live session cá nhân — UI/backend local, một workspace, chưa có immutable artifact. `sota deploy` đóng gói Staging artifact; `sota release` promote đúng artifact đó lên Production. Data partition luôn tách riêng, visibility quản lý độc lập._

| Environment | Tạo bởi | Ai dùng được | Dữ liệu |
| --- | --- | --- | --- |
| Development | `sota dev` | Developer sở hữu live session và là thành viên workspace | Partition dev cá nhân |
| Staging | `sota deploy` | Owner/contributor khi exact environment đã cài và bật | Partition Staging riêng |
| Production | `sota release` | User được visibility và workspace enablement cho phép | Partition Production |

> [!WARNING]
> Release chuyển code, không chuyển dữ liệu nghiệp vụ.
>
> Dữ liệu Staging không bao giờ tự sang Production. Nếu hai binding trỏ về cùng external backend thì app phải tự chịu trách nhiệm isolation.

![App Registry hiển thị Staging, Production và trạng thái App Store](/manual/assets/developer/app-registry-lifecycle.webp)
_Registry hiển thị exact version của Staging/Production; App Store listing là governance state riêng của Production._

### Sota CLI

**Sota CLI** là developer interface chính thức: tạo project, validate manifest, build UI contracts, mở Development session, tạo immutable Staging artifact và promote lên Production.

Terminal

```
curl -fsSL https://app.sotaagents.ai/cli/install.sh | sh
sota --version
sota login
sota whoami
sota update
```

Windows PowerShell:

PowerShell

```
irm https://app.sotaagents.ai/cli/install.ps1 | iex
```

### Bộ command

Chạy `sota --help` để có danh sách chuẩn theo đúng version bạn đang cài. Mọi command đều nhận các global option `--cwd <dir>`, `--origin <url>`, `--manifest <path>`, `--json` và `--verbose`.

| Nhóm | Command | Tác dụng |
| --- | --- | --- |
| Session | `login`, `logout`, `whoami` | Cấp cho máy này một app-developer session có scope, gắn với một origin. `login --manual` dùng cách copy/paste cho máy headless hoặc máy từ xa. |
| Scaffold | `init [dir]`, `add [features…]` | Tạo project, hoặc thêm capability vào project sẵn có. Cả hai đều additive: giữ nguyên file của bạn và báo collision thay vì ghi đè. |
| Config | `config set-origin <url>`, `config show`, `config set <key> <value>` | Quản lý `.sota/config.json`. `set-origin --env <name>` lưu origin cho một environment để `deploy -e` và `validate -e` chọn lại. |
| Manifest | `manifest schema [--version <major>]`, `manifest examples`, `manifest explain <topic>`, `manifest diff <left> <right>` | In đúng schema major được hỗ trợ mà server dùng để validate, xem ví dụ thực tế, giải thích một field và so sánh hai manifest. |
| Kiểm tra | `validate`, `build`, `contracts ensure`, `env template`, `env check` | Preflight manifest phía server (lint local khi offline), đóng gói output đã build sẵn, materialize App UI type contract, và render hoặc kiểm tra `.env.example` từ khai báo `env` trong manifest. |
| Develop | `dev`, `dev status`, `dev stop` | Publish một Development session cá nhân gắn với backend local và assets build tại máy, xem trạng thái và dừng nó. |
| Ship | `deploy` (có thể kèm `--release`), `release` | Build một immutable artifact và chạy nó ở Staging; có thể promote đúng artifact đó trong cùng command, hoặc promote artifact Staging đang chạy. |
| Tra cứu | `status`, `logs`, `workspaces`, `catalog`, `installed --org <id>`, `app info <slug>`, `app config` | Đọc trạng thái nền tảng đang giữ: các environment hiện tại, log backend (gồm filter exact `--environment`/`--environment-id`), workspace bạn được develop, catalog app và app config theo project. |
| Bảo trì | `update`, `update skills`, `docs [topic]` | Thay binary, cập nhật development skill đi kèm trong project app và mở tài liệu developer đúng version. |

> [!NOTE]
> Develop trên một deployment khác
>
> Dùng global option `--origin`, ví dụ `sota --origin https://v4.stg.sotaagents.ai login`, và giữ cùng origin cho các lifecycle command sau đó, hoặc lưu một lần bằng `sota config set-origin`.

> [!WARNING]
> CLI không bao giờ build app thay bạn.
>
> `sota build` chỉ đóng gói output đã có sẵn; nó không thay thế build frontend/backend của bạn, và `sota dev` không start hay stop process nào của bạn. Hãy build source trước, rồi để CLI đóng gói, publish hoặc tunnel.

### Tạo project

Terminal

```
sota init my-app --features all
cd my-app

# terminal 1 — watcher của chính bạn
npm run dev

# terminal 2 — Development session
npm run dev:sota
```

`sota init` tạo scaffold tương thích manifest schema v3. Chạy không kèm `--features` thì CLI hỏi capability cần scaffold; truyền `--features admin-screen,skill,backend,tool,tool-result-ui` (hoặc `all`) để trả lời sẵn. Thêm sau bằng `sota add`.

### Các scaffold feature

| Feature | Thêm gì | Kéo theo |
| --- | --- | --- |
| `admin-screen` | Một native page React + TypeScript + Vite kèm watch build cho local development. | — |
| `skill` | Một contribution dạng nội dung `SKILL.md`. Không cần backend. | — |
| `backend` | Một Express service tối thiểu với request log, Core JWT verification và health route. | — |
| `tool` | Một tool cho agent: entry trong manifest, JSON Schema input/output và backend route trả lời nó. | `backend` |
| `tool-result-ui` | Một tool cộng thêm React surface render ngay dưới kết quả của nó trong conversation. | `tool`, `backend` |

> [!NOTE]
> Scaffold là tiện lợi, không phải bắt buộc.
>
> Express, npm và TypeScript được chọn vì phổ biến dễ hiểu. Contract của nền tảng chỉ là HTTP thuần cộng một manifest — bất kỳ ngôn ngữ hay runtime nào phục vụ được route đã khai báo và verify được invocation token đều là app backend hợp lệ.

### Template có gì?

| Path | Ý nghĩa |
| --- | --- |
| `manifest.yaml` | Identity, version, contributions, runtime bindings, health, locales và platform compatibility. |
| `src/backend/` | Service đã có auth verification, health và tool routes mẫu. |
| `src/ui/` | Native React modules và style cho UI slots. |
| `src/skills/` | Skills dạng nội dung. |
| `src/schemas/` | JSON Schema cho tool input/output. |
| `src/locales/` | Nhãn và chuỗi đa ngôn ngữ. |
| `.sota/app-ui-contracts/` | Platform UI contracts được generate; không sửa tay. |
| `.agent/skills/` | Best practices đi kèm về architecture, security, testing, tools và UI. |

### Giải thích Manifest

`manifest.yaml` là contract khai báo giữa app và SotaAgents: app là gì, đóng góp gì, backend ở đâu, native module nào được load và hỗ trợ platform version nào. Server luôn validate lại; manifest từ client không phải authority. Property lạ bị từ chối thẳng — chạy `sota manifest schema` để đọc đúng JSON Schema và `sota manifest explain contributes.tools` để giải thích một field.

Năm field gốc `manifestSchemaVersion`, `appId`, `version`, `publisher` và `contributes` là bắt buộc. Còn lại đều tùy chọn.

YAML

```
manifestSchemaVersion: 3
appId: my-first-project-demo
version: 1.0.0
displayName: "My First Project Demo"
description: "SotaAgent app."
publisher:
  id: local-dev
  displayName: "Local Dev"
  contact: dev@local-dev.example

contributes:
  skills:
    - name: example
      description: Example content-backed skill.
      appendsTo: system
      content: src/skills/example
      timeoutMs: 3000
      failure_mode: skip
  tools:
    - name: example
      description: Example tool that echoes its input and verified tenant context.
      route: POST /tools/example
      inputSchema: src/schemas/tool-input.schema.json
      outputSchema: src/schemas/tool-output.schema.json
      timeoutMs: 15000
      failure_mode: abort
  ui:
    - id: admin-screen
      kind: nativeModule
      surface: page
      slot: admin.workspace.tab
      sectionId: my-first-project-demo
      label: Admin screen
      route: /admin/*
      module:
        entry: dist/ui/app.js
        export: AdminScreen
        styles: dist/ui/app.css

service:
  baseUrl: https://my-first-project-demo.example.com
health:
  url: https://my-first-project-demo.example.com/health
  intervalSeconds: 60
platform: ^1.2.0
locales:
  default: en
  files:
    en: src/locales/en.json
```

- **Identity** ổn định; coi `appId` là public identifier vĩnh viễn.
- **Version** bất biến theo artifact; đổi bytes sau deploy phải tăng version.
- **Contributions** là toàn bộ surface nền tảng được phép expose.
- **Environment overlay** là override tùy chọn mang đúng tên `local`, `stg` hoặc `prod`. Root service là hosted default; `local` dành cho `sota dev`. Global `--origin` chọn deployment SotaAgents, còn `-e` ở từng command chọn profile/overlay tương ứng.

Mỗi native UI surface và public runtime API được hướng dẫn trên một trang riêng sau phần manifest này.

YAML

```
service:
  baseUrl: https://my-app.example.com
health:
  url: https://my-app.example.com/health
  intervalSeconds: 60

environments:
  local:
    service:
      baseUrl: http://127.0.0.1:8787
    health:
      url: http://127.0.0.1:8787/health
  stg:
    service:
      baseUrl: https://stg.my-app.example.com
  prod:
    service:
      baseUrl: https://my-app.example.com
```

> [!WARNING]
> environments.local
>
> Manifest gốc mô tả app deploy được. URL `127.0.0.1` trong block `service` gốc sẽ bị từ chối, và base URL deploy vẫn còn đuôi `.invalid` nghĩa là backend host thật chưa từng được cấu hình.

### Khai báo một tool

Tool là cách trợ lý chạm tới backend của bạn. Bạn khai báo nó một lần dưới `contributes.tools`; nền tảng suy ra mọi thứ còn lại từ khai báo đó — model được mời gọi gì, request route ra sao, authenticate thế nào, và được phép chạy bao lâu. Schema là schema đóng, nên một key không khai báo sẽ fail validation.

| Field | Bắt buộc | Quy tắc |
| --- | --- | --- |
| `name` | Có | Public identifier ổn định. Ký tự đầu viết thường, sau đó là chữ, số và gạch nối, có thể chia đoạn bằng dấu chấm (ví dụ `documents.query`); mỗi đoạn tối đa 64 ký tự. Giữ nguyên tên sau khi release — prompt, renderer và conversation đã lưu đều tham chiếu tới nó. |
| `description` | Có | Không được rỗng. Đây chính là đoạn text model đọc khi quyết định có gọi tool hay không, nên hãy nói rõ khi nào dùng, backend làm gì, trả về gì và có điều kiện tiên quyết nào. Đừng chỉ chép lại tên tool. |
| `route` | Có | `METHOD /path`, với method thuộc `GET`, `POST`, `PUT`, `PATCH`, `DELETE` và path bắt đầu bằng `/`. Path được resolve theo `service.baseUrl` của environment đang dùng và không được thoát khỏi origin đó. Hãy khai `POST`: đường gọi của trợ lý gửi arguments dưới dạng JSON body, và scaffold cùng mọi system app đều dùng `POST /tools/<name>`. |
| `inputSchema` | Có | Đường dẫn tới file JSON Schema nằm trong app, hoặc một schema object viết thẳng. Nên dùng `required` tường minh, `additionalProperties: false`, string/array có giới hạn, và enum cho các mode đã biết. |
| `outputSchema` | Có | Cùng dạng với input schema. Bắt buộc kể cả khi kết quả rất đơn giản — nó tài liệu hóa contract mà backend của bạn phải giữ. |
| `timeoutMs` | Không | Số nguyên dương, bị kẹp tối đa 30.000 ms khi build artifact. Hãy khai báo. Tool không khai báo sẽ không có ngân sách thời gian nào trên backend host, còn tunnel Development giới hạn ở mười phút. |
| `failure_mode` | Không | `abort` hoặc `skip`; tool mặc định là `abort`. Chọn `abort` khi lỗi phải được báo là lỗi; chỉ chọn `skip` khi contribution thực sự tùy chọn và trợ lý vẫn trả lời trung thực được nếu thiếu nó. Đừng che giấu một lệnh ghi thất bại hay một truy vấn quan trọng sau vẻ ngoài "vẫn ổn". |
| `searchHint` | Không | Từ khóa bổ sung giúp tìm ra tool. Không được rỗng. |
| `undoable` | Không | Boolean. Đánh dấu action có thể hoàn tác. |
| `configGate` | Không | Một app-config key. Tool chỉ được đưa ra khi config key đó tồn tại. |

File schema mà khai báo trỏ tới là JSON Schema bình thường. Compiler resolve và inline chúng vào artifact, nên file được tham chiếu phải tồn tại trước khi build hay deploy, kể cả khi route đó không được gọi lúc test local.

JSON

```
// src/schemas/tool-input.schema.json
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "message": {
      "type": "string",
      "minLength": 1,
      "maxLength": 4000,
      "description": "Message for the example tool to echo."
    }
  },
  "required": ["message"]
}
```

Khai báo bất kỳ tool nào cũng khiến `service` trở thành bắt buộc — nền tảng phải biết gửi request đi đâu. Tên tool phải duy nhất trong một manifest, và UI contribution có `surface: tool-view` phải liệt kê trong `toolNames` những tên thực sự tồn tại trong `contributes.tools`; validator từ chối renderer gắn với tool bạn chưa khai báo. Hai app đã cài không được cùng giành một tên tool cho renderer — app vào sau mất các tên bị tranh chấp.

Về những gì nền tảng làm tiếp với khai báo này — tên model nhìn thấy, signed request, contract của response — xem _Triết lý kiến trúc_.

![Trang detail của Staging app hiển thị surface overview](/manual/assets/developer/app-surface-overview.webp)
_Exact environment chỉ expose tools, skills, UI slots, prompts và grants đã khai báo trong artifact đó._

### Trang Workspace

Workspace page là một màn hình hoàn chỉnh do app sở hữu, mở từ navigation của workspace. Core resolve app execution hiện tại của lane đã cài, giữ routing và mount boundary rồi render component được export. Phần bên trong boundary là của app; Core không tự chèn artifact launcher, chip environment hay control theo use case.

YAML

```
contributes:
  ui:
    - id: reports-page
      kind: nativeModule
      surface: page
      slot: workspace.nav
      sectionId: reports
      label: Reports
      icon: chart
      route: /reports/*
      module:
        entry: dist/ui/app.js
        export: ReportsPage
        styles: dist/ui/app.css
```

Dùng `workspace.nav` cho entry thông thường hoặc `workspace.nav.section` cho placement dạng section. `route` bắt đầu bằng `/`; hậu tố `/*` cho phép route con. Slot có navigation phải có route hoặc `sectionId`. Có thể giới hạn thêm bằng `roles` và `activation`.

TSX

```
import { useAppContext } from '@sota/platform';

type PageProps = { route?: string; subroute?: string };

export function ReportsPage({ route, subroute }: PageProps) {
  const { workspaceId, ui } = useAppContext();
  return (
    <main>
      <h1>Reports</h1>
      <button type="button" onClick={() => ui.openArtifact('reports.viewer')}>
        Open latest report
      </button>
      <small>Workspace: {workspaceId}</small>
      <small>Route: {subroute ?? route}</small>
    </main>
  );
}
```

Core truyền app route đã chọn qua `route` và `subroute`; dùng nó làm input cho app-local router. Identity, locale, theme, backend access, lifecycle và host actions lấy qua platform hooks. Không suy ra exact app execution từ route.

### Trang cài đặt người dùng

User settings page là tab do app sở hữu bên trong Account Settings. Tab chỉ xuất hiện khi Core có workspace đang hoạt động rõ ràng và app đã được cài, bật, cho phép trong workspace đó.

YAML

```
contributes:
  ui:
    - id: user-settings
      kind: nativeModule
      surface: page
      slot: user.settings.tab
      label: Reports preferences
      icon: settings
      route: /reports/user-settings
      module:
        entry: dist/ui/app.js
        export: UserSettings
        styles: dist/ui/app.css
```

Core mount exact contribution từ projected page placement; semantic route không được ghi vào URL trình duyệt. Chỉ tab đang chọn được mount, nên đổi tab hoặc đóng Settings sẽ abort scoped request và chạy disposer. App phải tự lưu draft nếu cần giữ qua unmount. Mọi mutation ở backend vẫn phải authorize signed invocation identity; việc nhìn thấy tab không phải security boundary.

### Trang Admin

Admin page vẫn là primitive `page`, nhưng được đặt trong phần quản trị workspace. Dùng nó cho cấu hình và vận hành app, không phải workflow thường ngày của member.

YAML

```
contributes:
  ui:
    - id: reports-admin
      kind: nativeModule
      surface: page
      slot: admin.workspace.tab
      sectionId: reports
      label: Reports settings
      route: /admin/reports/*
      roles: [OWNER, ADMIN]
      module:
        entry: dist/ui/app.js
        export: ReportsAdmin
        styles: dist/ui/app.css
```

`slot` quyết định nơi Core mount page; Core vẫn truyền `route`/`subroute` như workspace page và phần nội dung do app sở hữu. Khai báo `roles` và vẫn kiểm tra authorization ở app backend vì việc ẩn UI không phải security boundary.

TSX

```
import { useAppFetch } from '@sota/platform';

export function ReportsAdmin() {
  const appFetch = useAppFetch();
  async function save() {
    const response = await appFetch('/settings', {
      method: 'PUT',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ enabled: true }),
    });
    if (!response.ok) throw new Error('Could not save settings');
  }
  return <button type="button" onClick={save}>Enable reports</button>;
}
```

### Tool view

`tool-view` thay thế hoặc bổ sung cách hiển thị tool call do cùng app khai báo. Gắn nó bằng `toolNames` không rỗng. Core truyền đúng một prop `toolResult` và giữ nguyên mount khi state thay đổi.

YAML

```
contributes:
  ui:
    - id: calendar-result
      kind: nativeModule
      surface: tool-view
      slot: chat.message.inline.below
      toolNames: [readCalendar]
      renderBeforeOutput: true
      module:
        entry: dist/ui/app.js
        export: CalendarResult
```

TSX

```
import type { ToolResultSurfaceProps } from '@sota/platform';

type Input = { from: string; to: string };
type Output = { events: Array<{ id: string; title: string }> };

export function CalendarResult({ toolResult }: ToolResultSurfaceProps<Input, Output>) {
  if (toolResult.state === 'input-streaming') return <p>Preparing…</p>;
  if (toolResult.state === 'output-pending') return <p>Waiting for an action…</p>;
  if (toolResult.state === 'output-error') return <p>{toolResult.errorText}</p>;
  if (toolResult.state === 'output-denied') return <p>Permission denied.</p>;
  if (toolResult.state !== 'output-available') return <p>Running…</p>;
  return <p>{toolResult.result?.events.length ?? 0} events</p>;
}
```

| State | Contract |
| --- | --- |
| `input-streaming` | Partial input dạng `unknown`; chỉ có khi bật `renderBeforeOutput`. |
| `input-available`, approval states | Input hoàn chỉnh đã validate schema. |
| `output-pending` | Tool call đang deferred; có thể có `deferred.operationId` và `data` opaque của app. |
| `output-available` | `result` là app result đã bỏ metadata transport; `output` giữ raw host value. |
| `output-error`, `output-denied` | Không được giả định có result. |

`execution` chỉ app lane đã tạo tool call. Đây là context chẩn đoán cho payload, không phải yêu cầu load historical artifact của producer.

### Message part

Native `message-part` là tool surface inline đặt dưới chat message liên quan. Nó dùng cùng `ToolResultSurfaceProps`, state, context của producer lane, `toolNames` và `renderBeforeOutput` như tool view; chọn nó khi kết quả nên đọc như một phần của message.

YAML

```
contributes:
  ui:
    - id: source-summary
      kind: nativeModule
      surface: message-part
      slot: chat.message.inline.below
      toolNames: [searchSources]
      renderBeforeOutput: true
      module:
        entry: dist/ui/app.js
        export: SourceSummary
```

TSX

```
import type { ToolResultSurfaceProps } from '@sota/platform';

type SearchOutput = { sources: Array<{ title: string; url: string }> };

export function SourceSummary({ toolResult }: ToolResultSurfaceProps<unknown, SearchOutput>) {
  if (toolResult.state === 'output-pending') {
    return <aside aria-live="polite">Waiting for the app…</aside>;
  }
  if (toolResult.state !== 'output-available') return null;
  return <aside aria-label="Sources">{toolResult.result?.sources.map((source) => (
    <a key={source.url} href={source.url} rel="noreferrer" target="_blank">
      {source.title}
    </a>
  ))}</aside>;
}
```

> [!NOTE]
> Khác với declarative message renderer.
>
> `kind: messageRenderer` trang trí text match được thành citation, mention hoặc pill. `surface: message-part` load React của app cho named tool call.

### Artifact surface

Artifact surface sở hữu phần body của side panel bền vững. Khai báo `artifactKind` ổn định có namespace của app; Core dùng nó để resolve native module hiện tại trong app lane khi app mở artifact.

YAML

```
contributes:
  ui:
    - id: report-viewer
      kind: nativeModule
      surface: artifact
      slot: artifact.slot.reports-viewer
      artifactKind: reports.viewer
      module:
        entry: dist/ui/app.js
        export: ReportViewer
        styles: dist/ui/app.css
```

TSX

```
import { useAppContext } from '@sota/platform';

type ArtifactProps = {
  artifact: { artifactKind: string; context?: Record<string, unknown> };
};

export function ReportViewer({ artifact }: ArtifactProps) {
  const { ui } = useAppContext();
  const title = typeof artifact.context?.title === 'string'
    ? artifact.context.title
    : 'Report';
  return (
    <article>
      <h1>{title}</h1>
      <button type="button" onClick={ui.closeArtifact}>Close</button>
    </article>
  );
}
```

Mở từ surface trong cùng app lane bằng `useAppContext().ui.openArtifact('reports.viewer', context)`. Bản app hiện tại của lane render dữ liệu opaque trong `artifact.context`; app tự chịu trách nhiệm tương thích với payload do bản cũ tạo ra. Artifact kind phải duy nhất trong workspace đã resolve; namespace `core.*` được dành riêng.

TSX

```
import { useAppContext } from '@sota/platform';

export function OpenReportButton({ reportId }: { reportId: string }) {
  const { ui } = useAppContext();
  return <button type="button" onClick={() => ui.openArtifact('reports.viewer', {
    reportId, title: 'Quarterly report',
  })}>Open report</button>;
}
```

Validate `artifact.context` trước khi dùng; đây là transport data có cấu trúc, không thay thế việc load record có authority từ app backend.

### Workspace card

`card` là UI gọn của app trong vùng assistant của workspace. Nó không nhận card-specific props; dùng platform hooks cho context, data và actions.

YAML

```
contributes:
  ui:
    - id: reports-card
      kind: nativeModule
      surface: card
      slot: workspace.assistant-card
      module:
        entry: dist/ui/app.js
        export: ReportsCard
```

TSX

```
import { useAppContext } from '@sota/platform';

export function ReportsCard() {
  const { ui } = useAppContext();
  return (
    <section aria-label="Reports">
      <p>Three reports need review.</p>
      <button type="button" onClick={() => ui.openArtifact('reports.viewer', {
        filter: 'needs-review',
      })}>Review</button>
    </section>
  );
}
```

Hãy responsive theo host thay vì giả định kích thước trang. Dùng workspace page hoặc artifact khi cần canvas lớn.

### Composer action

`composer-action` là action gọn do app sở hữu, nằm cạnh control của chat input. Nó phù hợp để mở UI hoặc bắt đầu workflow của app.

YAML

```
contributes:
  ui:
    - id: report-action
      kind: nativeModule
      surface: composer-action
      slot: chat.composer.actions
      module:
        entry: dist/ui/app.js
        export: ReportAction
```

TSX

```
import { useAppContext } from '@sota/platform';

export function ReportAction() {
  const { ui } = useAppContext();
  return <button type="button" onClick={() => ui.openArtifact('reports.viewer')}>Reports</button>;
}
```

`useComposer` cố ý không dùng được ở đây. Nếu UI cần đọc hoặc sửa draft, hãy khai báo `composer-panel`; không truy cập Core private stores.

### Composer panel

`composer-panel` là UI contextual phía trên composer. Đây là surface duy nhất được bind public composer API: quan sát draft, edit atomically, đọc deferred result pending của exact execution và giữ composer lock.

YAML

```
contributes:
  ui:
    - id: report-panel
      kind: nativeModule
      surface: composer-panel
      slot: chat.composer.panel
      module:
        entry: dist/ui/app.js
        export: ReportPanel
```

TSX

```
import { useComposer } from '@sota/core/hooks';

export function ReportPanel() {
  const value = useComposer((composer) => composer.value);
  const applyEdit = useComposer((composer) => composer.applyEdit);
  if (!value.includes('/report')) return null;
  return (
    <button type="button" onClick={() => applyEdit({
      mode: 'replace',
      content: [{ kind: 'text', text: 'Create a weekly report for ' }],
    })}>
      Use weekly report template
    </button>
  );
}
```

Slot phải chính xác là `chat.composer.panel`. Panel có thể render `null` khi điều kiện riêng của app không còn. Slash command và panel là hai contribution độc lập.

### useAppContext

`useAppContext()` là runtime API chính cho mọi hosted native surface. Import từ `@sota/platform`; hook throw nếu không có app surface provider.

TSX

```
import { useAppContext } from '@sota/platform';

export function RuntimeDetails() {
  const context = useAppContext();
  return (
    <dl>
      <dt>App</dt><dd>{context.appId}@{context.appVersion}</dd>
      <dt>Organization</dt><dd>{context.organizationId}</dd>
      <dt>Workspace</dt><dd>{context.workspaceId ?? 'none'}</dd>
      <dt>Surface</dt><dd>{context.surface}</dd>
    </dl>
  );
}
```

| Field | Ý nghĩa |
| --- | --- |
| `appId`, `appVersion` | App package hiện tại được chọn cho lane này. |
| `organizationId`, `workspaceId` | Tenant context đã verify; `workspaceId` có thể vắng ngoài workspace scope. |
| `surface` | Surface kind hiện tại; dùng cho presentation, không dùng để authorize. |
| `theme`, `locale`, `t` | Presentation và localization hiện tại. |
| `fetch` | Cùng scoped data-plane function với `useAppFetch`. |
| `lifecycle` | Cancellation và cleanup theo mount. |
| `ui` | Host actions cho toast, navigation, artifact và lightbox. |

Các giá trị được server stamp từ resolved execution. Không dựng lại identity organization, workspace, app hoặc environment từ URL hay local storage.

### useAppFetch

`useAppFetch()` trả về authenticated data-plane fetcher của surface. Truyền path tương đối của app và `RequestInit`; Core resolve dưới backend của environment đã chọn, inject bearer credential, bind cancellation theo mount và refresh credential hết hạn đúng một lần.

TSX

```
import { useState } from 'react';
import { useAppFetch } from '@sota/platform';

export function SaveSettingsButton() {
  const appFetch = useAppFetch();
  const [status, setStatus] = useState('idle');
  async function save() {
    setStatus('saving');
    const response = await appFetch('/settings', {
      method: 'PUT',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ digest: 'weekly' }),
    });
    if (!response.ok) return setStatus('error');
    const saved = await response.json();
    setStatus(saved.digest === 'weekly' ? 'saved' : 'error');
  }
  return <button type="button" onClick={save}>{status}</button>;
}
```

Kết quả là `Response` chuẩn; parse JSON và domain error vẫn là logic app. URL khác origin hoặc path thoát scoped base path bị từ chối. Không tự thêm hay lưu authorization header.

| Case | Behavior |
| --- | --- |
| Response thường | Trả nguyên trạng, kể cả non-2xx. |
| Credential hết hạn | Core refresh descriptor và retry đúng một lần trong cùng environment. |
| Surface unmount | Request signal bị abort. |
| Stream body one-shot | Core tee trước khi có thể retry credential. |

### useLocale

`useLocale()` trả về `{ locale, t }`. `locale` là locale thực sự load cho surface. `t(key, values)` đọc message file đã khai báo và interpolate named values; key app bị thiếu fallback sang shell translator.

TSX

```
import { useLocale } from '@sota/platform';

export function Greeting({ name }: { name: string }) {
  const { locale, t } = useLocale();
  return <p lang={locale}>{t('greeting', { name })}</p>;
}
```

YAML

```yaml
locales:
  default: en
  files:
    en: src/locales/en.json
    vi: src/locales/vi.json
```

`src/locales/en.json`

JSON

```json
{ "greeting": "Hello, {{name}}" }
```

`src/locales/vi.json`

JSON

```json
{ "greeting": "Xin chào, {{name}}" }
```

Locale được chọn fallback theo contract locale của app; key thiếu trong app tiếp tục được đưa cho shell translator. Nếu label thiết yếu bị thiếu ở cả hai nơi, component vẫn nên có fallback hợp lý. Không nhét business data vào translation và luôn có accessible fallback text.

### useTheme

`useTheme()` trả về `'light'` hoặc `'dark'` và re-render theo host theme. API này read-only: app thích ứng với workspace, không đổi theme của Core.

TSX

```
import { useTheme } from '@sota/platform';

export function Preview() {
  const theme = useTheme();
  return <div className="preview" data-theme={theme}>Preview</div>;
}
```

Code

```css
.preview {
  color: var(--foreground);
  background: var(--background);
}

.preview[data-theme='dark'] .diagram {
  filter: brightness(0.9);
}
```

Ưu tiên design token được native surface kế thừa. Chỉ dùng hook khi behavior hoặc asset thật sự khác theo theme.

### Platform UI components

`@sota/platform/ui` là component library public cho native app. Component kế thừa token, theme, focus behavior, portal và accessibility default của Core. Import từ entry point này thay vì Core source path hoặc cài thêm bản Radix/Recharts riêng.

| Nhóm | Exports |
| --- | --- |
| Action và status | `Button`, `Badge`, `Spinner`, `Skeleton`, `buttonVariants`, `badgeVariants`. |
| Content và layout | `Card*`, `Separator`, `ScrollArea`, `ScrollBar`. |
| Form | `FormField`, `Label`, `Input`, `Textarea`, `Checkbox`, `Switch`, `Select`, `SelectContent`, `SelectGroup`, `SelectItem`, `SelectLabel`, `SelectSeparator`, `SelectTrigger`, `SelectValue`. |
| Overlay | `Dialog`, `DialogClose`, `DialogContent`, `DialogDescription`, `DialogFooter`, `DialogHeader`, `DialogTitle`, `DialogTrigger`, `Popover`, `PopoverContent`, `PopoverTrigger`, `Tooltip`, `TooltipContent`, `TooltipProvider`, `TooltipTrigger`; portal nằm trong app surface host. |
| Navigation | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`. |
| Grounded prose | `CitationText` map marker `[n]` sang source pill. |
| Chart | `ChartContainer`, `ChartStyle`, `ChartTooltip`, `ChartTooltipContent`, `ChartLegend`, `ChartLegendContent`, `Area`, `AreaChart`, `Bar`, `BarChart`, `Line`, `LineChart`, `Pie`, `PieChart`, `RadialBar`, `RadialBarChart`, `CartesianGrid`, `XAxis`, `YAxis`, `ChartLabel`, `ChartLabelList`, `Cell`, `Sector`, `ReferenceLine`. |

### Form và card

TSX

```
import { useState } from 'react';
import {
  Button, Card, CardContent, CardFooter, CardHeader, CardTitle,
  FormField, Input, Switch,
} from '@sota/platform/ui';

export function DigestSettings() {
  const [email, setEmail] = useState('');
  const [enabled, setEnabled] = useState(true);
  return (
    <form onSubmit={(event) => event.preventDefault()}><Card variant="flat">
      <CardHeader><CardTitle>Weekly digest</CardTitle></CardHeader>
      <CardContent>
        <FormField id="digest-email" label="Delivery email" required>
          <Input id="digest-email" type="email" value={email}
            onChange={(event) => setEmail(event.target.value)} required />
        </FormField>
        <Switch checked={enabled} onCheckedChange={setEnabled}
          aria-label="Enable weekly digest" />
      </CardContent>
      <CardFooter><Button type="submit" disabled={!email}>Save</Button></CardFooter>
    </Card></form>
  );
}
```

`Button` có variant `default`, `secondary`, `destructive`, `outline`, `link`, `ghost`; size `default`, `sm`, `lg`, `icon`, `icon-sm`, cùng prop `loading`. `CardTitle` mặc định là heading gọn.

### Dialog

TSX

```
import {
  Button, Dialog, DialogClose, DialogContent, DialogDescription,
  DialogFooter, DialogHeader, DialogTitle, DialogTrigger,
} from '@sota/platform/ui';

export function DeleteDialog() {
  return (
    <Dialog>
      <DialogTrigger asChild><Button variant="destructive">Delete</Button></DialogTrigger>
      <DialogContent preset="confirm" size="sm">
        <DialogHeader>
          <DialogTitle>Delete report?</DialogTitle>
          <DialogDescription>This action cannot be undone.</DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <DialogClose asChild><Button variant="outline">Cancel</Button></DialogClose>
          <Button variant="destructive">Delete</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}
```

Dialog preset gồm `modal`, `editor`, `confirm`, `command`; size từ `sm` tới `xl`. Luôn có `DialogTitle` và `DialogDescription`. Chỉ dùng `dismissible={false}` khi đóng sớm có thể phá action quan trọng đang chạy.

### Citation và chart

TSX

```
import { CitationText } from '@sota/platform/ui';

export function AnswerWithSources() {
  return <CitationText text="Revenue grew by 12% [1]." citations={[{
    index: 1,
    url: 'https://example.com/q3',
    title: 'Q3 filing',
    snippet: 'Revenue increased twelve percent year over year.',
  }]} />;
}
```

TSX

```
import {
  Bar, BarChart, CartesianGrid, ChartContainer, ChartTooltip,
  ChartTooltipContent, XAxis, YAxis, type ChartConfig,
} from '@sota/platform/ui';

const chartConfig = {
  credits: { label: 'Credits', color: 'var(--chart-1)' },
} satisfies ChartConfig;
const data = [{ month: 'Jul', credits: 24 }, { month: 'Aug', credits: 31 }];

export function CreditChart() {
  return <ChartContainer config={chartConfig} className="min-h-48 w-full">
    <BarChart data={data}>
      <CartesianGrid vertical={false} />
      <XAxis dataKey="month" /><YAxis />
      <ChartTooltip content={<ChartTooltipContent />} />
      <Bar dataKey="credits" fill="var(--color-credits)" />
    </BarChart>
  </ChartContainer>;
}
```

Chỉ import primitive surface dùng. Generated declarations là prop reference chính xác cho Core version đã cài.

### Platform icons

`@sota/platform/icons` expose các icon name ổn định: `alert`, `check`, `chevronDown`, `chevronLeft`, `chevronRight`, `close`, `info`, `loading`, `search`, `settings`, `warning`.

TSX

```
import { Icon, PLATFORM_ICON_NAMES, type IconName } from '@sota/platform/icons';

export function StatusIcon({ name, label }: { name: IconName; label: string }) {
  return <span>
    <Icon name={name} size={16} strokeWidth={2} aria-hidden />
    <span>{label}</span>
  </span>;
}

console.log(PLATFORM_ICON_NAMES);
```

`Icon` nhận SVG attributes thông thường. Dùng `aria-hidden` khi text bên cạnh đã đặt tên; nếu icon đứng một mình thì truyền `aria-label`. Union tên icon đóng để TypeScript bắt tên không tồn tại trước deploy.

### Lifecycle của surface

`useAppContext().lifecycle` sở hữu resource cho một surface mount. Cleanup chạy khi unmount hoặc exact runtime identity đổi.

| API | Cách dùng |
| --- | --- |
| `signal` | `AbortSignal` bị abort khi unmount. |
| `onDispose(dispose)` | Đăng ký cleanup của app và trả về hàm unregister. |
| `listen(target, type, listener, options?)` | Thêm event listener thuộc mount này và trả về disposer. |

TSX

```
import { useEffect, useState } from 'react';
import { useAppContext } from '@sota/platform';

export function OnlineState() {
  const { lifecycle } = useAppContext();
  const [online, setOnline] = useState(navigator.onLine);
  useEffect(() => {
    const update = () => setOnline(navigator.onLine);
    const stopOnline = lifecycle.listen(window, 'online', update);
    const stopOffline = lifecycle.listen(window, 'offline', update);
    return () => { stopOnline(); stopOffline(); };
  }, [lifecycle]);
  return <p>{online ? 'Online' : 'Offline'}</p>;
}
```

TypeScript

```
import { useEffect } from 'react';
import { useAppContext } from '@sota/platform';

export function MountTimer() {
  const { lifecycle } = useAppContext();
  useEffect(() => {
    const timer = window.setInterval(() => console.log('tick'), 30_000);
    const unregister = lifecycle.onDispose(() => window.clearInterval(timer));
    return () => {
      window.clearInterval(timer);
      unregister();
    };
  }, [lifecycle]);
  return null;
}
```

Dùng primitives này thay cho listener hoặc timer toàn process. React có thể dispose sớm; Core vẫn đảm bảo cleanup cưỡng bức khi surface biến mất.

### Platform UI bridge

`useAppContext().ui` cho phép app-owned UI yêu cầu host thực hiện presentation thuộc host. Đây là primitives, không phải business workflow.

| API | Hiệu ứng |
| --- | --- |
| `toast(message, options?)` | Hiển thị notice info, success, warning hoặc error. |
| `confirm(options)` | Trả promise cho quyết định confirm của host. |
| `navigate(to)` | Điều hướng qua host router. |
| `openArtifact(kind, context?)` | Mở artifact trong cùng app lane bằng renderer hiện tại. |
| `closeArtifact()` | Đóng artifact host hiện tại nếu có. |
| `openImageLightbox(images, startIndex?)` | Mở image viewer chung, có caption và source attribution tùy chọn. |

TSX

```
import { useAppContext, useAppFetch } from '@sota/platform';

export function DeleteReportButton({ reportId }: { reportId: string }) {
  const appFetch = useAppFetch();
  const { ui } = useAppContext();
  async function remove() {
    const confirmed = await ui.confirm({
      title: 'Delete report?', description: 'This cannot be undone.',
      confirmLabel: 'Delete', destructive: true,
    });
    if (!confirmed) return;
    const response = await appFetch('/reports/' + encodeURIComponent(reportId), {
      method: 'DELETE',
    });
    ui.toast(response.ok ? 'Report deleted' : 'Delete failed', {
      kind: response.ok ? 'success' : 'error',
    });
  }
  return <button type="button" onClick={remove}>Delete</button>;
}
```

TSX

```
import { useAppContext } from '@sota/platform';

export function PreviewButton({ previewUrl }: { previewUrl: string }) {
  const { ui } = useAppContext();
  return <button type="button" onClick={() => ui.openImageLightbox([{
    url: previewUrl,
    alt: 'Report preview',
    caption: 'Page 1',
    sourceTitle: 'Quarterly report',
    sourceUrl: 'https://reports.example.com/q3',
  }], 0)}>Preview</button>;
}
```

Method có thể no-op an toàn nếu host hiện tại không thực hiện được action. State nghiệp vụ vẫn phải có authority ở app backend.

### useComposer

`useComposer(selector)` chỉ chạy trong hosted `composer-panel` và throw ở nơi khác. Import từ `@sota/core/hooks`. Chỉ select field component cần để thay đổi không liên quan không gây thêm work.

| Field | Contract |
| --- | --- |
| `value` | Plain-text view của draft hiện tại. |
| `content` | Structured text và node `app-reference`. |
| `references` | Slash, conversation và app reference theo lane đã resolve. |
| `pendingToolResults` | Deferred tool results thuộc exact app execution của panel. |
| `applyEdit(edit)` | Một editor transaction có undo, với mode `replace`, `insert-at-cursor` hoặc `append`. |
| `focus()` | Focus composer editor. |
| `acquireLock()` | Disable send, edit draft, edit message cũ và queued auto-send khi đang giữ; trả release function idempotent. |

TSX

```
import { useEffect } from 'react';
import { useComposer } from '@sota/core/hooks';

export function PendingActionPanel() {
  const pending = useComposer((composer) => composer.pendingToolResults);
  const acquireLock = useComposer((composer) => composer.acquireLock);
  const active = pending.find((item) => item.toolName === 'readCalendar');

  useEffect(() => {
    if (!active) return;
    return acquireLock();
  }, [acquireLock, active]);

  if (!active) return null;
  return <p>Complete the app action to continue.</p>;
}
```

Lock được reference-count: nhiều panel hoặc operation giữ độc lập. Core auto-release khi surface unmount, nhưng app vẫn nên return release từ effect. Giữ structured `content`; dựng lại từ `value` sẽ làm phẳng reference.

TSX

```
import { useComposer } from '@sota/core/hooks';

export function InsertReportButton() {
  const applyEdit = useComposer((composer) => composer.applyEdit);
  return <button type="button" onClick={() => applyEdit({
    mode: 'insert-at-cursor',
    content: [
      { kind: 'text', text: 'Review ' },
      {
        kind: 'app-reference',
        referenceType: 'report',
        referenceId: 'report-42',
        label: 'Q3 report',
        fallbackText: 'Q3 report',
      },
    ],
  })}>Insert report</button>;
}
```

Mỗi `pendingToolResults` item có `operationId`, `toolCallId`, canonical `toolName`, collision-resolved `modelToolName`, `data` opaque tùy chọn và exact `execution`. Phải narrow `data` ở runtime. Hai field `pendingToolResults`/`acquireLock` cần platform contract `1.3.0` trở lên.

### Deferred tool result

App backend có thể defer kết quả của chính tool call hiện tại mà không tạo user message hay bắt model gọi lại tool. Core chỉ hiểu operation id và app data opaque; login, approval, payment, device pairing hay workflow nào khác đều là logic app.

JSON

```json
{
  "_sota": {
    "deferredToolResult": {
      "operationId": "app-defined-globally-unique-id",
      "data": { "anyAppOwnedValue": true }
    }
  }
}
```

Core đăng ký exact run, tool call, tool name, app installation và environment sau id đó. Nó stream `output-pending` cho tool surface tương ứng và thêm cùng item vào `pendingToolResults` của exact composer panel. Model chưa nhận tool result.

YAML

```yaml
coreToolGrants:
  - tool: core.app-operations.complete
    scope: write
  # Chỉ khi cần job callback token sống lâu hơn:
  - tool: core.tokens.issueJobCallback
    scope: write
```

App backend gửi callback tới Core origin. Bearer value là delegated capability nhận từ header `x-sota-core-token` của invocation gốc.

Code

```
POST /v1/app-operations/app-defined-globally-unique-id/complete
Authorization: Bearer <delegated-token>
Content-Type: application/json

{
  "status": "completed",
  "result": { "events": [] }
}
```

Dùng `status: "failed"` cùng `error` tùy ý để reject. Completion atomically trở thành result của tool call gốc và cùng agent run tiếp tục. Tool call song song có operation id riêng; model step chỉ tiếp tục khi tất cả call trong step đã settle.

JSON

```json
{
  "status": "failed",
  "error": {
    "code": "ACTION_NOT_COMPLETED",
    "message": "The requested action was not completed"
  }
}
```

- Grant `core.app-operations.complete:write` cho app backend.
- Operation id phải được trim, dài 1–200 ký tự và globally unique theo tool call; retry completion giống hệt nhau là idempotent.
- Registration xảy ra sau khi Core nhận deferred response, vì vậy retry `app_operation_not_found` với bounded backoff.
- Nếu callback có thể lâu hơn delegation token 60 giây, đổi sang job callback token khi invocation gốc còn valid, lưu token an toàn và gửi job id trong `x-sota-job-id`.
- Giữ `data` nhỏ và không có secret: Core không hiểu nó nhưng nó chủ động được expose tới client. App UI phải narrow shape trước khi dùng.
- Primitive hiện tại gắn với live run. Stop hoặc steer run làm operation còn pending fail bằng `operation_cancelled`. App phải tự quản expiry và complete failed; đây không phải background job queue.

### UI contract helpers

Generated package `@sota/platform` còn expose metadata của build contract và một typed descriptor helper. Manifest v3 vẫn là authority đăng ký contribution.

| Export | Contract |
| --- | --- |
| `PLATFORM_API_VERSION` | Public API version được compile trong runtime. |
| `getAppUiContractHash()` | Trả exact UI declaration hash đã cài; throw ngoài app runtime. |
| `assertCoreCompatibility(hash)` | No-op giữ source compatibility cho native app đời đầu. Exact hash định danh artifact, không phải runtime compatibility gate. |
| `defineAppExtension(descriptor)` | Validate `id`/`appId` không rỗng và trả frozen typed copy. Nó không đăng ký contribution bị thiếu trong manifest. |

### Type-only exports

| Nhóm | Types |
| --- | --- |
| Runtime context | `AppPlatformContextValue`, `AppLocale`, `AppTheme`, `AppSurfaceKind`, `AppLightboxImage`. |
| Tool UI | `ToolResultSurfaceProps`, `ToolResultSurfaceValue`, `ToolResultSurfaceState`, `ToolResultSurfaceCommon`, `ToolResultSurfaceExecution`. |
| Descriptor | `AppExtensionDescriptor`, `AppSurfaceSlot`, `NativeModuleSurfaceDescriptor`, `ToolResultSurfaceDescriptor`, `MessageDecorationDescriptor`, `ArtifactSurfaceDescriptor`, `ArtifactSurfaceRenderInput`. |

TypeScript

```
import {
  PLATFORM_API_VERSION,
  defineAppExtension,
  getAppUiContractHash,
} from '@sota/platform';

const descriptor = defineAppExtension({ id: 'reports', appId: 'reports-app' });
console.log(PLATFORM_API_VERSION, getAppUiContractHash(), descriptor.id);
```

TypeScript

```
import type {
  AppPlatformContextValue,
  ToolResultSurfaceProps,
  ToolResultSurfaceState,
} from '@sota/platform';

export type RuntimeIdentity = Pick<AppPlatformContextValue, 'appId' | 'appVersion'>;
export type CalendarSurface = ToolResultSurfaceProps<
  { from: string; to: string },
  { events: unknown[] }
>;
export const terminalStates: readonly ToolResultSurfaceState[] = [
  'output-available', 'output-error', 'output-denied',
];
```

Regenerate `.sota/app-ui-contracts/` bằng CLI hiện tại; không copy declaration giữa project hoặc sửa tay.

> [!WARNING]
> Generated declaration bundle không phải API catalog.
>
> App được dùng các export `@sota/platform` đã document và `useComposer`. Những host declaration khác vô tình xuất hiện trong `@sota/core/hooks` vẫn là internal nếu chưa có developer API page riêng.

### Develop local

Develop local dùng hai process độc lập ở hai terminal. **Bạn** sở hữu watcher của app; **CLI** sở hữu Development session và tunnel dẫn về máy bạn. Sota CLI không bao giờ start, restart hay kill process của bạn — kể cả khi session dừng.

Terminal

```
# terminal 1 — watcher của app
npm run dev

# terminal 2 — Development session và tunnel
npm run dev:sota   # tương đương: sota dev
```

1. #### Tự chạy watcher

   Chạy `npm run dev`. Trong project scaffold có cả backend lẫn native UI, chỉ một script này đã chạy **cả hai**: `concurrently` khởi động watcher backend và watch build UI song song, gắn nhãn `backend` và `ui`, kèm `--kill-others` để một bên crash thì dừng cả cặp thay vì để lại nửa stack đang chạy.

2. #### Mở Development

   Chạy `sota dev`, chọn organization và workspace, rồi giữ process chạy. CLI chuẩn bị mọi thứ ở local trước — compile manifest, kiểm tra output frontend đã build, mở tunnel transport — rồi mới publish session trong một lần commit. Nếu bước chuẩn bị lỗi, không có gì được tạo trên server.

3. #### Iterate an toàn

   CLI theo dõi mọi file mà manifest đã compile được dựng lên từ đó — chính manifest, JSON Schema, file locale, native asset và nội dung skill được inline — rồi re-sync vào session cá nhân của bạn khi có thay đổi. Lỗi compile chỉ in diagnostics và giữ nguyên manifest last-known-good. Heartbeat mỗi 15 giây gia hạn lease của session.

4. #### Dừng sạch

   Chạy `sota dev stop`, hoặc bấm Ctrl-C trong terminal `sota dev`. Development app biến mất và Core lên lịch cleanup data. Process frontend/backend của bạn vẫn chạy — CLI nói rõ điều đó khi thoát.

### Chạy riêng UI và backend

`npm run dev` chỉ là lớp bọc tiện lợi cho hai script vẫn tồn tại độc lập. Hãy chạy riêng khi bạn muốn restart một nửa mà không đụng nửa kia, gắn debugger vào đúng một bên, hoặc — quan trọng nhất — khi backend của bạn không phải process Node.js.

| Script | Thực chất chạy gì | Ghi chú |
| --- | --- | --- |
| `npm run dev` | `concurrently --kill-others --names backend,ui "tsx watch src/backend/server.ts" "sota contracts ensure && vite build --watch"` | Cả hai nửa cùng lúc. Project chỉ có backend hoặc chỉ có UI thì rút gọn về đúng nửa đó. |
| `npm run dev:backend` | `tsx watch src/backend/server.ts` | HTTP service của app, tự restart khi source đổi. Nghe ở `PORT`, mặc định `8787`. |
| `npm run dev:ui` | `sota contracts ensure && vite build --watch` | Là _watch build_, không phải dev server. Nó làm mới App UI type contract rồi build lại `dist/ui/app.js` và `dist/ui/app.css` mỗi lần thay đổi. |
| `npm run dev:sota` | `sota dev` | Development session, đồng bộ manifest và tunnel. Độc lập với hai script trên. |

> [!NOTE]
> Không có UI server local, và đó là chủ ý.
>
> Native UI chạy bên trong host SotaAgents, không phải trên `localhost`. `dev:ui` chỉ cần giữ module đã build trên đĩa luôn mới; tunnel phục vụ đúng những bytes đó cho nền tảng — nhờ vậy cùng bộ file chạy y hệt khi được đóng gói vào artifact đã deploy.

### Dùng backend runtime của riêng bạn

Scaffold dùng TypeScript và Express vì đó là thứ phổ biến dễ hiểu, không phải vì nền tảng bắt buộc. Contract giữa SotaAgents và backend của bạn chỉ là HTTP thuần cộng một token đã verify, nên service viết bằng Go, Python, Java hay Rust đều là app backend hạng nhất. Một trong các system app của SotaAgents là service FastAPI verify đúng invocation token đó bằng Python, với cùng endpoint JWKS của Core.

Trong trường hợp đó bạn đơn giản là không dùng các script backend Node. Khởi động service theo cách của stack bạn, giữ `npm run dev:ui` cho native UI nếu app có UI, và trỏ overlay local vào cổng service của bạn đang nghe:

Terminal

```
# terminal 1 — backend của bạn, bằng ngôn ngữ của bạn
uvicorn app.main:app --reload --port 8787

# terminal 2 — watch build native UI (chỉ khi app có UI)
npm run dev:ui

# terminal 3 — Development session
sota dev
```

`sota dev` resolve Local Backend Endpoint theo thứ tự: flag `--local-url`, rồi `environments.local.service.baseUrl` trong manifest, rồi endpoint bạn dùng lần gần nhất (ghi nhớ trong `.sota/dev.json`). Trong terminal tương tác, CLI sẽ hỏi nếu không có nguồn nào; ngược lại nó fail với `LOCAL_BACKEND_UNAVAILABLE`. Sau đó CLI poll endpoint đó khoảng mỗi giây và in `[backend] available` hoặc `[backend] unavailable`. Kiểm tra này chỉ mang tính thông báo — nó không chặn việc publish session, nên backend tạm sập không làm mất Development app của bạn.

> [!WARNING]
> Live session là cá nhân.
>
> Nó thuộc một developer và một workspace. Chạy lại `sota dev` chỉ thay session cũ của chính bạn; đây không phải Staging dùng chung. Staging và Production không bao giờ phụ thuộc tunnel — chúng gọi thẳng backend bạn host.

### Validate & test

Terminal

```
sota validate
npm run typecheck
npm run build
sota deploy --dry-run
```

- Test mọi tool với input đúng, sai, unauthorized và timeout.
- Mở mọi native UI slot/tool-result renderer ở light và dark theme.
- Kiểm tra entry, chunk, CSS, locale và AppData đều load qua platform.
- Test expectation của owner, contributor, workspace admin, member và non-member.
- Đảm bảo Development/Staging/Production không vô tình dùng chung secrets hoặc data.

### Deploy Staging

Browser không thể load source từ laptop developer một cách an toàn và reproducible. Deploy biến declared app surface thành immutable, content-addressed bytes để mọi screen, tool-result renderer, locale loader và artifact consumer resolve cùng một bản.

![Sơ đồ source project trở thành immutable SotaAgents artifact](/manual/assets/developer/bundle-pipeline.svg?v=2)
_Artifact upload chứa package assets và resolved definition; backend đang chạy, database, secrets và operational data không nằm trong bundle._

Terminal

```
sota validate
sota deploy --dry-run
sota deploy --description "Add grounded search results"
sota status
```

Deploy compile/pack bytes theo cách deterministic, upload rồi để Core validate authoritative definition. Shared Staging pointer chuyển tới artifact mới; Production không đổi.

> [!WARNING]
> Version là bất biến.
>
> Deploy bytes khác với version đã dùng sẽ trả `VERSION_IMMUTABLE_CONFLICT`. Hãy tăng version. Với breaking change, truyền acknowledgement và migration declaration mà CLI yêu cầu.

Muốn test Staging, exact Staging environment phải được cài và bật trong org/workspace. Tester cũng phải là owner/contributor và active workspace member.

### Release Production

Terminal

```
sota release
sota status
```

Release promote **chính exact Staging artifact hiện tại** lên Production. Nó không rebuild, không copy Staging data và không chờ duyệt release. Production release đầu tiên bắt đầu ở **Private**; những lần sau giữ nguyên visibility và App Store eligibility.

- Xác nhận artifact ID/version đúng bản đã test.
- Kiểm tra Production backend, health, secrets, CORS và data store.
- Smoke-test install, enablement, tools, native UI và audit event sau release.
- Giữ version trước; rollback bằng cách sửa và release một version mới.

### Đưa lên App Store

Production release và App Store listing là hai quyết định độc lập. Production có thể Private hoặc Restricted mà không xuất hiện trong public catalog.

| Visibility | Khả năng tìm/cài |
| --- | --- |
| Private | Chỉ quản lý; không ở install catalog và không ai cài được. |
| Restricted | Cho tối đa 50 tổ chức được chọn cụ thể. |
| Public / App Store | Hiện trong public catalog sau lần duyệt eligibility đầu tiên của bên vận hành SotaAgents. |

1. Release Production artifact đã test.
2. Mở _App Registry → My apps → app detail → Settings_.
3. Chọn public App Store visibility và gửi listing request.
4. Bên vận hành SotaAgents chỉ duyệt discovery eligibility; duyệt không release hay thay Production.
5. Sau khi được duyệt, owner có thể delist/relist không cần xin lại trừ khi eligibility bị revoke.

### Best practices

### Capability nhỏ và rõ

Mỗi tool một nhiệm vụ, schema chặt, timeout hữu hạn, failure mode an toàn.

### Tin platform context

Verify signed Sota request; không lấy workspace/actor/app/environment từ input tùy ý.

### UI portable

Dùng generated contracts và platform loaders; không tự ghép asset URL, token, navigation hay fallback.

### Quan sát exact version

Log artifact/version, environment, request ID, tool name; không log secret.

### Tách environment

Dùng credentials/data store riêng khi cần isolation. Staging vẫn dùng credit/audit context thật.

### Release forward

Không mutate version đã deploy; tăng version, validate, test exact Staging artifact rồi release.

### Xử lý sự cố Developer

| Hiện tượng | Cần kiểm tra |
| --- | --- |
| Native app entry trả 401/403 | Không gọi package asset trực tiếp; mount qua platform, kiểm tra exact environment đã cài/bật và actor được authorize. |
| Native app entry trả 404 | Artifact phải chứa đúng entry/chunk/CSS/locale đã khai báo và selected environment phải trỏ tới exact artifact. |
| AppData/tool unauthorized | Kiểm tra membership, rule owner/contributor của Staging, workspace enablement, signed request và environment credentials. |
| Manifest change bị bỏ qua | Dùng đúng overlay `local`/`stg`/`prod`, validate và tăng version trước deploy. |
| Staging chạy, Production lỗi | So sánh binding, secret, health, CORS, external data store và install/visibility, không chỉ source code. |

Lệnh hữu ích: `sota status`, `sota logs`, `sota manifest diff <left> <right>` và `sota docs`.

## Tích hợp doanh nghiệp

Đưa trợ lý vào chính các hệ thống công ty đang dùng — intranet, cổng khách hàng, ứng dụng nghiệp vụ — phía sau hệ thống đăng nhập sẵn có của bạn.

### Nhúng vào hệ thống nội bộ

SotaAgents có thể chạy ngay bên trong trang web của công ty bạn, dưới dạng bong bóng nổi hoặc panel gắn liền trong trang. Người dùng vẫn đăng nhập bằng hệ thống _của bạn_ và không bao giờ thấy màn hình đăng nhập SotaAgents: backend của bạn xác nhận họ là ai, và nền tảng tin vào chữ ký đó chứ không tin bất cứ thứ gì người dùng gõ vào.

[Phim mười lăm giây: ký ticket, gắn panel, và trợ lý chạy bên trong cổng thông tin của đối tác](/manual/assets/video/enterprise-embed-1080p.mp4)
_Mười lăm giây, không tiếng. Backend của bạn ký request và nhận ticket; frontend gắn panel bằng một lời gọi; trợ lý xuất hiện ngay trong cổng thông tin của chính đối tác — dạng inline, hoặc bong bóng nổi._

### Hai cách nhúng

| Chế độ | Người truy cập là ai | Dùng cho | Ai thiết lập |
| --- | --- | --- | --- |
| Public Deployment (_Configure Embed_) | Khách ẩn danh, dùng pool Guest Credit riêng | Website công khai, trang marketing, chat trước bán hàng | Chủ sở hữu/quản trị viên tổ chức — không cần code |
| Embed SDK (handshake có chữ ký) | Người dùng đích danh, đã là thành viên tổ chức | Intranet, cổng khách hàng, công cụ nội bộ | Chủ sở hữu/quản trị viên tổ chức và đội phát triển của bạn |

Trang này nói về cách thứ hai. Cách thứ nhất xem tại [Workspace](/vi/manual/admin-console/workspaces).

### Chuẩn bị trước khi bắt đầu

- **API key và secret** tạo tại _Console → Tổ chức của bạn → API Keys_. Secret chỉ hiện một lần — xem [API key](/vi/manual/admin-console/api-keys).
- Mọi người sẽ dùng embed đều phải là **thành viên đang hoạt động** của tổ chức. Hệ thống **không** tự tạo tài khoản: người lạ bị từ chối chứ không được tạo mới. Hãy [mời họ](/vi/manual/admin-console/inviting-people) trước, hoặc cấp tài khoản qua SSO.
- Một backend do bạn kiểm soát. Secret ký request ở phía server và tuyệt đối không được lộ ra trình duyệt.

### Luồng handshake

![Sơ đồ tuần tự: giao diện doanh nghiệp xin ticket từ backend doanh nghiệp, backend ký request gửi tới embed API của SotaAgents, ticket trả về và được iframe đổi lấy cookie phiên, và thao tác đăng xuất thu hồi phiên đó](/manual/assets/developer/embed-flow.svg?v=2)
_Bắt tay có chữ ký giữa ba bên. API secret không bao giờ rời cột giữa; trình duyệt chỉ giữ một ticket sống 60 giây, rồi tới cookie phiên đã phân vùng._

1. #### Trang của bạn xin ticket từ backend của bạn

   SDK không hề nói chuyện với hệ thống định danh của bạn. Nó gọi hàm `getTicket` do bạn cung cấp, hàm này gọi endpoint của bạn kèm cookie phiên của bạn.

2. #### Backend của bạn ký và tạo ticket

   Backend đọc danh tính người dùng từ session, ký request bằng API secret, và nhận về một ticket dùng một lần, hiệu lực 60 giây.

3. #### Iframe đổi ticket lấy phiên

   SDK chuyển ticket cho khung SotaAgents; khung này đổi ticket lấy phiên riêng của nó. Ticket không thể dùng lại.

4. #### SDK tự động xin ticket mới

   Gần hết hạn, hoặc sau bất kỳ lỗi 401 nào, SDK tự lặp lại bước 1. Phiên của bạn là vòng đời duy nhất có ý nghĩa — không có gì phải đồng bộ.

### Backend của bạn cần làm gì

Ba việc, và hai việc đầu là chỗ các tích hợp hay sai nhất.

1. #### Xác định người dùng ở phía server

   Dùng đúng thứ cổng thông tin của bạn đang có — SSO, OIDC, hay bảng session riêng. Gửi kèm `externalUserId` **bất biến theo từng người**: ánh xạ từ id này sang người dùng SotaAgents chỉ tạo một lần và không bao giờ được cập nhật. Hãy coi đó là ràng buộc _của bạn_, không phải của chúng tôi — chúng tôi không ép nó. Một id mới cho người mà hệ thống đã biết **không** bị từ chối: chúng tôi khớp người đó theo email rồi âm thầm tạo thêm một ánh xạ thứ hai, ánh xạ cũ nằm lại ở chỗ mà thu hồi danh tính không chạm tới. Thứ _bị_ từ chối là chiều ngược lại — một id đã tồn tại nhưng đi kèm email khác.

2. #### Mở một endpoint cấp ticket

   Đọc danh tính từ **session**, tuyệt đối không đọc từ body của request — đọc từ body đồng nghĩa mọi nhân viên đã đăng nhập đều có thể tạo ticket mạo danh đồng nghiệp. Ký lời gọi theo cách mô tả bên dưới rồi trả ticket về cho trình duyệt.

3. #### Trả về mã trạng thái mà SDK xử lý được

   SDK coi **4xx là lỗi vĩnh viễn** và dừng; **5xx là lỗi tạm thời** và thử lại có giãn cách. Trả `401` khi phiên của bạn đã hết, `409` khi người dùng chưa là thành viên đang hoạt động, và `502` khi không gọi được SotaAgents.

> [!WARNING]
> Secret chỉ nằm trên server của bạn
>
> Trình duyệt chỉ nhận ticket ngắn hạn, dùng một lần. API secret lọt xuống front-end đồng nghĩa với việc tạo được ticket cho bất kỳ danh tính nào trong tổ chức. Hãy lưu trong secret manager, không đưa vào source control.

### Ký request

Công thức giống nhau ở mọi ngôn ngữ: dựng chuỗi canonical 5 dòng, HMAC-SHA256 bằng API secret, rồi gửi kết quả dưới dạng `v1=<hex chữ thường>`. Năm dòng gồm: method, path, timestamp Unix tính bằng **giây**, nonce, và SHA-256 của body — nối bằng `\n`, không có newline ở cuối.

Code

```
POST
/api/embed/auth/tickets
1786800000
3f1c9a7e-5b02-4d6f-9a11-0c8e2d4b7f30
9b2a4c1d8e3f5a70b6c9d2e4f8a1b3c5d7e9f0a2b4c6d8e0f1a3b5c7d9e1f3a5
```

> [!WARNING]
> Hash đúng chuỗi byte mà bạn gửi đi
>
> Serialize body **một lần** và dùng lại đúng chuỗi đó cho cả phần hash lẫn phần gửi. Serialize lại lần hai là nguyên nhân phổ biến nhất khiến chữ ký không khớp ở đâu cả — hai bộ mã hoá JSON, hoặc cùng một bộ gọi hai lần, có thể xếp key hay khoảng trắng khác nhau.

#### Node.js

```javascript
import { createHash, createHmac, randomUUID } from 'node:crypto';

const body = JSON.stringify({ externalUserId, email, name }); // serialize ONCE
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = randomUUID();
const bodyHash = createHash('sha256').update(body, 'utf8').digest('hex');

const canonical = ['POST', PATH, timestamp, nonce, bodyHash].join('\n');
const signature = 'v1=' + createHmac('sha256', apiSecret).update(canonical).digest('hex');
```

#### Python

```python
import hashlib, hmac, json, time, uuid

body = json.dumps({"externalUserId": external_user_id, "email": email})  # serialize ONCE
timestamp = str(int(time.time()))
nonce = str(uuid.uuid4())
body_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()

canonical = "\n".join(["POST", PATH, timestamp, nonce, body_hash])
signature = "v1=" + hmac.new(
    api_secret.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256
).hexdigest()
```

#### Java

```java
String body = objectMapper.writeValueAsString(payload); // serialize ONCE
String timestamp = String.valueOf(Instant.now().getEpochSecond());
String nonce = UUID.randomUUID().toString();
String bodyHash = HexFormat.of().formatHex(
    MessageDigest.getInstance("SHA-256").digest(body.getBytes(StandardCharsets.UTF_8)));

String canonical = String.join("\n", "POST", PATH, timestamp, nonce, bodyHash);

Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String signature = "v1=" + HexFormat.of().formatHex(
    mac.doFinal(canonical.getBytes(StandardCharsets.UTF_8)));
```

#### C#

```csharp
var body = JsonSerializer.Serialize(payload); // serialize ONCE
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
var nonce = Guid.NewGuid().ToString();
var bodyHash = Convert.ToHexString(
    SHA256.HashData(Encoding.UTF8.GetBytes(body))).ToLowerInvariant();

var canonical = string.Join("\n", "POST", Path, timestamp, nonce, bodyHash);

using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(apiSecret));
var signature = "v1=" + Convert.ToHexString(
    hmac.ComputeHash(Encoding.UTF8.GetBytes(canonical))).ToLowerInvariant();
```

#### Go

```go
body, err := json.Marshal(payload) // serialize ONCE
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
nonce := uuid.NewString()
sum := sha256.Sum256(body)
bodyHash := hex.EncodeToString(sum[:])

canonical := strings.Join([]string{"POST", path, timestamp, nonce, bodyHash}, "\n")

mac := hmac.New(sha256.New, []byte(apiSecret))
mac.Write([]byte(canonical))
signature := "v1=" + hex.EncodeToString(mac.Sum(nil))
```

#### PHP

```php
$body = json_encode($payload); // serialize ONCE
$timestamp = (string) time();
$nonce = bin2hex(random_bytes(16));
$bodyHash = hash('sha256', $body);

$canonical = implode("\n", ['POST', $path, $timestamp, $nonce, $bodyHash]);
$signature = 'v1=' . hash_hmac('sha256', $canonical, $apiSecret);
```

### Request thực tế trên đường truyền

Dù ký bằng ngôn ngữ nào, đây là thứ SotaAgents nhận được. Header timestamp và nonce phải mang đúng giá trị đã đưa vào chuỗi canonical; mỗi nonce chỉ được chấp nhận một lần, và request lặp lại bị từ chối trong vòng 5 phút.

HTTP

```
POST https://app.sotaagents.ai/api/embed/auth/tickets
content-type: application/json
x-sota-key: sota_ek_...
x-sota-timestamp: 1786800000
x-sota-nonce: 3f1c9a7e-5b02-4d6f-9a11-0c8e2d4b7f30
x-sota-signature: v1=4d5e...

{"externalUserId":"e-1042","email":"an.tran@company.com"}
```

| Header | Giá trị |
| --- | --- |
| `x-sota-key` | API key của bạn (`sota_ek_…`) — không phải secret |
| `x-sota-timestamp` | Unix giây, đúng giá trị đã ký. Request cũ quá 5 phút bị từ chối |
| `x-sota-nonce` | Duy nhất theo từng request; trùng lặp bị coi là replay |
| `x-sota-signature` | `v1=` nối với HMAC dạng hex chữ thường |

SotaAgents trả `201` kèm bao ngoài chuẩn — `{ success: true, data: { ticket, expiresAt }, timestamp }`. **Hãy bóc lớp bao:** trả object `data` bên trong về cho trình duyệt, vì SDK đọc `ticket` ở cấp cao nhất và coi cả bao ngoài là ticket không hợp lệ — lỗi này biểu hiện thành `200` trên đường truyền và một khung chat quay mãi không lên. Response lỗi thì _không_ được bọc; chúng về phẳng dạng `{ statusCode, code, message }`, nên đọc `code` ở cấp cao nhất. Ticket sống 60 giây và chỉ đổi được một lần.

**Giới hạn tần suất.** Tạo ticket bị giới hạn **60 request mỗi phút cho mỗi API key** và **120 request mỗi phút cho mỗi IP nguồn**; bước đổi ticket bên trong iframe bị giới hạn **10 lần mỗi phút**, tính theo cả IP người dùng cuối lẫn theo ticket. Vượt ngưỡng sẽ nhận `429` kèm `Retry-After` — hãy chuyển nguyên nó cho SDK để SDK tự giãn cách và thử lại, đừng gộp vào một lỗi 4xx chung. Toàn bộ backend của bạn dùng chung một IP nguồn, và mọi người sau cùng một NAT văn phòng dùng chung hạn mức đổi ticket, nên hãy ước lượng đợt đăng nhập cao điểm theo các con số này và báo trước cho chúng tôi nếu không đủ.

### Frontend của bạn cần làm gì

Nạp `<script src="https://app.sotaagents.ai/embed/sdk.js"></script>` rồi khởi tạo. Hai điểm quan trọng: lời gọi xin ticket phải kèm cookie phiên của bạn, và khi đăng xuất phải thu hồi phiên SotaAgents trước.

Origin trong các ví dụ dưới đây chỉ là minh hoạ. Người phụ trách SotaAgents sẽ cấp origin embed riêng cho tenant của bạn, và production khác staging — hãy để origin đó, URL script SDK và base URL của API cấp ticket trong cấu hình chứ không hard-code. SDK từ chối mọi `embedUrl` không phải HTTPS.

TypeScript

```
const embed = window.SotaEmbedSDK.createSotaEmbed({
  embedUrl: 'https://app.sotaagents.ai/embed',
  mode: 'bubble',
  getTicket: async () => {
    const response = await fetch('/api/sota/embed-ticket', {
      method: 'POST',
      credentials: 'include',
    });
    if (!response.ok) {
      // Status rides along so the SDK can fast-fail a 401 or 409 instead of
      // retrying a permanent failure four times.
      throw Object.assign(new Error('ticket_failed'), { status: response.status });
    }
    return response.json(); // { ticket, expiresAt }
  },
});

// On sign-out: revoke the SotaAgents session BEFORE clearing your own.
const revoked = await embed.logout();
if (!revoked) console.warn('SotaAgents session may still be active');
await fetch('/api/logout', { method: 'POST', credentials: 'include' });
```

> [!WARNING]
> Thu hồi phiên trước khi đăng xuất người dùng
>
> Phiên SotaAgents nằm ở origin của embed, nên việc xoá phiên của bạn không đụng tới nó. Bỏ qua `logout()` thì người đăng nhập kế tiếp trên máy đó sẽ thừa hưởng một phiên còn sống — và rơi thẳng vào cuộc trò chuyện của người trước. Hãy gọi nó trước, rồi mới xoá phiên của bạn. Nó trả về `false` khi không xác nhận được việc thu hồi — vẫn cứ đăng xuất khỏi portal của bạn, nhưng trên máy dùng chung hoặc kiosk thì hãy báo cho người dùng biết phiên SotaAgents có thể vẫn còn sống.

### Tuỳ biến theo thương hiệu của bạn

Mọi tuỳ chọn ở đây đều không bắt buộc và mặc định đều là bản gốc của SotaAgents — không đặt gì thì giao diện y như hiện tại. Đặt vào thì khung chat mang tên bạn thay vì tên chúng tôi.

Code

```
createSotaEmbed({
  embedUrl: 'https://app.sotaagents.ai/embed',
  getTicket,

  // Inside the iframe
  language: 'vi',                  // 'en' | 'vi' | 'ja'
  branding: {
    logoUrl: 'https://intranet.acme.com/logo.svg',
    showUser: false,
    greeting: 'How can the Acme assistant help?',
    placeholder: 'Ask about policies, invoices, anything',
  },

  // The bubble chrome, drawn on your own page
  mode: 'bubble',
  title: 'Acme Assistant',
  primaryColor: '#0b5cff',
  position: 'bottom-left',
  width: 420,
  height: 640,
  openOnLoad: false,
});
```

**Hai kiểu gắn.** `mode` chỉ nhận đúng hai giá trị. `'bubble'` là mặc định: SDK tự vẽ nút nổi và panel của nó lên trang bạn, còn `container` bị bỏ qua. `'inline'` đặt thẳng iframe vào phần tử bạn chỉ định — không nút nổi, không panel — và ở đó `container` là bắt buộc, thiếu thì lệnh khởi tạo ném lỗi. Một khác biệt nên tính trước: bubble tạo iframe theo kiểu lười, tới lần bấm đầu tiên mới tạo, nên chưa ai mở chat thì chưa có gì xác thực; inline tạo ngay lúc tải trang, nên endpoint cấp ticket hỏng sẽ lộ ra lập tức và mỗi lượt xem trang tiêu một ticket.

**Bên trong khung chat.** Các giá trị này đi kèm URL của iframe chứ không chờ đăng nhập xong mới tới, nên khung hình đầu tiên người dùng thấy đã là của bạn — không có cảnh loé lên logo SotaAgents rồi mới đổi.

| Tuỳ chọn | Mặc định | Tác dụng |
| --- | --- | --- |
| `language` | Theo trình duyệt | `en`, `vi` hoặc `ja` (`vn` được chấp nhận như `vi`). Đây là điểm khởi đầu chứ không phải khoá cứng: người dùng đổi ngôn ngữ ngay trong khung chat thì giữ lựa chọn đó suốt phiên |
| `branding.logoUrl` | Wordmark SotaAgents | Logo của bạn ở sidebar và cạnh lời chào. Phải là URL `http(s)` tuyệt đối — khác đi thì bị bỏ qua và mặc định quay lại |
| `branding.showLogo` | `true` | `false` không hiển thị logo nào cả, và thắng `logoUrl` nếu đặt cả hai |
| `branding.showUser` | `true` | `false` ẩn khối người dùng đang đăng nhập ở chân sidebar — hợp lý khi trang của bạn đã hiển thị ai đang đăng nhập |
| `branding.greeting` | "How can I help you today?" | Dòng chữ trên màn hình chat mới |
| `branding.placeholder` | "Ask anything" | Placeholder của ô soạn tin |

Greeting và placeholder bị cắt còn 120 ký tự — đủ cho một câu, và đủ ngắn để không đẩy ô soạn tin ra khỏi màn hình.

**Khung bubble.** Nút nổi và panel do SDK vẽ trên trang của bạn, nằm ngoài iframe. Mọi thứ ở đây trừ `title` đều bị bỏ qua ở `mode: 'inline'`, nơi `container` bạn truyền vào quyết định kích thước và trang của bạn quyết định phần xung quanh.

| Tuỳ chọn | Mặc định | Tác dụng |
| --- | --- | --- |
| `title` | `SotaAgents` | Chữ trên header của panel — bỏ trống thì header chỉ còn các nút điều khiển. Nó cũng đặt tên iframe cho công nghệ trợ giúp, và nửa tác dụng này vẫn còn ở `mode: 'inline'`, nên đây là hàng duy nhất trong bảng không chỉ dành cho bubble. Bỏ trống thì trình đọc màn hình đọc khung là _SotaAgents_; hãy đặt lại nếu bạn white-label |
| `primaryColor` | `#2f6bff` | Nút nổi và header panel |
| `position` | `bottom-right` | Hoặc `bottom-left` |
| `width` / `height` | `400` / `620` | Kích thước panel tính bằng px. _Expand_ sẽ vượt qua mức này |
| `zIndex` | `2147483000` | Hạ xuống nếu panel cần nằm dưới một thành phần nào đó của bạn |
| `openOnLoad` | `false` | `true` mở panel ngay khi tải trang thay vì chờ người dùng bấm nút nổi |

Hai tuỳ chọn nữa thuộc về chính iframe. `allowMicrophone` mặc định tắt; nhập bằng giọng nói cần bật. `sandbox` thay cho mặc định `allow-scripts allow-same-origin allow-forms allow-popups` — và ở đây nó ngược với quy ước HTML quen thuộc: **`sandbox: ''` không phải mức hạn chế chặt nhất, mà gỡ bỏ hẳn thuộc tính**, kéo theo mất luôn lớp cách ly của iframe. Muốn siết chặt hơn thì truyền danh sách token hẹp hơn, tuyệt đối không truyền chuỗi rỗng. Đừng động vào cả hai trừ khi có lý do.

> [!WARNING]
> Phục vụ logo qua HTTPS
>
> Khung chat chạy trên HTTPS, nên trình duyệt tự nâng cấp logo `http://` rồi chặn luôn: logo của bạn biến mất, logo SotaAgents quay lại, và không có request lỗi nào trong network tab để giải thích. Đây là cái bẫy branding hay gặp nhất — hãy kiểm tra trên một trang được phục vụ đúng như cách production sẽ phục vụ.

### Cookie bên thứ ba

Khi embed chạy trên một site khác với cổng thông tin của bạn, cookie phiên của nó là cookie bên thứ ba.

| Trình duyệt | Hành vi | Kết quả |
| --- | --- | --- |
| Chrome, Edge | Giữ cookie, phân vùng theo từng site | Phiên đầy đủ |
| Firefox | Mặc định phân vùng cookie bên thứ ba | Phiên đầy đủ |
| Safari | Chặn cookie bên thứ ba | Chuyển sang token 10 phút và tự xác thực lại |

Cơ chế dự phòng vẫn chạy được, nhưng cứ vài phút lại phải xác thực lại. Phục vụ embed từ một subdomain của chính site bạn qua reverse proxy — ví dụ `ai.congty.com` đứng trước SotaAgents — sẽ biến cookie thành first-party và xoá bỏ vấn đề này trên mọi trình duyệt. Nếu làm vậy, nhớ hai điều: đừng để proxy sửa `Set-Cookie`, và phải tắt buffering vì câu trả lời của trợ lý được stream.

### Kiểm tra trước khi go-live

- Secret nằm trong secret manager, không nằm trong source control.
- Mọi người dùng dự kiến đều là thành viên đang hoạt động của tổ chức.
- Endpoint cấp ticket chỉ đọc danh tính từ session.
- `logout()` được gọi trước khi đăng xuất, trên mọi luồng kết thúc phiên.
- Workspace còn đủ credit — hội thoại nhúng vẫn tiêu credit như mọi hội thoại khác.
- Liên hệ bộ phận hỗ trợ để giới hạn những origin được phép nhúng embed về đúng tên miền của bạn.

> [!NOTE]
> Tài liệu SDK đầy đủ
>
> Toàn bộ tuỳ chọn, callback và mã lỗi nằm trong Embed SDK integration guide, kèm một bản tích hợp mẫu chạy được. Liên hệ đầu mối SotaAgents của bạn để nhận tài liệu.

## Tham khảo

Mẹo xử lý sự cố cho các vấn đề thường gặp.

### Xử lý sự cố

| Hiện tượng | Nguyên nhân có thể | Cách khắc phục |
| --- | --- | --- |
| "Out of credit" (Hết credit) | Pool hoặc phân bổ áp dụng đã hết. | Nhờ Owner/Admin kiểm tra phân bổ; top-up hoặc thay đổi vòng đời gói chỉ do bên vận hành SotaAgents thực hiện. |
| "This organization is inactive" | Dùng thử đã hết hạn hoặc admin đã tạm dừng tổ chức. | Chủ sở hữu nâng cấp gói hoặc kích hoạt lại tổ chức. |
| Đăng nhập liên tục bị chuyển hướng | Token cũ bị lưu trên trình duyệt. | Đăng xuất, xóa dữ liệu trang web cho tên miền, rồi đăng nhập lại. |
| Trợ lý không dùng tài liệu vừa thêm | Knowledge Base app/connector chưa bật, chưa index hoặc capability chưa được yêu cầu. | Kiểm tra trạng thái app/provider và indexing, rồi yêu cầu rõ Knowledge Base trong chat. |
| `ORG_IP_DENIED` | Địa chỉ hiện tại nằm ngoài các rule IP đang bật của tổ chức. | Chuyển sang mạng được phép hoặc nhờ admin sửa CIDR rule. |
| Trợ lý ngừng dùng một công cụ vốn hoạt động trước đó | Nhà cung cấp đã đổi tên hoặc tham số của công cụ. | Workspace admin nhấn _Refresh tools_ trên server MCP tương ứng. |
| "Tool call failed: not authorized" | Token đã lưu của server MCP hết hạn hoặc bị thu hồi. | Workspace admin ngắt kết nối và kết nối lại server MCP. |
| Email mời không tới hộp thư | Bị lọc vào thư rác (spam) hoặc sai địa chỉ email. | Gửi lại lời mời, hoặc sao chép liên kết lời mời và chia sẻ trực tiếp. |
| Tôi xem được workspace nhưng không sửa được gì | Bạn là Workspace Member và không có quyền quản trị. | Nhờ workspace admin cấp vai trò Workspace Admin nếu bạn cần quyền cấu hình. |
| Ứng dụng đã cài đặt không hiển thị trong workspace | Đúng environment đã bị gỡ hoặc không khả dụng, một override đã tắt ứng dụng ở workspace, hoặc vai trò của bạn không có quyền truy cập. | Nhờ admin tổ chức kiểm tra environment đã cài, rồi kiểm tra override trong thẻ Apps và vai trò của bạn. |
| Credit Logs hiển thị mức sử dụng ứng dụng bất thường | Một ứng dụng đang được bật ở workspace mà bạn không dự kiến. | Kiểm tra thẻ Apps của workspace và tắt ứng dụng không cần hoạt động. |
