@dsh-enhanced/assistant-delivery 0.1.3 → 0.1.4

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 CHANGED
@@ -106,6 +106,7 @@ rules:
106
106
  - 429/明确 5xx 未发送应给出 `retryable` 与可选 `retryAfterMs`;永久 4xx 进入 dead。
107
107
  - 请求可能已到平台但 response 丢失时返回 `unknown`。若声明 `reconcileUnknownSend` capability,必须实现对账,并使用独立的有界对账 attempt;连续无法判定达到上限后进入 dead,释放同 lane 后续消息。未声明该能力时记录保持 `unknown_after_send`,不会被领取为空对账 attempt,直到 owner 显式 retry/cancel。
108
108
  - receipt 必须带同一个 channel、account、provider message id;状态只允许单调 `accepted→delivered→read`。
109
+ - `progress()` 收到的工具参数/结果仅是最多 1500 字符的展示预览,并会脱敏常见 credential 字段与 token 形态;畸形或原始输入过大的参数只产生状态标记,不回显 raw text。它不包含任何 reasoning/thinking 内容或 provider 原始错误。adapter 仍必须按当前受众决定是否展示明细,群聊等共享受众应保持 status-only。
109
110
  - adapter 的 socket、timer、SDK client 和 listener 必须由 `start()` disposer 释放。
110
111
 
111
112
  内部身份始终是结构化的 `ExternalPrincipalKey`、`ConversationRef` 与 `ConversationBinding`,不会从 `platform:chat:thread` 字符串反解析路由。DM 不允许 thread;group 入口要求渠道提供显式且稳定的 thread,可以是真实 provider root,也可以是渠道命名空间内按 principal 派生的顶层合成 lane。binding 仍固定精确 principal,任何同 conversation 的另一 principal 都会在进入 session 前 dead-letter,不能共享既有 owner 上下文。Lark 顶层群消息使用按发送者派生的合成 lane,真实回复串继续按 `root_id` 隔离。
@@ -150,7 +151,7 @@ rules:
150
151
 
151
152
  `/model` 从宿主 `ctx.llm` 的实时 provider/model 目录生成受限的 typed `model-picker` intent,不发起模型生成;每个模型会显式关联它自己的 `effortIds`,而不是共享全局 effort 并集。支持它的渠道可以据此显示“分组、模型、effort”三级联动选择和确认按钮,不支持它的渠道会把该 intent 明确拒绝而不会降级为任意卡片 JSON。持久 selection 会先投影为不含 `updatedAt/version` 的纯路由再进入严格 intent 边界;如果目录中的异常数据仍使卡片校验失败,系统会立即退回完整纯文本目录,确定性校验错误不会重试。某个 provider 或模型能力解析失败不影响其他项,最多载入 20 个 provider、50 个模型、每模型 20 档 effort,并且全目录最多 20 个不同 effort id。
152
153
 
153
- 卡片提交后,Delivery 会再次核对 active binding、精确 principal/chat 和 Policy,并用实时 `resolveModelInfo()` 验证 provider/model 及该模型支持的 effort;通过后把三项选择按 canonical conversation 持久化到 Delivery SQLite。选择从下一条普通消息生效,保留当前 session 上下文,跨 `/new` 和 Host 重启仍有效。卡片可选择“默认(由模型决定)”;`/model use` 保留为无卡片渠道和排错时的文字后备,`/model reset` 删除会话覆盖并恢复 `agentProvider` / `agentModel`。模型目录按宿主约定是建议性的,最终调用是否成功仍由对应 adapter 和账号认证决定。
154
+ 卡片提交后,Delivery 会再次核对 active binding、精确 principal/chat、原始 provider message id 和 Policy,并用实时 `resolveModelInfo()` 验证 provider/model 及该模型支持的 effort;通过后把三项选择按 canonical conversation 持久化到 Delivery SQLite。渠道可以等待该 durable settlement:同实例由完成通知即时唤醒,共享数据库的另一实例完成时由有界轮询感知,从而把原卡更新成不可交互的最终结果。选择从下一条普通消息生效,保留当前 session 上下文,跨 `/new` 和 Host 重启仍有效。卡片可选择“默认(由模型决定)”;`/model use` 保留为无卡片渠道和排错时的文字后备,`/model reset` 删除会话覆盖并恢复 `agentProvider` / `agentModel`。模型目录按宿主约定是建议性的,最终调用是否成功仍由对应 adapter 和账号认证决定。若某个已持久化的显式 effort 在下一条普通消息前被 DSH 核心判为不支持,Delivery 会在任何 prompt 或 durable dispatch 标记之前,以 version CAS 仅清除该 effort、重新预检同一 provider/model 的默认档位;CAS 冲突会重试而不会覆盖更晚的 `/model` 选择。模型不会被静默替换,也不会重放或重复计费。
154
155
 
