qi-coding-agent 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. qi_agent/__init__.py +18 -0
  2. qi_agent/abort.py +40 -0
  3. qi_agent/auth.py +216 -0
  4. qi_agent/cli.py +2178 -0
  5. qi_agent/compaction.py +475 -0
  6. qi_agent/config.py +409 -0
  7. qi_agent/docs/cli.md +188 -0
  8. qi_agent/docs/compaction.md +148 -0
  9. qi_agent/docs/configuration.md +89 -0
  10. qi_agent/docs/containerization.md +80 -0
  11. qi_agent/docs/docs.json +1 -0
  12. qi_agent/docs/environment-variables.md +47 -0
  13. qi_agent/docs/extensions.md +382 -0
  14. qi_agent/docs/how-qi-works.md +93 -0
  15. qi_agent/docs/index.md +61 -0
  16. qi_agent/docs/json.md +112 -0
  17. qi_agent/docs/keybindings.md +83 -0
  18. qi_agent/docs/models.md +138 -0
  19. qi_agent/docs/packages.md +155 -0
  20. qi_agent/docs/providers.md +288 -0
  21. qi_agent/docs/quickstart.md +109 -0
  22. qi_agent/docs/security.md +69 -0
  23. qi_agent/docs/session-format.md +299 -0
  24. qi_agent/docs/sessions.md +127 -0
  25. qi_agent/docs/settings.md +138 -0
  26. qi_agent/docs/skills.md +124 -0
  27. qi_agent/docs/slash-commands.md +55 -0
  28. qi_agent/docs/themes.md +78 -0
  29. qi_agent/docs/tui.md +109 -0
  30. qi_agent/docs/usage.md +112 -0
  31. qi_agent/export_html.py +95 -0
  32. qi_agent/extensions.py +2267 -0
  33. qi_agent/llm.py +609 -0
  34. qi_agent/loader.py +299 -0
  35. qi_agent/model_catalog.py +108 -0
  36. qi_agent/models.py +103 -0
  37. qi_agent/packages.py +430 -0
  38. qi_agent/paths.py +242 -0
  39. qi_agent/pep723.py +74 -0
  40. qi_agent/presets.py +293 -0
  41. qi_agent/prompt.py +150 -0
  42. qi_agent/registry.py +485 -0
  43. qi_agent/runner.py +584 -0
  44. qi_agent/runtime.py +1940 -0
  45. qi_agent/session.py +739 -0
  46. qi_agent/settings.py +501 -0
  47. qi_agent/share.py +111 -0
  48. qi_agent/system_prompt.py +278 -0
  49. qi_agent/theme.py +389 -0
  50. qi_agent/themes/dark.json +90 -0
  51. qi_agent/themes/light.json +89 -0
  52. qi_agent/titling.py +90 -0
  53. qi_agent/tools/__init__.py +349 -0
  54. qi_agent/tools/shell.py +276 -0
  55. qi_agent/trust.py +106 -0
  56. qi_agent/tui.py +6224 -0
  57. qi_agent/workspaces.py +116 -0
  58. qi_coding_agent-0.1.0.dist-info/METADATA +151 -0
  59. qi_coding_agent-0.1.0.dist-info/RECORD +62 -0
  60. qi_coding_agent-0.1.0.dist-info/WHEEL +4 -0
  61. qi_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
  62. qi_coding_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,148 @@
