Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ For the console app, run `cd apps/undefined-console && npm ci && npm run check`.
For the native chat client, run `cd apps/undefined-chat && npm ci && npm run check` (Biome + TypeScript + Vitest unit/e2e + `cargo fmt --check`/`cargo check`/`cargo test`). Use `npm run tauri:dev` for the desktop shell.

## Coding Style & Naming Conventions
Use 4-space indentation. Python code must be fully type-annotated and pass strict mypy checks. Disk I/O should go through `src/Undefined/utils/io.py` so writes stay async-safe and atomic. Follow `snake_case` for modules and functions, `PascalCase` for classes, and prefer extending existing services/helpers over introducing one-off abstractions. Skills handlers must not import repo-local modules outside `skills/`; pass dependencies through the execution context instead. WebUI JavaScript in `src/Undefined/webui/static/js/` is formatted with Biome, and `apps/undefined-console/` changes must satisfy Biome, TypeScript, and Cargo checks.
Use 4-space indentation. Python code must be fully type-annotated and pass strict mypy checks. Disk I/O should go through `src/Undefined/utils/io.py` so writes stay async-safe and atomic. Follow `snake_case` for modules and functions, `PascalCase` for classes, and prefer extending existing services/helpers over introducing one-off abstractions. Skills handlers must not import repo-local modules outside `skills/`; pass dependencies through the execution context instead. WebUI JavaScript in `src/Undefined/webui/static/js/` and both native apps use Biome 2.5.10; keep the two package pins, lockfiles, and three configuration schemas in sync. Nested app configurations use `root: false` without inheriting the WebUI rules. App changes must satisfy Biome, TypeScript, and Cargo checks.

## Tools & Features

Expand All @@ -44,6 +44,10 @@ Consecutive messages from the same sender within `[message_batcher].window_secon
### User identification in prompts
The system prompt now includes a rule: **recognize and address users by their QQ ID (`sender_id`)** because nicknames can change. When needing to address a user, use the latest nickname obtained via `group.get_member_info(brief=true)`. `end.observations` must be substantive facts worth future retrieval (prefer empty over noise); user-centered observations should always include the QQ ID, e.g., “QQ号12345678(昵称张三)做了某事”.

### Reply relevance and member activity semantics
Both main prompt variants and `res/IMPORTANT/each.md` guide replies to add useful information without repeating shared background. Keep exceptions for requested explanations, recaps, complete steps, corrections, and natural social replies; never infer a user's knowledge solely from their identity or terminology.
`group_analysis.member_activity` distinguishes recency (`member_list`), retrieved window message counts (`history`), and weighted indicators (`hybrid`, default). History-mode timestamps must come from messages inside the selected window. History aggregation uses `try_parse_message_time` and skips invalid timestamps before counting or ranking; `parse_message_time` retains its current-time fallback for existing display callers. Missing/zero `last_sent_time` means unknown, not proof of inactivity or never speaking; last-message time does not indicate current online status. See [the toolset documentation](src/Undefined/skills/toolsets/group_analysis/README.md) for reporting limits.

## Testing Guidelines
Write tests as `tests/test_<feature>.py`. Async tests use `pytest-asyncio`. Add or update coverage for behavior changes in APIs, config loading/hot reload, cognitive memory, meme or knowledge flows, and WebUI/runtime routes. If you touch `apps/undefined-console/` or `src/Undefined/webui/static/js/`, run `npm run check` in `apps/undefined-console/` in addition to the Python checks; if you touch `apps/undefined-chat/`, run `npm run check` in `apps/undefined-chat/` (it bundles Vitest unit/e2e suites, so cover changed behavior there). No fixed coverage threshold is configured, so cover touched paths well.

Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<table border="0">
<tr>
<td width="100%" valign="top">
<td width="70%" valign="top">
<div align="center">
<h1>Undefined</h1>
<em>QQ and WeChat bot platform with cognitive memory architecture and multi-agent Skills.</em>
Expand All @@ -18,17 +18,18 @@
<strong>Undefined</strong> 是一个基于 Python 异步架构的高性能机器人平台,以 OneBot V11 接入 QQ,并可通过微信 ClawBot/iLink 接入微信私聊。项目搭载<strong>认知记忆架构</strong>,采用自研 <strong>Skills</strong> 系统,内置多个智能 Agent,支持代码分析、网络搜索、娱乐互动等多模态能力,并提供 <strong>WebUI</strong> 在线管理,以及可连接同一管理服务的 <strong>跨平台 App</strong>。
</p>
</td>
<td width="30%" valign="top">
<a href="https://raw.githubusercontent.com/69gg/Undefined/main/img/undefined-9527.png">
<img src="https://raw.githubusercontent.com/69gg/Undefined/main/img/undefined-9527.png" width="100%" alt="Undefined 9527 自主智能体识别证" />
</a>
</td>
</tr>
</table>

### _与 [NagaAgent](https://github.com/Xxiii8322766509/NagaAgent) 进行联动!_

---

## ⚡ 立即体验

[点击添加官方实例QQ](https://qm.qq.com/q/cvjJoNysGA)

## Release 下载速查

如果只是部署和运行 QQ Bot,通常**不需要**在 GitHub Release 的 Assets 里下载任何文件。推荐按下方[快速开始](#-快速开始-源码模式)使用源码部署;只想快速体验命令行入口时,可使用 `pip install -U Undefined-bot` 或 `uv tool install Undefined-bot`。
Expand Down
4 changes: 3 additions & 1 deletion apps/undefined-chat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ iOS:
## 快速开始

```bash
npm install
npm ci
npm run check
npm run tauri:dev
```

`npm run check` 当前包含 Biome、TypeScript、unit/jsdom integration tests、cargo fmt、cargo check 和 cargo test。

Biome 固定为 **2.5.10**,与 Console 和根目录 WebUI 配置保持一致;使用 `npm ci` 安装锁定版本。App 的 `biome.json` 使用独立嵌套配置(`root: false`),不继承 WebUI 格式规则,并显式启用 React 的 `noArrayIndexKey` 检查,避免依赖自动检测造成漏检;升级约定见 [构建文档](../../docs/build.md#6-git-hook-集成)。

Runtime 默认连接 `http://127.0.0.1:8788`,受保护请求由 Tauri Rust command 注入 `X-Undefined-API-Key`。React 侧不直接持有 API Key。

