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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Q_CODE_AUDIT_MAX_FILE_BYTES=52428800
Q_CODE_AUDIT_MAX_QUEUE_SIZE=1000
Q_CODE_AUDIT_PII=
Q_CODE_CRASH_GUARD=true
Q_CODE_MENTION_ALLOW_ABS=false
Q_CODE_SHELL_TIMEOUT_MS=60000
Q_CODE_SHELL_TIMEOUT_MAX_MS=1800000
Q_CODE_SHELL_MAX_BUFFER=4194304
Expand Down
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

`q-code` 是一个基于 Vercel AI SDK 的 TypeScript 命令行 Agent 框架。核心能力包括:

- **Agent / 任务**:Agent Loop、Plan Mode、Task V2、TodoWrite、上下文压缩、会话持久化(JSONL append-only)、项目记忆、Skills、SubAgent、Agent Teams、Worktree 隔离。
- **Agent / 任务**:Agent Loop、Plan Mode、Task V2、TodoWrite、上下文压缩、会话持久化(JSONL append-only)、`@file` 文件引用注入、项目记忆、Skills、SubAgent、Agent Teams、Worktree 隔离。
- **工具执行**:文件/搜索工具、可配置超时与 spill 的 Shell 工具、后台 Shell job(`f_status` / `f_tail` / `f_kill` / `f_list`)。
- **集成扩展**:MCP server、Hooks(pre/post tool-use 决策)、Slash 命令注册表、企业 AI 基建同步(Infra)、GitLab Wiki 知识库。
- **可观测性**:NDJSON 审计日志(默认开启)、崩溃保护(crash guard,默认开启)与 crash report、Usage / Cache / 成本统计、Token Budget。
Expand Down Expand Up @@ -76,6 +76,7 @@ pnpm build # 调 scripts/build.mjs,产出 dist/
- `src/runtime/`:早期 CLI 子命令路由(help/version/update/audit)、`getPackageVersion`、`runCliUpdate`、`installCrashGuard` 与崩溃报告生成。
- `src/config/`:`runtime-config.ts` 负责加载 `~/.q-code/config.toml`、`<cwd>/.q-code/config.toml`、`.env`,统一映射到 `process.env`(支持多 section/alias)。
- `src/session/`:`SessionStore`(JSONL append-only、原子写入、cache 模式与 usage 记录持久化)。
- `src/mentions/`:`@file` 文件引用解析、git/递归文件索引、fuzzy 排序、路径安全校验、文件内容截断和本轮上下文注入。
- `src/usage/`:token 归一化、定价、cache 策略、`UsageTracker` 与 `/usage` 渲染。
- `src/infra/`:企业 AI 基建配置同步(base URL / token / sync 状态 / 知识候选上报)。
- `src/gitlab-kb/`:GitLab Wiki 知识库读取/搜索/发布(`/gitlab-kb` 命令背后逻辑)。
Expand Down Expand Up @@ -103,6 +104,7 @@ pnpm build # 调 scripts/build.mjs,产出 dist/
- Prompt、工具描述、项目说明多为中文;新增用户可见文案时优先保持中文一致性。
- 新增环境变量需同时更新:(a) `.env.example`;(b) `src/config/runtime-config.ts` 的 `SECTION_ALIASES`(让 toml 配置可用);(c) README 配置表。
- 工具默认通过 `ToolRegistry.toAISDKFormat` 包装,会自动写 `tool.call` / `tool.result` 审计事件;新增工具入口或绕过 registry 时需自行接审计与 Hooks 管线(参考 `src/observability/audit.ts::getAuditLogger`)。
- `@file` mention 默认只能引用当前工作目录内文件,并必须校验 symlink 解析后的真实路径;绝对路径必须显式设置 `Q_CODE_MENTION_ALLOW_ABS=true`,并写 `user.mention` 审计事件。单文件/总附件预算变更需同步 README 和 `src/mentions/file-mentions.ts` 常量。
- Shell 工具默认只能在当前 `cwd` 内执行;跳出目录必须显式设置 `Q_CODE_SHELL_ALLOW_ABS_CWD=true`。长命令优先使用 `timeoutMs` 或 `background=true`,超大输出通过 `<Q_CODE_HOME>/shell-spills` 恢复全文,后台 job 元数据写 `<Q_CODE_HOME>/shell-jobs`。
- 自定义工具目录固定为 `~/.q-code/tools/<name>/` 与 `<cwd>/.q-code/tools/<name>/`;项目级覆盖用户级,用户级覆盖内置工具。每个工具目录必须提供 `schema.json`,其结构为 `Omit<ToolDefinition, 'isEnabled' | 'execute'> & { execute: string }`,其中 `execute` 会在该工具目录下作为 shell 命令运行。
- 新增 Slash 命令通过 `createSlashCommandRegistry` + `command(...)` 注册(见 `src/index.ts::createBuiltinSlashCommands`),并填好 `category`、`aliases`、`usage`,以便 `/help` 输出友好。
Expand All @@ -121,6 +123,7 @@ pnpm build # 调 scripts/build.mjs,产出 dist/
- Tool registry 改动:`vitest run tests/unit/tool-registry.test.ts`
- Shell 工具改动:`vitest run tests/unit/shell-tools.test.ts tests/integration/shell-streaming.test.ts`
- 自定义工具目录改动:`vitest run tests/unit/custom-tools.test.ts tests/unit/tool-registry.test.ts`
- `@file` 文件引用:`vitest run tests/unit/file-mentions.test.ts tests/unit/terminal.test.ts tests/unit/runtime-config.test.ts`
- 终端/输入状态机改动:`vitest run tests/unit/terminal.test.ts`
- 运行时配置/CLI 子命令:`vitest run tests/unit/runtime-config.test.ts tests/unit/cli-info.test.ts tests/unit/update.test.ts`
- 崩溃保护:`vitest run tests/unit/crash-guard.test.ts tests/unit/mcp-bootstrap.test.ts tests/unit/audit-logger.test.ts`
Expand All @@ -135,3 +138,4 @@ pnpm build # 调 scripts/build.mjs,产出 dist/
- 工作区可能存在用户改动;修改前先查看状态,避免覆盖不相关变更。
- pre-commit hook 由 `simple-git-hooks` 安装,默认执行 `pnpm precommit`。
- 只有在用户明确要求时才跳过 hook 或执行提交。
- 发现值得提issue的想法时,可以直接提到github issue中
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# q-code