155
156
  `/permissions`(别名 `/permission`)只允许当前 active binding 的精确 owner 使用,命令本身不会进入 Agent 或触发 LLM。支持 typed `permission-picker` 的私聊渠道会显示“请求批准(ask)/帮我批准(auto)/完全访问权限(full)”三档卡片并标出当前档位;不支持卡片或渲染失败时自动退回含同等信息的文字命令。前两档都使用 `workspace-write + ask`,区别是持久 reviewer 为 `user` 或 `auto-review`。`full` 使用 `danger-full-access + never + none`,可访问网络及任意文件;卡片按钮带原生风险确认,文字入口则由 `/permission full` 显示橙色警告,必须再次发送 `/permission full confirm` 才切换。
156
157
 
@@ -31,6 +31,13 @@ interface DshDeliveryRuntimeOptions {
31
31
  maxOutputTokens: number;
32
32
  permissionPickerTtlMs: number;
33
33
  getModelSelection(conversation: ConversationRef): ConversationModelSelection | undefined;
34
+ /** Atomically remove a stale explicit effort without overwriting a newer model choice. */
35
+ clearStaleModelReasoningEffort(conversation: ConversationRef, expected: ConversationModelSelection): {
36
+ applied: false;
37
+ } | {
38
+ applied: true;
39
+ selection: ConversationModelSelection;
40
+ };
34
41
  imageMaterializer: Pick<InboundImageMaterializer, 'materialize'>;
35
42
  isInboundAuthorized(binding: Readonly<ConversationBinding>, envelope: Readonly<InboundEnvelope>): boolean;
36
43
  isPermissionController(binding: Readonly<ConversationBinding>, envelope: Readonly<InboundEnvelope>): boolean;
@@ -60,15 +67,14 @@ interface ModelCommandReply {
60
67
  fallbackText?: string;
61
68
  }
62
69
  /**
63
- * Convert one durable session fact into a deliberately narrow, user-visible progress update.
64
- * Raw reasoning chunks, tool arguments, result content, and error details never cross this boundary.
70
+ * Convert one durable session fact into a bounded, user-visible progress update.
71
+ * Reasoning/thinking content and provider error messages never cross this boundary. Tool calls/results
72
+ * contribute bounded previews with common credential shapes redacted; the channel still decides
73
+ * whether its current audience is private enough to render those previews.
65
74
  *
66
- * The `reasoning` block of a durable `assistant/message` is the assistant's own settled summary of
67
- * the step, not a streaming fragment, so it is the one reasoning form safe to surface. Only some
68
- * providers emit it at all: subscription CLIs declare an effort capability but still return text
69
- * only, and the ACP thought channel is not mapped to DSH reasoning yet. `step/start` therefore also
70
- * yields a neutral phase label, so a turn reports progress even on providers that never reason
71
- * out loud.
75
+ * A DSH `reasoning` block is explicitly thinking content and is not guaranteed to be a public
76
+ * summary, even after assembly into `assistant/message`. `step/start` therefore provides the only
77
+ * reasoning-independent phase label, so a turn reports progress without exposing hidden thought.
72
78
  */
73
79
  export declare function deliveryProgressFromSessionEvent(event: SessionEvent): DeliveryProgressUpdate | undefined;
74
80
  export declare function permissionStateHash(events: readonly SessionEvent[], tableFingerprint?: string): string;
@@ -88,6 +94,13 @@ export declare class DshDeliveryRuntime implements DeliveryInboundRuntime {
88
94
  /** Wait for only the turn that claims Delivery's exact message identity. */
89
95
  private followupTurn;
90
96
  private setupAgent;
97
+ /**
98
+ * DSH validates an explicit reasoning effort before calling an adapter. That is normally the
99
+ * right contract, but a persisted external-channel selection can outlive a provider's live
100
+ * model directory. Clear only that stale persisted effort before a prompt crosses the durable
101
+ * dispatch boundary; TraeX still rechecks the newly created ACP session afterwards.
102
+ */
103
+ private resolveExecutionRoute;
91
104
  private reconcileNativeFullPermissionReviewer;
92
105
  private runPermissionCommand;
93
106
  createSession(input: {
@@ -1 +1 @@
1
- {"version":3,"file":"agent-runtime.d.ts","sourceRoot":"","sources":["../src/agent-runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-runtime.d.ts","sourceRoot":"","sources":["../src/agent-runtime.ts"],"names":[],"mappings":"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"}
@@ -22,6 +22,16 @@ class UserSessionCancellation extends Error {
22
22
  const MAX_CATALOG_MODELS = 50;
23
23
  const MAX_PROGRESS_TODOS = 20;
24
24
  const MAX_PROGRESS_TEXT_CHARS = 240;
25
+ const MAX_PROGRESS_TOOL_PREVIEW_CHARS = 1_500;
26
+ const MAX_PROGRESS_JSON_INPUT_CHARS = 32_768;
27
+ const MAX_PROGRESS_JSON_DEPTH = 16;
28
+ const MAX_PROGRESS_JSON_NODES = 500;
29
+ const MAX_PROGRESS_RESULT_BLOCKS = 100;
30
+ const REDACTED_PROGRESS_VALUE = '[REDACTED]';
31
+ const TRUNCATED_PROGRESS_VALUE = '[TRUNCATED]';
32
+ const PROGRESS_ASSIGNMENT_KEY = '([A-Za-z_][A-Za-z0-9_.-]{0,127})';
33
+ const PROGRESS_ASSIGNMENT_START = new RegExp(`(^|[^A-Za-z0-9_])(["']?)${PROGRESS_ASSIGNMENT_KEY}\\2(\\s*[:=]\\s*)`, 'giu');
34
+ const ESCAPED_QUOTED_PROGRESS_ASSIGNMENT_START = new RegExp(`(^|[^A-Za-z0-9_])\\\\+(["'])${PROGRESS_ASSIGNMENT_KEY}\\\\+\\2(\\s*[:=]\\s*)`, 'giu');
25
35
  class DurableAgentIdentityError extends Error {
26
36
  }
27
37
  class ApprovalReviewerReaderUnavailableError extends Error {
@@ -97,16 +107,214 @@ async function requireImageCapability(llm, provider, model, signal) {
97
107
  function boundedProgressText(value) {
98
108
  return [...value].slice(0, MAX_PROGRESS_TEXT_CHARS).join('');
99
109
  }
110
+ function boundedProgressToolPreview(value) {
111
+ const trimmed = value.trim();
112
+ const characters = [...trimmed];
113
+ return characters.length <= MAX_PROGRESS_TOOL_PREVIEW_CHARS
114
+ ? trimmed
115
+ : `${characters.slice(0, MAX_PROGRESS_TOOL_PREVIEW_CHARS - 1).join('')}…`;
116
+ }
117
+ function redactProgressText(value) {
118
+ const common = redactEscapedKeyProgressAssignments(value)
119
+ .replace(/-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?(?:-----END [^-]*PRIVATE KEY-----|$)/giu, '[REDACTED PRIVATE KEY]')
120
+ .replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^/\s@]+@/giu, '$1[REDACTED]@')
121
+ .replace(/\b((?:proxy-)?authorization\s*[:=]\s*)(?:basic|bearer)\s+[^\s,;]+/giu, '$1[REDACTED]');
122
+ return redactProgressAssignments(common)
123
+ .replace(/\b(?:github_pat_[A-Za-z0-9_]{20,}|gh[pousr]_[A-Za-z0-9]{20,}|(?:sk|xai)-[A-Za-z0-9_-]{8,})\b/gu, REDACTED_PROGRESS_VALUE)
124
+ .replace(/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/gu, REDACTED_PROGRESS_VALUE);
125
+ }
126
+ function isSensitiveProgressKey(key) {
127
+ const words = key.normalize('NFKC')
128
+ .replace(/([a-z\d])([A-Z])/gu, '$1_$2')
129
+ .toLowerCase()
130
+ .split(/[^a-z\d]+/gu)
131
+ .filter(Boolean);
132
+ const joined = words.join('');
133
+ return words.some(word => [
134
+ 'auth', 'authorization', 'bearer', 'cookie', 'cookies', 'credential', 'credentials',
135
+ 'password', 'passwd', 'secret', 'session', 'token',
136
+ ].includes(word))
137
+ || ['apikey', 'accesskey', 'privatekey'].some(phrase => joined.includes(phrase))
138
+ || words.some((word, index) => ['api', 'access', 'private'].includes(word) && words[index + 1] === 'key');
139
+ }
140
+ /**
141
+ * Text credentials have no trustworthy terminator: quotes may be malformed or multiply escaped,
142
+ * while spaces, semicolons, and nested assignments can still belong to the value. Once a key is
143
+ * recognized, fail closed through the logical line instead of exposing an arbitrary suffix.
144
+ */
145
+ function redactProgressAssignments(value) {
146
+ let retainedIndex = 0;
147
+ let redacted = '';
148
+ PROGRESS_ASSIGNMENT_START.lastIndex = 0;
149
+ for (let match = PROGRESS_ASSIGNMENT_START.exec(value); match !== null; match = PROGRESS_ASSIGNMENT_START.exec(value)) {
150
+ const key = match[3];
151
+ if (key === undefined || !isSensitiveProgressKey(key)) {
152
+ // The prefix regex consumes the separator of an innocent outer field. Resume inside that
153
+ // match so nested assignments on the same line remain visible to this scanner.
154
+ PROGRESS_ASSIGNMENT_START.lastIndex = match.index + 1;
155
+ continue;
156
+ }
157
+ const valueStart = match.index + match[0].length;
158
+ const lineEnd = progressRedactionEnd(value, valueStart);
159
+ redacted += value.slice(retainedIndex, valueStart) + REDACTED_PROGRESS_VALUE;
160
+ retainedIndex = lineEnd;
161
+ PROGRESS_ASSIGNMENT_START.lastIndex = lineEnd;
162
+ }
163
+ PROGRESS_ASSIGNMENT_START.lastIndex = 0;
164
+ return redacted + value.slice(retainedIndex);
165
+ }
166
+ /** Escaped JSON keys inside a log string cannot be parsed as the outer text; fail closed by line. */
167
+ function redactEscapedKeyProgressAssignments(value) {
168
+ let retainedIndex = 0;
169
+ let redacted = '';
170
+ ESCAPED_QUOTED_PROGRESS_ASSIGNMENT_START.lastIndex = 0;
171
+ for (let match = ESCAPED_QUOTED_PROGRESS_ASSIGNMENT_START.exec(value); match !== null; match = ESCAPED_QUOTED_PROGRESS_ASSIGNMENT_START.exec(value)) {
172
+ const key = match[3];
173
+ if (key === undefined || !isSensitiveProgressKey(key)) {
174
+ ESCAPED_QUOTED_PROGRESS_ASSIGNMENT_START.lastIndex = match.index + 1;
175
+ continue;
176
+ }
177
+ const valueStart = match.index + match[0].length;
178
+ const lineEnd = progressRedactionEnd(value, valueStart);
179
+ redacted += value.slice(retainedIndex, valueStart) + REDACTED_PROGRESS_VALUE;
180
+ retainedIndex = lineEnd;
181
+ ESCAPED_QUOTED_PROGRESS_ASSIGNMENT_START.lastIndex = lineEnd;
182
+ }
183
+ ESCAPED_QUOTED_PROGRESS_ASSIGNMENT_START.lastIndex = 0;
184
+ return redacted + value.slice(retainedIndex);
185
+ }
186
+ function progressRedactionEnd(value, start) {
187
+ let cursor = start;
188
+ while (cursor < value.length) {
189
+ const carriageReturn = value.indexOf('\r', cursor);
190
+ const lineFeed = value.indexOf('\n', cursor);
191
+ const candidates = [carriageReturn, lineFeed].filter(index => index >= 0);
192
+ if (candidates.length === 0)
193
+ return value.length;
194
+ const boundary = Math.min(...candidates);
195
+ if (value[boundary - 1] !== '\\')
196
+ return boundary;
197
+ cursor = boundary + (value[boundary] === '\r' && value[boundary + 1] === '\n' ? 2 : 1);
198
+ }
199
+ return value.length;
200
+ }
201
+ function redactProgressJson(value, state, depth = 0) {
202
+ state.nodes += 1;
203
+ if (depth > MAX_PROGRESS_JSON_DEPTH || state.nodes > MAX_PROGRESS_JSON_NODES) {
204
+ return TRUNCATED_PROGRESS_VALUE;
205
+ }
206
+ if (Array.isArray(value)) {
207
+ const items = value.slice(0, 100).map(item => redactProgressJson(item, state, depth + 1));
208
+ if (value.length > items.length)
209
+ items.push(TRUNCATED_PROGRESS_VALUE);
210
+ return items;
211
+ }
212
+ if (value !== null && typeof value === 'object') {
213
+ const entries = Object.entries(value).slice(0, 100).map(([key, item]) => [
214
+ key,
215
+ isSensitiveProgressKey(key)
216
+ ? REDACTED_PROGRESS_VALUE
217
+ : redactProgressJson(item, state, depth + 1),
218
+ ]);
219
+ if (Object.keys(value).length > entries.length)
220
+ entries.push(['…', TRUNCATED_PROGRESS_VALUE]);
221
+ return Object.fromEntries(entries);
222
+ }
223
+ return typeof value === 'string' ? redactProgressText(value) : value;
224
+ }
225
+ function progressArgumentsPlaceholder(value) {
226
+ const characters = [...redactProgressText(value.slice(0, MAX_PROGRESS_JSON_INPUT_CHARS))];
227
+ let retained = Math.min(characters.length, MAX_PROGRESS_TOOL_PREVIEW_CHARS);
228
+ while (retained >= 0) {
229
+ const encoded = JSON.stringify({
230
+ truncated: true,
231
+ preview: characters.slice(0, retained).join(''),
232
+ });
233
+ if ([...encoded].length <= MAX_PROGRESS_TOOL_PREVIEW_CHARS)
234
+ return encoded;
235
+ retained = retained === 0 ? -1 : Math.floor(retained * 0.75);
236
+ }
237
+ return '{"truncated":true}';
238
+ }
239
+ function progressArgumentsPreview(value) {
240
+ if (value.trim() === '')
241
+ return '{}';
242
+ // Never publish raw malformed or oversized model output. Normal tool calls are valid JSON; when
243
+ // that invariant fails, a status marker is more useful than a best-effort secret scrubber.
244
+ if (value.length > MAX_PROGRESS_JSON_INPUT_CHARS)
245
+ return '{"truncated":true}';
246
+ try {
247
+ const encoded = JSON.stringify(redactProgressJson(JSON.parse(value), { nodes: 0 }), null, 2);
248
+ if (encoded === undefined)
249
+ return '{"invalidJson":true}';
250
+ return [...encoded].length <= MAX_PROGRESS_TOOL_PREVIEW_CHARS
251
+ ? encoded
252
+ : progressArgumentsPlaceholder(encoded);
253
+ }
254
+ catch {
255
+ return '{"invalidJson":true}';
256
+ }
257
+ }
258
+ function redactProgressResultText(value) {
259
+ if (value.length > MAX_PROGRESS_JSON_INPUT_CHARS)
260
+ return '{"truncated":true}';
261
+ const trimmed = value.trim();
262
+ if ((trimmed.startsWith('{') && trimmed.endsWith('}'))
263
+ || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
264
+ try {
265
+ const encoded = JSON.stringify(redactProgressJson(JSON.parse(trimmed), { nodes: 0 }), null, 2);
266
+ if (encoded !== undefined)
267
+ return encoded;
268
+ }
269
+ catch {
270
+ // Fall through to conservative line-oriented handling for JSON-like logs and fragments.
271
+ }
272
+ }
273
+ return redactProgressText(value);
274
+ }
275
+ function progressResultPreview(content, depth = 0) {
276
+ if (depth > 4)
277
+ return TRUNCATED_PROGRESS_VALUE;
278
+ const parts = [];
279
+ let approximateLength = 0;
280
+ const append = (value) => {
281
+ if (approximateLength >= MAX_PROGRESS_TOOL_PREVIEW_CHARS * 2 || value === '')
282
+ return;
283
+ const retained = value.slice(0, MAX_PROGRESS_TOOL_PREVIEW_CHARS * 2 - approximateLength);
284
+ parts.push(retained);
285
+ approximateLength += retained.length;
286
+ };
287
+ let blockCount = 0;
288
+ for (const block of content) {
289
+ if (approximateLength >= MAX_PROGRESS_TOOL_PREVIEW_CHARS * 2)
290
+ break;
291
+ if (blockCount >= MAX_PROGRESS_RESULT_BLOCKS) {
292
+ append(TRUNCATED_PROGRESS_VALUE);
293
+ break;
294
+ }
295
+ blockCount += 1;
296
+ if (block.type === 'text')
297
+ append(redactProgressResultText(block.text));
298
+ else if (block.type === 'image')
299
+ append('[图片]');
300
+ else if (block.type === 'tool-call') {
301
+ append(`${block.name}\n${progressArgumentsPreview(block.arguments)}`);
302
+ }
303
+ else if (block.type === 'tool-result') {
304
+ append(progressResultPreview(block.content, depth + 1));
305
+ }
306
+ }
307
+ return boundedProgressToolPreview(redactProgressText(parts.join('\n')));
308
+ }
100
309
  /**
101
- * Convert one durable session fact into a deliberately narrow, user-visible progress update.
102
- * Raw reasoning chunks, tool arguments, result content, and error details never cross this boundary.
310
+ * Convert one durable session fact into a bounded, user-visible progress update.
311
+ * Reasoning/thinking content and provider error messages never cross this boundary. Tool calls/results
312
+ * contribute bounded previews with common credential shapes redacted; the channel still decides
313
+ * whether its current audience is private enough to render those previews.
103
314
  *
104
- * The `reasoning` block of a durable `assistant/message` is the assistant's own settled summary of
105
- * the step, not a streaming fragment, so it is the one reasoning form safe to surface. Only some
106
- * providers emit it at all: subscription CLIs declare an effort capability but still return text
107
- * only, and the ACP thought channel is not mapped to DSH reasoning yet. `step/start` therefore also
108
- * yields a neutral phase label, so a turn reports progress even on providers that never reason
109
- * out loud.
315
+ * A DSH `reasoning` block is explicitly thinking content and is not guaranteed to be a public
316
+ * summary, even after assembly into `assistant/message`. `step/start` therefore provides the only
317
+ * reasoning-independent phase label, so a turn reports progress without exposing hidden thought.
110
318
  */
111
319
  export function deliveryProgressFromSessionEvent(event) {
112
320
  if (event.type === 'step/start') {
@@ -117,18 +325,15 @@ export function deliveryProgressFromSessionEvent(event) {
117
325
  };
118
326
  }
119
327
  if (event.type === 'assistant/message') {
120
- const reasoning = event.data.message.content
121
- .filter(value => value.type === 'reasoning')
122
- .map(value => (value.type === 'reasoning' ? value.text.trim() : ''))
123
- .filter(text => text !== '')
124
- .join('\n');
125
- return reasoning === '' ? undefined : { kind: 'step', text: boundedProgressText(reasoning) };
328
+ return undefined;
126
329
  }
127
330
  if (event.type === 'tool/call') {
331
+ const argumentsPreview = progressArgumentsPreview(event.data.arguments);
128
332
  return {
129
333
  kind: 'tool-started',
130
334
  callId: String(event.data.callId),
131
335
  toolName: boundedProgressText(event.data.name),
336
+ ...(argumentsPreview === '' ? {} : { argumentsPreview }),
132
337
  };
133
338
  }
134
339
  if (event.type === 'tool/result') {
@@ -138,10 +343,16 @@ export function deliveryProgressFromSessionEvent(event) {
138
343
  : event.data.message.source?.callId;
139
344
  if (callId === undefined)
140
345
  return undefined;
346
+ const resultPreview = block?.type === 'tool-result'
347
+ ? progressResultPreview(block.content)
348
+ : '';
349
+ const code = event.data.error?.code;
141
350
  return {
142
351
  kind: 'tool-finished',
143
352
  callId: String(callId),
144
353
  failed: event.data.error !== undefined || (block?.type === 'tool-result' && block.isError === true),
354
+ ...(resultPreview === '' ? {} : { resultPreview }),
355
+ ...(code === undefined ? {} : { code: boundedProgressText(code) }),
145
356
  };
146
357
  }
147
358
  if (event.type === 'todo/write') {
@@ -412,6 +623,17 @@ function agentSelection(route) {
412
623
  : { reasoningEffort: ReasoningEffortId(route.reasoningEffort) }),
413
624
  };
414
625
  }
626
+ function errorHasCode(error, code) {
627
+ let current = error;
628
+ const visited = new Set();
629
+ while (typeof current === 'object' && current !== null && !visited.has(current)) {
630
+ visited.add(current);
631
+ if ('code' in current && current.code === code)
632
+ return true;
633
+ current = current instanceof Error ? current.cause : undefined;
634
+ }
635
+ return false;
636
+ }
415
637
  async function modelCatalog(llm, conversation, current, isDefault, eventId, expiresAt, signal) {
416
638
  const providers = llm.listProviders();
417
639
  const catalogs = await Promise.all(providers.map(async (provider) => {
@@ -810,6 +1032,44 @@ export class DshDeliveryRuntime {
810
1032
  installModelSelection(agentCtx, { current: selected, assembled: undefined });
811
1033
  await agentPresets?.mount(agentCtx, presetId);
812
1034
  }
1035
+ /**
1036
+ * DSH validates an explicit reasoning effort before calling an adapter. That is normally the
1037
+ * right contract, but a persisted external-channel selection can outlive a provider's live
1038
+ * model directory. Clear only that stale persisted effort before a prompt crosses the durable
1039
+ * dispatch boundary; TraeX still rechecks the newly created ACP session afterwards.
1040
+ */
1041
+ async resolveExecutionRoute(conversation, persisted, route, signal) {
1042
+ if (route.reasoningEffort === undefined)
1043
+ return { route };
1044
+ const llm = this.ctx.get('llm');
1045
+ if (llm === undefined)
1046
+ throw new Error('assistant-delivery: llm service is required');
1047
+ try {
1048
+ await llm.resolveCallConfig({
1049
+ provider: route.provider,
1050
+ model: route.model,
1051
+ reasoningEffort: ReasoningEffortId(route.reasoningEffort),
1052
+ }, signal);
1053
+ return { route };
1054
+ }
1055
+ catch (error) {
1056
+ if (signal.aborted || !errorHasCode(error, 'UNSUPPORTED_REASONING_EFFORT'))
1057
+ throw error;
1058
+ const fallback = { provider: route.provider, model: route.model };
1059
+ // Do not turn an invalid model/default into a silent recovery. This is the same core
1060
+ // preflight without the stale explicit effort, still before an Agent turn is dispatched.
1061
+ await llm.resolveCallConfig(fallback, signal);
1062
+ if (persisted === undefined) {
1063
+ this.ctx.logger.warn(`assistant-delivery: using provider default after unsupported reasoning effort for ${route.provider}/${route.model}`);
1064
+ return { route: fallback };
1065
+ }
1066
+ const recovered = this.options.clearStaleModelReasoningEffort(conversation, persisted);
1067
+ if (!recovered.applied)
1068
+ return { retry: true };
1069
+ this.ctx.logger.warn(`assistant-delivery: cleared stale reasoning effort for ${route.provider}/${route.model} before dispatch`);
1070
+ return { route: toModelRoute(recovered.selection) };
1071
+ }
1072
+ }
813
1073
  async reconcileNativeFullPermissionReviewer(session) {
814
1074
  const result = await this.policy.reconcileNativeFullReviewer(session);
815
1075
  if (result === 'unavailable') {
@@ -1707,9 +1967,15 @@ export class DshDeliveryRuntime {
1707
1967
  if (agents === undefined || sessions === undefined) {
1708
1968
  return { outcome: 'not-processed', failureCode: 'agent-runtime-unavailable', retryable: true };
1709
1969
  }
1710
- const selectedRoute = toModelRoute(prepared?.modelRoute
1711
- ?? this.options.getModelSelection(envelope.conversation)
1970
+ const persistedSelection = this.options.getModelSelection(envelope.conversation);
1971
+ const requestedRoute = toModelRoute(persistedSelection
1972
+ ?? prepared?.modelRoute
1712
1973
  ?? { provider: this.options.provider, model: this.options.model });
1974
+ const resolvedRoute = await this.resolveExecutionRoute(envelope.conversation, persistedSelection, requestedRoute, signal);
1975
+ if ('retry' in resolvedRoute) {
1976
+ return { outcome: 'not-processed', failureCode: 'model-selection-changed-before-dispatch', retryable: true };
1977
+ }
1978
+ const selectedRoute = resolvedRoute.route;
1713
1979
  const selected = agentSelection(selectedRoute);
1714
1980
  let handle;
1715
1981
  let dispatched = false;