## Android smoke checklist
Expand Down
22 changes: 13 additions & 9 deletions apps/undefined-chat/biome.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"root": false,
"$schema": "https://biomejs.dev/schemas/2.5.10/schema.json",
"files": {
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"package.json",
"tsconfig.json",
"vite.config.ts",
"src-tauri/tauri.conf.json",
"src-tauri/capabilities/**/*.json"
"includes": [
"**/src/**/*.ts",
"**/src/**/*.tsx",
"**/package.json",
"**/tsconfig.json",
"**/vite.config.ts",
"**/src-tauri/tauri.conf.json",
"**/src-tauri/capabilities/**/*.json"
]
},
"linter": {
Expand All @@ -18,6 +19,9 @@
},
"correctness": {
"noUnusedVariables": "warn"
},
"suspicious": {
"noArrayIndexKey": "error"
}
}
}
Expand Down
73 changes: 36 additions & 37 deletions apps/undefined-chat/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/undefined-chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"remark-gfm": "^4.0.1"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@biomejs/biome": "2.5.10",
"@tauri-apps/cli": "^2.3.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.2.0",
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-chat/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import userEvent from "@testing-library/user-event";
import type { ReactElement } from "react";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { App } from "./App";
import { LOCALE_STORAGE_KEY, LanguageProvider } from "./i18n";
import { LanguageProvider, LOCALE_STORAGE_KEY } from "./i18n";
import { createTauriRuntimeClient } from "./runtime-client/tauri";
import { conversation, historyItem, runtimeClientStub } from "./test-fixtures";

Expand Down
1 change: 1 addition & 0 deletions apps/undefined-chat/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ export function App() {
if (e.key === "Escape") closeMobileSidebar();
}}
role="presentation"
aria-hidden="true"
/>

