@sema-agent/client-core 0.16.1 → 0.17.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/README.md +3 -2
- package/dist/adapter/downstream/eventToSdkMessage.js +60 -20
- package/dist/adapter/downstream/terminalToSdkResult.js +16 -16
- package/dist/adapter/runStream.js +4 -3
- package/dist/engineErrorCodes.d.ts +119 -0
- package/dist/engineErrorCodes.js +147 -0
- package/dist/finalVerifyWire.d.ts +28 -23
- package/dist/finalVerifyWire.js +27 -11
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/interactiveToolsWire.d.ts +10 -0
- package/dist/interactiveToolsWire.js +10 -0
- package/dist/limitsWire.d.ts +28 -11
- package/dist/limitsWire.js +27 -29
- package/dist/subagent/engineTaskHandleWire.d.ts +8 -0
- package/dist/subagent/engineTaskHandleWire.js +10 -13
- package/dist/toolResult.d.ts +86 -0
- package/dist/toolResult.js +91 -3
- package/package.json +4 -3
package/dist/finalVerifyWire.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { hostEnv } from './hostEnv.js';
|
|
2
|
-
import { envFlagOff } from './envFlag.js';
|
|
2
|
+
import { envFlagOff, envFlagOn } from './envFlag.js';
|
|
3
3
|
/** Settings-lane env knob(settings.json `env` 块 → 1a-envseed → process.env)。'false'/'0' = off。 */
|
|
4
4
|
export const HEADLESS_FINAL_VERIFY_ENV = 'SEMA_HEADLESS_FINAL_VERIFY';
|
|
5
5
|
/**
|
|
@@ -15,6 +15,20 @@ export function parseNoFinalVerifyArgv(argv) {
|
|
|
15
15
|
}
|
|
16
16
|
return false;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Parse the explicit opt-in `--final-verify` out of an argv slice(同 `--no-final-verify` 的扫描
|
|
20
|
+
* 纪律:bare `--` 后是 positionals)。默认关时代的唯一 flag 开口;`--no-final-verify` 恒赢它
|
|
21
|
+
* (off 优先纪律)。壳侧 commander 需在 0.17.x 提货时注册本 flag(提货单点名)。
|
|
22
|
+
*/
|
|
23
|
+
export function parseFinalVerifyArgv(argv) {
|
|
24
|
+
for (const a of argv) {
|
|
25
|
+
if (a === '--')
|
|
26
|
+
break; // positionals — never flags
|
|
27
|
+
if (a === '--final-verify')
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
18
32
|
/**
|
|
19
33
|
* The settings-lane off-switch:`SEMA_HEADLESS_FINAL_VERIFY` 设成 {@link envFlagOff} 拼写集
|
|
20
34
|
* (REF-CC-141 dup-02 单源:`0`/`false`/`no`/`off`/`none`,大小写不敏感)disables the stamp。
|
|
@@ -39,28 +53,30 @@ export function hasUserStopHook(wireHooks) {
|
|
|
39
53
|
return Array.isArray(groups) && groups.length > 0;
|
|
40
54
|
}
|
|
41
55
|
/**
|
|
42
|
-
* Resolve the finalVerification stamp for a headless `-p` submit
|
|
43
|
-
* `--
|
|
56
|
+
* Resolve the finalVerification stamp for a headless `-p` submit:**默认 false**(clay 裁定
|
|
57
|
+
* 2026-08-04,头注案情);仅显式 `--final-verify` / env ON 拼写才可能 on,且 Stop hook 仍让位。
|
|
44
58
|
* 调用点必须 live-gate(SEMA_LIVE_BASEURL)——mock/offline 形状不变。
|
|
45
59
|
*/
|
|
46
60
|
export function resolveHeadlessFinalVerify(argv, env = hostEnv(), wireHooks) {
|
|
47
61
|
return resolveHeadlessFinalVerifyDetail(argv, env, wireHooks).on;
|
|
48
62
|
}
|
|
49
63
|
/**
|
|
50
|
-
* #106 裁 B(clay 2026-07-26)
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* 失去了终验)。方向注记:finalVerification 后续默认关(CC Stop hook 机制同,不叠超集)——
|
|
54
|
-
* 见记忆 final-verification-direction;届时本函数的默认臂翻转,detail 形不动。
|
|
64
|
+
* #106 裁 B(clay 2026-07-26)让位+告知维持,但默认臂已按 2026-08-04 裁定翻转(头注案情):
|
|
65
|
+
* 默认 off 且**无 offReason**(静默——没开过的东西无从让位,告知只在显式 on 被 Stop hook
|
|
66
|
+
* 压下时出现);flag/env 显式关同样不聒噪。detail 形不动(offReason 词表原样)。
|
|
55
67
|
*/
|
|
56
68
|
export function resolveHeadlessFinalVerifyDetail(argv, env = hostEnv(), wireHooks) {
|
|
57
69
|
if (parseNoFinalVerifyArgv(argv))
|
|
58
70
|
return { on: false, offReason: 'flag' };
|
|
59
71
|
if (headlessFinalVerifyDisabledByEnv(env))
|
|
60
72
|
return { on: false, offReason: 'env' };
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
73
|
+
const explicitOn = parseFinalVerifyArgv(argv) || envFlagOn(env[HEADLESS_FINAL_VERIFY_ENV]);
|
|
74
|
+
if (explicitOn) {
|
|
75
|
+
if (hasUserStopHook(wireHooks))
|
|
76
|
+
return { on: false, offReason: 'stop-hook' };
|
|
77
|
+
return { on: true };
|
|
78
|
+
}
|
|
79
|
+
return { on: false }; // 默认关(clay 裁定 2026-08-04):无 offReason = 静默缺省态
|
|
64
80
|
}
|
|
65
81
|
/**
|
|
66
82
|
* stop-hook 让位的告知文案(单一措辞源;stderr 一行)。措辞纪律与 hook_notice 同族:
|
package/dist/index.d.ts
CHANGED
|
@@ -155,6 +155,7 @@ export * from './controlRouter.js';
|
|
|
155
155
|
export * from './sseIdleTriage.js';
|
|
156
156
|
export * from './engineWireSdk.js';
|
|
157
157
|
export * from './classifierVerdictWire.js';
|
|
158
|
+
export * from './engineErrorCodes.js';
|
|
158
159
|
export * from './agentsWireCaps.js';
|
|
159
160
|
export * from './attachmentsWireCaps.js';
|
|
160
161
|
export * from './clientContextWireCaps.js';
|
package/dist/index.js
CHANGED
|
@@ -171,6 +171,10 @@ export * from './controlRouter.js';
|
|
|
171
171
|
export * from './sseIdleTriage.js';
|
|
172
172
|
export * from './engineWireSdk.js';
|
|
173
173
|
export * from './classifierVerdictWire.js';
|
|
174
|
+
// G1 去字面化(2026-08-04,core 5.10.0 / sdk 6.2.0 消费半场):引擎 `errorCode` 词表的单一真源。
|
|
175
|
+
// 进公面是**有意**的(与 envFlag 那条包内叶相反):壳/web/desktop 三端都要按同一批码分臂,
|
|
176
|
+
// 三端各抄一份字面正是本文件要根治的病;开集纪律与闭集唯一例外见该文件头注。
|
|
177
|
+
export * from './engineErrorCodes.js';
|
|
174
178
|
// caps / wire 门族(17 个 *WireCaps。B2 时 hooksWireCaps / scratchpadWireCaps 因宿主耦合未搬;
|
|
175
179
|
// B4 已搬 scratchpadWireCaps(走 FsPort),hooksWireCaps 仍在壳里等 SettingsPort 接线 —— 见交接报告)
|
|
176
180
|
export * from './agentsWireCaps.js';
|
|
@@ -11,6 +11,16 @@
|
|
|
11
11
|
* 该工具)= [909]A3(headless EnterPlanMode→park 弃 86% 预算)类事故的根治,比 [884]A1 的
|
|
12
12
|
* park→error 终帧防御纵深更靠前(现在根本不 park)。
|
|
13
13
|
*
|
|
14
|
+
* 🆕 **core 5.10.0 把这一 stamp 的作用域从「本 run」扩到「本 run 及其全部委派子代」**
|
|
15
|
+
* ([2575] 消费端自查④,委托继承收窄五座之②):此前子代 roster 是「**恒无** AskUserQuestion」,
|
|
16
|
+
* 5.10.0 起改成「**父面允许才有**」——`onQuestion` 在 prepare 期解析一次并**原样**传给子代
|
|
17
|
+
* (父子共用同一个函数对象)。对本模块是**净收益且零改动**:`-p` stamp `false` 之后,硬 headless
|
|
18
|
+
* 父的子代**压根不挂** AskUserQuestion,而不是挂了再靠子代自己的判断兜。同族一并单向收紧的还有
|
|
19
|
+
* `handsReadOnly`(只读父 ⇒ 子代无写手)与 `oneShot`(true 向传播)。
|
|
20
|
+
* 🔴 给下一棒的判据纪律:因此**别再用闭包身份判「谁在问」** —— 父子是同一个 `onQuestion` 函数对象,
|
|
21
|
+
* 闭包身份在 5.10.0 上不再有判别力;判身要读 `AskQuestionRequest.sourceTaskId` / `principal`
|
|
22
|
+
* (本包 hitl 面已是这个姿势,见 `hitl/toolApprovalWire.ts` 的 `sourceTaskId` 位)。
|
|
23
|
+
*
|
|
14
24
|
* 语义(headlessPermissionModeWire 同点同型 wire):
|
|
15
25
|
* · 仅 headless `-p` 车道(本模块只被 seamQueryEngine.ask import;交互 REPL 一根毛不动);
|
|
16
26
|
* · 显式交互意图恒赢:CLI 无 --interactive-tools 类旗(gap-check 2026-07-16:上游 CC 2.1.207
|
|
@@ -11,6 +11,16 @@
|
|
|
11
11
|
* 该工具)= [909]A3(headless EnterPlanMode→park 弃 86% 预算)类事故的根治,比 [884]A1 的
|
|
12
12
|
* park→error 终帧防御纵深更靠前(现在根本不 park)。
|
|
13
13
|
*
|
|
14
|
+
* 🆕 **core 5.10.0 把这一 stamp 的作用域从「本 run」扩到「本 run 及其全部委派子代」**
|
|
15
|
+
* ([2575] 消费端自查④,委托继承收窄五座之②):此前子代 roster 是「**恒无** AskUserQuestion」,
|
|
16
|
+
* 5.10.0 起改成「**父面允许才有**」——`onQuestion` 在 prepare 期解析一次并**原样**传给子代
|
|
17
|
+
* (父子共用同一个函数对象)。对本模块是**净收益且零改动**:`-p` stamp `false` 之后,硬 headless
|
|
18
|
+
* 父的子代**压根不挂** AskUserQuestion,而不是挂了再靠子代自己的判断兜。同族一并单向收紧的还有
|
|
19
|
+
* `handsReadOnly`(只读父 ⇒ 子代无写手)与 `oneShot`(true 向传播)。
|
|
20
|
+
* 🔴 给下一棒的判据纪律:因此**别再用闭包身份判「谁在问」** —— 父子是同一个 `onQuestion` 函数对象,
|
|
21
|
+
* 闭包身份在 5.10.0 上不再有判别力;判身要读 `AskQuestionRequest.sourceTaskId` / `principal`
|
|
22
|
+
* (本包 hitl 面已是这个姿势,见 `hitl/toolApprovalWire.ts` 的 `sourceTaskId` 位)。
|
|
23
|
+
*
|
|
14
24
|
* 语义(headlessPermissionModeWire 同点同型 wire):
|
|
15
25
|
* · 仅 headless `-p` 车道(本模块只被 seamQueryEngine.ask import;交互 REPL 一根毛不动);
|
|
16
26
|
* · 显式交互意图恒赢:CLI 无 --interactive-tools 类旗(gap-check 2026-07-16:上游 CC 2.1.207
|
package/dist/limitsWire.d.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* `limits` 是**唯一预算面,默认全空 = 无限制**。本模块拼的三个键都骑 5.8.0 的新词表:
|
|
7
7
|
* · `maxWalltimeMs`(**毫秒**;5.7 的 `limits.timeoutSec` 已删,单位一并变)
|
|
8
8
|
* · `maxTurns`
|
|
9
|
-
* · `
|
|
9
|
+
* · `maxOutputTokens`(5.8.0 的**主限**——time 退出任务轴之后,token 是首选的预算旋钮;
|
|
10
|
+
* wire 键名自 5.7 起一直是 maxOutputTokens,core 内部才叫 maxTokens——[2569] 案订正)
|
|
10
11
|
* 到限语义也换了:**没有配速机器了**。5.8.0 删掉了 deadlineNudge / callCapByDeadline /
|
|
11
12
|
* gracefulFinalize 那一套(以及引擎侧 1000 轮安全网、fork 200、session-bg 30min 暗注等全部隐式
|
|
12
13
|
* 默认),走到 `maxWalltimeMs` 不再是「引擎自己减速然后优雅收尾」,而是**响亮终局**:
|
|
@@ -28,7 +29,8 @@
|
|
|
28
29
|
* 793380) → ALIGNMENT-GAP fill: the flag was already registered (main.tsx CC-inherited surface) and
|
|
29
30
|
* drove the local query loop, but the seam path executes turns ENGINE-side — this wire finally
|
|
30
31
|
* carries the cap to where the turns actually run.
|
|
31
|
-
* · `--max-tokens <n>` → limits.
|
|
32
|
+
* · `--max-tokens <n>` → limits.maxOutputTokens(5.8.0 主限;wire 键名见接口注——绝不直发
|
|
33
|
+
* core 内部键 maxTokens,[2569] 案)。Upstream CC 2.1.207 has no run-level
|
|
32
34
|
* token budget flag → SUPERSET.
|
|
33
35
|
*
|
|
34
36
|
* PRECEDENCE per knob (flag > settings > none), scenarioWire 三件套同款:
|
|
@@ -57,9 +59,10 @@
|
|
|
57
59
|
* · 第一地板 `LIMITS_WIRE_MIN_ENGINE = 1.196.0` = 「`limits` 这个**字段**存不存在」(server 1.196.0
|
|
58
60
|
* 起接受 body.limits);低于它,整个预算面被丢。
|
|
59
61
|
* · 第二地板 **server ≥ 6.0.0** = 「**新键集**认不认」。6.0.0 是第一个捆 core 5.8.0 的 server 版本,
|
|
60
|
-
* 也就是第一个认识 `maxWalltimeMs
|
|
61
|
-
* 未知键静默丢弃
|
|
62
|
-
*
|
|
62
|
+
* 也就是第一个认识 `maxWalltimeMs` 的版本(5.7 只认 `timeoutSec`/`maxOutputTokens`/`maxTurns`,
|
|
63
|
+
* 未知键静默丢弃)。**[2569] 案订正**:`maxOutputTokens` 自 5.7 就在 wire 词表,本模块此前
|
|
64
|
+
* 直发 core 内部键 `maxTokens` 才是被静默无视的那个(server 6.3.0 起 400);对 1.196.0 ≤
|
|
65
|
+
* version < 6.0.0 的引擎,`maxTurns`/`maxOutputTokens` 生效而 `maxWalltimeMs` 被静默无视。
|
|
63
66
|
* 之所以过去写不出这条:/health 自报的是 server 版本,而键名代际取决于它捆的 core 版本,当时
|
|
64
67
|
* 两者没有可从 wire 读出的映射;6.0.0 这班车把映射钉死了(6.0.0 ⇒ core ≥5.8),这条地板才成立。
|
|
65
68
|
* · 本文件的运行期探针目前只对第一地板发声(warn, don't block);第二地板先落成契约事实写在这里,
|
|
@@ -97,8 +100,12 @@ export interface HeadlessLimits {
|
|
|
97
100
|
maxWalltimeMs?: number;
|
|
98
101
|
/** 轮数预算。到限 ⇒ failed + `limits.max_turns_exceeded`。 */
|
|
99
102
|
maxTurns?: number;
|
|
100
|
-
/** token 预算(5.8.0 主限)。到限 ⇒ failed + `limits.max_tokens_exceeded`(result 带已产出正文)。
|
|
101
|
-
|
|
103
|
+
/** token 预算(5.8.0 主限)。到限 ⇒ failed + `limits.max_tokens_exceeded`(result 带已产出正文)。
|
|
104
|
+
* 🔴 wire 键=**maxOutputTokens**(sdk 6.2.0 types.d.ts:472 实测词表;[2569] 案定谳):
|
|
105
|
+
* `maxTokens` 是 core TaskSpec 内部键,server 从未从 wire 采纳它(此前 200 静默空转,
|
|
106
|
+
* 6.3.0 起对 limits.maxTokens 直接 400)。用户面 `--max-tokens`/`SEMA_HEADLESS_MAX_TOKENS`
|
|
107
|
+
* 名称不变——变的只是上 wire 的键名。[same-name-different-meaning-crosses-layers] 同族。 */
|
|
108
|
+
maxOutputTokens?: number;
|
|
102
109
|
}
|
|
103
110
|
export type LimitsParseResult = {
|
|
104
111
|
ok: true;
|
|
@@ -117,11 +124,21 @@ export type LimitsParseResult = {
|
|
|
117
124
|
*/
|
|
118
125
|
export declare function parseLimitsArgv(argv: string[]): LimitsParseResult;
|
|
119
126
|
/**
|
|
120
|
-
* The settings-lane defaults.
|
|
121
|
-
*
|
|
122
|
-
*
|
|
127
|
+
* The settings-lane defaults. FAIL-LOUD on a bad value (#132 / test [2508],2026-08-04,推翻旧
|
|
128
|
+
* batch-E「静默降级」形):设置了 SEMA_HEADLESS_* 却是非法值时,旧行为=不开 SEMA_DEBUG 完全无声、
|
|
129
|
+
* 按「无限期限」跑——用户以为设了预算,实际什么都没设,这正是 [honest-absence] 禁的那类静默谎报。
|
|
130
|
+
* 现与 flag 路径同拍响亮拒(同文案形,caller exit 1):配置错了要在第一次跑就知道,而不是在一个
|
|
131
|
+
* 本该 300s 停的任务跑了一夜之后。返回判别联合;唯一消费者是下面的 limitsForPrint(仓际零外部
|
|
132
|
+
* 消费,干净切不设兼容)。
|
|
123
133
|
*/
|
|
124
|
-
export
|
|
134
|
+
export type HeadlessEnvLimitsResult = {
|
|
135
|
+
ok: true;
|
|
136
|
+
limits: HeadlessLimits | undefined;
|
|
137
|
+
} | {
|
|
138
|
+
ok: false;
|
|
139
|
+
error: string;
|
|
140
|
+
};
|
|
141
|
+
export declare function headlessLimitsFromEnv(env?: EnvLike): HeadlessEnvLimitsResult;
|
|
125
142
|
/**
|
|
126
143
|
* Resolve the limits for a headless `-p` submit: explicit flags > settings env defaults > none
|
|
127
144
|
* (= no stamp, today's behaviour). Merge is PER-KNOB: `--deadline 300` + SEMA_HEADLESS_MAX_TURNS=50
|
package/dist/limitsWire.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* `limits` 是**唯一预算面,默认全空 = 无限制**。本模块拼的三个键都骑 5.8.0 的新词表:
|
|
7
7
|
* · `maxWalltimeMs`(**毫秒**;5.7 的 `limits.timeoutSec` 已删,单位一并变)
|
|
8
8
|
* · `maxTurns`
|
|
9
|
-
* · `
|
|
9
|
+
* · `maxOutputTokens`(5.8.0 的**主限**——time 退出任务轴之后,token 是首选的预算旋钮;
|
|
10
|
+
* wire 键名自 5.7 起一直是 maxOutputTokens,core 内部才叫 maxTokens——[2569] 案订正)
|
|
10
11
|
* 到限语义也换了:**没有配速机器了**。5.8.0 删掉了 deadlineNudge / callCapByDeadline /
|
|
11
12
|
* gracefulFinalize 那一套(以及引擎侧 1000 轮安全网、fork 200、session-bg 30min 暗注等全部隐式
|
|
12
13
|
* 默认),走到 `maxWalltimeMs` 不再是「引擎自己减速然后优雅收尾」,而是**响亮终局**:
|
|
@@ -28,7 +29,8 @@
|
|
|
28
29
|
* 793380) → ALIGNMENT-GAP fill: the flag was already registered (main.tsx CC-inherited surface) and
|
|
29
30
|
* drove the local query loop, but the seam path executes turns ENGINE-side — this wire finally
|
|
30
31
|
* carries the cap to where the turns actually run.
|
|
31
|
-
* · `--max-tokens <n>` → limits.
|
|
32
|
+
* · `--max-tokens <n>` → limits.maxOutputTokens(5.8.0 主限;wire 键名见接口注——绝不直发
|
|
33
|
+
* core 内部键 maxTokens,[2569] 案)。Upstream CC 2.1.207 has no run-level
|
|
32
34
|
* token budget flag → SUPERSET.
|
|
33
35
|
*
|
|
34
36
|
* PRECEDENCE per knob (flag > settings > none), scenarioWire 三件套同款:
|
|
@@ -57,9 +59,10 @@
|
|
|
57
59
|
* · 第一地板 `LIMITS_WIRE_MIN_ENGINE = 1.196.0` = 「`limits` 这个**字段**存不存在」(server 1.196.0
|
|
58
60
|
* 起接受 body.limits);低于它,整个预算面被丢。
|
|
59
61
|
* · 第二地板 **server ≥ 6.0.0** = 「**新键集**认不认」。6.0.0 是第一个捆 core 5.8.0 的 server 版本,
|
|
60
|
-
* 也就是第一个认识 `maxWalltimeMs
|
|
61
|
-
* 未知键静默丢弃
|
|
62
|
-
*
|
|
62
|
+
* 也就是第一个认识 `maxWalltimeMs` 的版本(5.7 只认 `timeoutSec`/`maxOutputTokens`/`maxTurns`,
|
|
63
|
+
* 未知键静默丢弃)。**[2569] 案订正**:`maxOutputTokens` 自 5.7 就在 wire 词表,本模块此前
|
|
64
|
+
* 直发 core 内部键 `maxTokens` 才是被静默无视的那个(server 6.3.0 起 400);对 1.196.0 ≤
|
|
65
|
+
* version < 6.0.0 的引擎,`maxTurns`/`maxOutputTokens` 生效而 `maxWalltimeMs` 被静默无视。
|
|
63
66
|
* 之所以过去写不出这条:/health 自报的是 server 版本,而键名代际取决于它捆的 core 版本,当时
|
|
64
67
|
* 两者没有可从 wire 读出的映射;6.0.0 这班车把映射钉死了(6.0.0 ⇒ core ≥5.8),这条地板才成立。
|
|
65
68
|
* · 本文件的运行期探针目前只对第一地板发声(warn, don't block);第二地板先落成契约事实写在这里,
|
|
@@ -122,7 +125,7 @@ function parseIntInDomain(raw, min, max) {
|
|
|
122
125
|
}
|
|
123
126
|
/** 三个键任一在场 ⇒ 值得 stamp(空对象绝不上 wire —— 那是「配了个空预算」不是「没配」)。 */
|
|
124
127
|
function hasAnyLimit(l) {
|
|
125
|
-
return l.maxWalltimeMs !== undefined || l.maxTurns !== undefined || l.
|
|
128
|
+
return l.maxWalltimeMs !== undefined || l.maxTurns !== undefined || l.maxOutputTokens !== undefined;
|
|
126
129
|
}
|
|
127
130
|
/**
|
|
128
131
|
* Parse `--deadline` + `--max-turns` + `--max-tokens` out of an argv slice. No flags ⇒ `{ok:true}`
|
|
@@ -156,7 +159,7 @@ export function parseLimitsArgv(argv) {
|
|
|
156
159
|
const n = mk.raw !== undefined ? parseIntInDomain(mk.raw, MAX_TOKENS_MIN, MAX_TOKENS_MAX) : null;
|
|
157
160
|
if (n === null)
|
|
158
161
|
return { ok: false, error: MAX_TOKENS_USAGE };
|
|
159
|
-
limits.
|
|
162
|
+
limits.maxOutputTokens = n;
|
|
160
163
|
flaggedFlags.push('--max-tokens');
|
|
161
164
|
}
|
|
162
165
|
return {
|
|
@@ -165,44 +168,36 @@ export function parseLimitsArgv(argv) {
|
|
|
165
168
|
flaggedFlags,
|
|
166
169
|
};
|
|
167
170
|
}
|
|
168
|
-
/**
|
|
169
|
-
* The settings-lane defaults. SILENT degrade on a bad value (batch-E triage shape — stricter than
|
|
170
|
-
* scenarioWire's warn-and-ignore: a settings typo must neither brick nor spam every headless run).
|
|
171
|
-
* SEMA_DEBUG surfaces the drop.
|
|
172
|
-
*/
|
|
173
171
|
export function headlessLimitsFromEnv(env = hostEnv()) {
|
|
174
172
|
const out = {};
|
|
175
173
|
const dl = env[HEADLESS_DEADLINE_ENV]?.trim();
|
|
176
174
|
if (dl) {
|
|
177
175
|
const n = parseIntInDomain(dl, DEADLINE_SEC_MIN, DEADLINE_SEC_MAX);
|
|
178
|
-
if (n
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
176
|
+
if (n === null) {
|
|
177
|
+
return {
|
|
178
|
+
ok: false,
|
|
179
|
+
error: `Error: ${HEADLESS_DEADLINE_ENV}="${dl}" is not an integer in ${DEADLINE_SEC_MIN}..${DEADLINE_SEC_MAX} (seconds). Fix or unset it.`,
|
|
180
|
+
};
|
|
183
181
|
}
|
|
182
|
+
out.maxWalltimeMs = n * SEC_TO_MS; // env 面同样是秒(变量名带 _SEC),换算同一口
|
|
184
183
|
}
|
|
185
184
|
const mt = env[HEADLESS_MAX_TURNS_ENV]?.trim();
|
|
186
185
|
if (mt) {
|
|
187
186
|
const n = parseIntInDomain(mt, MAX_TURNS_MIN, MAX_TURNS_MAX);
|
|
188
|
-
if (n
|
|
189
|
-
|
|
190
|
-
else if (env.SEMA_DEBUG) {
|
|
191
|
-
// eslint-disable-next-line no-console
|
|
192
|
-
console.error(`[sema] ${HEADLESS_MAX_TURNS_ENV}="${mt}" is not a positive integer — ignoring the settings default`);
|
|
187
|
+
if (n === null) {
|
|
188
|
+
return { ok: false, error: `Error: ${HEADLESS_MAX_TURNS_ENV}="${mt}" is not a positive integer. Fix or unset it.` };
|
|
193
189
|
}
|
|
190
|
+
out.maxTurns = n;
|
|
194
191
|
}
|
|
195
192
|
const mk = env[HEADLESS_MAX_TOKENS_ENV]?.trim();
|
|
196
193
|
if (mk) {
|
|
197
194
|
const n = parseIntInDomain(mk, MAX_TOKENS_MIN, MAX_TOKENS_MAX);
|
|
198
|
-
if (n
|
|
199
|
-
|
|
200
|
-
else if (env.SEMA_DEBUG) {
|
|
201
|
-
// eslint-disable-next-line no-console
|
|
202
|
-
console.error(`[sema] ${HEADLESS_MAX_TOKENS_ENV}="${mk}" is not a positive integer — ignoring the settings default`);
|
|
195
|
+
if (n === null) {
|
|
196
|
+
return { ok: false, error: `Error: ${HEADLESS_MAX_TOKENS_ENV}="${mk}" is not a positive integer. Fix or unset it.` };
|
|
203
197
|
}
|
|
198
|
+
out.maxOutputTokens = n;
|
|
204
199
|
}
|
|
205
|
-
return hasAnyLimit(out) ? out : undefined;
|
|
200
|
+
return { ok: true, limits: hasAnyLimit(out) ? out : undefined };
|
|
206
201
|
}
|
|
207
202
|
/**
|
|
208
203
|
* Resolve the limits for a headless `-p` submit: explicit flags > settings env defaults > none
|
|
@@ -213,8 +208,11 @@ export function limitsForPrint(argv, env = hostEnv()) {
|
|
|
213
208
|
const parsed = parseLimitsArgv(argv);
|
|
214
209
|
if (!parsed.ok)
|
|
215
210
|
return parsed;
|
|
211
|
+
// #132:env 面非法值与 flag 同拍响亮拒(见 headlessLimitsFromEnv 头注)。
|
|
216
212
|
const fromEnv = headlessLimitsFromEnv(env);
|
|
217
|
-
|
|
213
|
+
if (!fromEnv.ok)
|
|
214
|
+
return { ok: false, error: fromEnv.error };
|
|
215
|
+
const merged = { ...(fromEnv.limits ?? {}), ...(parsed.limits ?? {}) };
|
|
218
216
|
return { ok: true, ...(hasAnyLimit(merged) ? { limits: merged } : {}), flaggedFlags: parsed.flaggedFlags };
|
|
219
217
|
}
|
|
220
218
|
/** True when the engine version string satisfies the limits-wire minimum (≥1.196.0). Unparsable ⇒ false. */
|
|
@@ -86,6 +86,14 @@ export type EngineTaskStopOutcome =
|
|
|
86
86
|
* 照样带原始 `code`),绝不塌进任何一个已知形 —— 塌进去就等于替 server 编了一个它没说的原因。
|
|
87
87
|
*/
|
|
88
88
|
export declare function classifyTaskStopConflict(e: unknown): EngineTaskStopOutcome;
|
|
89
|
+
/**
|
|
90
|
+
* 🟢 B8 ALLOW 清单登记(REF-CC-域词表-05,属主=client-core 规范重构轮,到期复议=SDK typed 错误类
|
|
91
|
+
* 覆盖全部 `stop.*` 码后或 2026-Q4 复审以先到者为准):兜底字面认码,前置合取 = typed
|
|
92
|
+
* `TaskStopConflictError` 缺席时才落到这里(见 `stopEngineTask` 的 `if (e instanceof
|
|
93
|
+
* TaskStopConflictError || status === 409)`)。同批同表的另两处:`detachWire.ts`
|
|
94
|
+
* `DETACH_DURABLE_OFF_400_ANCHOR`(server 契约话)、`classifierVerdictWire.ts`
|
|
95
|
+
* `CLASSIFIER_DENY_SIGNATURE`(core lockstep 机器签名)——三处互指,理由逐条写在各自站点。
|
|
96
|
+
*/
|
|
89
97
|
/** 停一个引擎侧任务句柄(新面 B)。只有 ok:true(=HTTP 200)算停了。绝不 throw。 */
|
|
90
98
|
export declare function stopEngineTask(handle: string, opts?: {
|
|
91
99
|
signal?: AbortSignal;
|
|
@@ -47,6 +47,9 @@ import { engineTaskHandlesCapable } from './engineRowStopGate.js';
|
|
|
47
47
|
// REF-CC-域词表-06 提单源:spool 全量/增量判读的单一真源现在在 toolResult.ts(它也消费同一份
|
|
48
48
|
// 协议标记表 PROTOCOL_MARKERS)—— 本文件不再自己 `.includes()` 抄一份判读。
|
|
49
49
|
import { spoolMarkerOf } from '../toolResult.js';
|
|
50
|
+
// G1 去字面化(2026-08-04):`stop.*` 五形的码字面收编进 `engineErrorCodes.ts` 单一真源。
|
|
51
|
+
// 顺序敏感的兜底认码数组也一并搬过去(顺序即语义,见那边的头注),本文件只 import。
|
|
52
|
+
import { STOP_CONFLICT_CODES, STOP_NOT_LANDED, STOP_NOT_LOCAL, STOP_PARKED, STOP_PARK_ARBITER_UNREACHABLE, STOP_PARK_RESUME_WON, } from '../engineErrorCodes.js';
|
|
50
53
|
// 能力门同步读口:真源在零依赖叶 `engineRowStopGate`(batch-stop 同步分类不拉本模块 SDK 图)。
|
|
51
54
|
// ⚠️ 此处**不再 re-export**——它与原定义同进 index.ts 的 `export *` barrel 会构成双出口,
|
|
52
55
|
// esbuild 对 star-export 歧义直接 build 失败(tsc 同源 symbol 不报=假绿;0.8.0 壳收批实撞,
|
|
@@ -153,15 +156,15 @@ export function classifyTaskStopConflict(e) {
|
|
|
153
156
|
? raw
|
|
154
157
|
: (STOP_CONFLICT_CODES.find(c => detail.includes(c)) ?? '');
|
|
155
158
|
switch (code) {
|
|
156
|
-
case
|
|
159
|
+
case STOP_NOT_LOCAL:
|
|
157
160
|
return { ok: false, reason: 'not_local', detail };
|
|
158
|
-
case
|
|
161
|
+
case STOP_PARKED:
|
|
159
162
|
return { ok: false, reason: 'parked', detail };
|
|
160
|
-
case
|
|
163
|
+
case STOP_PARK_RESUME_WON:
|
|
161
164
|
return { ok: false, reason: 'park_resume_won', detail };
|
|
162
|
-
case
|
|
165
|
+
case STOP_PARK_ARBITER_UNREACHABLE:
|
|
163
166
|
return { ok: false, reason: 'park_arbiter_unreachable', detail };
|
|
164
|
-
case
|
|
167
|
+
case STOP_NOT_LANDED:
|
|
165
168
|
return { ok: false, reason: 'not_landed', detail };
|
|
166
169
|
default:
|
|
167
170
|
// 开集:未知/未来 `stop.*`(以及 code 完全缺席的残破形)。诚实说「有冲突但我不认识它」,
|
|
@@ -177,14 +180,8 @@ export function classifyTaskStopConflict(e) {
|
|
|
177
180
|
* `DETACH_DURABLE_OFF_400_ANCHOR`(server 契约话)、`classifierVerdictWire.ts`
|
|
178
181
|
* `CLASSIFIER_DENY_SIGNATURE`(core lockstep 机器签名)——三处互指,理由逐条写在各自站点。
|
|
179
182
|
*/
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
'stop.park_arbiter_unreachable',
|
|
183
|
-
'stop.park_resume_won',
|
|
184
|
-
'stop.not_landed',
|
|
185
|
-
'stop.not_local',
|
|
186
|
-
'stop.parked',
|
|
187
|
-
];
|
|
183
|
+
/* 兜底字面认码用的已知集(顺序 = 长码优先,避免 `stop.parked` 抢走 `stop.park_resume_won`)
|
|
184
|
+
* 已随 G1 去字面化搬到 `engineErrorCodes.ts` 的 `STOP_CONFLICT_CODES` —— 顺序即语义那条注也在那边。 */
|
|
188
185
|
/** 停一个引擎侧任务句柄(新面 B)。只有 ok:true(=HTTP 200)算停了。绝不 throw。 */
|
|
189
186
|
export async function stopEngineTask(handle, opts) {
|
|
190
187
|
const cfg = engineWireTarget();
|
package/dist/toolResult.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { TaskResult, TaskStats } from '@sema-agent/sdk';
|
|
2
|
+
import { type ModelFallbackReason } from './engineErrorCodes.js';
|
|
1
3
|
/**
|
|
2
4
|
* 引擎会在 `tool_end.structured` 顶层 `type` 上发的**全部**取值。
|
|
3
5
|
* 用途**不是**「只处理这些」——下面的 switch 只认得其中一部分,认不得的照旧走 text 回落;
|
|
@@ -197,3 +199,87 @@ export declare function todoWriteToolUseResult(rawInput: unknown, oldTodos: unkn
|
|
|
197
199
|
export declare function reportFindingsToolUseResult(structured: unknown, rawInput: unknown): {
|
|
198
200
|
toolUseResult: unknown;
|
|
199
201
|
} | null;
|
|
202
|
+
/**
|
|
203
|
+
* 子代**完成着陆报告**卡(core `agents/subagent.ts#completedAgentCard` 的 wire 形)。
|
|
204
|
+
*
|
|
205
|
+
* 沿革:5.10.0 之前这张卡被 core 的投影白名单在上 wire 前整张丢掉 —— `tool_end.structured` 自己的
|
|
206
|
+
* 契约点名了它,而实际只有 `async_launched` 回执到得了消费方。5.10.0 修好了那条白名单,于是
|
|
207
|
+
* `type:"agent"` 从「只有一形」变成「两形按 `status` 分」({@link structuredToToolUseResult} 的
|
|
208
|
+
* `case 'agent'` 头注写了为什么本包不把它投成 CC 卡)。
|
|
209
|
+
*
|
|
210
|
+
* 🔴 **本读口只做窄化,不做解释**:每个键要么原样带出,要么(类型不符时)当缺席。绝不为缺席的量
|
|
211
|
+
* 造零值([honest-absence-not-fabricated-zero])—— `stats` 取不到就是 `undefined`,不是 `{}`。
|
|
212
|
+
* 🔴 **白名单不是闭集断言**:core 明写这张卡是「显式字段白名单」而非 spread(`checkpointToken` /
|
|
213
|
+
* `checkpointGate` 这两个**能力**位永不上 wire)。未来 core 往卡上加字段时,本读口读不到它们 ——
|
|
214
|
+
* 那是**加一个字段**的跟车工单,不是错误;宿主要原始全量随时可读 `structured` 本身。
|
|
215
|
+
*/
|
|
216
|
+
export interface CompletedAgentCard {
|
|
217
|
+
/** 子代**真实终态**(`completed` / `failed` / `blocked` / `suspended.*` …)。判别位;开集。 */
|
|
218
|
+
readonly status: string;
|
|
219
|
+
/** 子代任务 id(注意:完成卡是 camelCase `taskId`,启动回执是 snake_case `task_id`)。 */
|
|
220
|
+
readonly taskId: string;
|
|
221
|
+
/** 子代 transcript 句柄 —— 查询键,**不是**能力(续跑要部署侧的 resume 句柄)。 */
|
|
222
|
+
readonly sessionId?: string;
|
|
223
|
+
/** 子代类型名(roster 上的名字)。 */
|
|
224
|
+
readonly subagentType?: string;
|
|
225
|
+
/** 着陆报告正文 = 子代最终助手文本,**不含**模型面那层导向尾注。 */
|
|
226
|
+
readonly result?: string;
|
|
227
|
+
/** 到限截断时抢救出的最后模型文本。 */
|
|
228
|
+
readonly salvagedOutput?: string;
|
|
229
|
+
/** `outputSchema` 任务的结构化产出(不解释,原样)。 */
|
|
230
|
+
readonly structuredOutput?: unknown;
|
|
231
|
+
/** 失败归因三件之一:agent 自报无法推进的理由。 */
|
|
232
|
+
readonly blockedReason?: string;
|
|
233
|
+
/** 失败归因三件之二:人话。 */
|
|
234
|
+
readonly errorMessage?: string;
|
|
235
|
+
/** 失败归因三件之三:机器码(开集;词表见 `engineErrorCodes.ts`)。 */
|
|
236
|
+
readonly errorCode?: string;
|
|
237
|
+
/**
|
|
238
|
+
* 🆕 core 5.10.0 —— `errorCode` 的**机器可执行配对**:refusal 什么时候解除。
|
|
239
|
+
* 🔴 与 `retryable` 是**两个不同的问题**,可能同时出现且看起来矛盾(core 亲注):
|
|
240
|
+
* `retryable` = 「现在原样重发行不行」(`usage.window_exhausted` 上它是 false,且是对的);
|
|
241
|
+
* `retryAfterMs` = 「什么时候不再被拒」。调度器读前者+后者,立即重发的决策只读 `retryable`。
|
|
242
|
+
* 在场条件 = `usage.window_exhausted` 终局;**别拿它的在场性去判别的事**([2575] 自查③)。
|
|
243
|
+
*/
|
|
244
|
+
readonly retryAfterMs?: number;
|
|
245
|
+
/**
|
|
246
|
+
* 🆕 core 5.10.0 —— 降级实录(from/to/reason/atTurn)。形状与 `TaskResult.degraded` **同一个**
|
|
247
|
+
* (core 把子代 TaskResult 的这一位原样搬上卡),故直接借 SDK 的类型而不是再开一个 `unknown` 出口。
|
|
248
|
+
* 窄化 = 对象守卫(与 `terminalToSdkResult.degradedOf` 同一姿势):非对象一律当缺席。
|
|
249
|
+
*/
|
|
250
|
+
readonly degraded?: NonNullable<TaskResult['degraded']>;
|
|
251
|
+
/** 失败分类:错误种类。 */
|
|
252
|
+
readonly errorKind?: string;
|
|
253
|
+
/** 失败分类:原样重发是否可行(见 `retryAfterMs` 的配对说明)。 */
|
|
254
|
+
readonly retryable?: boolean;
|
|
255
|
+
/**
|
|
256
|
+
* run 总账(CC `AgentToolCompletedOutput` 对位)。同 `degraded`:core 搬的就是子代
|
|
257
|
+
* `TaskResult.stats`,所以借 SDK 的 `TaskStats`(它自带 `[key: string]: unknown` 开集索引,
|
|
258
|
+
* 新键不会因此被类型面挡住)。数值重算是渲染层的活,本层只窄化不解释。
|
|
259
|
+
*/
|
|
260
|
+
readonly stats?: TaskStats;
|
|
261
|
+
/** 实际服务该子代的模型 id(与 `resolvedModel` 同值,core 两个键都发)。 */
|
|
262
|
+
readonly model?: string;
|
|
263
|
+
/** 同上 —— core 的显式别名键;两键择一读即可,本读口都带出以免宿主二次猜。 */
|
|
264
|
+
readonly resolvedModel?: string;
|
|
265
|
+
/**
|
|
266
|
+
* 🆕 core 5.10.0 / [2577] —— **闭集单成员**:请求的模型词没绑上,子代跑在继承来的默认模型上。
|
|
267
|
+
* 🔴 **这不是错误**(回落行为本身没变,只是现在说出来了);缺席 = 正常绑定或压根没请求过词。
|
|
268
|
+
* 渲染建议(core 给的语义,UI 语言各端自裁):在生效 model 旁加「requested word did not bind /
|
|
269
|
+
* inherited」注记,**不因它的在场性改变别的渲染**。加成员 = BREAKING,见 {@link ModelFallbackReason}。
|
|
270
|
+
*/
|
|
271
|
+
readonly modelFallback?: ModelFallbackReason;
|
|
272
|
+
/** 子代跑在哪个 worktree(`isolation:'worktree'` 时)。 */
|
|
273
|
+
readonly worktreePath?: string;
|
|
274
|
+
/** 子代的逐工具调用统计。原样带出。 */
|
|
275
|
+
readonly toolStats?: unknown;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* 把一张 `tool_end.structured` 窄化成 {@link CompletedAgentCard} —— **仅当**它是 `type:"agent"` 的
|
|
279
|
+
* **完成**臂时。不是 agent 卡 / 是 `async_launched` 回执 / 缺 `status` 或 `taskId` ⇒ `null`。
|
|
280
|
+
*
|
|
281
|
+
* 🔴 分臂判据 = `status`(core 原话:"Branch on `status`, never on which keys are present")。
|
|
282
|
+
* 本函数是那条纪律在包内的**单一实现点**:三端(TUI / web / desktop)按同一份窄化读,
|
|
283
|
+
* 而不是各自抄一遍键名与 `!== 'async_launched'`。
|
|
284
|
+
*/
|
|
285
|
+
export declare function readCompletedAgentCard(structured: unknown): CompletedAgentCard | null;
|
package/dist/toolResult.js
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import { convertLeadingTabsToSpaces, getPatchFromContents } from './diff/patch.js';
|
|
28
28
|
import { markEngineWorkflowNotified, registerOutstandingBgTask, registerOutstandingWorkflowRun, } from './notifications.js';
|
|
29
29
|
import { parseWorkflowPollEnvelope, projectWorkflowTaskOutput } from './workflow.js';
|
|
30
|
+
import { asModelFallbackReason } from './engineErrorCodes.js';
|
|
30
31
|
// ══════════════════════════════════════════════════════════════════════════════════════════════
|
|
31
32
|
// ① structured 白名单(core design/116 的 details 顶层 `type` 集;[1840]§一 清单逐字)
|
|
32
33
|
// ══════════════════════════════════════════════════════════════════════════════════════════════
|
|
@@ -41,7 +42,6 @@ import { parseWorkflowPollEnvelope, projectWorkflowTaskOutput } from './workflow
|
|
|
41
42
|
*/
|
|
42
43
|
export const STRUCTURED_DETAIL_TYPES = new Set([
|
|
43
44
|
'edit',
|
|
44
|
-
'multiedit',
|
|
45
45
|
'create',
|
|
46
46
|
'update',
|
|
47
47
|
'bash',
|
|
@@ -59,7 +59,6 @@ export const STRUCTURED_DETAIL_TYPES = new Set([
|
|
|
59
59
|
'task',
|
|
60
60
|
'task-list',
|
|
61
61
|
'task-output',
|
|
62
|
-
'memory-saved',
|
|
63
62
|
'workflow-run',
|
|
64
63
|
'web-fetch',
|
|
65
64
|
'web-search',
|
|
@@ -70,7 +69,6 @@ export const STRUCTURED_DETAIL_TYPES = new Set([
|
|
|
70
69
|
'image',
|
|
71
70
|
'task-stop',
|
|
72
71
|
'tool-search',
|
|
73
|
-
'memory-recall',
|
|
74
72
|
'repo-map',
|
|
75
73
|
'fork',
|
|
76
74
|
'enter-plan-mode',
|
|
@@ -82,7 +80,29 @@ export const STRUCTURED_DETAIL_TYPES = new Set([
|
|
|
82
80
|
'worktree',
|
|
83
81
|
'monitor-start',
|
|
84
82
|
'path_not_in_root',
|
|
83
|
+
// ── core 5.10.0 词表换代(2026-08-04 提货批;engine-vocab 等值门对 5.10.0 实装物直证)──────
|
|
84
|
+
// 5.10.0 的 structured-card 审计把这张表变成**双向契约**(每个铸点都登记 / 每个登记词都真被产出)。
|
|
85
|
+
// 新登记 7 词 —— 它们的工具**一直**在铸卡,漏的只是登记,于是这 7 类卡从来没上过 wire:
|
|
86
|
+
// · `report-findings` 最坏(ReportFindings 的模型面被**故意**压成「N findings reported.」,
|
|
87
|
+
// 卡又丢了 ⇒ findings 模型看不到、宿主也看不到,两头落空);
|
|
88
|
+
// · `readonly_out_of_root` 与既有 `path_not_in_root` 是同一份「按字段数拒绝次数」契约的两半
|
|
89
|
+
// (一个在 shell 面、一个在 fs 面);
|
|
90
|
+
// · `schedule-wakeup` / `send-message` / `agent-transcript` / `a2a` / `document` 同理。
|
|
91
|
+
'readonly_out_of_root',
|
|
92
|
+
'report-findings',
|
|
93
|
+
'schedule-wakeup',
|
|
94
|
+
'send-message',
|
|
95
|
+
'agent-transcript',
|
|
96
|
+
'a2a',
|
|
97
|
+
'document',
|
|
85
98
|
]);
|
|
99
|
+
// 🔴 同批**删三词**(core 5.10.0 BREAKING「幽灵卡」清仓):`multiedit` / `memory-saved` /
|
|
100
|
+
// `memory-recall` —— 全树零铸点(MultiEdit/批量重放铸的是 `type:"edit"` 带 `edits[]`;core
|
|
101
|
+
// 根本不挂 memory 工具,memory 引擎是 runner 面子系统,没有工具面)。它们是「一个没人能兑现的
|
|
102
|
+
// 承诺」:按闭集分支的消费者为它们留了永不执行的臂。core 明写**不是**预留位——将来哪个工具
|
|
103
|
+
// 铸这种卡,和那个工具同一批加回来。
|
|
104
|
+
// ⚠️ 对本表的实际后果:这三个词此前会让 `structuredDetailType()` 判「structured 在场」⇒ 正则
|
|
105
|
+
// 退位。既然引擎从来不发它们,删掉是**零行为影响**的诚实化(不是收窄)。
|
|
86
106
|
/** structured 在场判别:顶层 `type` ∈ 白名单 ⇒ 返回该 type,否则 undefined(= 不在场)。 */
|
|
87
107
|
export function structuredDetailType(structured) {
|
|
88
108
|
if (typeof structured !== 'object' || structured === null)
|
|
@@ -686,6 +706,21 @@ modelText) {
|
|
|
686
706
|
case 'agent': {
|
|
687
707
|
// Agent 异步启动(core 1.272+ [666] 默认后台化主路径)。outputFile 是 schema 必填但 live lane
|
|
688
708
|
// 没有本地文件 ⇒ '' + canReadOutputFile:false(渲染文本因此省掉 file-tail 分支,与引擎模型面一致)。
|
|
709
|
+
//
|
|
710
|
+
// 🔴 **`type:"agent"` 自 core 5.10.0 起是两形一卡**([2575] 消费端自查②):`status` 是判别位 ——
|
|
711
|
+
// `"async_launched"` = 本臂的启动回执;**其它任何值** = 子代的**完成着陆报告**
|
|
712
|
+
// (`completedAgentCard`,core `agents/subagent.ts`;5.10.0 之前它被投影白名单在上 wire 前丢掉,
|
|
713
|
+
// 所以「`type:"agent"` ⇒ 后台启动回执」这个等式当时恰好成立,现在不成立了)。
|
|
714
|
+
// 🔴 **按 `status` 分臂,绝不按键集分臂** —— 这是 core 的原话,也是本行 `!==` 的全部理由。
|
|
715
|
+
// 键集判别在两形共有 `taskId`/`status`/`prompt`-无 的现实下必然错分。
|
|
716
|
+
// 🔴 完成卡**本批不投 CC 卡**(如实留白,不假装搬完):壳的 `AgentTool.outputSchema` 完成臂
|
|
717
|
+
// (`agentToolResultSchema` + `status:'completed'`)要 `content`/`totalToolUseCount`/
|
|
718
|
+
// `totalDurationMs`/`totalTokens`/`usage`/`prompt` 六件,而完成卡上**没有 `prompt`**、
|
|
719
|
+
// `status` 是子代真实终态(可能是 `failed`/`blocked`/`suspended.*`,壳的 zod 会当场拒),
|
|
720
|
+
// 统计也要从 `stats`/`toolStats` 重算 —— 那是**行为面**改动(要先答「失败的子代该渲成什么」),
|
|
721
|
+
// 按宪法三问单独走。返回 null ⇒ 回落模型面 text 路径,与 5.10.0 之前逐字同形,零回归。
|
|
722
|
+
// ⚠️ 完成卡本身**没有被吞**:`eventToSdkMessage` 的 `tool_end_result` 臂把 `structured`
|
|
723
|
+
// 原样透传给宿主,typed 读口见本文件的 {@link readCompletedAgentCard}。
|
|
689
724
|
if (s.status !== 'async_launched')
|
|
690
725
|
return null;
|
|
691
726
|
const agentId = typeof s.task_id === 'string' ? s.task_id : undefined;
|
|
@@ -853,3 +888,56 @@ export function reportFindingsToolUseResult(structured, rawInput) {
|
|
|
853
888
|
},
|
|
854
889
|
};
|
|
855
890
|
}
|
|
891
|
+
/**
|
|
892
|
+
* 把一张 `tool_end.structured` 窄化成 {@link CompletedAgentCard} —— **仅当**它是 `type:"agent"` 的
|
|
893
|
+
* **完成**臂时。不是 agent 卡 / 是 `async_launched` 回执 / 缺 `status` 或 `taskId` ⇒ `null`。
|
|
894
|
+
*
|
|
895
|
+
* 🔴 分臂判据 = `status`(core 原话:"Branch on `status`, never on which keys are present")。
|
|
896
|
+
* 本函数是那条纪律在包内的**单一实现点**:三端(TUI / web / desktop)按同一份窄化读,
|
|
897
|
+
* 而不是各自抄一遍键名与 `!== 'async_launched'`。
|
|
898
|
+
*/
|
|
899
|
+
export function readCompletedAgentCard(structured) {
|
|
900
|
+
if (structured === null || typeof structured !== 'object')
|
|
901
|
+
return null;
|
|
902
|
+
const s = structured;
|
|
903
|
+
if (s.type !== 'agent')
|
|
904
|
+
return null;
|
|
905
|
+
const status = s.status;
|
|
906
|
+
// 分臂:`async_launched` 是回执臂(归 `structuredToToolUseResult` 的 `case 'agent'`),不是本臂。
|
|
907
|
+
if (typeof status !== 'string' || status.length === 0 || status === 'async_launched')
|
|
908
|
+
return null;
|
|
909
|
+
const taskId = s.taskId;
|
|
910
|
+
// 身份缺席 ⇒ 这不是一张可用的完成卡(诚实拒,不铸空 id)。
|
|
911
|
+
if (typeof taskId !== 'string' || taskId.length === 0)
|
|
912
|
+
return null;
|
|
913
|
+
const str = (v) => (typeof v === 'string' && v.length > 0 ? v : undefined);
|
|
914
|
+
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : undefined);
|
|
915
|
+
return {
|
|
916
|
+
status,
|
|
917
|
+
taskId,
|
|
918
|
+
...(str(s.sessionId) !== undefined ? { sessionId: str(s.sessionId) } : {}),
|
|
919
|
+
...(str(s.subagent_type) !== undefined ? { subagentType: str(s.subagent_type) } : {}),
|
|
920
|
+
// `result` 允许空串(子代没产出正文是一个诚实的事实),故这里不用 `str` 的非空过滤。
|
|
921
|
+
...(typeof s.result === 'string' ? { result: s.result } : {}),
|
|
922
|
+
...(typeof s.salvagedOutput === 'string' ? { salvagedOutput: s.salvagedOutput } : {}),
|
|
923
|
+
...(s.structuredOutput !== undefined ? { structuredOutput: s.structuredOutput } : {}),
|
|
924
|
+
...(str(s.blockedReason) !== undefined ? { blockedReason: str(s.blockedReason) } : {}),
|
|
925
|
+
...(str(s.errorMessage) !== undefined ? { errorMessage: str(s.errorMessage) } : {}),
|
|
926
|
+
...(str(s.errorCode) !== undefined ? { errorCode: str(s.errorCode) } : {}),
|
|
927
|
+
...(num(s.retryAfterMs) !== undefined ? { retryAfterMs: num(s.retryAfterMs) } : {}),
|
|
928
|
+
// 对象守卫(与 terminalToSdkResult.degradedOf 同姿势):非对象/null 一律当缺席,不硬塞。
|
|
929
|
+
...(typeof s.degraded === 'object' && s.degraded !== null
|
|
930
|
+
? { degraded: s.degraded }
|
|
931
|
+
: {}),
|
|
932
|
+
...(str(s.error_kind) !== undefined ? { errorKind: str(s.error_kind) } : {}),
|
|
933
|
+
...(typeof s.retryable === 'boolean' ? { retryable: s.retryable } : {}),
|
|
934
|
+
...(typeof s.stats === 'object' && s.stats !== null ? { stats: s.stats } : {}),
|
|
935
|
+
...(str(s.model) !== undefined ? { model: str(s.model) } : {}),
|
|
936
|
+
...(str(s.resolvedModel) !== undefined ? { resolvedModel: str(s.resolvedModel) } : {}),
|
|
937
|
+
...(asModelFallbackReason(s.modelFallback) !== undefined
|
|
938
|
+
? { modelFallback: asModelFallbackReason(s.modelFallback) }
|
|
939
|
+
: {}),
|
|
940
|
+
...(str(s.worktreePath) !== undefined ? { worktreePath: str(s.worktreePath) } : {}),
|
|
941
|
+
...(s.toolStats !== undefined ? { toolStats: s.toolStats } : {}),
|
|
942
|
+
};
|
|
943
|
+
}
|