1
+ # 上下文压缩
2
+
3
+ 长会话迟早会超出模型窗口。qi 把「太久以前的部分」压成一段**结构化摘要**再继续跑 —— 不是丢弃,
4
+ 也不是简单截断。
5
+
6
+ 两种机制共用同一套摘要格式与同一条 LLM 调用路径:
7
+
8
+ | 机制 | 触发 | 压什么 | 落成 |
9
+ | --- | --- | --- | --- |
10
+ | **压缩** compaction | 上下文超阈值,或用户 `/compact` | 旧消息 | `type=compaction` entry(+ `firstKeptEntryId`) |
11
+ | **分支摘要** branch summary | `/tree` 跳到别的分支 | 「被放弃的那段」 | `type=branch_summary` entry |
12
+
13
+ **事实源:`src/qi_agent/compaction.py` + `runtime.py` 的 `_history()` / `_maybe_auto_compact()`。**
14
+
15
+ ## 1. 什么时候自动压
16
+
17
+ 开新一回合**之前**检查一次(`_maybe_auto_compact`):
18
+
19
+ ```text
20
+ tokens > contextWindow - reserveTokens → 压
21
+ ```
22
+
23
+ - **`reserveTokens` 是留给模型回答的余量**(默认 16384),所以阈值不是窗口本身。
24
+ - **预算口径是"重建后的上下文 + 基座提示词",不是原始 entry 之和**:
25
+ `messages_tokens(_history(session)) + estimate_tokens(base_prompt)`。
26
+ 用原始 entry 求和会把**已经压掉的内容再算一遍** —— 于是压完还超,反复压。
27
+ - `contextWindow` 来自当前模型的 spec;拿不到窗口(`<= 0`)或 `compaction.enabled=false` 就整段跳过。
28
+
29
+ 相关设置(`settings.json` 的 `compaction`,见 [settings.md](settings.md)):
30
+
31
+ | 键 | 默认 | 说明 |
32
+ | --- | --- | --- |
33
+ | `enabled` | `true` | 关掉后不再自动压(手动 `/compact` 仍可用) |
34
+ | `reserveTokens` | `16384` | 给回答留的余量 |
35
+ | `keepRecentTokens` | `20000` | 压完之后**至少保留**的近期内容 |
36
+
37
+ 非法值(非正整数)一律**回落到默认**,不报错 —— 一个写错的数字不该让会话跑不起来。
38
+
39
+ ## 2. 压什么、留什么:切点规则
40
+
41
+ 从**最新往回**累加 token,累到 `keepRecentTokens` 时把切点放在**不早于**该位置的最近一个合法切点。
42
+ 合法切点是:
43
+
44
+ - `user` / `assistant` 消息
45
+ - `compaction` / `branch_summary` entry
46
+
47
+ **绝不在工具结果上切** —— 工具结果必须跟在它的调用后面,切在那里会让模型看到一次没有调用的结果。
48
+
49
+ 配套的一个细节:切点会**向前吞掉相邻的不进上下文的 entry**(旧 `dispatch`、叙述、思考),让切点更贴近
50
+ 预算。但工具结果虽然也不进上下文,**不属于可吞的元数据** —— 吞掉它等于把切点挪到工具结果上。
51
+
52
+ ### 单个 turn 就超预算:split turn
53
+
54
+ 如果切点落在一个 turn 中间(前面还有 `user` 消息),说明**这一个回合本身就超了预算**。这时做
55
+ **split turn**:
56
+
57
+ 1. 历史部分照常摘要;
58
+ 2. 这个 turn 的**前缀**(从 turn 起点到切点)单独摘要一次;
59
+ 3. 两段用 `**Turn Context (split turn):**` 分隔后拼成一条摘要。
60
+
61
+ 好处是模型不会看到"半个回合"——要么整段都在摘要里,要么整段都还在上下文里。
62
+
63
+ ## 3. 摘要长什么样
64
+
65
+ 固定 7 段的结构化格式(`SUMMARIZATION_PROMPT`):`Goal` · `Constraints & Preferences` · `Progress`
66
+ (`Done`/`In Progress`/`Blocked`)· `Key Decisions` · `Next Steps` · `Critical Context`。
67
+
68
+ 提示词里两条硬要求:**保持每段简洁**、**原样保留文件路径 / 函数名 / 错误信息**。
69
+
70
+ **第二次之后的压缩用 `UPDATE_SUMMARIZATION_PROMPT`**:它要求保留旧摘要里的全部信息,只增改
71
+ (把 `In Progress` 里完成的挪进 `Done`、更新 `Next Steps`)。否则每次压缩都会把更早的决策洗掉 ——
72
+ 长会话跑到第十次压缩时,模型就不知道最初的目标了。
73
+
74
+ 摘要调用本身也是**一次普通 LLM 调用**,但:
75
+
76
+ - 用 `system` 提示词明确禁止"接着对话"(否则模型会把摘要写成回答);
77
+ - 会话内容用 `<conversation>` 包裹、旧摘要用 `<previous-summary>` 包裹;
78
+ - **模型试图调用工具就直接报错**(`RuntimeError`):摘要请求必须是一次纯文本输出。
79
+
80
+ ## 4. 摘要怎么回到上下文
81
+
82
+ `_history()` 的装配规则:
83
+
84
+ ```text
85
+ [system 提示词]
86
+ [上下文已压缩] <summary> [摘要结束,以下是其后的消息] ← 用 user 角色,不是 system
87
+ 从 firstKeptEntryId 开始的消息 …
88
+ 沿途的 branch_summary(用「另一条分支的摘要」标签)
89
+ ```
90
+
91
+ 三个刻意的选择:
92
+
93
+ 1. **摘要包成 `user` 消息而不是 `system`**:各 provider 对"多个 system"支持不一,而摘要本来就是
94
+ "过去那段对话"的替身。
95
+ 2. **切点由落盘的 `firstKeptEntryId` 决定,不由摘要文本决定**。摘要只负责"讲清楚",保留范围是数据,
96
+ 所以压缩后不会因为模型摘要写得长短而改变窗口。
97
+ 3. **没压过时退回「最近 40 条」的简易窗口**。那是压缩接管窗口之前的旧行为;一旦有过压缩,窗口就
98
+ 完全由摘要 + 切点决定。
99
+
100
+ ## 5. 落盘的 entry
101
+
102
+ ```jsonc
103
+ // 压缩
104
+ {"type":"compaction","summary":"…","firstKeptEntryId":"a1b2c3d4e5f6",
105
+ "tokensBefore":41230,"usage":{…}}
106
+
107
+ // 分支摘要
108
+ {"type":"branch_summary","summary":"…","fromId":"…","usage":{…}}
109
+ ```
110
+
111
+ `tokensBefore` 是**压缩前**的估算值(前端用它显示"从多少压到多少")。字段全集见
112
+ [session-format.md](session-format.md) §5。
113
+
114
+ `compact_session()` 在**没什么可压时返回 `None`**,调用方据此提示用户,而不是硬写一条空摘要。
115
+ 同一分支上已经压过一次、之后没有任何新内容时也返回 `None`(避免连点两次 `/compact` 压出两条)。
116
+
117
+ ## 6. 分支摘要:`/tree` 跳分支时
118
+
119
+ `/tree` 跳到旧节点继续时,原来那条分支上的内容会离开上下文。为了让"切回来"之后不丢,qi 把
120
+ **被放弃的那段**压成一条 `branch_summary`,挂到跳过去的位置下:
121
+
122
+ - 要摘要的范围 = 从 `from_id` **沿 parentId 往回走到共同祖先(不含祖先)**;
123
+ - 只收"会进上下文"的 entry(`is_context_entry`:消息、压缩、分支摘要);
124
+ - **必须在移动 `position` 之前**取好源分支(移完再取拿到的就是目标分支,摘要会静默变成空);
125
+ - 先 `set_position(目标)` 再 `append(摘要)`,于是新 leaf 就是这条摘要。
126
+
127
+ ## 7. 失败与边界
128
+
129
+ | 情况 | 行为 |
130
+ | --- | --- |
131
+ | 自动压缩失败(模型报错/超时) | **不卡住这一轮**:发一个 error 事件后继续跑。压缩是优化,不是前置条件 |
132
+ | 没什么可压 | `compact_session()` 返回 `None`,前端提示"没有可压缩的内容" |
133
+ | 老 entry 没有 `id` | 不给 id 就没法被 `firstKeptEntryId` 引用 → 直接不压(不猜) |
134
+ | 摘要模型试图调工具 | 抛错(见 §3) |
135
+ | `settings.compaction` 值非法 | 回落到默认,不报错 |
136
+
137
+ ## 8. 扩展能干预压缩吗
138
+
139
+ 能,三个事件(见 [extensions.md](extensions.md) §4)。压缩**只有一个入口**
140
+ (`QiRuntime.compact_session`:TUI 的 `/compact` 与自动压缩都走它),所以三个事件都在那一处发:
141
+
142
+ | 事件 | 时机 | 契约 |
143
+ | --- | --- | --- |
144
+ | `session_before_compact` | 已经算出怎么切、还没调模型 | `{cancel: true}` 拦下这次压缩;或 `{summary: "…"}` 自带摘要 —— **自带时不调模型**(扩展可能比模型更清楚该记住什么) |
145
+ | `session_compact` | 成功落盘后 | `{entry, summary, provided}`(`provided=true` 表示摘要是扩展给的) |
146
+ | `session_compact_failed` | 摘要调用/落盘失败 | `{error}` —— **先发事件,再把异常抛给调用方**(失败必须传出去,但扩展也该看得见) |
147
+
148
+ handler 抛异常不影响压缩本身:记一条 note 后继续(与其它事件同一条规矩)。
@@ -0,0 +1,89 @@
1
+ # 配置:qi 目录与上下文文件
2
+
3
+ qi 的配置分**用户级**与**项目级**两层。
4
+
5
+ - 用户级 = **agent 目录**,默认 `~/.qi/agent`(`QI_AGENT_HOME` 可改),所有用户级状态都挂在这里。
6
+ - 项目级 = 工作目录下的 `.qi/`,跟仓库走、可提交共享。
7
+
8
+ 项目级**扩展**要过[项目信任](security.md)才加载;`settings.json` 的普通字段(`sessionDir`、`theme`、
9
+ `defaultTools`…)与文本指令(`AGENTS.md`、技能)**不受门控**。
10
+
11
+ 改完手动编辑的文件(设置、指令、资源)后,在 TUI 里 `/reload` 一次。
12
+
13
+ ## agent 目录(用户级)
14
+
15
+ 下表里写成 `<agent-dir>`:
16
+
17
+ | 路径 | 职责 |
18
+ | --- | --- |
19
+ | `<agent-dir>/settings.json` | 用户级[设置](settings.md):偏好、默认值、资源路径、扩展声明(`packages`) |
20
+ | `<agent-dir>/models.json` | provider 与模型定义、`apiKey` 引用 → [models.md](models.md) |
21
+ | `<agent-dir>/auth.json` | 凭证(0600,按 provider)→ [providers.md](providers.md) |
22
+ | `<agent-dir>/trust.json` | 按目录记住的信任决定(`/trust` 写它) |
23
+ | `<agent-dir>/sessions/` | 会话 JSONL(`settings.sessionDir` 可改)→ [sessions.md](sessions.md) |
24
+ | `<agent-dir>/skills/` | 用户级[技能](skills.md) |
25
+ | `<agent-dir>/extensions/` | 用户级[扩展](extensions.md),一目录一个扩展 |
26
+ | `<agent-dir>/SYSTEM.md` | **整体替换**默认基座(见下) |
27
+ | `<agent-dir>/AGENTS.md`(或 `AGENTS.override.md` / `CLAUDE.md`) | 跨工作目录的用户指令 |
28
+ | `<agent-dir>/agents/`、`<agent-dir>/mcp.json` | **官方扩展自己的目录**:角色(qi-agents)与 MCP server(qi-mcp)—— 没装对应扩展就是普通目录 |
29
+
30
+ ## 项目 `.qi` 目录
31
+
32
+ | 路径 | 职责 |
33
+ | --- | --- |
34
+ | `.qi/settings.json` | 项目级设置(键级深合并覆盖用户级) |
35
+ | `.qi/models.json` | 项目级 provider / 模型定义(优先于用户级) |
36
+ | `.qi/SYSTEM.md` | **整体替换**本项目里的基座 |
37
+ | `.qi/extensions/` | 项目级扩展(**信任后才加载**) |
38
+ | `.qi/skills/` | 项目级技能 |
39
+ | `.qi/agents/`、`.qi/mcp.json` | 同上:官方扩展自己的目录与声明 |
40
+
41
+ `SYSTEM.md` 有项目级就用项目级(同名文件**不合并**)。
42
+
43
+ ## 上下文文件
44
+
45
+ 上下文文件与 `.qi/` 配置是两回事:qi 从 **agent 目录**、**工作目录**以及**它的祖先目录**里找,
46
+ 在某个目录或其子目录里跑都生效。每级目录按候选顺序取**第一个**命中:
47
+
48
+ ```text
49
+ AGENTS.override.md > AGENTS.md > AGENTS.MD > CLAUDE.md > CLAUDE.MD
50
+ ```
51
+
52
+ 顺序 = 用户级 → 项目祖先链**由远到近**(含 cwd),按路径去重;祖先链**止于 git 根**。
53
+ `AGENTS.override.md` 只在**同一目录**里顶替 `AGENTS.md` / `CLAUDE.md`,不会压掉其它目录的。
54
+ 空文件视为未配置。上下文文件的发现**不需要项目信任**。
55
+
56
+ 注入位置在 `<project_context>` 块里,是系统提示词的一部分(见 [how-qi-works.md](how-qi-works.md))。
57
+
58
+ ## 换掉或追加系统提示词
59
+
60
+ | 入口 | 语义 | 落盘 |
61
+ | --- | --- | --- |
62
+ | `.qi/SYSTEM.md` / `<agent-dir>/SYSTEM.md` | **整体替换**基座(项目 > 用户) | 是(仓库 / 用户文件) |
63
+ | `qi --system-prompt "<文本\|文件>"` | **整体替换**基座(只本次运行) | 否 |
64
+ | `qi --append-system-prompt "<文本\|文件>"` | **追加**到每回合提示词末尾(可重复) | 否 |
65
+ | 扩展的 `before_agent_start` | 改 `system_prompt`(链式) | 否 |
66
+
67
+ **副作用**:默认基座里的「可用工具」「指南」两块跟基座绑在一起,写了 `SYSTEM.md` 就等于接管基座、
68
+ 那两块不再出现。写自定义基座时请自己交代工具约定,例如:
69
+
70
+ ```markdown
71
+ 你是本仓库(订单服务)的开发助手。
72
+
73
+ 可用工具:`read` / `ls` / `find` / `grep` / `edit` / `write` / `bash` / `clarify`。
74
+ - `bash` 不做命令级过滤,行为由本仓库约定约束。
75
+ - 需求不明确时先用 `clarify` 提问。
76
+
77
+ ## 本仓库环境
78
+ - 跑测试:`uv run pytest -q`
79
+ - 数据库迁移在 `migrations/`,只允许新增文件。
80
+ ```
81
+
82
+ **动态块不受影响**:项目上下文、技能清单、手册索引、工作目录照旧追加 —— 不是"整条提示词只剩你的文件"。
83
+ (实现细节见 [design/internals.md](../design/internals.md)。)
84
+
85
+ ## qi 没有的配置项
86
+
87
+ - **prompt 模板**:qi 不做可复用提示词模板;要固定前缀就写进 `SYSTEM.md`,或做成技能。
88
+ - **键位配置文件**:qi 没有 `keybindings.json`;键位固定,扩展可用 `register_shortcut` 加自己的。见 [keybindings.md](keybindings.md)。
89
+ - **用户主题目录**:只有内置 `dark` / `light`,`theme` 只认这三个值(`auto` 是探测)。见 [themes.md](themes.md)。
@@ -0,0 +1,80 @@
1
+ # 隔离运行(容器 / VM)
2
+
3
+ qi **不做进程内沙箱**:`bash` 能跑任何东西,文件工具只是"相对路径基于会话 cwd"的约定。
4
+ 工具与扩展都以 qi 进程本身的权限运行(见 [security.md](security.md))。所以**真边界必须来自 qi 之外**。
5
+
6
+ 需要隔离的典型场景:
7
+
8
+ - 无人值守地跑(`-p` / `--mode json` 喂给 CI),没人盯着它按 `escape`;
9
+ - 让 qi 碰不信任的仓库 —— 里面可能有 `.qi/extensions/`(`-na` 只挡加载,不挡 `bash`);
10
+ - 把 qi 暴露给别的机器或别的用户。
11
+
12
+ ## 选一条隔离路线
13
+
14
+ | 路线 | 得到什么 | 代价 |
15
+ | --- | --- | --- |
16
+ | **容器**(Docker / Podman) | 文件系统与进程隔离;工作目录按需挂载 | 要自己搭镜像与挂载;凭证得想办法传进去 |
17
+ | **VM** | 与宿主机完全隔离(内核级) | 重、启动慢,适合长期跑或跑高风险任务 |
18
+ | **受限用户 + 只读挂载** | 便宜:换个没有权限的用户跑,仓库只读挂 | 同一内核,隔离弱于容器 |
19
+ | **远端机器 / 一次性实例** | 物理隔离 | 要同步代码与密钥 |
20
+
21
+ **共同原则**:只把**要干活的目录**放进去;**凭证走运行时注入**(环境变量 / 只读挂载),不要烤进镜像;
22
+ `~/.qi/agent/`(会话、trust、auth)单独挂一个卷,否则每次重建都会丢历史。
23
+
24
+ ## 容器示例
25
+
26
+ qi 目前**没有发布到 PyPI**(见 [quickstart.md](quickstart.md)),所以镜像是"把仓库放进去装一遍":
27
+
28
+ ```dockerfile
29
+ FROM python:3.12-slim
30
+ RUN apt-get update && apt-get install -y --no-install-recommends git bash \
31
+ && rm -rf /var/lib/apt/lists/*
32
+ RUN pip install --no-cache-dir uv
33
+
34
+ WORKDIR /opt/qi
35
+ COPY . /opt/qi
36
+ RUN uv sync --no-dev && ln -s /opt/qi/.venv/bin/qi /usr/local/bin/qi
37
+
38
+ # 工作目录由运行时挂载进来;agent 目录单独挂卷(会话与 trust 都在这)
39
+ VOLUME ["/root/.qi/agent"]
40
+ WORKDIR /workspace
41
+ ENTRYPOINT ["qi"]
42
+ ```
43
+
44
+ ```bash
45
+ docker build -t qi-local .
46
+ docker run --rm -it \
47
+ -v "$PWD:/workspace" \
48
+ -v qi-home:/root/.qi/agent \
49
+ -e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
50
+ qi-local
51
+ ```
52
+
53
+ 要点:
54
+
55
+ - **凭证只从环境变量进**(或把 `auth.json` 只读挂载进去),不要 `COPY` 进镜像层 —— 镜像层会被推送、缓存。
56
+ - **工作目录挂载的是宿主机的仓库**,所以 qi 的写操作会真的落到宿主机上;要更安全就挂 `:ro` 再把可写区
57
+ 单独挂成临时目录。
58
+ - **`~/.qi/agent/` 用卷**:会话历史、`trust.json`、agent 级设置都在里面;不挂就每次重建。
59
+ - 想跑无头:`docker run --rm -v "$PWD:/workspace" qi-local -p "把 CHANGELOG 里最近 10 条整理成一句话"`。
60
+
61
+ ## 只读工作目录 + 可写输出目录
62
+
63
+ 只想让它**看**代码、把结果写到别处:
64
+
65
+ ```bash
66
+ docker run --rm -v "$PWD:/workspace:ro" -v "$PWD/out:/out" -w /out qi-local -p "审查 /workspace/src"
67
+ ```
68
+
69
+ 文件工具的越界检查只覆盖 `read` / `ls` / `find` / `grep` / `write` / `edit`,而 `bash` 不受限 ——
70
+ 所以**只读挂载是这里唯一真正的保证**,不是提示词。
71
+
72
+ ## 网络
73
+
74
+ qi 只需要能连到模型 provider。要收紧就在容器/防火墙层限制出网;qi 没有内置的网络开关
75
+ (`--offline` 被接受但不做任何事,因为启动期本来就没有网络操作)。
76
+
77
+ ## 与其它页面的关系
78
+
79
+ - [security.md](security.md):有哪些闸门、哪些地方没有闸门;信任门控挡什么。
80
+ - [bash-allowlist.md](../design/bash-allowlist.md):为什么不做命令白名单(以及它为什么不是边界)。
@@ -0,0 +1 @@
1
+ {"_note":["qi 的文档导航清单,形状对齐 pi 的 docs/docs.json(navigation + redirects),可以直接被文档站消费。","navigation = 已验证存在的文档(每条 path 都必须在 docs/ 下真实存在);分组照 pi 的三段式:开始 / 指南(运行 · 定制 · 构建)/ 参考。","planned = qi 增补字段(pi 无),是补齐文档的施工清单:补完一篇,就把该条从 planned 移进 navigation 的对应分类。","planned[].source_of_truth = 写这篇时必须对照的唯一事实来源;不照它写就会产生『文档写了、代码没做』的行为不一致。","planned[].blocker = 非空表示这篇现在**不许写**:功能本身还没实现,先改代码或先撤掉承诺。"],"navigation":[{"title":"开始","items":[{"title":"文档索引","path":"index.md"},{"title":"快速开始","path":"quickstart.md"},{"title":"qi 是怎么工作的","path":"how-qi-works.md"}]},{"title":"指南","items":[{"title":"运行 qi","items":[{"title":"在终端里用 qi","path":"usage.md"},{"title":"选一个模型","path":"models.md"},{"title":"会话与上下文","path":"sessions.md"},{"title":"安全地运行","path":"security.md"},{"title":"隔离运行(容器 / VM)","path":"containerization.md"}]},{"title":"定制 qi","items":[{"title":"配置:qi 目录与上下文文件","path":"configuration.md"},{"title":"技能","path":"skills.md"},{"title":"主题","path":"themes.md"},{"title":"扩展的安装与声明","path":"packages.md"}]},{"title":"构建于 qi","items":[{"title":"写扩展","path":"extensions.md"},{"title":"终端 UI 组件","path":"tui.md"}]}]},{"title":"参考","items":[{"title":"命令行","path":"cli.md"},{"title":"斜杠命令","path":"slash-commands.md"},{"title":"设置(settings.json)","path":"settings.md"},{"title":"环境变量","path":"environment-variables.md"},{"title":"键位","path":"keybindings.md"},{"title":"供应商与凭证","path":"providers.md"},{"title":"会话文件格式","path":"session-format.md"},{"title":"上下文压缩","path":"compaction.md"},{"title":"JSON 事件流","path":"json.md"}]}],"redirects":[{"from":"PLAN.md","to":"../design/PLAN.md"},{"from":"web.md","to":"../design/web.md"},{"from":"plugins.md","to":"../design/plugins.md"},{"from":"dispatcher.md","to":"../design/dispatcher.md"},{"from":"bash-allowlist.md","to":"../design/bash-allowlist.md"},{"from":"tools.md","to":"how-qi-works.md"},{"from":"system-prompt.md","to":"configuration.md"},{"from":"model-config.md","to":"models.md"},{"from":"development.md","to":"../design/development.md"}],"planned":[],"excluded":[{"pi":"prompt-templates.md","reason":"qi 没有 prompt 模板功能(grep promptPaths/prompt_dir 零命中)。要么做,要么不写。"},{"pi":"sdk.md","reason":"qi 没有 createAgentSession 式公开 SDK;等价面是扩展 API,见 docs/extensions.md。"},{"pi":"rpc.md / rpc-commands.md / rpc-extension-ui.md / cli-integration.md","reason":"qi 没有 stdio RPC 模式(--mode 只有 text|json),也没有多语言 SDK;程序化使用只有 JSON 事件流(docs/json.md)。"},{"pi":"message-types.md","reason":"qi 的消息/entry 类型表在 docs/session-format.md §5,不单开一页。"},{"pi":"custom-provider.md","reason":"自定义 provider 在 qi 走 models.json 的 provider 段 + 扩展的 register_provider,已有专页覆盖(docs/models.md、docs/extensions.md)。"},{"pi":"windows.md / termux.md / tmux.md / terminal-setup.md / shell-aliases.md","reason":"平台与外部工具集成,qi 不做;真隔离路线见 docs/containerization.md。"},{"pi":"llama-cpp.md","reason":"本地模型由 docs/models.md 的『连接本地模型』覆盖,不做专用路由器集成。"}]}
@@ -0,0 +1,47 @@
1
+ # 环境变量
2
+
3
+ 分三类:**qi 自己的配置变量**、**qi 注入给 shell 工具子进程的会话变量**,以及**各 provider 的凭证变量**
4
+ (后者在 [providers.md](providers.md))。
5
+
6
+ ## qi 的配置变量
7
+
8
+ | 变量 | 作用 |
9
+ | --- | --- |
10
+ | `QI_AGENT_HOME` | **agent 目录本身**(默认 `~/.qi/agent`)。全部用户级状态的挂载点:`settings.json` / `models.json` / `auth.json` / `sessions/` / `skills/` / `extensions/` / `trust.json` |
11
+ | `QI_CONFIG_DIR` | 名字空间根(默认 `~/.qi`)。仅用于推导默认路径与旧布局迁移 |
12
+ | `QI_AGENT_CONFIG` | **直接指定 `models.json` 文件**(优先于项目 / 全局路径) |
13
+ | `QI_THEME` | `dark` / `light` / `auto`,覆盖 `settings.theme` → [themes.md](themes.md) |
14
+
15
+ > `QI_AGENT_HOME` 指向的是**目录本身**,不是它的父目录 —— 拿它去拼 `agent/settings.json` 会得到
16
+ > `…/agent/agent/settings.json`。这是这个变量最常见的误用。
17
+
18
+ `models.json` 的完整查找顺序是 `QI_AGENT_CONFIG` → `<项目>/.qi/models.json` → `~/.qi/agent/models.json`,
19
+ 见 [models.md](models.md)。
20
+
21
+ ## 注入给 shell 工具的会话变量
22
+
23
+ `bash` 与 `powershell` 的**子进程**(以及它们拉起的命令)会拿到这五个:
24
+
25
+ | 变量 | 值 |
26
+ | --- | --- |
27
+ | `QI_SESSION_ID` | 当前会话 id |
28
+ | `QI_SESSION_FILE` | 会话 JSONL 的绝对路径 |
29
+ | `QI_PROVIDER` / `QI_MODEL` | 当前模型(子运行里是**子运行自己的**模型) |
30
+ | `QI_REASONING_LEVEL` | 当前思考级别 |
31
+
32
+ **为什么需要**:换模型是**界面状态**,不作为消息进对话 —— 所以 agent 无从"感知"切换动作,
33
+ `env | grep QI_` 是它唯一能**自证**"我现在跑的是什么"的通道(同一条也写进了 bash 的
34
+ prompt guidelines)。
35
+
36
+ 两条约定:
37
+
38
+ - 注入前**先删掉**这五个键再填 —— 否则子运行里跑的 bash 会继承父的值。
39
+ - 取不到的字段**不设**,而不是设成空串(空串会被读成"设过了,值是空的")。
40
+
41
+ 它们**只注入子进程**,不改变 qi 自己进程的环境。
42
+
43
+ ## provider 凭证变量
44
+
45
+ `DEEPSEEK_API_KEY`、`OPENAI_API_KEY`、`ANTHROPIC_API_KEY` 这类**约定变量**由 provider 名推导,
46
+ 是凭证解析的第 2 顺位(优先于 `models.json` 的 `apiKey`,低于 auth store)。完整表与排查口径见
47
+ [providers.md](providers.md)。