{/* 侧边栏 */}
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-chat/src/chat-store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1476,7 +1476,7 @@ export function createChatStore({

async function loadMoreHistory(conversationId: string): Promise<void> {
const historyState = state.historyByConversation[conversationId];
if (!historyState || !historyState.hasMore || historyState.loading) {
if (!historyState?.hasMore || historyState.loading) {
return;
}

Expand Down
1 change: 1 addition & 0 deletions apps/undefined-chat/src/components/ConfirmDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export function ConfirmDialog({
>
<div
className="confirm-dialog"
role="document"
onClick={(event) => event.stopPropagation()}
onKeyDown={(event) => event.stopPropagation()}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export function ConversationList({
const navLabel = t("conversation.nav");

return (
// biome-ignore lint/a11y/useAriaPropsSupportedByRole: aria-modal 仅在同一条件将 role 切换为 dialog 时启用。
<nav
id={id}
aria-label={navLabel}
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-chat/src/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type ReactNode,
createContext,
createElement,
type ReactNode,
useCallback,
useContext,
useMemo,
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-chat/src/i18n/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* 包含聊天阶段状态的中英文字映射
*/

import { type Locale, defaultLocale } from "./index";
import { defaultLocale, type Locale } from "./index";

export type ChatStage =
| "received"
Expand Down
2 changes: 2 additions & 0 deletions apps/undefined-chat/src/image-viewer/ImageViewerModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export function ImageViewerModal({
>
<div
className="runtime-image-viewer-stage"
role="document"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
>
Expand All @@ -97,6 +98,7 @@ export function ImageViewerModal({
</div>
<div
className="runtime-image-viewer-toolbar"
role="group"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
>
Expand Down
4 changes: 2 additions & 2 deletions apps/undefined-chat/src/image-viewer/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { ImageViewerModal } from "./ImageViewerModal";
export type { ImageViewerModalProps } from "./ImageViewerModal";
export { useImageViewer } from "./useImageViewer";
export { ImageViewerModal } from "./ImageViewerModal";
export type { UseImageViewerReturn } from "./useImageViewer";
export { useImageViewer } from "./useImageViewer";
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import type { ComponentProps } from "react";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { LOCALE_STORAGE_KEY, LanguageProvider } from "../i18n";
import { LanguageProvider, LOCALE_STORAGE_KEY } from "../i18n";
import { commandInfo, subcommandInfo } from "../test-fixtures";
import { CommandPalette } from "./CommandPalette";
import { buildCommandContext, computeMatches } from "./command-context";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import userEvent from "@testing-library/user-event";
import type { ReactNode } from "react";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { LOCALE_STORAGE_KEY, LanguageProvider } from "../i18n";
import { LanguageProvider, LOCALE_STORAGE_KEY } from "../i18n";
import { commandInfo, subcommandInfo } from "../test-fixtures";
import { MessageComposer } from "./MessageComposer";

Expand Down
8 changes: 4 additions & 4 deletions apps/undefined-chat/src/message-composer/MessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import type { AttachmentDraft } from "../chat-store/store";
import { useTranslation } from "../i18n";
import type { CommandInfo, MessageReference } from "../runtime-client/types";
import { CommandPalette } from "./CommandPalette";
import { ReferenceChips } from "./ReferenceChips";
import {
type CommandMatch,
buildCommandContext,
buildReplacement,
type CommandMatch,
computeMatches,
findCommandByNameOrAlias,
} from "./command-context";
import { ReferenceChips } from "./ReferenceChips";

export type MessageComposerProps = {
attachmentQueue: AttachmentDraft[];
Expand Down Expand Up @@ -95,7 +95,7 @@ export function MessageComposer({
}, [focusRequest]);

// 输入框高度自适应
// biome-ignore lint/correctness/useExhaustiveDependencies: update height on value change
// 随输入内容更新高度。
useEffect(() => {
const textarea = textareaRef.current;
if (textarea) {
Expand Down Expand Up @@ -125,7 +125,7 @@ export function MessageComposer({

// 子命令模式下命令存在但无子命令 → 展示帮助卡片
const helpCommand = useMemo<CommandInfo | null>(() => {
if (!commandContext || commandContext.mode !== "subcommand") return null;
if (commandContext?.mode !== "subcommand") return null;
if (commandMatches.length > 0) return null;
const command = findCommandByNameOrAlias(
commandSuggestions,
Expand Down
Loading
Loading