@sema-agent/client-core 0.76.2 → 0.77.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.
@@ -0,0 +1,68 @@
1
+ /**
2
+ * sessionPolicyCapability — `GET /v1/capabilities.sessionPolicy` 的三端共用四态读面(0.77.0 CC-105;
3
+ * 与 sql / writeProtection / webSearch.backend / executionLane / approvalsStreamLive /
4
+ * deviceExecutor.management / peerLane / permissionRulesWrite / memoryCompliance / memoryOrigin
5
+ * 各只兄弟同一套四态词汇,四口走共用工厂)。引擎的 wire 型面里这一位已声明为可选布尔,读法仍按结构走
6
+ * (声明形与在场性是两件事:声明说「型上可以有」,回体才说「这台有没有」)。
7
+ *
8
+ * ── 这一位答的是哪一个问题 ────────────────────────────────────────────────────────────────
9
+ * 「per-session 工具规则的那一对读写口(`GET` / `PUT /v1/sessions/:id/policy`)在**这台引擎**上挂没挂」。
10
+ * 上游的铸点是一次**设施在场判**(会话规则店接线了没),不是一次身份判:
11
+ * · 值为真 ⇒ 规则店在场,这对口有人应答(某一次调用成不成功仍由那一次的应答逐次回答:
12
+ * 无主会话 409、非属主 404、放宽方向 403、CAS 不符 409 —— 这一位一个都不预告);
13
+ * · 值为假 ⇒ 本部署**没有**会话规则店,这对口恒以「这台没有这一面」拒,写入口该藏起来。
14
+ *
15
+ * ── 🔴 四态,不是两态 ───────────────────────────────────────────────────────────────────────
16
+ * ① `unobserved` —— 本进程一次能力回体都没观测到。**不是**引擎说过的话。
17
+ * ② `not_reported` —— 回体观测到了,而**没有**这个键 ⇒ 这份二进制比这一位本身还老。
18
+ * ③ `absent` —— 键在、值明确是 `false`:这台没有会话规则店。**正面事实**,与 ② 是两件事。
19
+ * ④ `present` —— 键在、值为 `true`:这一面在这台引擎上挂着。
20
+ * 值既不是 `true` 也不是 `false`(`null` / 串 / 数 / 数组 / 对象)⇒ 畸形 ⇒ **删格**(⇒ `unobserved`);
21
+ * 能力回体整只不是对象同判畸形。🔴 三种「没有」(`unobserved` / `not_reported` / `absent`)**不许互折**。
22
+ * 判在场用 `hasOwn`:原型链上继承来的同名键不算这台引擎说过话。
23
+ *
24
+ * ── 🔴 「藏入口」与「别发请求」是两个问题,判官也是两个 ─────────────────────────────────────
25
+ * 本文件只铸前者:{@link sessionPolicyFaceAvailable} 回答「要不要渲这个写入口」,
26
+ * 保守方向 —— 只有明说在场才 `'yes'`,明说不在与「比这一位还老的二进制」都 `'no'`,从没观测过才 `'unknown'`。
27
+ * 后者(「这次编排该不该把请求发出去」)在会话策略写端口那一侧,它拿的是**同一只读数**、不另开一张表,
28
+ * 判据与本函数刻意分开写:一个决定界面长什么样,一个决定要不要动网络,两者对 `unobserved` 的答案
29
+ * 本来就该不同(界面保守地不渲,而一次用户已经点过的动作不该因为「本进程没探过能力面」被凭空挡住)。
30
+ */
31
+ /** 四态读数。`unobserved` 由读口在这一格空缺时铸,不由投影铸。 */
32
+ export type SessionPolicyReading = {
33
+ kind: 'unobserved';
34
+ }
35
+ /** 回体在、键不在 ⇒ 这份二进制比这一位老。**不等于** `false`。 */
36
+ | {
37
+ kind: 'not_reported';
38
+ }
39
+ /** 键在、值为 `false`:本部署没有会话规则店 ⇒ 这一对口恒拒。 */
40
+ | {
41
+ kind: 'absent';
42
+ }
43
+ /** 键在、值为 `true`:这一面挂着。 */
44
+ | {
45
+ kind: 'present';
46
+ };
47
+ /** 能力回体 → 本格读数;畸形一律 `undefined`(= 这一格不写 ⇒ 读口答 `unobserved`)。 */
48
+ export declare function projectSessionPolicyCapability(caps: unknown): SessionPolicyReading | undefined;
49
+ /** 宿主 caps probe 的读面 tee 落点。绝不 throw;畸形 ⇒ 删格;`opts.generation` 关掉旧探测覆盖新读数的竞态。 */
50
+ export declare function noteEngineCapsForSessionPolicy(baseUrl: string, caps: unknown, opts?: {
51
+ generation?: number;
52
+ }): void;
53
+ /** 本进程观测到的读数;这一格空缺 ⇒ `{kind:'unobserved'}`。 */
54
+ export declare function observedSessionPolicy(baseUrl?: string | undefined): SessionPolicyReading;
55
+ /**
56
+ * 「要不要渲会话规则的写入口」的判据单源(三态)。
57
+ * 🔴 只有引擎明说在场(`present`)才 `'yes'`;明说不在(`absent`)与「这份二进制比这一位老」
58
+ * (`not_reported`)都是 `'no'` —— 后者不是在说这一面不存在,是在说**这台说不出**,而一个说不出
59
+ * 的写入口按假 affordance 纪律不渲(真要试,走读口,读是无副作用的那一半)。从没观测过 ⇒ `'unknown'`,
60
+ * 端自己决定渲不渲(它既不是引擎的否定,也不是引擎的肯定)。
61
+ */
62
+ export declare function sessionPolicyFaceAvailable(reading: SessionPolicyReading): 'yes' | 'no' | 'unknown';
63
+ /** doctor 那一行的 detail 串,唯一措辞真源。🔴 两种「读不出」都不许暗示「这一面在」或「这一面不在」。 */
64
+ export declare function sessionPolicyDoctorDetail(reading: SessionPolicyReading): string;
65
+ /** 换代失效口(引擎温切成功后调):清成未观测。空串 ⇒ no-op;绝不 throw。 */
66
+ export declare function forgetSessionPolicyReading(baseUrl: string | undefined): void;
67
+ /** 测试钩子。 */
68
+ export declare function __resetSessionPolicyReadingsForTests(): void;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * sessionPolicyCapability — `GET /v1/capabilities.sessionPolicy` 的三端共用四态读面(0.77.0 CC-105;
3
+ * 与 sql / writeProtection / webSearch.backend / executionLane / approvalsStreamLive /
4
+ * deviceExecutor.management / peerLane / permissionRulesWrite / memoryCompliance / memoryOrigin
5
+ * 各只兄弟同一套四态词汇,四口走共用工厂)。引擎的 wire 型面里这一位已声明为可选布尔,读法仍按结构走
6
+ * (声明形与在场性是两件事:声明说「型上可以有」,回体才说「这台有没有」)。
7
+ *
8
+ * ── 这一位答的是哪一个问题 ────────────────────────────────────────────────────────────────
9
+ * 「per-session 工具规则的那一对读写口(`GET` / `PUT /v1/sessions/:id/policy`)在**这台引擎**上挂没挂」。
10
+ * 上游的铸点是一次**设施在场判**(会话规则店接线了没),不是一次身份判:
11
+ * · 值为真 ⇒ 规则店在场,这对口有人应答(某一次调用成不成功仍由那一次的应答逐次回答:
12
+ * 无主会话 409、非属主 404、放宽方向 403、CAS 不符 409 —— 这一位一个都不预告);
13
+ * · 值为假 ⇒ 本部署**没有**会话规则店,这对口恒以「这台没有这一面」拒,写入口该藏起来。
14
+ *
15
+ * ── 🔴 四态,不是两态 ───────────────────────────────────────────────────────────────────────
16
+ * ① `unobserved` —— 本进程一次能力回体都没观测到。**不是**引擎说过的话。
17
+ * ② `not_reported` —— 回体观测到了,而**没有**这个键 ⇒ 这份二进制比这一位本身还老。
18
+ * ③ `absent` —— 键在、值明确是 `false`:这台没有会话规则店。**正面事实**,与 ② 是两件事。
19
+ * ④ `present` —— 键在、值为 `true`:这一面在这台引擎上挂着。
20
+ * 值既不是 `true` 也不是 `false`(`null` / 串 / 数 / 数组 / 对象)⇒ 畸形 ⇒ **删格**(⇒ `unobserved`);
21
+ * 能力回体整只不是对象同判畸形。🔴 三种「没有」(`unobserved` / `not_reported` / `absent`)**不许互折**。
22
+ * 判在场用 `hasOwn`:原型链上继承来的同名键不算这台引擎说过话。
23
+ *
24
+ * ── 🔴 「藏入口」与「别发请求」是两个问题,判官也是两个 ─────────────────────────────────────
25
+ * 本文件只铸前者:{@link sessionPolicyFaceAvailable} 回答「要不要渲这个写入口」,
26
+ * 保守方向 —— 只有明说在场才 `'yes'`,明说不在与「比这一位还老的二进制」都 `'no'`,从没观测过才 `'unknown'`。
27
+ * 后者(「这次编排该不该把请求发出去」)在会话策略写端口那一侧,它拿的是**同一只读数**、不另开一张表,
28
+ * 判据与本函数刻意分开写:一个决定界面长什么样,一个决定要不要动网络,两者对 `unobserved` 的答案
29
+ * 本来就该不同(界面保守地不渲,而一次用户已经点过的动作不该因为「本进程没探过能力面」被凭空挡住)。
30
+ */
31
+ import { engineWireTarget } from './engineWireTarget.js';
32
+ import { createEngineCapReader } from './engineCapReader.js';
33
+ /** 能力回体 → 本格读数;畸形一律 `undefined`(= 这一格不写 ⇒ 读口答 `unobserved`)。 */
34
+ export function projectSessionPolicyCapability(caps) {
35
+ // 🔴 数组也是畸形:一个数组回体**不是**引擎对能力面的回答,从它上面读出「这份二进制比这一位老」
36
+ // 是替引擎编了一句它没说的话。`not_reported` 只留给**真的是能力对象、但这一格不在**那一形。
37
+ if (caps === null || typeof caps !== 'object' || Array.isArray(caps))
38
+ return undefined;
39
+ const c = caps;
40
+ if (!Object.hasOwn(c, 'sessionPolicy'))
41
+ return { kind: 'not_reported' };
42
+ // 🔴 只读一次:在场判据与取值同一次读(变化的 getter 会让校验读到布尔、返回读到别的东西)。
43
+ const v = c.sessionPolicy;
44
+ if (v === undefined)
45
+ return { kind: 'not_reported' };
46
+ if (typeof v !== 'boolean')
47
+ return undefined;
48
+ return v ? { kind: 'present' } : { kind: 'absent' };
49
+ }
50
+ /**
51
+ * 本格的 per-baseUrl 读账 + tee / 读口 / 失效口 / 测试钩四口 —— 各只能力位读器**共用同一份实现**
52
+ * ({@link createEngineCapReader});本文件只留这一格真正不同的部分:投影函数
53
+ * {@link projectSessionPolicyCapability}、判据单源与 doctor 措辞表。
54
+ * 🔴 Map 在工厂闭包里,**一只读器一张**:收到读不懂的回体只删自己这一格,绝不连坐别的能力面。
55
+ */
56
+ const reader = createEngineCapReader({
57
+ name: 'sessionPolicy',
58
+ project: projectSessionPolicyCapability,
59
+ makeUnobserved: () => ({ kind: 'unobserved' }),
60
+ });
61
+ /** 宿主 caps probe 的读面 tee 落点。绝不 throw;畸形 ⇒ 删格;`opts.generation` 关掉旧探测覆盖新读数的竞态。 */
62
+ export function noteEngineCapsForSessionPolicy(baseUrl, caps, opts) {
63
+ reader.note(baseUrl, caps, opts);
64
+ }
65
+ /** 本进程观测到的读数;这一格空缺 ⇒ `{kind:'unobserved'}`。 */
66
+ export function observedSessionPolicy(baseUrl = engineWireTarget()?.baseUrl) {
67
+ return reader.observed(baseUrl);
68
+ }
69
+ /**
70
+ * 「要不要渲会话规则的写入口」的判据单源(三态)。
71
+ * 🔴 只有引擎明说在场(`present`)才 `'yes'`;明说不在(`absent`)与「这份二进制比这一位老」
72
+ * (`not_reported`)都是 `'no'` —— 后者不是在说这一面不存在,是在说**这台说不出**,而一个说不出
73
+ * 的写入口按假 affordance 纪律不渲(真要试,走读口,读是无副作用的那一半)。从没观测过 ⇒ `'unknown'`,
74
+ * 端自己决定渲不渲(它既不是引擎的否定,也不是引擎的肯定)。
75
+ */
76
+ export function sessionPolicyFaceAvailable(reading) {
77
+ if (reading.kind === 'present')
78
+ return 'yes';
79
+ if (reading.kind === 'absent' || reading.kind === 'not_reported')
80
+ return 'no';
81
+ return 'unknown';
82
+ }
83
+ /** doctor 那一行的 detail 串,唯一措辞真源。🔴 两种「读不出」都不许暗示「这一面在」或「这一面不在」。 */
84
+ export function sessionPolicyDoctorDetail(reading) {
85
+ switch (reading.kind) {
86
+ case 'unobserved':
87
+ return 'per-session rule face not observed — the engine reports it on /v1/capabilities (sessionPolicy); this process has no usable capabilities reading cached for it (none received, or the last one was unreadable)';
88
+ case 'not_reported':
89
+ return 'per-session rule face not reported by this engine — this binary predates the position itself; it says nothing about whether the face exists, so read the rules rather than writing them blind';
90
+ case 'absent':
91
+ return 'no per-session rule store on this deployment — reading and writing this session’s tool rules is unavailable here';
92
+ case 'present':
93
+ return 'per-session rule face on — this session’s tool rules can be read, and a tightening write is accepted or refused by the engine call by call';
94
+ }
95
+ }
96
+ /** 换代失效口(引擎温切成功后调):清成未观测。空串 ⇒ no-op;绝不 throw。 */
97
+ export function forgetSessionPolicyReading(baseUrl) {
98
+ reader.forget(baseUrl);
99
+ }
100
+ /** 测试钩子。 */
101
+ export function __resetSessionPolicyReadingsForTests() {
102
+ reader.__resetForTests();
103
+ }
@@ -56,7 +56,11 @@ import { createEngineCapReader } from './engineCapReader.js';
56
56
  export function projectSqlEngineCapability(caps) {
57
57
  if (caps === null || typeof caps !== 'object')
58
58
  return undefined;
59
- if (!('sql' in caps))
59
+ // 0.77.0 整族改齐:① 非对象 / 数组 caps 不是能力表 ⇒ 畸形(投影答 undefined ⇒ 删格 ⇒ 读口答 unobserved),不再答 not_reported
60
+ // (那是把「取不到」冒充「报了但没提」);② 在场判据改 hasOwn —— 原型链上的同名键永远不会被序列化上 wire,读成在场 = 凭空造一格。
61
+ if (caps === null || typeof caps !== 'object' || Array.isArray(caps))
62
+ return undefined;
63
+ if (!Object.hasOwn(caps, 'sql'))
60
64
  return { kind: 'not_reported' };
61
65
  const sql = caps.sql;
62
66
  if (sql === null)
@@ -25,7 +25,14 @@ export declare function fetchEngineTaskOutput(handle: string, opts?: {
25
25
  */
26
26
  /** 0.73.3(CC-60 A-D3):`cursorSemantics` 机读位(server ≥7.77.0 `taskOutput` 面):"cursor" = 读了就消费 ⇒ append;"full" = 全量重读安全 ⇒ 替换。
27
27
  * **缺席 / 认不出 ⇒ 现行文案标记臂兜底**(老 server 不发这个键;sdk 头注逐字「别把缺席当 full」)。 */
28
- export declare function mergeTaskOutput(prev: string, fresh: string, cursorSemantics?: string): string;
28
+ /**
29
+ * 回体 `output` 里参与合并判据的两枚提示位(sdk `SubagentOutputResult.output` 的子集;运行期仍按自有键结构读,不信任声明形)。
30
+ */
31
+ export interface TaskOutputMergeHint {
32
+ readonly type?: string;
33
+ readonly retrieval_status?: string;
34
+ }
35
+ export declare function mergeTaskOutput(prev: string, fresh: string, cursorSemantics?: string, output?: TaskOutputMergeHint | null): string;
29
36
  export type EngineTaskStopOutcome =
30
37
  /** 200:停止动作完成(status = wire 终态照实;重复 stop 幂等)。 */
31
38
  {
@@ -123,8 +123,11 @@ export async function fetchEngineTaskOutput(handle, opts) {
123
123
  const prev = accumulated.get(handle) ?? '';
124
124
  // A-D3:机读位优先 —— sdk 9.7.1 `SubagentOutputResult.cursorSemantics` 是**回体顶层**键(与 `output` 并列;generic taskOutput 面才铸,
125
125
  // 窄面 subagentOutput 不带 ⇒ 缺席走文案臂)。异源对抗复审:首版读成 `output.cursorSemantics`,正常回体恒回落文案臂。
126
+ // 0.77.1 CC-109(server ≥7.92.0):机读位缺席时把回体 `output` 的 `type` / `retrieval_status` 交给合并判据(见 mergeTaskOutput 头注);
127
+ // 声明形只到 `TaskOutputMergeHint`(sdk 回体 `output` 的子集),运行期在 `terminalOutputHint` 里再按自有键结构读一遍(不信任声明形)。
126
128
  const cursorSemantics = r?.cursorSemantics;
127
- const merged = mergeTaskOutput(prev, fresh, typeof cursorSemantics === 'string' ? cursorSemantics : undefined);
129
+ const output = r?.output;
130
+ const merged = mergeTaskOutput(prev, fresh, typeof cursorSemantics === 'string' ? cursorSemantics : undefined, output);
128
131
  accumulated.set(handle, merged);
129
132
  const out = r?.output ?? {};
130
133
  if (engineWireDebugEnabled()) {
@@ -143,23 +146,22 @@ export async function fetchEngineTaskOutput(handle, opts) {
143
146
  return null;
144
147
  }
145
148
  }
146
- /**
147
- * [1505] 契约标记判别(头注):spool 全量 → 替换;cursor 增量 → append;无标记 → 保守
148
- * append-if-changed(与上次逐字节相同视为幂等重读,不重复)。空 content 不动累积。
149
- * 🔴 B6 提出成具名纯函数 —— 它是这条读面唯一会「把输出显示成两遍」的地方,值得被直接断言。
150
- * 🔴 REF-CC-域词表-06:标记判读本身**提单源**到 `toolResult.ts` 的 `spoolMarkerOf`(它与本文件
151
- * 曾经各写一份 `.includes()` 判读,同一个协议标记有两处互不知情的判读点——现在两处判读结果
152
- * 对同一输入恒一致,因为已经是同一个函数)。
153
- */
154
- /** 0.73.3(CC-60 A-D3):`cursorSemantics` 机读位(server ≥7.77.0 `taskOutput` 面):"cursor" = 读了就消费 ⇒ append;"full" = 全量重读安全 ⇒ 替换。
155
- * **缺席 / 认不出 ⇒ 现行文案标记臂兜底**(老 server 不发这个键;sdk 头注逐字「别把缺席当 full」)。 */
156
- export function mergeTaskOutput(prev, fresh, cursorSemantics) {
149
+ export function mergeTaskOutput(prev, fresh, cursorSemantics, output) {
157
150
  if (fresh.length === 0)
158
151
  return prev;
159
152
  if (cursorSemantics === 'full')
160
153
  return fresh;
161
154
  if (cursorSemantics === 'cursor')
162
155
  return prev + fresh;
156
+ // 🔴 机读位缺席(0.77.1 CC-109,server ≥7.92.0 判据换源 core `details.replay`):
157
+ // · `type: background_agent / workflow` 的 poll 面**不铸**这一位(终局结果面,再 poll 答同一段文本)⇒ 整段替换,绝不追加;
158
+ // · `retrieval_status` 在场且不是 `success`(not_ready / error 无已消费正文)⇒ 那段 `content` 是一句状态说明不是输出体 ⇒ 不合并;
159
+ // · 其余(老 server / background_bash 无机读位)才走下面的文案臂。判据只在缺席时看 `output`,在场的机读位永远优先。
160
+ const hint = terminalOutputHint(output);
161
+ if (hint === 'not_output')
162
+ return prev;
163
+ if (hint === 'snapshot')
164
+ return fresh;
163
165
  const marker = spoolMarkerOf(fresh);
164
166
  if (marker === 'full')
165
167
  return fresh;
@@ -167,6 +169,19 @@ export function mergeTaskOutput(prev, fresh, cursorSemantics) {
167
169
  return prev + fresh;
168
170
  return fresh === prev ? prev : prev + fresh;
169
171
  }
172
+ /** 回体 `output` 的两枚提示位(只读自有键,坏形一律 `undefined` = 没提示)。 */
173
+ function terminalOutputHint(output) {
174
+ if (output === null || typeof output !== 'object' || Array.isArray(output))
175
+ return undefined;
176
+ const o = output;
177
+ const rs = Object.hasOwn(o, 'retrieval_status') ? o.retrieval_status : undefined;
178
+ if (typeof rs === 'string' && rs !== 'success')
179
+ return 'not_output';
180
+ const type = Object.hasOwn(o, 'type') ? o.type : undefined;
181
+ if (type === 'background_agent' || type === 'workflow')
182
+ return 'snapshot';
183
+ return undefined;
184
+ }
170
185
  /**
171
186
  * 🔴 **G13 删债(先红后绿)**:taskStop 的 409 冲突分类 —— 手抄 code 判 → typed
172
187
  * `TaskStopConflictError`(SDK 把**任何** `stop.*` 前缀码映到该类,errors.js:277-278;
@@ -52,7 +52,11 @@ export const WEB_SEARCH_BACKEND_NONE = 'none';
52
52
  export function projectWebSearchBackendCapability(caps) {
53
53
  if (caps === null || typeof caps !== 'object')
54
54
  return undefined;
55
- if (!('webSearch' in caps))
55
+ // 0.77.0 整族改齐:① 非对象 / 数组 caps 不是能力表 ⇒ 畸形(投影答 undefined ⇒ 删格 ⇒ 读口答 unobserved),不再答 not_reported
56
+ // (那是把「取不到」冒充「报了但没提」);② 在场判据改 hasOwn —— 原型链上的同名键永远不会被序列化上 wire,读成在场 = 凭空造一格。
57
+ if (caps === null || typeof caps !== 'object' || Array.isArray(caps))
58
+ return undefined;
59
+ if (!Object.hasOwn(caps, 'webSearch'))
56
60
  return { kind: 'not_reported' };
57
61
  const ws = caps.webSearch;
58
62
  if (ws === undefined)
@@ -45,7 +45,11 @@ import { createEngineCapReader } from './engineCapReader.js';
45
45
  export function projectWriteProtectionCapability(caps) {
46
46
  if (caps === null || typeof caps !== 'object')
47
47
  return undefined;
48
- if (!('writeProtection' in caps))
48
+ // 0.77.0 整族改齐:① 非对象 / 数组 caps 不是能力表 ⇒ 畸形(投影答 undefined ⇒ 删格 ⇒ 读口答 unobserved),不再答 not_reported
49
+ // (那是把「取不到」冒充「报了但没提」);② 在场判据改 hasOwn —— 原型链上的同名键永远不会被序列化上 wire,读成在场 = 凭空造一格。
50
+ if (caps === null || typeof caps !== 'object' || Array.isArray(caps))
51
+ return undefined;
52
+ if (!Object.hasOwn(caps, 'writeProtection'))
49
53
  return { kind: 'not_reported' };
50
54
  const wp = caps.writeProtection;
51
55
  if (wp === null)