@sema-agent/client-core 0.4.0 → 0.5.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.
@@ -0,0 +1,73 @@
1
+ /**
2
+ * ⇄ B3 批搬迁(2026-07-27,多端改造设计稿 §3 B3 / §2.4.1-C):cli `src/seam/adapter/types.ts` 的
3
+ * **运行时半场**搬入(`eventSeq` / `uuid` / `stamp` / `EmitContext`),外加 B3 的两个注入位。
4
+ *
5
+ * 🔴 **这个文件是「拆」不是「整搬」**,拆缝写明(设计稿 §2.5 同款口径):
6
+ * · 搬进来的 = 三个纯函数 + EmitContext(零依赖,浏览器可跑);
7
+ * · **留在壳里的 = 那 8 行 zod 推断型别名**(`z.infer<ReturnType<typeof SDKMessageSchema>>` …,
8
+ * 真源 = 壳内 CC 自带的 `src/entrypoints/sdk/coreSchemas.ts`)。
9
+ * 为什么不搬:① 搬它就得把 coreSchemas.ts(1,996 行 CC 自带 zod)一起 vendor 进本包并新增
10
+ * `zod` 运行时依赖 —— 本包至今 `dependencies: {}`;② 那是 CC 自带文件,本包是**公开 npm 包**;
11
+ * ③ 本仓既定词表来源是 `@sema-agent/agent-types`(全量镜像,非 curation)。
12
+ * ⇒ 壳侧 shim 里 `export type SDKMessage = z.infer<…>` 这几行**原样保留**,壳的消费面看到的
13
+ * 仍然是 zod 推断真身,一个消费点都不换型。本包内部用 agent-types 的同名镜像。
14
+ *
15
+ * 🔴 **两个 `SDKMessage` 不是同一个 symbol**(B1 发现#4,printToolResultFrame.ts 同款记账):
16
+ * 两者都镜像 CC 的同一个 union,但是两条独立的类型来源。本包搬入的三个文件里 `SDKMessage`
17
+ * **只当不透明返回型用**(每个构造点都走 `as unknown as SDKMessage`),所以这个选择在本包内
18
+ * 零后果;真正的接触点在壳侧 shim 那一刻的 tsc —— 报错就是真信号,不许 any 压。
19
+ *
20
+ * ── 以下为原文件头(逐字保留)────────────────────────────────────────────────────────────────
21
+ * Adapter-internal shared types + id-stamp / emit / uuid helpers (T21).
22
+ *
23
+ * The seam runs ONE direction here: downstream `@sema-ai/sdk` `AgentEvent`s
24
+ * (events.ts:14-41) are projected into Claude Code `SDKMessage` arms
25
+ * (coreSchemas.ts:1854 union) that the verbatim-copied REPL renders.
26
+ *
27
+ * Two laws this file pins (contract 02 §0-§1, 08 branding/altitude invariants):
28
+ * - contract-sovereign: the WIRE shape is `AgentEvent`; CC's `SDKMessage` is
29
+ * the *reference shape we reproduce* for the renderer. We DOWN-map (drop
30
+ * fields), never demand the SDK grow.
31
+ * - provider-agnostic: NO Anthropic symbol crosses this seam — no raw
32
+ * `APIAssistantMessage` block array, no `RawMessageStreamEvent` wire, no
33
+ * rate-limit window taxonomy, no `effort`/`fast_mode` vocabulary
34
+ * (events.ts:6-10; contract 02 §5). Those fields are simply never populated.
35
+ */
36
+ import type { AgentEvent } from '@sema-agent/sdk';
37
+ import type { ModelUsage, SDKMessage } from '@sema-agent/agent-types';
38
+ import type { ChromeEvent } from '../seam.js';
39
+ export type { ModelUsage, SDKMessage };
40
+ export type StampedAgentEvent = AgentEvent & {
41
+ id?: string;
42
+ };
43
+ /** Read the stamped durable seq off an AgentEvent, if present. */
44
+ export declare function eventSeq(ev: AgentEvent): string | undefined;
45
+ export declare function uuid(): string;
46
+ /**
47
+ * Per-stream identity context. `session_id` threads every emitted arm; it is the
48
+ * service-minted sessionId (TaskResult.sessionId / RunReceipt.sessionId) once
49
+ * known, falling back to a locally-minted placeholder before the first reply.
50
+ */
51
+ export interface EmitContext {
52
+ sessionId: string;
53
+ /** Wall-clock ms when the stream started draining (runStream stamps it once) — the terminal projector
54
+ * derives the CC `duration_ms` from it (P1-5: the wire carries no duration; 0 was a fake constant). */
55
+ startedAtMs?: number;
56
+ /**
57
+ * B3 新增(设计稿 §5.2 两条外向边的落点)—— chrome 事件下泄口。runStream 里那两条
58
+ * `import('../../sema/…')` 动态边改走这里:宿主注入一个 sink,库侧只发事件。
59
+ * 缺席 = 该宿主不消费(**语义 = 那个 UI 面在这个宿主上是哑的**,不是报错)。
60
+ *
61
+ * 返回 `Promise` 时 runStream 只在 **cli 原本 await 的那一处** await(plan_review park),
62
+ * 其余点 fire-and-forget —— 与搬迁前的时序逐点对齐,别顺手改成全 await。
63
+ */
64
+ emitChrome?(event: ChromeEvent): void | Promise<void>;
65
+ }
66
+ /** Stamp `uuid` + `session_id` onto a freshly-built arm body. */
67
+ export declare function stamp<T extends {
68
+ uuid?: string;
69
+ session_id?: string;
70
+ }>(ctx: EmitContext, body: T): T & {
71
+ uuid: string;
72
+ session_id: string;
73
+ };
@@ -0,0 +1,23 @@
1
+ /** Read the stamped durable seq off an AgentEvent, if present. */
2
+ export function eventSeq(ev) {
3
+ return ev.id;
4
+ }
5
+ // ── identity mock-fill (contract 02 §4: AgentEvents are anonymous) ────────────
6
+ // CC SDKMessage arms require `uuid` + `session_id`. The wire drops them, so the
7
+ // adapter mints them client-side. A v4-shaped uuid is sufficient for the
8
+ // renderer's keying; it is NEVER smuggled onto the wire.
9
+ export function uuid() {
10
+ // RFC4122-v4 shape; crypto.randomUUID when available, else a deterministic-enough fallback.
11
+ const g = typeof globalThis !== 'undefined' ? globalThis.crypto : undefined;
12
+ if (g && typeof g.randomUUID === 'function')
13
+ return g.randomUUID();
14
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
15
+ const r = (Math.random() * 16) | 0;
16
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
17
+ return v.toString(16);
18
+ });
19
+ }
20
+ /** Stamp `uuid` + `session_id` onto a freshly-built arm body. */
21
+ export function stamp(ctx, body) {
22
+ return { ...body, uuid: body.uuid ?? uuid(), session_id: ctx.sessionId };
23
+ }
package/dist/index.d.ts CHANGED
@@ -5,6 +5,12 @@
5
5
  * 沿革:@sema-agent/wire-cc-adapter 0.1.0 = seam 类型 + id 确定性派生 + 首批踩坑纯函数;
