@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 +2 -1
- package/lib/agent-runtime.d.ts +21 -8
- package/lib/agent-runtime.d.ts.map +1 -1
- package/lib/agent-runtime.js +282 -16
- package/lib/agent-runtime.js.map +1 -1
- package/lib/service.d.ts +8 -22
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +158 -11
- package/lib/service.js.map +1 -1
- package/lib/store.d.ts +27 -0
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +70 -4
- package/lib/store.js.map +1 -1
- package/lib/types.d.ts +35 -5
- package/lib/types.d.ts.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +3 -3
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
|
|
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
|
|
package/lib/agent-runtime.d.ts
CHANGED
|
@@ -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
|
|
64
|
-
*
|
|
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
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
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":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,EAEL,KAAK,KAAK,EAGX,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAUlE,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAMrF,OAAO,EAKL,KAAK,sBAAsB,EAC5B,MAAM,gCAAgC,CAAA;AAEvC,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EAEpB,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EACvB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AACnE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAS1D,OAAO,KAAK,EACV,mBAAmB,EACnB,0BAA0B,EAC1B,eAAe,EACf,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,sBAAsB,EAEvB,MAAM,YAAY,CAAA;AAEnB,OAAO,QAAQ,sBAAsB,CAAC;IACpC,UAAU,gBAAgB;QACxB,QAAQ,EAAE;YACR,IAAI,EAAE,UAAU,CAAA;YAChB,OAAO,EAAE,MAAM,CAAA;YACf,OAAO,EAAE,MAAM,CAAA;YACf,OAAO,EAAE,MAAM,CAAA;YACf,KAAK,EAAE,WAAW,CAAA;SACnB,CAAA;KACF;CACF;AAED,UAAU,yBAAyB;IACjC,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,IAAI,IAAI,CAAC,YAAY,EAAE,OAAO,GAAG,SAAS,CAAC,GAAG,SAAS,CAAA;IACtE,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACzC,qBAAqB,EAAE,OAAO,GAAG,MAAM,CAAA;IACvC,eAAe,EAAE,MAAM,CAAA;IACvB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,iBAAiB,CAAC,YAAY,EAAE,eAAe,GAAG,0BAA0B,GAAG,SAAS,CAAA;IACxF,0FAA0F;IAC1F,8BAA8B,CAC5B,YAAY,EAAE,eAAe,EAC7B,QAAQ,EAAE,0BAA0B,GACnC;QAAE,OAAO,EAAE,KAAK,CAAA;KAAE,GAAG;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,SAAS,EAAE,0BAA0B,CAAA;KAAE,CAAA;IAChF,iBAAiB,EAAE,IAAI,CAAC,wBAAwB,EAAE,aAAa,CAAC,CAAA;IAChE,mBAAmB,CACjB,OAAO,EAAE,QAAQ,CAAC,mBAAmB,CAAC,EACtC,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,GAClC,OAAO,CAAA;IACV,sBAAsB,CACpB,OAAO,EAAE,QAAQ,CAAC,mBAAmB,CAAC,EACtC,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,GAClC,OAAO,CAAA;IACV,iGAAiG;IACjG,wBAAwB,CACtB,OAAO,EAAE,QAAQ,CAAC,mBAAmB,CAAC,EACtC,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,GAClC,OAAO,CAAA;IACV,iBAAiB,CAAC,YAAY,EAAE,eAAe,GAAG,MAAM,CAAA;IACxD,kBAAkB,CAAC,KAAK,EAAE;QACxB,YAAY,EAAE,eAAe,CAAA;QAC7B,aAAa,EAAE,MAAM,CAAA;QACrB,KAAK,CAAC,EAAE,aAAa,CAAA;KACtB,GAAG;QAAE,OAAO,EAAE,KAAK,CAAA;KAAE,GAAG;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,0BAA0B,CAAA;KAAE,CAAA;IAClF,gBAAgB,EAAE,MAAM,CAAA;IACxB,QAAQ,CACN,OAAO,EAAE,QAAQ,CAAC,mBAAmB,CAAC,EACtC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAAA;IAChB,YAAY,CACV,OAAO,EAAE,QAAQ,CAAC,mBAAmB,CAAC,EACtC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,iBAAiB,EACxB,cAAc,CAAC,EAAE,MAAM,GACtB,IAAI,CAAA;IACP,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAA;CACrE;AAED,UAAU,iBAAiB;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,UAAU,GAAG,cAAc,GAAG,mBAAmB,GAAG,OAAO,CAAA;IACpE,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,gBAAgB,CAAC,EAAE,sBAAsB,CAAA;IACzC,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AA6UD;;;;;;;;;GASG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,YAAY,GAAG,sBAAsB,GAAG,SAAS,CA6CxG;AAkMD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,EAAE,gBAAgB,SAAK,GAAG,MAAM,CAalG;AAED,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAU5G;AA2CD,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAUvG;AAyTD,qBAAa,kBAAmB,YAAW,sBAAsB;IAK7D,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAN1B,QAAQ,CAAC,eAAe,EAAG,UAAU,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAA0C;gBAG7D,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,yBAAyB;IAG/C,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAwBrG,OAAO,CAAC,mBAAmB;IAwB3B,OAAO,CAAC,iBAAiB;YAMX,yBAAyB;IAwBvC,4EAA4E;IAC5E,OAAO,CAAC,YAAY;YA2CN,UAAU;IAqBxB;;;;;OAKG;YACW,qBAAqB;YAiCrB,qCAAqC;YASrC,oBAAoB;IAmb5B,aAAa,CAAC,KAAK,EAAE;QACzB,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAA;QACnC,UAAU,EAAE,MAAM,CAAA;QAClB,QAAQ,CAAC,EAAE,QAAQ,CAAC,mBAAmB,CAAC,CAAA;QACxC,MAAM,EAAE,WAAW,CAAA;KACpB,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAqEvF,OAAO,CACX,OAAO,EAAE,QAAQ,CAAC,mBAAmB,CAAC,EACtC,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,EACnC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,QAAQ,CAAC,qBAAqB,CAAC,GACvC,OAAO,CAAC,oBAAoB,CAAC;IAmDhC,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,WAAW;YAkBL,iBAAiB;IA2LzB,OAAO,CACX,OAAO,EAAE,QAAQ,CAAC,mBAAmB,CAAC,EACtC,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,EACnC,WAAW,EAAE,WAAW,EACxB,QAAQ,CAAC,EAAE,QAAQ,CAAC,sBAAsB,CAAC,EAC3C,eAAe,CAAC,EAAE,sBAAsB,GACvC,OAAO,CAAC,oBAAoB,CAAC;YA+ClB,iBAAiB;CAyQhC"}
|
package/lib/agent-runtime.js
CHANGED
|
@@ -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
|
|
102
|
-
*
|
|
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
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
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
|
-
|
|
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
|
|
1711
|
-
|
|
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;
|