@sema-agent/client-core 0.71.4 → 0.72.1

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +3 -3
  3. package/dist/adapter/activeRunSelfHeal.d.ts +0 -2
  4. package/dist/adapter/activeRunSelfHeal.js +1 -1
  5. package/dist/engineErrorCodes.d.ts +12 -20
  6. package/dist/engineErrorCodes.js +18 -6
  7. package/dist/finalVerifyWire.d.ts +0 -6
  8. package/dist/finalVerifyWire.js +1 -1
  9. package/dist/fleet/fleetProjection.d.ts +0 -44
  10. package/dist/fleet/fleetProjection.js +9 -9
  11. package/dist/hitl/armedGateRegistry.d.ts +0 -6
  12. package/dist/hitl/armedGateRegistry.js +3 -3
  13. package/dist/hitl/gateIdentity.d.ts +0 -4
  14. package/dist/hitl/gateIdentity.js +2 -2
  15. package/dist/hitl/hitlBridge.d.ts +0 -2
  16. package/dist/hitl/hitlBridge.js +1 -1
  17. package/dist/hitl/planReviewWire.d.ts +0 -2
  18. package/dist/hitl/planReviewWire.js +1 -1
  19. package/dist/model/catalogLoader.d.ts +0 -6
  20. package/dist/model/catalogLoader.js +3 -3
  21. package/dist/model/providerAuth.d.ts +0 -8
  22. package/dist/model/providerAuth.js +4 -4
  23. package/dist/model/providerCatalog.d.ts +0 -2
  24. package/dist/model/providerCatalog.js +1 -1
  25. package/dist/modelCapabilityProbe.d.ts +0 -4
  26. package/dist/modelCapabilityProbe.js +2 -2
  27. package/dist/oneShotWireCaps.d.ts +0 -2
  28. package/dist/oneShotWireCaps.js +1 -1
  29. package/dist/peerFrames.d.ts +0 -11
  30. package/dist/peerFrames.js +1 -1
  31. package/dist/promptProfileWireCaps.d.ts +0 -1
  32. package/dist/promptProfileWireCaps.js +1 -1
  33. package/dist/resumeRefusalCopy.d.ts +74 -0
  34. package/dist/resumeRefusalCopy.js +42 -1
  35. package/dist/skillsWireCaps.d.ts +31 -1
  36. package/dist/skillsWireCaps.js +28 -7
  37. package/dist/toolResult.d.ts +0 -17
  38. package/dist/toolResult.js +1 -1
  39. package/dist/ultracodeWireCaps.d.ts +0 -3
  40. package/dist/ultracodeWireCaps.js +1 -1
  41. package/docs/INTEGRATION-CLIENTS.md +79 -23
  42. package/package.json +1 -1
@@ -56,12 +56,6 @@ export declare const DEFAULT_CATALOG_SOURCES: readonly string[];
56
56
  export declare const CATALOG_DEFAULT_HOSTS: readonly string[];
57
57
  /** 候选链的 env 键(一键安装脚本改这里;settings.json `env` 块同名同义,见 design/166 §2)。 */
58
58
  export declare const CATALOG_SOURCES_ENV = "SEMA_CATALOG_SOURCES";
59
- /** 每源传输预算(design/166 §1:onboard 不能被网络拖住)。 */
60
- export declare const DEFAULT_CATALOG_TIMEOUT_MS = 3500;
61
- /** 缓存相对 configHome 的落点(design/166 §3)。 */
62
- export declare const CATALOG_CACHE_RELATIVE_PATH = "cache/model-catalog.json";
63
- /** 缓存陈旧阈值:30 天(照用,但如实标 stale)。 */
64
- export declare const CATALOG_CACHE_STALE_MS: number;
65
59
  /**
66
60
  * 单源的**传输层**结局。
67
61
  *
@@ -59,11 +59,11 @@ export const CATALOG_DEFAULT_HOSTS = ['raw.githubusercontent.com', 'cdn.jsdelivr
59
59
  /** 候选链的 env 键(一键安装脚本改这里;settings.json `env` 块同名同义,见 design/166 §2)。 */
60
60
  export const CATALOG_SOURCES_ENV = 'SEMA_CATALOG_SOURCES';
61
61
  /** 每源传输预算(design/166 §1:onboard 不能被网络拖住)。 */
62
- export const DEFAULT_CATALOG_TIMEOUT_MS = 3500;
62
+ const DEFAULT_CATALOG_TIMEOUT_MS = 3500;
63
63
  /** 缓存相对 configHome 的落点(design/166 §3)。 */
64
- export const CATALOG_CACHE_RELATIVE_PATH = 'cache/model-catalog.json';
64
+ const CATALOG_CACHE_RELATIVE_PATH = 'cache/model-catalog.json';
65
65
  /** 缓存陈旧阈值:30 天(照用,但如实标 stale)。 */
66
- export const CATALOG_CACHE_STALE_MS = 30 * 24 * 60 * 60 * 1000;
66
+ const CATALOG_CACHE_STALE_MS = 30 * 24 * 60 * 60 * 1000;
67
67
  /** 单源允许的重定向跳数上限(有界:302 环不许把 onboard 转死)。 */
68
68
  const MAX_REDIRECTS = 5;
