@hcmai/sdk 0.1.0
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/LICENSE +201 -0
- package/README.md +17 -0
- package/dist/index.d.ts +1775 -0
- package/dist/index.js +5208 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1775 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
|
|
3
|
+
interface ModelQueryDsl {
|
|
4
|
+
filter?: Record<string, unknown>;
|
|
5
|
+
sort?: Array<Record<string, unknown>>;
|
|
6
|
+
fields?: string[];
|
|
7
|
+
keyword?: string;
|
|
8
|
+
limit?: number;
|
|
9
|
+
offset?: number;
|
|
10
|
+
}
|
|
11
|
+
interface QueryResult<T = Record<string, unknown>> {
|
|
12
|
+
rows: T[];
|
|
13
|
+
total?: number;
|
|
14
|
+
}
|
|
15
|
+
interface ActionInput {
|
|
16
|
+
id?: string;
|
|
17
|
+
ids?: string[];
|
|
18
|
+
method?: ActionHttpMethod;
|
|
19
|
+
scope?: ActionScope;
|
|
20
|
+
params?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
type ActionHttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
23
|
+
type ActionScope = 'class' | 'object';
|
|
24
|
+
interface ActionResult<R = unknown> {
|
|
25
|
+
result?: R;
|
|
26
|
+
taskId?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface FieldMeta {
|
|
30
|
+
name: string;
|
|
31
|
+
type: string;
|
|
32
|
+
required?: boolean;
|
|
33
|
+
label?: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
enum?: string[];
|
|
36
|
+
relation?: {
|
|
37
|
+
model: string;
|
|
38
|
+
field?: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
interface ActionMeta {
|
|
42
|
+
name: string;
|
|
43
|
+
label?: string;
|
|
44
|
+
description?: string;
|
|
45
|
+
params?: Array<{
|
|
46
|
+
name: string;
|
|
47
|
+
type: string;
|
|
48
|
+
required?: boolean;
|
|
49
|
+
description?: string;
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
52
|
+
interface RelationMeta {
|
|
53
|
+
key: string;
|
|
54
|
+
via: string;
|
|
55
|
+
label?: string;
|
|
56
|
+
}
|
|
57
|
+
interface ModelDescription {
|
|
58
|
+
model: string;
|
|
59
|
+
labels: Record<string, string>;
|
|
60
|
+
fields: FieldMeta[];
|
|
61
|
+
actions: ActionMeta[];
|
|
62
|
+
relations: RelationMeta[];
|
|
63
|
+
}
|
|
64
|
+
interface DescribeOpts {
|
|
65
|
+
verbose?: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface CreateResult {
|
|
69
|
+
id: string;
|
|
70
|
+
[key: string]: unknown;
|
|
71
|
+
}
|
|
72
|
+
/** 创建一条 Model 记录,走后端 Model 层完整校验。
|
|
73
|
+
* 注:返回体形态(尤其 id 字段名)见 spec §12.3,实现期对真后端核实。 */
|
|
74
|
+
declare function create(http: AxiosInstance, model: string, data: Record<string, unknown>): Promise<CreateResult>;
|
|
75
|
+
|
|
76
|
+
declare function update(http: AxiosInstance, model: string, id: string, data: Record<string, unknown>): Promise<CreateResult>;
|
|
77
|
+
|
|
78
|
+
interface RemoveResult {
|
|
79
|
+
id: string;
|
|
80
|
+
status: number;
|
|
81
|
+
data?: unknown;
|
|
82
|
+
}
|
|
83
|
+
declare function remove(http: AxiosInstance, model: string, id: string): Promise<RemoveResult>;
|
|
84
|
+
|
|
85
|
+
interface Fixture {
|
|
86
|
+
model: string;
|
|
87
|
+
key_field: string;
|
|
88
|
+
count: number;
|
|
89
|
+
items: Array<Record<string, unknown>>;
|
|
90
|
+
/** 来源文件路径,仅用于报错定位 */
|
|
91
|
+
sourcePath?: string;
|
|
92
|
+
}
|
|
93
|
+
type RowStatus = 'created' | 'updated' | 'skipped' | 'failed';
|
|
94
|
+
interface RowResult {
|
|
95
|
+
model: string;
|
|
96
|
+
key: string;
|
|
97
|
+
status: RowStatus;
|
|
98
|
+
id?: string;
|
|
99
|
+
error?: string;
|
|
100
|
+
traceId?: string;
|
|
101
|
+
}
|
|
102
|
+
interface ImportResult {
|
|
103
|
+
created: number;
|
|
104
|
+
updated: number;
|
|
105
|
+
skipped: number;
|
|
106
|
+
failed: number;
|
|
107
|
+
rows: RowResult[];
|
|
108
|
+
/** Model:key → uuid(已解析引用快照) */
|
|
109
|
+
refs: Record<string, string>;
|
|
110
|
+
}
|
|
111
|
+
interface ImportOptions {
|
|
112
|
+
dryRun?: boolean;
|
|
113
|
+
updateOnly?: boolean;
|
|
114
|
+
onError?: 'stop' | 'continue';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** snake_case → camelCase(单个 key)。 */
|
|
118
|
+
declare function snakeToCamel(key: string): string;
|
|
119
|
+
/** 转换 record 的**顶层** key:snake_case → camelCase。
|
|
120
|
+
* 嵌套对象/数组的值(jsonb / i18n)原样透传,不转内部键名(对齐 schema §4)。 */
|
|
121
|
+
declare function camelizeKeys(obj: Record<string, unknown>): Record<string, unknown>;
|
|
122
|
+
|
|
123
|
+
declare class RefStore {
|
|
124
|
+
private map;
|
|
125
|
+
private k;
|
|
126
|
+
register(model: string, key: string, id: string): void;
|
|
127
|
+
get(model: string, key: string): string | undefined;
|
|
128
|
+
snapshot(): Record<string, string>;
|
|
129
|
+
}
|
|
130
|
+
interface ParsedPlaceholder {
|
|
131
|
+
model: string;
|
|
132
|
+
key: string;
|
|
133
|
+
}
|
|
134
|
+
/** 若 value 恰好是一个 ${Model:key} 占位符,返回其 model/key;否则 null。 */
|
|
135
|
+
declare function parsePlaceholder(value: unknown): ParsedPlaceholder | null;
|
|
136
|
+
/** 用 store 替换 record 顶层的 ${Model:key} 占位符。
|
|
137
|
+
* 返回 { resolved, unresolved };unresolved 是 "字段名=${Model:key}" 列表,原值保留便于报错。 */
|
|
138
|
+
declare function resolveRefs(record: Record<string, unknown>, store: RefStore): {
|
|
139
|
+
resolved: Record<string, unknown>;
|
|
140
|
+
unresolved: string[];
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** 解析 + 校验单个 data fixture(对齐 spec §10 校验项 1–4)。 */
|
|
144
|
+
declare function parseFixture(yamlText: string, sourcePath?: string): Fixture;
|
|
145
|
+
|
|
146
|
+
/** runner 只依赖 client 的这三个方法,便于单测 stub。 */
|
|
147
|
+
interface ImportClient {
|
|
148
|
+
findIdByKey(model: string, keyField: string, keyValue: string): Promise<string | null>;
|
|
149
|
+
create(model: string, data: Record<string, unknown>): Promise<{
|
|
150
|
+
id: string;
|
|
151
|
+
}>;
|
|
152
|
+
update(model: string, id: string, data: Record<string, unknown>): Promise<unknown>;
|
|
153
|
+
}
|
|
154
|
+
declare function runImport(client: ImportClient, fixtures: Fixture[], opts?: ImportOptions): Promise<ImportResult>;
|
|
155
|
+
|
|
156
|
+
interface HcmClientOpts {
|
|
157
|
+
endpoint: string;
|
|
158
|
+
tenantId: string;
|
|
159
|
+
accessToken?: string;
|
|
160
|
+
scheme?: 'client_credentials' | 'pat';
|
|
161
|
+
profile?: string;
|
|
162
|
+
timeoutMs?: number;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Structural type representing the resolved auth context that
|
|
166
|
+
* `HcmClient.fromAuthContext` consumes. Kept as a structural interface
|
|
167
|
+
* (rather than importing `AuthContext` from `@hcmai/cli`) to avoid a circular
|
|
168
|
+
* package dependency — the CLI's `AuthContext` is assignable to this shape.
|
|
169
|
+
*/
|
|
170
|
+
interface ClientAuthContext {
|
|
171
|
+
env: string;
|
|
172
|
+
endpoint: string;
|
|
173
|
+
tenantId: string;
|
|
174
|
+
identity: string;
|
|
175
|
+
}
|
|
176
|
+
declare class HcmClient {
|
|
177
|
+
readonly profile: string;
|
|
178
|
+
readonly endpoint: string;
|
|
179
|
+
readonly tenantId: string;
|
|
180
|
+
private http;
|
|
181
|
+
private tokenStore;
|
|
182
|
+
private explicitToken?;
|
|
183
|
+
constructor(opts: HcmClientOpts);
|
|
184
|
+
/**
|
|
185
|
+
* Build a client from a resolved env + identity context (T15).
|
|
186
|
+
*
|
|
187
|
+
* Uses `TokenStore.forIdentity(ctx.identity)` so the token is read from
|
|
188
|
+
* the identity-scoped store (written by `hcm login --env <env> --name <id>`),
|
|
189
|
+
* not the legacy profile-scoped credentials.json.
|
|
190
|
+
*
|
|
191
|
+
* The `profile` field is kept = `ctx.env` for downstream callers that still
|
|
192
|
+
* read `client.profile` (e.g. workspace context dump). Phase 2 will rename
|
|
193
|
+
* the field to `env`.
|
|
194
|
+
*
|
|
195
|
+
* Refresh path: fromAuthContext binds `this.identity` so handleRefresh
|
|
196
|
+
* routes refresh through TokenStore.forIdentity — works for
|
|
197
|
+
* client_credentials sessions (1h TTL) post-T8 migration.
|
|
198
|
+
*/
|
|
199
|
+
/**
|
|
200
|
+
* Identity name for refresh path (set by fromAuthContext). When set,
|
|
201
|
+
* handleRefresh routes through TokenStore.forIdentity rather than the
|
|
202
|
+
* profile-scoped legacy store — critical for client_credentials sessions
|
|
203
|
+
* (~1h token TTL) on post-T8-migration installs.
|
|
204
|
+
*/
|
|
205
|
+
private identity?;
|
|
206
|
+
static fromAuthContext(ctx: ClientAuthContext): HcmClient;
|
|
207
|
+
/**
|
|
208
|
+
* @deprecated Use `HcmClient.fromAuthContext(ctx)` together with the CLI
|
|
209
|
+
* helper `getActiveAuthContextFromCli(args)`. WILL THROW on post-T8
|
|
210
|
+
* installs where `~/.config/hcm/<profile>/profile.yml` was migrated to
|
|
211
|
+
* `envs/<name>/env.yml` (loadProfile reads the legacy path only).
|
|
212
|
+
* Kept only for dead-code paths (mini.ts) and pre-migration users.
|
|
213
|
+
*/
|
|
214
|
+
static fromConfig(args: {
|
|
215
|
+
profile?: string;
|
|
216
|
+
}): Promise<HcmClient>;
|
|
217
|
+
query<T = Record<string, unknown>>(model: string, dsl: ModelQueryDsl): Promise<QueryResult<T>>;
|
|
218
|
+
action<R = unknown>(model: string, name: string, input: ActionInput): Promise<ActionResult<R>>;
|
|
219
|
+
describe(model: string, opts?: DescribeOpts): Promise<ModelDescription>;
|
|
220
|
+
create(model: string, data: Record<string, unknown>): Promise<CreateResult>;
|
|
221
|
+
update(model: string, id: string, data: Record<string, unknown>): Promise<CreateResult>;
|
|
222
|
+
remove(model: string, id: string): Promise<RemoveResult>;
|
|
223
|
+
/** 按业务键查已存在记录的 id(幂等用);命中返回 id,否则 null。 */
|
|
224
|
+
findIdByKey(model: string, keyField: string, keyValue: string): Promise<string | null>;
|
|
225
|
+
importPackage(fixtures: Fixture[], opts?: ImportOptions): Promise<ImportResult>;
|
|
226
|
+
raw(): AxiosInstance;
|
|
227
|
+
close(): Promise<void>;
|
|
228
|
+
getAccessToken(): Promise<string | null>;
|
|
229
|
+
handleRefresh(): Promise<void>;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
declare const SDK_VERSION: string;
|
|
233
|
+
|
|
234
|
+
declare enum ExitCode {
|
|
235
|
+
SUCCESS = 0,
|
|
236
|
+
USAGE_ERROR = 1,
|
|
237
|
+
AUTH_ERROR = 2,
|
|
238
|
+
BUSINESS_ERROR = 3,
|
|
239
|
+
NETWORK_ERROR = 4,
|
|
240
|
+
CONFIG_ERROR = 5,
|
|
241
|
+
SIGINT = 130
|
|
242
|
+
}
|
|
243
|
+
declare enum CliErrorCode {
|
|
244
|
+
INVALID_ARGUMENT = "INVALID_ARGUMENT",
|
|
245
|
+
INVALID_JSON = "INVALID_JSON",
|
|
246
|
+
MISSING_FLAG = "MISSING_FLAG",
|
|
247
|
+
AUTH_FAILED = "AUTH_FAILED",
|
|
248
|
+
AUTH_EXPIRED = "AUTH_EXPIRED",
|
|
249
|
+
AUTH_REFRESH_FAILED = "AUTH_REFRESH_FAILED",
|
|
250
|
+
WS_AUTH_ERROR = "WS_AUTH_ERROR",
|
|
251
|
+
SCOPE_FORBIDDEN = "SCOPE_FORBIDDEN",
|
|
252
|
+
BUSINESS_RULE_VIOLATION = "BUSINESS_RULE_VIOLATION",
|
|
253
|
+
MODEL_NOT_FOUND = "MODEL_NOT_FOUND",
|
|
254
|
+
ACTION_NOT_FOUND = "ACTION_NOT_FOUND",
|
|
255
|
+
AGENT_NOT_FOUND = "AGENT_NOT_FOUND",
|
|
256
|
+
TOOL_TIMEOUT = "TOOL_TIMEOUT",
|
|
257
|
+
NETWORK_ERROR = "NETWORK_ERROR",
|
|
258
|
+
TIMEOUT = "TIMEOUT",
|
|
259
|
+
SERVER_ERROR = "SERVER_ERROR",
|
|
260
|
+
PROTOCOL_UNSUPPORTED = "PROTOCOL_UNSUPPORTED",
|
|
261
|
+
PROFILE_NOT_FOUND = "PROFILE_NOT_FOUND",
|
|
262
|
+
CONFIG_PARSE_ERROR = "CONFIG_PARSE_ERROR",
|
|
263
|
+
KEYCHAIN_UNAVAILABLE = "KEYCHAIN_UNAVAILABLE",
|
|
264
|
+
ENV_NOT_FOUND = "ENV_NOT_FOUND",
|
|
265
|
+
ENV_ALREADY_EXISTS = "ENV_ALREADY_EXISTS",
|
|
266
|
+
ENV_ENDPOINT_REQUIRED = "ENV_ENDPOINT_REQUIRED",
|
|
267
|
+
IDENTITY_NOT_FOUND = "IDENTITY_NOT_FOUND",
|
|
268
|
+
IDENTITY_ALREADY_EXISTS = "IDENTITY_ALREADY_EXISTS",
|
|
269
|
+
IDENTITY_NOT_IN_ENV = "IDENTITY_NOT_IN_ENV",
|
|
270
|
+
TOKEN_EXPIRED = "TOKEN_EXPIRED",
|
|
271
|
+
NO_ACTIVE_ENV = "NO_ACTIVE_ENV"
|
|
272
|
+
}
|
|
273
|
+
declare function exitCodeFor(code: CliErrorCode): ExitCode;
|
|
274
|
+
|
|
275
|
+
interface CliErrorInit {
|
|
276
|
+
code: CliErrorCode;
|
|
277
|
+
message: string;
|
|
278
|
+
context?: Record<string, unknown>;
|
|
279
|
+
traceId?: string;
|
|
280
|
+
profile?: string;
|
|
281
|
+
cause?: unknown;
|
|
282
|
+
}
|
|
283
|
+
declare class CliError extends Error {
|
|
284
|
+
readonly code: CliErrorCode;
|
|
285
|
+
readonly exitCode: ExitCode;
|
|
286
|
+
readonly context?: Record<string, unknown>;
|
|
287
|
+
readonly traceId?: string;
|
|
288
|
+
readonly profile?: string;
|
|
289
|
+
readonly cause?: unknown;
|
|
290
|
+
constructor(init: CliErrorInit);
|
|
291
|
+
toFormattedString(): string;
|
|
292
|
+
toJSON(): {
|
|
293
|
+
code: CliErrorCode;
|
|
294
|
+
message: string;
|
|
295
|
+
context: Record<string, unknown> | undefined;
|
|
296
|
+
traceId: string | undefined;
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
declare function fromAxiosError(err: any, profile?: string): CliError;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 会话附件元数据——与后端 `ConversationWebSocketHandler.parseAttachments` 形态一致
|
|
303
|
+
* (message.create payload.attachments 元素;#1849 后随 USER_MESSAGE 入 journal 单源)。
|
|
304
|
+
*/
|
|
305
|
+
interface AttachmentMeta {
|
|
306
|
+
documentId: string;
|
|
307
|
+
fileName: string;
|
|
308
|
+
mimeType: string;
|
|
309
|
+
fileSize: number;
|
|
310
|
+
}
|
|
311
|
+
declare function guessMimeType(filePath: string): string;
|
|
312
|
+
/**
|
|
313
|
+
* 上传本地文件到 HCM 文档存储(web ChatInput 同端点同 storageCategory),
|
|
314
|
+
* 返回 message.create 可直接携带的附件元数据。类型限制跟随服务端校验。
|
|
315
|
+
*/
|
|
316
|
+
declare function uploadDocument(http: AxiosInstance, filePath: string): Promise<AttachmentMeta>;
|
|
317
|
+
/** 默认产物落盘目录(对标 Claude Code 工具产物本地直接访问)。 */
|
|
318
|
+
declare function defaultDownloadDir(): string;
|
|
319
|
+
/**
|
|
320
|
+
* 把后端相对下载链接(`/api/documents/download/{id}?...sig=`)拼成可点的绝对 URL,
|
|
321
|
+
* 供终端渲染——签名 URL 自洽免 Bearer,点开即由浏览器打开(ADR-140)。
|
|
322
|
+
* apiBase 为 CLI 连接的后端地址(`HcmClient.endpoint`);无 apiBase 或已是绝对 URL 时原样返回。
|
|
323
|
+
*/
|
|
324
|
+
declare function absoluteDownloadUrl(apiBase: string | undefined, url: string): string;
|
|
325
|
+
/**
|
|
326
|
+
* 认证下载文档到本地(`/api/documents/download` 需 Bearer,浏览器裸 GET 恒 401 的同款教训)。
|
|
327
|
+
* destPath 为目录或省略时落 defaultDownloadDir,重名自动加序号。返回写入的绝对路径。
|
|
328
|
+
*/
|
|
329
|
+
declare function downloadDocument(http: AxiosInstance, ref: {
|
|
330
|
+
documentId?: string;
|
|
331
|
+
downloadUrl?: string;
|
|
332
|
+
fileName?: string;
|
|
333
|
+
}, destPath?: string): Promise<string>;
|
|
334
|
+
|
|
335
|
+
declare function toJson(value: unknown): string;
|
|
336
|
+
|
|
337
|
+
declare function toYaml(value: unknown): string;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* 盒式文本表格。列宽**按内容自适应**、以 {@link MAX_COL_WIDTH} 为上限——短列(SEQ/ROUND
|
|
341
|
+
* 等)不再被强制撑到 40 列(旧 `colWidths.map(()=>40)` 的 BUG:7 列表 ≈ 290 列宽、糊成一片)。
|
|
342
|
+
* 宽度与截断均按**终端显示宽度**(CJK=2 列)计,避免中文单元格溢出错位。
|
|
343
|
+
*/
|
|
344
|
+
declare function toTable(rows: Record<string, unknown>[]): string;
|
|
345
|
+
|
|
346
|
+
type OutputFormat = 'table' | 'json' | 'yaml';
|
|
347
|
+
interface FormatOptions {
|
|
348
|
+
format: OutputFormat;
|
|
349
|
+
}
|
|
350
|
+
declare function formatRows(rows: Record<string, unknown>[], opts: FormatOptions): string;
|
|
351
|
+
declare function formatObject(obj: unknown, opts: FormatOptions): string;
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* 凭据的来路。
|
|
355
|
+
*
|
|
356
|
+
* 🔴 **来路 ≠ 语义**:`password` 与 `pairing` 是两条完全不同的取得方式,但换来的是
|
|
357
|
+
* **同一种东西**——`TokenManager.createToken` 铸的服务端会话 token(滑动续期、
|
|
358
|
+
* 可 `/api/auth/refresh`、可 `/api/auth/logout` 单杀)。而 `pat` 与 `client_credentials`
|
|
359
|
+
* 不是。判「能不能续期 / 该不该请服务端吊销」必须走 {@link isServerSlidingSession},
|
|
360
|
+
* 不要拿 `=== 'password'` 去比——见该函数的说明。
|
|
361
|
+
*/
|
|
362
|
+
type AuthScheme = 'client_credentials' | 'pat' | 'password' | 'pairing';
|
|
363
|
+
/**
|
|
364
|
+
* 这条凭据是不是「服务端会话 token」——即后端在每个请求上滑动续期、且
|
|
365
|
+
* `/api/auth/logout` 能单条吊销的那一类。
|
|
366
|
+
*
|
|
367
|
+
* 🔴 **存在的理由是防一类静默退化**:全仓有三处要问这个问题——`refreshToken` 的
|
|
368
|
+
* scheme 分支、`ensureSessionFresh` 的早退、`logout` 的 `revokeOnServer`。散着写成三个
|
|
369
|
+
* 字面量比较,下次加第五轨时必然漏掉其中一处,而漏掉的后果**没有任何报错**:
|
|
370
|
+
* - 漏在 refresh 分支 → 落到 default 抛「unknown auth scheme」,会话再也续不上
|
|
371
|
+
* - 漏在 ensureSessionFresh → 不再主动续期,退回「每天重登」
|
|
372
|
+
* - 漏在 revokeOnServer → **`logout` 静默跳过服务端吊销**,而 ADR-308 把
|
|
373
|
+
* 「用完跑 hcm logout」写成了顾问在客户主机上那一行**唯一可操作的缓解**
|
|
374
|
+
*
|
|
375
|
+
* 所以这里收敛成一个谓词,三处都调它。
|
|
376
|
+
*/
|
|
377
|
+
declare function isServerSlidingSession(scheme: AuthScheme): boolean;
|
|
378
|
+
interface TokenRecord {
|
|
379
|
+
scheme: AuthScheme;
|
|
380
|
+
accessTokenRef: string;
|
|
381
|
+
refreshTokenRef?: string;
|
|
382
|
+
expiresAt: string;
|
|
383
|
+
scopes: string[];
|
|
384
|
+
/**
|
|
385
|
+
* 签发时服务端告知的 TTL(秒)。**续期后本地 expiresAt 只能靠它重算**:后端
|
|
386
|
+
* `POST /api/auth/refresh` 的响应体是 `{"message":"token刷新成功"}` —— 不回新的
|
|
387
|
+
* 过期时间,而它重设的 Redis TTL 用的正是签发时那个全局配置值
|
|
388
|
+
* (`SecurityProperties.token.expiration`,缺省 24h)。不存这个数,续期成功之后就只能
|
|
389
|
+
* 猜一个 TTL 写回本地,猜小了照样早退、猜大了会放过一个已死的 token。
|
|
390
|
+
*
|
|
391
|
+
* 🔴 缺省(旧记录升级上来)时按 DEFAULT_SESSION_TTL_SECONDS 兜底,见 refresh.ts。
|
|
392
|
+
*/
|
|
393
|
+
ttlSeconds?: number;
|
|
394
|
+
}
|
|
395
|
+
declare class TokenStore {
|
|
396
|
+
private profile;
|
|
397
|
+
constructor(profile: string);
|
|
398
|
+
/**
|
|
399
|
+
* Factory: build an identity-scoped TokenStore. Stores meta at
|
|
400
|
+
* `identities/<name>/credentials.json` and uses keychain account prefix
|
|
401
|
+
* `identity-<name>` to isolate from legacy profile-scoped stores.
|
|
402
|
+
*
|
|
403
|
+
* Used by T13 login when writing identity meta and by T15 business
|
|
404
|
+
* commands when reading the active identity's token.
|
|
405
|
+
*/
|
|
406
|
+
static forIdentity(name: string): TokenStore;
|
|
407
|
+
private get isIdentity();
|
|
408
|
+
private metaFile;
|
|
409
|
+
private dataDir;
|
|
410
|
+
private keychainAccountPrefix;
|
|
411
|
+
writeMeta(record: TokenRecord): Promise<void>;
|
|
412
|
+
readMeta(): Promise<TokenRecord | null>;
|
|
413
|
+
setAccessToken(token: string): Promise<string>;
|
|
414
|
+
setRefreshToken(token: string): Promise<string>;
|
|
415
|
+
getAccessToken(): Promise<string | null>;
|
|
416
|
+
getRefreshToken(): Promise<string | null>;
|
|
417
|
+
clear(): Promise<void>;
|
|
418
|
+
private resolveRef;
|
|
419
|
+
private canUseKeychain;
|
|
420
|
+
private writeTokenFile;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* **服务端认定的 token 归属**——与"调用方以为的归属"是两回事,两者不一致时
|
|
425
|
+
* 一切看起来都正常,而你在对着另一个租户干活。
|
|
426
|
+
*
|
|
427
|
+
* 这正是 fde-kit 那边 Ruling 30 防的同一件事:`hcm login --tenant 1` 传进去的是
|
|
428
|
+
* 「我要的租户」,服务端回的 `user.tenantId` 才是「token 实际归属的租户」。此前
|
|
429
|
+
* login 成功那行只回显前者,于是登错租户是静默的。
|
|
430
|
+
*/
|
|
431
|
+
interface Principal {
|
|
432
|
+
/** 服务端认定的租户。ADR-177 之后新租户是 UUID,别按 int 处理。 */
|
|
433
|
+
tenantId?: string;
|
|
434
|
+
username?: string;
|
|
435
|
+
roles?: string[];
|
|
436
|
+
}
|
|
437
|
+
/** 登录helper 的统一返回:凭据元数据 + 服务端认定的归属(拿不到时 undefined)。 */
|
|
438
|
+
interface LoginOutcome {
|
|
439
|
+
record: TokenRecord;
|
|
440
|
+
/**
|
|
441
|
+
* 🔴 `undefined` = **服务端没在响应里给身份**,与「给了但字段为空」是两个状态。
|
|
442
|
+
* 前者的下一步是问服务端为什么不给,后者的下一步是看那个账号本身。合并之后
|
|
443
|
+
* 回显只能给出一句放之四海而皆准的废话。
|
|
444
|
+
*/
|
|
445
|
+
principal?: Principal;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* 从服务端响应里的 user/userInfo 对象抽出 Principal。
|
|
449
|
+
*
|
|
450
|
+
* 两个来源字段名一致但形状不同,所以这里只认键、不认容器:
|
|
451
|
+
* - `POST /api/auth/login` → `LoginResponse.user`(Map,含 id/username/tenantId/roles)
|
|
452
|
+
* - `GET /api/auth/info` → `UserInfoResponse`(含 userId/username/tenantId/roles)
|
|
453
|
+
*
|
|
454
|
+
* 全部字段都当可选处理:任何一个缺失都不该让登录失败——登录已经成功了,
|
|
455
|
+
* 这里只是回显。
|
|
456
|
+
*/
|
|
457
|
+
declare function toPrincipal(raw: unknown): Principal | undefined;
|
|
458
|
+
/** 回显用的一行摘要。`principal` 为空时明说「服务端未回身份」,不静默略过。 */
|
|
459
|
+
declare function describePrincipal(principal: Principal | undefined): string;
|
|
460
|
+
|
|
461
|
+
interface ClientCredentialsInput {
|
|
462
|
+
endpoint: string;
|
|
463
|
+
tenantId: string;
|
|
464
|
+
clientId: string;
|
|
465
|
+
clientSecret: string;
|
|
466
|
+
profile: string;
|
|
467
|
+
httpClient: AxiosInstance;
|
|
468
|
+
}
|
|
469
|
+
declare function loginClientCredentials(input: ClientCredentialsInput): Promise<LoginOutcome>;
|
|
470
|
+
|
|
471
|
+
interface PatLoginInput {
|
|
472
|
+
endpoint: string;
|
|
473
|
+
tenantId: string;
|
|
474
|
+
pat: string;
|
|
475
|
+
profile: string;
|
|
476
|
+
httpClient: AxiosInstance;
|
|
477
|
+
}
|
|
478
|
+
declare function loginPat(input: PatLoginInput): Promise<LoginOutcome>;
|
|
479
|
+
|
|
480
|
+
interface PasswordLoginInput {
|
|
481
|
+
endpoint: string;
|
|
482
|
+
tenantId: string;
|
|
483
|
+
username: string;
|
|
484
|
+
password: string;
|
|
485
|
+
profile: string;
|
|
486
|
+
httpClient: AxiosInstance;
|
|
487
|
+
}
|
|
488
|
+
/** 后端 `SecurityProperties.token.expiration` 的缺省值(24h),仅在服务端没回 expiresIn 时兜底。 */
|
|
489
|
+
declare const DEFAULT_SESSION_TTL_SECONDS: number;
|
|
490
|
+
declare function loginPassword(input: PasswordLoginInput): Promise<LoginOutcome>;
|
|
491
|
+
|
|
492
|
+
interface PairingLoginInput {
|
|
493
|
+
endpoint: string;
|
|
494
|
+
/** 页面上复制来的配对码。大小写与连字符由服务端归一化,这里原样传。 */
|
|
495
|
+
code: string;
|
|
496
|
+
profile: string;
|
|
497
|
+
httpClient: AxiosInstance;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* 配对码登录(ADR-308 第四轨)。
|
|
501
|
+
*
|
|
502
|
+
* 解的是「没有任何一方知道后端地址与 tenantId」:CLI 只有磁盘上人手填的值,用户知道的
|
|
503
|
+
* 是浏览器地址栏那一串,后端全树无 frontend URL 配置。唯一同时知道这两件事的是那个
|
|
504
|
+
* 已登录的页面——所以地址由命令行位置参数带进来(人从页面复制整行),租户由服务端
|
|
505
|
+
* 在响应里给出,**不再有 `'1'` 这个猜测**。
|
|
506
|
+
*
|
|
507
|
+
* 🔴 `scheme` 记 `'pairing'` 而不是 `'password'`:如实记录来路(审计与回显需要)。
|
|
508
|
+
* 但它换来的是 `TokenManager.createToken` 铸的**普通会话 token**,续期/吊销语义与口令
|
|
509
|
+
* 登录完全一致——这一点由 `isServerSlidingSession` 谓词保证,别在别处写
|
|
510
|
+
* `=== 'password'` 的比较。
|
|
511
|
+
*/
|
|
512
|
+
declare function loginPairing(input: PairingLoginInput): Promise<LoginOutcome>;
|
|
513
|
+
/**
|
|
514
|
+
* 从用户粘贴的地址里取出 origin。
|
|
515
|
+
*
|
|
516
|
+
* 人复制的是**浏览器地址栏那一串**,带路径、带查询参数都正常——那正是他唯一确定知道的
|
|
517
|
+
* 东西。要求他先手工削成 origin,等于把「知道后端地址」这个门槛又搬了回来。
|
|
518
|
+
*/
|
|
519
|
+
declare function toOrigin(raw: string): string;
|
|
520
|
+
/**
|
|
521
|
+
* 由 origin 推一个 env 名:`https://hcm.custa.com` → `custa`。
|
|
522
|
+
*
|
|
523
|
+
* 取的是主域左边那一段而不是整个 hostname:`hcm.custa.com` 与 `hcm-test.custa.com`
|
|
524
|
+
* 会推出不同的名字,而 `hcm.custa.com` 与 `www.custa.com` 也不会撞。
|
|
525
|
+
* 推不出来时落到 `default`,由调用方处理重名。
|
|
526
|
+
*/
|
|
527
|
+
declare function inferEnvName(origin: string): string;
|
|
528
|
+
|
|
529
|
+
interface RefreshInput {
|
|
530
|
+
endpoint: string;
|
|
531
|
+
/**
|
|
532
|
+
* Identity name (preferred). When set, uses TokenStore.forIdentity(identity).
|
|
533
|
+
* Required for sessions established via HcmClient.fromAuthContext.
|
|
534
|
+
*/
|
|
535
|
+
identity?: string;
|
|
536
|
+
/**
|
|
537
|
+
* @deprecated Profile name (legacy). Used when identity is not provided —
|
|
538
|
+
* for backward compat with fromConfig callers and tests pre-T15.
|
|
539
|
+
*/
|
|
540
|
+
profile?: string;
|
|
541
|
+
httpClient: AxiosInstance;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* 本地在「还剩这么多」时就主动去续期。
|
|
545
|
+
*
|
|
546
|
+
* 🔴 这个值**不需要、也不应该**去对齐服务端的 `SecurityProperties.token.refreshThreshold`
|
|
547
|
+
* (缺省 2h):那是服务端**自动**续期的触发线,而我们打的 `/api/auth/refresh` 是
|
|
548
|
+
* **无条件**续期(只要 Redis key 还在就重设 TTL),跟阈值无关。抄一份服务端常量到这里
|
|
549
|
+
* 只会多一个会漂的载体。
|
|
550
|
+
*/
|
|
551
|
+
declare const PROACTIVE_REFRESH_MARGIN_SECONDS: number;
|
|
552
|
+
/**
|
|
553
|
+
* 续期。**三种 scheme 是三套完全不同的服务端语义,不是一个端点能覆盖的。**
|
|
554
|
+
*
|
|
555
|
+
* 🔴 此前这里写成了一条,于是三条全错,而且错得不会被发现——口令会话在
|
|
556
|
+
* 「取 refresh token」那一步就抛了,请求根本没发出去,后面那段对不上的协议
|
|
557
|
+
* 因此永远跑不到,测试也只覆盖了这些提前抛出的错误路径。三条的真实形态:
|
|
558
|
+
*
|
|
559
|
+
* - **password**:`POST /api/auth/refresh`,认 **Authorization 头**,服务端把
|
|
560
|
+
* 同一个 token 的 Redis TTL 重设回 expiration,响应体只有
|
|
561
|
+
* `{"message":"token刷新成功"}`——**没有 access_token**。旧实现往 body 塞
|
|
562
|
+
* `{refresh_token}` 再读 `resp.data.access_token`,两头都对不上;而口令登录
|
|
563
|
+
* 压根拿不到 refresh token,连这个请求都发不出去。
|
|
564
|
+
* - **client_credentials**:走 `POST /api/oauth/token` + `grant_type=refresh_token`,
|
|
565
|
+
* 且后端在进 switch 之前就强制校验 `client_id` + `client_secret`。本 CLI
|
|
566
|
+
* **不保存 client_secret**(有意为之:那是长期凭据) ⇒ 这条路在当前设计下
|
|
567
|
+
* 不可能执行成功。所以这里**明说要重登**,而不是发一个注定 400 的请求
|
|
568
|
+
* 再把它包装成一句看不懂的错误。
|
|
569
|
+
* - **pat**:本来就不可续期,由后端管失效。
|
|
570
|
+
*/
|
|
571
|
+
declare function refreshToken(input: RefreshInput): Promise<TokenRecord>;
|
|
572
|
+
declare function needsRefresh(meta: TokenRecord, marginSec?: number): boolean;
|
|
573
|
+
/**
|
|
574
|
+
* 命令开跑前把会话续上——**这才是"登录一次基本不用再输密码"落地的那一处**。
|
|
575
|
+
*
|
|
576
|
+
* 背景:后端 `TokenAuthenticationFilter` 在每个带 token 的请求上都会滑动续期
|
|
577
|
+
* (剩余 <= refreshThreshold 时把 Redis TTL 重设回 24h),也就是说**只要你在用,
|
|
578
|
+
* 服务端的会话就不会死**。而 CLI 这边的 `expiresAt` 是登录那一刻算死的静态值、
|
|
579
|
+
* 之后再没更新过,`ensureActiveAuth` 拿它一比就抛 TOKEN_EXPIRED —— 结果是
|
|
580
|
+
* **CLI 在拒绝服务端明明还认的 token**。
|
|
581
|
+
*
|
|
582
|
+
* 这里做的事:本地剩余时间进入窗口时主动打一次无条件续期端点,并把服务端那次
|
|
583
|
+
* 续期的结果**写回本地**,让本地的过期判断重新变成真的。
|
|
584
|
+
*
|
|
585
|
+
* 返回是否真的续了。**任何失败都不抛**:续期是尽力而为,失败时后面的请求自己会
|
|
586
|
+
* 401 并给出该给的引导;为了一次续期失败就让整条命令挂掉是本末倒置。
|
|
587
|
+
*/
|
|
588
|
+
declare function ensureSessionFresh(input: RefreshInput): Promise<boolean>;
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* 首登强制改密(fresh tenant 的必经一步)。
|
|
592
|
+
*
|
|
593
|
+
* <p>后端契约(AuthController):
|
|
594
|
+
* <ul>
|
|
595
|
+
* <li>用初始口令 `POST /api/auth/login` → **403**,body 是
|
|
596
|
+
* `{error:{code:"AUTH_PASSWORD_CHANGE_REQUIRED", details:{changePasswordToken, expiresInSeconds}}}`
|
|
597
|
+
* —— 那个临时 token **只活 300 秒**;</li>
|
|
598
|
+
* <li>拿它作 Bearer 调 `POST /api/auth/change-password`,body `{oldPassword,newPassword,confirmPassword}`;</li>
|
|
599
|
+
* <li>改完用新口令重新 login 才拿到正式 token。</li>
|
|
600
|
+
* </ul>
|
|
601
|
+
*
|
|
602
|
+
* <p>🔴 这是 `hcm tenant bootstrap` 之后的**硬阻塞**:不走这一步,新建的租户登不进去。
|
|
603
|
+
* 所以 `hcm login` 自动识别并接管,而不是让顾问自己去拼两条 curl。
|
|
604
|
+
*/
|
|
605
|
+
declare const PASSWORD_CHANGE_REQUIRED = "AUTH_PASSWORD_CHANGE_REQUIRED";
|
|
606
|
+
interface PasswordChangeChallenge {
|
|
607
|
+
changePasswordToken: string;
|
|
608
|
+
expiresInSeconds?: number;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* 从一个失败的 login 响应里识别「强制改密」挑战。
|
|
612
|
+
*
|
|
613
|
+
* 不是该挑战(网络错、口令错、账号锁定…)一律返回 null,由调用方按原错误处理——
|
|
614
|
+
* 判据只认后端的错误码,不靠 HTTP 状态猜(423 账号锁定同样是 4xx,不能混)。
|
|
615
|
+
*/
|
|
616
|
+
declare function detectPasswordChangeChallenge(err: any): PasswordChangeChallenge | null;
|
|
617
|
+
interface ChangePasswordInput {
|
|
618
|
+
endpoint: string;
|
|
619
|
+
/** 登录挑战里拿到的临时 token(300 秒有效) */
|
|
620
|
+
changePasswordToken: string;
|
|
621
|
+
oldPassword: string;
|
|
622
|
+
newPassword: string;
|
|
623
|
+
httpClient: AxiosInstance;
|
|
624
|
+
}
|
|
625
|
+
declare function changePassword(input: ChangePasswordInput): Promise<void>;
|
|
626
|
+
|
|
627
|
+
declare function hcmConfigDir(): string;
|
|
628
|
+
declare function profileDir(profile: string): string;
|
|
629
|
+
declare function profileFile(profile: string): string;
|
|
630
|
+
declare function credentialsFile(profile: string): string;
|
|
631
|
+
declare function globalConfigFile(): string;
|
|
632
|
+
declare function conversationStateFile(profile: string): string;
|
|
633
|
+
declare function replHistoryFile(profile: string): string;
|
|
634
|
+
declare function envsDir(): string;
|
|
635
|
+
declare function envDir(name: string): string;
|
|
636
|
+
declare function envFile(name: string): string;
|
|
637
|
+
declare function identitiesDir(): string;
|
|
638
|
+
declare function identityDir(name: string): string;
|
|
639
|
+
declare function identityMetaFile(name: string): string;
|
|
640
|
+
declare function archiveDir(): string;
|
|
641
|
+
|
|
642
|
+
interface ProfileConfig {
|
|
643
|
+
endpoint: string;
|
|
644
|
+
tenantId: string;
|
|
645
|
+
defaultAgent?: string;
|
|
646
|
+
displayName?: string;
|
|
647
|
+
}
|
|
648
|
+
declare function loadProfile(profile: string): Promise<ProfileConfig>;
|
|
649
|
+
declare function saveProfile(profile: string, cfg: ProfileConfig): Promise<void>;
|
|
650
|
+
declare function listProfiles(): Promise<string[]>;
|
|
651
|
+
|
|
652
|
+
interface GlobalConfig {
|
|
653
|
+
/** Canonical: name of the active environment. */
|
|
654
|
+
activeEnv: string;
|
|
655
|
+
/**
|
|
656
|
+
* @deprecated Use `activeEnv`. Kept in sync with `activeEnv` for backward compat
|
|
657
|
+
* while in-tree callers migrate. Will be removed at end of Phase 2.
|
|
658
|
+
*/
|
|
659
|
+
activeProfile?: string;
|
|
660
|
+
defaults?: {
|
|
661
|
+
output?: 'table' | 'json' | 'yaml';
|
|
662
|
+
timeout?: string;
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
declare function loadGlobalConfig(): Promise<GlobalConfig>;
|
|
666
|
+
declare function saveGlobalConfig(cfg: GlobalConfig): Promise<void>;
|
|
667
|
+
/**
|
|
668
|
+
* Resolve active env name. Priority: --env flag > HCM_ENV > HCM_PROFILE (legacy) > global.activeEnv
|
|
669
|
+
*/
|
|
670
|
+
declare function resolveActiveEnv(args: {
|
|
671
|
+
envFlag?: string;
|
|
672
|
+
}, env: NodeJS.ProcessEnv, global: GlobalConfig): string;
|
|
673
|
+
/**
|
|
674
|
+
* @deprecated Use `resolveActiveEnv`. Keeps old signature `(args: {flag})` for backward compat.
|
|
675
|
+
* Will be removed at end of Phase 2.
|
|
676
|
+
*/
|
|
677
|
+
declare function resolveActiveProfile(args: {
|
|
678
|
+
flag?: string;
|
|
679
|
+
}, env: NodeJS.ProcessEnv, global: GlobalConfig): string;
|
|
680
|
+
|
|
681
|
+
interface EnvConfig {
|
|
682
|
+
endpoint: string;
|
|
683
|
+
tenantId?: string;
|
|
684
|
+
defaultIdentity?: string;
|
|
685
|
+
defaultAgent?: string;
|
|
686
|
+
}
|
|
687
|
+
declare function saveEnv(name: string, cfg: EnvConfig): Promise<void>;
|
|
688
|
+
declare function loadEnv(name: string): Promise<EnvConfig>;
|
|
689
|
+
declare function listEnvs(): Promise<string[]>;
|
|
690
|
+
declare function deleteEnv(name: string): Promise<void>;
|
|
691
|
+
|
|
692
|
+
interface IdentityMeta {
|
|
693
|
+
env: string;
|
|
694
|
+
scheme: AuthScheme;
|
|
695
|
+
scopes: string[];
|
|
696
|
+
expiresAt?: string;
|
|
697
|
+
username?: string;
|
|
698
|
+
/**
|
|
699
|
+
* keychain ref same as TokenRecord.accessTokenRef. Populated by TokenStore.forIdentity.
|
|
700
|
+
* Optional here because saveIdentity may be called before token is written.
|
|
701
|
+
*/
|
|
702
|
+
accessTokenRef?: string;
|
|
703
|
+
refreshTokenRef?: string;
|
|
704
|
+
/**
|
|
705
|
+
* **服务端认定的租户**(登录响应里的 `user.tenantId` / `/api/auth/info` 的 tenantId),
|
|
706
|
+
* 不是命令行上传进去的那个。两者不一致 = 你在对着另一个租户干活,而一切看起来都正常。
|
|
707
|
+
*
|
|
708
|
+
* 命令执行时优先用它,`EnvConfig.tenantId` 只是登录前的入参与兜底 —— 后者缺省是
|
|
709
|
+
* 写死的 `'1'`,而 ADR-177 之后新租户是 UUID,那个缺省几乎必然是错的。
|
|
710
|
+
*/
|
|
711
|
+
tenantId?: string;
|
|
712
|
+
/**
|
|
713
|
+
* 见 `TokenRecord.ttlSeconds`。
|
|
714
|
+
*
|
|
715
|
+
* 🔴 必须在这里也留一份:`identities/<name>/credentials.json` 这**一个文件**有两个
|
|
716
|
+
* 写入方——`TokenStore.forIdentity().writeMeta()` 写 TokenRecord 形状,`saveIdentity()`
|
|
717
|
+
* 写 IdentityMeta 形状,后者最后写、且只写自己声明的字段。不在这里声明,
|
|
718
|
+
* 登录流程末尾这一次 saveIdentity 就会把 ttlSeconds 抹掉,续期时只能去猜 TTL。
|
|
719
|
+
*/
|
|
720
|
+
ttlSeconds?: number;
|
|
721
|
+
}
|
|
722
|
+
declare function saveIdentity(name: string, meta: IdentityMeta): Promise<void>;
|
|
723
|
+
declare function loadIdentity(name: string): Promise<IdentityMeta>;
|
|
724
|
+
declare function listIdentities(env?: string): Promise<string[]>;
|
|
725
|
+
declare function deleteIdentity(name: string): Promise<void>;
|
|
726
|
+
|
|
727
|
+
interface MigrationSummary {
|
|
728
|
+
migrated: string[];
|
|
729
|
+
failed: string[];
|
|
730
|
+
skipped: string[];
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* One-shot lazy migration from legacy `<hcm>/<profile>/profile.yml + credentials.json`
|
|
734
|
+
* to canonical `<hcm>/envs/<name>/env.yml + identities/<name>/credentials.json`.
|
|
735
|
+
*
|
|
736
|
+
* Semantics:
|
|
737
|
+
* - Idempotent: profiles already migrated (env.yml exists) are skipped.
|
|
738
|
+
* - Per-profile atomicity: any failure inside migrateOne rolls back the partial env.
|
|
739
|
+
* - Legacy files for failed profiles stay in place so the user can inspect / retry.
|
|
740
|
+
* - On any migration success, the global config.yml is rewritten so activeEnv is
|
|
741
|
+
* the canonical key (loadGlobalConfig already provides fallback read of
|
|
742
|
+
* legacy activeProfile).
|
|
743
|
+
* - Structured stderr log: `[hcm] event=migration_done count=N archive=PATH/`
|
|
744
|
+
* and `[hcm] event=migration_failed profile=NAME reason="..."`.
|
|
745
|
+
*/
|
|
746
|
+
declare function migrateLegacyProfiles(): Promise<MigrationSummary>;
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Resolve active identity name.
|
|
750
|
+
* Priority: --as flag > HCM_IDENTITY env var > env.defaultIdentity > first identity in env > throw.
|
|
751
|
+
*/
|
|
752
|
+
declare function resolveActiveIdentity(args: {
|
|
753
|
+
asFlag?: string;
|
|
754
|
+
}, env: NodeJS.ProcessEnv, activeEnv: string): Promise<string>;
|
|
755
|
+
|
|
756
|
+
interface StreamCommandEnvelope<P = unknown> {
|
|
757
|
+
protocolVersion: '3.0' | '3.2' | '4.0';
|
|
758
|
+
commandId: string;
|
|
759
|
+
conversationId: string;
|
|
760
|
+
type: StreamCommandType;
|
|
761
|
+
timestamp: number;
|
|
762
|
+
payload: P;
|
|
763
|
+
}
|
|
764
|
+
interface StreamEventEnvelope<P = unknown> {
|
|
765
|
+
protocolVersion: '3.0' | '4.0';
|
|
766
|
+
streamId: string;
|
|
767
|
+
conversationId: string;
|
|
768
|
+
requestId: string;
|
|
769
|
+
traceId: string | null;
|
|
770
|
+
eventId: string;
|
|
771
|
+
seq: number;
|
|
772
|
+
type: string;
|
|
773
|
+
timestamp: number;
|
|
774
|
+
payload: P;
|
|
775
|
+
}
|
|
776
|
+
type ConnectionMessage = {
|
|
777
|
+
type: 'auth_success';
|
|
778
|
+
sessionId: string;
|
|
779
|
+
} | {
|
|
780
|
+
type: 'auth_error';
|
|
781
|
+
code: string;
|
|
782
|
+
message: string;
|
|
783
|
+
} | {
|
|
784
|
+
type: 'pong';
|
|
785
|
+
} | {
|
|
786
|
+
type: 'token_expiring';
|
|
787
|
+
expiresAt: number;
|
|
788
|
+
} | {
|
|
789
|
+
type: 'error';
|
|
790
|
+
code: string;
|
|
791
|
+
message: string;
|
|
792
|
+
};
|
|
793
|
+
type StreamCommandType = 'message.create' | 'stream.cancel' | 'approval.confirm' | 'approval.reject' | 'interaction.answer' | 'response.interrupt' | 'response.steer' | 'response.regenerate';
|
|
794
|
+
type StreamEventType = 'response.started' | 'response.part.delta' | 'response.part.upserted' | 'response.completed' | 'response.failed' | 'response.interrupted' | 'response.tool.started' | 'response.tool.completed' | 'response.tool.failed' | 'task_progress' | 'task_done' | 'task_failed';
|
|
795
|
+
|
|
796
|
+
interface ResponseStartedPayload {
|
|
797
|
+
assistantMessageId: string;
|
|
798
|
+
resumeSupported: boolean;
|
|
799
|
+
}
|
|
800
|
+
interface ResponsePartDeltaPayload {
|
|
801
|
+
delta: string;
|
|
802
|
+
partId?: string;
|
|
803
|
+
partType?: 'text' | 'reasoning';
|
|
804
|
+
index?: number;
|
|
805
|
+
}
|
|
806
|
+
interface ResponseCompletedPayload {
|
|
807
|
+
assistantMessageId: string;
|
|
808
|
+
finalContent: string;
|
|
809
|
+
usage?: {
|
|
810
|
+
inputTokens: number;
|
|
811
|
+
outputTokens: number;
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
interface ResponseFailedPayload {
|
|
815
|
+
code: string;
|
|
816
|
+
message: string;
|
|
817
|
+
}
|
|
818
|
+
interface ToolCallStartedPayload {
|
|
819
|
+
callId: string;
|
|
820
|
+
toolName: string;
|
|
821
|
+
category?: string;
|
|
822
|
+
input?: Record<string, unknown>;
|
|
823
|
+
}
|
|
824
|
+
interface ToolCallResultPayload {
|
|
825
|
+
callId: string;
|
|
826
|
+
output?: unknown;
|
|
827
|
+
elapsedMs?: number;
|
|
828
|
+
}
|
|
829
|
+
interface ToolCallFailedPayload {
|
|
830
|
+
callId: string;
|
|
831
|
+
code: string;
|
|
832
|
+
message: string;
|
|
833
|
+
}
|
|
834
|
+
interface TaskProgressPayload {
|
|
835
|
+
taskId: string;
|
|
836
|
+
progress: number;
|
|
837
|
+
message?: string;
|
|
838
|
+
}
|
|
839
|
+
type AnyStreamEvent = StreamEventEnvelope<ResponseStartedPayload> | StreamEventEnvelope<ResponsePartDeltaPayload> | StreamEventEnvelope<ResponseCompletedPayload> | StreamEventEnvelope<ResponseFailedPayload> | StreamEventEnvelope<ToolCallStartedPayload> | StreamEventEnvelope<ToolCallResultPayload> | StreamEventEnvelope<ToolCallFailedPayload> | StreamEventEnvelope<TaskProgressPayload>;
|
|
840
|
+
/** v4 展示流事件 wire type(ADR-118)。终态三事件沿用 v3 命名作兼容边界。 */
|
|
841
|
+
type V4StreamEventType = 'message.start' | 'block.start' | 'block.delta' | 'block.stop' | 'tool.result' | 'message.stop' | 'response.completed' | 'response.failed' | 'response.interrupted';
|
|
842
|
+
/** 控制通道信封:channel='control',不带 stream 的 streamId/seq 语义。 */
|
|
843
|
+
interface ControlChannelEnvelope {
|
|
844
|
+
channel: 'control';
|
|
845
|
+
protocolVersion: string;
|
|
846
|
+
conversationId: string;
|
|
847
|
+
type: string;
|
|
848
|
+
timestamp: number;
|
|
849
|
+
payload: Record<string, unknown>;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
interface MessageCreatePayload {
|
|
853
|
+
content: string;
|
|
854
|
+
agentKey?: string;
|
|
855
|
+
directOpsAgentId?: string;
|
|
856
|
+
parentMessageId?: string;
|
|
857
|
+
pageContext?: Record<string, unknown>;
|
|
858
|
+
/** issue #64:附件元数据(后端 parseAttachments 形态,documentId/fileName/mimeType/fileSize)。 */
|
|
859
|
+
attachments?: Array<{
|
|
860
|
+
documentId: string;
|
|
861
|
+
fileName: string;
|
|
862
|
+
mimeType: string;
|
|
863
|
+
fileSize: number;
|
|
864
|
+
}>;
|
|
865
|
+
}
|
|
866
|
+
interface StreamCancelPayload {
|
|
867
|
+
streamId: string;
|
|
868
|
+
}
|
|
869
|
+
interface ApprovalConfirmPayload {
|
|
870
|
+
confirmationId: string;
|
|
871
|
+
}
|
|
872
|
+
interface ApprovalRejectPayload {
|
|
873
|
+
confirmationId: string;
|
|
874
|
+
reason?: string;
|
|
875
|
+
}
|
|
876
|
+
interface InteractionAnswerPayload {
|
|
877
|
+
interactionId: string;
|
|
878
|
+
answer: string;
|
|
879
|
+
choiceIndex?: number | null;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
type MessageClass = 'control' | 'event' | 'connection';
|
|
883
|
+
/** WS 入站消息三分:控制通道(channel='control')优先于流事件(streamId+seq),其余为连接层。 */
|
|
884
|
+
declare function classifyMessage(parsed: any): MessageClass;
|
|
885
|
+
interface WsClientOpts {
|
|
886
|
+
endpoint: string;
|
|
887
|
+
getAccessToken: () => Promise<string | null>;
|
|
888
|
+
onTokenExpiring?: () => Promise<void>;
|
|
889
|
+
heartbeatMs?: number;
|
|
890
|
+
}
|
|
891
|
+
declare class WsClient {
|
|
892
|
+
private opts;
|
|
893
|
+
private ws?;
|
|
894
|
+
private handlers;
|
|
895
|
+
private heartbeatTimer?;
|
|
896
|
+
private ready;
|
|
897
|
+
private sessionId?;
|
|
898
|
+
/** 流进行中断连的持久订阅者(iterator 等终态收口者)。RISK-1:auth 后无持久
|
|
899
|
+
* close handler,断连时上层 await 永久挂死。这里让消费方能感知 socket 死。 */
|
|
900
|
+
private disconnectHandlers;
|
|
901
|
+
/** 由 close()/ensureConnected 主动关闭 → 不广播 disconnect(非异常断连)。 */
|
|
902
|
+
private intentionalClose;
|
|
903
|
+
constructor(opts: WsClientOpts);
|
|
904
|
+
/**
|
|
905
|
+
* 订阅「流进行中 socket 异常断连」(后端重启 / 网络闪断)。主动 close() 不触发。
|
|
906
|
+
* iterator 订阅它 → 断连时 finish()(incomplete 终态),杜绝 await consume 永久挂死。
|
|
907
|
+
*/
|
|
908
|
+
onDisconnect(fn: () => void): () => void;
|
|
909
|
+
private notifyDisconnect;
|
|
910
|
+
connect(): Promise<void>;
|
|
911
|
+
send<P>(cmd: StreamCommandEnvelope<P>): void;
|
|
912
|
+
onEvent(fn: (ev: StreamEventEnvelope) => void): () => void;
|
|
913
|
+
onConnection(fn: (msg: ConnectionMessage) => void): () => void;
|
|
914
|
+
onControl(fn: (ev: ControlChannelEnvelope) => void): () => void;
|
|
915
|
+
isReady(): boolean;
|
|
916
|
+
/**
|
|
917
|
+
* 连接已断(后端重启 / 网络闪断 / 心跳静默失败后 socket 已死)时重建连接。
|
|
918
|
+
* REPL 长开场景每轮发消息前调用——旧连接死掉不应让用户重启 CLI(2026-06-12 用户
|
|
919
|
+
* 实测:后端重启后 REPL 下一条消息报错)。已连接且 socket OPEN 时为 no-op。
|
|
920
|
+
*/
|
|
921
|
+
ensureConnected(): Promise<void>;
|
|
922
|
+
getSessionId(): string | undefined;
|
|
923
|
+
close(): Promise<void>;
|
|
924
|
+
/** 流进行中 socket 异常断连(非主动 close)→ 广播 disconnect 让消费方收口。 */
|
|
925
|
+
private persistentOnClose;
|
|
926
|
+
private persistentOnMessage;
|
|
927
|
+
private startHeartbeat;
|
|
928
|
+
private dispatchEvent;
|
|
929
|
+
private dispatchConnection;
|
|
930
|
+
private dispatchControl;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
interface InteractionRequest {
|
|
934
|
+
kind: 'CONFIRM' | 'ASK';
|
|
935
|
+
interactionId: string;
|
|
936
|
+
summary: string;
|
|
937
|
+
originAgentPath: string;
|
|
938
|
+
ttlSeconds: number;
|
|
939
|
+
riskLevel?: string;
|
|
940
|
+
toolName?: string;
|
|
941
|
+
argsPreview?: Record<string, unknown>;
|
|
942
|
+
question?: string;
|
|
943
|
+
choices?: string[];
|
|
944
|
+
}
|
|
945
|
+
interface InteractionResolved {
|
|
946
|
+
interactionId: string;
|
|
947
|
+
phase: 'RESUMED' | 'TERMINAL' | string;
|
|
948
|
+
}
|
|
949
|
+
interface StreamCommand {
|
|
950
|
+
protocolVersion: '3.0';
|
|
951
|
+
commandId: string;
|
|
952
|
+
conversationId: string;
|
|
953
|
+
type: 'approval.confirm' | 'approval.reject' | 'interaction.answer' | 'response.interrupt' | 'response.steer';
|
|
954
|
+
timestamp: number;
|
|
955
|
+
payload: Record<string, unknown>;
|
|
956
|
+
}
|
|
957
|
+
declare function parseInteractionRequest(payload: Record<string, unknown>): InteractionRequest;
|
|
958
|
+
declare function parseInteractionResolved(payload: Record<string, unknown>): InteractionResolved;
|
|
959
|
+
declare function buildConfirm(confirmationId: string, conversationId: string): StreamCommand;
|
|
960
|
+
declare function buildReject(confirmationId: string, conversationId: string, reason?: string): StreamCommand;
|
|
961
|
+
declare function buildAnswer(interactionId: string, conversationId: string, answer: string, choiceIndex?: number): StreamCommand;
|
|
962
|
+
/**
|
|
963
|
+
* 流中途打断(cooperative cancel)。后端收到后停止当前 round,流以 ROOT message.stop
|
|
964
|
+
* (payload.stopReason=="interrupted")终结。payload 为空对象 {}(后端契约已核实)。
|
|
965
|
+
*/
|
|
966
|
+
declare function buildInterrupt(conversationId: string): StreamCommand;
|
|
967
|
+
/**
|
|
968
|
+
* 流中途引导(steer)。后端入队 content,下一 round 注入 SystemMessage;无任何 ack 事件
|
|
969
|
+
* (v3 ack 已退役),流正常完成。payload 为 {content}(后端契约已核实)。
|
|
970
|
+
*/
|
|
971
|
+
declare function buildSteer(content: string, conversationId: string): StreamCommand;
|
|
972
|
+
|
|
973
|
+
interface SendMessageOpts {
|
|
974
|
+
conversationId?: string;
|
|
975
|
+
agentKey?: string;
|
|
976
|
+
agentId?: string;
|
|
977
|
+
pageContext?: Record<string, unknown>;
|
|
978
|
+
/**
|
|
979
|
+
* confirm/ask 决策回发后继续消费 resume 流到真终态(Task B3)。
|
|
980
|
+
*
|
|
981
|
+
* v4 协议:confirm 触发时原流以 ROOT message.stop 终态(HALT 语义);决策回发后后端
|
|
982
|
+
* resume 续推事件。开启本开关后终态规则变为:
|
|
983
|
+
* - ROOT message.stop 且无未决交互 → 终态(含全程无交互的普通流)
|
|
984
|
+
* - ROOT message.stop 且有未决交互 → hold(HALT / 串联 confirm 的中间 stop)
|
|
985
|
+
* - `control.interaction.resolved(phase=TERMINAL)` 清空未决集且曾 hold 过 ROOT stop
|
|
986
|
+
* → 终态兜底(TERMINAL 是 InteractionSession 生命周期事件,必达;防 resume 段缺
|
|
987
|
+
* ROOT message.stop 的后端回归挂死 CLI)
|
|
988
|
+
* 仅在调用方会回发决策(--on-confirm / --on-ask / 交互式确认)时开启;默认关闭,
|
|
989
|
+
* 行为与历史完全一致。
|
|
990
|
+
*/
|
|
991
|
+
holdUntilInteractionTerminal?: boolean;
|
|
992
|
+
/**
|
|
993
|
+
* 重新生成模式(ADR-089):不发 message.create,改发 `response.regenerate`
|
|
994
|
+
* (payload {fromSeq})。fromSeq = 被重生成 assistant 段起点的 journal seq
|
|
995
|
+
* (LLM_DONE;timeline chat-text item 的 seq 即它)。后端 append SEGMENT_SUPERSEDED
|
|
996
|
+
* marker 后用原 user message 重跑 loop,新流事件 requestId 回显本命令 commandId
|
|
997
|
+
* ——iterator 过滤与终态规则完全复用。必须配合已存在的 conversationId。
|
|
998
|
+
*/
|
|
999
|
+
regenerateFromSeq?: number;
|
|
1000
|
+
/** 附件元数据(issue #64):已上传文档随消息发送,后端 parseAttachments 同形态。 */
|
|
1001
|
+
attachments?: AttachmentMeta[];
|
|
1002
|
+
}
|
|
1003
|
+
interface MessageStreamHandle {
|
|
1004
|
+
conversationId: string;
|
|
1005
|
+
commandId: string;
|
|
1006
|
+
events: AsyncIterable<StreamEventEnvelope>;
|
|
1007
|
+
/**
|
|
1008
|
+
* 客户端中断当前流(ADR-085)。
|
|
1009
|
+
* 发送 `stream.cancel` command 到后端;后端会回 `response.interrupted` 作 iterator 终态。
|
|
1010
|
+
* 调用方仍需等 iterator 走完才能确认终止。
|
|
1011
|
+
*/
|
|
1012
|
+
cancel(reason?: string): void;
|
|
1013
|
+
/**
|
|
1014
|
+
* 订阅本 conversation 的带外控制通道事件(control.interaction.request / resolved)。
|
|
1015
|
+
* 仅转发 `ev.conversationId === conversationId` 的事件;返回取消订阅函数。
|
|
1016
|
+
*/
|
|
1017
|
+
onControl(fn: (ev: ControlChannelEnvelope) => void): () => void;
|
|
1018
|
+
/**
|
|
1019
|
+
* 回发 client→server 控制命令(approval.confirm / approval.reject / interaction.answer)。
|
|
1020
|
+
* 由 control-channel 的 build* 构造,透传到 ws。
|
|
1021
|
+
*/
|
|
1022
|
+
sendCommand(cmd: StreamCommand): void;
|
|
1023
|
+
}
|
|
1024
|
+
declare function sendMessageAndStream(ws: WsClient, prompt: string, opts?: SendMessageOpts): Promise<MessageStreamHandle>;
|
|
1025
|
+
|
|
1026
|
+
interface OneShotToolCall {
|
|
1027
|
+
/** 工具名;委派为 'hcm_delegate'。无法解析时 '?'。 */
|
|
1028
|
+
name: string;
|
|
1029
|
+
/** 参数预览(reducer 已按显示宽度截断);委派时为目标 agentKey。 */
|
|
1030
|
+
argsPreview: string;
|
|
1031
|
+
success: boolean;
|
|
1032
|
+
elapsedMs: number;
|
|
1033
|
+
/** 委派调用标记。 */
|
|
1034
|
+
isDelegation?: boolean;
|
|
1035
|
+
/** 归属父 tool_use(顶层直接工具为 null)。 */
|
|
1036
|
+
parentToolUseId: string | null;
|
|
1037
|
+
}
|
|
1038
|
+
interface OneShotResult {
|
|
1039
|
+
conversationId: string;
|
|
1040
|
+
streamId: string;
|
|
1041
|
+
traceId: string | null;
|
|
1042
|
+
/** 最终助手回答正文(ROOT 段文本;委派 specialist 的中间文本不计入)。 */
|
|
1043
|
+
content: string;
|
|
1044
|
+
toolCalls: OneShotToolCall[];
|
|
1045
|
+
elapsedMs: number;
|
|
1046
|
+
events: StreamEventEnvelope[];
|
|
1047
|
+
/** 终态原因(v4 message.stop.stopReason);未见终态时 undefined。 */
|
|
1048
|
+
stopReason?: string;
|
|
1049
|
+
/** 本轮总 token(v4 message.stop.totalTokens)。 */
|
|
1050
|
+
tokens?: number;
|
|
1051
|
+
}
|
|
1052
|
+
interface OneShotOpts extends SendMessageOpts {
|
|
1053
|
+
collectEvents?: boolean;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* 非流式单发:消费 v4 事件流并聚合出最终结果。
|
|
1057
|
+
*
|
|
1058
|
+
* 🔴 与流式路径同源:把事件喂进 {@link createV4Reducer}(与 CLI streaming、Web 端 fold
|
|
1059
|
+
* 状态机一致的 v4 语义层),用一个 headless sink 累加 ROOT 文本 + 收集工具调用——而非
|
|
1060
|
+
* 手搓第二套聚合。历史 bug:旧实现监听已废弃的 v3 事件名(response.part.delta /
|
|
1061
|
+
* response.completed / response.tool.*),后端早改发 v4(block.delta:text_delta /
|
|
1062
|
+
* message.stop)→ switch 全不命中 → content 恒空(--no-stream / --output json 拿到空答案)。
|
|
1063
|
+
* 语义层复用见 ADR-129 D2:只换「画」的层,聚合逻辑不重复实现。
|
|
1064
|
+
*
|
|
1065
|
+
* 终态由 stream-iterator 在 ROOT message.stop(或断连)关闭 async iterator 驱动,本函数
|
|
1066
|
+
* 无需自判终止。v3 残余 `response.failed`(仅老路径出现)仍显式抛 CliError 保留旧语义。
|
|
1067
|
+
*/
|
|
1068
|
+
declare function oneShot(ws: WsClient, prompt: string, opts?: OneShotOpts): Promise<OneShotResult>;
|
|
1069
|
+
|
|
1070
|
+
interface ConversationState {
|
|
1071
|
+
lastConversationId?: string;
|
|
1072
|
+
lastStreamId?: string;
|
|
1073
|
+
lastTraceId?: string;
|
|
1074
|
+
lastAgentKey?: string;
|
|
1075
|
+
updatedAt: string;
|
|
1076
|
+
}
|
|
1077
|
+
declare function loadConversationState(profile: string): Promise<ConversationState | null>;
|
|
1078
|
+
declare function saveConversationState(profile: string, state: ConversationState): Promise<void>;
|
|
1079
|
+
|
|
1080
|
+
/** Default analyst agent id (seeded in HCM at tenant=__SYSTEM__). */
|
|
1081
|
+
declare const DEFAULT_AGENT_ID = "019cca00-0000-7000-0000-000000000001";
|
|
1082
|
+
interface CreatedConversation {
|
|
1083
|
+
id: string;
|
|
1084
|
+
agentId: string;
|
|
1085
|
+
title?: string;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Create a new conversation in HCM. Required before sending any /ws/conversation
|
|
1089
|
+
* message — the WS handler looks up conversation→agentId in DB.
|
|
1090
|
+
*/
|
|
1091
|
+
declare function createConversation(http: AxiosInstance, agentId?: string): Promise<CreatedConversation>;
|
|
1092
|
+
|
|
1093
|
+
interface V4RenderSink {
|
|
1094
|
+
onText(text: string, parentToolUseId: string | null): void;
|
|
1095
|
+
onThinkingDelta(text: string, parentToolUseId: string | null): void;
|
|
1096
|
+
onThinkingDone(parentToolUseId: string | null): void;
|
|
1097
|
+
onToolStarted(callId: string, toolName: string, argsPreview: string, parentToolUseId: string | null): void;
|
|
1098
|
+
/**
|
|
1099
|
+
* @param nextSteps G5 · 工具失败时从 result envelope 解析出的 ADR-058 路标(`error.nextSteps`
|
|
1100
|
+
* 数组 / 顶层 `next_step`)。成功或无路标时 undefined。供 host 在失败行下透出下一步指引。
|
|
1101
|
+
* @param toolName #1930 · 工具名。优先取 block.start 注册值;缓存短路 no-op 等无 block.start
|
|
1102
|
+
* 的事件由 reducer 从 result 文本兜底解析(如「hcm_describe_tool (缓存命中)」),避免 host
|
|
1103
|
+
* 裸显示 `?`。仍解析不出时 undefined,host 自行兜底。
|
|
1104
|
+
*/
|
|
1105
|
+
onToolResult(callId: string, success: boolean, durMs: number, childConversationId: string | null, parentToolUseId: string | null, nextSteps?: string[], toolName?: string): void;
|
|
1106
|
+
onContentPart(part: Record<string, unknown>, parentToolUseId: string | null): void;
|
|
1107
|
+
/**
|
|
1108
|
+
* 用户中途纠偏在轮界被真正注入(v4 `steering.applied` 顶层事件,W1a-2)。
|
|
1109
|
+
* 仅注入时到达——客户端禁止在发 response.steer 时乐观本地显示「已纠偏」。
|
|
1110
|
+
*/
|
|
1111
|
+
onSteeringApplied(contents: string[], round: number): void;
|
|
1112
|
+
onDelegationStart(agentKey: string, parentToolUseId: string): void;
|
|
1113
|
+
/**
|
|
1114
|
+
* 委派收口。由 hcm_delegate 自身的 tool.result 驱动(后端时序保证:子流 message.stop
|
|
1115
|
+
* 先到、父 tool.result 紧随其后,见 WebSocketRuntimeEventProjector.finishChildStreamIfAny)
|
|
1116
|
+
* ——因此能携带委派整体 success / 耗时 / 失败路标(2026-06-11 review C1/I1:旧版由子
|
|
1117
|
+
* message.stop 驱动拿不到 success,失败委派被静默渲染成功)。
|
|
1118
|
+
*
|
|
1119
|
+
* @param nextSteps 委派失败时的 ADR-058 路标;成功为 undefined。
|
|
1120
|
+
*/
|
|
1121
|
+
onDelegationEnd(parentToolUseId: string, success: boolean, durMs: number, nextSteps?: string[]): void;
|
|
1122
|
+
/**
|
|
1123
|
+
* 委派「暂停待应答」(非失败):specialist 在子流内调 hcm_ask_user / 触发风险确认时,
|
|
1124
|
+
* 后端按 HALT 语义给 hcm_delegate 的 tool.result 发 transport `success=false`,但其
|
|
1125
|
+
* envelope 实为 `ok:true` + `INTERACTION_REQUIRED`/`CONFIRMATION_REQUIRED` 标记
|
|
1126
|
+
* (后端 InteractionSuspendMiddleware / RiskConfirmationMiddleware)。这不是失败——
|
|
1127
|
+
* 用户应答后委派会 RESUME(同 parentToolUseId 再来一段 message.start)续跑到真终态。
|
|
1128
|
+
*
|
|
1129
|
+
* 消费端据此:保留委派块不收口、累计本段耗时,等 RESUME 续同一块——避免把暂停
|
|
1130
|
+
* 渲染成红色「委派失败」、并避免暂停→续跑被计为两次委派(CLI append-only 无法回填)。
|
|
1131
|
+
* @param durMs 暂停前本段执行耗时(累计进委派块总耗时)。
|
|
1132
|
+
*/
|
|
1133
|
+
onDelegationPaused(parentToolUseId: string, durMs: number): void;
|
|
1134
|
+
onTerminal(stopReason: string, tokens: number): void;
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* 判定一条 hcm_delegate 的 tool.result(transport `success=false`)实为「暂停待应答」而非失败。
|
|
1138
|
+
*
|
|
1139
|
+
* 后端 wire 接缝不一致:HALT 时 executor 结果 `success=true`+`haltConversation`,但投影到
|
|
1140
|
+
* delegate 的 tool.result `success` 字段为 false;其 resultData envelope 仍诚实保留 `ok:true`
|
|
1141
|
+
* + summary/data 带 `INTERACTION_REQUIRED`(ASK)/`CONFIRMATION_REQUIRED`(CONFIRM)标记。
|
|
1142
|
+
* 以「envelope ok===true 且含交互标记」为准绳——纯结构信号,不误伤真失败(真失败 ok:false)。
|
|
1143
|
+
*/
|
|
1144
|
+
declare function detectDelegationPause(resultData: unknown): boolean;
|
|
1145
|
+
/**
|
|
1146
|
+
* #1930 Bug B · 判定一条**直接工具**(非委派)的 tool.result 实为「待确认 / 待应答」控制信号,
|
|
1147
|
+
* 而非工具执行结果。
|
|
1148
|
+
*
|
|
1149
|
+
* 后端写动作命中确认门 / 交互门时,会先发一条 transport `success=false` 的 TOOL_COMPLETED,其
|
|
1150
|
+
* result 是纯文本前缀 {@code "CONFIRMATION_REQUIRED: ..."} / {@code "INTERACTION_REQUIRED: ..."}
|
|
1151
|
+
* (非 JSON envelope),同时另发一条 {@code control.interaction.request} 让 host 弹确认卡。该 tool.result
|
|
1152
|
+
* 对展示流是冗余的——若按普通工具渲染会变成红色 `⚙ ? ✗ 0ms` 假失败并计入失败统计(实测用户困惑)。
|
|
1153
|
+
*
|
|
1154
|
+
* 以「结果文本以这两个标记**起始**」为准绳(强协议信号,正常工具结果不会以此开头),故 host 可安全
|
|
1155
|
+
* 抑制该行,把待确认态交给 confirm 卡承载。委派场景的同类暂停由 {@link detectDelegationPause} 处理,
|
|
1156
|
+
* 二者互不重叠(委派结果是 envelope,直接工具是纯文本前缀)。
|
|
1157
|
+
*/
|
|
1158
|
+
declare function detectDirectInteractionPending(resultData: unknown): boolean;
|
|
1159
|
+
/**
|
|
1160
|
+
* #1930 Bug A · 为无 block.start 注册名的 tool.result 从结果文本兜底解析有意义的展示名。
|
|
1161
|
+
*
|
|
1162
|
+
* 当前唯一已知来源:ADR-095 catalog 缓存短路 no-op——后端直接发 TOOL_COMPLETED(无 TOOL_STARTED),
|
|
1163
|
+
* result 为 {@code "[ADR-095 catalog cache hit] 工具 <toolName> 已在本 sub-session ..."}。解析出工具名
|
|
1164
|
+
* 时返回 {@code "<toolName> (缓存命中)"},解析不出则返回 {@code "缓存命中"}。其他结果返回 undefined
|
|
1165
|
+
* (host 保留原 `?` 兜底给真未知事件,不强行编造名字)。
|
|
1166
|
+
*/
|
|
1167
|
+
declare function deriveNamelessToolLabel(resultData: unknown): string | undefined;
|
|
1168
|
+
/**
|
|
1169
|
+
* #1949 · 从确认门 / 交互门占位结果文本解析被挂起的工具名。
|
|
1170
|
+
*
|
|
1171
|
+
* 后端 {@code RiskConfirmationMiddleware} 挂起时发的占位 TOOL_COMPLETED,其 result 为纯文本
|
|
1172
|
+
* {@code "CONFIRMATION_REQUIRED: 等待用户确认 tool=<name>, confirmationId=..., scopeKey=..."}
|
|
1173
|
+
* (INTERACTION_REQUIRED 亦可能带 {@code tool=})。用户确认后被 resume 的工具,其真结果
|
|
1174
|
+
* {@code tool.result} 可能先于(或缺失)resume 段 {@code block.start} 抵达消费端 → {@code toolNameByCallId}
|
|
1175
|
+
* 无注册名 → 裸显示 `?`(#1949)。这里在抑制占位结果时解析出工具名,供 resume 真结果按 callId 兜底取名。
|
|
1176
|
+
* 解析不出返回 undefined。
|
|
1177
|
+
*/
|
|
1178
|
+
declare function parseConfirmToolName(resultData: unknown): string | undefined;
|
|
1179
|
+
/**
|
|
1180
|
+
* G5 · 从 tool.result 的 resultData 防御性提取 ADR-058 `next_step` 路标。
|
|
1181
|
+
*
|
|
1182
|
+
* 后端 wire:`resultData = { result: "<envelope JSON 字符串>" }`,失败 envelope 形如
|
|
1183
|
+
* `{ ok:false, error:{ code, message, nextSteps:[...] } }`;部分执行器平铺顶层 `next_step`
|
|
1184
|
+
* (可为 string / string[] / {message,...} map)。这里兼容多形态,任一解析失败都静默返回
|
|
1185
|
+
* undefined(非 JSON 纯文本 result 不炸)。
|
|
1186
|
+
*/
|
|
1187
|
+
declare function extractNextSteps(resultData: unknown): string[] | undefined;
|
|
1188
|
+
declare function createV4Reducer(sink: V4RenderSink): {
|
|
1189
|
+
handle: (type: string, payload: Record<string, unknown>) => boolean;
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1192
|
+
declare function isDelegationToolName(name: string): boolean;
|
|
1193
|
+
|
|
1194
|
+
/** 字符串的终端显示宽度(列数)。 */
|
|
1195
|
+
declare function displayWidth(s: string): number;
|
|
1196
|
+
/** 按显示宽度截断,超宽时截到 maxWidth-1 列并补「…」;未超宽原样返回。 */
|
|
1197
|
+
declare function truncateDisplay(s: string, maxWidth: number): string;
|
|
1198
|
+
|
|
1199
|
+
/**
|
|
1200
|
+
* 续会话回放(CLI 做透轮刀⑤,对标 Claude Code resume 的上下文可见性)。
|
|
1201
|
+
*
|
|
1202
|
+
* hcm chat 只有显式 --resume 续接 conversation 时才回放历史;否则默认开新会话。
|
|
1203
|
+
* 本模块拉
|
|
1204
|
+
* timeline(GET /api/models/ConversationModel/{id}/action/timeline,与 Web 回放
|
|
1205
|
+
* 同源)折出轻量摘要:最近 N 轮 user/assistant 文本 + 更早轮次计数。
|
|
1206
|
+
*/
|
|
1207
|
+
/** timeline 条目(后端 TimelineItemDto 的 CLI 侧投影,只取回放需要的字段)。 */
|
|
1208
|
+
interface TimelineItemLite {
|
|
1209
|
+
kind: string;
|
|
1210
|
+
seq: number;
|
|
1211
|
+
/** user-msg / steering 的原文。 */
|
|
1212
|
+
text?: string | null;
|
|
1213
|
+
/** chat-text 的正文。 */
|
|
1214
|
+
content?: string | null;
|
|
1215
|
+
/** user-msg 附件元数据(#1849 journal 单源透传;issue #64 回放 📎)。 */
|
|
1216
|
+
attachments?: Array<{
|
|
1217
|
+
fileName?: string;
|
|
1218
|
+
}> | null;
|
|
1219
|
+
}
|
|
1220
|
+
interface ResumeTurn {
|
|
1221
|
+
role: 'user' | 'assistant';
|
|
1222
|
+
text: string;
|
|
1223
|
+
}
|
|
1224
|
+
interface ResumeSummary {
|
|
1225
|
+
conversationId: string;
|
|
1226
|
+
/** 会话总轮数(user-msg 条目数)。 */
|
|
1227
|
+
totalRounds: number;
|
|
1228
|
+
/** 最近 maxRounds 轮的 user/assistant 文本(已按显示宽度截断为单行摘要)。 */
|
|
1229
|
+
recent: ResumeTurn[];
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* 拉取 conversation timeline。任何失败(网络/404/结构异常)返回 null——
|
|
1233
|
+
* 回放是体验增强,绝不阻断聊天主流程。
|
|
1234
|
+
*/
|
|
1235
|
+
declare function fetchConversationTimeline(http: AxiosInstance, conversationId: string): Promise<TimelineItemLite[] | null>;
|
|
1236
|
+
/**
|
|
1237
|
+
* 严格拉取 conversation timeline。用于 CLI 显式 inspect 场景:网络/404/结构异常都应暴露,
|
|
1238
|
+
* 不能像 resume 体验增强那样静默吞掉。
|
|
1239
|
+
*/
|
|
1240
|
+
declare function fetchConversationTimelineStrict(http: AxiosInstance, conversationId: string): Promise<TimelineItemLite[]>;
|
|
1241
|
+
/**
|
|
1242
|
+
* timeline → 续会话摘要。取最近 maxRounds 轮:每轮 = user-msg 原文 + 该轮最后一条
|
|
1243
|
+
* chat-text 正文(中间 tools/delegation/steering 等过程条目不进摘要——回顾要的是
|
|
1244
|
+
* 「聊到哪了」不是过程重放)。文本截为单行(换行折叠 + 显示宽度截断)。
|
|
1245
|
+
*/
|
|
1246
|
+
declare function buildResumeSummary(conversationId: string, items: TimelineItemLite[], maxRounds?: number, maxWidth?: number): ResumeSummary | null;
|
|
1247
|
+
declare function findLastAssistantSeq(items: TimelineItemLite[] | null | undefined): number | null;
|
|
1248
|
+
|
|
1249
|
+
interface ConversationListItem {
|
|
1250
|
+
id?: string;
|
|
1251
|
+
title?: string | null;
|
|
1252
|
+
summary?: string | null;
|
|
1253
|
+
agentId?: string | null;
|
|
1254
|
+
messageCount?: number | null;
|
|
1255
|
+
totalTokens?: number | null;
|
|
1256
|
+
lastActiveAt?: string | null;
|
|
1257
|
+
lastMessagePreview?: string | null;
|
|
1258
|
+
createTime?: string | null;
|
|
1259
|
+
updateTime?: string | null;
|
|
1260
|
+
}
|
|
1261
|
+
interface ConversationListResult {
|
|
1262
|
+
rows: ConversationListItem[];
|
|
1263
|
+
total?: number;
|
|
1264
|
+
}
|
|
1265
|
+
declare function fetchRecentConversations(http: AxiosInstance, limit?: number): Promise<ConversationListResult>;
|
|
1266
|
+
|
|
1267
|
+
declare const MINI_CONTEXT_FILE = "hcm.context.json";
|
|
1268
|
+
declare const MINI_STATE_DIR = ".hcm";
|
|
1269
|
+
declare const MINI_PULL_MANIFEST_FILE = "mini-manifest.json";
|
|
1270
|
+
declare const MINI_REMOTE_ROOT = "mini_apps";
|
|
1271
|
+
declare const MINI_CONTEXT_KIND = "hcm-mini-app";
|
|
1272
|
+
declare const MINI_CONTEXT_VERSION = 1;
|
|
1273
|
+
type MiniSurface = 'embedded' | 'standalone';
|
|
1274
|
+
type MiniTemplateKind = 'dashboard' | 'multi-page' | 'writable-workbench';
|
|
1275
|
+
interface WorkspaceFileItem {
|
|
1276
|
+
id: string;
|
|
1277
|
+
workspaceKey: string;
|
|
1278
|
+
relativePath: string;
|
|
1279
|
+
fileType?: string;
|
|
1280
|
+
fileSize?: number;
|
|
1281
|
+
lastModified?: number | string | null;
|
|
1282
|
+
}
|
|
1283
|
+
interface WorkspaceFileContent {
|
|
1284
|
+
workspaceKey: string;
|
|
1285
|
+
relativePath: string;
|
|
1286
|
+
content: string;
|
|
1287
|
+
fileType?: string;
|
|
1288
|
+
}
|
|
1289
|
+
interface WorkspaceFileListResult {
|
|
1290
|
+
data: WorkspaceFileItem[];
|
|
1291
|
+
total: number;
|
|
1292
|
+
}
|
|
1293
|
+
interface MiniProjectContext {
|
|
1294
|
+
kind: typeof MINI_CONTEXT_KIND;
|
|
1295
|
+
version: typeof MINI_CONTEXT_VERSION;
|
|
1296
|
+
appCode: string;
|
|
1297
|
+
workspaceKey: string;
|
|
1298
|
+
remotePrefix: string;
|
|
1299
|
+
tenantId?: string;
|
|
1300
|
+
endpoint?: string;
|
|
1301
|
+
profile?: string;
|
|
1302
|
+
updatedAt: string;
|
|
1303
|
+
}
|
|
1304
|
+
interface MiniFileSnapshot {
|
|
1305
|
+
localPath: string;
|
|
1306
|
+
remotePath: string;
|
|
1307
|
+
fileId?: string;
|
|
1308
|
+
sha256: string;
|
|
1309
|
+
size: number;
|
|
1310
|
+
lastModified?: number | string | null;
|
|
1311
|
+
}
|
|
1312
|
+
interface MiniPullManifest {
|
|
1313
|
+
version: typeof MINI_CONTEXT_VERSION;
|
|
1314
|
+
appCode: string;
|
|
1315
|
+
workspaceKey: string;
|
|
1316
|
+
remotePrefix: string;
|
|
1317
|
+
profile?: string;
|
|
1318
|
+
pulledAt: string;
|
|
1319
|
+
files: MiniFileSnapshot[];
|
|
1320
|
+
}
|
|
1321
|
+
interface MiniLocalFile {
|
|
1322
|
+
localPath: string;
|
|
1323
|
+
absolutePath: string;
|
|
1324
|
+
content: string;
|
|
1325
|
+
sha256: string;
|
|
1326
|
+
size: number;
|
|
1327
|
+
}
|
|
1328
|
+
type MiniLocalStatus = 'added' | 'modified' | 'deleted' | 'unchanged';
|
|
1329
|
+
interface MiniStatusEntry {
|
|
1330
|
+
localPath: string;
|
|
1331
|
+
remotePath: string;
|
|
1332
|
+
status: MiniLocalStatus;
|
|
1333
|
+
fileId?: string;
|
|
1334
|
+
sha256?: string;
|
|
1335
|
+
baseSha256?: string;
|
|
1336
|
+
}
|
|
1337
|
+
interface MiniValidationResult {
|
|
1338
|
+
valid: boolean;
|
|
1339
|
+
appCode?: string;
|
|
1340
|
+
workspaceKey?: string;
|
|
1341
|
+
errors: string[];
|
|
1342
|
+
warnings: string[];
|
|
1343
|
+
}
|
|
1344
|
+
interface MiniPullResult {
|
|
1345
|
+
appCode: string;
|
|
1346
|
+
workspaceKey: string;
|
|
1347
|
+
targetDir: string;
|
|
1348
|
+
files: MiniFileSnapshot[];
|
|
1349
|
+
}
|
|
1350
|
+
type MiniPushOperation = 'create' | 'update' | 'delete' | 'skip';
|
|
1351
|
+
interface MiniPushPlanItem {
|
|
1352
|
+
operation: MiniPushOperation;
|
|
1353
|
+
localPath: string;
|
|
1354
|
+
remotePath: string;
|
|
1355
|
+
fileId?: string;
|
|
1356
|
+
reason?: string;
|
|
1357
|
+
}
|
|
1358
|
+
interface MiniPushConflict {
|
|
1359
|
+
localPath: string;
|
|
1360
|
+
remotePath: string;
|
|
1361
|
+
reason: string;
|
|
1362
|
+
}
|
|
1363
|
+
interface MiniPushResult {
|
|
1364
|
+
appCode: string;
|
|
1365
|
+
workspaceKey: string;
|
|
1366
|
+
dryRun: boolean;
|
|
1367
|
+
operations: MiniPushPlanItem[];
|
|
1368
|
+
conflicts: MiniPushConflict[];
|
|
1369
|
+
}
|
|
1370
|
+
interface MiniPreviewTarget {
|
|
1371
|
+
key: 'bare' | 'standalone' | 'embedded';
|
|
1372
|
+
name: string;
|
|
1373
|
+
url: string;
|
|
1374
|
+
}
|
|
1375
|
+
type MiniSmokeTargetKind = 'preview' | 'manifest' | 'asset';
|
|
1376
|
+
interface MiniSmokeTargetResult {
|
|
1377
|
+
kind: MiniSmokeTargetKind;
|
|
1378
|
+
surface?: MiniPreviewTarget['key'];
|
|
1379
|
+
url: string;
|
|
1380
|
+
status: number | null;
|
|
1381
|
+
ok: boolean;
|
|
1382
|
+
error?: string;
|
|
1383
|
+
}
|
|
1384
|
+
interface MiniSmokeResult {
|
|
1385
|
+
ok: boolean;
|
|
1386
|
+
checked: number;
|
|
1387
|
+
passed: number;
|
|
1388
|
+
failed: number;
|
|
1389
|
+
targets: MiniSmokeTargetResult[];
|
|
1390
|
+
}
|
|
1391
|
+
type MiniVerifyNextAction = 'push' | 'fix-validation' | 'resolve-conflict' | 'inspect-status' | 'inspect-preview';
|
|
1392
|
+
interface MiniVerifyPlan {
|
|
1393
|
+
operations: MiniPushPlanItem[];
|
|
1394
|
+
conflicts: MiniPushConflict[];
|
|
1395
|
+
}
|
|
1396
|
+
interface MiniVerifyReport {
|
|
1397
|
+
ok: boolean;
|
|
1398
|
+
validate: MiniValidationResult;
|
|
1399
|
+
status: MiniStatusEntry[];
|
|
1400
|
+
plan: MiniVerifyPlan | null;
|
|
1401
|
+
planSkipped?: string;
|
|
1402
|
+
preview: MiniPreviewTarget[];
|
|
1403
|
+
smoke?: MiniSmokeResult | null;
|
|
1404
|
+
}
|
|
1405
|
+
interface MiniVerifySummary {
|
|
1406
|
+
validate: {
|
|
1407
|
+
ok: boolean;
|
|
1408
|
+
errors: number;
|
|
1409
|
+
warnings: number;
|
|
1410
|
+
appCode?: string;
|
|
1411
|
+
workspaceKey?: string;
|
|
1412
|
+
};
|
|
1413
|
+
status: {
|
|
1414
|
+
total: number;
|
|
1415
|
+
changed: number;
|
|
1416
|
+
added: number;
|
|
1417
|
+
modified: number;
|
|
1418
|
+
deleted: number;
|
|
1419
|
+
unchanged: number;
|
|
1420
|
+
};
|
|
1421
|
+
dryRun: {
|
|
1422
|
+
ok: boolean;
|
|
1423
|
+
skipped: boolean;
|
|
1424
|
+
skippedReason?: string;
|
|
1425
|
+
operations: number;
|
|
1426
|
+
creates: number;
|
|
1427
|
+
updates: number;
|
|
1428
|
+
deletes: number;
|
|
1429
|
+
skips: number;
|
|
1430
|
+
conflicts: number;
|
|
1431
|
+
};
|
|
1432
|
+
preview: {
|
|
1433
|
+
ok: boolean;
|
|
1434
|
+
count: number;
|
|
1435
|
+
surfaces: MiniPreviewTarget['key'][];
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
interface MiniVerifyChangedFile {
|
|
1439
|
+
path: string;
|
|
1440
|
+
status: MiniLocalStatus;
|
|
1441
|
+
remotePath: string;
|
|
1442
|
+
}
|
|
1443
|
+
interface MiniVerifyPreviewUrl {
|
|
1444
|
+
surface: MiniPreviewTarget['key'];
|
|
1445
|
+
name: string;
|
|
1446
|
+
url: string;
|
|
1447
|
+
}
|
|
1448
|
+
interface MiniVerifyBrowserAcceptance {
|
|
1449
|
+
recommendedUrl?: string;
|
|
1450
|
+
expectedTexts: string[];
|
|
1451
|
+
expectedNetwork: string[];
|
|
1452
|
+
console: {
|
|
1453
|
+
failOn: Array<'error' | 'warn'>;
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
interface MiniVerifyAgentReport {
|
|
1457
|
+
ok: boolean;
|
|
1458
|
+
safeToPush: boolean;
|
|
1459
|
+
nextAction: MiniVerifyNextAction;
|
|
1460
|
+
summary: MiniVerifySummary;
|
|
1461
|
+
changedFiles: MiniVerifyChangedFile[];
|
|
1462
|
+
conflicts: MiniPushConflict[];
|
|
1463
|
+
previewUrls: MiniVerifyPreviewUrl[];
|
|
1464
|
+
browserAcceptance: MiniVerifyBrowserAcceptance;
|
|
1465
|
+
smoke: MiniSmokeResult | null;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
declare function listWorkspaceFiles(http: AxiosInstance, workspaceKey: string, options?: {
|
|
1469
|
+
pageSize?: number;
|
|
1470
|
+
}): Promise<WorkspaceFileListResult>;
|
|
1471
|
+
declare function loadWorkspaceFileContent(http: AxiosInstance, fileId: string): Promise<WorkspaceFileContent>;
|
|
1472
|
+
declare function saveWorkspaceFileContent(http: AxiosInstance, fileId: string, content: string): Promise<WorkspaceFileItem>;
|
|
1473
|
+
declare function createWorkspaceFile(http: AxiosInstance, workspaceKey: string, relativePath: string, content: string): Promise<WorkspaceFileItem>;
|
|
1474
|
+
declare function deleteWorkspaceFile(http: AxiosInstance, fileId: string): Promise<void>;
|
|
1475
|
+
|
|
1476
|
+
interface MiniInitArgs {
|
|
1477
|
+
appCode: string;
|
|
1478
|
+
workspaceKey: string;
|
|
1479
|
+
targetDir: string;
|
|
1480
|
+
name?: string;
|
|
1481
|
+
defaultSurface?: MiniSurface;
|
|
1482
|
+
templateKind?: MiniTemplateKind;
|
|
1483
|
+
writableModel?: string;
|
|
1484
|
+
writableFields?: string[] | null;
|
|
1485
|
+
writableDefaults?: Record<string, unknown>;
|
|
1486
|
+
writableTodayDefaultFields?: string[];
|
|
1487
|
+
requiredScopes?: string[];
|
|
1488
|
+
tenantId?: string;
|
|
1489
|
+
endpoint?: string;
|
|
1490
|
+
profile?: string;
|
|
1491
|
+
force?: boolean;
|
|
1492
|
+
}
|
|
1493
|
+
interface MiniPullArgs {
|
|
1494
|
+
http: AxiosInstance;
|
|
1495
|
+
appCode: string;
|
|
1496
|
+
workspaceKey: string;
|
|
1497
|
+
targetDir: string;
|
|
1498
|
+
tenantId?: string;
|
|
1499
|
+
endpoint?: string;
|
|
1500
|
+
profile?: string;
|
|
1501
|
+
force?: boolean;
|
|
1502
|
+
}
|
|
1503
|
+
interface MiniPushArgs {
|
|
1504
|
+
http: AxiosInstance;
|
|
1505
|
+
rootDir: string;
|
|
1506
|
+
dryRun?: boolean;
|
|
1507
|
+
}
|
|
1508
|
+
declare function initMiniAppProject(args: MiniInitArgs): Promise<MiniPullResult>;
|
|
1509
|
+
declare function pullMiniAppProject(args: MiniPullArgs): Promise<MiniPullResult>;
|
|
1510
|
+
declare function getMiniStatus(rootDir: string): Promise<MiniStatusEntry[]>;
|
|
1511
|
+
declare function validateMiniProject(rootDir: string): Promise<MiniValidationResult>;
|
|
1512
|
+
declare function pushMiniAppProject(args: MiniPushArgs): Promise<MiniPushResult>;
|
|
1513
|
+
declare function buildMiniPreviewTargets(rootDir: string, frontendBaseUrl: string): Promise<MiniPreviewTarget[]>;
|
|
1514
|
+
interface MiniSmokeFetchResponse {
|
|
1515
|
+
status: number;
|
|
1516
|
+
}
|
|
1517
|
+
type MiniSmokeFetch = (url: string, init?: {
|
|
1518
|
+
headers?: Record<string, string>;
|
|
1519
|
+
method: 'GET';
|
|
1520
|
+
signal?: AbortSignal;
|
|
1521
|
+
}) => Promise<MiniSmokeFetchResponse>;
|
|
1522
|
+
interface MiniSmokeCheckArgs {
|
|
1523
|
+
rootDir: string;
|
|
1524
|
+
frontendBaseUrl: string;
|
|
1525
|
+
previewTargets: MiniPreviewTarget[];
|
|
1526
|
+
fetcher?: MiniSmokeFetch;
|
|
1527
|
+
accessToken?: string | null;
|
|
1528
|
+
timeoutMs?: number;
|
|
1529
|
+
}
|
|
1530
|
+
declare function runMiniSmokeChecks(args: MiniSmokeCheckArgs): Promise<MiniSmokeResult>;
|
|
1531
|
+
declare function buildMiniVerifyAgentReport(report: MiniVerifyReport): MiniVerifyAgentReport;
|
|
1532
|
+
declare function formatMiniVerifyReport(report: MiniVerifyReport): string;
|
|
1533
|
+
declare function readMiniContext(rootDir: string): Promise<MiniProjectContext>;
|
|
1534
|
+
declare function readPullManifest(rootDir: string): Promise<MiniPullManifest>;
|
|
1535
|
+
|
|
1536
|
+
interface MiniTemplateArgs {
|
|
1537
|
+
appCode: string;
|
|
1538
|
+
name?: string;
|
|
1539
|
+
defaultSurface: MiniSurface;
|
|
1540
|
+
requiredScopes: string[];
|
|
1541
|
+
tenantId: string;
|
|
1542
|
+
templateKind?: MiniTemplateKind;
|
|
1543
|
+
writableModel?: string;
|
|
1544
|
+
writableFields?: string[] | null;
|
|
1545
|
+
writableDefaults?: Record<string, unknown>;
|
|
1546
|
+
writableTodayDefaultFields?: string[];
|
|
1547
|
+
}
|
|
1548
|
+
interface MiniWritableTemplateConfig {
|
|
1549
|
+
model?: string;
|
|
1550
|
+
fields?: string[] | null;
|
|
1551
|
+
defaults?: Record<string, unknown>;
|
|
1552
|
+
todayDefaultFields?: string[];
|
|
1553
|
+
}
|
|
1554
|
+
declare function defaultMiniRequiredScopes(templateKind?: MiniTemplateKind, writableConfig?: MiniWritableTemplateConfig): string[];
|
|
1555
|
+
declare function buildMiniAppTemplateFiles(args: MiniTemplateArgs): Array<{
|
|
1556
|
+
localPath: string;
|
|
1557
|
+
content: string;
|
|
1558
|
+
}>;
|
|
1559
|
+
declare function inferMiniWritableTemplateOptionsFromModel(description: ModelDescription): {
|
|
1560
|
+
fields: string[];
|
|
1561
|
+
defaults: Record<string, unknown>;
|
|
1562
|
+
todayDefaultFields: string[];
|
|
1563
|
+
};
|
|
1564
|
+
|
|
1565
|
+
/**
|
|
1566
|
+
* 模型缓存运维(两级缓存:Caffeine 本地 + Redis 远程)。
|
|
1567
|
+
*
|
|
1568
|
+
* 后端契约(CommonModelQueryCacheController):
|
|
1569
|
+
* - `GET /api/models/{model}/cache` 统计
|
|
1570
|
+
* - `DELETE /api/models/{model}/cache` 清全部实体缓存
|
|
1571
|
+
* - `DELETE /api/models/{model}/cache/{id}` 清单条
|
|
1572
|
+
* - `DELETE /api/models/{model}/meta/cache` 清元数据缓存(Code 级 YAML + Action 定义)
|
|
1573
|
+
*
|
|
1574
|
+
* 🔴 缓存必须走 API 管理,不直接操作 Redis —— 保证一致性、审计与业务规则生效。
|
|
1575
|
+
*/
|
|
1576
|
+
interface CacheStats {
|
|
1577
|
+
modelKey: string;
|
|
1578
|
+
[k: string]: unknown;
|
|
1579
|
+
}
|
|
1580
|
+
interface CacheClearResult {
|
|
1581
|
+
modelKey: string;
|
|
1582
|
+
clearedCount?: number;
|
|
1583
|
+
message?: string;
|
|
1584
|
+
[k: string]: unknown;
|
|
1585
|
+
}
|
|
1586
|
+
declare function getCacheStats(http: AxiosInstance, model: string): Promise<CacheStats>;
|
|
1587
|
+
declare function clearModelCache(http: AxiosInstance, model: string, id?: string): Promise<CacheClearResult>;
|
|
1588
|
+
interface ClearMetaCacheOpts {
|
|
1589
|
+
/** 元数据类型(list / info / view / filter / page…),省略 = 全清 */
|
|
1590
|
+
type?: string;
|
|
1591
|
+
/** 状态变体(如 self),配合 type 使用 */
|
|
1592
|
+
state?: string;
|
|
1593
|
+
}
|
|
1594
|
+
declare function clearMetaCache(http: AxiosInstance, model: string, opts?: ClearMetaCacheOpts): Promise<CacheClearResult>;
|
|
1595
|
+
|
|
1596
|
+
/**
|
|
1597
|
+
* 租户开通(bootstrap)—— 交付旅程第一步。
|
|
1598
|
+
*
|
|
1599
|
+
* 后端契约(TenantBootstrapController):
|
|
1600
|
+
* - `POST /api/bootstrap/tenant`,header `X-Bootstrap-Key: <secret>`
|
|
1601
|
+
* - body `{ tenantId, adminUsername, adminPassword, tenantName? }`
|
|
1602
|
+
*
|
|
1603
|
+
* 🔴 该端点不走普通 Bearer 认证,用独立的 bootstrap key —— 它要在「还没有任何
|
|
1604
|
+
* 租户和用户」的时刻可用。key 由部署方持有,绝不写进 skill 正文或提交进仓库。
|
|
1605
|
+
*/
|
|
1606
|
+
interface BootstrapTenantRequest {
|
|
1607
|
+
tenantId: string;
|
|
1608
|
+
adminUsername: string;
|
|
1609
|
+
adminPassword: string;
|
|
1610
|
+
tenantName?: string;
|
|
1611
|
+
}
|
|
1612
|
+
interface BootstrapReconcileResult {
|
|
1613
|
+
component?: string;
|
|
1614
|
+
version?: number;
|
|
1615
|
+
status?: string;
|
|
1616
|
+
[k: string]: unknown;
|
|
1617
|
+
}
|
|
1618
|
+
interface BootstrapTenantResult {
|
|
1619
|
+
success: boolean;
|
|
1620
|
+
tenantId: string;
|
|
1621
|
+
adminUserId?: string;
|
|
1622
|
+
message?: string;
|
|
1623
|
+
results?: BootstrapReconcileResult[];
|
|
1624
|
+
}
|
|
1625
|
+
declare function bootstrapTenant(http: AxiosInstance, bootstrapKey: string, req: BootstrapTenantRequest): Promise<BootstrapTenantResult>;
|
|
1626
|
+
|
|
1627
|
+
/**
|
|
1628
|
+
* 租户元数据读写(TenantMetaController,`/api/tenant-meta`)。
|
|
1629
|
+
*
|
|
1630
|
+
* 统一走 query-param 形态(`?path=`)而非路径通配 `/meta/**`:meta 路径含斜杠与点,
|
|
1631
|
+
* query param 避免 URL 段解析歧义与路径穿越面。
|
|
1632
|
+
*
|
|
1633
|
+
* 后端契约:
|
|
1634
|
+
* - `GET /api/tenant-meta/list?keyword=&type=&page=&pageSize=`
|
|
1635
|
+
* - `GET /api/tenant-meta/meta?path=<metaName>`
|
|
1636
|
+
* - `PUT /api/tenant-meta/meta?path=<metaName>` body `{ content: "<原文>" }`
|
|
1637
|
+
* - `DELETE /api/tenant-meta/meta?path=<metaName>`
|
|
1638
|
+
*
|
|
1639
|
+
* 🔴 写入需 config-admin 身份,且 source 必须可写(租户开发空间绑定态)——
|
|
1640
|
+
* 后端 `assertConfigAdmin` / `assertWritableSource` 会拒绝越权与只读源。
|
|
1641
|
+
*/
|
|
1642
|
+
interface TenantMetaEntry {
|
|
1643
|
+
metaName?: string;
|
|
1644
|
+
name?: string;
|
|
1645
|
+
type?: string;
|
|
1646
|
+
lastModified?: string;
|
|
1647
|
+
size?: number;
|
|
1648
|
+
[k: string]: unknown;
|
|
1649
|
+
}
|
|
1650
|
+
interface ListTenantMetaOpts {
|
|
1651
|
+
keyword?: string;
|
|
1652
|
+
/** yml / properties / all(默认 all) */
|
|
1653
|
+
type?: string;
|
|
1654
|
+
page?: number;
|
|
1655
|
+
pageSize?: number;
|
|
1656
|
+
}
|
|
1657
|
+
declare function listTenantMeta(http: AxiosInstance, opts?: ListTenantMetaOpts): Promise<{
|
|
1658
|
+
items: TenantMetaEntry[];
|
|
1659
|
+
total?: number;
|
|
1660
|
+
raw: unknown;
|
|
1661
|
+
}>;
|
|
1662
|
+
/** 读单份 meta 原文。返回 `content` 字符串(后端包在信封里时自动解包)。 */
|
|
1663
|
+
declare function getTenantMeta(http: AxiosInstance, path: string): Promise<string>;
|
|
1664
|
+
interface SaveMetaResult {
|
|
1665
|
+
success?: boolean;
|
|
1666
|
+
message?: string;
|
|
1667
|
+
[k: string]: unknown;
|
|
1668
|
+
}
|
|
1669
|
+
declare function saveTenantMeta(http: AxiosInstance, path: string, content: string): Promise<SaveMetaResult>;
|
|
1670
|
+
declare function deleteTenantMeta(http: AxiosInstance, path: string): Promise<SaveMetaResult>;
|
|
1671
|
+
|
|
1672
|
+
interface HttpClientOptions {
|
|
1673
|
+
endpoint: string;
|
|
1674
|
+
getAccessToken: () => Promise<string | null>;
|
|
1675
|
+
onRefresh: () => Promise<void>;
|
|
1676
|
+
timeoutMs?: number;
|
|
1677
|
+
userAgent?: string;
|
|
1678
|
+
}
|
|
1679
|
+
declare function createHttpClient(opts: HttpClientOptions): AxiosInstance;
|
|
1680
|
+
|
|
1681
|
+
/**
|
|
1682
|
+
* 睿戎 Chiron 交付技能库客户端。
|
|
1683
|
+
*
|
|
1684
|
+
* <p>🔴 这些端点**免认证**——交付顾问接手的是「还没有租户、还没有账号」的空环境,
|
|
1685
|
+
* 必须先装技能才能建租户。所以本模块只需要 endpoint,不碰 identity/token。
|
|
1686
|
+
*
|
|
1687
|
+
* 后端契约(ChironController):
|
|
1688
|
+
* - `GET /skills.json` 目录 manifest
|
|
1689
|
+
* - `GET /skills/{id}.md` 技能原文
|
|
1690
|
+
*/
|
|
1691
|
+
interface ChironSkill {
|
|
1692
|
+
id: string;
|
|
1693
|
+
title: string;
|
|
1694
|
+
category: string;
|
|
1695
|
+
/** cli = 动作面已全改写为 hcm 命令;curl = 仍为原始 HTTP 形态 */
|
|
1696
|
+
actionSurface: 'cli' | 'curl';
|
|
1697
|
+
description: string;
|
|
1698
|
+
/** 交付生命周期阶段(多值)——一个技能天然跨阶段 */
|
|
1699
|
+
stages: string[];
|
|
1700
|
+
bytes: number;
|
|
1701
|
+
markdownUrl: string;
|
|
1702
|
+
}
|
|
1703
|
+
interface ChironStage {
|
|
1704
|
+
key: string;
|
|
1705
|
+
name: string;
|
|
1706
|
+
description: string;
|
|
1707
|
+
/** 该阶段下的技能数。0 = 交付路径上的覆盖缺口 */
|
|
1708
|
+
skillCount: number;
|
|
1709
|
+
}
|
|
1710
|
+
interface ChironCategory {
|
|
1711
|
+
key: string;
|
|
1712
|
+
name: string;
|
|
1713
|
+
description: string;
|
|
1714
|
+
skills: ChironSkill[];
|
|
1715
|
+
}
|
|
1716
|
+
interface ChironManifest {
|
|
1717
|
+
name: string;
|
|
1718
|
+
title: string;
|
|
1719
|
+
tagline: string;
|
|
1720
|
+
skillCount: number;
|
|
1721
|
+
stages: ChironStage[];
|
|
1722
|
+
categories: ChironCategory[];
|
|
1723
|
+
}
|
|
1724
|
+
declare function fetchSkillCatalog(http: AxiosInstance): Promise<ChironManifest>;
|
|
1725
|
+
declare function fetchSkillMarkdown(http: AxiosInstance, id: string): Promise<string>;
|
|
1726
|
+
declare function flattenSkills(manifest: ChironManifest): ChironSkill[];
|
|
1727
|
+
/** 按交付生命周期阶段筛选。 */
|
|
1728
|
+
declare function filterByStage(skills: ChironSkill[], stage?: string): ChironSkill[];
|
|
1729
|
+
/** 关键词匹配 id / 标题 / 描述 / 分类,全部小写包含。 */
|
|
1730
|
+
declare function matchSkills(skills: ChironSkill[], keyword?: string): ChironSkill[];
|
|
1731
|
+
|
|
1732
|
+
/**
|
|
1733
|
+
* 租户系统参数(配置中心)。
|
|
1734
|
+
*
|
|
1735
|
+
* <p>后端契约(TenantSettingDomainController):
|
|
1736
|
+
* <ul>
|
|
1737
|
+
* <li>`GET /api/tenant-settings/domains/{domain}` 读某域(定义 + 租户覆盖值合并,缺失回退默认值)</li>
|
|
1738
|
+
* <li>`PATCH /api/tenant-settings/domains/{domain}` 写,body `{items:[{namespace,settingKey,value}]}`</li>
|
|
1739
|
+
* <li>`DELETE /api/tenant-settings/domains/{domain}/items/{ns}/{key}?revision=N` 清除租户覆盖</li>
|
|
1740
|
+
* </ul>
|
|
1741
|
+
*
|
|
1742
|
+
* <p>🔴 **不要走标准 Model 的 PUT 改这些值**:那条路直接写库,绕过定义校验、
|
|
1743
|
+
* 乐观锁与历史留痕——改坏 namespace/valueType 会让记录不再匹配任何定义。
|
|
1744
|
+
* 后端已从该 Model 的权限里移除 update action,这里也只暴露上面三条受控路径。
|
|
1745
|
+
*/
|
|
1746
|
+
interface SettingItem {
|
|
1747
|
+
namespace: string;
|
|
1748
|
+
settingKey: string;
|
|
1749
|
+
value?: unknown;
|
|
1750
|
+
valueType?: string;
|
|
1751
|
+
defaultValue?: unknown;
|
|
1752
|
+
revision?: number;
|
|
1753
|
+
label?: string;
|
|
1754
|
+
[k: string]: unknown;
|
|
1755
|
+
}
|
|
1756
|
+
declare function getSettingDomain(http: AxiosInstance, domain: string): Promise<{
|
|
1757
|
+
items: SettingItem[];
|
|
1758
|
+
raw: unknown;
|
|
1759
|
+
}>;
|
|
1760
|
+
interface SettingWrite {
|
|
1761
|
+
namespace: string;
|
|
1762
|
+
settingKey: string;
|
|
1763
|
+
value: unknown;
|
|
1764
|
+
}
|
|
1765
|
+
declare function patchSettingDomain(http: AxiosInstance, domain: string, items: SettingWrite[]): Promise<unknown>;
|
|
1766
|
+
declare function resetSettingItem(http: AxiosInstance, domain: string, namespace: string, settingKey: string, revision?: number): Promise<unknown>;
|
|
1767
|
+
/**
|
|
1768
|
+
* 解析 `namespace.settingKey=value` 形态的 --set 参数。
|
|
1769
|
+
*
|
|
1770
|
+
* settingKey 取最后一段,其余是 namespace —— namespace 本身含点
|
|
1771
|
+
* (如 `security.password`),所以从右边切,不能 split('.') 取前两段。
|
|
1772
|
+
*/
|
|
1773
|
+
declare function parseSettingAssignment(raw: string): SettingWrite;
|
|
1774
|
+
|
|
1775
|
+
export { type ActionHttpMethod, type ActionInput, type ActionMeta, type ActionResult, type ActionScope, type AnyStreamEvent, type ApprovalConfirmPayload, type ApprovalRejectPayload, type AttachmentMeta, type AuthScheme, type BootstrapReconcileResult, type BootstrapTenantRequest, type BootstrapTenantResult, type CacheClearResult, type CacheStats, type ChangePasswordInput, type ChironCategory, type ChironManifest, type ChironSkill, type ChironStage, type ClearMetaCacheOpts, CliError, CliErrorCode, type CliErrorInit, type ClientAuthContext, type ClientCredentialsInput, type ConnectionMessage, type ControlChannelEnvelope, type ConversationListItem, type ConversationListResult, type ConversationState, type CreateResult, type CreatedConversation, DEFAULT_AGENT_ID, DEFAULT_SESSION_TTL_SECONDS, type DescribeOpts, type EnvConfig, ExitCode, type FieldMeta, type Fixture, type FormatOptions, type GlobalConfig, HcmClient, type HcmClientOpts, type HttpClientOptions, type IdentityMeta, type ImportClient, type ImportOptions, type ImportResult, type InteractionAnswerPayload, type InteractionRequest, type InteractionResolved, type ListTenantMetaOpts, type LoginOutcome, MINI_CONTEXT_FILE, MINI_CONTEXT_KIND, MINI_CONTEXT_VERSION, MINI_PULL_MANIFEST_FILE, MINI_REMOTE_ROOT, MINI_STATE_DIR, type MessageClass, type MessageCreatePayload, type MessageStreamHandle, type MigrationSummary, type MiniFileSnapshot, type MiniInitArgs, type MiniLocalFile, type MiniLocalStatus, type MiniPreviewTarget, type MiniProjectContext, type MiniPullArgs, type MiniPullManifest, type MiniPullResult, type MiniPushArgs, type MiniPushConflict, type MiniPushOperation, type MiniPushPlanItem, type MiniPushResult, type MiniSmokeCheckArgs, type MiniSmokeFetch, type MiniSmokeFetchResponse, type MiniSmokeResult, type MiniSmokeTargetKind, type MiniSmokeTargetResult, type MiniStatusEntry, type MiniSurface, type MiniTemplateArgs, type MiniTemplateKind, type MiniValidationResult, type MiniVerifyAgentReport, type MiniVerifyBrowserAcceptance, type MiniVerifyChangedFile, type MiniVerifyNextAction, type MiniVerifyPlan, type MiniVerifyPreviewUrl, type MiniVerifyReport, type MiniVerifySummary, type MiniWritableTemplateConfig, type ModelDescription, type ModelQueryDsl, type OneShotOpts, type OneShotResult, type OneShotToolCall, type OutputFormat, PASSWORD_CHANGE_REQUIRED, PROACTIVE_REFRESH_MARGIN_SECONDS, type PairingLoginInput, type ParsedPlaceholder, type PasswordChangeChallenge, type PasswordLoginInput, type PatLoginInput, type Principal, type ProfileConfig, type QueryResult, RefStore, type RefreshInput, type RelationMeta, type RemoveResult, type ResponseCompletedPayload, type ResponseFailedPayload, type ResponsePartDeltaPayload, type ResponseStartedPayload, type ResumeSummary, type ResumeTurn, type RowResult, type RowStatus, SDK_VERSION, type SaveMetaResult, type SendMessageOpts, type SettingItem, type SettingWrite, type StreamCancelPayload, type StreamCommand, type StreamCommandEnvelope, type StreamCommandType, type StreamEventEnvelope, type StreamEventType, type TaskProgressPayload, type TenantMetaEntry, type TimelineItemLite, type TokenRecord, TokenStore, type ToolCallFailedPayload, type ToolCallResultPayload, type ToolCallStartedPayload, type V4RenderSink, type V4StreamEventType, type WorkspaceFileContent, type WorkspaceFileItem, type WorkspaceFileListResult, WsClient, type WsClientOpts, absoluteDownloadUrl, archiveDir, bootstrapTenant, buildAnswer, buildConfirm, buildInterrupt, buildMiniAppTemplateFiles, buildMiniPreviewTargets, buildMiniVerifyAgentReport, buildReject, buildResumeSummary, buildSteer, camelizeKeys, changePassword, classifyMessage, clearMetaCache, clearModelCache, conversationStateFile, create, createConversation, createHttpClient, createV4Reducer, createWorkspaceFile, credentialsFile, defaultDownloadDir, defaultMiniRequiredScopes, deleteEnv, deleteIdentity, deleteTenantMeta, deleteWorkspaceFile, deriveNamelessToolLabel, describePrincipal, detectDelegationPause, detectDirectInteractionPending, detectPasswordChangeChallenge, displayWidth, downloadDocument, ensureSessionFresh, envDir, envFile, envsDir, exitCodeFor, extractNextSteps, fetchConversationTimeline, fetchConversationTimelineStrict, fetchRecentConversations, fetchSkillCatalog, fetchSkillMarkdown, filterByStage, findLastAssistantSeq, flattenSkills, formatMiniVerifyReport, formatObject, formatRows, fromAxiosError, getCacheStats, getMiniStatus, getSettingDomain, getTenantMeta, globalConfigFile, guessMimeType, hcmConfigDir, identitiesDir, identityDir, identityMetaFile, inferEnvName, inferMiniWritableTemplateOptionsFromModel, initMiniAppProject, isDelegationToolName, isServerSlidingSession, listEnvs, listIdentities, listProfiles, listTenantMeta, listWorkspaceFiles, loadConversationState, loadEnv, loadGlobalConfig, loadIdentity, loadProfile, loadWorkspaceFileContent, loginClientCredentials, loginPairing, loginPassword, loginPat, matchSkills, migrateLegacyProfiles, needsRefresh, oneShot, parseConfirmToolName, parseFixture, parseInteractionRequest, parseInteractionResolved, parsePlaceholder, parseSettingAssignment, patchSettingDomain, profileDir, profileFile, pullMiniAppProject, pushMiniAppProject, readMiniContext, readPullManifest, refreshToken, remove, replHistoryFile, resetSettingItem, resolveActiveEnv, resolveActiveIdentity, resolveActiveProfile, resolveRefs, runImport, runMiniSmokeChecks, saveConversationState, saveEnv, saveGlobalConfig, saveIdentity, saveProfile, saveTenantMeta, saveWorkspaceFileContent, sendMessageAndStream, snakeToCamel, toJson, toOrigin, toPrincipal, toTable, toYaml, truncateDisplay, update, uploadDocument, validateMiniProject };
|