@sema-agent/client-core 0.2.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 +112 -0
- package/dist/adapt.d.ts +66 -0
- package/dist/adapt.js +773 -0
- package/dist/diagnostics.d.ts +37 -0
- package/dist/diagnostics.js +29 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/notifications.d.ts +55 -0
- package/dist/notifications.js +97 -0
- package/dist/retryStatus.d.ts +36 -0
- package/dist/retryStatus.js +28 -0
- package/dist/seam.d.ts +187 -0
- package/dist/seam.js +13 -0
- package/dist/steering.d.ts +61 -0
- package/dist/steering.js +103 -0
- package/dist/workflow.d.ts +18 -0
- package/dist/workflow.js +60 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# @sema-agent/client-core
|
|
2
|
+
|
|
3
|
+
**Client-side session runtime**, shared by every sema human client — TUI, web and desktop.
|
|
4
|
+
It turns sema wire frames (`AgentEvent`) into CC-shaped session state (`SDKMessage` transcript
|
|
5
|
+
messages + `ChromeEvent` UI events), and it is where the *client-side* session logic lives, so
|
|
6
|
+
that each UI is left with rendering and input handling only.
|
|
7
|
+
|
|
8
|
+
Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migration* below).
|
|
9
|
+
|
|
10
|
+
## Design axioms
|
|
11
|
+
|
|
12
|
+
1. **Every client copies the CC UI/UX — but no CC shape reaches the wire.** The CC vocabulary
|
|
13
|
+
(`SDKMessage`), chrome events and CC-worded copy are collected *here*; the wire stays free
|
|
14
|
+
of them.
|
|
15
|
+
2. **The wire carries only neutral, universal UI-plane information** — structured results, DAG
|
|
16
|
+
relations, lifecycle discrimination, ownership filtering: what *any* human client needs,
|
|
17
|
+
CC-skinned or not.
|
|
18
|
+
3. **The debt this package pays off is an information cut, not a vocabulary problem.** In a
|
|
19
|
+
single-process client (CC) the UI and the engine share memory and UI-plane facts are free;
|
|
20
|
+
here they cross a wire on which the model was historically the first-class consumer and the
|
|
21
|
+
human UI second. This package makes that cut an explicit, first-class contract instead of
|
|
22
|
+
per-bug patches inside one shell.
|
|
23
|
+
|
|
24
|
+
## Scope
|
|
25
|
+
|
|
26
|
+
- **Today (0.2.0)** — the adapter seam plus the first half of the `adapt()` pipeline (detail
|
|
27
|
+
below), migrated as-is from `wire-cc-adapter` 0.1.x.
|
|
28
|
+
- **Planned** — the shell's remaining shared session logic lands here in batches: the
|
|
29
|
+
five-layer frame dispatch, the compensation arms, the notification/dedup ledgers, the
|
|
30
|
+
fleet/panel projections. The TUI then keeps only its Ink rendering and keyboard interaction;
|
|
31
|
+
the web session view (first target consumer) and the desktop client (second) consume the
|
|
32
|
+
same package.
|
|
33
|
+
|
|
34
|
+
## Dependencies
|
|
35
|
+
|
|
36
|
+
- **Zero runtime dependencies** (`"dependencies": {}`), `sideEffects: false`.
|
|
37
|
+
- **Peer / type-only**: `@sema-agent/agent-types` owns the CC session vocabulary (today's only
|
|
38
|
+
import — `SDKMessage`, type-only), `@sema-agent/sdk` owns the wire contract.
|
|
39
|
+
- The direction is constitutional: **sdk (wire) + agent-types (vocabulary) → this package**,
|
|
40
|
+
and neither depends back.
|
|
41
|
+
|
|
42
|
+
## Signed design
|
|
43
|
+
|
|
44
|
+
- **Dual-plane output** — `transcript` (persistable CC messages) vs `chrome` (ephemeral UI
|
|
45
|
+
events). Criterion: *"what should still be visible after a restart goes to transcript;
|
|
46
|
+
pure-transient goes to chrome."*
|
|
47
|
+
- **Deterministic transcript ids** — derived from wire stable keys (frame id > seq >
|
|
48
|
+
toolCallId); `ctx.uuid()` only for keyless synthetic frames. Invariant (guarded):
|
|
49
|
+
same stream replayed ⇒ same id sequence.
|
|
50
|
+
- **Lane discipline as a type** — chrome events require a `LaneProof`; a historical ghost-row bug
|
|
51
|
+
family is structurally impossible to reintroduce.
|
|
52
|
+
- **Field-level round-trip guard** — every semantic wire field either maps into
|
|
53
|
+
the CC message/chrome event or carries an explicit DROPPED annotation; reflective tests
|
|
54
|
+
turn upstream field additions into compile-time red.
|
|
55
|
+
|
|
56
|
+
## Shipped so far
|
|
57
|
+
|
|
58
|
+
0.1.0 shipped the seam types, the id derivation, and the first tranche of battle-tested pure
|
|
59
|
+
functions extracted from the sema CLI shell (workflow lane discrimination, workflow poll
|
|
60
|
+
envelope/projection, task-notification XML rendering with the `<result>` discipline, dedup
|
|
61
|
+
keys).
|
|
62
|
+
|
|
63
|
+
0.1.2 landed the **first half of the `adapt()` pipeline**:
|
|
64
|
+
|
|
65
|
+
- `createWireToCcAdapter()` / `adapt(frames, ctx)` — `AsyncIterable<frame>` →
|
|
66
|
+
`AsyncGenerator<AdapterOutput>`, covering the pure-projection arms: assistant text /
|
|
67
|
+
thinking / tool_use (engine-subagent param remap, `_sema_*` sanitising, reject/cancel/error
|
|
68
|
+
sentinels, per-response grouping id), live delta coalescing, `task_notification` XML,
|
|
69
|
+
diagnostics / steering / workspace-changed attachments, compact boundaries, retry status,
|
|
70
|
+
workflow completion enqueue.
|
|
71
|
+
- Host-coupled arms (panel store writes, hook fires, queue drops, task-ledger sync) project
|
|
72
|
+
into **additive `ChromeEvent` arms**, each carrying its *host consumption duty* in the
|
|
73
|
+
`seam.ts` comment — the side effect stays with the host, the decision logic moves here.
|
|
74
|
+
- Ledger serialisation (`exportLedger` / `importLedger` / `importLedgerFromTranscript`) so a
|
|
75
|
+
restarted host reloads the notification dedup state instead of re-feeding the model
|
|
76
|
+
(the at-least-once completion-inbox hazard).
|
|
77
|
+
- A **differential guard**: the same frame stream is fed to `adapt()` and to the CLI's
|
|
78
|
+
`sdkMessagesToCcEvents`, asserting field-level equivalence of the transcript plane, the
|
|
79
|
+
attachment payloads, the concatenated live deltas, and the observable host-store
|
|
80
|
+
trajectories — plus the replay id invariant. Arms not yet ported are listed in
|
|
81
|
+
`ADAPTER_COVERAGE.todo` and pinned per-fixture, so a silently missing arm turns the guard
|
|
82
|
+
red.
|
|
83
|
+
|
|
84
|
+
0.2.0 is the repo/name migration: **no behaviour change**, both guard suites carried over and
|
|
85
|
+
green (22 + 203 checks).
|
|
86
|
+
|
|
87
|
+
Known intentional deltas from the CLI are enumerated in `ADAPTER_DIVERGENCES`.
|
|
88
|
+
|
|
89
|
+
## Guards
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm install
|
|
93
|
+
node scripts/run-client-core-pure-test.mjs # 22 consumer-view checks; no CLI tree needed
|
|
94
|
+
node scripts/run-client-core-diff-test.mjs # 203 checks: differential + replay-id + ledger
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The differential half needs the CLI tree (the reference bridge lives there). It is located via
|
|
98
|
+
`SEMA_CLI_ROOT`, else a sibling checkout of the sema CLI source tree (internal reference bridge; not part of the published package); a pinned `SEMA_CLI_ROOT` without
|
|
99
|
+
`src/sema/upstreamBridge.ts` fails loudly instead of falling back to another tree. With no CLI
|
|
100
|
+
tree in sight the runner still executes the package-local replay/ledger half and then **exits 3
|
|
101
|
+
(SKIP)** — a run that never compared anything is never reported as a pass.
|
|
102
|
+
`SEMA_DIFF_EMPTY_ADAPT=1` replaces `adapt()` with an empty generator and must turn the guard
|
|
103
|
+
red (the fence proving it has teeth).
|
|
104
|
+
|
|
105
|
+
## Migration
|
|
106
|
+
|
|
107
|
+
`@sema-agent/wire-cc-adapter` (published 0.1.0 / 0.1.1) is **deprecated in favour of this
|
|
108
|
+
package**. Same seam design, same guards; the version line restarts at **0.2.0** so the two
|
|
109
|
+
names never overlap. For consumers the change is the dependency name only — the exported API
|
|
110
|
+
(`createWireToCcAdapter`, `deriveTranscriptId`, `renderTaskNotificationXml`,
|
|
111
|
+
`taskNotificationDedupKey`, `isWorkflowAgentTaskId`, `parseWorkflowPollEnvelope`,
|
|
112
|
+
`projectWorkflowTaskOutput`, the `seam.ts` types …) is unchanged.
|
package/dist/adapt.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { AdapterContext, AdapterOutput, WireToCcAdapter } from './seam.js';
|
|
2
|
+
/** cli COALESCER 节流(207 口径;187 时代是 16ms)。宿主无关,时钟从 ctx.now() 取。 */
|
|
3
|
+
export declare const FLUSH_INTERVAL_MS = 100;
|
|
4
|
+
/** cli SUBAGENT_TOOL_NAMES / WORKFLOW_TOOL_NAMES 同源(判别资产,别各自重写)。 */
|
|
5
|
+
export declare const SUBAGENT_TOOL_NAMES: ReadonlySet<string>;
|
|
6
|
+
export declare const WORKFLOW_TOOL_NAMES: ReadonlySet<string>;
|
|
7
|
+
/**
|
|
8
|
+
* CC 自己的 reject/cancel 哨兵串(**数据**,非 CC 逻辑;cli src/utils/messages.ts:210-212 逐字)。
|
|
9
|
+
* CC 的 UserToolResultMessage 按 content 串前缀路由到 reject/cancel 卡,不看 is_error 位。
|
|
10
|
+
*/
|
|
11
|
+
export declare const REJECT_MESSAGE = "The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.";
|
|
12
|
+
export declare const CANCEL_MESSAGE = "The user doesn't want to take this action right now. STOP what you are doing and wait for the user to tell you how to proceed.";
|
|
13
|
+
/** CJK 加权 token 估算(cli estimateCjkTokens 逐字:CJK≈0.6,其余≈0.25 tok/char)。 */
|
|
14
|
+
export declare function estimateCjkTokens(s: string): number;
|
|
15
|
+
/** 引擎 subagent 卡的短标签(cli shortTaskLabel 逐字)。 */
|
|
16
|
+
export declare function shortTaskLabel(task: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* tool_use 块的渲染面消毒(cli assistant 臂逐条搬运,**纯函数**,不改入参):
|
|
19
|
+
* ① 摘 §E2 wire 身份(eventId/parentToolCallId)并从渲染块剥掉;
|
|
20
|
+
* ② 剥所有 `_sema_*` 保留哨兵(CC 的 per-tool 渲染器 safeParse strictObject,多一个键就降级成通用卡);
|
|
21
|
+
* ③ 引擎 subagent 参数名 {agent,task,taskName} → CC 名 {subagent_type,description,prompt}
|
|
22
|
+
* (strict schema 拒绝多余键 ⇒ REPLACE 不 merge;taskName 缺席时从 task 派生短标签);
|
|
23
|
+
* ④ 件8 类型标签诚实化:引擎通道兑现不了的 subagent_type 值一律删(只留 fork/general-purpose/worker),
|
|
24
|
+
* 否则卡头会显示 `Plan(…)` 而实跑 general-purpose。
|
|
25
|
+
*/
|
|
26
|
+
export declare function sanitizeToolUseBlock(block: Record<string, unknown>): {
|
|
27
|
+
block: Record<string, unknown>;
|
|
28
|
+
rawInput: unknown;
|
|
29
|
+
eventId?: string;
|
|
30
|
+
parentToolCallId?: string;
|
|
31
|
+
};
|
|
32
|
+
/** 台账序列化形(#63 回植:重启恢复 = 台账加载 + 纯重放)。 */
|
|
33
|
+
export interface AdapterLedgerState {
|
|
34
|
+
version: 1;
|
|
35
|
+
/** 已渲染过的通知去重键(`[external:]taskId:status[:seq]`)。 */
|
|
36
|
+
renderedNotifications: string[];
|
|
37
|
+
/** 已「跨通道通知过」的 run(收到终态推送帧后,补发通道不得再注入模型一遍)。 */
|
|
38
|
+
notifiedRuns: string[];
|
|
39
|
+
/** 完成卡已入队的 run(probe 合成先发时,后到的推送帧只记账不再渲第二条)。 */
|
|
40
|
+
cardEnqueuedRuns: string[];
|
|
41
|
+
}
|
|
42
|
+
/** 本批覆盖清单——差分守卫按它区分「已覆盖臂必须逐字段等价」与「已声明未覆盖臂」。 */
|
|
43
|
+
export declare const ADAPTER_COVERAGE: {
|
|
44
|
+
/** 已落码的帧臂。 */
|
|
45
|
+
readonly frames: readonly ["assistant", "user", "stream_event", "turn_usage", "result", "system", "task_notification", "workflow_complete", "diagnostics", "steering_injected", "workspace_changed", "retry_status", "tool_end_result(仅 response-id 复位 + 开卡台账出栈,不铸 tool_result)"];
|
|
46
|
+
/** 已落码臂产出的 transcript 消息类目(差分守卫的比对域)。 */
|
|
47
|
+
readonly transcriptKinds: readonly ["assistant_text", "assistant_thinking", "assistant_tool_use", "user_tool_result_decision", "user_task_notification", "system_passthrough"];
|
|
48
|
+
/** 如实留白——写不动/依赖未搬运资产的臂,#52b 起分批接。 */
|
|
49
|
+
readonly todo: readonly ["tool_end_result → user tool_result 正常路径(需搬 wireOutputToBody/structuredToToolUseResult/mock 合成器 + TodoWrite/ReportFindings 富卡片,约 600 行;workflow 轮询信封的人话投影已在 workflow.ts 就位,等这条接线)", "tool_end_result 的两个侧产物:Remember → memory_saved 系统消息、task/task-list structured → 面板台账全量同步", "result/turn 末的开卡兜底关闭(同上,依赖合成器)", "task_progress → 面板行绑定(bindTaskCard FIFO/显式父、inline stats、alias、resident 台账、SubagentStart hook、workflow lane 三级门)", "settlePanelTasks 三处 sweep(依赖上面的行台账)", "#117a bg Bash 回执探测(detectEngineBgShellReceipt 文案锚定探测器)", "abort/Esc 半场(AdapterContext 无 signal 位——见 SEAM-GAP-4)", "IDLE_FLUSH 1.5s 竞速(件1 大 Write 静默窗;需 ctx 侧定时器能力)", "macrotask 让渡(cli 在提交前让出宏任务给 Ink;宿主渲染策略,不进库)"];
|
|
50
|
+
};
|
|
51
|
+
/** 本批**已知的、有意的**行为分歧(差分守卫显式登记,绝不当等价) */
|
|
52
|
+
export declare const ADAPTER_DIVERGENCES: readonly [string, string, string];
|
|
53
|
+
/** 有台账序列化位的适配器(seam 的 WireToCcAdapter 只定了 exportLedger——见 SEAM-GAP-1)。 */
|
|
54
|
+
export interface WireToCcAdapterWithLedger extends WireToCcAdapter {
|
|
55
|
+
exportLedger(): AdapterLedgerState & Record<string, unknown>;
|
|
56
|
+
/** 台账回植:返回真正吃进去的键数。 */
|
|
57
|
+
importLedger(state: Partial<AdapterLedgerState> | Record<string, unknown>): number;
|
|
58
|
+
/** #63 resume 回植:从落盘转录里的 `<task-notification>` 块重建终态去重键。 */
|
|
59
|
+
importLedgerFromTranscript(messages: ReadonlyArray<unknown>): number;
|
|
60
|
+
/** 宿主把「完成卡已入队」的事实喂回来(双通道反向去重;见 SEAM-GAP-3)。 */
|
|
61
|
+
noteCardEnqueued(runId: string): void;
|
|
62
|
+
}
|
|
63
|
+
/** 造一个带台账的适配器实例(**一个 session 一个**——跨 turn 去重靠实例存活,重启靠 import/export)。 */
|
|
64
|
+
export declare function createWireToCcAdapter(): WireToCcAdapterWithLedger;
|
|
65
|
+
/** 一次性(无跨 turn 台账)投影:`frames → AdapterOutput`。多 turn 请用 createWireToCcAdapter()。 */
|
|
66
|
+
export declare function adapt(frames: AsyncIterable<unknown>, ctx: AdapterContext): AsyncGenerator<AdapterOutput>;
|