@h-ai/cli 1.0.0-beta.5 → 1.0.0-beta.7
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.
- package/README.en.md +24 -10
- package/README.ja.md +24 -10
- package/README.md +24 -10
- package/dist/{chunk-YHSX7AQX.js → chunk-7DNIMG2H.js} +89 -14
- package/dist/chunk-7DNIMG2H.js.map +1 -0
- package/dist/{chunk-YVNNMC4H.js → chunk-RT5OZJKM.js} +644 -11
- package/dist/chunk-RT5OZJKM.js.map +1 -0
- package/dist/{chunk-VUWM43KR.js → chunk-XI5SKOOR.js} +6673 -723
- package/dist/chunk-XI5SKOOR.js.map +1 -0
- package/dist/cli-config-templates-5KS3AE4H.js +5 -0
- package/dist/{cli-config-templates-LUJGDQNW.js.map → cli-config-templates-5KS3AE4H.js.map} +1 -1
- package/dist/{cli-skill-templates-COTP767H.js → cli-skill-templates-S2YH642Z.js} +3 -3
- package/dist/{cli-skill-templates-COTP767H.js.map → cli-skill-templates-S2YH642Z.js.map} +1 -1
- package/dist/index.js +395 -387
- package/dist/index.js.map +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/src-CX5IJGYM.js +5 -0
- package/dist/src-CX5IJGYM.js.map +1 -0
- package/package.json +11 -11
- package/templates/README.md +4 -0
- package/templates/apps/api/apps/{{projectName}}-serv/README.md.hbs +1 -1
- package/templates/apps/api/apps/{{projectName}}-serv/package.json.hbs +4 -4
- package/templates/apps/api/apps/{{projectName}}-serv/src/lib/server/init.ts.hbs +41 -2
- package/templates/apps/api/images/serv.Dockerfile.hbs +11 -6
- package/templates/apps/fullstack/docs/security-configuration.md.hbs +1 -1
- package/templates/apps/fullstack/images/serv.Dockerfile.hbs +11 -6
- package/templates/apps/fullstack/images/web.Dockerfile.hbs +11 -5
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +6 -6
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +4 -4
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +1 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +26 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +0 -1
- package/templates/base/images/web.Dockerfile.hbs +20 -10
- package/templates/base/package.json.hbs +8 -3
- package/templates/base/src/lib/server/init.ts.hbs +40 -2
- package/templates/shared/run-node.mjs +45 -0
- package/templates/skills/hai-ai/SKILL.md +6 -2
- package/templates/skills/hai-ai/acp.md +143 -0
- package/templates/skills/hai-ai/guide.md +2 -2
- package/templates/skills/hai-ai/resources.md +69 -0
- package/templates/skills/hai-app-create/development.md +9 -8
- package/templates/skills/hai-app-create/reference.md +1 -1
- package/templates/skills/hai-build/SKILL.md +5 -2
- package/templates/skills/hai-build/reference.md +16 -2
- package/templates/skills/hai-ci/SKILL.md +2 -0
- package/templates/skills/hai-core/SKILL.md +2 -2
- package/templates/skills/hai-core/reference.md +3 -3
- package/templates/skills/hai-core/request-access.md +1 -1
- package/templates/skills/hai-crypto/reference.md +1 -1
- package/templates/skills/hai-deploy/SKILL.md +8 -4
- package/templates/skills/hai-deploy/middleware.md +9 -9
- package/templates/skills/hai-iam/reference.md +2 -2
- package/templates/skills/hai-serv/reference.md +1 -1
- package/dist/chunk-VUWM43KR.js.map +0 -1
- package/dist/chunk-YHSX7AQX.js.map +0 -1
- package/dist/chunk-YVNNMC4H.js.map +0 -1
- package/dist/cli-config-templates-LUJGDQNW.js +0 -5
- package/dist/src-24PXSKE6.js +0 -5
- package/dist/src-24PXSKE6.js.map +0 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** 所有模板共用环境来源规则启动 Node,让 NODE_OPTIONS 在子进程启动前生效。 */
|
|
2
|
+
import { spawn } from 'node:child_process'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import { resolve } from 'node:path'
|
|
5
|
+
import process from 'node:process'
|
|
6
|
+
import { pathToFileURL } from 'node:url'
|
|
7
|
+
|
|
8
|
+
const [mode, environmentDirectory, entry, ...arguments_] = process.argv.slice(2)
|
|
9
|
+
if (!mode || !environmentDirectory || !entry)
|
|
10
|
+
throw new Error('Usage: node run-node.mjs <mode> <env-directory> <entry> [arguments]')
|
|
11
|
+
|
|
12
|
+
// 从实际应用解析公开 Core 入口,脚本可以放在 workspace 根目录。
|
|
13
|
+
const require = createRequire(resolve('package.json'))
|
|
14
|
+
const { core } = await import(pathToFileURL(require.resolve('@h-ai/core')).href)
|
|
15
|
+
const loaded = core.config.loadEnvFiles({ mode, envDir: resolve(environmentDirectory) })
|
|
16
|
+
if (!loaded.success)
|
|
17
|
+
throw new Error(loaded.error.message)
|
|
18
|
+
|
|
19
|
+
const child = spawn(process.execPath, [resolve(entry), ...arguments_], {
|
|
20
|
+
cwd: process.cwd(),
|
|
21
|
+
env: { ...process.env, ...loaded.data },
|
|
22
|
+
stdio: 'inherit',
|
|
23
|
+
windowsHide: true,
|
|
24
|
+
})
|
|
25
|
+
function interrupt() {
|
|
26
|
+
child.kill('SIGINT')
|
|
27
|
+
}
|
|
28
|
+
function terminate() {
|
|
29
|
+
child.kill('SIGTERM')
|
|
30
|
+
}
|
|
31
|
+
process.on('SIGINT', interrupt)
|
|
32
|
+
process.on('SIGTERM', terminate)
|
|
33
|
+
try {
|
|
34
|
+
const result = await new Promise((resolve, reject) => {
|
|
35
|
+
child.once('error', reject)
|
|
36
|
+
child.once('close', (code, signal) => resolve({ code, signal }))
|
|
37
|
+
})
|
|
38
|
+
process.exitCode = result.code ?? (result.signal === 'SIGINT' ? 130 : 143)
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
process.off('SIGINT', interrupt)
|
|
42
|
+
process.off('SIGTERM', terminate)
|
|
43
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
44
|
+
child.kill('SIGTERM')
|
|
45
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hai-ai
|
|
3
|
-
description: "接入 @h-ai/ai 的 LLM、工具/MCP、RAG
|
|
3
|
+
description: "接入 @h-ai/ai 的 LLM、工具/MCP、RAG、会话记忆、图像、语音、A2A 及 ACP。"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# hai-ai
|
|
@@ -9,7 +9,7 @@ description: "接入 @h-ai/ai 的 LLM、工具/MCP、RAG、会话记忆、图像
|
|
|
9
9
|
|
|
10
10
|
| 项目 | 契约 |
|
|
11
11
|
| --- | --- |
|
|
12
|
-
| 能力 | 接入 @h-ai/ai 的 LLM、工具/MCP、RAG
|
|
12
|
+
| 能力 | 接入 @h-ai/ai 的 LLM、工具/MCP、RAG、会话记忆、图像、语音、A2A 及 ACP |
|
|
13
13
|
| 适用场景 | AI 应用服务端集成或浏览器代理接入 |
|
|
14
14
|
| 输入 | AIConfigInput、消息/音频/图片、scope、AbortSignal |
|
|
15
15
|
| 输出 | 领域操作为 HaiResult;流为 AsyncIterable,迭代可抛错 |
|
|
@@ -26,6 +26,8 @@ description: "接入 @h-ai/ai 的 LLM、工具/MCP、RAG、会话记忆、图像
|
|
|
26
26
|
|
|
27
27
|
## 按需参考
|
|
28
28
|
|
|
29
|
+
智能体接入、工程/worktree、容器和 Git 发布见 [ACP 接入指南](acp.md)。
|
|
30
|
+
|
|
29
31
|
详细配置、API 签名、错误码和范本在 [guide.md](guide.md)。按下列标题定位所需小节,不默认通读;用例中的业务变量需结合当前应用补齐。
|
|
30
32
|
|
|
31
33
|
- 使用边界
|
|
@@ -48,3 +50,5 @@ description: "接入 @h-ai/ai 的 LLM、工具/MCP、RAG、会话记忆、图像
|
|
|
48
50
|
公开 API 示例以安装包的实际入口为准;框架源码中的内部工厂例子仅供模块维护。HaiResult 调用先检查 success,流与浏览器 AudioClient 按显式抛错契约处理;Context flush 失败和 A2A 未知外部效果不得盲目重放。
|
|
49
51
|
|
|
50
52
|
Node 自定义 SSE 使用 ai.stream 的统一编解码,不用 split/trim 手拆上游事件;encoder 的非法元数据会显式抛错。Anthropic/MiMo 的真实 HTTP 流在退出和取消时释放正文,EOF 不伪造未完成事件,不自动重连或重放业务。
|
|
53
|
+
|
|
54
|
+
资源目录维护见 [配置与数据库资源清单](resources.md):配置只读、数据库 CRUD、来源与版本、LLM/音频/图像/MCP/检索源/ACP。
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# ACP 智能体、工作区和会话
|
|
2
|
+
|
|
3
|
+
ACP 把宿主应用接到能操作工程的智能体。`ai.llm` 负责模型调用,`ai.acp` 负责智能体进程、协议、工程目录和会话;SDD、代码评审规则、预览页面、验收和业务部署仍由应用实现。仅在 Node 服务端使用,浏览器经应用自己鉴权的 API 接收事件。
|
|
4
|
+
|
|
5
|
+
## 选择接入方式
|
|
6
|
+
|
|
7
|
+
| mode | 用途 | 模型和文件 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `embedded` | 启动本机安装的原生 ACP 程序 | 引用 Hai `llm.models`;使用 directory 工作区 |
|
|
10
|
+
| `builtin-service` | 使用已构建镜像启动专属容器 | 引用 Hai 模型;workspace volume 挂 `/workspace`,状态卷挂 `/state` |
|
|
11
|
+
| `custom-service` | 已有原生 HTTP/WS ACP 服务 | 远端自行维护模型;传远端可见的 cwd;框架不操作远端 Git |
|
|
12
|
+
|
|
13
|
+
Codex 用 `adapter: 'codex'`,DeepSeek Harness 用 `adapter: 'deepseek-harness'`,其他已配置模型的 ACP 程序用 `adapter: 'generic'` 和 `command/args`。需生成其他原生配置时,向 `ai.init(config, { acpAdapters })` 提供可信服务端适配器;适配器返回 command、args、env、可选 files(写到隔离状态目录)。不把密钥存入 Agent 定义,`environmentRefs` 是目标变量名到宿主环境变量名的映射。
|
|
14
|
+
|
|
15
|
+
内置 Codex/dsh 通过 stdio 使用。HTTP/WS 是锁定 SDK 2.2.0 的官方实验传输,服务必须实现相同的 ACP 传输版本;不把任意 HTTP 聊天接口当 ACP,也不自动架设 stdio 网关。自定义服务必须声明 `supportedTransports`,协议不匹配返回错误。
|
|
16
|
+
|
|
17
|
+
## 原生 HTTP/WS 服务:Goose
|
|
18
|
+
|
|
19
|
+
Goose 1.57.0 自身提供 `goose serve` 与 `/acp` HTTP/WS,使用 custom-service 接入。先由部署方启动服务、挂载工作区/状态卷,并在 Goose 端维护模型(例如 `GOOSE_PROVIDER=openrouter`、`GOOSE_MODEL`、`OPENROUTER_API_KEY`);服务独立设置 `GOOSE_SERVER__SECRET_KEY`。Hai 只使用 `X-Secret-Key` 认证,不向服务注入 Hai 模型密钥。
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { ai } from '@h-ai/ai'
|
|
23
|
+
|
|
24
|
+
// ai 已初始化,Goose 已运行;HAI_GOOSE_SECRET 为服务端环境变量,值与远端服务密钥一致。
|
|
25
|
+
async function connectGoose() {
|
|
26
|
+
const registered = await ai.acp.agents.register({
|
|
27
|
+
id: 'goose', name: 'Goose', mode: 'custom-service',
|
|
28
|
+
transport: 'http', url: 'http://127.0.0.1:3284/acp',
|
|
29
|
+
supportedTransports: ['http', 'ws'],
|
|
30
|
+
headerEnv: { 'X-Secret-Key': 'HAI_GOOSE_SECRET' },
|
|
31
|
+
})
|
|
32
|
+
if (!registered.success) return registered
|
|
33
|
+
return ai.acp.sessions.create({ agentId: 'goose', cwd: '/workspace' })
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
WebSocket 改为 `transport: 'ws'` 和 `ws://` 地址。`cwd` 是远端可见路径;应用负责远程服务和存储的启动、关闭、Git、归属检查及 TLS。缺少或错误服务密钥会被 Goose 拒绝;恢复按握手能力走原生 load/resume,不用历史 prompt 模拟。依据:[Goose 固定版本接入文档](https://github.com/aaif-goose/goose/blob/v1.57.0/documentation/docs/gdk/acp/index.md)。
|
|
38
|
+
|
|
39
|
+
## 配置和数据库共同维护
|
|
40
|
+
|
|
41
|
+
`acp.agents`、`acp.workspaces` 可以直接写在配置文件里,也可通过 register 保存到数据库。get/list 返回 `{ value, source, version? }`,配置同 ID 优先且不可修改;数据库支持完整 update 和 remove。模型、MCP 等清单也遵循此规则,见 [资源清单](resources.md)。创建会话可通过 `mcpServerIds` 绑定合并清单里的 MCP。
|
|
42
|
+
|
|
43
|
+
## 最小本机接入
|
|
44
|
+
|
|
45
|
+
下面的目录、Git 地址、模型和密钥由部署方提供。首次登记一次,后续用 get/list 读取;重复 register 返回冲突。普通 API 都先检查 `success`。
|
|
46
|
+
|
|
47
|
+
Windows stdio 需要系统 PowerShell 可用;npm 的 `.cmd` 包装应改用 `command: 'node.exe'` 和实际 JS 入口参数数组。框架跟踪整个进程树,关闭时清理后台子进程;系统策略阻止启动时直接返回错误。
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { ai } from '@h-ai/ai'
|
|
51
|
+
|
|
52
|
+
async function run() {
|
|
53
|
+
const initialized = await ai.init({
|
|
54
|
+
llm: { models: [{
|
|
55
|
+
id: 'coding', model: 'your-responses-model', protocol: 'responses',
|
|
56
|
+
apiKey: process.env.MODEL_API_KEY,
|
|
57
|
+
baseUrl: 'https://models.example.com/v1',
|
|
58
|
+
capabilities: { contextWindow: 128000, maxOutputTokens: 16384, inputModalities: ['text'], outputModalities: ['text'], toolCalling: true, reasoning: { supported: false } },
|
|
59
|
+
}] },
|
|
60
|
+
})
|
|
61
|
+
if (!initialized.success) return initialized
|
|
62
|
+
try {
|
|
63
|
+
const agent = await ai.acp.agents.register({ id: 'coder', name: 'Codex', mode: 'embedded', adapter: 'codex', modelIds: ['coding'], defaultModelId: 'coding' })
|
|
64
|
+
if (!agent.success) return agent
|
|
65
|
+
const workspace = await ai.acp.workspaces.register({ id: 'project', storage: { type: 'directory', path: '/srv/projects/demo' }, repositories: [{ id: 'api', url: 'https://git.example.com/team/api.git', mainBranch: 'trunk' }] })
|
|
66
|
+
if (!workspace.success) return workspace
|
|
67
|
+
const ready = await ai.acp.workspaces.prepare('project')
|
|
68
|
+
if (!ready.success) return ready
|
|
69
|
+
const created = await ai.acp.sessions.create({ agentId: 'coder', workspaceId: 'project', newWorktree: true })
|
|
70
|
+
if (!created.success) return created
|
|
71
|
+
const session = created.data
|
|
72
|
+
const response = await session.prompt([{ type: 'text', text: '阅读工程并说明结构' }])
|
|
73
|
+
if (!response.success) return response
|
|
74
|
+
return response.data.text
|
|
75
|
+
}
|
|
76
|
+
finally { await ai.close() }
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`agents.initialize(id)` 检查模型绑定和原生配置能否生成;真正启动和握手用 `agents.check(id, { workspaceId })`。容器 Agent 的 `image` 必须与 workspace 的 image 一致。注册 volume 时固定实际选中的引擎;`auto` 优先就绪 Podman,再尝试 Docker,显式选定不可用引擎直接失败。
|
|
81
|
+
|
|
82
|
+
生产环境先初始化共享数据库和网络向量库,或注入 shared AIStoreProvider。Agent/workspace/session 记录和 CAS 占用存入 AI Store,原生历史保存在隔离状态目录。不能把临时内存 Provider 当跨节点锁。
|
|
83
|
+
|
|
84
|
+
## 容器运行与模型复用
|
|
85
|
+
|
|
86
|
+
源码仓库提供 `packages/ai/models/agents` Dockerfile 和构建命令:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
pnpm --filter @h-ai/ai agent:build codex-acp
|
|
90
|
+
pnpm --filter @h-ai/ai agent:build deepseek-harness --engine=docker --java --rust --go
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Codex 镜像明确包含 `@agentclientprotocol/codex-acp@2.5.0` 和 `@openai/codex@0.165.0`。所有镜像包含 Git、Node、Python;Java/Rust/Go 按构建选项安装。镜像自行构建、推送和部署;框架启动时不偷偷构建镜像、不挂引擎 socket。Dockerfile 随 npm 包的 models/agents 一起提供。
|
|
94
|
+
|
|
95
|
+
Agent 改为 `mode: 'builtin-service', image: 'hai-ai/codex-acp:2.5.0', engine: 'auto'`;workspace 改为 `storage: { type: 'volume', name: 'project-code', image: 'hai-ai/codex-acp:2.5.0', engine: 'auto' }`。容器重建继续复用这两个命名卷,Git 和 Agent 在容器内始终看到 `/workspace`,不会混用宿主绝对 worktree 路径。工作区主目录为 `main/`,隔离 session 为 `sessions/<id>/`,上传文件放 `files/`。
|
|
96
|
+
|
|
97
|
+
Podman 不自动继承远程服务的代理变量。需要代理时,使用 Agent/workspace 的 `environmentRefs` 显式传入容器可达的地址;宿主的回环代理不能直接在容器内使用。
|
|
98
|
+
|
|
99
|
+
模型能力配置位于 `llm.models[].capabilities`:contextWindow、maxOutputTokens、inputModalities、outputModalities、toolCalling、reasoning.supported/efforts。未知就省略;不能用请求 `maxTokens` 预算推断真实容量。dsh 自定义模型需明确上下文、输出容量、输入模态、工具和推理能力,避免其内部默认值夸大模型能力。dsh 映射 Chat Completions、Responses、Anthropic;Codex 原生要求 Responses。现有 Hai 的 Responses 转换是客户端能力,不是对外反向网关。只有 Chat Completions 的服务不会因配置了 Codex 自动变成 Responses 服务。
|
|
100
|
+
|
|
101
|
+
模型密钥在启动时重新解析;恢复时模型名、协议、地址、能力发生变化会拒绝,密钥轮换可以继续。自定义 ACP 服务只接受 `headerEnv` 里的认证引用,不接收 Hai 模型配置。
|
|
102
|
+
|
|
103
|
+
## 工程和 Git
|
|
104
|
+
|
|
105
|
+
每个 Git 工程必须写明 `mainBranch`,remote 默认为 origin。`prepare` 只克隆缺失工程并校验既有工程;不自动 pull、不重置脏目录。`workspaces.importFiles(id, [{ path, content }])` 添加普通文件,拒绝路径逃逸、链接和覆盖;没有 Git 工程的工作区无需安装 Git。涉及 Git 而命令缺失返回 `ACP_GIT_NOT_FOUND`。
|
|
106
|
+
|
|
107
|
+
当前托管 Git 面向普通仓库;发现子模块或 Git LFS 属性时返回 `ACP_UNSUPPORTED`,不把未拉取的子模块或 LFS 指针当作完整工程。需要这些能力的应用可自行准备环境并使用低层 `connect`,其 Git 生命周期由应用负责。
|
|
108
|
+
|
|
109
|
+
创建 session 默认共享 `main/` 路径,框架会阻止另一个共享 session 同时占用它。`newWorktree: true` 从每个工程当前已提交的本地主分支创建同名分支和 worktree,普通文件单独复制;可以指定 `branch`,默认 `acp/<sessionId>`。它不会自动拉远端,也不会导入主目录未提交修改。
|
|
110
|
+
|
|
111
|
+
- `session.updateMain()`:停止本 session 的执行环境,再 fetch 各工程的明确主分支并 ff-only 更新本地主目录;不把主分支合入 session,不自动 rebase。共享主目录被其他会话占用时返回 busy。
|
|
112
|
+
- `session.publish({ mode: 'push-branch', message, author: { name, email } })`:提交隔离 session 修改并推送其同名分支。
|
|
113
|
+
- `session.publish({ mode: 'merge-main', message, author })`:提交后检查、更新登记的主分支,再合并并推送。无 force push,不静默处理冲突。
|
|
114
|
+
|
|
115
|
+
发布仅允许框架创建的隔离 session,避免将共享目录中别人的修改算入当前任务。操作前停止受管进程/容器,避免背景工具继续写入;要继续编码显式 `sessions.resume(id)`。原生 Agent 自己创建的 worktree 不自动纳入框架发布范围。
|
|
116
|
+
|
|
117
|
+
多个工程不是原子事务。失败时返回 `ACP_PARTIAL_FAILURE`,`error.cause.reports` 列出每个工程 success、phase、commit 和 error;已完成工程不回滚,冲突保留现场。失败于 push 后,排除权限/网络问题可重试;远端结果不明时先核对实际分支。普通文件的修改留在 session 目录,不自动纳入 Git 发布。失败创建的 worktree 也保留,避免删除已有成果。
|
|
118
|
+
|
|
119
|
+
## 交互、事件和结果
|
|
120
|
+
|
|
121
|
+
`onEvent` 在创建会话前注册,接收原生 session/update(含 `_meta`);`session.stream(content, signal)` 提供有界 AsyncIterable,提前退出会取消该轮。prompt 返回 stopReason、text、events;有 messageId 时 text 取最后一条消息,完整消息仍在 events。没有消息边界的 Agent 只能拼接其文本,不能猜测某段是最终答案。
|
|
122
|
+
|
|
123
|
+
`promptStructured(content, zodSchema, signal)` 只接受 end_turn、完整唯一 JSON(可为完整 JSON fenced block)和通过 Schema 的内容。不提取夹在说明中的任意对象,不自动重试付费请求。需要业务证据、ID 或范围判断时由应用进一步校验。
|
|
124
|
+
|
|
125
|
+
权限默认取消。通过 `handlers` 提供官方 SDK 类型的 permission/fs/terminal/elicitation 回调,并在 `capabilities` 声明实际实现;terminal 必须提供完整回调组。回调运行在可信服务端,应用需按用户、session 和路径授权。Agent 自带本机工具可能不经过回调,worktree 也不是安全沙箱;只读评审需要操作系统/容器权限隔离。
|
|
126
|
+
|
|
127
|
+
图片、音频、嵌入资源和远程 MCP 按初始化能力检查。mode/config 通过原生协议操作。取消、握手/任务超时、输出超限会返回明确错误;连接关闭后不会自动重放请求。`sessions.resume` 仅在 Agent 声明 load/resume 时使用原生 ID 和原状态,不用历史 prompt 伪造恢复。`connect` 提供底层连接、认证和原生会话枚举/删除,用于应用已经管理执行环境的情况。
|
|
128
|
+
|
|
129
|
+
`session.close()` / `ai.close()` 等待拥有的进程树或容器停止后释放占用,保留目录和卷。失联或清理失败不会按 TTL 直接抢占;管理员先检查记录里的容器资源和实际执行环境,确认停止后再恢复占用记录。生产部署需监控此类故障。应用仍需负责 API 鉴权、项目归属、外部 Git 凭据及预览/业务发布。
|
|
130
|
+
|
|
131
|
+
## 配置与错误
|
|
132
|
+
|
|
133
|
+
`acp` 可省略:requestTimeout=30000ms、promptTimeout=600000ms、cancelTimeout=5000ms、maxContentBytes=8388608、maxPendingEvents=1024。对应 `HAI_AI_ACP_REQUESTTIMEOUT`、`HAI_AI_ACP_PROMPTTIMEOUT` 等环境变量。CLI 的 `_ai.yml` 和 `.env.example` 由公共配置目录生成这些字段;仅取消需要的配置注释不会自动启动 Agent。
|
|
134
|
+
|
|
135
|
+
错误码:070 Git 缺失;071 普通操作失败;072 参数;073 未登记;074 占用;075 不支持;076 取消;077 超时;078 输出无效/超限;079 状态冲突;080 多工程部分失败;081 已关闭;082 运行环境不可用;083 模型不兼容;084 清理失败。统一前缀 `hai:ai:`。错误不包含原始 stderr、凭据或完整命令行。
|
|
136
|
+
|
|
137
|
+
## 事件与恢复边界
|
|
138
|
+
|
|
139
|
+
事件保留原生内容。智能体提供 messageId 时,prompt.text 使用最后一条消息;没有 messageId 时合并本轮收到的文本。HTTP 的 session/new 响应与会话事件是独立通道,早期事件可能稍晚到达;需要严格结果时使用 promptStructured,混入说明或不完整 JSON 会返回失败,不自动截取一个看似合法的对象。
|
|
140
|
+
|
|
141
|
+
DSH 模型需要容量、输入模态、工具能力与推理声明;reasoning.supported 为 true 时需明确 efforts,false 会映射为原生 reasoningEfforts: false。Codex 原生配置会选择 api-key 认证并使用 Hai Responses provider。
|
|
142
|
+
|
|
143
|
+
会话绑定、定义删除和工作区修改通过共享 CAS 临界区防止悬空引用;工作路径使用独立占用锁。进程或宿主异常中断后,不自动用 TTL 假定执行者已停止:需运维先核验/终止记录中的执行资源,再修复遗留占用;正常 close 后可直接 resume。框架不将 Git worktree 当成安全沙箱。
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
从 [SKILL.md](SKILL.md) 选择主题后读取对应小节。代码片段展示 API 用法;省略的业务变量、schema、依赖与 return 上下文需由应用补齐。
|
|
4
4
|
|
|
5
|
-
`@h-ai/ai` 统一提供 LLM、工具、MCP、Embedding、Memory、Retrieval/RAG、Knowledge、Context、File、Rerank、Image、Audio 与
|
|
5
|
+
`@h-ai/ai` 统一提供 LLM、工具、MCP、Embedding、Memory、Retrieval/RAG、Knowledge、Context、File、Rerank、Image、Audio、A2A 与 ACP 能力。工程智能体、工作区和会话见 [ACP 指南](acp.md),配置与数据库资源合并见 [资源清单](resources.md)。
|
|
6
6
|
|
|
7
7
|
## 使用边界
|
|
8
8
|
|
|
@@ -427,7 +427,7 @@ await saveImage(image.data.images[0].data, image.data.images[0].mimeType)
|
|
|
427
427
|
```
|
|
428
428
|
|
|
429
429
|
- `Pollinations` 提供可免费起步的开发额度,但仍需 API key;不要把“免费额度”描述为匿名或无限调用。
|
|
430
|
-
- 应用应通过 `core.init({ configDir: './config' })` 加载 `_core.yml` / `_ai.yml
|
|
430
|
+
- 应用应通过 `core.init({ configDir: './config', configSchemas: { ai: AIConfigSchema } })` 加载 `_core.yml` / `_ai.yml`,并登记其他所用模块的公开 Schema,再以 `core.config.validate('ai', AIConfigSchema)` 校验并传给 `ai.init`;不要在启动代码中重复拼装模型、端点和超时,也不要传入 `logging` 覆盖 `_core.yml`。
|
|
431
431
|
- Pollinations 的 HTTP 402 表示账户余额或该 API Key 的预算耗尽;401 是密钥无效,403 是权限或模型访问被拒。诊断日志记录厂商、模型、状态、上游错误码和请求 ID,不记录密钥、提示词或图片内容。
|
|
432
432
|
- Qwen 等返回的临时图片 URL 会在 Provider 内立即下载,公共结果不泄漏易过期 URL。
|
|
433
433
|
- 参考图统一使用 `{ data: Uint8Array, mimeType: 'image/...' }`;不要把厂商 URL、multipart 或 Base64 结构泄漏到业务层。
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# 配置与数据库资源清单
|
|
2
|
+
|
|
3
|
+
AI 的可维护资源统一使用两份来源:初始化配置(通常来自 config/ai.yml)和 AIStoreProvider 数据库。按 ID 合并,同 ID 配置优先;配置条目只读,不复制到数据库。移除配置后,原来被遮蔽的数据库条目重新可见。数据库变更不会修改配置文件。
|
|
4
|
+
|
|
5
|
+
| 清单入口 | 配置数组 | 使用方 |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `ai.llm.models` | `llm.models` | LLM、embedding、rerank、ACP 原生模型配置 |
|
|
8
|
+
| `ai.audio.models` | `audio.models` | ASR/TTS |
|
|
9
|
+
| `ai.image.models` | `image.models` | 图像生成 |
|
|
10
|
+
| `ai.mcp.servers` | `mcp.servers` | ACP `mcpServerIds`,外部 MCP 连接资料 |
|
|
11
|
+
| `ai.retrieval.sources` | `retrieval.sources` | 检索源及旧 addSource/removeSource/listSources 接口 |
|
|
12
|
+
| `ai.acp.agents` | `acp.agents` | 智能体接入、初始化和会话 |
|
|
13
|
+
| `ai.acp.workspaces` | `acp.workspaces` | 工程准备、会话目录及 Git 操作 |
|
|
14
|
+
|
|
15
|
+
这些清单支持 `list/get/register/update/remove`。`list/get` 返回 `HaiResult`,条目为 `{ value, source, version? }`;`source` 是 config 或 database,数据库 `get` 带版本。`update` 接受完整定义,不是部分 patch。编辑前 get,再把 version 传给 update/remove,过期写入返回冲突。已有运行中的 ACP 会话保留原模型绑定;新会话读取最新清单,恢复会话会校验模型与智能体定义是否仍兼容。
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { ai } from '@h-ai/ai'
|
|
19
|
+
|
|
20
|
+
// 已按应用生命周期成功初始化 reldb、vecdb 和 ai。
|
|
21
|
+
async function changeModel() {
|
|
22
|
+
const current = await ai.llm.models.get('coding')
|
|
23
|
+
if (!current.success) return current
|
|
24
|
+
if (current.data.source === 'config') return current
|
|
25
|
+
return ai.llm.models.update({ ...current.data.value, temperature: 0.2 }, current.data.version)
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
配置示例(模型名、地址与路径由部署提供):
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
llm:
|
|
33
|
+
models:
|
|
34
|
+
- id: coding
|
|
35
|
+
model: your-model
|
|
36
|
+
protocol: responses
|
|
37
|
+
acp:
|
|
38
|
+
agents:
|
|
39
|
+
- id: coder
|
|
40
|
+
name: Codex
|
|
41
|
+
mode: embedded
|
|
42
|
+
adapter: codex
|
|
43
|
+
modelIds: [coding]
|
|
44
|
+
workspaces:
|
|
45
|
+
- id: project
|
|
46
|
+
storage:
|
|
47
|
+
type: directory
|
|
48
|
+
path: /srv/projects/project
|
|
49
|
+
repositories:
|
|
50
|
+
- id: api
|
|
51
|
+
url: https://git.example.com/team/api.git
|
|
52
|
+
mainBranch: trunk
|
|
53
|
+
mcp:
|
|
54
|
+
servers:
|
|
55
|
+
- id: docs
|
|
56
|
+
name: Docs
|
|
57
|
+
transport: stdio
|
|
58
|
+
command: docs-mcp
|
|
59
|
+
environmentRefs:
|
|
60
|
+
API_TOKEN: DOCS_API_TOKEN
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
创建会话时传 `mcpServerIds: ['docs']`;配置和数据库 MCP 条目都可选择。环境变量在实际启动时解析,不将引用替换为密钥后保存到 MCP 定义。HTTP/SSE 的 headers 使用 `headerEnv`,stdio 的环境变量使用 `environmentRefs`。这份清单只维护外部服务器;`ai.mcp.registerTool/registerResource/registerPrompt` 仍是宿主进程的函数注册,不将可执行函数存进数据库。
|
|
64
|
+
|
|
65
|
+
框架的异步模型调用每次读取最新清单;mem0 外部运行时在初始化时读取合并快照,其内置模型配置修改后需要重新初始化 AI。音频 `getCapabilities()` 保留原同步契约,使用本进程最近一次目录读写或异步音频调用刷新的快照;跨节点修改后先 await `ai.audio.models.get/list` 再同步查询能力。清单不是业务会话历史:session、聊天记录、persona 等继续使用各自已有的持久化 API,不从配置导入运行历史。
|
|
66
|
+
|
|
67
|
+
配置项改写返回 `hai:ai:090`,缺失为 091,版本/重复冲突为 092,无效定义为 093,存储失败为 094。工作区路径重叠、仍被会话引用等使用 ACP 错误。数据库工作区更新/删除前需关闭并移除引用它的会话记录;移除记录不删除磁盘目录、worktree 或 volume。
|
|
68
|
+
|
|
69
|
+
清单 API 是可信服务端管理接口,模型原始定义可能包含 apiKey。应用必须鉴权并自行选择可返回浏览器的字段,不直接透传整个 value。生产继续沿用 AI 模块已有共享数据库及向量库前置要求。
|
|
@@ -22,7 +22,7 @@ README 编辑先读章节,把功能说明与示例合并到对应章节;新
|
|
|
22
22
|
|
|
23
23
|
### 页面与布局
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
每个模板只安装实际使用的模块:admin/h5 包含认证、验证码及找回密码通知,以及数据库、缓存、加密依赖;website 只包含 Core/Kit/UI;API 包含认证、通知、上传和反馈;fullstack 保留管理后台的认证、审计、存储、通知、调度和反馈;mobile-app 只使用客户端能力。AI、payment、vecdb、datapipe 不默认安装。所有项目保留完整模块 YAML 和 `.env.example`,未启用项以注释提供,只有已启用项进入公共环境默认值。后续添加对应依赖、取消所需配置注释,再接入初始化、关闭及业务路由;添加依赖不会自动启用功能。`--template`、`--features`、`--examples/--no-examples` 已删除,`create --yes/-y` 改为 `--defaults`,旧参数明确报错;`deploy --yes` 保持原义。已有工程不会自动裁剪依赖。
|
|
26
26
|
|
|
27
27
|
website默认SSR;静态发布须先移除服务端业务依赖、初始化及API/actions。静态构建与实际路径验证按[hai-build](../hai-build/SKILL.md)。
|
|
28
28
|
|
|
@@ -42,6 +42,7 @@ website默认SSR;静态发布须先移除服务端业务依赖、初始化及A
|
|
|
42
42
|
| `.env.test` | 测试环境公共覆盖 | 提交 |
|
|
43
43
|
| `.env.prod` | 生产环境公共覆盖 | 提交 |
|
|
44
44
|
| `.env.local` | 当前机器的私有配置,覆盖公共配置 | 不提交 |
|
|
45
|
+
| `.env.<mode>.local` | 当前模式的私有覆盖、初始密码和传输密钥环 | 不提交 |
|
|
45
46
|
|
|
46
47
|
YAML 使用 `${HAI_DB_TYPE:sqlite}` 等显式引用,冒号后是未配置时的本地默认值。应用仍通过 `core.config.validate(name, ModuleConfigSchema)` 和模块初始化校验类型、枚举、范围和必填项;插值本身不能代替校验。只开启实际使用的模块,未启用模块的整份 YAML 保留注释。
|
|
47
48
|
|
|
@@ -51,8 +52,8 @@ YAML 使用 `${HAI_DB_TYPE:sqlite}` 等显式引用,冒号后是未配置时
|
|
|
51
52
|
|
|
52
53
|
- `HAI_<配置名>_<字段路径>`:`_db.yml` 的 `type` 对应 `HAI_DB_TYPE`;`_ai.yml` 的 `llm.apiKey` 对应 `HAI_AI_LLM_APIKEY`。camelCase 不拆词,数组使用从零开始的索引,如 `HAI_AI_AUDIO_MODELS_0_BASEURL`。
|
|
53
54
|
- 框架配置统一采用路径命名,如 `HAI_REACH_PROVIDERS_0_PASS`。第三方 SDK 自身读取的 `OPENAI_API_KEY` 等外部变量保留厂商约定;应用 YAML 使用框架标准名。
|
|
54
|
-
-
|
|
55
|
-
-
|
|
55
|
+
- Schema 决定类型:布尔用 `true/false`,数字不带单位;字符串直接填写即可保留前导零,无须再嵌套 YAML 引号。没有 Schema 的自定义配置按已有 YAML/JSON 结构与 YAML 标量规则解析。空字符串保持为空字符串。
|
|
56
|
+
- JSON 对象可作为基础,后续按路径细化;同来源先父后子、与行序无关,高来源整段替换会清掉低来源细项。生成配置优先细项,数组保留 JSON;动态字典与空对象保留原意,JSON 放对应模块细项之前。
|
|
56
57
|
- `PUBLIC_` / `VITE_` 只用于浏览器可见的 API 地址和非敏感参数。业务正文、模板 ID、表名/列名及数据结构留在代码/YAML;环境参数不能改变固定表结构契约。
|
|
57
58
|
|
|
58
59
|
#### 加载与启动
|
|
@@ -62,13 +63,13 @@ YAML 使用 `${HAI_DB_TYPE:sqlite}` 等显式引用,冒号后是未配置时
|
|
|
62
63
|
| Vite / SvelteKit | `pnpm dev` | `pnpm dev:test` | `pnpm dev:prod` 预览;build 不选择环境 |
|
|
63
64
|
| Node service | `pnpm dev` | 构建后 `pnpm start:test` | 构建后 `pnpm start:prod` |
|
|
64
65
|
|
|
65
|
-
`hai create` 为 fullstack 在 `.env.dev/.env.test/.env.prod`
|
|
66
|
+
`hai create` 为 fullstack 在 `.env.dev.local/.env.test.local/.env.prod.local` 分别生成初始密码和传输密钥环,API 启用 crypto 时也生成 ring;这些私有文件不提交 Git。克隆后通过受保护配置或 Secret Manager 分发对应环境秘密。同环境节点共享 ring 和 registry,运行时不生成或轮换。模板默认 `pretty` 并声明运行依赖 `pino-pretty`,需要 JSON 时覆盖 `HAI_CORE_LOGGING_FORMAT`。启动失败检查模块、错误码和字段路径;监听冲突检查 `EADDRINUSE` 的地址与端口。
|
|
66
67
|
|
|
67
|
-
所有入口优先级一致:**进程环境 > `.env.local` > `.env.<
|
|
68
|
+
所有入口优先级一致:**进程环境 > `.env.<mode>.local` > `.env.local` > `.env.<mode>` > `.env` > YAML**。Vite 在 Node 阶段通过 `core.config.loadEnvFiles` 读取并检查结果;Node 服务复用 `scripts/run-node.mjs <mode> <环境目录> <入口>`,让来源与对象覆盖规则一致,`NODE_OPTIONS` 在子进程启动前生效。`development`/`production` 映射为 `dev`/`prod`,`core.init` 不自行读取 dotenv。
|
|
68
69
|
|
|
69
70
|
托管 `test:e2e` 是明确的隔离测试入口:服务子进程使用本轮绝对数据目录和完整模块配置,不继承 ambient 模块变量,也不读取用户 `.env.local`。Svelte 仅在 `HAI_E2E=1` 的测试分支跳过 dotenv;API 测试直接执行已构建产物。普通开发与生产的上述优先级和共享后端守卫保持不变,transport 使用测试独立可信 ring 与实际 registry。
|
|
70
71
|
|
|
71
|
-
`prod` 是文件模式名,对应 `HAI_CORE_ENV=production`;环境枚举为 `development | test | production | staging`。SvelteKit adapter-node 产物使用 `node
|
|
72
|
+
`prod` 是文件模式名,对应 `HAI_CORE_ENV=production`;环境枚举为 `development | test | production | staging`。SvelteKit adapter-node 产物使用 `node scripts/run-node.mjs prod . build/index.js`。路径相对工作目录,公共文件提交 Git,私有文件不进入镜像或版本库。部署容器由 SDK 统一运行文件加载,浏览器只接收公开白名单。
|
|
72
73
|
|
|
73
74
|
当前 SDK 的 Node 后端默认读取实际挂载的发布 `.env.runtime`,修改后重启容器生效;托管连接保持更高优先级,静态 Web、中间件、网关路径、端口/挂载和 Node 启动参数按部署流程更新。长期修改同步回源环境,下一次后端发布会重新生成;见[部署指引](../hai-deploy/SKILL.md#后端运行配置重载)。自行维护且只使用 Compose `env_file` 注入环境的容器,改文件后需要重新创建,通常无需重建镜像。请求策略可固定引用只读挂载 YAML,后续改文件并重启服务实例重新读取;运行中不监视文件、不逐请求读盘。完整 `HAI_SERV` / `HAI_KIT` 优先于叶子变量,不叠加覆盖。待上传归档与 SDK 策略快照不能手改;运维外部文件和 Compose override 另行备份,SDK 自动回滚不恢复外部文件。
|
|
74
75
|
|
|
@@ -96,7 +97,7 @@ Fullstack 的环境文件在工程根;service 脚本通过 `../../.env*` 加
|
|
|
96
97
|
| `HAI_SERV_SERVER_HOST` / `HAI_SERV_SERVER_PORT` | HTTP 主机和整数端口 1–65535;部署平台 HOST/PORT 可覆盖 |
|
|
97
98
|
| `HAI_AI_LLM_PROTOCOL` / `HAI_AI_LLM_TEMPERATURE` | `chat \| responses \| anthropic`;temperature 为数字 0–2 |
|
|
98
99
|
| `HAI_AI_LLM_APIKEY` / `HAI_AI_LLM_BASEURL` / `HAI_AI_LLM_MODEL` | 厂商密钥、服务 URL、实际可用模型名;启用时满足厂商要求 |
|
|
99
|
-
| `HAI_ADMIN_DEFAULT_PASSWORD` | 首次创建管理员的密码,满足 IAM 密码策略;CLI 为 fullstack 的 `.env.dev/.env.test/.env.prod` 分别生成独立随机值 |
|
|
100
|
+
| `HAI_ADMIN_DEFAULT_PASSWORD` | 首次创建管理员的密码,满足 IAM 密码策略;CLI 为 fullstack 的 `.env.dev.local/.env.test.local/.env.prod.local` 分别生成独立随机值 |
|
|
100
101
|
| `PUBLIC_API_BASE` | 可公开的 API 地址,不得包含用户名、密码或 token |
|
|
101
102
|
|
|
102
103
|
其余变量逐项见对应 `.env.example` 及模块公开 Schema;可选块启用后也应使用相同命名、显式引用与逐项说明。
|
|
@@ -179,7 +180,7 @@ CLI 用户消息通过现有中英字典输出。初始语言按非空的 `HAI_C
|
|
|
179
180
|
|
|
180
181
|
独立 fullstack 默认后台的 Web/App/Desktop 共用管理组件、状态与 typed client。`@h-ai/serv/features/iam` 默认 procedures 可按需选用并与 contract 对齐;有业务缺口时覆写必要路由。主题的 DOM 更新和安全缓存使用 `@h-ai/ui/theme`(`applyTheme` / `getSavedTheme`),应用保留响应式状态与已编译主题白名单。初始 admin/user、管理员自我保护、头像限额、分页和审计策略都是应用示例,可按需求替换;实际权限、资源范围和写入一致性按目标业务落实。
|
|
181
182
|
|
|
182
|
-
Fullstack 创建时在根 `.env.dev/.env.test/.env.prod` 分别写入独立随机 `HAI_ADMIN_DEFAULT_PASSWORD
|
|
183
|
+
Fullstack 创建时在根 `.env.dev.local/.env.test.local/.env.prod.local` 分别写入独立随机 `HAI_ADMIN_DEFAULT_PASSWORD`,文件不提交 Git,克隆后通过受保护配置共享,`.env.example` 保留空占位。`dev` / `start` 自动读取;已有用户不重设密码。启动边界保留模块错误的具体说明与错误码,失败后反序清理。不能把环境凭据复制到前端配置、日志、源码或镜像。
|
|
183
184
|
|
|
184
185
|
## 落实契约与安全边界
|
|
185
186
|
|
|
@@ -315,7 +315,7 @@ interface CreateArticleInput {
|
|
|
315
315
|
|
|
316
316
|
## 初始化顺序
|
|
317
317
|
|
|
318
|
-
CLI 项目统一忽略 `**/data/`,含根目录、workspace 子目录及 `.gitkeep`;数据库/上传文件不入 Git
|
|
318
|
+
CLI 项目统一忽略 `**/data/`,含根目录、workspace 子目录及 `.gitkeep`;数据库/上传文件不入 Git。每个模板只安装实际使用的模块,同时在应用 config 保留 core、serv、kit、db、cache、iam、storage、audit、reach、ai、vecdb、payment、scheduler、feedback 共 14 份 YAML;纯客户端的服务端配置仅作参考。构建部署配置统一位于项目根 hai.json,未启用模块整份注释,可选字段及替代 Provider 按块注释,`.env.example` 保留全部模块变量,未启用项不进入公共环境默认值。新增功能时添加对应依赖、启用所需配置,并接入初始化、反序关闭和业务路由;添加依赖不会自动接入模块。LDAP 只有在 login.ldap 为 true 时才参与部署扫描。
|
|
319
319
|
|
|
320
320
|
H5 所有页面继承根布局的 header/footer;认证页隐藏底部导航,业务页面使用 `@h-ai/ui` 的 `BottomNav` 与移动端壳样式,导航启用 centered/maxWidth/safeArea,与页面同宽。
|
|
321
321
|
|
|
@@ -27,7 +27,7 @@ description: "构建或排查 hai 应用的源码、静态网站、容器镜像
|
|
|
27
27
|
|
|
28
28
|
## 构建与部署
|
|
29
29
|
|
|
30
|
-
- 新建website
|
|
30
|
+
- 新建website只安装Core/Kit/UI,默认Node SSR,可直接切换静态发布;注释配置与环境变量参考不启用模块。后续增加的服务端业务依赖、初始化及API/actions须先移除。compile静态预渲染使用`HAI_BUILD_STATIC=1 PUBLIC_BASE_PATH=/docs-site`,docs-site替换为实际仓库名,各路径段仅ASCII字母、数字、_、.、-,无末尾/;省略/0保持SSR,根站路径留空。HAI_BUILD_STATIC仅用于compile,PUBLIC_BASE_PATH也支持image网站构建。静态拒绝服务端业务模块、自定义API/actions与未预渲染页面,不放宽生产守卫、不用SPA fallback;默认健康端点不发布。构建不读根.env,static为公开目录,拒绝.env/.env.*与符号链接。上传build使用include-hidden-files:true保留.nojekyll,验首页/深链/刷新/资产与敏感哨兵。
|
|
31
31
|
|
|
32
32
|
- 创建默认包含开发期部署工具,可用 --no-deploy 关闭。每个镜像使用 images/ 中独立 Dockerfile,Web 配置位于 images/web/,不使用镜像阶段 target 配置。
|
|
33
33
|
- 构建器生成 images/dependencies/ 安装输入;fetch、安装和生产依赖整理共用 pnpm store,安装与生产依赖整理共用 SQLite 驱动缓存,并复用依赖层;依赖安装在源码COPY前,前后端仅复制各自需要的源码。运行镜像去掉应用自身声明、source map和README。file: 归档、补丁、workspace 元数据必须进入下载上下文;原生依赖缓存按系统/架构/ABI 区分。基础镜像、中间件及工具指定版本,禁止 latest。
|
|
@@ -35,7 +35,8 @@ description: "构建或排查 hai 应用的源码、静态网站、容器镜像
|
|
|
35
35
|
- pnpm run deploy --build 显式串联构建、打包和发布;--bundle-only 只交付完整离线包与跨平台脚本;--services 以服务器成功状态为基线只更新选中服务。完整包自动包含自建中间件镜像,服务器禁止联网拉取。
|
|
36
36
|
- `pnpm build` 默认仅编译源码(调用 compile);`pnpm build --target image` 根据 hai.json.build.images 的 dockerfile 构建并导出 serv/web 镜像(可单独或组合),可用 --images web 只构建前端,默认最多两项并行,--concurrency 1可串行,auto 优先可达 Podman、其次 Docker,可用 --runtime 显式指定。
|
|
37
37
|
- compile 交付源码编译结果,image 交付镜像归档,desktop 交付 Tauri 系统安装包,app 交付 Capacitor 原生制品;每次选择一个 target。hai.json 的 build.images[].dockerfile 指向独立镜像文件,build.desktop/build.app 声明原生工程目录,deploy 保存整个项目的部署配置;参数和平台前置以当前 CLI 类型与工程 scripts 为准。cap:sync 只同步工程,不能作为已打包或已安装的证据。
|
|
38
|
-
-
|
|
38
|
+
- `--image-source` / `--npm-source` 分别选 official/cn/custom;首次交互保存到 hai.json.build.sources,参数仅覆盖本次,--yes 复用且未配置时走官方。cn 镜像用毫秒镜像覆盖 Docker Hub/GHCR,npm 用 npmmirror;保留版本和 lockfile 校验。旧 Dockerfile 缺参数时明确提示同步模板。自定义映射见 [下载源与代理](reference.md#下载源与代理)。
|
|
39
|
+
- Docker/Podman 的构建代理用预定义参数传递;--proxy-mode 选 inherit/system/custom/none,--proxy-url 指定自定义地址,--proxy-bypass 覆盖绕过列表,--no-http-proxy 等价 none。Podman 默认网络保留原代理地址,显式 pasta 等隔离网络才改为宿主入口;Docker 非 host 网络使用宿主入口。首个下载步骤检查实际构建可达性,不能用 run 网络代替。镜像拉取端仍需 Docker daemon/Desktop 或 Podman Machine/API 自身代理,CLI 分别预检提示。不要把代理写成 Dockerfile ENV 或固化进运行镜像。
|
|
39
40
|
- --network 默认沿用引擎网络;只有实际构建网络无法访问依赖/代理时才显式选择网络,pasta 仅适用于 rootless Podman。build 与 run 的主机名解析可能不同,须验证构建网络中的可达性,不默认改成 host 网络。
|
|
40
41
|
- 从运行build的应用根package.json.version读取版本,不能使用CLI/框架版本或服务子包版本代替。根目录清单version=4,非根目录version=5并记录webBasePath;appVersion为应用版本。servContainerPort/webContainerPort仅记录images配置显式填写的端口,部分构建也保留未选镜像端口,未填写时沿用部署默认值。镜像tar名含版本、buildId区分同版本构建,Tauri也接收根版本。不要手改清单或保留无版本的交付归档。
|
|
41
42
|
- 构建不区分 dev/test/prod,不加载部署环境覆盖,不注入运行期凭据;所选镜像清单含 SHA-256,全部成功才更新本机最近构建指针(不是镜像标签)。不要让 compile、Dockerfile 或 Tauri beforeBuildCommand 递归调用自身的原生/镜像构建。
|
|
@@ -54,6 +55,8 @@ description: "构建或排查 hai 应用的源码、静态网站、容器镜像
|
|
|
54
55
|
|
|
55
56
|
## 按需参考
|
|
56
57
|
|
|
58
|
+
Windows 构建子进程使用追加式 pnpm 日志并规范 CRLF,等待输出写完再完成;保留 stdout/stderr、UTF-8 和所有警告,不靠静默输出隐藏下载错误。Dockerfile 的 pnpm fetch/install 同步使用 --reporter=append-only。
|
|
59
|
+
|
|
57
60
|
按任务读取 [reference.md](reference.md) 的对应小节;签名以当前源码或安装版本的公开类型为准,不默认加载全部资料。示例中的业务变量由应用提供。
|
|
58
61
|
|
|
59
62
|
- 适用场景
|
|
@@ -44,9 +44,23 @@
|
|
|
44
44
|
|
|
45
45
|
pnpm 专属配置放在 `pnpm-workspace.yaml`,`.npmrc` 仅用于注册表与认证。Docker 缓存层使用 `pnpm fetch` 读取锁文件;pnpm 12 的该命令不支持 `--frozen-lockfile`。后续仍通过 `pnpm install --offline --frozen-lockfile` 校验清单与锁文件一致。容器裁剪用 `pnpm pm deploy --filter <服务包> --prod <目标目录>`,确保执行内置命令,避免项目同名 `deploy` 脚本覆盖它。保留 `.gitattributes`,让 `.dockerignore`、Dockerfile 和 POSIX 脚本统一使用 LF,保持跨平台输入一致并确保 Linux 脚本可运行。
|
|
46
46
|
|
|
47
|
+
### 下载源与代理
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
pnpm build --target image --image-source cn --npm-source cn
|
|
51
|
+
pnpm build --target image --proxy-mode custom --proxy-url http://127.0.0.1:10808
|
|
52
|
+
pnpm build --target image --image-source custom --image-mirrors "docker.io=mirror.example.com/docker.io,ghcr.io=mirror.example.com/ghcr.io" --npm-source custom --npm-registry https://npm.example.com/registry
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
首次交互保存尚未配置的 `hai.json.build.sources.image` / `.npm` 与 `build.proxy`。命令参数优先且不回写,已保存值其次;非交互未配置时官方源与 inherit 代理。image 形如 `{ "type": "custom", "registries": { "docker.io": "mirror.example.com/docker.io", "ghcr.io": "mirror.example.com/ghcr.io" } }`,npm 形如 `{ "type": "custom", "registry": "https://npm.example.com/registry" }`。official/cn 只需 type。cn 使用毫秒镜像的 docker.1ms.run / ghcr.1ms.run 及 registry.npmmirror.com;公共服务有同步延迟和限流,需验证实际版本。打包中间件会复用本地原始镜像,缺失时从所选源拉取并恢复原标签,离线包与部署身份保持一致。
|
|
56
|
+
|
|
57
|
+
代理形如 `{ "mode": "custom", "url": "http://127.0.0.1:10808", "bypass": "localhost,127.0.0.1" }`。inherit 使用进程变量;system 读取 Windows/macOS 静态代理,PAC/自动发现或其他平台用继承/自定义;none 清空本次代理变量,--no-http-proxy 等价 none。自定义地址不含认证,认证代理仅从环境继承。Podman 默认构建网络保留原代理地址,显式 --network pasta 等隔离网络时回环地址替换为 host.containers.internal;Docker 非 host 网络使用 host.docker.internal。须验证真实构建网络,run 结果不能代替;默认不通时显式试 --network pasta 或改用可达代理地址。Docker/远程 Podman 拉取使用引擎侧代理,不能用构建参数代替配置引擎,修改与重启独立安排。
|
|
58
|
+
|
|
59
|
+
Dockerfile 在 FROM 前声明 HAI_NODE_IMAGE / 适用的 HAI_NGINX_IMAGE,构建阶段用 HAI_NPM_REGISTRY 设置 COREPACK_NPM_REGISTRY、npm_config_registry、pnpm_config_registry。首个下载步骤验证代理 TCP 可达性,代理不进入运行 ENV。旧工程同步模板后重新构建;显式 tarball/Git URL 和原生包不由 registry 覆盖,SQLite 继续用 --sqlite-prebuild-mirror。deploy 的 npm 参数须与 --build 一起使用。
|
|
60
|
+
|
|
47
61
|
### SvelteKit Adapter 模式
|
|
48
62
|
|
|
49
|
-
CLI website模板默认Node SSR
|
|
63
|
+
CLI website模板默认Node SSR,只安装Core/Kit/UI;注释配置和环境变量参考不启用模块。后续增加的服务端业务依赖、初始化和API/actions须在静态发布前移除;在compile时使用`HAI_BUILD_STATIC=1`与可选`PUBLIC_BASE_PATH`切换完整静态预渲染,边界见本skill的静态网站说明;该模板没有使用下方VITE_ADAPTER工厂。静态产物用preview本机检查,上传build到Pages/CDN;生成的start/Docker配置属于普通SSR模式,不能用于运行静态HTML。切回SSR只需清除静态开关并重建,PUBLIC_BASE_PATH仍可按实际网站目录用于SSR或Docker;SvelteKit/Vite差异、制品格式和健康检查见[网站挂载目录](paths.md)。
|
|
50
64
|
|
|
51
65
|
仅在项目实际使用 SvelteKit 和 `@h-ai/kit/adapter` 时使用;Vite SPA 不配置 SvelteKit adapter,fullstack 各前端按自身 scripts 构建。
|
|
52
66
|
|
|
@@ -141,7 +155,7 @@ import { reldb, ReldbConfigSchema } from '@h-ai/reldb'
|
|
|
141
155
|
import { iam, IamConfigSchema } from '@h-ai/iam'
|
|
142
156
|
|
|
143
157
|
// 放入应用已有的启动函数;并发防护和关闭钩子复用当前模板。
|
|
144
|
-
core.init({ configDir: './config' })
|
|
158
|
+
core.init({ configDir: './config', configSchemas: { db: ReldbConfigSchema, cache: CacheConfigSchema, iam: IamConfigSchema } })
|
|
145
159
|
const dbConfig = core.config.validate('db', ReldbConfigSchema)
|
|
146
160
|
if (!dbConfig.success) return dbConfig
|
|
147
161
|
const cacheConfig = core.config.validate('cache', CacheConfigSchema)
|
|
@@ -61,6 +61,8 @@ lint/typecheck 只能执行已配置的规则。新增开发规范时区分可
|
|
|
61
61
|
- 若 E2E 稳定且耗时可控,可作为 required check。
|
|
62
62
|
- 若 E2E 依赖浏览器、服务、密钥或容易 flaky,先放到 `workflow_dispatch` / `schedule` / 独立 job。
|
|
63
63
|
- 应用级 E2E 使用:`pnpm --filter <app> test:e2e`。
|
|
64
|
+
- 依赖镜像或环境开关的关键 E2E,在 CI 中必须明确准备前置并启用;准备失败应阻断,不能让缺少配置导致的 skip 冒充验收。使用 Turbo 严格环境时,将影响测试执行范围的变量放入 `env`,同时传递变量并参与缓存身份,避免命中跳过用例时的缓存。
|
|
65
|
+
- 真实付费服务验收限用户已授权的可信分支和事件。可复用 workflow 的 secret 仅在调用方按事件显式传入,并只向定向验收步骤注入;安装、构建和普通根测试剥离该凭据。PR 继续使用夹具。成功和失败均可保留脱敏 JSON/HTML/截图证据,不能将密钥或认证头写入制品。
|
|
64
66
|
|
|
65
67
|
### hai-framework 依赖策略
|
|
66
68
|
|
|
@@ -17,10 +17,10 @@ description: "配置 hai 应用启动、YAML/环境变量、日志、i18n 与 Ha
|
|
|
17
17
|
|
|
18
18
|
## 使用路径
|
|
19
19
|
|
|
20
|
-
- Node 启动用 core.init({ configDir })
|
|
20
|
+
- Node 启动用 core.init({ configDir, configSchemas }) 加载配置;configSchemas 登记所用模块的公开 Schema,使 YAML 未声明的合法可选字段也能接收细项环境变量。勿传 logging 覆盖 _core.yml 中的日志设置,除非任务明确要求。
|
|
21
21
|
- 自动加载和监听配置目录内所有 YAML;项目根 `hai.json` 的 `build` / `deploy` 由 CLI 读取,不进入运行时配置。单项 load 返回失败时记日志并继续,必需配置须 validate 并检查 success;目录扫描、日志设置等异常仍可能传播。
|
|
22
22
|
- 应用名称、版本、环境、默认语言和日志设置统一从 `_core.yml` 经 core.config 获取,日志使用 core.logger;UI 不另维护硬编码副本。SvelteKit layout / fullstack app.info 仅下发公开字段白名单;独立 Vite mobile-app 在 Node 构建时解析并注入公开默认值,修改这些编译默认值需要重新构建。静态 Web 的公开 API 地址等通过 `runtime-config.js` 覆盖时无需重建镜像,原生包在复制静态资源前准备配置入口。浏览器不加载 YAML,不接收完整模块配置、密钥或日志脱敏上下文。
|
|
23
|
-
- `HAI_<配置名>_<路径>`
|
|
23
|
+
- `HAI_<配置名>_<路径>` 支持父 JSON 基础与细项覆盖,同源先父后子,高来源整段替换清除低来源细项。camelCase 不拆词,约定变量优先于显式插值;宿主显式提供 `configSchemas` 识别 YAML 缺少的可选路径,模块执行业务校验。
|
|
24
24
|
- 环境文件采用六类:`.env.example` 完整列出字段用途、默认值、枚举和范围;`.env` 为公共基础;`.env.dev`、`.env.test`、`.env.prod` 为公共环境覆盖,这五类均提交 Git。`.env.local` 为本机私有配置,不提交 Git。优先级为进程环境 > `.env.local` > 环境文件 > `.env`;Vite 在 Node 配置阶段调用 `core.config.loadEnvFiles({ mode, envDir })` 并检查 success 后注入进程,Node 脚本按相同顺序传入 `--env-file`。`core.init` 不自行读取 dotenv。
|
|
25
25
|
- 配置 getter 与 ID/logger 方法不是 HaiResult;validate/load 等须检查 success。
|
|
26
26
|
- 日志可用 `{ error }` 记录原生 Error;两端先规范化诊断再字段/URL 脱敏,配置 redact 路径仍生效。默认包含 passwordHash、passwd、secretKey 等实际凭据字段;异常文本清理显式凭据标签和 URL,不保证识别无标签秘密。原异常不变,错误子图循环/深度用固定标记;业务隐私仍由调用方清理或遮蔽整条路径。
|
|
@@ -118,8 +118,8 @@ unwatch()
|
|
|
118
118
|
|
|
119
119
|
- YAML 根、对象、数组和叶子节点均可映射为 `HAI_<配置名>_<YAML 路径>`;配置名和 key 转大写、层级以 `_` 分隔,camelCase 不拆词
|
|
120
120
|
- 例如 `core.config.load('ai', ...)` 加载的 `llm.apiKey` 对应 `HAI_AI_LLM_APIKEY`,`llm.models[0].temperature` 对应 `HAI_AI_LLM_MODELS_0_TEMPERATURE`
|
|
121
|
-
-
|
|
122
|
-
-
|
|
121
|
+
- 父 JSON 提供节点基础,随后细项覆盖;例如 HAI_DB 提供连接基础,HAI_DB_HOST 细化主机。数组 JSON 替换完整列表,索引仅更新已有元素,缺少列表或越界报错。Schema 决定字段类型,字符串保留前导零,变量/JSON 中的内容不再次插值。
|
|
122
|
+
- 所有入口统一来源:进程 > `.env.<mode>.local` > `.env.local` > `.env.<mode>` > `.env` > YAML。同源先父后子,与行序无关;高来源整段替换清除低来源细项。约定变量优先于显式 `${VAR}`。`configSchemas` 由宿主显式传入,Core 不导入业务模块;扫描只使用结构,模块负责完整校验。
|
|
123
123
|
|
|
124
124
|
- `${VAR}` — 读取 `process.env.VAR`;缺失则返回 `HaiConfigError.CONFIG_ENV_VAR_MISSING` 错误
|
|
125
125
|
- `${VAR:default}` — 读取 `process.env.VAR`;缺失则使用默认值
|
|
@@ -474,7 +474,7 @@ if (!result.success) {
|
|
|
474
474
|
|
|
475
475
|
### 服务端初始化(SvelteKit hooks.server.ts)
|
|
476
476
|
|
|
477
|
-
在应用已有的 init.ts 中执行 core.init({ configDir: './config' })
|
|
477
|
+
在应用已有的 init.ts 中执行 core.init({ configDir: './config', configSchemas }),configSchemas 登记所用模块的公开 Schema,再校验配置并初始化依赖模块;hooks.server.ts 等待该启动流程完成后接入 kit.createHandle。完整依赖顺序与失败清理见 [hai-build](../hai-build/SKILL.md)。不要在一个文件里同时 import 和声明同名 initModules。
|
|
478
478
|
|
|
479
479
|
### 配置校验最佳实践
|
|
480
480
|
|
|
@@ -48,7 +48,7 @@ Node listener 使用 socket 对端;Fetch 必须传可信平台 `remoteAddress`
|
|
|
48
48
|
|
|
49
49
|
## 部署与公共边界
|
|
50
50
|
|
|
51
|
-
`.env.<环境>` 或私有覆盖可设置 `HAI_SERV_HTTP_ACCESS='{"file":"./config/security/request-access.yml"}'` / `HAI_KIT_ACCESS
|
|
51
|
+
`.env.<环境>` 或私有覆盖可设置 `HAI_SERV_HTTP_ACCESS='{"file":"./config/security/request-access.yml"}'` / `HAI_KIT_ACCESS=...`。同来源 `HAI_SERV` / `HAI_KIT` JSON 提供基础,`HAI_SERV_HTTP_ACCESS_FILE` / `HAI_KIT_ACCESS_FILE` 等细项继续覆盖;更高来源整段替换会清除低来源细项。
|
|
52
52
|
|
|
53
53
|
部署校验当前选中后端的配置文件,规范化快照放入交付包并参加 SHA-256 校验,容器通过 `/run/hai/request-access.yml` 只读挂载。工程原始引用不回写成容器路径。仅更新前端时保留后端原文件;失败恢复使用旧发布的 Compose/文件。待上传归档和 SDK 策略快照不能手改;替换快照时重新打包。已部署 Node 后端挂载的 `.env.runtime` 可按[部署指引](../hai-deploy/SKILL.md#后端运行配置重载)修改后重启,注意上层 JSON 优先级;不会自动重写 Nginx 或策略文件。镜像/配置回退不能撤销数据库和业务效果。
|
|
54
54
|
|
|
@@ -214,7 +214,7 @@ const sharedServer = crypto.transport.createServer({
|
|
|
214
214
|
|
|
215
215
|
`serverKeyRing` 为 `{ activeKeyId, keys: { [id]: { publicKey, privateKey } } }`,ID 为 1–64 个字母、数字、下划线或连字符。根入口 `parseTransportServerKeyRing(secret)` 接受服务端 Secret JSON 或配置对象,返回 HaiResult,错误不附带原始 Secret 或解析片段。工厂验证每对公私钥并捕获快照;没有隐式随机私钥或 memory registry 回退。`keyStore` 也必填,生产用共享 Redis/数据库;开发/测试可显式 `createInMemoryKeyStore(capacity)`。
|
|
216
216
|
|
|
217
|
-
示例服务端读取 `process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING`,检查解析结果后仅传给服务端工厂;禁止写入普通 YAML、PUBLIC_/VITE_、日志或响应。CLI 创建服务端项目时在 `.env.dev/.env.test/.env.prod` 分别生成独立 ring;环境文件随项目提交,克隆用户共享对应环境配置。可由进程环境、私有覆盖或 Secret Manager 替换,同一环境所有节点使用同一 ring。
|
|
217
|
+
示例服务端读取 `process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING`,检查解析结果后仅传给服务端工厂;禁止写入普通 YAML、PUBLIC_/VITE_、日志或响应。CLI 创建服务端项目时在 `.env.dev.local/.env.test.local/.env.prod.local` 分别生成独立 ring;环境文件随项目提交,克隆用户共享对应环境配置。可由进程环境、私有覆盖或 Secret Manager 替换,同一环境所有节点使用同一 ring。
|
|
218
218
|
|
|
219
219
|
请求密文 `EncryptedRequestPayload` 携带协商的 `serverKeyId`;服务端只按 ID 选择私钥,未知/撤销 ID 拒绝,不尝试其它 key。轮换先向所有节点分发包含新旧版本的 ring,再切 activeKeyId 并滚动重建 manager;过渡期后删除旧 ID 并更新所有节点。旧客户端 destroy 后重协商;禁止自动重放非幂等请求。共享 ring + registry 支持跨节点与重启,不依赖 sticky session。
|
|
220
220
|
|