@h-ai/cli 1.0.0-beta.6 → 1.0.0-beta.8
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 +9 -9
- package/README.ja.md +9 -9
- package/README.md +9 -9
- package/dist/{chunk-4XVCGFBA.js → chunk-MEEFB4BI.js} +552 -11
- package/dist/chunk-MEEFB4BI.js.map +1 -0
- package/dist/{chunk-STWZQ3IP.js → chunk-SGWSVWZ5.js} +588 -11
- package/dist/chunk-SGWSVWZ5.js.map +1 -0
- package/dist/cli-config-templates-SVV3633D.js +5 -0
- package/dist/{cli-config-templates-YGPVNRX5.js.map → cli-config-templates-SVV3633D.js.map} +1 -1
- package/dist/index.js +97 -78
- package/dist/index.js.map +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/{src-NXCCU6GO.js → src-IW4CY2IN.js} +3 -3
- package/dist/src-IW4CY2IN.js.map +1 -0
- package/package.json +16 -12
- package/templates/apps/api/apps/{{projectName}}-serv/config/_serv.yml.hbs +1 -1
- package/templates/apps/api/apps/{{projectName}}-serv/src/app.ts.hbs +6 -2
- package/templates/apps/api/apps/{{projectName}}-serv/tests/service.test.ts.hbs +29 -3
- package/templates/apps/fullstack/.gitignore.hbs +7 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +2 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +2 -2
- package/templates/apps/mobile-app/messages/en-US.json +1 -1
- package/templates/apps/mobile-app/messages/ja-JP.json +1 -1
- package/templates/apps/mobile-app/messages/zh-CN.json +1 -1
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +8 -5
- package/templates/base/.gitignore.hbs +7 -2
- package/templates/base/e2e/app.spec.ts.hbs +9 -4
- package/templates/base/package.json.hbs +7 -2
- package/templates/skills/hai-ai/SKILL.md +6 -2
- package/templates/skills/hai-ai/acp.md +145 -0
- package/templates/skills/hai-ai/guide.md +1 -1
- package/templates/skills/hai-ai/reference.md +1 -1
- package/templates/skills/hai-ai/resources.md +69 -0
- package/templates/skills/hai-api-client/reference.md +2 -0
- package/templates/skills/hai-app-create/development.md +1 -1
- package/templates/skills/hai-app-create/reference.md +1 -1
- package/templates/skills/hai-build/SKILL.md +1 -1
- package/templates/skills/hai-build/reference.md +1 -1
- package/templates/skills/hai-cache/reference.md +6 -2
- package/templates/skills/hai-ci/SKILL.md +2 -0
- package/templates/skills/hai-core/SKILL.md +1 -1
- package/templates/skills/hai-crypto/reference.md +2 -0
- package/templates/skills/hai-payment/reference.md +39 -17
- package/templates/skills/hai-reach/reference.md +15 -7
- package/templates/skills/hai-reldb/reference.md +5 -2
- package/templates/skills/hai-scheduler/reference.md +1 -1
- package/templates/skills/hai-storage/reference.md +8 -2
- package/templates/skills/hai-vecdb/reference.md +7 -2
- package/dist/chunk-4XVCGFBA.js.map +0 -1
- package/dist/chunk-STWZQ3IP.js.map +0 -1
- package/dist/cli-config-templates-YGPVNRX5.js +0 -5
- package/dist/src-NXCCU6GO.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { DEPLOY_MIDDLEWARE, DeployConfigSchema, DeploymentArtifactSchema, HaiDeployError, ProviderConfigSchema, artifactChecksum, canKeepMiddleware, createDeploymentPlan, deploy, describeMiddlewareBackend, getDeployCommandState, getDeployRecovery, importDeploymentSecrets, loadDeploymentConfig, loadDeploymentEnvironment, mergeEnvironmentLayers, normalizeProjectName, parseMiddlewareField, readDeploymentArtifact, resolveDownloadProxy, resolveImageReference, saveDeploymentEnvironment, saveMiddlewareEnvironment, validateMiddlewareField } from './chunk-
|
|
2
|
+
export { DEPLOY_MIDDLEWARE, DeployConfigSchema, DeploymentArtifactSchema, HaiDeployError, ProviderConfigSchema, artifactChecksum, canKeepMiddleware, createDeploymentPlan, deploy, describeMiddlewareBackend, getDeployCommandState, getDeployRecovery, importDeploymentSecrets, loadDeploymentConfig, loadDeploymentEnvironment, mergeEnvironmentLayers, normalizeProjectName, parseMiddlewareField, readDeploymentArtifact, resolveDownloadProxy, resolveImageReference, saveDeploymentEnvironment, saveMiddlewareEnvironment, validateMiddlewareField } from './chunk-MEEFB4BI.js';
|
|
3
3
|
import './chunk-ZSDEVXZ7.js';
|
|
4
|
-
//# sourceMappingURL=src-
|
|
5
|
-
//# sourceMappingURL=src-
|
|
4
|
+
//# sourceMappingURL=src-IW4CY2IN.js.map
|
|
5
|
+
//# sourceMappingURL=src-IW4CY2IN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"src-IW4CY2IN.js"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-ai/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.8",
|
|
5
5
|
"description": "Hai Framework CLI for project scaffolding and code generation.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"keywords": [
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"node": "^22.23.4 || ^24.22.1 || ^26.12.1"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@h-ai/core": "1.0.0-beta.
|
|
33
|
+
"@h-ai/core": "1.0.0-beta.8",
|
|
34
34
|
"cac": "^7.0.1",
|
|
35
35
|
"chalk": "^6.0.1",
|
|
36
36
|
"fast-wrap-ansi": "^0.2.2",
|
|
@@ -41,15 +41,18 @@
|
|
|
41
41
|
"yaml": "^2.9.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@h-ai/api-client": "1.0.0-beta.
|
|
45
|
-
"@h-ai/cache": "1.0.0-beta.
|
|
46
|
-
"@h-ai/crypto": "1.0.0-beta.
|
|
47
|
-
"@h-ai/deploy": "1.0.0-beta.
|
|
48
|
-
"@h-ai/feedback": "1.0.0-beta.
|
|
49
|
-
"@h-ai/iam": "1.0.0-beta.
|
|
50
|
-
"@h-ai/
|
|
51
|
-
"@h-ai/
|
|
52
|
-
"@h-ai/
|
|
44
|
+
"@h-ai/api-client": "1.0.0-beta.8",
|
|
45
|
+
"@h-ai/cache": "1.0.0-beta.8",
|
|
46
|
+
"@h-ai/crypto": "1.0.0-beta.8",
|
|
47
|
+
"@h-ai/deploy": "1.0.0-beta.8",
|
|
48
|
+
"@h-ai/feedback": "1.0.0-beta.8",
|
|
49
|
+
"@h-ai/iam": "1.0.0-beta.8",
|
|
50
|
+
"@h-ai/kit": "1.0.0-beta.8",
|
|
51
|
+
"@h-ai/payment": "1.0.0-beta.8",
|
|
52
|
+
"@h-ai/reach": "1.0.0-beta.8",
|
|
53
|
+
"@h-ai/reldb": "1.0.0-beta.8",
|
|
54
|
+
"@h-ai/storage": "1.0.0-beta.8",
|
|
55
|
+
"@h-ai/vecdb": "1.0.0-beta.8",
|
|
53
56
|
"@types/fs-extra": "^11.0.4",
|
|
54
57
|
"@types/node": "^26.6.5",
|
|
55
58
|
"@types/prompts": "^2.4.9",
|
|
@@ -59,7 +62,8 @@
|
|
|
59
62
|
"testcontainers": "^12.2.0",
|
|
60
63
|
"tsup": "^8.5.1",
|
|
61
64
|
"typescript": "^6.0.3",
|
|
62
|
-
"vitest": "^4.1.11"
|
|
65
|
+
"vitest": "^4.1.11",
|
|
66
|
+
"zod": "^4.6.5"
|
|
63
67
|
},
|
|
64
68
|
"scripts": {
|
|
65
69
|
"build": "tsup",
|
|
@@ -33,7 +33,7 @@ http:
|
|
|
33
33
|
|
|
34
34
|
cors:
|
|
35
35
|
origin: ${HAI_SERV_CORS_ORIGIN:*}
|
|
36
|
-
nativeOrigins: ${HAI_SERV_CORS_NATIVEORIGINS:http://localhost,https://tauri.localhost,tauri://localhost,capacitor://localhost}
|
|
36
|
+
nativeOrigins: ${HAI_SERV_CORS_NATIVEORIGINS:http://localhost,https://localhost,http://tauri.localhost,https://tauri.localhost,tauri://localhost,capacitor://localhost}
|
|
37
37
|
{{#if features.iam}} credentials: ${HAI_SERV_CORS_CREDENTIALS:true}
|
|
38
38
|
{{else}} credentials: ${HAI_SERV_CORS_CREDENTIALS:false}
|
|
39
39
|
{{/if}} allowedHeaders:
|
|
@@ -68,7 +68,11 @@ export function {{api.serviceAppFactoryName}}(_options: CreateApiServiceAppOptio
|
|
|
68
68
|
{{else}} const transport = undefined
|
|
69
69
|
{{/if}}
|
|
70
70
|
{{#if features.iam}}
|
|
71
|
-
const refreshCookie = _options.refreshCookie === 'disabled'
|
|
71
|
+
const refreshCookie = _options.refreshCookie === 'disabled'
|
|
72
|
+
? undefined
|
|
73
|
+
: { secure: security.secureRefreshCookie, nativeTokenTransport: { isRequest: (request: Request) => request.headers.get('x-hai-token-transport') === 'body'
|
|
74
|
+
&& ['hai-mobile-app', 'hai-desktop-app'].includes(request.headers.get('x-request-client') ?? '')
|
|
75
|
+
&& security.isNativeOrigin(request.headers.get('origin') ?? '') } }
|
|
72
76
|
{{/if}}
|
|
73
77
|
{{#if features.feedback}}
|
|
74
78
|
// 普通业务日志与反馈使用同一可信应用范围;身份仍由 IAM 验证。
|
|
@@ -113,7 +117,7 @@ export function {{api.serviceAppFactoryName}}(_options: CreateApiServiceAppOptio
|
|
|
113
117
|
middleware: serv.cors({
|
|
114
118
|
origin: security.allowOrigin,
|
|
115
119
|
credentials: servConfig.cors.credentials,
|
|
116
|
-
allowedHeaders: [...servConfig.cors.allowedHeaders],
|
|
120
|
+
allowedHeaders: [...new Set([...servConfig.cors.allowedHeaders, 'x-request-client', 'x-hai-token-transport'])],
|
|
117
121
|
exposedHeaders: [...servConfig.cors.exposedHeaders],
|
|
118
122
|
}),
|
|
119
123
|
},
|
|
@@ -14,7 +14,7 @@ import { join } from 'node:path'
|
|
|
14
14
|
{{#if features.storage}}import { storage } from '@h-ai/storage'
|
|
15
15
|
{{/if}}{{#if features.vecdb}}import { vecdb } from '@h-ai/vecdb'
|
|
16
16
|
{{/if}}import { {{api.contractExportName}} } from '{{api.contractPackageName}}'
|
|
17
|
-
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
17
|
+
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
|
|
18
18
|
|
|
19
19
|
import { {{api.serviceAppFactoryName}} } from '../src/app.js'
|
|
20
20
|
|
|
@@ -44,6 +44,7 @@ beforeAll(async () => {
|
|
|
44
44
|
{{/if}}{{#if features.vecdb}} vecdbRoot = join(tempRoot, 'vecdb')
|
|
45
45
|
{{/if}}
|
|
46
46
|
|
|
47
|
+
vi.stubEnv('HAI_SERV_CORS_NATIVEORIGINS', undefined)
|
|
47
48
|
core.init({
|
|
48
49
|
configDir: './config',
|
|
49
50
|
logging: { level: 'warn' },
|
|
@@ -82,8 +83,7 @@ beforeAll(async () => {
|
|
|
82
83
|
{{/if}}
|
|
83
84
|
|
|
84
85
|
const app = {{api.serviceAppFactoryName}}({
|
|
85
|
-
transport: 'disabled',
|
|
86
|
-
refreshCookie: 'disabled',{{/if}}
|
|
86
|
+
transport: 'disabled',
|
|
87
87
|
})
|
|
88
88
|
const listening = new Promise<AddressInfo>((resolve) => {
|
|
89
89
|
server = serv.listen(app, {
|
|
@@ -98,6 +98,8 @@ beforeAll(async () => {
|
|
|
98
98
|
client = apiClient.create({{api.contractExportName}})
|
|
99
99
|
const initResult = await client.init({
|
|
100
100
|
baseUrl: `${baseUrl}${servConfig.http.apiPrefix}`,
|
|
101
|
+
clientName: 'hai-mobile-app',
|
|
102
|
+
headers: { 'origin': 'capacitor://localhost', 'x-hai-token-transport': 'body' },
|
|
101
103
|
auth: { storage: apiClient.tokenStorage.memory() },
|
|
102
104
|
})
|
|
103
105
|
if (!initResult.success)
|
|
@@ -114,6 +116,7 @@ afterAll(async () => {
|
|
|
114
116
|
{{/if}}{{#if features.cache}} await cache.close()
|
|
115
117
|
{{/if}}{{#if features.db}} await reldb.close()
|
|
116
118
|
{{/if}} rmSync(tempRoot, { recursive: true, force: true })
|
|
119
|
+
vi.unstubAllEnvs()
|
|
117
120
|
})
|
|
118
121
|
|
|
119
122
|
describe('{{api.servPackageName}}', () => {
|
|
@@ -144,6 +147,29 @@ describe('{{api.servPackageName}}', () => {
|
|
|
144
147
|
})
|
|
145
148
|
|
|
146
149
|
{{#if features.iam}}
|
|
150
|
+
it.each([
|
|
151
|
+
['capacitor://localhost', 'hai-mobile-app'],
|
|
152
|
+
['https://localhost', 'hai-mobile-app'],
|
|
153
|
+
['http://tauri.localhost', 'hai-desktop-app'],
|
|
154
|
+
['https://tauri.localhost', 'hai-desktop-app'],
|
|
155
|
+
['tauri://localhost', 'hai-desktop-app'],
|
|
156
|
+
])('refreshes an expired access token at the default native Origin %s', async (origin, clientName) => {
|
|
157
|
+
const native = apiClient.create({{api.contractExportName}})
|
|
158
|
+
try {
|
|
159
|
+
expect((await native.init({ baseUrl: `${baseUrl}${servConfig.http.apiPrefix}`, clientName, headers: { 'origin': origin, 'x-hai-token-transport': 'body' }, auth: { storage: apiClient.tokenStorage.memory() } })).success).toBe(true)
|
|
160
|
+
const registration = await apiClient.createAuthActions(native).register(uniqueUser('refresh'))
|
|
161
|
+
expect(registration.success).toBe(true)
|
|
162
|
+
if (!registration.success)
|
|
163
|
+
return
|
|
164
|
+
expect(registration.data.tokens.refreshToken).toBeTruthy()
|
|
165
|
+
await native.auth.setTokens({ ...registration.data.tokens, accessToken: 'expired-access-for-test' })
|
|
166
|
+
expect((await native.iam.auth.currentUser()).success).toBe(true)
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
await native.close()
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
|
|
147
173
|
it('registers a user and returns authenticated echo context', async () => {
|
|
148
174
|
const user = uniqueUser('svc')
|
|
149
175
|
const authActions = apiClient.createAuthActions(client)
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"lint:fix": "eslint . --fix",
|
|
24
24
|
"test": "pnpm paraglide:compile && vitest run --passWithNoTests",
|
|
25
25
|
"test:watch": "pnpm paraglide:compile && vitest",
|
|
26
|
-
"clean": "rimraf dist build
|
|
26
|
+
"clean": "rimraf dist build .turbo src/lib/paraglide",
|
|
27
27
|
"cap:add:android": "cap add android",
|
|
28
28
|
"cap:add:ios": "cap add ios",
|
|
29
29
|
"cap:sync": "pnpm build && cap sync",
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
- `config/_core.yml`:应用元信息(名称 / 版本 / 环境 / 语言),启动时由 `core.config` 加载。
|
|
20
20
|
- `config/_serv.yml`:服务端运行时设置(CORS 来源等)。
|
|
21
21
|
- `config/_db.yml` / `_cache.yml` / `_iam.yml` / `_storage.yml` / `_audit.yml` / `_reach.yml`:管理及开发通知配置,初始化前使用模块公开 Schema 验证。Reach 默认控制台 Provider,生产按注释替换为 SMTP/短信/API。
|
|
22
|
-
- `config/_ai.yml` / `_vecdb.yml` / `_payment.yml` / `
|
|
22
|
+
- `config/_ai.yml` / `_vecdb.yml` / `_payment.yml` / `_kit.yml`:保留未启用模块的完整注释示例;配置文件存在不代表模块已接入。先在本 workspace 安装对应依赖,再在 `src/server/runtime.ts` 按公开 Schema 校验、初始化并反序关闭,接入实际业务后按需取消注释。整份注释先去掉一层外层注释,可选字段继续按需开启。scheduler 已用于审计任务,配置位于 `_scheduler.yml`。
|
|
23
23
|
- `src/index.ts`:进程入口,通过 `src/server/runtime.ts` 先向 Core 注册各模块公开 Schema 并加载配置,再初始化 reldb → cache → crypto → storage → reach(有配置时)→ audit → iam → scheduler(有任务时);YAML 未声明的合法可选字段同样支持细项环境变量,启动失败及退出反序释放。
|
|
24
|
-
-
|
|
24
|
+
- 默认不安装 AI、vecdb、payment 和 datapipe。后续接入 AI 时安装对应依赖,在 reldb/cache 之后初始化 vecdb、在其余依赖之后初始化 AI;关闭时先释放 AI,最后释放 vecdb/关系库。生产环境必须使用共享后端,根 `.env.prod` 默认连接 Compose `db/cache/storage`,新增 vecdb 后补充共享向量库配置;凭据和真实 Reach Provider JSON 私有注入。
|
|
25
25
|
- `src/app-server.ts`:服务装配入口,挂载健康检查、OpenAPI、docs 与 procedures;应用名称 / 版本与 CORS 来源均取自上述配置。
|
|
26
26
|
- `src/server/procedures/app-procedures.ts`:app 领域 procedures,对应 contract 中的 `app.info` / `app.echo` 路由。
|
|
27
27
|
- `src/server/procedures/admin-procedures.ts`:认证、管理员用户/角色/审计管理和个人设置,使用 `.auth()` / `.permission(...)` 守卫。每个请求读取用户最新启用状态、角色和权限。
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"status_runtime_web": "Browser preview",
|
|
25
25
|
"status_token_label": "Token storage",
|
|
26
26
|
"status_token_native": "Secure storage",
|
|
27
|
-
"status_token_memory": "Memory
|
|
27
|
+
"status_token_memory": "Memory access token + httpOnly cookie",
|
|
28
28
|
"tasks_title": "Today's tasks",
|
|
29
29
|
"tasks_sync": "Wire backend APIs",
|
|
30
30
|
"tasks_polish": "Polish mobile UX",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"status_runtime_web": "ブラウザープレビュー",
|
|
25
25
|
"status_token_label": "トークン保存",
|
|
26
26
|
"status_token_native": "安全なストレージ",
|
|
27
|
-
"status_token_memory": "
|
|
27
|
+
"status_token_memory": "メモリー内access token + httpOnly Cookie",
|
|
28
28
|
"tasks_title": "本日のタスク",
|
|
29
29
|
"tasks_sync": "バックエンドAPIを接続",
|
|
30
30
|
"tasks_polish": "モバイルUXを改善",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"status_runtime_web": "浏览器预览",
|
|
25
25
|
"status_token_label": "Token 存储",
|
|
26
26
|
"status_token_native": "安全存储",
|
|
27
|
-
"status_token_memory": "
|
|
27
|
+
"status_token_memory": "内存 access token + httpOnly Cookie",
|
|
28
28
|
"tasks_title": "今日任务",
|
|
29
29
|
"tasks_sync": "同步后端接口",
|
|
30
30
|
"tasks_polish": "打磨移动端体验",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* {{projectName}} — API 客户端初始化。
|
|
3
|
-
* Web
|
|
3
|
+
* Web 预览使用内存 access token + httpOnly refresh Cookie;原生端使用 Capacitor 安全存储。
|
|
4
4
|
* 原生存储失败会拒绝 Promise;登录/退出调用方须处理 auth.setTokens/clear 失败并释放界面 loading。
|
|
5
5
|
*/
|
|
6
6
|
import { apiClient } from '@h-ai/api-client'
|
|
@@ -56,7 +56,7 @@ export function resolveApiBase(): string {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* 查询实际 Capacitor 平台,决定安全原生存储或 Web
|
|
59
|
+
* 查询实际 Capacitor 平台,决定安全原生存储或 Web Cookie 刷新通道。
|
|
60
60
|
* @returns 是否处于 Capacitor 原生平台;浏览器预览返回 false。
|
|
61
61
|
* @throws Error - 平台模块加载或检测失败时拒绝 Promise,不伪装为 Web 模式。
|
|
62
62
|
* @example 模块内部上下文
|
|
@@ -74,7 +74,7 @@ export async function usesNativeTokenStorage(): Promise<boolean> {
|
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
76
|
* 按真实平台创建 TokenStorage;绑定 client 后通过 client.auth 变更身份。
|
|
77
|
-
* @returns
|
|
77
|
+
* @returns 原生安全存储或内存 access token + httpOnly refresh Cookie;其方法的读写故障继续拒绝 Promise。
|
|
78
78
|
* @throws Error - 平台检测或存储工厂失败时拒绝,不改用较弱的持久化方式。
|
|
79
79
|
* @example 模块内部上下文
|
|
80
80
|
* ```ts
|
|
@@ -86,7 +86,7 @@ async function createTokenStorage() {
|
|
|
86
86
|
if (await usesNativeTokenStorage())
|
|
87
87
|
return createCapacitorTokenStorage()
|
|
88
88
|
|
|
89
|
-
return apiClient.tokenStorage.
|
|
89
|
+
return apiClient.tokenStorage.httpOnlyCookie()
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
/**
|
|
@@ -137,6 +137,7 @@ export async function initApi(): Promise<void> {
|
|
|
137
137
|
* ```
|
|
138
138
|
*/
|
|
139
139
|
async function initialize(): Promise<void> {
|
|
140
|
+
const native = await usesNativeTokenStorage()
|
|
140
141
|
const storage = await createTokenStorage()
|
|
141
142
|
{{#if features.crypto}}
|
|
142
143
|
const transport = resolveTransport()
|
|
@@ -149,6 +150,8 @@ async function initialize(): Promise<void> {
|
|
|
149
150
|
}
|
|
150
151
|
const result = await apiClient.init({
|
|
151
152
|
baseUrl: resolveApiBase(),
|
|
153
|
+
clientName: 'hai-mobile-app',
|
|
154
|
+
headers: native ? { 'x-hai-token-transport': 'body' } : {},
|
|
152
155
|
auth: { storage },
|
|
153
156
|
...(transport ? { transport } : {}),
|
|
154
157
|
})
|
|
@@ -169,7 +172,7 @@ async function initialize(): Promise<void> {
|
|
|
169
172
|
throw error
|
|
170
173
|
}
|
|
171
174
|
{{else}}
|
|
172
|
-
const result = await apiClient.init({ baseUrl: resolveApiBase(), auth: { storage } })
|
|
175
|
+
const result = await apiClient.init({ baseUrl: resolveApiBase(), clientName: 'hai-mobile-app', headers: native ? { 'x-hai-token-transport': 'body' } : {}, auth: { storage } })
|
|
173
176
|
if (!result.success)
|
|
174
177
|
throw new Error(result.error.message)
|
|
175
178
|
initialized = true
|
|
@@ -120,12 +120,16 @@ test('configured IAM registration and password login', async ({ page, context })
|
|
|
120
120
|
await registerForm.locator('#register-confirm-password').fill(password)
|
|
121
121
|
await registerForm.locator('button[type="submit"]').click()
|
|
122
122
|
await expect(page.locator('.register-form')).not.toBeVisible()
|
|
123
|
-
{{#
|
|
123
|
+
{{#if isCapacitorApp}}
|
|
124
|
+
// 浏览器预览通过刷新 Cookie 恢复会话,不能把页面重载当作退出。
|
|
125
|
+
await page.reload()
|
|
126
|
+
await expect(page.getByRole('navigation')).toBeVisible()
|
|
127
|
+
{{else}}
|
|
124
128
|
// 通过页面 fetch 复用 Cookie 与已安装的传输加密,避免 HTTP 辅助客户端绕过浏览器协议。
|
|
125
129
|
const me = await page.evaluate(async url => (await fetch(url)).status, new URL('./api/auth/me', test.info().project.use.baseURL).href)
|
|
126
130
|
expect(me).toBe(200)
|
|
131
|
+
{{/if}}
|
|
127
132
|
await context.clearCookies()
|
|
128
|
-
{{/unless}}
|
|
129
133
|
await page.reload()
|
|
130
134
|
await page.goto(authUrl)
|
|
131
135
|
await expect(page.locator('#login-username')).toBeVisible()
|
|
@@ -204,8 +208,9 @@ test('configured IAM registration and password login', async ({ page, context })
|
|
|
204
208
|
expect(new URL(page.url()).searchParams.has('token')).toBe(false)
|
|
205
209
|
await page.reload()
|
|
206
210
|
{{#if isCapacitorApp}}
|
|
207
|
-
//
|
|
208
|
-
await expect(page.
|
|
211
|
+
// 已消费的重置令牌不再参与启动;刷新 Cookie 恢复同一已登录会话。
|
|
212
|
+
await expect(page.getByRole('navigation')).toBeVisible()
|
|
213
|
+
await expect(page.locator('#login-username')).not.toBeVisible()
|
|
209
214
|
{{else}}
|
|
210
215
|
expect(await page.evaluate(async url => (await fetch(url)).status, new URL('./api/auth/me', test.info().project.use.baseURL).href)).toBe(200)
|
|
211
216
|
{{/if}}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"engines": { "node": "^22.23.4 || ^24.22.1 || ^26.12.1" },
|
|
3
3
|
"name": "{{projectName}}",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.8",
|
|
5
5
|
"private": true,
|
|
6
6
|
"packageManager": "pnpm@12.11.2",
|
|
7
7
|
"description": "{{#if_eq appType 'admin'}}Enterprise management system built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'website'}}Enterprise website built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'h5'}}Mobile H5 app built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'api'}}REST API service built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'mobile-app'}}Mobile app built with hai Agent Framework{{/if_eq}}",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"test": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}vitest run",
|
|
42
42
|
"test:watch": "vitest",
|
|
43
43
|
"test:e2e": "node scripts/run-e2e.mjs",
|
|
44
|
-
"clean": "{{#if isSvelteOnlyApp}}rimraf dist build
|
|
44
|
+
"clean": "{{#if isSvelteOnlyApp}}rimraf dist build .turbo src/lib/paraglide{{else}}rimraf .svelte-kit build .turbo{{/if}}"{{#if isCapacitorApp}},
|
|
45
45
|
"cap:add:android": "cap add android",
|
|
46
46
|
"cap:add:ios": "cap add ios",
|
|
47
47
|
"cap:sync": "pnpm compile && cap sync",
|
|
@@ -56,7 +56,9 @@
|
|
|
56
56
|
"cap:build:ios": "pnpm compile && cap sync ios && cap build ios"{{/if}}
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
+
{{#unless isSvelteOnlyApp}}
|
|
59
60
|
"pino-pretty": "{{versions.pinoPretty}}",
|
|
61
|
+
{{/unless}}
|
|
60
62
|
{{#if isSvelteOnlyApp}}
|
|
61
63
|
"@h-ai/api-client": "{{haiPackageSpecifier}}",
|
|
62
64
|
"@h-ai/capacitor": "{{haiPackageSpecifier}}",
|
|
@@ -96,6 +98,9 @@
|
|
|
96
98
|
"@h-ai/cli": "{{haiPackageSpecifier}}",
|
|
97
99
|
{{#if isSvelteOnlyApp}}
|
|
98
100
|
"@h-ai/core": "{{haiPackageSpecifier}}",
|
|
101
|
+
{{#if features.iam}}
|
|
102
|
+
"@h-ai/api-contract": "{{haiPackageSpecifier}}",
|
|
103
|
+
{{/if}}
|
|
99
104
|
{{/if}}
|
|
100
105
|
"@antfu/eslint-config": "{{versions.antfuEslintConfig}}",
|
|
101
106
|
"@playwright/test": "{{versions.playwright}}",
|
|
@@ -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,145 @@
|
|
|
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
|
+
Linux 上运行 embedded Codex 前,按 [Codex 沙箱前置](https://learn.chatgpt.com/docs/sandboxing) 准备 bubblewrap 和所需的 AppArmor profile,并用 `codex sandbox` 在临时目录验证读写。沙箱初始化失败应先修复宿主环境,不能通过关闭沙箱或让模型猜测文件内容完成任务。
|
|
18
|
+
|
|
19
|
+
## 原生 HTTP/WS 服务:Goose
|
|
20
|
+
|
|
21
|
+
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 模型密钥。
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { ai } from '@h-ai/ai'
|
|
25
|
+
|
|
26
|
+
// ai 已初始化,Goose 已运行;HAI_GOOSE_SECRET 为服务端环境变量,值与远端服务密钥一致。
|
|
27
|
+
async function connectGoose() {
|
|
28
|
+
const registered = await ai.acp.agents.register({
|
|
29
|
+
id: 'goose', name: 'Goose', mode: 'custom-service',
|
|
30
|
+
transport: 'http', url: 'http://127.0.0.1:3284/acp',
|
|
31
|
+
supportedTransports: ['http', 'ws'],
|
|
32
|
+
headerEnv: { 'X-Secret-Key': 'HAI_GOOSE_SECRET' },
|
|
33
|
+
})
|
|
34
|
+
if (!registered.success) return registered
|
|
35
|
+
return ai.acp.sessions.create({ agentId: 'goose', cwd: '/workspace' })
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
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)。
|
|
40
|
+
|
|
41
|
+
## 配置和数据库共同维护
|
|
42
|
+
|
|
43
|
+
`acp.agents`、`acp.workspaces` 可以直接写在配置文件里,也可通过 register 保存到数据库。get/list 返回 `{ value, source, version? }`,配置同 ID 优先且不可修改;数据库支持完整 update 和 remove。模型、MCP 等清单也遵循此规则,见 [资源清单](resources.md)。创建会话可通过 `mcpServerIds` 绑定合并清单里的 MCP。
|
|
44
|
+
|
|
45
|
+
## 最小本机接入
|
|
46
|
+
|
|
47
|
+
下面的目录、Git 地址、模型和密钥由部署方提供。首次登记一次,后续用 get/list 读取;重复 register 返回冲突。普通 API 都先检查 `success`。
|
|
48
|
+
|
|
49
|
+
Windows stdio 需要系统 PowerShell 可用;npm 的 `.cmd` 包装应改用 `command: 'node.exe'` 和实际 JS 入口参数数组。框架跟踪整个进程树,关闭时清理后台子进程;系统策略阻止启动时直接返回错误。
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { ai } from '@h-ai/ai'
|
|
53
|
+
|
|
54
|
+
async function run() {
|
|
55
|
+
const initialized = await ai.init({
|
|
56
|
+
llm: { models: [{
|
|
57
|
+
id: 'coding', model: 'your-responses-model', protocol: 'responses',
|
|
58
|
+
apiKey: process.env.MODEL_API_KEY,
|
|
59
|
+
baseUrl: 'https://models.example.com/v1',
|
|
60
|
+
capabilities: { contextWindow: 128000, maxOutputTokens: 16384, inputModalities: ['text'], outputModalities: ['text'], toolCalling: true, reasoning: { supported: false } },
|
|
61
|
+
}] },
|
|
62
|
+
})
|
|
63
|
+
if (!initialized.success) return initialized
|
|
64
|
+
try {
|
|
65
|
+
const agent = await ai.acp.agents.register({ id: 'coder', name: 'Codex', mode: 'embedded', adapter: 'codex', modelIds: ['coding'], defaultModelId: 'coding' })
|
|
66
|
+
if (!agent.success) return agent
|
|
67
|
+
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' }] })
|
|
68
|
+
if (!workspace.success) return workspace
|
|
69
|
+
const ready = await ai.acp.workspaces.prepare('project')
|
|
70
|
+
if (!ready.success) return ready
|
|
71
|
+
const created = await ai.acp.sessions.create({ agentId: 'coder', workspaceId: 'project', newWorktree: true })
|
|
72
|
+
if (!created.success) return created
|
|
73
|
+
const session = created.data
|
|
74
|
+
const response = await session.prompt([{ type: 'text', text: '阅读工程并说明结构' }])
|
|
75
|
+
if (!response.success) return response
|
|
76
|
+
return response.data.text
|
|
77
|
+
}
|
|
78
|
+
finally { await ai.close() }
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`agents.initialize(id)` 检查模型绑定和原生配置能否生成;真正启动和握手用 `agents.check(id, { workspaceId })`。容器 Agent 的 `image` 必须与 workspace 的 image 一致。注册 volume 时固定实际选中的引擎;`auto` 优先就绪 Podman,再尝试 Docker,显式选定不可用引擎直接失败。
|
|
83
|
+
|
|
84
|
+
生产环境先初始化共享数据库和网络向量库,或注入 shared AIStoreProvider。Agent/workspace/session 记录和 CAS 占用存入 AI Store,原生历史保存在隔离状态目录。不能把临时内存 Provider 当跨节点锁。
|
|
85
|
+
|
|
86
|
+
## 容器运行与模型复用
|
|
87
|
+
|
|
88
|
+
源码仓库提供 `packages/ai/models/agents` Dockerfile 和构建命令:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
pnpm --filter @h-ai/ai agent:build codex-acp
|
|
92
|
+
pnpm --filter @h-ai/ai agent:build deepseek-harness --engine=docker --java --rust --go
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Codex 镜像明确包含 `@agentclientprotocol/codex-acp@2.5.0` 和 `@openai/codex@0.165.0`。所有镜像包含 Git、Node、Python;Java/Rust/Go 按构建选项安装。镜像自行构建、推送和部署;框架启动时不偷偷构建镜像、不挂引擎 socket。Dockerfile 随 npm 包的 models/agents 一起提供。
|
|
96
|
+
|
|
97
|
+
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/`。
|
|
98
|
+
|
|
99
|
+
Podman 不自动继承远程服务的代理变量。需要代理时,使用 Agent/workspace 的 `environmentRefs` 显式传入容器可达的地址;宿主的回环代理不能直接在容器内使用。
|
|
100
|
+
|
|
101
|
+
模型能力配置位于 `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 服务。
|
|
102
|
+
|
|
103
|
+
模型密钥在启动时重新解析;恢复时模型名、协议、地址、能力发生变化会拒绝,密钥轮换可以继续。自定义 ACP 服务只接受 `headerEnv` 里的认证引用,不接收 Hai 模型配置。
|
|
104
|
+
|
|
105
|
+
## 工程和 Git
|
|
106
|
+
|
|
107
|
+
每个 Git 工程必须写明 `mainBranch`,remote 默认为 origin。`prepare` 只克隆缺失工程并校验既有工程;不自动 pull、不重置脏目录。`workspaces.importFiles(id, [{ path, content }])` 添加普通文件,拒绝路径逃逸、链接和覆盖;没有 Git 工程的工作区无需安装 Git。涉及 Git 而命令缺失返回 `ACP_GIT_NOT_FOUND`。
|
|
108
|
+
|
|
109
|
+
当前托管 Git 面向普通仓库;发现子模块或 Git LFS 属性时返回 `ACP_UNSUPPORTED`,不把未拉取的子模块或 LFS 指针当作完整工程。需要这些能力的应用可自行准备环境并使用低层 `connect`,其 Git 生命周期由应用负责。
|
|
110
|
+
|
|
111
|
+
创建 session 默认共享 `main/` 路径,框架会阻止另一个共享 session 同时占用它。`newWorktree: true` 从每个工程当前已提交的本地主分支创建同名分支和 worktree,普通文件单独复制;可以指定 `branch`,默认 `acp/<sessionId>`。它不会自动拉远端,也不会导入主目录未提交修改。
|
|
112
|
+
|
|
113
|
+
- `session.updateMain()`:停止本 session 的执行环境,再 fetch 各工程的明确主分支并 ff-only 更新本地主目录;不把主分支合入 session,不自动 rebase。共享主目录被其他会话占用时返回 busy。
|
|
114
|
+
- `session.publish({ mode: 'push-branch', message, author: { name, email } })`:提交隔离 session 修改并推送其同名分支。
|
|
115
|
+
- `session.publish({ mode: 'merge-main', message, author })`:提交后检查、更新登记的主分支,再合并并推送。无 force push,不静默处理冲突。
|
|
116
|
+
|
|
117
|
+
发布仅允许框架创建的隔离 session,避免将共享目录中别人的修改算入当前任务。操作前停止受管进程/容器,避免背景工具继续写入;要继续编码显式 `sessions.resume(id)`。原生 Agent 自己创建的 worktree 不自动纳入框架发布范围。
|
|
118
|
+
|
|
119
|
+
多个工程不是原子事务。失败时返回 `ACP_PARTIAL_FAILURE`,`error.cause.reports` 列出每个工程 success、phase、commit 和 error;已完成工程不回滚,冲突保留现场。失败于 push 后,排除权限/网络问题可重试;远端结果不明时先核对实际分支。普通文件的修改留在 session 目录,不自动纳入 Git 发布。失败创建的 worktree 也保留,避免删除已有成果。
|
|
120
|
+
|
|
121
|
+
## 交互、事件和结果
|
|
122
|
+
|
|
123
|
+
`onEvent` 在创建会话前注册,接收原生 session/update(含 `_meta`);`session.stream(content, signal)` 提供有界 AsyncIterable,提前退出会取消该轮。prompt 返回 stopReason、text、events;有 messageId 时 text 取最后一条消息,完整消息仍在 events。没有消息边界的 Agent 只能拼接其文本,不能猜测某段是最终答案。
|
|
124
|
+
|
|
125
|
+
`promptStructured(content, zodSchema, signal)` 只接受 end_turn、完整唯一 JSON(可为完整 JSON fenced block)和通过 Schema 的内容。不提取夹在说明中的任意对象,不自动重试付费请求。需要业务证据、ID 或范围判断时由应用进一步校验。
|
|
126
|
+
|
|
127
|
+
权限默认取消。通过 `handlers` 提供官方 SDK 类型的 permission/fs/terminal/elicitation 回调,并在 `capabilities` 声明实际实现;terminal 必须提供完整回调组。回调运行在可信服务端,应用需按用户、session 和路径授权。Agent 自带本机工具可能不经过回调,worktree 也不是安全沙箱;只读评审需要操作系统/容器权限隔离。
|
|
128
|
+
|
|
129
|
+
图片、音频、嵌入资源和远程 MCP 按初始化能力检查。mode/config 通过原生协议操作。取消、握手/任务超时、输出超限会返回明确错误;连接关闭后不会自动重放请求。`sessions.resume` 仅在 Agent 声明 load/resume 时使用原生 ID 和原状态,不用历史 prompt 伪造恢复。`connect` 提供底层连接、认证和原生会话枚举/删除,用于应用已经管理执行环境的情况。
|
|
130
|
+
|
|
131
|
+
`session.close()` / `ai.close()` 等待拥有的进程树或容器停止后释放占用,保留目录和卷。失联或清理失败不会按 TTL 直接抢占;管理员先检查记录里的容器资源和实际执行环境,确认停止后再恢复占用记录。生产部署需监控此类故障。应用仍需负责 API 鉴权、项目归属、外部 Git 凭据及预览/业务发布。
|
|
132
|
+
|
|
133
|
+
## 配置与错误
|
|
134
|
+
|
|
135
|
+
`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。
|
|
136
|
+
|
|
137
|
+
错误码:070 Git 缺失;071 普通操作失败;072 参数;073 未登记;074 占用;075 不支持;076 取消;077 超时;078 输出无效/超限;079 状态冲突;080 多工程部分失败;081 已关闭;082 运行环境不可用;083 模型不兼容;084 清理失败。统一前缀 `hai:ai:`。错误不包含原始 stderr、凭据或完整命令行。
|
|
138
|
+
|
|
139
|
+
## 事件与恢复边界
|
|
140
|
+
|
|
141
|
+
事件保留原生内容。智能体提供 messageId 时,prompt.text 使用最后一条消息;没有 messageId 时合并本轮收到的文本。HTTP 的 session/new 响应与会话事件是独立通道,早期事件可能稍晚到达;需要严格结果时使用 promptStructured,混入说明或不完整 JSON 会返回失败,不自动截取一个看似合法的对象。
|
|
142
|
+
|
|
143
|
+
DSH 模型需要容量、输入模态、工具能力与推理声明;reasoning.supported 为 true 时需明确 efforts,false 会映射为原生 reasoningEfforts: false。Codex 原生配置会选择 api-key 认证并使用 Hai Responses provider。
|
|
144
|
+
|
|
145
|
+
会话绑定、定义删除和工作区修改通过共享 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
|
|
|
@@ -193,7 +193,7 @@ flush 对本节点后台提取的异常和失败 HaiResult 返回 MEMORY_EXTRACT
|
|
|
193
193
|
|
|
194
194
|
- `ai.file.parse({ content, filename, options })`:text/html/pdf/docx/ocr 解析。
|
|
195
195
|
- `ai.file.parseText(content, filename?)`:直接返回文本。
|
|
196
|
-
- `ai.rerank.rerank(request)` / `rerankTexts(query, texts, topN?)
|
|
196
|
+
- `ai.rerank.rerank(request)` / `rerankTexts(query, texts, topN?)`:相关性重排;模型 `timeout` 覆盖响应头与正文读取,超时返回失败结果。
|
|
197
197
|
- `ai.reasoning.run(query, options?)` / `runStream(query, options?)`:`react`、`cot`、`plan-execute`。
|
|
198
198
|
|
|
199
199
|
## Audio
|