69
69
  /** 异常 → 短 detail(只取 message 前段,绝不带栈、绝不带载荷;与 catalog.ts 同一口径)。 */
@@ -131,14 +131,6 @@ export interface DeviceCodeAuthOptions {
131
131
  /** 单次 HTTP 预算(缺省 `DEVICE_CODE_HTTP_TIMEOUT_MS`)。 */
132
132
  timeoutMs?: number;
133
133
  }
134
- /** 轮询间隔下限(RFC 8628 建议 5s;服务端给更大的值时听服务端的)。 */
135
- export declare const DEVICE_CODE_MIN_INTERVAL_MS = 5000;
136
- /** 会话墙钟上限(design/166 §5:15min)。服务端 `expires_in` 更短时听服务端的。 */
137
- export declare const DEVICE_CODE_MAX_LIFETIME_MS: number;
138
- /** 429 / slow_down 的退避增量(RFC 8628 §3.5 就是「加 5 秒」)。 */
139
- export declare const DEVICE_CODE_BACKOFF_STEP_MS = 5000;
140
- /** 单次 HTTP 预算。 */
141
- export declare const DEVICE_CODE_HTTP_TIMEOUT_MS = 10000;
142
134
  /**
143
135
  * 开一次设备码会话 —— **端点由调用方给**(包内表里那一行,或门的合成 descriptor)。
144
136
  * 🔴 端点是**编译进包的常量**,不是远端数据,所以本函数不对它再做白名单;真正的门是
@@ -28,13 +28,13 @@ export function providerAuthMethods(preset) {
28
28
  return out;
29
29
  }
30
30
  /** 轮询间隔下限(RFC 8628 建议 5s;服务端给更大的值时听服务端的)。 */
31
- export const DEVICE_CODE_MIN_INTERVAL_MS = 5000;
31
+ const DEVICE_CODE_MIN_INTERVAL_MS = 5000;
32
32
  /** 会话墙钟上限(design/166 §5:15min)。服务端 `expires_in` 更短时听服务端的。 */
33
- export const DEVICE_CODE_MAX_LIFETIME_MS = 15 * 60 * 1000;
33
+ const DEVICE_CODE_MAX_LIFETIME_MS = 15 * 60 * 1000;
34
34
  /** 429 / slow_down 的退避增量(RFC 8628 §3.5 就是「加 5 秒」)。 */
35
- export const DEVICE_CODE_BACKOFF_STEP_MS = 5000;
35
+ const DEVICE_CODE_BACKOFF_STEP_MS = 5000;
36
36
  /** 单次 HTTP 预算。 */
37
- export const DEVICE_CODE_HTTP_TIMEOUT_MS = 10_000;
37
+ const DEVICE_CODE_HTTP_TIMEOUT_MS = 10_000;
38
38
  /** 异常 → 短 detail(绝不带栈、绝不带响应体)。 */
