@sema-agent/client-core 0.7.0 → 0.8.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.
- package/README.md +31 -11
- package/dist/adapt.js +36 -3
- package/dist/compensations.d.ts +63 -0
- package/dist/compensations.js +138 -0
- package/dist/detachWire.d.ts +141 -0
- package/dist/detachWire.js +182 -0
- package/dist/engineSessionParam.d.ts +6 -2
- package/dist/engineSessionParam.js +24 -18
- package/dist/engineWireTarget.d.ts +14 -0
- package/dist/engineWireTarget.js +53 -0
- package/dist/fleet/fleetLedger.d.ts +132 -0
- package/dist/fleet/fleetLedger.js +393 -0
- package/dist/fleet/fleetProjection.d.ts +181 -0
- package/dist/fleet/fleetProjection.js +258 -0
- package/dist/hooksWireCaps.d.ts +91 -0
- package/dist/hooksWireCaps.js +359 -0
- package/dist/host.d.ts +34 -2
- package/dist/host.js +6 -0
- package/dist/index.d.ts +56 -1
- package/dist/index.js +82 -1
- package/dist/liveInitToolFace.d.ts +53 -0
- package/dist/liveInitToolFace.js +251 -0
- package/dist/model/modelFamilies.json +63 -0
- package/dist/model/providerPresets.d.ts +46 -0
- package/dist/model/providerPresets.js +160 -0
- package/dist/model/providerPresets.json +1179 -0
- package/dist/seam.d.ts +19 -3
- package/dist/seam.js +14 -2
- package/dist/subagent/engineCompactWire.d.ts +12 -0
- package/dist/subagent/engineCompactWire.js +176 -0
- package/dist/subagent/engineDelegatedPrompt.d.ts +44 -0
- package/dist/subagent/engineDelegatedPrompt.js +205 -0
- package/dist/subagent/engineRowStopGate.d.ts +18 -0
- package/dist/subagent/engineRowStopGate.js +54 -0
- package/dist/subagent/engineSubagentOutput.d.ts +15 -0
- package/dist/subagent/engineSubagentOutput.js +98 -0
- package/dist/subagent/engineSubagentSteer.d.ts +10 -0
- package/dist/subagent/engineSubagentSteer.js +68 -0
- package/dist/subagent/engineSubagentTail.d.ts +40 -0
- package/dist/subagent/engineSubagentTail.js +241 -0
- package/dist/subagent/engineTaskHandleWire.d.ts +89 -0
- package/dist/subagent/engineTaskHandleWire.js +213 -0
- package/dist/workflowClient.d.ts +42 -0
- package/dist/workflowClient.js +467 -0
- package/dist/workflowMonitor.d.ts +94 -0
- package/dist/workflowMonitor.js +43 -0
- package/package.json +2 -2
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⇄ B6 批搬迁(2026-07-27,设计稿 §3 B6):cli `src/sema/engineTaskHandleWire.ts` 整搬。
|
|
3
|
+
*
|
|
4
|
+
* 🔴 搬迁差分:`wireConfig()` → `engineWireTarget()`;`getActiveEngineTaskId()` →
|
|
5
|
+
* `activeEngineRunId()`;`logForDebugging` → `hostLog`;`process.env.SEMA_DEBUG` 前置判
|
|
6
|
+
* → `engineWireDebugEnabled()`。
|
|
7
|
+
*
|
|
8
|
+
* ── 以下为原文件的领域说明(逐字保留)────────────────────────────────────────────────────────
|
|
9
|
+
*
|
|
10
|
+
* engineTaskHandleWire — CC TaskOutput/TaskStop 人侧对位的 wire 消费([1501] server 1.246.0 /
|
|
11
|
+
* SDK 0.0.67 提货)。
|
|
12
|
+
*
|
|
13
|
+
* Wire(两面都是出生即强制带 `?session=`,与 subagentOutput 同姿势;非阻塞、副本本地):
|
|
14
|
+
* · 读:`GET /v1/runs/:id/tasks/:handle/output?session=` → SDK `runs.taskOutput`
|
|
15
|
+
* · 停:`POST /v1/runs/:id/tasks/:handle/stop?session=` → SDK `runs.taskStop`
|
|
16
|
+
* · :id = 宿主 run;:handle = canonical 句柄 ONLY(b… bash / m… monitor / a… agent)——
|
|
17
|
+
* 不收名字、不收 legacy shellId,传了就是 404;workflow(w…/wa…)恒 404(journal 是那边的
|
|
18
|
+
* 读面,永不打这里)。
|
|
19
|
+
* · 能力门 = capabilities.taskHandles(engineCapsCache 通用缓存,[1501] 明令:用能力位判断,
|
|
20
|
+
* 别 trial-by-501/404)。老引擎(≤1.245)缺该键 → 两动词都回落 null/不可用,version-safe。
|
|
21
|
+
*
|
|
22
|
+
* 读面游标语义([1501]§一 + [1505] must-fix 修正,UI 必须按此做):是否「读即消费」取决于句柄
|
|
23
|
+
* 形态——spooled 腿全量可重读、GET 幂等;cursor-only 腿每次 GET 只出新字节、读走就没了。
|
|
24
|
+
* 两形判别用 **wire 契约自带标记**(server [1505] 钉版承诺稳定,变更走 CHANGELOG+黑板;core
|
|
25
|
+
* CC-verbatim 固定形):content 含 `--- stdout (full, re-readable) ---` = spool 全量形 → 整体
|
|
26
|
+
* 替换;含 `--- new stdout ---` = cursor 增量形 → append。初版的「前缀延展」启发式被 [1505]
|
|
27
|
+
* 否决——spool content 首行 `status: <displayStatus>` 在任务完成时刻从 running 翻 exited,
|
|
28
|
+
* 前缀检测恰在最常见时刻失败 → 全量被 append = 输出重复。无标记(极端/未知形)按保守形
|
|
29
|
+
* append-if-changed 处理;空 content 不动累积。绝不假设重拉能拿到同样字节。
|
|
30
|
+
*
|
|
31
|
+
* ⚠️ cursor-only 腿双消费者 caveat([1505]):同一 b* 句柄若同时被模型侧 TaskOutput 工具读,
|
|
32
|
+
* 两个消费者互抢增量(工具读走的字节本读面拿不到,反之亦然),端侧累积在该形下不保证完整;
|
|
33
|
+
* spool 腿(server 正常接线下的常态)无此问题。UI 在 cursor 形下不承诺完整性。
|
|
34
|
+
*
|
|
35
|
+
* 停面语义([1501]§二):**只有 200 算停了**;409 `stop.not_landed` = kill 没落地、进程可能
|
|
36
|
+
* 还在跑(UI 绝不能渲染成「已停止」);终态句柄重复 stop 幂等诚实(200,状态照实)。
|
|
37
|
+
*
|
|
38
|
+
* `?filter=` 禁用(带了=400):CC 的 filter 语义由调用方拿回 content 后本地做,本模块不透传。
|
|
39
|
+
*/
|
|
40
|
+
import { TaskStopConflictError } from '@sema-agent/sdk';
|
|
41
|
+
import { hostLog } from '../host.js';
|
|
42
|
+
import { makeEngineWireClient } from '../engineWireSdk.js';
|
|
43
|
+
import { engineWireDebugEnabled, engineWireTarget } from '../engineWireTarget.js';
|
|
44
|
+
import { activeEngineRunId, engineSessionParamSpread } from '../engineSessionParam.js';
|
|
45
|
+
import { getBgParentRun } from '../subagentContentStore.js';
|
|
46
|
+
import { engineTaskHandlesCapable } from './engineRowStopGate.js';
|
|
47
|
+
// 能力门同步读口:真源在零依赖叶 `engineRowStopGate`(batch-stop 同步分类不拉本模块 SDK 图)。
|
|
48
|
+
// ⚠️ 此处**不再 re-export**——它与原定义同进 index.ts 的 `export *` barrel 会构成双出口,
|
|
49
|
+
// esbuild 对 star-export 歧义直接 build 失败(tsc 同源 symbol 不报=假绿;0.8.0 壳收批实撞,
|
|
50
|
+
// idle-terminal-settle 套逮住)。包内消费者直接 import 原叶。
|
|
51
|
+
/** 宿主 run 解析:fleet 行/通知帧喂的 parent 映射优先(行 id=`${runId} ${handle}` 的 parentId
|
|
52
|
+
* 投影),缺席退当前交互 run(register 时刻的活跃 run 即宿主——engineSide bash 行的出生形)。 */
|
|
53
|
+
function resolveHostRun(handle) {
|
|
54
|
+
return getBgParentRun(handle) ?? activeEngineRunId() ?? null;
|
|
55
|
+
}
|
|
56
|
+
// 游标累积(按 handle;进程内存态,与查看态生命周期同级——端重启即空,重读从引擎再取)
|
|
57
|
+
const accumulated = new Map();
|
|
58
|
+
/** 测试钩:清累积缓存。 */
|
|
59
|
+
export function __resetTaskHandleWireForTests() {
|
|
60
|
+
accumulated.clear();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 读一个引擎侧任务句柄的输出(新面 A)。null = 读不到(无 wire / caps 缺 / 无宿主 run / 404
|
|
64
|
+
* 域外或已 reap / 网错)——调用方回落既有渲染(receipt/outputFile tail)。绝不 throw。
|
|
65
|
+
*/
|
|
66
|
+
export async function fetchEngineTaskOutput(handle, opts) {
|
|
67
|
+
const cfg = engineWireTarget();
|
|
68
|
+
if (!cfg || !engineTaskHandlesCapable())
|
|
69
|
+
return null;
|
|
70
|
+
const runId = resolveHostRun(handle);
|
|
71
|
+
if (!runId)
|
|
72
|
+
return null;
|
|
73
|
+
const client = makeEngineWireClient({
|
|
74
|
+
baseUrl: cfg.baseUrl,
|
|
75
|
+
...(cfg.token ? { token: cfg.token } : {}),
|
|
76
|
+
principal: cfg.principal,
|
|
77
|
+
});
|
|
78
|
+
if (!client)
|
|
79
|
+
return null;
|
|
80
|
+
try {
|
|
81
|
+
const r = await client.runs.taskOutput(runId, handle, {
|
|
82
|
+
...(opts?.signal ? { signal: opts.signal } : {}),
|
|
83
|
+
...engineSessionParamSpread(),
|
|
84
|
+
});
|
|
85
|
+
const fresh = typeof r?.content === 'string' ? r.content : '';
|
|
86
|
+
const prev = accumulated.get(handle) ?? '';
|
|
87
|
+
const merged = mergeTaskOutput(prev, fresh);
|
|
88
|
+
accumulated.set(handle, merged);
|
|
89
|
+
const out = r?.output ?? {};
|
|
90
|
+
if (engineWireDebugEnabled()) {
|
|
91
|
+
hostLog('debug', `[task-handle] output ${handle} via run ${runId}: ok fresh=${fresh.length}b merged=${merged.length}b status=${typeof out.status === 'string' ? out.status : '-'}`);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
content: merged,
|
|
95
|
+
...(typeof out.status === 'string' ? { status: out.status } : {}),
|
|
96
|
+
...(typeof out.stoppedBy === 'string' ? { stoppedBy: out.stoppedBy } : {}),
|
|
97
|
+
...(out.partial_result === true ? { partialResult: true } : {}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
const status = e?.status;
|
|
102
|
+
hostLog('debug', `[task-handle] output ${handle} via run ${runId}: ${typeof status === 'number' ? status : String(e).slice(0, 160)}`);
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* [1505] 契约标记判别(头注):spool 全量 → 替换;cursor 增量 → append;无标记 → 保守
|
|
108
|
+
* append-if-changed(与上次逐字节相同视为幂等重读,不重复)。空 content 不动累积。
|
|
109
|
+
* 🔴 B6 提出成具名纯函数 —— 它是这条读面唯一会「把输出显示成两遍」的地方,值得被直接断言。
|
|
110
|
+
*/
|
|
111
|
+
export function mergeTaskOutput(prev, fresh) {
|
|
112
|
+
if (fresh.length === 0)
|
|
113
|
+
return prev;
|
|
114
|
+
if (fresh.includes('--- stdout (full, re-readable) ---'))
|
|
115
|
+
return fresh;
|
|
116
|
+
if (fresh.includes('--- new stdout ---'))
|
|
117
|
+
return prev + fresh;
|
|
118
|
+
return fresh === prev ? prev : prev + fresh;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 🔴 **G13 删债(先红后绿)**:taskStop 的 409 冲突分类 —— 手抄 code 判 → typed
|
|
122
|
+
* `TaskStopConflictError`(SDK 把**任何** `stop.*` 前缀码映到该类,errors.js:277-278;
|
|
123
|
+
* SDK 自己的注释还点名了壳的手抄位置 `engineTaskHandleWire.ts:186-197`)。
|
|
124
|
+
*
|
|
125
|
+
* 手抄版的实际缺陷(pure 门先红实证):SDK 明列**五**形,手抄只认前四形 ⇒ 第五形
|
|
126
|
+
* `stop.park_arbiter_unreachable`(core 1.397 三分:仲裁店不可达 ⇒ **真相未知**)静默塌进
|
|
127
|
+
* `not_landed`。
|
|
128
|
+
* 🔴 后果方向先定正负号([anchor-on-the-deciding-quantity]):门控的量 = 「UI 翻不翻这一行」,
|
|
129
|
+
* 五形与 default **全都是不翻** ⇒ 这不是安全缺陷,是**诚实缺陷** —— 把「真相未知」渲染成
|
|
130
|
+
* 「kill 没落地」,用户与诊断双双被误导。所以修法是给第五形一个自己的名字,而不是调整门。
|
|
131
|
+
*
|
|
132
|
+
* 开集纪律:`switch` 留 `default`(未来新 `stop.*` 码落 `unknown_conflict`,照样不翻行、
|
|
133
|
+
* 照样带原始 `code`),绝不塌进任何一个已知形 —— 塌进去就等于替 server 编了一个它没说的原因。
|
|
134
|
+
*/
|
|
135
|
+
export function classifyTaskStopConflict(e) {
|
|
136
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
137
|
+
const raw = e?.code;
|
|
138
|
+
// typed 优先:SDK ≥0.0.11x 恒给 TaskStopConflictError 且 `code` 是 errorCode 原文。
|
|
139
|
+
// 兜底(老 SDK / 非 typed 抛点):code 非串时从 message 里认字面 —— 与手抄版同姿势,只是
|
|
140
|
+
// 现在是**回落**而不是主判据。
|
|
141
|
+
const code = typeof raw === 'string'
|
|
142
|
+
? raw
|
|
143
|
+
: (STOP_CONFLICT_CODES.find(c => detail.includes(c)) ?? '');
|
|
144
|
+
switch (code) {
|
|
145
|
+
case 'stop.not_local':
|
|
146
|
+
return { ok: false, reason: 'not_local', detail };
|
|
147
|
+
case 'stop.parked':
|
|
148
|
+
return { ok: false, reason: 'parked', detail };
|
|
149
|
+
case 'stop.park_resume_won':
|
|
150
|
+
return { ok: false, reason: 'park_resume_won', detail };
|
|
151
|
+
case 'stop.park_arbiter_unreachable':
|
|
152
|
+
return { ok: false, reason: 'park_arbiter_unreachable', detail };
|
|
153
|
+
case 'stop.not_landed':
|
|
154
|
+
return { ok: false, reason: 'not_landed', detail };
|
|
155
|
+
default:
|
|
156
|
+
// 开集:未知/未来 `stop.*`(以及 code 完全缺席的残破形)。诚实说「有冲突但我不认识它」,
|
|
157
|
+
// 不冒充 not_landed。UI 处置与其余五形相同(不翻行)。
|
|
158
|
+
return { ok: false, reason: 'unknown_conflict', detail };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/** 兜底字面认码用的已知集(顺序 = 长码优先,避免 `stop.parked` 抢走 `stop.park_resume_won`)。 */
|
|
162
|
+
const STOP_CONFLICT_CODES = [
|
|
163
|
+
'stop.park_arbiter_unreachable',
|
|
164
|
+
'stop.park_resume_won',
|
|
165
|
+
'stop.not_landed',
|
|
166
|
+
'stop.not_local',
|
|
167
|
+
'stop.parked',
|
|
168
|
+
];
|
|
169
|
+
/** 停一个引擎侧任务句柄(新面 B)。只有 ok:true(=HTTP 200)算停了。绝不 throw。 */
|
|
170
|
+
export async function stopEngineTask(handle, opts) {
|
|
171
|
+
const cfg = engineWireTarget();
|
|
172
|
+
if (!cfg || !engineTaskHandlesCapable())
|
|
173
|
+
return { ok: false, reason: 'unavailable' };
|
|
174
|
+
const runId = resolveHostRun(handle);
|
|
175
|
+
if (!runId)
|
|
176
|
+
return { ok: false, reason: 'unavailable', detail: 'no host run mapping' };
|
|
177
|
+
const client = makeEngineWireClient({
|
|
178
|
+
baseUrl: cfg.baseUrl,
|
|
179
|
+
...(cfg.token ? { token: cfg.token } : {}),
|
|
180
|
+
principal: cfg.principal,
|
|
181
|
+
});
|
|
182
|
+
if (!client)
|
|
183
|
+
return { ok: false, reason: 'unavailable' };
|
|
184
|
+
try {
|
|
185
|
+
const r = await client.runs.taskStop(runId, handle, {
|
|
186
|
+
...(opts?.signal ? { signal: opts.signal } : {}),
|
|
187
|
+
...engineSessionParamSpread(),
|
|
188
|
+
});
|
|
189
|
+
const out = r?.output ?? {};
|
|
190
|
+
if (engineWireDebugEnabled()) {
|
|
191
|
+
hostLog('debug', `[task-handle] stop ${handle} via run ${runId}: ok status=${typeof out.status === 'string' ? out.status : '-'} stoppedBy=${typeof out.stoppedBy === 'string' ? out.stoppedBy : '-'}`);
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
ok: true,
|
|
195
|
+
...(typeof out.status === 'string' ? { status: out.status } : {}),
|
|
196
|
+
...(typeof out.stoppedBy === 'string' ? { stoppedBy: out.stoppedBy } : {}),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
const status = e?.status;
|
|
201
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
202
|
+
hostLog('debug', `[task-handle] stop ${handle} via run ${runId}: ${typeof status === 'number' ? status : detail.slice(0, 160)}`);
|
|
203
|
+
// [1521]F2 订正:server 文案句里不含 errorCode 字面(message 判恒 miss=死分形),而 SDK
|
|
204
|
+
// APIError.code 一直透 errorCode——锚 code(对文案措辞变化免疫),message 兜底保留。
|
|
205
|
+
// 🔴 G13:判别锚从 `status===409` 换成 **typed 类**(`TaskStopConflictError`)—— SDK 把任何
|
|
206
|
+
// `stop.*` 码都映到它,以后 server 换 HTTP 码(或同码复用别的语义)时这条腿不会失灵;
|
|
207
|
+
// status 判保留为兜底(老 SDK 不抛 typed 类时仍要认)。
|
|
208
|
+
if (e instanceof TaskStopConflictError || status === 409) {
|
|
209
|
+
return classifyTaskStopConflict(e);
|
|
210
|
+
}
|
|
211
|
+
return { ok: false, reason: 'error', detail };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { WorkflowRun } from '@sema-agent/sdk';
|
|
2
|
+
import type { WorkflowRunState } from './workflowMonitor.js';
|
|
3
|
+
/** The live controller the host drives — a projected-snapshot reader + diagnostics + teardown. */
|
|
4
|
+
export interface LiveWorkflowController {
|
|
5
|
+
/** the latest projected run state, or null (not yet resolved / degraded 501-404 / empty). */
|
|
6
|
+
snapshot(): WorkflowRunState | null;
|
|
7
|
+
/** connection + degrade diagnostics for the empty/degraded render + verify cross-check. */
|
|
8
|
+
status(): {
|
|
9
|
+
connected: boolean;
|
|
10
|
+
/** true when the endpoint is unavailable (501 = S8 flag off) / the run is absent (404) / the list is empty. */
|
|
11
|
+
degraded: boolean;
|
|
12
|
+
/** the HTTP status behind a degrade (501 / 404), else null. */
|
|
13
|
+
code: number | null;
|
|
14
|
+
/** the resolved workflow run id (null until list()/config resolves one). */
|
|
15
|
+
runId: string | null;
|
|
16
|
+
lastError: string | null;
|
|
17
|
+
};
|
|
18
|
+
/** abort the underlying SSE stream + stop the background loop (host unmount). */
|
|
19
|
+
dispose(): void;
|
|
20
|
+
}
|
|
21
|
+
export interface LiveWorkflowConfig {
|
|
22
|
+
baseUrl: string;
|
|
23
|
+
authToken: string;
|
|
24
|
+
/** SDK Principal — e.g. 'anon:sema' (workflows are owner-scoped by principal). */
|
|
25
|
+
principal: string;
|
|
26
|
+
/** explicit workflow run id; when absent the source picks the first RUNNING run from list() (else the first). */
|
|
27
|
+
workflowId?: string;
|
|
28
|
+
}
|
|
29
|
+
/** THE projector: SDK `WorkflowRun` (client.workflows.get) → the monitor's `WorkflowRunState`. Pure/total —
|
|
30
|
+
* tolerates absent/permissive fields and never throws (the graceful-degrade contract lives above, in the
|
|
31
|
+
* source loop; this just maps a well-formed run). Exported for the mock-parity unit cross-check. */
|
|
32
|
+
export declare function projectWorkflowRun(run: WorkflowRun): WorkflowRunState;
|
|
33
|
+
/**
|
|
34
|
+
* createLiveWorkflowSource({ baseUrl, authToken, principal, workflowId? }) — the LIVE workflow-monitor source.
|
|
35
|
+
*
|
|
36
|
+
* Constructs the AgentClient and kicks off a background loop that (1) resolves a run id (explicit config, else
|
|
37
|
+
* the first running run from `list()`), (2) snapshots it via `get()` → project → store, and (3) follows
|
|
38
|
+
* `stream()` as a change-ticker (re-`get()` throttled on each frame). Every step is degrade-safe: a 501 (S8
|
|
39
|
+
* off) / 404 (non-owner/absent) / empty list / connection error is RECORDED (status().degraded/code) and
|
|
40
|
+
* leaves `snapshot()` null so the host falls back to the mock/empty state — the source never throws into it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createLiveWorkflowSource(config: LiveWorkflowConfig): LiveWorkflowController;
|