6
6
  * 0.1.2(#52a)= adapt() 管线首批(纯投影臂全落 + 壳态耦合臂投影成 ChromeEvent + 差分守卫);
7
7
  * 0.2.0 = 迁入 sema-client-core 独立仓并改名(旧 npm 名 deprecate 指本包);
8
+ * 0.5.0 = **B3 批**(设计稿 §3 B3):`AdapterContext` 扩容(log/probe/signal/setTimer/clearTimer/
9
+ * coalesceIntervalMs,**只加可选位 = minor 不 BREAKING**)· 适配内核搬入(`adapter/types` 运行时
10
+ * 半场 + `adapter/downstream` 三件 + `adapter/runStream`)· runStream **两条外向边切除**
11
+ * (lastTurnUsageStore / planReviewWire ⇒ `last_turn_usage` / `plan_review_park` 两个 chrome 臂)·
12
+ * T38 三清第三清 + T40 IDLE-FLUSH + T57 第五处断闸 + T58 iterator cleanup + T59 abort + T60 收尾腿。
13
+ * 🆕 第三道守卫 `scripts/run-client-core-portability-test.mjs`(内核闭包 1571 文件 → **6 文件**)。
8
14
  * 0.4.0 = **B2 批**(设计稿 §3 B2):通知族三文件合并进 notifications.ts(engineTaskNotification 全文 +
9
15
  * taskNotificationErrorSupplement 全文 + hookNoticeStore 判定半场)· 17 个 `*WireCaps` + engineWireSdk +
10
16
  * sseIdleTriage + classifierVerdictWire 判定半场 + controlRouter + 模型面纯逻辑 4 件 + 新写 hostEnv;
@@ -23,8 +29,14 @@
23
29
  * —— B2 新增:notifications.ts(11 个通知台账 + 队列 port + 两个 probe 槽)·
24
30
  * agentsWireCaps.ts(prepare 缓存/投影闭包/一次性告警集)· liveModelCatalog.ts(目录 + refresher)·
25
31
  * sessionModelLatch.ts(三个 latch)· ultracodeWireCaps.ts(sticky preset)。
32
+ * —— B3 新增:adapter/runStream.ts(`inFlightTurns` 计数,读口 `isRunStreamActive()`;
33
+ * 两份实例 = 引擎滚动升级门恒读 false ⇒ **turn 中途热换引擎**,正是它当初要防的事)。
26
34
  * 🔴 **宿主装配**(B2 起本包有必须由宿主注入才完整的口):`installNotificationQueuePort()` ——
27
35
  * 不装 = task-notification 投递静默丢失(`notificationQueuePortMisses()` 恒应为 0)。
36
+ * —— B3 新增(可选口,缺席 = 对应 UI 面在该宿主上是哑的,不是报错):`ctx.emitChrome`
37
+ * (runStream 的两条切边:statusline 真 usage / plan-review 审批卡)· `ctx.setTimer`
38
+ * (T40 IDLE-FLUSH,不给 = 长工具参数生成期已生成内容仍押到下一帧才上屏)· `ctx.signal`
39
+ * (Esc 中断)· `ctx.log` / `ctx.probe`(诊断面)。
28
40
  */
29
41
  export * from './seam.js';
30
42
  export * from './workflow.js';
@@ -68,3 +80,8 @@ export * from './liveModelCatalog.js';
68
80
  export * from './modelBudgetRule.js';
69
81
  export * from './sessionModelLatch.js';
70
82
  export * from './effortWire.js';
83
+ export * from './adapter/types.js';
84
+ export * from './adapter/downstream/turnUsageToModelUsage.js';
85
+ export * from './adapter/downstream/eventToSdkMessage.js';
86
+ export * from './adapter/downstream/terminalToSdkResult.js';
87
+ export * from './adapter/runStream.js';
package/dist/index.js CHANGED
@@ -5,6 +5,12 @@
5
5
  * 沿革:@sema-agent/wire-cc-adapter 0.1.0 = seam 类型 + id 确定性派生 + 首批踩坑纯函数;
6
6
  * 0.1.2(#52a)= adapt() 管线首批(纯投影臂全落 + 壳态耦合臂投影成 ChromeEvent + 差分守卫);
7
7
  * 0.2.0 = 迁入 sema-client-core 独立仓并改名(旧 npm 名 deprecate 指本包);
8
+ * 0.5.0 = **B3 批**(设计稿 §3 B3):`AdapterContext` 扩容(log/probe/signal/setTimer/clearTimer/
9
+ * coalesceIntervalMs,**只加可选位 = minor 不 BREAKING**)· 适配内核搬入(`adapter/types` 运行时
10
+ * 半场 + `adapter/downstream` 三件 + `adapter/runStream`)· runStream **两条外向边切除**
11
+ * (lastTurnUsageStore / planReviewWire ⇒ `last_turn_usage` / `plan_review_park` 两个 chrome 臂)·
12
+ * T38 三清第三清 + T40 IDLE-FLUSH + T57 第五处断闸 + T58 iterator cleanup + T59 abort + T60 收尾腿。
13
+ * 🆕 第三道守卫 `scripts/run-client-core-portability-test.mjs`(内核闭包 1571 文件 → **6 文件**)。
8
14
  * 0.4.0 = **B2 批**(设计稿 §3 B2):通知族三文件合并进 notifications.ts(engineTaskNotification 全文 +
9
15
  * taskNotificationErrorSupplement 全文 + hookNoticeStore 判定半场)· 17 个 `*WireCaps` + engineWireSdk +
10
16
  * sseIdleTriage + classifierVerdictWire 判定半场 + controlRouter + 模型面纯逻辑 4 件 + 新写 hostEnv;
@@ -23,8 +29,14 @@
23
29
  * —— B2 新增:notifications.ts(11 个通知台账 + 队列 port + 两个 probe 槽)·
24
30
  * agentsWireCaps.ts(prepare 缓存/投影闭包/一次性告警集)· liveModelCatalog.ts(目录 + refresher)·
25
31
  * sessionModelLatch.ts(三个 latch)· ultracodeWireCaps.ts(sticky preset)。
32
+ * —— B3 新增:adapter/runStream.ts(`inFlightTurns` 计数,读口 `isRunStreamActive()`;
33
+ * 两份实例 = 引擎滚动升级门恒读 false ⇒ **turn 中途热换引擎**,正是它当初要防的事)。
26
34
  * 🔴 **宿主装配**(B2 起本包有必须由宿主注入才完整的口):`installNotificationQueuePort()` ——
27
35
  * 不装 = task-notification 投递静默丢失(`notificationQueuePortMisses()` 恒应为 0)。
36
+ * —— B3 新增(可选口,缺席 = 对应 UI 面在该宿主上是哑的,不是报错):`ctx.emitChrome`
37
+ * (runStream 的两条切边:statusline 真 usage / plan-review 审批卡)· `ctx.setTimer`
38
+ * (T40 IDLE-FLUSH,不给 = 长工具参数生成期已生成内容仍押到下一帧才上屏)· `ctx.signal`
39
+ * (Esc 中断)· `ctx.log` / `ctx.probe`(诊断面)。
28
40
  */
29
41
  export * from './seam.js';
30
42
  export * from './workflow.js';
@@ -74,3 +86,12 @@ export * from './liveModelCatalog.js';
74
86
  export * from './modelBudgetRule.js';
75
87
  export * from './sessionModelLatch.js';
76
88
  export * from './effortWire.js';
89
+ // ── B3 批:适配内核(增量合并 + 计量 + 流驱动)(2026-07-27)──────────────────────────────────
90
+ // M/adapter 的 downstream 三件 + types 运行时半场 + runStream 整搬(设计稿 §3 B3)。
91
+ // 🔴 runStream.ts 持 module 级 `inFlightTurns` 计数(isRunStreamActive 的读口)——单例清单新增项:
92
+ // 写方=runStream 自己,读方=壳的引擎滚动升级门(turn 中途绝不热换引擎)。两份实例 = 门恒读 false。
93
+ export * from './adapter/types.js';
94
+ export * from './adapter/downstream/turnUsageToModelUsage.js';
95
+ export * from './adapter/downstream/eventToSdkMessage.js';
96
+ export * from './adapter/downstream/terminalToSdkResult.js';
97
+ export * from './adapter/runStream.js';
package/dist/seam.d.ts CHANGED
@@ -6,13 +6,69 @@
6
6
  * (StampedAgentEvent.id/seq/toolCallId),ctx.uuid() 仅兜底无稳定键合成帧;
7
7
  * 往返守卫不变量:同流重放 ⇒ 同 id 序列。
8
8
  */
9
- import type { SDKMessage } from '@sema-agent/agent-types';
10
- /** 宿主注入的适配上下文(时钟/铸号可测,B 形跨端确定性)。 */
9
+ import type { ModelUsage, SDKMessage } from '@sema-agent/agent-types';
10
+ /**
11
+ * `AbortSignal` 的结构型(B3 扩容,SEAM-GAP-4)。
12
+ *
13
+ * 🔴 **别写成 `AbortSignal`**:本包 tsconfig 是 `"types": []` + 无 DOM lib(浏览器可移植性的
14
+ * 静态保障),`AbortSignal` 这个名字在本包里根本不存在。真 `AbortSignal`(浏览器原生 / Node
15
+ * globalThis)结构上满足本型,直接传即可,零适配。
16
+ */
17
+ export interface AbortSignalLike {
18
+ readonly aborted: boolean;
19
+ }
20
+ /** 宿主定时器句柄(不透明:Node 是 Timeout 对象、浏览器是 number,库侧只负责回传)。 */
21
+ export type TimerHandle = unknown;
22
+ /** `ctx.log` 的级别词表(cli utils/debug logForDebugging 的等价面)。 */
23
+ export type AdapterLogLevel = 'debug' | 'info' | 'warn' | 'error';
24
+ /**
25
+ * 宿主注入的适配上下文(时钟/铸号可测,B 形跨端确定性)。
26
+ *
27
+ * **B3 扩容(0.5.0,契约 minor —— 只加可选位,既有宿主零改动)**:下面五个可选口是
28
+ * 「客户端内核需要、但实现方式因端而异」的能力(设计稿 §5.4①)。浏览器实现全部原生零 polyfill:
29
+ * `log`→console/上报、`probe`→省略、`signal`→AbortController.signal、
30
+ * `setTimer/clearTimer`→setTimeout/clearTimeout。
31
+ *
32
+ * 🔴 **缺席语义都是「该能力不启用」,不是「报错」也不是「换个方式偷偷做」**:
33
+ * 内核绝不 `require('fs')` 写探针、绝不 `console.log`、绝不 `setTimeout` 兜底 ——
34
+ * 那正是这批要切掉的东西(§5.2 的 33 个 `utils/debug` 消费点)。
35
+ */
11
36
  export interface AdapterContext {
12
37
  sessionId: string;
13
38
  /** 兜底铸号——仅当帧无稳定键时使用(见 deriveTranscriptId)。 */
14
39
  uuid(): string;
15
40
  now(): number;
41
+ /**
42
+ * 诊断日志(替 cli `utils/debug.logForDebugging` —— 那一行 import 就把 1565 个文件拉进闭包,
43
+ * 是浏览器可移植性的头号污染源,设计稿 §2.1.3)。缺席 = 内核静默(绝不回落 console)。
44
+ */
45
+ log?(level: AdapterLogLevel, msg: string, data?: unknown): void;
46
+ /**
47
+ * 行式探针(替 cli 的 6 处 `SEMA_*_PROBE` 文件探针 —— Ink 挂载后 console 被吞,壳侧靠写文件
48
+ * 取证)。`channel` = 探针名(如 `'stream'`),`line` = 单行载荷。浏览器宿主直接省略。
49
+ */
50
+ probe?(channel: string, line: string): void;
51
+ /**
52
+ * turn 级中断信号(SEAM-GAP-4)。Esc/abort 后内核立刻停止投影,**不产终答**
53
+ * (CC 自己的 abort 路径渲 InterruptedByUser chrome,由宿主负责)。
54
+ * 缺席 = 该 turn 不可中断(既有行为)。
55
+ */
56
+ signal?: AbortSignalLike;
57
+ /**
58
+ * 宿主定时器(T40 IDLE-FLUSH 竞速需要)。`fn` 到点执行一次。
59
+ * 🔴 **缺席 = IDLE-FLUSH 过渡修不启用**(纯等下一帧,与 0.4.0 逐字同行为)——绝不用
60
+ * `setTimeout` 兜底:那会在没有宏任务概念的宿主上凭空造出一个时钟。
61
+ */
62
+ setTimer?(ms: number, fn: () => void): TimerHandle;
63
+ clearTimer?(handle: TimerHandle): void;
64
+ /**
65
+ * 活体增量攒批节拍(T29)。缺席 = `FLUSH_INTERVAL_MS` 默认值。
66
+ * 🔴 这是**宿主渲染策略**,不是 wire 语义:CC 自己两个端就是两个值(桌面宿主
67
+ * `makeStreamDeltaCoalescer`=16ms 单帧攒批;我们对标的 CC 2.1.207 CLI 口径=100ms,
68
+ * 降重绘频率让长回答滚动更稳)。合并**语义**(先思考后文本、段边界重开块)与节拍无关,
69
+ * 所以节拍下放给宿主、语义留库里。
70
+ */
71
+ coalesceIntervalMs?: number;
16
72
  }
17
73
  /** chrome 平面事件 v1 枚举(cli 现役 UI 面归纳;additive 演进,臂只增不改)。
18
74
  *
@@ -150,6 +206,45 @@ export type ChromeEvent = {
150
206
  toolName: string;
151
207
  toolUseId: string;
152
208
  input: unknown;
209
+ }
210
+ /**
211
+ * B3 新臂 ①(T38 三清的第三清 + T57 五处断闸的第五处)—— E12「下一步可问什么」建议**批**。
212
+ * `suggestions: null` = 作废(turn 开场必发一次:上一轮的建议对新上下文是噪声,挂着不掉会让人
213
+ * 以为是这一轮的产出);非空数组 = 本轮产出。
214
+ * 🔴 UNTRUSTED 模型文本(service 已 redact),**仅 UI**:宿主消费义务 = 推给 composer 上方的
215
+ * chips 面;**绝不回喂模型、绝不落转录、绝不进 /compact 的对话体**。
216
+ * 子流(parentToolCallId 标)的建议不发本臂 —— 断闸在库里,不摊给宿主。
217
+ */
218
+ | {
219
+ kind: 'prompt_suggestions';
220
+ laneProof: LaneProof;
221
+ suggestions: readonly string[] | null;
222
+ }
223
+ /**
224
+ * B3 新臂 ②(runStream 外向边切除,设计稿 §5.2 第二条边)—— 每个 leader `turn_end` 折叠出的
225
+ * 真 usage。此前是 runStream 里一条 `void import('lastTurnUsageStore')` 的壳内动态边。
226
+ * 宿主消费义务:落「最近一次 turn 的真 usage」槽,供 statusline 类消费面在消息面取不到 usage
227
+ * 时回落(不落 = 插件的 Context 条恒 0%,原病复发)。子流 turn_end **不发本臂**(判据同 §E2:
228
+ * statusline 显示的是主会话窗口,子代 usage 会把 Context% 打成孩子的窗口占用)。
229
+ */
230
+ | {
231
+ kind: 'last_turn_usage';
232
+ laneProof: LaneProof;
233
+ usage: ModelUsage;
234
+ }
235
+ /**
236
+ * B3 新臂 ③(runStream 外向边切除,设计稿 §5.2 头号污染源)—— `done{status:"needs_review"}` +
237
+ * gate `plan_review` 的 park 终帧。此前是 runStream 里一条 `await import('planReviewWire')`,
238
+ * 而 planReviewWire → `utils/debug` → **整棵 1565 文件的 CC 树**进闭包(浏览器可移植性归零)。
239
+ * 宿主消费义务:弹审批卡(cli 借 AskUserQuestion overlay 合成 question,决断走
240
+ * `/v1/assistant/tasks/:id/plan_review` resume verb);**fail-soft**——不实现或实现失败都不影响
241
+ * 紧随其后的终态帧照常投影(用户至少看到 plan fence + 一条普通完成)。
242
+ * `result` = done 帧的 RAW `result` 载荷(判形与取 taskId 都由宿主做,库不猜)。
243
+ */
244
+ | {
245
+ kind: 'plan_review_park';
246
+ laneProof: LaneProof;
247
+ result: unknown;
153
248
  };
154
249
  /** 车道纪律一等公民([1617] 家族固化):chrome 事件构造必须携判别证明。 */
155
250
  export type LaneProof = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/client-core",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Client-side session runtime shared by every sema human client (TUI / web / desktop): sema wire frames (AgentEvent) -> CC session vocabulary (SDKMessage) with dual-plane output (transcript/chrome), deterministic transcript ids, lane discipline as a type, and the notification/dedup ledgers. Every CC-skin shape is collected here so the wire itself stays neutral. Blackboard [1832] design axioms; [1651]/[1652]/[1653] signed seam design. Renamed from @sema-agent/wire-cc-adapter (0.1.x).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,7 +14,7 @@
14
14
  "sideEffects": false,
15
15
  "scripts": {
16
16
  "build": "tsc -p tsconfig.json",
17
- "test": "node scripts/run-client-core-pure-test.mjs && node scripts/run-client-core-diff-test.mjs"
17
+ "test": "node scripts/run-client-core-pure-test.mjs && node scripts/run-client-core-portability-test.mjs && node scripts/run-client-core-diff-test.mjs"
18
18
  },
19
19
  "dependencies": {},
20
20
  "peerDependencies": {