39
39
  function shortError(e) {
40
40
  return (e instanceof Error ? e.message : String(e)).slice(0, 160);
@@ -47,8 +47,6 @@ export interface ProviderCatalogRow {
47
47
  /** 没账号时的注册入口;缺席 = 用 consoleUrl 那条 */
48
48
  signupUrl?: string;
49
49
  }
50
- /** api 家族的行内短名(目录行/芯片行两端同一个词,不各自缩写)。 */
51
- export declare const providerApiShortName: (api: ProviderPreset["api"]) => string;
52
50
  /**
53
51
  * 全表 → 目录行。**顺序 = 表序**(约束②):这里没有 filter,也没有 sort。
54
52
  * `presets` 入参只给测试用(注入一张小表),产品路径恒走 `PROVIDER_PRESETS`。
@@ -34,7 +34,7 @@ const hostOf = (u) => {
34
34
  }
35
35
  };
36
36
  /** api 家族的行内短名(目录行/芯片行两端同一个词,不各自缩写)。 */
37
- export const providerApiShortName = (api) => api === 'anthropic-messages' ? 'anthropic' : 'openai';
37
+ const providerApiShortName = (api) => api === 'anthropic-messages' ? 'anthropic' : 'openai';
38
38
  /**
39
39
  * 全表 → 目录行。**顺序 = 表序**(约束②):这里没有 filter,也没有 sort。
40
40
  * `presets` 入参只给测试用(注入一张小表),产品路径恒走 `PROVIDER_PRESETS`。
@@ -186,10 +186,6 @@ export interface ModelProbeResult {
186
186
  verdict: string;
187
187
  evidence: ModelProbeEvidence;
188
188
  }
189
- /** 固定题面 —— 短、无歧义、任何模型都答得出,且答案长度可判。 */
190
- export declare const PROBE_PROMPT = "Reply with exactly OK";
191
- /** 输出上限:够答一句 `OK`,又不至于让一台在思考的网关烧掉一整个预算。 */
192
- export declare const PROBE_MAX_TOKENS = 64;
193
189
  /**
194
190
  * 铸一只探测请求体。
195
191
  * @param entry 被探测的条目(只取 `id`)
@@ -95,9 +95,9 @@ export const MODEL_PROBE_VERDICTS = Object.freeze([
95
95
  'inconclusive',
96
96
  ]);
97
97
  /** 固定题面 —— 短、无歧义、任何模型都答得出,且答案长度可判。 */
98
- export const PROBE_PROMPT = 'Reply with exactly OK';
98
+ const PROBE_PROMPT = 'Reply with exactly OK';
99
99
  /** 输出上限:够答一句 `OK`,又不至于让一台在思考的网关烧掉一整个预算。 */
100
- export const PROBE_MAX_TOKENS = 64;
100
+ const PROBE_MAX_TOKENS = 64;
101
101
  /**
102
102
  * 铸一只探测请求体。
103
103
  * @param entry 被探测的条目(只取 `id`)
@@ -29,8 +29,6 @@
29
29
  * 那个)。PURE — callers pass `env`;caller gates to LIVE mode(mock request shape 不变)。
30
30
  */
31
31
  import { type EnvLike } from './hostEnv.js';
32
- /** The env key the shell reads(SEMA_ 命名空间;`-p` 车道 per-request 意图面,非引擎部署面)。 */
33
- export declare const ONE_SHOT_ENV: "SEMA_HEADLESS_ONE_SHOT";
34
32
  /**
35
33
  * ENV source → `true`(缺省 ON = `-p` 提交的一次性本性),只有显式 falsy 逃生口
36
34
  * ({@link envFlagOff} 拼写集)⇒ `undefined`(不 stamp ⇒ 引擎缺省交互态指引)。
@@ -31,7 +31,7 @@
31
31
  import { hostEnv } from './hostEnv.js';
32
32
  import { envFlagOff } from './envFlag.js';
33
33
  /** The env key the shell reads(SEMA_ 命名空间;`-p` 车道 per-request 意图面,非引擎部署面)。 */
34
- export const ONE_SHOT_ENV = 'SEMA_HEADLESS_ONE_SHOT';
34
+ const ONE_SHOT_ENV = 'SEMA_HEADLESS_ONE_SHOT';
35
35
  /**
36
36
  * ENV source → `true`(缺省 ON = `-p` 提交的一次性本性),只有显式 falsy 逃生口
37
37
  * ({@link envFlagOff} 拼写集)⇒ `undefined`(不 stamp ⇒ 引擎缺省交互态指引)。
@@ -89,17 +89,6 @@ export type PeerFrameProjection = AgentMessageFrame | CrossSessionMessageFrame |
89
89
  * 后者是一张 15 键白名单,这三条载体一个都不在里面。
90
90
  */
91
91
  export declare function classifyPeerNotification(n: Record<string, unknown>): PeerFrameProjection | null;
92
- /**
93
- * core `ENGINE_AUTHORITY_ENVELOPE_TAGS` 的镜像(`core/untrusted-text.ts::ENGINE_ENVELOPES` 里 kind="authority" 的标签):
94
- * 这些信封在模型面/转录面代表**引擎权威**,一段同事正文里出现它们就是伪造。core 的 `neutralizePeerBody`
95
- * (= `sanitizeUntrustedText(body, PEER_BODY_ENVELOPE_TAGS)`)在渲染信封**之前**先把它们拆火(`<` 后插 ZWSP),
96
- * 本模块首版只抄了后一步(同名信封拆火)——异源发包扫描 [high] 实证:子代正文里一段
97
- * `<task-notification><task-id>victim</task-id><status>completed</status>…` 会原样进转录 block,
98
- * 宿主 resume 时 `parseTranscriptNotificationSeeds` 把它读成真完成通知 ⇒ 去重台账被毒化,受害 run 的
99
- * 真完成通知随后被跨通道臂整条吞掉。这里补上那一步,并与 core 同形(`<\s*\/?\s*(tag)(\s[^>]*)?>` 全大小写)。
100
- * 🔴 单向(与 core 一致):端的解析腿**不**还原 ZWSP —— 拆掉的权威标签就该永远是拆掉的。
101
- */
102
- export declare const AUTHORITY_ENVELOPE_TAGS: readonly ["system-reminder", "task-notification", "new-diagnostics", "user_memory", "scope", "skills", "total_tokens", "instruction-files"];
103
92
  /**
104
93
  * 投影 → 转录行文本(端的消息面按标签分派的那一份)。
105
94
  *
@@ -225,7 +225,7 @@ export function classifyPeerNotification(n) {
225
225
  * 真完成通知随后被跨通道臂整条吞掉。这里补上那一步,并与 core 同形(`<\s*\/?\s*(tag)(\s[^>]*)?>` 全大小写)。
226
226
  * 🔴 单向(与 core 一致):端的解析腿**不**还原 ZWSP —— 拆掉的权威标签就该永远是拆掉的。
227
227
  */
228
- export const AUTHORITY_ENVELOPE_TAGS = Object.freeze([
228
+ const AUTHORITY_ENVELOPE_TAGS = Object.freeze([
229
229
  'system-reminder',
230
230
  'task-notification',
231
231
  'new-diagnostics',
@@ -12,6 +12,5 @@
12
12
  * 同款)。CALLER 前置 live 门(mock 形不变)。
13
13
  */
14
14
  import { type EnvLike } from './hostEnv.js';
15
- export declare const PROMPT_PROFILE_ENV: "SEMA_PROMPT_PROFILE";
16
15
  export type PromptProfile = 'simple' | 'classic';
17
16
  export declare function promptProfileFromEnv(env?: EnvLike): PromptProfile | undefined;
@@ -12,7 +12,7 @@
12
12
  * 同款)。CALLER 前置 live 门(mock 形不变)。
13
13
  */
14
14
  import { hostEnv } from './hostEnv.js';
15
- export const PROMPT_PROFILE_ENV = 'SEMA_PROMPT_PROFILE';
15
+ const PROMPT_PROFILE_ENV = 'SEMA_PROMPT_PROFILE';
16
16
  export function promptProfileFromEnv(env = hostEnv()) {
17
17
  const raw = env[PROMPT_PROFILE_ENV];
18
18
  const s = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
@@ -1,3 +1,44 @@
1
+ /**
2
+ * src/resumeRefusalCopy.ts — resume 族**拒绝文案的三端单一铸点**(L-102 下半场,0.58.0)。
3
+ *
4
+ * ── 这一件上收的是什么 ──────────────────────────────────────────────────────────────────────
5
+ * 0.57.0 已经把 L-102 的**事实读数**上收了({@link resumeRetryLaterFromError}:命中哪个码、
6
+ * server 给没给窗、等一会儿有没有用)。留在壳里的另一半是**人话** —— cli 1.0.x 的
7
+ * `src/sema/resumeRefusalCopy.ts` 自铸了一份判型 + 三句 `·` 分段文案,而 web-client / desktop
8
+ * 各自要么没有、要么将来会再抄一份。三端各抄一份文案 = 同一次拒绝在三个端上说三句不一样的话,
9
+ * 而这三句话回答的是**同一个安全问题**:「这次拒绝有没有消费掉我的决定 / 这张卡还能不能再决」。
10
+ * ⇒ 文案与判型同属**判定归包、呈现归端**里的「判定」那一半,本模块是它的单一铸点。
11
+ *
12
+ * ── 与 0.57.0 §21 那一口的分工(两个闭集刻意分家)──────────────────────────────────────────
13
+ * · {@link RESUME_RETRY_LATER_CODES}(engineErrorCodes.ts,0.57.0)闭的是「**server 在哪些码上铸
14
+ * `retryAfterSec`**」——它回答**能不能等**;
15
+ * · {@link RESUME_REFUSAL_CODES}(本文件)闭的是「**哪些 resume 拒绝有人话可补**」——它回答
16
+ * **该对人说什么**。
17
+ * 两集**交于** `resume.preflight_rejected` 一码、**各有**一个独占成员
18
+ * (`resume.usage_window_exhausted` 只在前者 / `resume.placement_mismatch` 只在后者),所以它们
19
+ * 不是同一张表的两个名字。🔴 **本模块不复制判定**:`preflight_rejected` 的窗与可等性一律**转调**
20
+ * `resumeRetryLaterFromError`,本文件里没有第二个 `retryAfterSec` 窄读器
21
+ * ([paired-mechanisms-must-share-premise]:两处各自判必然在某一格上分叉)。
22
+ *
23
+ * ── `retryAfterSec` 的窄读域比 cli 1.0.x 现行的更窄(端提货时要知道的差分)──────────────────
24
+ * cli 那份自铸读器收的是「有限数 ∧ ≥0」;本口沿用 §21 的域 =「**整数 ∧ ≥1**」,坏值一律降缺席:
25
+ * · server 的铸键逐字是「ms → 秒**向上取整**、**下限 1**」⇒ 真供给里不存在 0 / 负数 / 小数;
26
+ * · 放行 `0` 就是对端说「立刻重试」,而 resume 是 AT-MOST-ONCE 的**有副作用**动作(叫醒 = 真跑
27
+ * 一轮)—— 一个 0 会把「等一会儿」变成热循环;
28
+ * · 放行小数会让端渲出「等 0.4 秒」这种上游从未说过的量。
29
+ * ⇒ 壳提货后在这三形上的**行为会变**(从「渲一个 0 秒等待」变成「不渲等待行」),这是**修好**
30
+ * 不是回归([verdict-must-accept-stronger-form])。
31
+ *
32
+ * ── 开集纪律(与 cli 那份逐字同律)────────────────────────────────────────────────────────
33
+ * 🔴 判型是**闭集**的(只认下面两个码),消费面是**开集**的:不认识的码一律 `null` ⇒ 端一个字
34
+ * 都不补 ⇒ 机器可读那一行(`API Error: <status> <errorCode> · <msg>`)原样上屏。
35
+ * **绝不按前缀/子串猜一族码的语义** —— `resume.` 前缀下同时住着「等就好」「换参数」「没救了」
36
+ * 三种处置。
37
+ * 🔴 文案**不复述** server 的 message(那一行已经在屏上了),只补 message 里没有的那件事。
38
+ * 🔴 文案里**不铸等待的秒数**:那一行由端用 {@link ResumeRefusalDetail.retryAfterSec} 单独渲
39
+ * (cli 的 `retryAfterHint` 是那一位的唯一取值口),在这里再说一遍就是同一个数字上屏两遍。
40
+ */
41
+ import { RESUME_CONTEXT_UNAVAILABLE } from './engineErrorCodes.js';
1
42
  /**
2
43
  * 本模块认识的 resume 拒绝码(**闭集**)—— 「有人话可补」的那两个。
3
44
  *
@@ -83,3 +124,36 @@ export declare function resumeReopenFromError(err: unknown): ResumeReopenDetail
83
124
  * 🔴 零内部车道词(DS-04):只说用户能做的事;不复述 server 的 message(那一行已在屏上)。
84
125
  */
85
126
  export declare function resumeReopenContent(detail: ResumeReopenDetail): string;
127
+ /** {@link resumeContextUnavailableFromError} 的结构化读数。 */
128
+ export interface ResumeContextUnavailableDetail {
129
+ /** 恒为 {@link RESUME_CONTEXT_UNAVAILABLE}(留字段是为了与本文件其它读数同形,消费端可按 `code` 分派)。 */
130
+ code: typeof RESUME_CONTEXT_UNAVAILABLE;
131
+ /**
132
+ * 该部署当下的会话保留时长(整数秒;server 逐字 = `REAP_RUN_STALE_SEC` 真值,与回收器用的是同一个值)。
133
+ * 🔴 窄读域 = 整数 ∧ ≥1(与 `retryAfterSec` 同律);坏值**降缺席**不降 0。
134
+ * 🔴 缺席 = **没读到**:老 server(<7.80.1)不铸这一位,**sdk 9.6.0 也还没把它带到错误对象上**
135
+ * (`ConflictError` 只带 activeTaskId 三件)⇒ 经 sdk 的端今天恒缺席;这不是「保留时长为零」,
136
+ * 端渲「部署保留时长」那一行时缺席就**不渲**([honest-absence-not-fabricated-zero])。
137
+ */
138
+ staleAfterSec?: number;
139
+ /** 这条 run 的 id(体上既有键;非空串才在场)—— 出路①「自己恢复这条 run」用的句柄。缺席同上(sdk 未带)。 */
140
+ runId?: string;
141
+ }
142
+ /**
143
+ * 被 catch 的错误 → 「批准无处投递:上下文当前不可用」读数;不是本码 ⇒ `null`(绝不误吃别的 `conflict.*`)。
144
+ *
145
+ * 🔴 duck-typed 不 `instanceof`、键位只认 `errorCode`(与 {@link resumeRefusalFromError} 同律;退役 `code` 槽不做兼容)。
146
+ * 🔴 **本码不进两个 resume 闭集**:server 在上下文被回收 **或读取失败** 时都发它,能不能等 wire 上没有判别位 ⇒ 不进「可等」集;
147
+ * 也不是「换参数重来」⇒ 不进 refusal 集;出路见文案第三句(按 `runId` 在场与否)。
148
+ * 🔴 `staleAfterSec` / `runId` 都按结构读:server 铸在体上、sdk 今天没搬上来 ⇒ 缺席;sdk 出键当天自动填满,本包零改动。
149
+ */
150
+ export declare function resumeContextUnavailableFromError(err: unknown): ResumeContextUnavailableDetail | null;
151
+ /**
152
+ * 读数 → 人话三句(`·` 分段,与 {@link resumeRefusalContent} 同形):①发生了什么 · ②你的决定没被消费 · ③出路。
153
+ * 🔴 **只凭码不推断原因**(异源对抗复审实抓):server 在「上下文被回收」与「上下文读取失败(如存储故障)」两种情况下
154
+ * 都返回本码,wire 上没有判别位 ⇒ 第一句只说「当前没有可用的上下文来继续它」,不说「等得太久 / 已被回收」。
155
+ * 🔴 **调大保留时长救不回这一次**(上下文已删就是删了),它只能避免以后再发生 ⇒ 第三句按 `runId` 在场与否给恢复指引,
156
+ * 保留时长只作预防句。零内部车道词;不铸秒数(保留时长由端按 {@link ResumeContextUnavailableDetail.staleAfterSec}
157
+ * 单独渲,缺席就不渲)。
158
+ */
159
+ export declare function resumeContextUnavailableContent(d: ResumeContextUnavailableDetail): string;
@@ -38,7 +38,7 @@
38
38
  * 🔴 文案里**不铸等待的秒数**:那一行由端用 {@link ResumeRefusalDetail.retryAfterSec} 单独渲
39
39
  * (cli 的 `retryAfterHint` 是那一位的唯一取值口),在这里再说一遍就是同一个数字上屏两遍。
40
40
  */
41
- import { RESUME_PLACEMENT_MISMATCH, RESUME_PREFLIGHT_REJECTED, RESUME_REOPEN_CODES, RESUME_ENV_FAILED, RESUME_TOOL_UNAVAILABLE, } from './engineErrorCodes.js';
41
+ import { RESUME_CONTEXT_UNAVAILABLE, RESUME_PLACEMENT_MISMATCH, RESUME_PREFLIGHT_REJECTED, RESUME_REOPEN_CODES, RESUME_ENV_FAILED, RESUME_TOOL_UNAVAILABLE, } from './engineErrorCodes.js';
42
42
  import { resumeRetryLaterFromError } from './wireErrorTriage.js';
43
43
  /**
44
44
  * 本模块认识的 resume 拒绝码(**闭集**)—— 「有人话可补」的那两个。
@@ -157,3 +157,44 @@ export function resumeReopenContent(detail) {
157
157
  'Re-fetch the pending approvals and decide again on an engine where the same tool is available',
158
158
  ].join(' · ');
159
159
  }
160
+ /**
161
+ * 被 catch 的错误 → 「批准无处投递:上下文当前不可用」读数;不是本码 ⇒ `null`(绝不误吃别的 `conflict.*`)。
162
+ *
163
+ * 🔴 duck-typed 不 `instanceof`、键位只认 `errorCode`(与 {@link resumeRefusalFromError} 同律;退役 `code` 槽不做兼容)。
164
+ * 🔴 **本码不进两个 resume 闭集**:server 在上下文被回收 **或读取失败** 时都发它,能不能等 wire 上没有判别位 ⇒ 不进「可等」集;
165
+ * 也不是「换参数重来」⇒ 不进 refusal 集;出路见文案第三句(按 `runId` 在场与否)。
166
+ * 🔴 `staleAfterSec` / `runId` 都按结构读:server 铸在体上、sdk 今天没搬上来 ⇒ 缺席;sdk 出键当天自动填满,本包零改动。
167
+ */
168
+ export function resumeContextUnavailableFromError(err) {
169
+ if (typeof err !== 'object' || err === null)
170
+ return null;
171
+ const e = err;
172
+ if (e.errorCode !== RESUME_CONTEXT_UNAVAILABLE)
173
+ return null;
174
+ const sec = e.staleAfterSec;
175
+ const staleSec = typeof sec === 'number' && Number.isInteger(sec) && sec >= 1 ? sec : undefined;
176
+ const runId = typeof e.runId === 'string' && e.runId.length > 0 ? e.runId : undefined;
177
+ return {
178
+ code: RESUME_CONTEXT_UNAVAILABLE,
179
+ ...(staleSec !== undefined ? { staleAfterSec: staleSec } : {}),
180
+ ...(runId !== undefined ? { runId } : {}),
181
+ };
182
+ }
183
+ /**
184
+ * 读数 → 人话三句(`·` 分段,与 {@link resumeRefusalContent} 同形):①发生了什么 · ②你的决定没被消费 · ③出路。
185
+ * 🔴 **只凭码不推断原因**(异源对抗复审实抓):server 在「上下文被回收」与「上下文读取失败(如存储故障)」两种情况下
186
+ * 都返回本码,wire 上没有判别位 ⇒ 第一句只说「当前没有可用的上下文来继续它」,不说「等得太久 / 已被回收」。
187
+ * 🔴 **调大保留时长救不回这一次**(上下文已删就是删了),它只能避免以后再发生 ⇒ 第三句按 `runId` 在场与否给恢复指引,
188
+ * 保留时长只作预防句。零内部车道词;不铸秒数(保留时长由端按 {@link ResumeContextUnavailableDetail.staleAfterSec}
189
+ * 单独渲,缺席就不渲)。
190
+ */
191
+ export function resumeContextUnavailableContent(d) {
192
+ const third = d.runId !== undefined
193
+ ? '错误里带了这条运行的 id,请用它自己恢复这条运行再决;调大会话保留时长只能避免以后再发生,救不回这一次'
194
+ : '错误里没有带运行 id,请回到运行列表找到这条运行自己恢复它再决;调大会话保留时长只能避免以后再发生,救不回这一次';
195
+ return [
196
+ '这个批准无处投递:发起这次运行的会话当前没有可用的上下文来继续它',
197
+ '什么都没有被决定,你的决定没有被消费,这张卡仍在等待',
198
+ third,
199
+ ].join(' · ');
200
+ }
@@ -77,4 +77,34 @@ export declare function toCappedSpec(name: string, description: string, whenToUs
77
77
  * unit-testable. Only MODEL-INVOCABLE prompt skills are sent — UI-only / `disableModelInvocation` commands
78
78
  * are excluded (engine injection feeds the model's Skill tool, not the slash menu).
79
79
  */
80
- export declare function skillCommandsToSpecs(cmds: SkillCmdLike[], resolveBody: (cmd: SkillCmdLike) => string | null): SkillSpec[];
80
+ /** `SkillSpec` 上 wire 的形:0.72.1 起可带 `baseDir`(server ≥7.80.1 / core 7.20.0 #799;sdk 9.6.0 的 `SkillSpec` 还没声明它,
81
+ * 所以本包用交叉型出口 —— 赋给 `TaskRequestBody.skills: SkillSpec[]` 零摩擦)。 */
82
+ export type SkillSpecOnWire = SkillSpec & {
83
+ baseDir?: string;
84
+ };
85
+ /** {@link skillCommandsToSpecs} 的开关。 */
86
+ export interface SkillSpecsOptions {
87
+ /**
88
+ * 工具是否跑在**加载这些技能的这台机器**上(单用户 host 车道 = true)。
89
+ * 🔴 只有 true 才带 `baseDir`:它是「工具执行环境那台机器上的坐标」,沙箱 / 远端车道(k8s / e2b / ssh / device)上
90
+ * 本机路径在那边不存在,带过去只是假线索 ⇒ 缺席(fail-closed,与 server 出厂自扫技能在那些车道上不带同律)。
91
+ * 缺席 = 引擎不加首行、不做 `${SKILL_ROOT}` 替换,结果与 0.72.0 之前逐字节相同。
92
+ */
93
+ toolsRunHere?: boolean;
94
+ /**
95
+ * server 判 `baseDir` 用的是它自己那台机器上的 `node:path.isAbsolute`(平台相关):Linux / macOS 上 `C:\\x` **不是**绝对路径
96
+ * (整条提交 400),Windows 上 UNC `\\\\host\\share\\x` 才算。本包不知道 server 跑在哪个平台 ⇒ 由调用方声明;缺席 = `'posix'`
97
+ * (server 出厂镜像与三端 host 车道的常态)。声明错 = server 400 整拒,不是静默降级。
98
+ */
99
+ platform?: 'posix' | 'win32';
100
+ }
101
+ /**
102
+ * `SkillSpec.baseDir` 的窄读:与 server 顶层门**同一只**结构判据(绝对路径、无 `..` 段、无 NUL、≤ 4096 字符;**不做存在性检查**)。
103
+ * 🔴 「绝对路径」按 **server 所在平台**的 `node:path.isAbsolute` 判(异源对抗复审实抓:此前把盘符路径在 posix 上也放行 = 送出去必 400):
104
+ * · `'posix'`(默认):只有 `/…` 算;`C:\\x` / `\\\\host\\share` 都不算;
105
+ * · `..` 段检查两个平台都按 `/` **与** `\\` 拆(server `isValidCwd` 在所有平台都这么拆);
106
+ * · `'win32'`:`X:\\…` / `X:/…` 与 UNC `\\\\host\\share\\…` 算;段按 `\\` 与 `/` 切。
107
+ * 不合判据 ⇒ `undefined`(不发假坐标;在本包拦下比让 server 400 整拒更诚实)。
108
+ */
109
+ export declare function skillBaseDirOf(skillRoot: string | undefined, platform?: 'posix' | 'win32'): string | undefined;
110
+ export declare function skillCommandsToSpecs(cmds: SkillCmdLike[], resolveBody: (cmd: SkillCmdLike) => string | null, opts?: SkillSpecsOptions): SkillSpecOnWire[];
@@ -39,13 +39,33 @@ export function toCappedSpec(name, description, whenToUse, body) {
39
39
  return { name: n, description: desc, content: c };
40
40
  }
41
41
  /**
42
- * PURE projection: loaded skill commands → `SkillSpec[]` (content-capped, deduped by name; 0.70.0: NO item cap —
43
- * every eligible skill goes on the wire, the engine budgets the listing). `resolveBody`
44
- * supplies each command's raw SKILL.md body (null ⇒ skip); injecting it keeps this function IO-free and
45
- * unit-testable. Only MODEL-INVOCABLE prompt skills are sent — UI-only / `disableModelInvocation` commands
46
- * are excluded (engine injection feeds the model's Skill tool, not the slash menu).
42
+ * `SkillSpec.baseDir` 的窄读:与 server 顶层门**同一只**结构判据(绝对路径、无 `..` 段、无 NUL、≤ 4096 字符;**不做存在性检查**)。
43
+ * 🔴 「绝对路径」按 **server 所在平台**的 `node:path.isAbsolute` 判(异源对抗复审实抓:此前把盘符路径在 posix 上也放行 = 送出去必 400):
44
+ * · `'posix'`(默认):只有 `/…` 算;`C:\\x` / `\\\\host\\share` 都不算;
45
+ * · `..` 段检查两个平台都按 `/` **与** `\\` 拆(server `isValidCwd` 在所有平台都这么拆);
46
+ * · `'win32'`:`X:\\…` / `X:/…` 与 UNC `\\\\host\\share\\…` 算;段按 `\\` 与 `/` 切。
47
+ * 不合判据 ⇒ `undefined`(不发假坐标;在本包拦下比让 server 400 整拒更诚实)。
47
48
  */
48
- export function skillCommandsToSpecs(cmds, resolveBody) {
49
+ export function skillBaseDirOf(skillRoot, platform = 'posix') {
50
+ if (skillRoot === undefined || skillRoot.length === 0 || skillRoot.length > 4096)
51
+ return undefined;
52
+ if (skillRoot.includes('\u0000'))
53
+ return undefined;
54
+ if (platform === 'win32') {
55
+ if (!(/^[A-Za-z]:[\\/]/.test(skillRoot) || /^\\\\[^\\/]+[\\/][^\\/]+/.test(skillRoot)))
56
+ return undefined;
57
+ if (skillRoot.split(/[\\/]/).includes('..'))
58
+ return undefined;
59
+ return skillRoot;
60
+ }
61
+ if (!skillRoot.startsWith('/'))
62
+ return undefined;
63
+ // `..` 段两种分隔符都拆(server isValidCwd 在所有平台都按 /[/\\]/ 拆段;POSIX 文件名可含反斜杠 —— 复审第 2 轮实抓)
64
+ if (skillRoot.split(/[\\/]/).includes('..'))
65
+ return undefined;
66
+ return skillRoot;
67
+ }
68
+ export function skillCommandsToSpecs(cmds, resolveBody, opts) {
49
69
  const specs = [];
50
70
  const seen = new Set();
51
71
  for (const cmd of cmds) {
@@ -58,7 +78,8 @@ export function skillCommandsToSpecs(cmds, resolveBody) {
58
78
  if (!spec || seen.has(spec.name))
59
79
  continue;
60
80
  seen.add(spec.name);
61
- specs.push(spec);
81
+ const baseDir = opts?.toolsRunHere === true ? skillBaseDirOf(cmd.skillRoot, opts.platform ?? 'posix') : undefined;
82
+ specs.push(baseDir !== undefined ? { ...spec, baseDir } : spec);
62
83
  }
63
84
  return specs;
64
85
  }
@@ -107,23 +107,6 @@ export type ModelFacingTaskOutput = {
107
107
  export declare function parseModelFacingTaskOutput(text: string): ModelFacingTaskOutput;
108
108
  export declare function backgroundReceiptParseCalls(): number;
109
109
  export declare function _resetBackgroundReceiptParseCountForTest(): void;
110
- /**
111
- * T14 回落 —— 后台回执**三代文案**正则(#117a:core ≥1.283 的 `Command running in background` 是
112
- * 第三代)。structured bash 在场时由 `background`/`task_id` 位取代(见 `wireOutputToBody`)——
113
- * **该函数在 structured 权威时必须零调用**(`wireOutputToBody` 的 T14 分支已经这样接线;
114
- * `backgroundReceiptParseCalls()` 是这条不变量的仪器)。
115
- * 🔴 legacy 回落登记(REF-CC-域词表-04):三代并列是本函数唯一存在的理由 —— 旧分支不能删,
116
- * 是因为不知道哪个仍在跑的引擎版本还只发这三代文案里的某一代。**何时可删**:当
117
- * `run-engine-vocab-floor-test.mjs`(engine-vocab 门)确认全部受支持的引擎地板版本都已发
118
- * `structured.type === 'bash'` 的 `task_id`/`background.task_id` 位(即 structured 白名单成为
119
- * **唯一**判据,不再有 tolerate-absent 的老引擎在保修窗内)时,本函数随之退役;
120
- * `detectEngineBgShellReceipt`(#117a 面板探测器,复用同一份 `THREE_GEN_BG_RECEIPT_RE`)另行
121
- * 评估——它的存在理由不是「structured 回落」而是「引擎侧秒关该卡、只能靠回执文案桥」,
122
- * 不随本函数退役自动失效。
123
- */
124
- export declare function parseBackgroundReceipt(text: string): {
125
- taskId: string;
126
- } | null;
127
110
  /**
128
111
  * T8 —— 压平非均匀 §E1 `output`(`string` | `(Text|Image)[]`)。
129
112
  * ⚠️ 真源在 adapt.ts(A 层 settle 报告也用同一份),本文件只 import,别再写第二份。
@@ -327,7 +327,7 @@ export function _resetBackgroundReceiptParseCountForTest() {
327
327
  * 评估——它的存在理由不是「structured 回落」而是「引擎侧秒关该卡、只能靠回执文案桥」,
328
328
  * 不随本函数退役自动失效。
329
329
  */
330
- export function parseBackgroundReceipt(text) {
330
+ function parseBackgroundReceipt(text) {
331
331
  backgroundReceiptParseCount++;
332
332
  const bg = THREE_GEN_BG_RECEIPT_RE.exec(text);
333
333
  return bg?.[1] !== undefined ? { taskId: bg[1] } : null;
@@ -56,9 +56,6 @@ export declare function ultracodeFromInput(humanInput: string | undefined): true
56
56
  */
57
57
  export declare function ultracodeForRequest(humanInput: string | undefined): true | undefined;
58
58
  export declare function workflowFromInput(humanInput: string | undefined): true | undefined;
59
- /** Env escape hatch for the deferred-Workflow factory default: `SEMA_WORKFLOW_DEFER=0/false/no/off` restores the
60
- * pre-[1052] always-exposed shape (schema bytes back in the prefix). Anything else (incl. unset) = defer ON. */
61
- export declare const WORKFLOW_DEFER_ENV: "SEMA_WORKFLOW_DEFER";
62
59
  /**
63
60
  * [1052]① 收官形(clay 裁 B 的 core deferred 机制件,cli 接线半场):Workflow「全局默认开但不暴露」——
64
61
  * 未命中激活源的 TaskRequest 带 `deferTools:["Workflow"]`(core 1.314 对已挂载工具做 design/36 延迟披露:
@@ -87,7 +87,7 @@ export function workflowFromInput(humanInput) {
87
87
  }
88
88
  /** Env escape hatch for the deferred-Workflow factory default: `SEMA_WORKFLOW_DEFER=0/false/no/off` restores the
89
89
  * pre-[1052] always-exposed shape (schema bytes back in the prefix). Anything else (incl. unset) = defer ON. */
90
- export const WORKFLOW_DEFER_ENV = 'SEMA_WORKFLOW_DEFER';
90
+ const WORKFLOW_DEFER_ENV = 'SEMA_WORKFLOW_DEFER';
91
91
  /** 拼写集 = {@link envFlagOff}(REF-CC-141 dup-02 单源)。 */
92
92
  function deferOptedOut(env) {
93
93
  return envFlagOff(env[WORKFLOW_DEFER_ENV]);