基于 AI SDK 的命令行 Agent 框架,支持工具调用、可后台运行的 Shell 长任务、Plan Mode、Task V2 持久化任务图、上下文自动压缩、会话持久化、跨对话项目记忆、Skills 渐进式披露、后台 SubAgent、Worktree 隔离、Agent Teams 多智能体协作和 MCP 扩展。
基于 AI SDK 的命令行 Agent 框架,支持工具调用、可后台运行的 Shell 长任务、Plan Mode、Task V2 持久化任务图、上下文自动压缩、会话持久化、`@file` 文件引用、跨对话项目记忆、Skills 渐进式披露、后台 SubAgent、Worktree 隔离、Agent Teams 多智能体协作和 MCP 扩展。

## 技术栈

Expand Down Expand Up @@ -110,6 +110,7 @@ cp .env.example .env
| `Q_CODE_AUDIT_MAX_QUEUE_SIZE` | ❌ | 审计写入内存队列上限,默认 1000 |
| `Q_CODE_AUDIT_PII` | ❌ | 默认不写 prompt/tool 原文;设为 `full` 才写入原文 |
| `Q_CODE_CRASH_GUARD` | ❌ | 崩溃保护开关,默认开启;设为 `false` 可关闭全局兜底 handler |
| `Q_CODE_MENTION_ALLOW_ABS` | ❌ | 设为 true 后允许 `@file` 引用绝对路径;默认只允许当前目录内路径 |
| `Q_CODE_SHELL_TIMEOUT_MS` | ❌ | `f` 同步命令默认超时,默认 60000ms |
| `Q_CODE_SHELL_TIMEOUT_MAX_MS` | ❌ | `f.timeoutMs` 上限,默认 1800000ms(30 分钟) |
| `Q_CODE_SHELL_MAX_BUFFER` | ❌ | `f` 同步输出内存阈值,默认 4194304(4MB),超出后落盘 spill |
Expand Down Expand Up @@ -163,6 +164,22 @@ pnpm run continue # 恢复上次会话

默认在交互式 TTY 中启动 Ink TUI;非 TTY、`--classic` 或 `Q_CODE_TUI=0` 会回退到传统 readline。TUI 将 Agent 输出、工具调用、上下文占用、任务进度、后台 Agent 和 token 用量统一渲染为事件流,支持 `Shift+Enter`/`Ctrl+J` 多行输入、`Ctrl+R` 历史搜索、`Esc` 清空/恢复输入、忙时 `Ctrl+C` 中断当前任务和 Markdown 代码块/列表/表格展示。输入区使用真实终端光标锚定输入法候选窗,避免 macOS IME 跑到屏幕角落。

### @file 文件引用

在 TUI 输入框中输入 `@` 后跟文件名片段,会出现基于仓库文件索引的 fuzzy 候选;使用方向键切换,`Tab` 插入当前候选。例如输入 `@rou` 可以补全到匹配的源码或文档路径。

提交消息时,`@file` 会把文件内容注入本轮用户上下文,并写入 `user.mention` 审计事件。支持以下形式:

```text
请解释 @src/runtime/cli-info.ts
只看一行 @src/runtime/cli-info.ts:42
只看范围 @src/runtime/cli-info.ts:10-30
定位正则 @src/runtime/cli-info.ts:#getEarlyCliCommand
路径含空格 @"My Project/notes.md"
```

默认只允许引用当前工作目录内的文件,并会校验 symlink 指向的真实路径;绝对路径如 `@/etc/passwd` 会被阻止,确需引用绝对路径时设置 `Q_CODE_MENTION_ALLOW_ABS=true`。单个引用最多注入 50KB,单轮全部引用合计最多 200KB,超出时会截断或明确提示丢弃。文件候选优先使用 git 索引,非 git 目录会回退递归扫描;超过 20000 个文件时候选会裁剪并在 TUI 中提示。

### npm 发布

仓库已配置为可发布的 npm CLI 包:
Expand Down Expand Up @@ -214,6 +231,7 @@ src/
│ └── memory-types.ts# 记忆类型定义与引导指令
├── session/
│ └── store.ts # JSONL 会话持久化
├── mentions/ # @file 文件引用解析、索引、fuzzy 补全和上下文注入
├── skills/ # SKILL.md 加载、渐进式披露、条件激活
├── agents/
│ ├── bootstrap.ts # SubAgent 启动加载
Expand Down
4 changes: 4 additions & 0 deletions src/config/runtime-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const SECTION_ALIASES: Record<string, Record<string, string>> = {
audit_max_queue_size: 'Q_CODE_AUDIT_MAX_QUEUE_SIZE',
audit_pii: 'Q_CODE_AUDIT_PII',
crash_guard: 'Q_CODE_CRASH_GUARD',
mention_allow_abs: 'Q_CODE_MENTION_ALLOW_ABS',
shell_timeout_ms: 'Q_CODE_SHELL_TIMEOUT_MS',
shell_timeout_max_ms: 'Q_CODE_SHELL_TIMEOUT_MAX_MS',
shell_max_buffer: 'Q_CODE_SHELL_MAX_BUFFER',
Expand All @@ -66,6 +67,9 @@ const SECTION_ALIASES: Record<string, Record<string, string>> = {
allow_abs_cwd: 'Q_CODE_SHELL_ALLOW_ABS_CWD',
kill_bg_on_exit: 'Q_CODE_SHELL_KILL_BG_ON_EXIT'
},
mention: {
allow_abs: 'Q_CODE_MENTION_ALLOW_ABS'
},
audit: {
enabled: 'Q_CODE_AUDIT_ENABLED',
dir: 'Q_CODE_AUDIT_DIR',
Expand Down
36 changes: 35 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ import {
createUserPromptPayload,
getAuditLogger
} from './observability/audit'
import {
createFileMentionIndex,
createUserMentionPayload,
expandFileMentions,
type FileMentionIndex
} from './mentions'

const packageVersion = getPackageVersion()
const earlyCliCommand = getEarlyCliCommand(process.argv.slice(2))
Expand Down Expand Up @@ -903,6 +909,9 @@ async function main() {
category: 'Skills'
}))
]
const fileMentionIndex: FileMentionIndex | undefined = useTui
? await createFileMentionIndex(activeStore.cwd)
: undefined

if (useTui) {
registry.setQuiet(true)
Expand All @@ -915,6 +924,7 @@ async function main() {
cwd: activeStore.cwd,
initialEvents: pendingTerminalEvents,
slashCommands: buildSlashCommandSuggestions(),
fileMentionIndex,
onSubmit: handleInput,
onInterrupt: interruptActiveTurn,
onExit: closeCli
Expand Down Expand Up @@ -1045,7 +1055,31 @@ async function main() {
}

async function runAgentTurn(userContent: string): Promise<void> {
const userMsg: ModelMessage = { role: 'user', content: userContent }
const mentionExpansion = expandFileMentions(userContent, { cwd: activeStore.cwd })
if (mentionExpansion.results.length > 0) {
getAuditLogger().emit(
'user.mention',
createUserMentionPayload(mentionExpansion),
{ sessionId, cwd: activeStore.cwd, agent: { kind: 'main' } }
)

if (mentionExpansion.included.length > 0) {
print(
`\n [@file] 已注入 ${mentionExpansion.included.length} 个文件,合计 ${mentionExpansion.totalBytes} bytes`
)
}
for (const warning of mentionExpansion.warnings) {
print(`\n [@file] ${warning}`)
}

const activated = activateConditionalSkillsForPaths(mentionExpansion.paths, activeStore.cwd)
if (activated.length > 0) {
print(`\n [Skills] 条件激活: ${activated.join(', ')}`)
emitTerminal({ type: 'slash_commands', commands: buildSlashCommandSuggestions() })
}
}

const userMsg: ModelMessage = { role: 'user', content: mentionExpansion.prompt }
await runAgentTurnWithMessages([userMsg], userContent)
}

Expand Down
Loading
Loading