@sema-agent/client-core 0.2.0 → 0.3.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/dist/engineAgentPanelStore.d.ts +111 -0
- package/dist/engineAgentPanelStore.js +93 -0
- package/dist/engineCapsCache.d.ts +32 -0
- package/dist/engineCapsCache.js +64 -0
- package/dist/engineInlineTaskStats.d.ts +66 -0
- package/dist/engineInlineTaskStats.js +97 -0
- package/dist/engineToolLabelStore.d.ts +32 -0
- package/dist/engineToolLabelStore.js +55 -0
- package/dist/fleetAgentPanelProjection.d.ts +31 -0
- package/dist/fleetAgentPanelProjection.js +0 -0
- package/dist/fleetTaskDesc.d.ts +20 -0
- package/dist/fleetTaskDesc.js +35 -0
- package/dist/index.d.ts +21 -1
- package/dist/index.js +21 -1
- package/dist/liveQuestionStore.d.ts +101 -0
- package/dist/liveQuestionStore.js +109 -0
- package/dist/printToolResultFrame.d.ts +59 -0
- package/dist/printToolResultFrame.js +65 -0
- package/dist/steering.d.ts +4 -0
- package/dist/steering.js +4 -0
- package/dist/subagentContentStore.d.ts +131 -0
- package/dist/subagentContentStore.js +322 -0
- package/dist/types/engineState.d.ts +116 -0
- package/dist/types/engineState.js +17 -0
- package/dist/workflow.d.ts +40 -8
- package/dist/workflow.js +69 -11
- package/package.json +1 -1
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⇄ B1 批搬迁(2026-07-27,多端改造设计稿 §2.4.1-B):cli src/sema/subagentContentStore.ts 逐字搬入(零依赖;壳侧改 re-export)。
|
|
3
|
+
* 🔴 module 级台账(tasks/parentToTask/ownEngineRuns/bgFacts)= 单实例语义:写方(seam adapter
|
|
4
|
+
* runStream 的子代内容分流 + liveClient 的 run_started own 台账)与读方(查看态/帧级 own 判别)
|
|
5
|
+
* 必须走**同一个模块实例**,否则 own 判别恒 false(帧级校验静默失效)。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* subagentContentStore — dep-free side-channel store for ENGINE subagent CONTENT events (C1 consumption,
|
|
9
|
+
* service 1.89 `forwardSubagentEvents` + core 1.219; blackboard [381]⑤/[383]③).
|
|
10
|
+
*
|
|
11
|
+
* WIRE FACTS: with `forwardSubagentEvents: true` on the task request, the live stream carries the
|
|
12
|
+
* subagent's text_delta / reasoning_delta / tool_start / tool_end stamped with EventIdentity
|
|
13
|
+
* {taskId, parentToolCallId} (§E1 redact upstream; §E2 identity). The seam adapter (runStream.ts)
|
|
14
|
+
* DIVERTS those events here — they must never reach the main-transcript pipeline (a subagent's tokens
|
|
15
|
+
* rendering as the leader's text is the pollution failure mode).
|
|
16
|
+
*
|
|
17
|
+
* WHAT IT KEEPS (bounded, per task):
|
|
18
|
+
* - a NEUTRAL item log: {kind:'text'|'thinking',text} | {kind:'tool',name,input,output,isError,id}
|
|
19
|
+
* (ring-capped) — the 查看态 transcript backlog, so entering a running agent mid-flight still shows
|
|
20
|
+
* history. Message SHAPES are built by the consumers (engineAgentView/chrome-agentprogress own the
|
|
21
|
+
* CC message creators; this store stays dep-free like engineAgentPanelStore).
|
|
22
|
+
* - live accumulation buffers (streaming text/thinking not yet segment-closed).
|
|
23
|
+
* - taskId → parentToolCallId (the C2 steer target — service routes subagent steer by parentToolCallId).
|
|
24
|
+
*
|
|
25
|
+
* NOTIFY: coalesced (~250ms) per-task notify so a per-token delta storm never turns into per-token
|
|
26
|
+
* AppState writes; consumers pull a snapshot on notify.
|
|
27
|
+
*/
|
|
28
|
+
const MAX_ITEMS_PER_TASK = 200;
|
|
29
|
+
const MAX_TASKS = 32;
|
|
30
|
+
const NOTIFY_COALESCE_MS = 250;
|
|
31
|
+
const tasks = new Map();
|
|
32
|
+
// parentToolCallId ↔ engine taskId aliasing: CONTENT events carry ONLY {eventId, parentToolCallId}
|
|
33
|
+
// (EventIdentity — no taskId on the wire), while the panel rows / 查看态 lookups key by the ENGINE task id
|
|
34
|
+
// (task_progress carries BOTH). Content arriving before the first tick parks under the parent key and is
|
|
35
|
+
// RE-KEYED when the alias registers.
|
|
36
|
+
const parentToTask = new Map();
|
|
37
|
+
const taskToParent = new Map();
|
|
38
|
+
let notifyListener = null;
|
|
39
|
+
const pendingNotify = new Set();
|
|
40
|
+
let notifyTimer = null;
|
|
41
|
+
/** LRU touch:Map 迭代序=插入序,容量淘汰取「最老」——不刷新的话是纯 FIFO,长会话里第 33 个
|
|
42
|
+
* 子代会把【仍在流式/正被查看】的最早行挤掉(扩散面复审 F11)。访问即 re-insert,活跃项恒新。 */
|
|
43
|
+
function touchLru(map, key) {
|
|
44
|
+
const v = map.get(key);
|
|
45
|
+
if (v !== undefined) {
|
|
46
|
+
map.delete(key);
|
|
47
|
+
map.set(key, v);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function stateFor(taskId, parentToolCallId) {
|
|
51
|
+
let s = tasks.get(taskId);
|
|
52
|
+
if (!s) {
|
|
53
|
+
// Bound total task footprint: evict the LEAST-RECENTLY-USED task's log (reads/writes touch below).
|
|
54
|
+
if (tasks.size >= MAX_TASKS) {
|
|
55
|
+
const oldest = tasks.keys().next().value;
|
|
56
|
+
if (oldest !== undefined)
|
|
57
|
+
tasks.delete(oldest);
|
|
58
|
+
}
|
|
59
|
+
s = { items: [], textBuf: '', thinkBuf: '', openTools: new Map(), parentToolCallId };
|
|
60
|
+
tasks.set(taskId, s);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
touchLru(tasks, taskId);
|
|
64
|
+
}
|
|
65
|
+
return s;
|
|
66
|
+
}
|
|
67
|
+
function pushItem(s, item) {
|
|
68
|
+
s.items.push(item);
|
|
69
|
+
if (s.items.length > MAX_ITEMS_PER_TASK) {
|
|
70
|
+
s.items.splice(0, s.items.length - MAX_ITEMS_PER_TASK);
|
|
71
|
+
// open-tool indexes shifted — rebuild from the surviving items
|
|
72
|
+
s.openTools.clear();
|
|
73
|
+
s.items.forEach((it, i) => {
|
|
74
|
+
if (it.kind === 'tool' && it.output === undefined)
|
|
75
|
+
s.openTools.set(it.id, i);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Close out streaming buffers into items (segment boundary: a tool starts, or the run settles). */
|
|
80
|
+
function flushBuffers(s) {
|
|
81
|
+
if (s.thinkBuf.trim())
|
|
82
|
+
pushItem(s, { kind: 'thinking', text: s.thinkBuf });
|
|
83
|
+
s.thinkBuf = '';
|
|
84
|
+
if (s.textBuf.trim())
|
|
85
|
+
pushItem(s, { kind: 'text', text: s.textBuf });
|
|
86
|
+
s.textBuf = '';
|
|
87
|
+
}
|
|
88
|
+
function scheduleNotify(taskId) {
|
|
89
|
+
pendingNotify.add(taskId);
|
|
90
|
+
if (notifyTimer)
|
|
91
|
+
return;
|
|
92
|
+
notifyTimer = setTimeout(() => {
|
|
93
|
+
notifyTimer = null;
|
|
94
|
+
const ids = [...pendingNotify];
|
|
95
|
+
pendingNotify.clear();
|
|
96
|
+
if (!notifyListener)
|
|
97
|
+
return;
|
|
98
|
+
for (const id of ids) {
|
|
99
|
+
try {
|
|
100
|
+
notifyListener(id);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// a consumer render error must never tear down the stream drain
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}, NOTIFY_COALESCE_MS);
|
|
107
|
+
}
|
|
108
|
+
/** Register the parentToolCallId ↔ engine-taskId pair (from task_progress, which carries both).
|
|
109
|
+
* Content parked under the parent key migrates to the canonical task key. */
|
|
110
|
+
export function registerSubagentAlias(parentToolCallId, taskId) {
|
|
111
|
+
if (!parentToolCallId || !taskId || parentToolCallId === taskId)
|
|
112
|
+
return;
|
|
113
|
+
parentToTask.set(parentToolCallId, taskId);
|
|
114
|
+
taskToParent.set(taskId, parentToolCallId);
|
|
115
|
+
// a notify queued under the parent key before the alias landed must fire under the canonical key
|
|
116
|
+
if (pendingNotify.delete(parentToolCallId))
|
|
117
|
+
pendingNotify.add(taskId);
|
|
118
|
+
const parked = tasks.get(parentToolCallId);
|
|
119
|
+
if (parked && !tasks.has(taskId)) {
|
|
120
|
+
tasks.delete(parentToolCallId);
|
|
121
|
+
tasks.set(taskId, parked);
|
|
122
|
+
scheduleNotify(taskId);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function canonicalKey(ev) {
|
|
126
|
+
// the wire's content events carry no taskId — runStream passes parentToolCallId in both slots
|
|
127
|
+
if (ev.taskId !== ev.parentToolCallId)
|
|
128
|
+
return ev.taskId;
|
|
129
|
+
return parentToTask.get(ev.parentToolCallId) ?? ev.parentToolCallId;
|
|
130
|
+
}
|
|
131
|
+
export function publishSubagentContentEvent(ev) {
|
|
132
|
+
const key = canonicalKey(ev);
|
|
133
|
+
const s = stateFor(key, ev.parentToolCallId);
|
|
134
|
+
switch (ev.type) {
|
|
135
|
+
case 'text_delta':
|
|
136
|
+
if (typeof ev.delta === 'string')
|
|
137
|
+
s.textBuf += ev.delta;
|
|
138
|
+
break;
|
|
139
|
+
case 'reasoning_delta':
|
|
140
|
+
if (typeof ev.delta === 'string')
|
|
141
|
+
s.thinkBuf += ev.delta;
|
|
142
|
+
break;
|
|
143
|
+
case 'tool_start': {
|
|
144
|
+
flushBuffers(s);
|
|
145
|
+
const id = ev.toolCallId ?? `${ev.taskId}-tool-${s.items.length}`;
|
|
146
|
+
pushItem(s, { kind: 'tool', id, name: ev.toolName ?? 'Tool', input: ev.args ?? {} });
|
|
147
|
+
s.openTools.set(id, s.items.length - 1);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case 'tool_end': {
|
|
151
|
+
const id = ev.toolCallId;
|
|
152
|
+
const idx = id !== undefined ? s.openTools.get(id) : undefined;
|
|
153
|
+
if (idx !== undefined && s.items[idx]?.kind === 'tool') {
|
|
154
|
+
const it = s.items[idx];
|
|
155
|
+
it.output = ev.output ?? '';
|
|
156
|
+
it.isError = ev.isError === true;
|
|
157
|
+
if (id !== undefined)
|
|
158
|
+
s.openTools.delete(id);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// orphan close (start lost to the ring / arrived first) — synthesize a resolved card
|
|
162
|
+
pushItem(s, {
|
|
163
|
+
kind: 'tool',
|
|
164
|
+
id: id ?? `${ev.taskId}-tool-${s.items.length}`,
|
|
165
|
+
name: ev.toolName ?? 'Tool',
|
|
166
|
+
input: ev.args ?? {},
|
|
167
|
+
output: ev.output ?? '',
|
|
168
|
+
isError: ev.isError === true,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
scheduleNotify(key);
|
|
175
|
+
}
|
|
176
|
+
/** 查看态 composer echo (C2 steer optimistic display) — segment-closes the buffers first so the echo
|
|
177
|
+
* lands AFTER the text streamed before it. taskId may be unseen (row entered before any content):
|
|
178
|
+
* parentToolCallId may then be unknown — pass '' and the steer path resolves the target separately. */
|
|
179
|
+
export function pushSubagentLocalEcho(taskId, text) {
|
|
180
|
+
const s = stateFor(taskId, tasks.get(taskId)?.parentToolCallId ?? '');
|
|
181
|
+
flushBuffers(s);
|
|
182
|
+
pushItem(s, { kind: 'echo', text });
|
|
183
|
+
scheduleNotify(taskId);
|
|
184
|
+
}
|
|
185
|
+
export function getSubagentContentSnapshot(taskId) {
|
|
186
|
+
const key = tasks.has(taskId) ? taskId : (taskToParent.get(taskId) ?? '');
|
|
187
|
+
const s = tasks.get(key);
|
|
188
|
+
if (!s)
|
|
189
|
+
return null;
|
|
190
|
+
touchLru(tasks, key); // 正被查看的转录不因容量淘汰当场清空(F11)
|
|
191
|
+
return { items: [...s.items], textBuf: s.textBuf, thinkBuf: s.thinkBuf };
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 把一个子代的内容账本投影成查看态渲染计划。
|
|
195
|
+
*
|
|
196
|
+
* [1568] 之前的形(壳侧 engineAgentView 里)有两处「等段闭合」:①遇到第一个未闭合工具就 break;
|
|
197
|
+
* ②textBuf/thinkBuf 完全不投影。实测(engine 1.275.0 真跑):数据面 ~23 帧/s 的 delta 连续到壳,
|
|
198
|
+
* 查看态却只在 tool 边界更新(进查看态后 6.0s 全空,之后每 5–15s 才动一次)。现形:在飞工具照投,
|
|
199
|
+
* 流式尾巴作为 live 槽投 —— 屏面粒度 = wire 粒度(store notify 已按 250ms 合并)。
|
|
200
|
+
*/
|
|
201
|
+
export function planSubagentViewSlots(taskId) {
|
|
202
|
+
const snap = getSubagentContentSnapshot(taskId);
|
|
203
|
+
if (!snap)
|
|
204
|
+
return [];
|
|
205
|
+
const out = [];
|
|
206
|
+
snap.items.forEach((it, i) => {
|
|
207
|
+
if (it.kind === 'tool') {
|
|
208
|
+
out.push({
|
|
209
|
+
kind: 'tool',
|
|
210
|
+
slot: `u${i}`,
|
|
211
|
+
id: it.id,
|
|
212
|
+
name: it.name,
|
|
213
|
+
input: it.input,
|
|
214
|
+
...(it.output !== undefined ? { output: it.output } : {}),
|
|
215
|
+
...(it.isError === true ? { isError: true } : {}),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
else if (it.kind === 'text') {
|
|
219
|
+
out.push({ kind: 'text', slot: `t${i}`, text: it.text });
|
|
220
|
+
}
|
|
221
|
+
else if (it.kind === 'thinking') {
|
|
222
|
+
out.push({ kind: 'thinking', slot: `k${i}`, text: it.text });
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
out.push({ kind: 'echo', slot: `e${i}`, text: it.text });
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
// 尾巴顺序与 flushBuffers 落项顺序一致(thinking 先、text 后)——段闭合时屏面内容连续不跳。
|
|
229
|
+
if (snap.thinkBuf.trim()) {
|
|
230
|
+
out.push({ kind: 'thinking', slot: 'live-think', text: snap.thinkBuf, live: true });
|
|
231
|
+
}
|
|
232
|
+
if (snap.textBuf.trim()) {
|
|
233
|
+
out.push({ kind: 'text', slot: 'live-text', text: snap.textBuf, live: true });
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
/** C2 steer target: the service routes subagent steer by the delegating tool call's id. */
|
|
238
|
+
export function parentToolCallIdOf(taskId) {
|
|
239
|
+
return taskToParent.get(taskId) ?? tasks.get(taskId)?.parentToolCallId;
|
|
240
|
+
}
|
|
241
|
+
/** Settle a task's streaming buffers (turn ended / run settled) so the snapshot is complete. */
|
|
242
|
+
export function settleSubagentContent(taskId) {
|
|
243
|
+
const s = tasks.get(taskId);
|
|
244
|
+
if (!s)
|
|
245
|
+
return;
|
|
246
|
+
flushBuffers(s);
|
|
247
|
+
scheduleNotify(taskId);
|
|
248
|
+
}
|
|
249
|
+
export function clearSubagentContent(taskId) {
|
|
250
|
+
tasks.delete(taskId);
|
|
251
|
+
}
|
|
252
|
+
const bgFacts = new Map();
|
|
253
|
+
export function recordBgTerminalFacts(taskId, facts) {
|
|
254
|
+
if (!taskId)
|
|
255
|
+
return;
|
|
256
|
+
if (bgFacts.size >= MAX_TASKS && !bgFacts.has(taskId)) {
|
|
257
|
+
const oldest = bgFacts.keys().next().value;
|
|
258
|
+
if (oldest !== undefined)
|
|
259
|
+
bgFacts.delete(oldest);
|
|
260
|
+
}
|
|
261
|
+
bgFacts.delete(taskId); // LRU touch(F11):重录/更新即刷新插入序
|
|
262
|
+
bgFacts.set(taskId, facts);
|
|
263
|
+
scheduleNotify(taskId); // 打开中的查看态借既有 notify 链重建消息
|
|
264
|
+
}
|
|
265
|
+
export function getBgTerminalFacts(taskId) {
|
|
266
|
+
const key = bgFacts.has(taskId) ? taskId : (taskToParent.get(taskId) ?? '');
|
|
267
|
+
touchLru(bgFacts, key);
|
|
268
|
+
return bgFacts.get(key);
|
|
269
|
+
}
|
|
270
|
+
// ── bg 子代 → spawn 它的 leader run(server 1.244 subagentOutput 读面的寻址元组)──────────────
|
|
271
|
+
// GET /v1/runs/:runId/subagents/:handle/output 的 runId = spawn 该子代的 run(registry access 从
|
|
272
|
+
// run 行推导,[1491]①)。来源两路:fleet 行 parentId(出生帧即带)+ bg_notification.parentTaskId。
|
|
273
|
+
const bgParentRun = new Map();
|
|
274
|
+
export function recordBgParentRun(taskId, runId) {
|
|
275
|
+
if (!taskId || !runId || taskId === runId)
|
|
276
|
+
return;
|
|
277
|
+
if (bgParentRun.size >= MAX_TASKS * 2 && !bgParentRun.has(taskId)) {
|
|
278
|
+
const oldest = bgParentRun.keys().next().value;
|
|
279
|
+
if (oldest !== undefined)
|
|
280
|
+
bgParentRun.delete(oldest);
|
|
281
|
+
}
|
|
282
|
+
bgParentRun.delete(taskId); // LRU touch(F11):活跃映射不被容量淘汰挤掉 → 读面退错 runId
|
|
283
|
+
bgParentRun.set(taskId, runId);
|
|
284
|
+
}
|
|
285
|
+
export function getBgParentRun(taskId) {
|
|
286
|
+
touchLru(bgParentRun, taskId);
|
|
287
|
+
return bgParentRun.get(taskId);
|
|
288
|
+
}
|
|
289
|
+
// ── 本壳自己发起过的引擎 run 台账([1498]④ 帧级校验换锚)────────────────────────────────────
|
|
290
|
+
// 喂给:engineToolDetach.setActiveEngineTaskId(liveClient 从 run_started 帧绑定,本壳每个交互
|
|
291
|
+
// turn 的 run 必经)。与 bgParentRun 的关键区别:bgParentRun 由 fleet 行/通知帧喂(≤1.244 事实
|
|
292
|
+
// 广播下可能混入别的壳的行),这份台账**只**记本进程亲手驱动的 run,是帧级 own/foreign 判别的
|
|
293
|
+
// 唯一可信锚。进程内存态,壳重启即空(跨壳 resume 的历史 run 判不了 own——诚实边界,见
|
|
294
|
+
// fleetClient 帧级校验注释)。
|
|
295
|
+
const ownEngineRuns = new Set();
|
|
296
|
+
export function recordOwnEngineRun(runId) {
|
|
297
|
+
if (!runId)
|
|
298
|
+
return;
|
|
299
|
+
// 防御性上限:一个壳进程生命周期内 turn 数远小于此;溢出丢最老(Set 迭代序=插入序)。
|
|
300
|
+
if (ownEngineRuns.size >= 8192 && !ownEngineRuns.has(runId)) {
|
|
301
|
+
const oldest = ownEngineRuns.values().next().value;
|
|
302
|
+
if (oldest !== undefined)
|
|
303
|
+
ownEngineRuns.delete(oldest);
|
|
304
|
+
}
|
|
305
|
+
ownEngineRuns.add(runId);
|
|
306
|
+
}
|
|
307
|
+
export function isOwnEngineRun(runId) {
|
|
308
|
+
return ownEngineRuns.has(runId);
|
|
309
|
+
}
|
|
310
|
+
/** Single consumer (the panel hook). Returns an unsubscribe. */
|
|
311
|
+
export function subscribeSubagentContent(fn) {
|
|
312
|
+
notifyListener = fn;
|
|
313
|
+
// replay: anything already accumulated gets one notify so a late-mounting consumer catches up
|
|
314
|
+
for (const id of tasks.keys())
|
|
315
|
+
pendingNotify.add(id);
|
|
316
|
+
if (pendingNotify.size > 0)
|
|
317
|
+
scheduleNotify([...pendingNotify][0]);
|
|
318
|
+
return () => {
|
|
319
|
+
if (notifyListener === fn)
|
|
320
|
+
notifyListener = null;
|
|
321
|
+
};
|
|
322
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⇄ B1 批搬迁(2026-07-27,多端改造设计稿 §2.4.1-B):cli src/sema/types/engineState.ts 逐字搬入(纯类型,零运行时;壳侧改 re-export)。
|
|
3
|
+
* ⚠️ 归属注记:`EngineInstance`/`TOCConfig` 描述的是**宿主**职责(spawn/端口/日志路径),逻辑本体
|
|
4
|
+
* (engineLifecycleManager)按设计稿 §2.4.2-A 留 TUI;搬进本包的只是**形状**,让 desktop 宿主与
|
|
5
|
+
* TUI 宿主共用同一套 /health 与 engine.port 契约类型,不代表生命周期逻辑下沉。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* TOC 一体化 (TP-A-P0-IMPL Step 5) — Engine Lifecycle 状态类型。
|
|
9
|
+
*
|
|
10
|
+
* 壳一体化起本地引擎(ai-agent-service file 模式),对用户透明,体验等同 CC 单进程。
|
|
11
|
+
* 本文件只定义跨 lifecycle 流转的纯数据形状,不含逻辑(逻辑在 engineLifecycleManager.ts)。
|
|
12
|
+
*
|
|
13
|
+
* 真相源契约:engine.port 文件是壳↔引擎的「当前活引擎」单一发现锚点(flock 串行化写,
|
|
14
|
+
* write-temp+fsync+mv 原子落盘)。HealthResponse 字段与引擎 server.ts:645-660 /health 逐字对齐
|
|
15
|
+
* (W2 同约定实现):instanceId/dataRoot/configHash/pid —— 壳据此判断「连对引擎」防陈旧 port 连错。
|
|
16
|
+
*/
|
|
17
|
+
/** 引擎 GET /health 返回体(与 ai-agent-service server.ts:645-660 对齐;字段大多 additive 可缺)。 */
|
|
18
|
+
export interface HealthResponse {
|
|
19
|
+
/** 恒为 'ok';非 200 / 非此值 = 不健康。 */
|
|
20
|
+
status: string;
|
|
21
|
+
/** 当前模型 id(configHash 的一部分,信息性)。 */
|
|
22
|
+
model?: string;
|
|
23
|
+
/** 会话存储后端('file' for TOC local)。 */
|
|
24
|
+
sessionBackend?: string;
|
|
25
|
+
/** 活跃 session 数(信息性)。 */
|
|
26
|
+
sessions?: number;
|
|
27
|
+
/** 引擎实例身份(uuidv7,每次 boot 唯一)。陈旧 port 连错引擎时此值会变 → 拒连。 */
|
|
28
|
+
instanceId?: string;
|
|
29
|
+
/** 引擎 localDataRoot 解析结果。壳校验 === assembleChildEnv 注入的 LOCAL_DATA_ROOT。 */
|
|
30
|
+
dataRoot?: string;
|
|
31
|
+
/** restart-relevant config 的稳定指纹(无 secret)。drift 检测:configHash 变 → 拒连旧引擎。 */
|
|
32
|
+
configHash?: string;
|
|
33
|
+
/** 引擎进程 pid。orphan reap 的 kill-0 目标 + heartbeat 死活判定。 */
|
|
34
|
+
pid?: number;
|
|
35
|
+
/** 引擎监听端口。 */
|
|
36
|
+
port?: number;
|
|
37
|
+
/** 引擎请求重启(center restart 信号);壳侧信息性。 */
|
|
38
|
+
restartRequired?: boolean;
|
|
39
|
+
/** 引擎 build 版本自述(service 1.96+,[392]ask②)。复用时与本地 dist 版本比对,不一致=陈旧代码拒复用。 */
|
|
40
|
+
version?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* engine.port 文件落盘的 JSON 形状(write-temp+fsync+mv 原子写)。
|
|
44
|
+
* 壳启动时读此文件做引擎发现:存在且 kill-0 活 + /health 身份匹配 → 复用;否则 reap + spawn。
|
|
45
|
+
* 🔴 secrets 边界:绝不写入任何明文 token/secret(只透传到 child env)。grep engine.port 不得见 secret。
|
|
46
|
+
*/
|
|
47
|
+
export interface EnginePortFile {
|
|
48
|
+
/** 引擎监听端口。 */
|
|
49
|
+
port: number;
|
|
50
|
+
/** spawn 出的引擎子进程 pid(kill-0 存活检测 / orphan reap 目标)。 */
|
|
51
|
+
pid: number;
|
|
52
|
+
/** 引擎实例身份(uuidv7);连接前用 /health 复验,不匹配 = 陈旧 port。 */
|
|
53
|
+
instanceId?: string;
|
|
54
|
+
/** 引擎 localDataRoot;身份校验用。 */
|
|
55
|
+
dataRoot?: string;
|
|
56
|
+
/** boot 时记录的 configHash;drift 检测基线。 */
|
|
57
|
+
configHash?: string;
|
|
58
|
+
/** 引擎日志文件路径(<configHome>/engine-<pid>.log),壳可 tail 诊断。 */
|
|
59
|
+
logPath: string;
|
|
60
|
+
/** 落盘时间戳(epoch ms);信息性 / 调试用。 */
|
|
61
|
+
startedAt: number;
|
|
62
|
+
/** 写文件的格式版本(向后兼容);当前 1。 */
|
|
63
|
+
version: number;
|
|
64
|
+
/**
|
|
65
|
+
* P2 云配置整体开关:spawn 该引擎时生效的配置源标签('local' | 'cloud:<profile>')。
|
|
66
|
+
* ADDITIVE 可缺(老文件缺 = 'local')。boot 复用检查:与当前 desiredEngineConfigSource() 不一致
|
|
67
|
+
* → 视同配置漂移 reap+respawn(models 域走 CONFIG_LOCAL_DIR shadow,restart-to-apply)。
|
|
68
|
+
*/
|
|
69
|
+
configSource?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 内存中持有的「已托管引擎实例」句柄。lifecycle 全程持有,shutdown 时据此 SIGTERM/SIGKILL。
|
|
73
|
+
*/
|
|
74
|
+
export interface EngineInstance {
|
|
75
|
+
/** 监听端口。 */
|
|
76
|
+
port: number;
|
|
77
|
+
/** 子进程 pid。 */
|
|
78
|
+
pid: number;
|
|
79
|
+
/** baseUrl = http://127.0.0.1:<port>,壳设到 SEMA_LIVE_BASEURL。 */
|
|
80
|
+
baseUrl: string;
|
|
81
|
+
/** /health 校验拿到的身份(复用既有引擎时可能来自 probe,而非自己 spawn)。 */
|
|
82
|
+
instanceId?: string;
|
|
83
|
+
/** 引擎 dataRoot。 */
|
|
84
|
+
dataRoot?: string;
|
|
85
|
+
/** boot configHash 基线(heartbeat drift 检测)。 */
|
|
86
|
+
configHash?: string;
|
|
87
|
+
/** 日志文件路径(自己 spawn 的才有;复用既有引擎为 undefined)。 */
|
|
88
|
+
logPath?: string;
|
|
89
|
+
/** true = 本壳 spawn 出来的(shutdown 时负责回收);false = 复用既有引擎(不杀)。 */
|
|
90
|
+
ownedByThisShell: boolean;
|
|
91
|
+
/** node ChildProcess 句柄(仅自己 spawn 时有;复用既有为 undefined)。as unknown 避免类型耦合 child_process。 */
|
|
92
|
+
child?: unknown;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 壳注入给引擎子进程的 TOC env pack(§3.1)。引擎默认偏云(PORT=8090/DB_BACKEND=tidb/
|
|
96
|
+
* CONFIG_PROVIDER=undefined),壳必须显式覆盖为 local 单用户模式,绝不依赖引擎默认。
|
|
97
|
+
* secrets(ANTHROPIC_API_KEY/E2B_API_KEY 等)仅在父 env 存在时透传到 child,不落任何文件。
|
|
98
|
+
*/
|
|
99
|
+
export interface TOCConfig {
|
|
100
|
+
/** 选定端口(显式 pin SEMA_ENGINE_PORT 优先;无 pin 按 config dir 派生 + 空闲探测,P0-2)。 */
|
|
101
|
+
port: number;
|
|
102
|
+
/** 引擎数据根 = <configHome>/engine-data;引擎 LOCAL_DATA_ROOT 优先级链最高项。 */
|
|
103
|
+
localDataRoot: string;
|
|
104
|
+
/** 本地 config.d 目录 = <configHome>/config.d(引擎 CONFIG_LOCAL_DIR)。 */
|
|
105
|
+
configLocalDir: string;
|
|
106
|
+
/** 壳配置主目录(getClaudeConfigHomeDir());也作 SEMA_CONFIG_DIR 注入对齐路径。 */
|
|
107
|
+
configHome: string;
|
|
108
|
+
/** per-spawn 随机服务令牌(uuid),SERVICE_AUTH_TOKENS="<uuid>=system:shell-local";不落 .env。 */
|
|
109
|
+
serviceAuthToken: string;
|
|
110
|
+
/** 引擎入口文件绝对路径(ai-agent-service/dist/main.js,缺则回退 tsx src/main.ts)。 */
|
|
111
|
+
engineEntry: string;
|
|
112
|
+
/** 引擎入口的工作目录(ai-agent-service 根)。 */
|
|
113
|
+
engineCwd: string;
|
|
114
|
+
/** 引擎日志输出文件(<configHome>/engine-<pid>.log;pid spawn 后回填,先用占位)。 */
|
|
115
|
+
logPath: string;
|
|
116
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⇄ B1 批搬迁(2026-07-27,多端改造设计稿 §2.4.1-B):cli src/sema/types/engineState.ts 逐字搬入(纯类型,零运行时;壳侧改 re-export)。
|
|
3
|
+
* ⚠️ 归属注记:`EngineInstance`/`TOCConfig` 描述的是**宿主**职责(spawn/端口/日志路径),逻辑本体
|
|
4
|
+
* (engineLifecycleManager)按设计稿 §2.4.2-A 留 TUI;搬进本包的只是**形状**,让 desktop 宿主与
|
|
5
|
+
* TUI 宿主共用同一套 /health 与 engine.port 契约类型,不代表生命周期逻辑下沉。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* TOC 一体化 (TP-A-P0-IMPL Step 5) — Engine Lifecycle 状态类型。
|
|
9
|
+
*
|
|
10
|
+
* 壳一体化起本地引擎(ai-agent-service file 模式),对用户透明,体验等同 CC 单进程。
|
|
11
|
+
* 本文件只定义跨 lifecycle 流转的纯数据形状,不含逻辑(逻辑在 engineLifecycleManager.ts)。
|
|
12
|
+
*
|
|
13
|
+
* 真相源契约:engine.port 文件是壳↔引擎的「当前活引擎」单一发现锚点(flock 串行化写,
|
|
14
|
+
* write-temp+fsync+mv 原子落盘)。HealthResponse 字段与引擎 server.ts:645-660 /health 逐字对齐
|
|
15
|
+
* (W2 同约定实现):instanceId/dataRoot/configHash/pid —— 壳据此判断「连对引擎」防陈旧 port 连错。
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
package/dist/workflow.d.ts
CHANGED
|
@@ -1,18 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* workflow 车道纯函数
|
|
3
|
-
*
|
|
2
|
+
* workflow 车道纯函数 + workflow-agent 台账。
|
|
3
|
+
*
|
|
4
|
+
* 沿革(B1 批,2026-07-27):0.2.0 时本文件是 cli `footerRowBelt.ts` / `workflowTaskOutputProjection.ts`
|
|
5
|
+
* 的**双份抄件**;B1 批按设计稿 §2.4.1-A/B 裁决「以壳侧现文件为真身、包内不留两份实现」:
|
|
6
|
+
* · `parseWorkflowPollEnvelope` / `projectWorkflowTaskOutput` = cli `workflowTaskOutputProjection.ts`
|
|
7
|
+
* 的逐字搬入(0.2.0 抄件漏了 `agent_runs[].errorCode` 类型位,已按真身补齐);
|
|
8
|
+
* · `rowIdTail` / `isWorkflowAgentTaskId` / `recordWorkflowAgentTaskId` / `isKnownWorkflowAgentTaskId`
|
|
9
|
+
* = cli `footerRowBelt.ts` 的**belt 半场**(判据①④),按设计稿 §2.4.1-B「拆」搬入。
|
|
10
|
+
* ⚠️ 留在 TUI 的是 `isFooterTaskRow` / `filterFooterTaskRows`(判据②③)—— 那两条含
|
|
11
|
+
* 「丢弃所有无 parentId 顶层行」的**端专属决策**(主 run 头部行由壳侧 fleetClient 合成渲染,
|
|
12
|
+
* 设计稿 §2.4.2-B),壳侧文件改成 import 本包的四个出口 + 自留那两个函数。
|
|
13
|
+
* 🔴 台账(`workflowAgentTaskIds`)是 module 级单实例:壳侧 bridge 的 task_progress lane 门写、
|
|
14
|
+
* 壳侧 belt 的 `isFooterTaskRow` 读 —— 两边都必须走**本包同一个模块实例**,否则台账写进一个
|
|
15
|
+
* 副本、读另一个副本(判据④ 静默失效,幽灵行回归)。
|
|
16
|
+
*
|
|
17
|
+
* 围栏见 cli 仓 frameLaneGuards / taskOutputWorkflowEnvelope / footerRowBelt 套;
|
|
18
|
+
* [1617] 幽灵行家族的判别资产。
|
|
4
19
|
*/
|
|
5
|
-
/**
|
|
20
|
+
/** 复合行键(`<runId> <taskId>`,fleet-bus fleetRunPublisher.childId)的尾段 = 引擎 taskId。
|
|
21
|
+
* 导出给壳侧 belt 半场复用(判据②③ 的行 id 归一);包内 fleetAgentPanelProjection 保留了它
|
|
22
|
+
* 自己的同语义私有副本(零依赖叶模块纪律,drift 由两侧测试各自锁)。 */
|
|
23
|
+
export declare function rowIdTail(id: string): string;
|
|
24
|
+
/** core 1.356 BCE 合成 workflow-agent id 形:wa+16hex(判据①)。
|
|
6
25
|
* 真实 fleet 子行 id 是 `${runId} ${engineTaskId}` 复合形——必须取空白分隔尾段再匹配
|
|
7
|
-
* (cli footerRowBelt 原版语义;codex 对抗复审抓的等价性破坏,0.1.1 修)。
|
|
26
|
+
* (cli footerRowBelt 原版语义;codex 对抗复审抓的等价性破坏,0.1.1 修)。
|
|
27
|
+
* upstreamBridge 的 task_progress 前台 lane 门在 wire 无 workflowRunId 时(server <a76bd77,
|
|
28
|
+
* 黑板 [1619]:1.272.0 投影白名单仍剥该键)以此为次级判别——与 belt 双判据同模板。 */
|
|
8
29
|
export declare function isWorkflowAgentTaskId(taskId: string): boolean;
|
|
9
|
-
|
|
10
|
-
|
|
30
|
+
export declare function recordWorkflowAgentTaskId(taskId: string): void;
|
|
31
|
+
export declare function isKnownWorkflowAgentTaskId(taskId: string): boolean;
|
|
32
|
+
/** 测试钩:清台账(壳侧原版无此口——module 级 Set 在壳里靠进程边界隔离;包内多组断言同进程跑,
|
|
33
|
+
* 必须能清,否则前一组的登记污染后一组)。 */
|
|
34
|
+
export declare function __resetWorkflowAgentTaskIdsForTests(): void;
|
|
35
|
+
/**
|
|
36
|
+
* TaskOutput legacy 腿(tool_end 无 structured)的 workflow 信封识别(#49 C3)。
|
|
37
|
+
* 判据=core task-registry formatWorkflowRun 真形:`{task_id, type:"workflow", status, name?, …}`
|
|
38
|
+
* ——type 判别位 + task_id 字符串双门,非该形返回 null(绝不误吞 bash/agent 轮询)。
|
|
39
|
+
*/
|
|
11
40
|
export declare function parseWorkflowPollEnvelope(body: string): {
|
|
12
41
|
taskId: string;
|
|
13
42
|
status: string;
|
|
14
43
|
name?: string;
|
|
15
44
|
} | null;
|
|
16
|
-
/**
|
|
17
|
-
*
|
|
45
|
+
/**
|
|
46
|
+
* workflow 轮询 JSON → 人话多行投影(agents 汇总行 + per-agent ✓/✗ 明细 + note/error 尾透传);
|
|
47
|
+
* 非 JSON 原样返回(fail-soft,`description [status]` 行仍在)。
|
|
48
|
+
* 输入=引擎 formatWorkflowRun 的 JSON 原文(core 1.352 起带 agent_runs per-agent 明细 + 失败 note 指路)。
|
|
49
|
+
*/
|
|
18
50
|
export declare function projectWorkflowTaskOutput(jsonSrc: string): string;
|
package/dist/workflow.js
CHANGED
|
@@ -1,16 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* workflow 车道纯函数
|
|
3
|
-
*
|
|
2
|
+
* workflow 车道纯函数 + workflow-agent 台账。
|
|
3
|
+
*
|
|
4
|
+
* 沿革(B1 批,2026-07-27):0.2.0 时本文件是 cli `footerRowBelt.ts` / `workflowTaskOutputProjection.ts`
|
|
5
|
+
* 的**双份抄件**;B1 批按设计稿 §2.4.1-A/B 裁决「以壳侧现文件为真身、包内不留两份实现」:
|
|
6
|
+
* · `parseWorkflowPollEnvelope` / `projectWorkflowTaskOutput` = cli `workflowTaskOutputProjection.ts`
|
|
7
|
+
* 的逐字搬入(0.2.0 抄件漏了 `agent_runs[].errorCode` 类型位,已按真身补齐);
|
|
8
|
+
* · `rowIdTail` / `isWorkflowAgentTaskId` / `recordWorkflowAgentTaskId` / `isKnownWorkflowAgentTaskId`
|
|
9
|
+
* = cli `footerRowBelt.ts` 的**belt 半场**(判据①④),按设计稿 §2.4.1-B「拆」搬入。
|
|
10
|
+
* ⚠️ 留在 TUI 的是 `isFooterTaskRow` / `filterFooterTaskRows`(判据②③)—— 那两条含
|
|
11
|
+
* 「丢弃所有无 parentId 顶层行」的**端专属决策**(主 run 头部行由壳侧 fleetClient 合成渲染,
|
|
12
|
+
* 设计稿 §2.4.2-B),壳侧文件改成 import 本包的四个出口 + 自留那两个函数。
|
|
13
|
+
* 🔴 台账(`workflowAgentTaskIds`)是 module 级单实例:壳侧 bridge 的 task_progress lane 门写、
|
|
14
|
+
* 壳侧 belt 的 `isFooterTaskRow` 读 —— 两边都必须走**本包同一个模块实例**,否则台账写进一个
|
|
15
|
+
* 副本、读另一个副本(判据④ 静默失效,幽灵行回归)。
|
|
16
|
+
*
|
|
17
|
+
* 围栏见 cli 仓 frameLaneGuards / taskOutputWorkflowEnvelope / footerRowBelt 套;
|
|
18
|
+
* [1617] 幽灵行家族的判别资产。
|
|
4
19
|
*/
|
|
5
|
-
/**
|
|
20
|
+
/** 复合行键(`<runId> <taskId>`,fleet-bus fleetRunPublisher.childId)的尾段 = 引擎 taskId。
|
|
21
|
+
* 导出给壳侧 belt 半场复用(判据②③ 的行 id 归一);包内 fleetAgentPanelProjection 保留了它
|
|
22
|
+
* 自己的同语义私有副本(零依赖叶模块纪律,drift 由两侧测试各自锁)。 */
|
|
23
|
+
export function rowIdTail(id) {
|
|
24
|
+
return id.split(/\s+/).pop() ?? id;
|
|
25
|
+
}
|
|
26
|
+
/** core 1.356 BCE 合成 workflow-agent id 形:wa+16hex(判据①)。
|
|
6
27
|
* 真实 fleet 子行 id 是 `${runId} ${engineTaskId}` 复合形——必须取空白分隔尾段再匹配
|
|
7
|
-
* (cli footerRowBelt 原版语义;codex 对抗复审抓的等价性破坏,0.1.1 修)。
|
|
28
|
+
* (cli footerRowBelt 原版语义;codex 对抗复审抓的等价性破坏,0.1.1 修)。
|
|
29
|
+
* upstreamBridge 的 task_progress 前台 lane 门在 wire 无 workflowRunId 时(server <a76bd77,
|
|
30
|
+
* 黑板 [1619]:1.272.0 投影白名单仍剥该键)以此为次级判别——与 belt 双判据同模板。 */
|
|
8
31
|
export function isWorkflowAgentTaskId(taskId) {
|
|
9
|
-
|
|
10
|
-
|
|
32
|
+
return /^wa[0-9a-f]{16}$/.test(rowIdTail(taskId));
|
|
33
|
+
}
|
|
34
|
+
// ── 判据④:bridge task_progress lane 门台账([1641])────────────────────────────────────────────
|
|
35
|
+
// bridge 的 workflow lane 门(帧带 workflowRunId 的权威判定)把子代 taskId 登记进来,行按尾段查。
|
|
36
|
+
// ③④ 互为兜底:③(集合级双生识别,TUI 半场)不依赖时序但要求 wa 行在场;④ 不依赖 wa 行但要求
|
|
37
|
+
// 本壳在读该 run 的会话流。
|
|
38
|
+
const workflowAgentTaskIds = new Set();
|
|
39
|
+
const MAX_WORKFLOW_TASK_IDS = 4096;
|
|
40
|
+
export function recordWorkflowAgentTaskId(taskId) {
|
|
41
|
+
if (!taskId)
|
|
42
|
+
return;
|
|
43
|
+
workflowAgentTaskIds.delete(taskId);
|
|
44
|
+
workflowAgentTaskIds.add(taskId);
|
|
45
|
+
if (workflowAgentTaskIds.size > MAX_WORKFLOW_TASK_IDS) {
|
|
46
|
+
const oldest = workflowAgentTaskIds.values().next().value;
|
|
47
|
+
if (oldest !== undefined)
|
|
48
|
+
workflowAgentTaskIds.delete(oldest);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function isKnownWorkflowAgentTaskId(taskId) {
|
|
52
|
+
return workflowAgentTaskIds.has(taskId);
|
|
53
|
+
}
|
|
54
|
+
/** 测试钩:清台账(壳侧原版无此口——module 级 Set 在壳里靠进程边界隔离;包内多组断言同进程跑,
|
|
55
|
+
* 必须能清,否则前一组的登记污染后一组)。 */
|
|
56
|
+
export function __resetWorkflowAgentTaskIdsForTests() {
|
|
57
|
+
workflowAgentTaskIds.clear();
|
|
11
58
|
}
|
|
12
|
-
/**
|
|
13
|
-
*
|
|
59
|
+
/**
|
|
60
|
+
* TaskOutput legacy 腿(tool_end 无 structured)的 workflow 信封识别(#49 C3)。
|
|
61
|
+
* 判据=core task-registry formatWorkflowRun 真形:`{task_id, type:"workflow", status, name?, …}`
|
|
62
|
+
* ——type 判别位 + task_id 字符串双门,非该形返回 null(绝不误吞 bash/agent 轮询)。
|
|
63
|
+
*/
|
|
14
64
|
export function parseWorkflowPollEnvelope(body) {
|
|
15
65
|
try {
|
|
16
66
|
const j = JSON.parse(body);
|
|
@@ -26,8 +76,11 @@ export function parseWorkflowPollEnvelope(body) {
|
|
|
26
76
|
return null;
|
|
27
77
|
}
|
|
28
78
|
}
|
|
29
|
-
/**
|
|
30
|
-
*
|
|
79
|
+
/**
|
|
80
|
+
* workflow 轮询 JSON → 人话多行投影(agents 汇总行 + per-agent ✓/✗ 明细 + note/error 尾透传);
|
|
81
|
+
* 非 JSON 原样返回(fail-soft,`description [status]` 行仍在)。
|
|
82
|
+
* 输入=引擎 formatWorkflowRun 的 JSON 原文(core 1.352 起带 agent_runs per-agent 明细 + 失败 note 指路)。
|
|
83
|
+
*/
|
|
31
84
|
export function projectWorkflowTaskOutput(jsonSrc) {
|
|
32
85
|
try {
|
|
33
86
|
const j = JSON.parse(jsonSrc);
|
|
@@ -35,19 +88,24 @@ export function projectWorkflowTaskOutput(jsonSrc) {
|
|
|
35
88
|
if (j.agents && typeof j.agents.done === 'number' && typeof j.agents.total === 'number') {
|
|
36
89
|
parts.push(`agents: ${j.agents.done}/${j.agents.total} done${typeof j.agents.failed === 'number' && j.agents.failed > 0 ? ` (${j.agents.failed} failed)` : ''}`);
|
|
37
90
|
}
|
|
91
|
+
// per-agent 明细([1411]③/[1408] 围栏 M1):✓/✗ label [status] — error 首行/output 摘要。
|
|
92
|
+
// 缺席(老 core)零行=形状不变。
|
|
38
93
|
if (Array.isArray(j.agent_runs)) {
|
|
39
94
|
for (const r of j.agent_runs) {
|
|
40
95
|
if (!r || typeof r !== 'object')
|
|
41
96
|
continue;
|
|
42
97
|
const label = typeof r.label === 'string' && r.label.length > 0 ? r.label : '(agent)';
|
|
43
98
|
const st = typeof r.status === 'string' ? r.status : '?';
|
|
44
|
-
const errLine = typeof r.error === 'string' && r.error.length > 0
|
|
99
|
+
const errLine = typeof r.error === 'string' && r.error.length > 0
|
|
100
|
+
? ` — ${r.error.split('\n')[0].slice(0, 120)}`
|
|
101
|
+
: '';
|
|
45
102
|
const outLine = !errLine && typeof r.output === 'string' && r.output.length > 0
|
|
46
103
|
? ` — ${r.output.split('\n')[0].slice(0, 80)}`
|
|
47
104
|
: '';
|
|
48
105
|
parts.push(` ${st === 'completed' ? '✓' : st === 'failed' ? '✗' : '·'} ${label} [${st}]${errLine}${outLine}`);
|
|
49
106
|
}
|
|
50
107
|
}
|
|
108
|
+
// note 尾透传(core 失败指路:TaskOutput+resumeFromRunId)——不留死胡同(围栏 M2)。
|
|
51
109
|
if (typeof j.note === 'string' && j.note.length > 0)
|
|
52
110
|
parts.push(j.note);
|
|
53
111
|
if (typeof j.error === 'string' && j.error.length > 0)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|