@margay/ccl-core 1.5.1-alpha.2 → 1.5.1-alpha.3
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/dist/cli.js +3189 -3010
- package/dist/sdk/cancellation.d.ts +15 -0
- package/dist/sdk/cancellation.js +18 -0
- package/dist/sdk/index.d.ts +1 -0
- package/dist/sdk/query.d.ts +1 -0
- package/dist/sdk/query.js +9 -0
- package/dist/sdk/queryHandle.js +28 -8
- package/dist/sdk/queryLifecycleTypes.d.ts +2 -2
- package/dist/sdk/types.d.ts +4 -1
- package/docs/releases/1.5.1-alpha.3.md +23 -0
- package/package.json +3 -2
- package/sdk/API.md +60 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
/** Control-channel provenance, not the identity or intent of a person. */
|
|
3
|
+
export declare const hostCancellationSchema: z.ZodObject<{
|
|
4
|
+
origin: z.ZodLiteral<"host">;
|
|
5
|
+
reason: z.ZodEnum<{
|
|
6
|
+
cancelled: "cancelled";
|
|
7
|
+
timeout: "timeout";
|
|
8
|
+
connection_closed: "connection_closed";
|
|
9
|
+
callback_error: "callback_error";
|
|
10
|
+
permission_denied: "permission_denied";
|
|
11
|
+
}>;
|
|
12
|
+
}, z.core.$strict>;
|
|
13
|
+
export type HostCancellation = z.infer<typeof hostCancellationSchema>;
|
|
14
|
+
export declare function readHostCancellation(value: unknown): HostCancellation | undefined;
|
|
15
|
+
export declare function hostCancellationMessage(cancellation: HostCancellation, execution?: 'not_started' | 'unknown'): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
/** Control-channel provenance, not the identity or intent of a person. */
|
|
3
|
+
export const hostCancellationSchema = z.object({
|
|
4
|
+
origin: z.literal('host'),
|
|
5
|
+
reason: z.enum(['cancelled', 'timeout', 'connection_closed', 'callback_error', 'permission_denied']),
|
|
6
|
+
}).strict();
|
|
7
|
+
export function readHostCancellation(value) {
|
|
8
|
+
const parsed = hostCancellationSchema.safeParse(value);
|
|
9
|
+
return parsed.success ? parsed.data : undefined;
|
|
10
|
+
}
|
|
11
|
+
export function hostCancellationMessage(cancellation, execution = 'unknown') {
|
|
12
|
+
return `Host control channel ended this turn (${JSON.stringify(cancellation)}). ` +
|
|
13
|
+
(cancellation.reason === 'permission_denied'
|
|
14
|
+
? 'The host ended the turn after a permission denial; preserve the recorded decision without inferring who made it. '
|
|
15
|
+
: 'This lifecycle cancellation is not a user permission refusal or a user preference. ') +
|
|
16
|
+
(execution === 'not_started' ? 'This tool call was not executed. ' : 'A missing result does not establish whether an in-flight operation had effects. ') +
|
|
17
|
+
'Stop this turn. On a later authorized continuation, retain completed results and check existing execution evidence before repeating side effects. This cancellation grants no permission and does not revoke unrelated existing authorization.';
|
|
18
|
+
}
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { buildCliArgs, buildCliEnv, decideFailClosed, getSessionUsage, prepareSk
|
|
|
9
9
|
export { query, queryHandleWithTransport } from './queryHandle.js';
|
|
10
10
|
export { createSession, Session, type SessionOptions, type SessionControls, type Turn, type TurnResult, type InputMetadata } from './session.js';
|
|
11
11
|
export type { InputProvenance } from './inputProvenance.js';
|
|
12
|
+
export type { HostCancellation } from './cancellation.js';
|
|
12
13
|
export { readQueryLifecycle, type QueryLifecycleRead } from './queryLifecycle.js';
|
|
13
14
|
export type { QueryLifecycleRecord, QueryLifecycleObservation, QueryLifecycleWrite } from './queryLifecycleTypes.js';
|
|
14
15
|
export { RuntimeResourcesSchema, RuntimeResourceSchema, type RuntimeResources, type RuntimeResource } from './runtimeResources.js';
|
package/dist/sdk/query.d.ts
CHANGED
package/dist/sdk/query.js
CHANGED
|
@@ -34,6 +34,7 @@ import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, statSync, sym
|
|
|
34
34
|
import { createHash } from 'crypto';
|
|
35
35
|
import { describeError, errorDetailsList, sanitizeErrorEnvelope, sanitizeErrorText, sanitizeRecoveryDiagnostic, sanitizeRecoveryOutcome } from './errorDiagnostics.js';
|
|
36
36
|
import { interactionResponseSchema } from './interactionResponseSchema.js';
|
|
37
|
+
import { hostCancellationSchema } from './cancellation.js';
|
|
37
38
|
import { tmpdir } from 'os';
|
|
38
39
|
import { dirname, join, resolve } from 'path';
|
|
39
40
|
import { fileURLToPath } from 'url';
|
|
@@ -196,6 +197,7 @@ export async function decideFailClosed(callback, toolName, input, context, timeo
|
|
|
196
197
|
const deny = (message, failClosedReason) => ({
|
|
197
198
|
kind: 'decision',
|
|
198
199
|
decision: { behavior: 'deny', message, toolUseID: context.toolUseId,
|
|
200
|
+
cancellation: { origin: 'host', reason: timedOut ? 'timeout' : 'callback_error' },
|
|
199
201
|
...(toolName === 'AskUserQuestion' ? { interaction: { outcome: timedOut ? 'timed_out' : 'interaction_unavailable' } } : {}),
|
|
200
202
|
},
|
|
201
203
|
failClosedReason,
|
|
@@ -239,6 +241,10 @@ export async function decideFailClosed(callback, toolName, input, context, timeo
|
|
|
239
241
|
};
|
|
240
242
|
}
|
|
241
243
|
case 'deny': {
|
|
244
|
+
const cancellation = hostCancellationSchema.safeParse(raw.cancellation);
|
|
245
|
+
if (raw.cancellation !== undefined && !cancellation.success) {
|
|
246
|
+
return deny('Cancellation provenance was invalid; no user choice was inferred.', 'unrecognized_result');
|
|
247
|
+
}
|
|
242
248
|
const interaction = interactionResponseSchema.safeParse(raw.interaction);
|
|
243
249
|
if (raw.interaction !== undefined && !interaction.success) {
|
|
244
250
|
return deny('Interaction response was invalid; no user choice was inferred.', 'unrecognized_result');
|
|
@@ -250,6 +256,8 @@ export async function decideFailClosed(callback, toolName, input, context, timeo
|
|
|
250
256
|
message: sanitizeErrorText(raw.message ?? 'Denied by host'),
|
|
251
257
|
...(toolName === 'AskUserQuestion' && interaction.success ? { interaction: interaction.data } : {}),
|
|
252
258
|
...(raw.interrupt !== undefined && { interrupt: raw.interrupt }),
|
|
259
|
+
...(cancellation.success ? { cancellation: cancellation.data } : raw.interrupt
|
|
260
|
+
? { cancellation: { origin: 'host', reason: 'permission_denied' } } : {}),
|
|
253
261
|
toolUseID: context.toolUseId,
|
|
254
262
|
},
|
|
255
263
|
};
|
|
@@ -699,6 +707,7 @@ export async function* queryWithTransport(prompt, options, transport) {
|
|
|
699
707
|
toolUseId,
|
|
700
708
|
behavior: outcome.decision.behavior,
|
|
701
709
|
...(outcome.decision.behavior === 'deny' && outcome.decision.interaction ? { interaction: outcome.decision.interaction } : {}),
|
|
710
|
+
...(outcome.decision.behavior === 'deny' && outcome.decision.cancellation ? { cancellation: outcome.decision.cancellation } : {}),
|
|
702
711
|
source: 'callback',
|
|
703
712
|
...(outcome.failClosedReason && {
|
|
704
713
|
failClosedReason: outcome.failClosedReason,
|
package/dist/sdk/queryHandle.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { queryWithTransport, spawnForOptions } from './query.js';
|
|
27
27
|
import { isAsyncInput } from './queryInput.js';
|
|
28
|
+
import { hostCancellationSchema } from './cancellation.js';
|
|
28
29
|
let requestCounter = 0;
|
|
29
30
|
const nextRequestId = () => `ccl-sdk-req-${++requestCounter}-${Math.random().toString(36).slice(2, 10)}`;
|
|
30
31
|
class QueryHandle {
|
|
@@ -81,14 +82,15 @@ class QueryHandle {
|
|
|
81
82
|
// The init promise is settled internally; hosts that never ask for the
|
|
82
83
|
// initialization result must not see an unhandled rejection.
|
|
83
84
|
this.initPromise.catch(() => { });
|
|
84
|
-
|
|
85
|
+
const retired = () => this.cancelPermission?.({ origin: 'host', reason: 'connection_closed' });
|
|
86
|
+
void transport.exited.then(retired, retired);
|
|
85
87
|
const callback = options.canUseTool;
|
|
86
88
|
void this.pump(prompt, callback ? {
|
|
87
89
|
...options,
|
|
88
90
|
canUseTool: async (...args) => {
|
|
89
91
|
let cancel;
|
|
90
92
|
const cancelled = new Promise(resolve => {
|
|
91
|
-
cancel =
|
|
93
|
+
cancel = cancellation => resolve({ behavior: 'deny', message: 'Permission wait cancelled by host', interrupt: true, cancellation });
|
|
92
94
|
});
|
|
93
95
|
this.cancelPermission = cancel;
|
|
94
96
|
try {
|
|
@@ -220,9 +222,12 @@ class QueryHandle {
|
|
|
220
222
|
if (response.subtype === 'error') {
|
|
221
223
|
entry.reject(new Error(String(response.error ?? 'control request failed')));
|
|
222
224
|
}
|
|
223
|
-
else {
|
|
225
|
+
else if (response.subtype === 'success') {
|
|
224
226
|
entry.resolve(response.response);
|
|
225
227
|
}
|
|
228
|
+
else {
|
|
229
|
+
entry.reject(new Error('ccl-sdk: invalid control acknowledgement'));
|
|
230
|
+
}
|
|
226
231
|
}
|
|
227
232
|
trackInitialize(requestId) {
|
|
228
233
|
this.pending.set(requestId, {
|
|
@@ -278,19 +283,34 @@ class QueryHandle {
|
|
|
278
283
|
return Promise.reject(new Error(this.endReason));
|
|
279
284
|
const requestId = nextRequestId();
|
|
280
285
|
return new Promise((resolve, reject) => {
|
|
281
|
-
|
|
286
|
+
const timer = opts.timeoutMs ? setTimeout(() => {
|
|
287
|
+
const entry = this.pending.get(requestId);
|
|
288
|
+
this.pending.delete(requestId);
|
|
289
|
+
entry?.reject(new Error('ccl-sdk: interrupt acknowledgement timed out; stop outcome is unknown'));
|
|
290
|
+
}, opts.timeoutMs) : undefined;
|
|
291
|
+
this.pending.set(requestId, {
|
|
292
|
+
resolve: value => { if (timer)
|
|
293
|
+
clearTimeout(timer); resolve(value); },
|
|
294
|
+
reject: error => { if (timer)
|
|
295
|
+
clearTimeout(timer); reject(error); },
|
|
296
|
+
});
|
|
282
297
|
this.transport
|
|
283
298
|
.write({ type: 'control_request', request_id: requestId, request })
|
|
284
299
|
.catch(error => {
|
|
285
300
|
if (this.pending.delete(requestId)) {
|
|
301
|
+
if (timer)
|
|
302
|
+
clearTimeout(timer);
|
|
286
303
|
reject(error instanceof Error ? error : new Error(String(error)));
|
|
287
304
|
}
|
|
288
305
|
});
|
|
289
306
|
});
|
|
290
307
|
}
|
|
291
|
-
interrupt() {
|
|
292
|
-
|
|
293
|
-
|
|
308
|
+
interrupt(cancellation = { origin: 'host', reason: 'cancelled' }) {
|
|
309
|
+
const parsed = hostCancellationSchema.safeParse(cancellation);
|
|
310
|
+
if (!parsed.success)
|
|
311
|
+
return Promise.reject(new Error('ccl-sdk: invalid cancellation provenance'));
|
|
312
|
+
this.cancelPermission?.(parsed.data);
|
|
313
|
+
return this.controlRequest({ subtype: 'interrupt', cancellation: parsed.data }, { timeoutMs: 10000 });
|
|
294
314
|
}
|
|
295
315
|
setPermissionMode(mode) {
|
|
296
316
|
return this.controlRequest({ subtype: 'set_permission_mode', mode });
|
|
@@ -372,7 +392,7 @@ class QueryHandle {
|
|
|
372
392
|
return (await this.initializationResult()).account;
|
|
373
393
|
}
|
|
374
394
|
async close() {
|
|
375
|
-
this.cancelPermission?.();
|
|
395
|
+
this.cancelPermission?.({ origin: 'host', reason: 'connection_closed' });
|
|
376
396
|
this.markDead('ccl-sdk: query closed');
|
|
377
397
|
await this.transport.close();
|
|
378
398
|
}
|
|
@@ -28,8 +28,8 @@ export declare const QueryLifecycleSchema: z.ZodObject<{
|
|
|
28
28
|
actor: z.ZodEnum<{
|
|
29
29
|
unknown: "unknown";
|
|
30
30
|
agent: "agent";
|
|
31
|
-
human: "human";
|
|
32
31
|
host: "host";
|
|
32
|
+
human: "human";
|
|
33
33
|
}>;
|
|
34
34
|
source: z.ZodString;
|
|
35
35
|
session_id: z.ZodOptional<z.ZodString>;
|
|
@@ -113,8 +113,8 @@ export declare const QueryLifecycleObservationSchema: z.ZodObject<{
|
|
|
113
113
|
actor: z.ZodEnum<{
|
|
114
114
|
unknown: "unknown";
|
|
115
115
|
agent: "agent";
|
|
116
|
-
human: "human";
|
|
117
116
|
host: "host";
|
|
117
|
+
human: "human";
|
|
118
118
|
}>;
|
|
119
119
|
source: z.ZodString;
|
|
120
120
|
session_id: z.ZodOptional<z.ZodString>;
|
package/dist/sdk/types.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export type CanUseToolResult = {
|
|
|
29
29
|
interaction?: import('./interactionResponseSchema.js').InteractionResponse;
|
|
30
30
|
/** Also interrupt the current turn instead of letting the agent continue. */
|
|
31
31
|
interrupt?: boolean;
|
|
32
|
+
/** Host control-channel provenance; not a human refusal or a new authorization. */
|
|
33
|
+
cancellation?: import('./cancellation.js').HostCancellation;
|
|
32
34
|
} | {
|
|
33
35
|
/**
|
|
34
36
|
* Suspend the session at this tool call (issue P0-2 pending): the tool
|
|
@@ -403,6 +405,7 @@ type SDKEventPayload = {
|
|
|
403
405
|
behavior: 'allow' | 'deny';
|
|
404
406
|
/** Why the SDK decided this way when the host callback did not decide. */
|
|
405
407
|
failClosedReason?: string;
|
|
408
|
+
cancellation?: import('./cancellation.js').HostCancellation;
|
|
406
409
|
interaction?: import('./interactionResponseSchema.js').InteractionResponse;
|
|
407
410
|
/** 'pending_decision' when resolved by resumeWithDecision, not the live callback. */
|
|
408
411
|
source?: 'callback' | 'pending_decision';
|
|
@@ -479,7 +482,7 @@ export type InitializationResult = {
|
|
|
479
482
|
* the stream is identical to the bare queryWithTransport generator.
|
|
480
483
|
*/
|
|
481
484
|
export interface Query extends AsyncGenerator<SDKEvent, void, unknown> {
|
|
482
|
-
interrupt(): Promise<unknown>;
|
|
485
|
+
interrupt(cancellation?: import('./cancellation.js').HostCancellation): Promise<unknown>;
|
|
483
486
|
setPermissionMode(mode: string): Promise<unknown>;
|
|
484
487
|
setModel(model?: string): Promise<unknown>;
|
|
485
488
|
setMaxThinkingTokens(n: number | null): Promise<unknown>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 1.5.1-alpha.3
|
|
2
|
+
|
|
3
|
+
本预发布版在 [1.5.1-alpha.2](1.5.1-alpha.2.md) 基础上增加宿主取消来源契约(#58)和视觉输出预算配置及有界恢复(#59)。使用明确版本或 `alpha` 标签安装;npm 发布状态以 registry 为准,`latest` 保持不变。
|
|
4
|
+
|
|
5
|
+
## 宿主停止与权限拒绝
|
|
6
|
+
|
|
7
|
+
SDK `handle.interrupt()`、权限回调超时/异常和会话退役会保留宿主取消来源,不再被误写成用户拒绝或用户偏好。普通工具 Deny 继续保留拒绝依据;取消不撤销其他授权,也不授予新权限。
|
|
8
|
+
|
|
9
|
+
`interrupt()` 可接收 `HostCancellation`,权限回调 deny 可附带 `cancellation`;来源也会出现在 `permission_resolved` 和原生 transcript。旧客户端只返回 `deny('Conversation stopped')` 时仍按拒绝处理,需迁移到 interrupt 或显式 cancellation。AskUserQuestion 的非终止 unavailable/timeout 交互保持原行为,不制造用户答案。
|
|
10
|
+
|
|
11
|
+
取消与结果返回竞态时保留已完成事实,迟到的 allow 不启动已取消的工具。成功停止 ACK 只确认控制请求被接受;拒绝、畸形 ACK、断连或10秒无 ACK 会明确失败,停止结果可能未知。取消不回滚已发生的外部效果,强杀或close也不保证最后消息持久化;恢复前仍需核对事实,不能盲目重放。
|
|
12
|
+
|
|
13
|
+
## 视觉输出预算
|
|
14
|
+
|
|
15
|
+
CLI 环境变量 `CCL_VISUAL_MAX_OUTPUT_TOKENS` 默认8192;可选 `CCL_VISUAL_RECOVERY_MAX_OUTPUT_TOKENS` 显式授权一次更高预算恢复,默认不启用。SDK通过已有 `options.env` 配置。值必须是正安全整数,恢复预算必须大于初始预算;非法配置在读图或模型请求前失败。Agent模型不能通过工具参数改预算或覆盖视觉模型。
|
|
16
|
+
|
|
17
|
+
现有模型输出上限和共享上下文限制仍会约束请求。只有完整终态的输出限额失败、无拒绝/过滤、有效预算确实增加且恢复额度未用时,才会用原模型、协议、提示和已授权图片字节重试。预算被模型上限截平时不发无效重试。空响应与输出限额恢复共用一次内容重试,另允许此前一次能力上下文刷新;至多3次推理共用900秒期限。
|
|
18
|
+
|
|
19
|
+
成功和失败均记录尝试历史、request ID、请求/有效预算、限制来源、重试关联、耗时及累计已知usage。缺少input/output用量时 `usageComplete=false`,不把未知成本当作零。最终未完成不会报视觉通过。成功结果提示也不再无条件暗示此前发生过失败和恢复。
|
|
20
|
+
|
|
21
|
+
提高预算可能增加成本,不保证提升视觉准确性。原三图同提示的真实模型对照中,8192和16384都单次完成,未复现旧截断;本次真实运行未触发自动恢复,恢复边界由CLI/SDK接线及单元测试验证。模型仍有精确颜色判断误差,完整输出不能替代业务验收。
|
|
22
|
+
|
|
23
|
+
使用方式及兼容边界见 [SDK API](../../sdk/API.md)。CLI与SDK保持同版本;已运行进程不会热更新,正常结束后新会话使用新版。未改默认模型、权限策略或业务数据,无需数据迁移。仍不承诺跨外部系统exactly-once、模型始终正确理解图像或任意终止都能回收已发生的副作用。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@margay/ccl-core",
|
|
3
|
-
"version": "1.5.1-alpha.
|
|
3
|
+
"version": "1.5.1-alpha.3",
|
|
4
4
|
"description": "Core runtime extracted from Claude Code (ccl) for Margay integration",
|
|
5
5
|
"license": "SEE LICENSE IN README.md",
|
|
6
6
|
"type": "module",
|
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
"docs/releases/1.5.0-alpha.6.md",
|
|
55
55
|
"docs/releases/1.5.1-alpha.md",
|
|
56
56
|
"docs/releases/1.5.1-alpha.1.md",
|
|
57
|
-
"docs/releases/1.5.1-alpha.2.md"
|
|
57
|
+
"docs/releases/1.5.1-alpha.2.md",
|
|
58
|
+
"docs/releases/1.5.1-alpha.3.md"
|
|
58
59
|
],
|
|
59
60
|
"engines": {
|
|
60
61
|
"node": ">=22.19.0"
|
package/sdk/API.md
CHANGED
|
@@ -213,7 +213,7 @@ type CanUseToolFn = (
|
|
|
213
213
|
|
|
214
214
|
type CanUseToolResult =
|
|
215
215
|
| { behavior: 'allow'; updatedInput?: Record<string, unknown> } // 放行;updatedInput=改写输入(modify)
|
|
216
|
-
| { behavior: 'deny'; message?: string; interrupt?: boolean
|
|
216
|
+
| { behavior: 'deny'; message?: string; interrupt?: boolean; cancellation?: HostCancellation }
|
|
217
217
|
| { behavior: 'pending' } // 挂起走异步审批(§5)
|
|
218
218
|
```
|
|
219
219
|
|
|
@@ -222,6 +222,12 @@ type CanUseToolResult =
|
|
|
222
222
|
- `allow` 未提供 `updatedInput`(或提供空对象)时按原始 input 执行。
|
|
223
223
|
- 未配置 `canUseTool` 时 SDK 不启用权限转发(工具按 CLI 自身权限规则走);若此时 CLI 仍发来请求,按 deny 兜底。
|
|
224
224
|
|
|
225
|
+
宿主停止与权限拒绝是不同事件。停止生成可直接 `await handle.interrupt()`;它取消未完成权限等待,默认来源为 `{origin:'host',reason:'cancelled'}`。也可传完整的 `HostCancellation`,reason 支持 `cancelled`、`timeout`、`connection_closed`、`callback_error`、`permission_denied`。`origin` 表示控制通道来源,不推断是谁点击了停止。原始 stream-json 请求为 `{type:'control_request',request_id,request:{subtype:'interrupt',cancellation}}`,省略 cancellation 同样表示宿主取消。
|
|
226
|
+
|
|
227
|
+
需要在权限回调结束等待时,可返回 `{behavior:'deny',message:'Conversation stopped',cancellation:{origin:'host',reason:'cancelled'}}`。这结束当前轮,工具不执行;`permission_resolved.cancellation` 与原生 transcript 的 `toolUseResult.cancellation` 保留来源。回调超时/异常自动携带 timeout/callback_error。AskUserQuestion 的 timed_out/interaction_unavailable 继续按已有非终止交互失败处理,不制造用户答案。未带 cancellation 的普通 deny 仍是拒绝;deny+interrupt 记录 permission_denied 并保留原拒绝依据,不推断人工决定。旧客户端的 `deny('Conversation stopped')` 仍是拒绝,宿主应迁移到 interrupt 或显式 cancellation,CCL 不通过文字猜测来源。
|
|
228
|
+
|
|
229
|
+
interrupt 的 Promise 只在匹配成功 ACK 后完成;拒绝、畸形 ACK、断连会 reject,10 秒没有 ACK 也会 reject,表示停止结果未知。还须观察当前轮的 result,不能把 ACK 当作工具已回滚。迟到的 allow 不会启动已取消的工具。已完成结果保留;在途操作缺少结果不能证明没有副作用,恢复时先核对事实,不能盲目重放。取消本身不撤销其他已存在授权,也不授予新权限。`end_session` 支持优雅退役并记录 connection_closed;直接强杀进程或 `close()` 是资源清理,不保证最后一条消息已持久化,需按未知结果恢复。
|
|
230
|
+
|
|
225
231
|
---
|
|
226
232
|
|
|
227
233
|
## 5. pending 挂起 / 跨进程恢复(审批闭环)
|
|
@@ -673,3 +679,56 @@ operation-replay guarantee, and does not introduce a new permission gate.
|
|
|
673
679
|
Check the actual tool schema for `mode`; an older installation with the same
|
|
674
680
|
candidate version string may not have it. An existence precheck followed by normal
|
|
675
681
|
Write is not an equivalent no-replacement contract.
|
|
682
|
+
|
|
683
|
+
## Visual specialist output budget (1.5.1-alpha.3)
|
|
684
|
+
|
|
685
|
+
The synchronous `Agent` specialist `visual-ui-analysis` accepts its existing
|
|
686
|
+
`prompt` and `image_paths`. The host controls its output allowance; the model
|
|
687
|
+
cannot set it through Agent arguments or override the selected vision model.
|
|
688
|
+
CLI processes read `CCL_VISUAL_MAX_OUTPUT_TOKENS` (default `8192`) and optional
|
|
689
|
+
`CCL_VISUAL_RECOVERY_MAX_OUTPUT_TOKENS` (disabled by default). SDK hosts use the
|
|
690
|
+
same variables through `options.env`:
|
|
691
|
+
|
|
692
|
+
```ts
|
|
693
|
+
const handle = query({
|
|
694
|
+
prompt: 'Inspect the supplied rendered slides with the visual specialist.',
|
|
695
|
+
options: {
|
|
696
|
+
env: {
|
|
697
|
+
CCL_VISUAL_MAX_OUTPUT_TOKENS: '16384',
|
|
698
|
+
CCL_VISUAL_RECOVERY_MAX_OUTPUT_TOKENS: '32768',
|
|
699
|
+
},
|
|
700
|
+
// Supply your normal cwd, credentials and permission callback as appropriate.
|
|
701
|
+
},
|
|
702
|
+
})
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
Values must be decimal positive safe integers; the recovery value must exceed
|
|
706
|
+
the initial value. Invalid configuration fails before image reads or model I/O.
|
|
707
|
+
The existing provider/configured output limits and shared context budget still
|
|
708
|
+
cap each request. Unknown limits remain unknown; a larger allowance is not a
|
|
709
|
+
claim that the model supports it. Reasoning and visible text share the allowance.
|
|
710
|
+
|
|
711
|
+
A terminal output-limit failure can use the higher allowance once, only when
|
|
712
|
+
its effective budget increases. If a model caps both requests at 8192, no recovery
|
|
713
|
+
request is sent. Recovery reuses the selected model, protocol, prompt and already
|
|
714
|
+
authorized image bytes. It shares one content retry with normal-terminal empty
|
|
715
|
+
response recovery. A capability-context refresh is separately allowed once before
|
|
716
|
+
content recovery, so at most three inference attempts share one 900-second
|
|
717
|
+
operation deadline. Cancellation, refusal, filtering, incomplete streams and an
|
|
718
|
+
exhausted recovery do not trigger a further content retry.
|
|
719
|
+
|
|
720
|
+
The returned visual facts retain every attempt's request ID, requested and
|
|
721
|
+
effective budget, safe `requestBudget` limit snapshot, duration, and one-based
|
|
722
|
+
`retryOfAttempt` / `retryOfRequestId` links. Both completed and failed inspections
|
|
723
|
+
include `visualDiagnostics.usage`, `usageComplete`, and total `durationMs` (from
|
|
724
|
+
post-image-load resolution to terminal outcome). `usageComplete` concerns reported
|
|
725
|
+
input/output counts; optional cache breakdowns may be absent. Missing usage is
|
|
726
|
+
not proof of zero cost: totals sum known values, and `usageComplete: false` marks
|
|
727
|
+
the incomplete accounting. Successful SDK tool usage retains the same numeric
|
|
728
|
+
totals for compatibility. A final failure remains an incomplete inspection,
|
|
729
|
+
preserves earlier failures, and asks the Owner to stop same-condition retries.
|
|
730
|
+
|
|
731
|
+
Increasing output allowance can increase cost and does not establish visual
|
|
732
|
+
accuracy. Verify page observations separately; a complete response alone is not
|
|
733
|
+
business acceptance. These variables require a build containing this feature;
|
|
734
|
+
the previously published alpha.2 does not support them.
|