From 3d9700345c1dc6ef1f4080cd9b783c0cc01cf3a8 Mon Sep 17 00:00:00 2001
From: "jinye.djy"
Date: Sat, 7 Nov 2026 22:25:05 +0800
Subject: [PATCH] feat(managed-agent): add Tool v4 execution measurements
Extend the existing activity diagnostics with frozen executor evidence and measured native file changes.
Co-authored-by: Qwen-Coder
---
...026-11-07-managed-execution-diagnostics.md | 90 ++
...-07-managed-execution-diagnostics.zh-CN.md | 90 ++
.../managed-agent-configuration-reference.md | 1 +
...ged-agent-configuration-reference.zh-CN.md | 1 +
.../cli/src/serve/hosted-runtime-recovery.ts | 15 +
.../src/serve/hosted-workspace-broker.test.ts | 38 +
.../cli/src/serve/hosted-workspace-broker.ts | 79 +-
.../serve/hosted-workspace-tool-turn.test.ts | 60 +
.../src/serve/hosted-workspace-tool-turn.ts | 31 +-
.../cli/src/serve/managed-context-worker.ts | 6 +-
.../cli/src/serve/managed-external-storage.ts | 4 +-
.../managed-runtime-attestation-worker.ts | 15 +-
.../serve/managed-runtime-tool-executor.ts | 296 ++++-
.../serve/managed-runtime-tool-v3-routes.ts | 1 +
.../managed-runtime-tool-v4-routes.test.ts | 1086 +++++++++++++++++
.../serve/managed-runtime-tool-v4-routes.ts | 321 +++++
.../src/serve/managed-workspace-activation.ts | 17 +-
.../src/serve/workspace-recovery-session.ts | 15 +-
.../managed-event-envelope-v1.fixtures.json | 19 +
.../managed-event-envelope-v1.schema.json | 2 +
.../managed-session-store-v1.fixtures.json | 1 +
...managed-tool-measurements-v1.fixtures.json | 307 +++++
.../http-managed-session-store.test.ts | 2 +-
.../http-managed-session-store.ts | 46 +-
.../managed-execution-diagnostics.test.ts | 160 ++-
.../managed-session-records.test.ts | 2 +-
.../managed-session-records.ts | 13 +-
.../managed-tool-measurements.test.ts | 58 +
.../managed-tool-measurements.ts | 314 +++++
.../configuration-descriptions.json | 4 +
.../event/ManagedEventEnvelopes.java | 6 +-
.../ManagedActivityDiagnostics.java | 34 +
.../persistence/ManagedExtensionRecords.java | 2 +-
.../persistence/ManagedStorageVersions.java | 2 +-
.../persistence/ManagedToolMeasurements.java | 172 +++
.../settings/ManagedAgentSettings.java | 4 +
.../RuntimeToolMeasurements.java | 100 ++
.../code/runtimebroker/RuntimeTransport.java | 51 +
.../runtimebroker/ToolExecutionRecord.java | 16 +
.../ToolExecutionRepository.java | 12 +
.../WorkspaceExecutionProfile.java | 6 +-
.../managed-agent-public-api.openapi.json | 212 ++++
.../ManagedSessionActivityService.java | 4 +
.../runtime/WorkspaceRuntimeProvisioner.java | 2 +-
.../runtime/WorkspaceRuntimeResolver.java | 26 +-
.../runtime/WorkspaceRuntimeTransport.java | 59 +-
.../persistence/JournalCommit.java | 7 +-
.../persistence/JournalWriterLifecycle.java | 25 +-
.../ManagedActivityDiagnosticProjection.java | 57 +-
.../ManagedModelDiagnosticProjection.java | 6 +-
.../ManagedNativeJournalSnapshots.java | 6 +
.../persistence/WorkspaceRecoveryStore.java | 2 +-
.../InMemoryToolExecutionRepository.java | 17 +
.../JdbcToolExecutionObservations.java | 68 ++
.../JdbcToolExecutionRepository.java | 43 +-
.../service/RuntimeBrokerService.java | 68 +-
.../service/RuntimeBrokerSupport.java | 50 +
.../service/RuntimeExecutionCancellation.java | 6 +-
.../service/RuntimeExecutionDispatcher.java | 59 +-
.../service/RuntimeExecutionReconciler.java | 80 +-
.../ExternalWorkspaceProvisioner.java | 7 +-
.../adapter/HttpRuntimeTransport.java | 210 +++-
.../config/ManagedAgentProperties.java | 1 +
.../config/ManagedSessionReaderVersion.java | 2 +-
.../server/src/main/resources/application.yml | 1 +
.../ManagedActivityDiagnosticsTest.java | 58 +
.../ManagedAgentApiContractTest.java | 17 +
.../ManagedToolMeasurementsTest.java | 113 ++
.../ManagedSessionReaderVersionTest.java | 5 +
.../WorkspaceMeasurementScopeTest.java | 46 +
.../WorkspaceRuntimeTransportTest.java | 58 +-
.../HttpRuntimeToolV4TransportTest.java | 372 ++++++
.../ExternalWorkspaceProvisionerTest.java | 29 +
.../RuntimeExecutionDispatcherTest.java | 38 +-
.../RuntimeExecutionReconcilerTest.java | 68 +-
.../resources/class-size-ratchet.properties | 6 +-
.../managed/ManagedActivityView.tsx | 126 +-
.../managed/ManagedDiagnosticsPanel.test.tsx | 54 +
.../managed/generated/managed-agent-api.ts | 37 +
.../managed/managed-diagnostics-messages.ts | 19 +
.../client/e2e/managed-diagnostics.spec.ts | 36 +-
81 files changed, 5366 insertions(+), 233 deletions(-)
create mode 100644 docs/design/2026-11-07-managed-execution-diagnostics.md
create mode 100644 docs/design/2026-11-07-managed-execution-diagnostics.zh-CN.md
create mode 100644 packages/cli/src/serve/managed-runtime-tool-v4-routes.test.ts
create mode 100644 packages/cli/src/serve/managed-runtime-tool-v4-routes.ts
create mode 100644 packages/core/src/managed-runtime/contracts/managed-tool-measurements-v1.fixtures.json
create mode 100644 packages/core/src/managed-runtime/managed-tool-measurements.test.ts
create mode 100644 packages/core/src/managed-runtime/managed-tool-measurements.ts
create mode 100644 packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedToolMeasurements.java
create mode 100644 packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/runtimebroker/RuntimeToolMeasurements.java
create mode 100644 packages/managed-agent-server/persistence/src/main/java/com/alibaba/qwen/code/runtimebroker/persistence/JdbcToolExecutionObservations.java
create mode 100644 packages/managed-agent-server/verification/src/test/java/com/alibaba/qwen/code/managedagent/ManagedToolMeasurementsTest.java
create mode 100644 packages/managed-agent-server/verification/src/test/java/com/alibaba/qwen/code/managedagent/workspace/runtime/WorkspaceMeasurementScopeTest.java
create mode 100644 packages/managed-agent-server/verification/src/test/java/com/alibaba/qwen/code/runtimebroker/adapter/HttpRuntimeToolV4TransportTest.java
diff --git a/docs/design/2026-11-07-managed-execution-diagnostics.md b/docs/design/2026-11-07-managed-execution-diagnostics.md
new file mode 100644
index 00000000000..c3c139e312a
--- /dev/null
+++ b/docs/design/2026-11-07-managed-execution-diagnostics.md
@@ -0,0 +1,90 @@
+# Managed Tool v4 executor measurements
+
+[English](2026-11-07-managed-execution-diagnostics.md) | [简体中文](2026-11-07-managed-execution-diagnostics.zh-CN.md)
+
+## Status and baseline
+
+This design narrows PR #16498 after the author selected #16499 as the baseline. The prerequisite remains #16475; the initial integration base is `d886d0640a623928cea4507a413a1f1899bb1e8a`. Implementation and final CI acceptance are tracked separately.
+
+The baseline owns invocation identity, activity evidence v1, incremental SQL projections, authorized activity queries, the statistics panel and the child invocation tree. This increment extends those paths. It does not restore the earlier `execution_diagnostics` domain, a second collector, `/stats.execution`, or a second graph.
+
+## Remaining behavior
+
+An ordinary Hosted Runtime can report its actual execution duration and the native file changes it measured. The existing activity view must retain those measurements after Runtime shutdown and replay. A successful filesystem operation remains successful when formatting, output limits or delivery later fail.
+
+| Measurement | Meaning |
+| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Execution duration | A nonnegative integer measured with the executor's monotonic clock around the actual invocation. Approval, queueing and result publication are excluded. |
+| Model lines | Added/removed lines between the original content and the model proposal. |
+| Applied lines | Added/removed lines between the original content and the final applied content. |
+| File scope | Only successful native `read_file`, `write_file` and `edit`; Shell and MCP internals are not inferred. |
+
+Line counts accumulate per operation, including repeated edits; they are not a Git diff and undo does not subtract earlier work. Explicit zero, unchanged content, unmeasured content and missing evidence remain distinguishable. Background Shell/Monitor measurements cover only their launch invocation. Execution duration is not presented as end-to-end invocation latency, and timestamps from separate processes are never subtracted.
+
+The Harness compares the persisted model request with the persisted dispatch input and records bounded proposal provenance (`original / modified / unknown`). If a Hook or approval changed the input, or the original input cannot be proven, model line counts are not exposed as measured samples. Applied line counts and execution duration remain available. No original content is copied into diagnostic evidence.
+
+## Runtime protocol
+
+Tool v2/v3 remain closed and unchanged. Tool v4 exposes `execute`, `status`, `cancel` and `acknowledge` under `/internal/managed-runtime/v4/`. Its request selects an underlying execution protocol, 2 or 3. Its response wraps that original result with `managed-tool-measurements/1` and a canonical digest of the complete result and measurement packet.
+
+The packet contains only a nullable execution duration, a physical execution outcome and a nullable native-file measurement. File measurements contain operation/status and nullable model/applied line counts. They contain no paths, file contents, patches or Skill text. Parsing is closed, integers are bounded and the reference packet is bounded to 4 KiB.
+
+A new attested ordinary Workspace capability explicitly advertises v4. Existing ordinary, extension, resident and CSI digests retain their meanings. Protocol selection happens before dispatch. A lost v4 reply is followed by a status query for the same execution; it never falls back to a v2 re-execution.
+
+The Worker freezes result and measurement together before settlement. The Broker validates the canonical digest and persists the combined result using its existing result/version CAS before ACK. A v3 publication that settles before the measurement arrives is reconciled against the same original execution and result. ACK carries the original frozen digest; v3 capture, manifest, publication receipt and reclamation rules are preserved.
+
+## Existing activity projection path
+
+```mermaid
+flowchart LR
+ W[Executor result and measurements] --> B[Broker durable result]
+ B --> H[Hosted completion writer]
+ H --> J[tool.measured source event]
+ J --> P[Existing ActivityProjection]
+ P --> Q[Existing authorized activity queries]
+ Q --> U[Existing activity view]
+```
+
+A dedicated `tool.measured` source event references the original `toolOutcomeRef` and an immutable `managed-tool-measurements` resource. The resource binds the packet and frozen result digest to the execution call and existing invocation identity. This is source evidence for the existing ActivityProjection, not another collector or projection domain.
+
+Some v3 receipts commit during Worker publication, before the v4 result freezes. Enriching those receipts afterward would change their immutable identity and closed publication contract. The separate event preserves both v2/v3 results and all original receipt bytes. It commits before model-result delivery or ACK, including blocked delivery, and does not authorize dispatch, create an invocation, invalidate Context or settle execution. A v2 results-ready checkpoint remains distinct from a receipt.
+
+The Harness freezes packet bytes and the measurement command identity. On recovery it first checks committed evidence; exact replay changes no total and conflicting evidence is rejected. The event and the existing activity updates share one journal transaction and the writer/activation fences. The reducer verifies kind, version, digest, byte bounds, outcome provenance and the existing execution-to-invocation link. File measurements must match the resolved native tool. No database table or public write endpoint is added, and no tool is re-run to improve diagnostics.
+
+After the measurement event commits, later cancellation or delivery recovery may produce a different conversation outcome. Replaying the same frozen measurement retains the original event and outcome reference. A proven Shell completion or native-file effect remains successful execution evidence even if cancellation changes the conversation result.
+
+Aggregates are added to the existing overall tool/file metrics and three file-operation groups. Per-tool buckets retain the baseline's bounded representation; executor measurements remain available in paginated invocation details. This avoids multiplying the retained bucket payload while preserving cumulative measured totals.
+
+Measurement fields are added lazily, only when measurement evidence exists. Unmeasured activity-v1 and CSI `/3` records must preserve their previous projection bytes. Internal references and digests are not exposed in activity responses; public additions contain bounded measurements, source sequence and aggregate decimal-string counters.
+
+## Coverage and presentation
+
+The existing tool/file summaries and invocation details show executor duration, valid sample counts and model/applied line changes. Means divide by measured samples, never by all requests. Missing measurement fields in older rows mean not recorded. Partial coverage is shown when recorded measurements cover only part of the relevant activity, and unsupported profiles do not become zero-valued samples.
+
+The measurements reuse the activity query's authorization, passive reads, pagination and `no-store` behavior. No Runtime attach, MCP discovery or filesystem read is added to queries. The existing child invocation tree and its authorization limits are unchanged.
+
+## Compatibility and rollout
+
+The new source event requires Session storage floor 5, HTTP reader version 5 and native reader capability `managed-session/3`, distinct from the database schema version. Promotion depends on the committed measurement event/reference, including replay of an already stored resource, not merely on newly uploaded bytes. New readers deploy before measurement producers; older readers cannot acquire a floor-5 Session.
+
+Set `QWEN_MANAGED_AGENT_RUNTIME_TOOL_MEASUREMENTS=true` (default `false`) to select the measured profile for newly resolved ordinary bindings after compatible readers and Workers deploy. Existing bindings keep their attested capability. Measurement commits also require the baseline activity evidence and a storage-version-5 grant; there is no second collector switch.
+
+CSI `/1`, `/2` and `/3` keep their existing storage support of 1, 3 and 4. CSI `/3` activity evidence remains supported; this increment adds no CSI executor measurement capability. An ordinary measurement profile must not widen extension/resident/CSI admission. Resource snapshots, recovery and permanent deletion retain the new reference closure through the existing mechanisms.
+
+## Implementation boundaries
+
+| Layer | Increment |
+| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
+| Core | Bounded measurement contract/fixture, measurement event/reference and reference/version validation. |
+| Runtime and Broker | Attested v4 wrapper, monotonic/native-file measurements, frozen result persistence, status/cancel/ACK recovery. |
+| Hosted | Carry Broker measurements into live and recovered completion writers and release transient measurement state after durable commit. |
+| Server | Extend the existing activity reducer, storage/reader gates, snapshot closure and existing activity response schemas. |
+| WebShell | Extend current summaries/details and bilingual labels; regenerate API types. |
+
+## Verification and acceptance
+
+Shared TypeScript/Java fixtures cover integer/null/status validation and canonical digests, including UTF-8. Focused tests cover duplicate requests/results, v4 status/cancel/ACK, publication-before-measurement races, output failure after filesystem success, model/final content differences, unchanged files and legacy compatibility. Measurement binding, replay, counter replacement and snapshot closure require transactional tests.
+
+The test engineer runs the relevant packaged Worker path and browser presentation checks using the existing test-script fallback where no public CLI command exposes an internal contract. Presentation fixtures do not replace real Worker/Broker, MySQL or recovery acceptance. Root build/typecheck/bundle, affected TypeScript tests, API/SurfaceRegistry validation and generated-type checks remain required.
+
+Java verification continues the existing ten-minute ledger; it is not reset for this integration. Focused checks consume only the remaining budget. Full Java, MySQL, Hosted/fault, native CSI and SpotBugs gates remain in CI, and any unexecuted real path is reported explicitly. Success means one frozen execution contributes once, offline activity retains known measurements, and legacy/unmeasured data remains clearly distinguishable.
diff --git a/docs/design/2026-11-07-managed-execution-diagnostics.zh-CN.md b/docs/design/2026-11-07-managed-execution-diagnostics.zh-CN.md
new file mode 100644
index 00000000000..6e1b811a9aa
--- /dev/null
+++ b/docs/design/2026-11-07-managed-execution-diagnostics.zh-CN.md
@@ -0,0 +1,90 @@
+# Managed Tool v4 执行方测量
+
+[English](2026-11-07-managed-execution-diagnostics.md) | [简体中文](2026-11-07-managed-execution-diagnostics.zh-CN.md)
+
+## 状态与基线
+
+作者已选择以 #16499 为基线收敛 PR #16498,本设计据此缩小范围。前置仍为 #16475,初始整合基线是 `d886d0640a623928cea4507a413a1f1899bb1e8a`。实现状态与最终 CI 验收分别记录。
+
+基线负责调用身份、activity evidence v1、SQL 增量投影、授权 activity 查询、统计面板及子代理调用树。本增量扩展这些路径,不恢复此前的 `execution_diagnostics` domain、第二套 collector、`/stats.execution` 或另一张关系图。
+
+## 剩余行为
+
+普通 Hosted Runtime 可以报告实际执行耗时及原生文件变更测量。Runtime 关闭及重放后,现有 activity 视图仍需保留这些信息。文件系统操作已成功时,后续格式化、输出限制或交付失败不能抹去成功证据。
+
+| 测量 | 含义 |
+| -------- | ---------------------------------------------------------------------------------- |
+| 执行耗时 | 执行方用单调时钟围绕实际调用测得的非负整数,不包含审批、排队和结果发布。 |
+| 模型行数 | 原内容与模型提议之间的增删行。 |
+| 实际行数 | 原内容与最终应用内容之间的增删行。 |
+| 文件范围 | 仅成功的原生 `read_file`、`write_file`、`edit`,不推断 Shell 或 MCP 内部文件行为。 |
+
+行数按操作累计,包括重复编辑;不表示 Git diff,undo 不扣减此前工作。明确的零、无变化、未测量及证据缺失保持可区分。后台 Shell/Monitor 只测量启动调用。执行耗时不冒充完整调用响应延迟,也不将不同进程的时间戳相减。
+
+Harness 比较已持久化的模型请求与 dispatch 输入,记录有界的提议来源(`original / modified / unknown`)。Hook 或审批修改了输入,或无法证明原始输入时,模型行数不作为确定的测量样本公开。实际应用行数与执行耗时仍可用。诊断证据不复制原始内容。
+
+## Runtime 协议
+
+Tool v2/v3 保持闭合且不变。Tool v4 在 `/internal/managed-runtime/v4/` 下提供 `execute`、`status`、`cancel`、`acknowledge`。请求选择底层执行协议 2 或 3;响应封装原结果、`managed-tool-measurements/1` 及完整结果与测量包的规范化摘要。
+
+测量包只包含可空执行耗时、物理执行结果及可空原生文件测量。文件测量包括 operation/status 和可空模型/实际增删行,不携带路径、文件内容、patch 或 Skill 正文。解析保持闭合,整数有界,引用包限制为 4 KiB。
+
+新的经过 attestation 的普通 Workspace capability 显式声明 v4。既有 ordinary、extension、resident 和 CSI digest 保持原义。dispatch 前确定协议;丢失 v4 响应后查询同一 execution 的状态,不降级为 v2 重新执行。
+
+Worker 在 settled 前一起冻结结果和测量。Broker 校验规范化摘要,使用现有结果/version CAS 持久化完整结果后再 ACK。v3 publication 先于测量 settled 时,仍按原 execution 和结果核对归并。ACK 携带原冻结摘要;v3 capture、manifest、publication receipt 及回收规则保持不变。
+
+## 现有 activity 投影路径
+
+```mermaid
+flowchart LR
+ W[执行方结果与测量] --> B[Broker 持久结果]
+ B --> H[Hosted 完成 writer]
+ H --> J[tool.measured 来源事件]
+ J --> P[现有 ActivityProjection]
+ P --> Q[现有授权 activity 查询]
+ Q --> U[现有 activity 视图]
+```
+
+专用 `tool.measured` 来源事件引用原 `toolOutcomeRef` 和不可变 `managed-tool-measurements` 资源。资源将测量包与冻结结果摘要绑定到 execution call 和已有 invocation 身份。这是现有 ActivityProjection 的来源证据,不是另一个 collector 或投影 domain。
+
+部分 v3 receipt 在 Worker 发布期间提交,早于 v4 结果冻结。事后增加字段会改变其不可变身份和闭合发布契约。独立事件保留 v2/v3 结果及全部原 receipt 字节,在模型结果交付或 ACK 前提交,也覆盖交付受阻的情况。它不授权 dispatch、不创建 invocation、不使 Context 失效,也不推动执行完成。v2 的 results-ready checkpoint 仍与 receipt 区分。
+
+Harness 冻结测量包字节和 command 身份;恢复时先检查已提交证据。精确重放不改变总数,证据冲突则拒绝。事件与现有 activity 更新共享 journal 原子事务和 writer/activation fence。reducer 校验 kind、version、digest、字节上限、outcome 来源和已有 execution-to-invocation 关联;文件测量必须匹配实际解析的原生工具。不增加数据库表或公开写入入口,也不为改善诊断重新执行工具。
+
+测量事件提交后,取消或交付恢复可以产生不同的对话结果。相同冻结测量的重放保留原事件及 outcome 引用。即使取消改变对话返回,已证明完成的 Shell 或原生文件副作用仍保留成功执行证据。
+
+聚合加入现有工具/文件总览和三种文件操作分组。按工具分桶保持基线的有界表示,执行方测量仍可在分页调用详情中读取。这样保留累计测量总数,同时避免给每个保留桶扩充测量负载。
+
+仅在存在测量证据时惰性增加测量字段。未测量的 activity-v1 和 CSI `/3` 记录必须保留此前投影字节。activity 响应不公开内部引用和摘要,新增公开字段只包含有界测量、来源 sequence 和十进制字符串聚合计数。
+
+## 覆盖与展示
+
+现有工具/文件概要及调用详情展示执行耗时、有效样本数和模型/实际增删行。平均值以测量样本数为分母,不使用全部请求数。旧记录缺少测量字段表示未记录;测量只覆盖部分 activity 时显示部分覆盖,不支持的 profile 不产生值为零的样本。
+
+测量复用 activity 查询的授权、被动读取、分页和 `no-store` 语义。查询不新增 Runtime attach、MCP discovery 或文件系统读取。既有子代理调用树及其授权限制不变。
+
+## 兼容与发布
+
+新的来源事件要求 Session storage floor 5、HTTP reader version 5 和原生 reader capability `managed-session/3`,与数据库 schema 版本区分。升级条件依据已提交的测量事件/reference,包括重放已存储资源,不只检查本次新上传的字节。先部署新 reader,再开启测量 producer;旧 reader 不能接管 floor-5 Session。
+
+兼容 reader 和 Worker 部署后,设置 `QWEN_MANAGED_AGENT_RUNTIME_TOOL_MEASUREMENTS=true`(默认 `false`),为新解析的普通 binding 选择测量 profile。已有 binding 保留 attested capability。测量提交还要求基线 activity evidence 和 storage-version-5 grant,不增加第二个 collector 开关。
+
+CSI `/1`、`/2`、`/3` 分别保留既有 storage 1、3、4 支持。CSI `/3` 的 activity evidence 继续可用,本增量不增加 CSI 执行方测量能力。普通测量 profile 不得放宽 extension/resident/CSI 准入。资源快照、恢复及永久删除沿用现有机制,保留新增引用闭包。
+
+## 实现边界
+
+| 层次 | 增量 |
+| ----------------- | ------------------------------------------------------------------------------------- |
+| Core | 有界测量契约/fixture,测量事件/引用及引用/version 校验。 |
+| Runtime 与 Broker | attested v4 封装,单调时钟/原生文件测量,冻结结果持久化,status/cancel/ACK 恢复。 |
+| Hosted | 将 Broker 测量接入实时及恢复完成 writer,持久提交后释放临时测量状态。 |
+| Server | 扩展现有 activity reducer、storage/reader 门禁、快照闭包及既有 activity 响应 schema。 |
+| WebShell | 扩展现有概要/详情和中英文文案,重新生成 API 类型。 |
+
+## 验证与验收
+
+TypeScript/Java 共享 fixture 覆盖整数/null/status 校验和包含 UTF-8 的规范化摘要。定向测试覆盖重复请求/结果、v4 status/cancel/ACK、publication 先于测量完成的竞态、文件成功后的输出失败、模型/最终内容不同、无变化文件及旧版本兼容。测量绑定、重放、贡献替换和快照闭包需要事务测试。
+
+test-engineer 运行相关打包 Worker 路径和浏览器展示检查;内部契约无公开 CLI 命令时采用既有 test-script 回退。展示 fixture 不替代真实 Worker/Broker、MySQL 或恢复验收。根 build/typecheck/bundle、受影响 TypeScript 测试、API/SurfaceRegistry 校验和生成类型检查继续为必需项。
+
+Java 验证继续使用既有十分钟账本,不因本次整合重置。定向检查只消耗剩余预算。完整 Java、MySQL、Hosted/fault、native CSI 和 SpotBugs 门禁交给 CI,未运行的真实路径明确报告。成功标准为:同一次冻结执行只贡献一次,离线 activity 保留已知测量,旧数据/未测量数据仍能清楚区分。
diff --git a/docs/users/managed-agent-configuration-reference.md b/docs/users/managed-agent-configuration-reference.md
index 96e2ede8f83..805173b30bc 100644
--- a/docs/users/managed-agent-configuration-reference.md
+++ b/docs/users/managed-agent-configuration-reference.md
@@ -262,6 +262,7 @@ The embedded Runtime Broker: its private HTTP listener for the Hosted Harness, t
| `static-runtime-instance-id` | — | string | `standalone-runtime` | The Runtime instance ID of the static Runtime worker's lease; the worker's attestation must report the same value. A non-empty string of at most 512 characters. Read only when `qwen.managed-agent.runtime-broker.provisioner` is `static`. |
| `static-token` | `QWEN_MANAGED_AGENT_STATIC_RUNTIME_TOKEN` | string | *(empty)* | The bearer token the Broker presents to the static Runtime worker. A secret; saved bindings store it encrypted with the Runtime credential key (`qwen.managed-agent.runtime-broker.credential-key`). Required when `qwen.managed-agent.runtime-broker.provisioner` is `static`; at most 512 characters. |
| `token` | `QWEN_MANAGED_AGENT_RUNTIME_BROKER_TOKEN` | string | *(empty)* | The bearer token the Hosted Harness presents to the embedded Runtime Broker's private listener; it also authorizes the loopback-only external storage registration endpoint. A secret. Required when `qwen.managed-agent.runtime-broker.enabled` is true; at most 512 characters. It can come from `qwen.managed-agent.harness.credentials-file` (`QWEN_MANAGED_RUNTIME_BROKER_TOKEN`) instead; setting it in both places is refused at startup. |
+| `tool-measurements` | `QWEN_MANAGED_AGENT_RUNTIME_TOOL_MEASUREMENTS` | boolean | `false` | Whether newly resolved ordinary Workspace bindings select the attested managed-workspace-execution/2 profile and Tool v4 numeric execution/file measurements. Defaults to false; existing bindings, extension/MCP owners, resident and CSI profiles retain their contracts. Deploy compatible Worker/Broker readers first. Measurement commits reuse existing activity evidence and require a storage-version-5 grant; no separate collector flag is introduced. See the [execution diagnostics design](../design/2026-11-07-managed-execution-diagnostics.md). |
| `trusted-local-reboot-recovery` | `QWEN_MANAGED_AGENT_RUNTIME_TRUSTED_LOCAL_REBOOT_RECOVERY` | boolean | `true` | Whether, after a whole-host reboot on Linux, the Broker treats the changed kernel boot ID as proof that the original local workers stopped, and clears the holders of their saved Runtime bindings once every execution is terminal. It scans 8 saved bindings every 5 seconds and never starts a replacement worker or replays an execution. Requires `qwen.managed-agent.runtime-broker.durable-local-process` and the `local-process` provisioner; startup refuses it otherwise, on macOS and with resident Workspaces. |
| `v3-result-window` | `QWEN_MANAGED_AGENT_RUNTIME_BROKER_V3_RESULT_WINDOW` | duration | `30m` | How long the Runtime Broker keeps waiting for the worker's result of a dispatched Shell or Monitor execution that publishes its output (Tool v3, with `qwen.managed-agent.tool-publication.enabled`) before marking it `UNKNOWN`; a value shorter than the longest such call turns that call into `UNKNOWN`. Other tool executions do not use this window. A value above `30m` buys nothing on the shipped path: the Hosted Harness stops waiting for a Tool v3 execution after its own fixed 30 minutes. A suffix-less number binds as seconds. Startup refuses a value below `1s`. |
| `verified-workspace-recovery-enabled` | `QWEN_MANAGED_AGENT_RUNTIME_VERIFIED_WORKSPACE_RECOVERY_ENABLED` | boolean | `false` | Whether LOCAL Workspace execution is gated on a verified physical storage registration: before tools, file access or child Git run, the server checks that the Workspace root still matches its registered identity and marker, and otherwise refuses with `409 workspace_unavailable`. Required by `qwen.managed-agent.runtime-broker.workspace-creation-root` and `qwen.managed-agent.runtime-broker.local-directory-import-enabled`; without `qwen.managed-agent.runtime-broker.workspace-mounts` it also satisfies the Workspace source that `qwen.managed-agent.harness.workspace-files-enabled` needs. Startup refuses it with external provisioners and resident Workspaces. |
diff --git a/docs/users/managed-agent-configuration-reference.zh-CN.md b/docs/users/managed-agent-configuration-reference.zh-CN.md
index 4adaf030151..5d7568dec8d 100644
--- a/docs/users/managed-agent-configuration-reference.zh-CN.md
+++ b/docs/users/managed-agent-configuration-reference.zh-CN.md
@@ -262,6 +262,7 @@ Workspace 扩展包的发布与管理,以及哪些 actor 有权执行这些操
| `static-runtime-instance-id` | — | 字符串 | `standalone-runtime` | static Runtime worker 租约中的 Runtime 实例 ID;worker 的证明(attestation)必须回报相同的值。须为非空字符串,最长 512 个字符。仅当 `qwen.managed-agent.runtime-broker.provisioner` 为 `static` 时读取。 |
| `static-token` | `QWEN_MANAGED_AGENT_STATIC_RUNTIME_TOKEN` | 字符串 | *(空)* | Broker 访问 static Runtime worker 时出示的 Bearer 令牌,属于密钥;已保存的绑定用 Runtime 凭据密钥(`qwen.managed-agent.runtime-broker.credential-key`)加密存储它。`qwen.managed-agent.runtime-broker.provisioner` 为 `static` 时必填,最长 512 个字符。 |
| `token` | `QWEN_MANAGED_AGENT_RUNTIME_BROKER_TOKEN` | 字符串 | *(空)* | Hosted Harness 访问内嵌 Runtime Broker 私有监听端口时出示的 Bearer 令牌;它也用于授权仅限回环地址的外部存储登记接口。属于密钥。`qwen.managed-agent.runtime-broker.enabled` 为 true 时必填,最长 512 个字符。也可以改由 `qwen.managed-agent.harness.credentials-file` 中的 `QWEN_MANAGED_RUNTIME_BROKER_TOKEN` 提供;两处同时设置时启动时拒绝。 |
+| `tool-measurements` | `QWEN_MANAGED_AGENT_RUNTIME_TOOL_MEASUREMENTS` | 布尔 | `false` | 是否让新解析的普通 Workspace binding 选择经过 attestation 的 managed-workspace-execution/2 profile 和 Tool v4 数值执行/文件测量。默认 false;已有 binding、extension/MCP owner、resident 和 CSI profile 保留原契约。先部署兼容 Worker/Broker reader。测量提交复用现有 activity evidence,并要求 storage grant 支持 version 5;不引入独立 collector 开关。参见[执行诊断设计](../design/2026-11-07-managed-execution-diagnostics.zh-CN.md)。 |
| `trusted-local-reboot-recovery` | `QWEN_MANAGED_AGENT_RUNTIME_TRUSTED_LOCAL_REBOOT_RECOVERY` | 布尔 | `true` | Linux 主机整机重启后,Broker 是否把内核 boot ID 的变化视为原本地 worker 已停止的证据,并在所有执行都已终结后清除这些已保存 Runtime 绑定的持有者。它每 5 秒扫描 8 个已保存的绑定,从不启动替代 worker,也不重放执行。要求 `qwen.managed-agent.runtime-broker.durable-local-process` 和 `local-process` provisioner;不满足条件、在 macOS 上或配置了常驻 Workspace 时启动时拒绝。 |
| `v3-result-window` | `QWEN_MANAGED_AGENT_RUNTIME_BROKER_V3_RESULT_WINDOW` | 时长 | `30m` | Runtime Broker 等待 worker 返回已派发的、会发布输出的 Shell 或 Monitor 执行(Tool v3,开启 `qwen.managed-agent.tool-publication.enabled` 时使用)结果的最长时间,超时后把该执行标记为 `UNKNOWN`;若短于最长的此类调用,该调用会变成 `UNKNOWN`。其他工具执行不使用此窗口。大于 `30m` 在随发行版提供的执行路径上没有作用:Hosted Harness 自身等待 Tool v3 执行的期限固定为 30 分钟。不带单位的数字按秒解析。低于 `1s` 时启动时拒绝。 |
| `verified-workspace-recovery-enabled` | `QWEN_MANAGED_AGENT_RUNTIME_VERIFIED_WORKSPACE_RECOVERY_ENABLED` | 布尔 | `false` | LOCAL Workspace 的执行是否以已验证的物理存储登记为前提:在工具、文件访问或子 Git 操作运行前,服务端检查 Workspace 根目录是否仍与登记的身份和标记一致,否则以 `409 workspace_unavailable` 拒绝。`qwen.managed-agent.runtime-broker.workspace-creation-root` 和 `qwen.managed-agent.runtime-broker.local-directory-import-enabled` 都依赖它;未配置 `qwen.managed-agent.runtime-broker.workspace-mounts` 时,它也满足 `qwen.managed-agent.harness.workspace-files-enabled` 对 Workspace 来源的要求。与外部 provisioner 或常驻 Workspace 同时使用时启动时拒绝。 |
diff --git a/packages/cli/src/serve/hosted-runtime-recovery.ts b/packages/cli/src/serve/hosted-runtime-recovery.ts
index 7ce85617195..0e218561817 100644
--- a/packages/cli/src/serve/hosted-runtime-recovery.ts
+++ b/packages/cli/src/serve/hosted-runtime-recovery.ts
@@ -5,6 +5,7 @@
*/
import { randomUUID } from 'node:crypto';
+import { recordManagedToolMeasurements } from '@qwen-code/qwen-code-core/managed-runtime/managed-tool-measurements.js';
import { parseManagedConfigInstallRecord } from '@qwen-code/qwen-code-core/managed-runtime/managed-config-install.js';
import { expandHostedExtensionCommand } from './hosted-extension-command.js';
import { isDeepStrictEqual } from 'node:util';
@@ -576,6 +577,13 @@ export async function settleParkedTurnCancelled(input: {
'managed-tool-outcome',
outcomeBytes(item, parts),
));
+ await recordManagedToolMeasurements(
+ input.session,
+ item.executionCallId,
+ outcomeRef,
+ input.broker?.toolMeasurements(item.executionCallId),
+ );
+ input.broker?.releaseToolMeasurements(item.executionCallId);
// The assistant's functionCall must meet its functionResponse in the next
// turn's history, or providers reject the request as malformed.
if (!journaled.has(item.functionCallId)) {
@@ -3007,6 +3015,13 @@ export async function recoverHostedRuntimeTurn(input: {
'managed-tool-outcome',
outcome,
);
+ await recordManagedToolMeasurements(
+ session,
+ item.executionCallId,
+ outcomeRef,
+ broker.toolMeasurements(item.executionCallId),
+ );
+ broker.releaseToolMeasurements(item.executionCallId);
if (!journaled.has(item.functionCallId)) {
await writeRecoveryRecord(session, record);
}
diff --git a/packages/cli/src/serve/hosted-workspace-broker.test.ts b/packages/cli/src/serve/hosted-workspace-broker.test.ts
index 8a8a92cfb11..0f420cb22cd 100644
--- a/packages/cli/src/serve/hosted-workspace-broker.test.ts
+++ b/packages/cli/src/serve/hosted-workspace-broker.test.ts
@@ -60,6 +60,44 @@ it('resolves and pins Harness capability once without acquiring or warming a Run
expect(broker.runtime).toBeUndefined();
});
+it('retains frozen measurements observed by a cancellation status poll', async () => {
+ const metadata = {
+ protocolVersion: 4,
+ executionProtocol: 2,
+ resultDigest: 'a'.repeat(64),
+ measurements: {
+ protocol: 'managed-tool-measurements/1',
+ executionDurationMs: 0,
+ executionOutcome: 'success',
+ file: null,
+ },
+ };
+ const broker = await fixture(() => ({
+ body: {
+ ...identity,
+ executionCallId: 'execution',
+ status: {
+ state: 'settled',
+ result: {
+ executionStatus: 'success',
+ responseParts: [],
+ diagnostics: metadata,
+ },
+ },
+ },
+ }));
+ expect(await broker.status('execution')).toEqual({
+ state: 'settled',
+ executionStatus: 'success',
+ });
+ expect(broker.toolMeasurements('execution')).toEqual({
+ resultDigest: metadata.resultDigest,
+ measurements: metadata.measurements,
+ });
+ broker.releaseToolMeasurements('execution');
+ expect(broker.toolMeasurements('execution')).toBeUndefined();
+});
+
it('reads a fresh saved Workspace context after capabilities were cached', async () => {
let generation = 0;
const broker = await fixture(() => ({
diff --git a/packages/cli/src/serve/hosted-workspace-broker.ts b/packages/cli/src/serve/hosted-workspace-broker.ts
index 269a081dae0..f1bf8611ac3 100644
--- a/packages/cli/src/serve/hosted-workspace-broker.ts
+++ b/packages/cli/src/serve/hosted-workspace-broker.ts
@@ -22,9 +22,14 @@ import type {
import { parseToolResultEnvelope } from '@qwen-code/qwen-code-core/managed-runtime/managed-tool-result.js';
import type { LocalShellReceipt } from '@qwen-code/qwen-code-core/managed-runtime/local-shell-result-session.js';
import type { ManagedToolResultPayload } from './managed-runtime-tool-executor.js';
+import {
+ parseManagedToolMeasurements,
+ type ManagedToolMeasurements,
+} from '@qwen-code/qwen-code-core/managed-runtime/managed-tool-measurements.js';
import { resolveManagedRuntimeBrokerBaseUrl } from './managed-runtime-broker-url.js';
import {
WORKSPACE_CAPABILITY_DIGEST,
+ MEASURED_WORKSPACE_CAPABILITY_DIGEST,
RESIDENT_WORKSPACE_CAPABILITY_DIGEST,
} from './managed-workspace-activation.js';
import type {
@@ -135,6 +140,43 @@ export class HostedWorkspaceBroker {
private readonly baseUrl: URL;
private leaseAcquired = false;
private capability?: string;
+ private readonly measuredResults = new Map<
+ string,
+ { measurements: ManagedToolMeasurements | null; resultDigest: string }
+ >();
+
+ releaseToolMeasurements(id: string): void {
+ this.measuredResults.delete(id);
+ }
+
+ toolMeasurements(id: string) {
+ return this.measuredResults.get(id);
+ }
+
+ private readToolResult(id: string, value: unknown): Record {
+ const result = object(value);
+ const { diagnostics, ...base } = result;
+ if (diagnostics !== undefined) {
+ const metadata = object(diagnostics);
+ if (
+ metadata['protocolVersion'] !== 4 ||
+ ![2, 3].includes(metadata['executionProtocol'] as number) ||
+ Object.keys(metadata).sort().join(',') !==
+ 'executionProtocol,measurements,protocolVersion,resultDigest' ||
+ typeof metadata['resultDigest'] !== 'string' ||
+ !/^[0-9a-f]{64}$/.test(metadata['resultDigest'])
+ )
+ throw new Error('Invalid measured tool result.');
+ this.measuredResults.set(id, {
+ measurements:
+ metadata['measurements'] === null
+ ? null
+ : parseManagedToolMeasurements(metadata['measurements']),
+ resultDigest: metadata['resultDigest'],
+ });
+ }
+ return base;
+ }
private capabilityResolution?: Promise;
provisionerKind?: string;
private readonly identity: {
@@ -335,6 +377,7 @@ export class HostedWorkspaceBroker {
scope['tenantId'] !== this.key.tenantId ||
scope['workspaceId'] !== this.key.workspaceId ||
(scope['capabilityDigest'] !== WORKSPACE_CAPABILITY_DIGEST &&
+ scope['capabilityDigest'] !== MEASURED_WORKSPACE_CAPABILITY_DIGEST &&
scope['capabilityDigest'] !==
MANAGED_EXTENSION_LINUX_CAPABILITY_DIGEST &&
scope['capabilityDigest'] !==
@@ -412,6 +455,9 @@ export class HostedWorkspaceBroker {
promptId: turnId,
callId,
argsDigest: digest,
+ ...(this.capability === MEASURED_WORKSPACE_CAPABILITY_DIGEST
+ ? { resultProtocol: 4 }
+ : {}),
...(inputDigest ? { runtimeProtocol: 3, inputDigest } : {}),
},
};
@@ -481,7 +527,13 @@ export class HostedWorkspaceBroker {
const status = object(response['status']);
if (status['state'] === 'prepared') return { executionCallId: id };
if (status['state'] === 'settled') {
- return { executionCallId: id, settled: settledResult(status) };
+ return {
+ executionCallId: id,
+ settled: settledResult({
+ ...status,
+ result: this.readToolResult(id, status['result']),
+ }),
+ };
}
throw new Error(
'Hosted Workspace Broker answered a reservation that is neither fresh nor settled.',
@@ -516,6 +568,9 @@ export class HostedWorkspaceBroker {
promptId: turnId,
callId,
argsDigest,
+ ...(this.capability === MEASURED_WORKSPACE_CAPABILITY_DIGEST
+ ? { resultProtocol: 4 }
+ : {}),
},
});
const executionCallId = response['executionCallId'];
@@ -576,7 +631,7 @@ export class HostedWorkspaceBroker {
throw new Error('Tool v3 execution identity changed.');
const status = object(response['status']);
if (status['state'] === 'settled') {
- const result = object(status['result']);
+ const result = this.readToolResult(id, status['result']);
if (
!['success', 'error', 'cancelled', 'not_started'].includes(
String(result['executionStatus']),
@@ -704,7 +759,7 @@ export class HostedWorkspaceBroker {
throw new Error('Runtime execution identity changed.');
const status = object(response['status']);
if (status['state'] === 'settled') {
- const result = object(status['result']);
+ const result = this.readToolResult(id, status['result']);
if (
!['success', 'error', 'cancelled', 'not_started'].includes(
String(result['executionStatus']),
@@ -754,7 +809,13 @@ export class HostedWorkspaceBroker {
const status = object(response['status']);
const state = String(status['state']);
return state === 'settled'
- ? { state, settled: settledResult(status) }
+ ? {
+ state,
+ settled: settledResult({
+ ...status,
+ result: this.readToolResult(id, status['result']),
+ }),
+ }
: { state };
}
@@ -953,6 +1014,14 @@ export class HostedWorkspaceBroker {
)
throw new Error('Runtime execution outcome is unknown.');
const result = status['result'];
+ if (
+ state === 'settled' &&
+ result &&
+ typeof result === 'object' &&
+ !Array.isArray(result)
+ ) {
+ this.readToolResult(id, result);
+ }
const executionStatus =
state === 'settled' &&
typeof result === 'object' &&
@@ -1002,7 +1071,7 @@ export class HostedWorkspaceBroker {
'Original Shell result is not settled under its binding.',
);
}
- const result = object(status['result']);
+ const result = this.readToolResult(id, status['result']);
if (!('capture' in result)) {
const error = 'error' in result ? object(result['error']) : undefined;
if (
diff --git a/packages/cli/src/serve/hosted-workspace-tool-turn.test.ts b/packages/cli/src/serve/hosted-workspace-tool-turn.test.ts
index cebb7412ba6..f8444171af1 100644
--- a/packages/cli/src/serve/hosted-workspace-tool-turn.test.ts
+++ b/packages/cli/src/serve/hosted-workspace-tool-turn.test.ts
@@ -97,12 +97,15 @@ import {
} from '@qwen-code/qwen-code-core/managed-runtime/managed-execution-diagnostics.js';
import {
RESIDENT_WORKSPACE_CAPABILITY_DIGEST,
+ MEASURED_WORKSPACE_CAPABILITY_DIGEST,
WORKSPACE_CAPABILITY_DIGEST,
} from './managed-workspace-activation.js';
const broker = vi.hoisted(() => ({
capabilityDigest:
'sha256:7df3981fa14b397fe09f6ba84ed0c096c71902394da667bf47f3b315866e7f91',
+ toolMeasurements: vi.fn(),
+ releaseToolMeasurements: vi.fn(),
fileHistory: vi.fn(),
authorizeLifecycle: vi.fn(),
workspaceContext: vi.fn(),
@@ -140,6 +143,8 @@ vi.mock('./hosted-workspace-broker.js', async (importOriginal) => ({
// request leaves, so a lost reply still names it.
private claim?: string;
mountClaim = () => this.claim;
+ toolMeasurements = broker.toolMeasurements;
+ releaseToolMeasurements = broker.releaseToolMeasurements;
fileHistory = broker.fileHistory;
authorizeLifecycle = broker.authorizeLifecycle;
workspaceContext = broker.workspaceContext;
@@ -546,6 +551,61 @@ it.each([
},
);
+it('persists frozen executor measurements before model result delivery fails', async () => {
+ await enableManagedExecutionDiagnostics(session);
+ broker.capabilityDigest = MEASURED_WORKSPACE_CAPABILITY_DIGEST;
+ broker.toolMeasurements.mockReturnValue({
+ resultDigest: 'a'.repeat(64),
+ measurements: {
+ protocol: 'managed-tool-measurements/1',
+ executionDurationMs: 0,
+ executionOutcome: 'success',
+ file: {
+ operation: 'read',
+ status: 'measured',
+ modelLinesAdded: null,
+ modelLinesRemoved: null,
+ appliedLinesAdded: null,
+ appliedLinesRemoved: null,
+ },
+ },
+ });
+ const original = commit;
+ commit = async (...args) => {
+ if (args[0] === 'tool_result') throw new Error('delivery unavailable');
+ return original(...args);
+ };
+ turn = createTurn();
+ await expect(
+ turn.execute([calls[0]], [parts[0]], 'model', new AbortController().signal),
+ ).rejects.toThrow();
+ const event = session.authority.lastEventOfKind('tool.measured')!;
+ expect(event).toBeDefined();
+ const packet = JSON.parse(
+ (
+ await session.resources.read(
+ assertManagedSessionDurableRef(
+ event.payload['measurementsRef'],
+ 'measurement',
+ ),
+ )
+ ).toString(),
+ );
+ expect(packet.measurements).toMatchObject({
+ executionDurationMs: 0,
+ executionOutcome: 'success',
+ file: { operation: 'read' },
+ });
+ expect(broker.releaseToolMeasurements).toHaveBeenCalledWith(
+ event.payload['executionCallId'],
+ );
+ expect(
+ session.authority.committedReceipt(
+ String(event.payload['executionCallId']),
+ ),
+ ).toBeUndefined();
+});
+
it('refuses a mixed Skill and Workspace batch before approval Hooks or tool dispatch', async () => {
const fire = vi.fn().mockResolvedValue(undefined);
turn = createTurn(
diff --git a/packages/cli/src/serve/hosted-workspace-tool-turn.ts b/packages/cli/src/serve/hosted-workspace-tool-turn.ts
index 28581ae4abc..e1b1ee02af3 100644
--- a/packages/cli/src/serve/hosted-workspace-tool-turn.ts
+++ b/packages/cli/src/serve/hosted-workspace-tool-turn.ts
@@ -24,7 +24,10 @@ import {
waitForHostedQuestion,
} from './hosted-question-wait.js';
import { probeNeverStarted } from './hosted-never-started-reconcile.js';
-import { RESIDENT_WORKSPACE_CAPABILITY_DIGEST } from './managed-workspace-activation.js';
+import {
+ MEASURED_WORKSPACE_CAPABILITY_DIGEST,
+ RESIDENT_WORKSPACE_CAPABILITY_DIGEST,
+} from './managed-workspace-activation.js';
import {
MANAGED_EXTENSION_LINUX_CAPABILITY_DIGEST,
MANAGED_EXTENSION_DEVELOPER_CAPABILITY_DIGEST,
@@ -120,6 +123,7 @@ import {
type ManagedSessionDomain,
type ManagedSessionDurableRef,
} from '@qwen-code/qwen-code-core/managed-runtime/managed-session-records.js';
+import { recordManagedToolMeasurements } from '@qwen-code/qwen-code-core/managed-runtime/managed-tool-measurements.js';
import type { ManagedHarnessHandle } from '@qwen-code/qwen-code-core/managed-runtime/managed-harness-factory.js';
import {
harnessCommandLocked,
@@ -6041,6 +6045,10 @@ export class HostedWorkspaceToolTurn {
? await this.publisher!.receipt(executionCallId, shellResult)
: undefined;
if (receipt?.deliveryStatus === 'blocked') {
+ await this.recordToolMeasurements(
+ executionCallId,
+ receipt.outcomeRef,
+ );
await this.broker.acknowledge(executionCallId, receipt);
throw new Error('Complete Shell output was not admitted.');
}
@@ -6148,6 +6156,7 @@ export class HostedWorkspaceToolTurn {
'managed-tool-outcome',
outcome,
));
+ await this.recordToolMeasurements(executionCallId, outcomeRef);
await this.commit('tool_result', converted, model);
await this.harness.resolveAwaitRuntime(executionCallId, outcomeRef);
if (receipt) await this.broker.acknowledge(executionCallId, receipt);
@@ -6472,6 +6481,7 @@ export class HostedWorkspaceToolTurn {
'managed-tool-outcome',
Buffer.from(JSON.stringify({ executionCallId, ...parts[0] })),
);
+ await this.recordToolMeasurements(executionCallId, outcomeRef);
await this.commit('tool_result', parts, model);
await this.harness.resolveAwaitRuntime(executionCallId, outcomeRef);
}
@@ -6728,6 +6738,7 @@ export class HostedWorkspaceToolTurn {
}
if (!receipt)
throw new Error('Original Monitor watch receipt disappeared.');
+ await this.recordToolMeasurements(executionCallId, ref);
await this.commit('tool_result', converted, model, {
uuid: messageId,
timestamp,
@@ -8452,6 +8463,7 @@ export class HostedWorkspaceToolTurn {
}
if (!receipt)
throw new Error('Original background Shell receipt disappeared.');
+ await this.recordToolMeasurements(executionCallId, ref);
await this.commit('tool_result', converted, model, {
uuid: messageId,
timestamp,
@@ -8473,6 +8485,21 @@ export class HostedWorkspaceToolTurn {
return converted;
}
+ private async recordToolMeasurements(
+ executionCallId: string,
+ outcomeRef: ManagedSessionDurableRef,
+ ): Promise {
+ if (this.broker.capabilityDigest !== MEASURED_WORKSPACE_CAPABILITY_DIGEST)
+ return;
+ await recordManagedToolMeasurements(
+ this.session,
+ executionCallId,
+ outcomeRef,
+ this.broker.toolMeasurements(executionCallId),
+ );
+ this.broker.releaseToolMeasurements(executionCallId);
+ }
+
private async acceptShell(
call: ToolCallRequestInfo,
executionCallId: string,
@@ -8621,6 +8648,7 @@ export class HostedWorkspaceToolTurn {
}
if (!receipt)
throw new Error('Original unstarted Shell receipt disappeared.');
+ await this.recordToolMeasurements(executionCallId, ref);
await this.commit('tool_result', converted, model, {
uuid: messageId,
timestamp,
@@ -8797,6 +8825,7 @@ export class HostedWorkspaceToolTurn {
);
}
if (!receipt) throw new Error('Original Shell receipt disappeared.');
+ await this.recordToolMeasurements(executionCallId, ref);
await this.commit('tool_result', converted, model, {
uuid: messageId,
timestamp,
diff --git a/packages/cli/src/serve/managed-context-worker.ts b/packages/cli/src/serve/managed-context-worker.ts
index 116dcb9ca6b..395bac738f0 100644
--- a/packages/cli/src/serve/managed-context-worker.ts
+++ b/packages/cli/src/serve/managed-context-worker.ts
@@ -72,6 +72,7 @@ import type { ManagedShellPublisherRegistry } from './managed-shell-publisher.js
import { ManagedRuntimeLedger } from './managed-runtime-ledger.js';
import { registerManagedRuntimeToolRoutes } from './managed-runtime-tool-routes.js';
import { registerManagedRuntimeToolV3Routes } from './managed-runtime-tool-v3-routes.js';
+import { registerManagedRuntimeToolV4Routes } from './managed-runtime-tool-v4-routes.js';
import {
MANAGED_RUNTIME_PROVIDER_ROUTE,
ManagedRuntimeProviderProtocolError,
@@ -80,7 +81,7 @@ import { registerManagedRuntimeProviderRoute } from './managed-runtime-provider-
import {
WorkspaceActivations,
WORKSPACE_ACTIVATION_ROUTE,
- WORKSPACE_CAPABILITY_DIGEST,
+ isWorkspaceToolCapability,
WORKSPACE_CONTEXT_CONFIG_REF,
RESIDENT_WORKSPACE_CAPABILITY_DIGEST,
} from './managed-workspace-activation.js';
@@ -385,7 +386,7 @@ export function registerManagedContextRoutes(
if (resident && boot.isolationClass !== 'workspace')
throw new Error('Resident Workspace requires Workspace isolation.');
const requiresActivation =
- boot.capabilityDigest === WORKSPACE_CAPABILITY_DIGEST ||
+ isWorkspaceToolCapability(boot.capabilityDigest) ||
resident ||
extensionConfiguration !== undefined;
const admissionOpen = (): boolean => executor.isAdmissionOpen;
@@ -1071,6 +1072,7 @@ export function registerManagedContextRoutes(
activations.register(app, boot, installations, executor);
registerManagedRuntimeToolRoutes(app, boot, executor);
+ registerManagedRuntimeToolV4Routes(app, boot, executor);
if (publisher) {
registerManagedRuntimeToolV3Routes(app, boot, executor);
}
diff --git a/packages/cli/src/serve/managed-external-storage.ts b/packages/cli/src/serve/managed-external-storage.ts
index 383e481cb8d..2bbfc69a5e6 100644
--- a/packages/cli/src/serve/managed-external-storage.ts
+++ b/packages/cli/src/serve/managed-external-storage.ts
@@ -13,7 +13,7 @@ import {
} from './managed-context-envelope.js';
import { publisherFields } from './managed-shell-publisher.js';
import { realpathDeepestExisting } from './managed-runtime-tool-executor.js';
-import { WORKSPACE_CAPABILITY_DIGEST } from './managed-workspace-activation.js';
+import { isWorkspaceToolCapability } from './managed-workspace-activation.js';
const INVALID = 'Managed Runtime external storage admission failed.';
const directories = new WeakMap();
@@ -91,7 +91,7 @@ export async function readManagedExternalBoot(
if (
boot.isolationClass !== 'session' ||
boot.mountRoot !== storage.mountRoot ||
- boot.capabilityDigest !== WORKSPACE_CAPABILITY_DIGEST
+ !isWorkspaceToolCapability(boot.capabilityDigest)
)
throw new Error(INVALID);
const directory = path.dirname(bootPath);
diff --git a/packages/cli/src/serve/managed-runtime-attestation-worker.ts b/packages/cli/src/serve/managed-runtime-attestation-worker.ts
index 9deff1441d5..0a933bd81d1 100644
--- a/packages/cli/src/serve/managed-runtime-attestation-worker.ts
+++ b/packages/cli/src/serve/managed-runtime-attestation-worker.ts
@@ -45,7 +45,11 @@ import {
} from './managed-extension-runtime.js';
import { MANAGED_EXTENSION_COMMAND_ROUTE } from './managed-extension-command-runtime.js';
import { PUBLICATION_INSTALL_ROUTE } from './remote-shell-result-publication.js';
-import { WORKSPACE_CAPABILITY_DIGEST } from './managed-workspace-activation.js';
+import {
+ isWorkspaceToolCapability,
+ MEASURED_WORKSPACE_CAPABILITY_DIGEST,
+} from './managed-workspace-activation.js';
+import { MANAGED_RUNTIME_TOOL_V4_ROUTES } from './managed-runtime-tool-v4-routes.js';
import {
ManagedShellPublisherRegistry,
MANAGED_SHELL_PUBLISHER_ROUTE,
@@ -384,10 +388,13 @@ export async function startManagedRuntimeAttestationWorker(
boot.version === 2
? capturePublisher ||
remotePublishers ||
- boot.capabilityDigest === WORKSPACE_CAPABILITY_DIGEST ||
+ isWorkspaceToolCapability(boot.capabilityDigest) ||
boot.managedExtensions !== undefined
? [
...MANAGED_CONTEXT_WORKER_ROUTES,
+ ...(boot.capabilityDigest === MEASURED_WORKSPACE_CAPABILITY_DIGEST
+ ? MANAGED_RUNTIME_TOOL_V4_ROUTES
+ : []),
...(boot.managedExtensions
? [
MANAGED_EXTENSION_RUNTIME_ROUTE,
@@ -408,7 +415,7 @@ export async function startManagedRuntimeAttestationWorker(
...(remotePublishers ? [MANAGED_SHELL_PUBLISHER_ROUTE] : []),
...(!capturePublisher &&
!publication &&
- (boot.capabilityDigest === WORKSPACE_CAPABILITY_DIGEST ||
+ (isWorkspaceToolCapability(boot.capabilityDigest) ||
boot.managedExtensions !== undefined)
? [PUBLICATION_INSTALL_ROUTE]
: []),
@@ -689,7 +696,7 @@ export async function runManagedRuntimeAttestationWorker(
undefined,
contextBoot.version === 2 &&
!('storage' in contextBoot) &&
- (contextBoot.capabilityDigest === WORKSPACE_CAPABILITY_DIGEST ||
+ (isWorkspaceToolCapability(contextBoot.capabilityDigest) ||
contextBoot.managedExtensions !== undefined)
? new ManagedShellPublisherRegistry()
: undefined,
diff --git a/packages/cli/src/serve/managed-runtime-tool-executor.ts b/packages/cli/src/serve/managed-runtime-tool-executor.ts
index 9a0d7dd5847..2ca6dbb4b3d 100644
--- a/packages/cli/src/serve/managed-runtime-tool-executor.ts
+++ b/packages/cli/src/serve/managed-runtime-tool-executor.ts
@@ -39,6 +39,11 @@ import {
import { managedToolDigest } from '@qwen-code/qwen-code-core/tools/managed-tool-protocol.js';
import type { ShellToolInvocation } from '@qwen-code/qwen-code-core/tools/shell.js';
import type { ToolResultEnvelope } from '@qwen-code/qwen-code-core/managed-runtime/managed-tool-result.js';
+import type {
+ ManagedToolMeasurements,
+ ManagedFileMeasurement,
+} from '@qwen-code/qwen-code-core/managed-runtime/managed-tool-measurements.js';
+import { getDiffStat } from '@qwen-code/qwen-code-core/tools/diffOptions.js';
import type { LocalShellCaptureRequest } from '@qwen-code/qwen-code-core/managed-runtime/managed-shell-result-session.js';
import type { LocalShellResultCapture } from '@qwen-code/qwen-code-core/managed-runtime/local-shell-result-capture.js';
import type { ToolResultExpectedIdentity } from '@qwen-code/qwen-code-core/managed-runtime/managed-tool-result-store.js';
@@ -57,6 +62,7 @@ import {
} from './managed-runtime-ledger.js';
import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js';
import { getShellConfiguration } from '@qwen-code/qwen-code-core/utils/shell-utils.js';
+import { isShellResultDisplay } from '@qwen-code/qwen-code-core/utils/shell-result.js';
import { getShellContextEnvVars } from '@qwen-code/qwen-code-core/services/shellContextEnv.js';
import type {
AnyDeclarativeTool,
@@ -293,6 +299,8 @@ interface JournalEntry {
state: ManagedToolExecutionState;
lastSequence: number;
result?: ManagedToolResultPayload;
+ measurements?: ManagedToolMeasurements;
+ readonly measure?: true;
v3Result?: ToolResultEnvelope;
readonly v3Capture?: LocalShellCaptureRequest['capture'];
readonly captureSink?: ManagedShellCaptureSink;
@@ -1000,9 +1008,10 @@ export class ManagedToolExecutor {
reference: ManagedToolReference,
toolName: string,
input: Record,
+ measure?: true,
): Promise {
return this.trackStart(() =>
- this.executeAdmitted(reference, toolName, input),
+ this.executeAdmitted(reference, toolName, input, measure),
);
}
@@ -1010,6 +1019,7 @@ export class ManagedToolExecutor {
reference: ManagedToolReference,
toolName: string,
input: Record,
+ measure?: true,
): Promise {
if (
this.options.sshForegroundOnly &&
@@ -1051,7 +1061,7 @@ export class ManagedToolExecutor {
'Managed MCP Session is unavailable.',
);
if (this.mcpCalls.has(reference.callId))
- return this.execute(reference, toolName, input);
+ return this.execute(reference, toolName, input, measure);
const control = parseManagedMcpControl(input);
if (
control.kind !== 'mcp-invoke' ||
@@ -1098,7 +1108,7 @@ export class ManagedToolExecutor {
}
const existing = this.entries.get(reference.callId);
if (existing) {
- if (existing.version !== 2) {
+ if (existing.version !== 2 || existing.measure !== measure) {
throw new ManagedToolConflictError(
'Managed Runtime protocol conflicts.',
);
@@ -1114,7 +1124,7 @@ export class ManagedToolExecutor {
// A concurrent execute of the same call may have journaled it meanwhile.
const joined = this.entries.get(reference.callId);
if (joined) {
- if (joined.version !== 2) {
+ if (joined.version !== 2 || joined.measure !== measure) {
throw new ManagedToolConflictError(
'Managed Runtime protocol conflicts.',
);
@@ -1156,6 +1166,7 @@ export class ManagedToolExecutor {
}
const entry: JournalEntry = {
version: 2,
+ measure,
reference,
toolName,
input,
@@ -1173,6 +1184,7 @@ export class ManagedToolExecutor {
request: LocalShellCaptureRequest & {
readonly toolName: string;
readonly input: Record;
+ readonly measure?: true;
},
): Promise {
return this.trackStart(() => this.executeV3Admitted(request));
@@ -1182,6 +1194,7 @@ export class ManagedToolExecutor {
request: LocalShellCaptureRequest & {
readonly toolName: string;
readonly input: Record;
+ readonly measure?: true;
},
): Promise {
if (this.closing) {
@@ -1222,6 +1235,7 @@ export class ManagedToolExecutor {
if (existing) {
if (
existing.version !== 3 ||
+ existing.measure !== request.measure ||
!sameReference(existing.reference, reference) ||
existing.toolName !== toolName ||
!sameCapture(existing.v3Capture, capture)
@@ -1300,6 +1314,7 @@ export class ManagedToolExecutor {
}
const entry: JournalEntry = {
version: 3,
+ measure: request.measure,
reference,
toolName,
input,
@@ -1328,6 +1343,7 @@ export class ManagedToolExecutor {
request: LocalShellCaptureRequest & {
readonly toolName: string;
readonly input: Record;
+ readonly measure?: true;
},
tools: ManagedToolSet,
normalized: Record,
@@ -1338,6 +1354,7 @@ export class ManagedToolExecutor {
if (this.entries.has(reference.callId)) return this.executeV3(request);
const entry: JournalEntry = {
version: 3,
+ measure: request.measure,
reference,
toolName: request.toolName,
input,
@@ -1348,7 +1365,15 @@ export class ManagedToolExecutor {
controller: new AbortController(),
};
this.entries.set(reference.callId, entry);
+ let executionDurationMs: number | null = null;
const settle = (result: ToolResultEnvelope): ManagedToolV3View => {
+ if (entry.measure)
+ entry.measurements = {
+ protocol: 'managed-tool-measurements/1',
+ executionDurationMs,
+ executionOutcome: result.executionStatus,
+ file: null,
+ };
entry.v3Result = result;
entry.state = 'settled';
entry.lastSequence = 1;
@@ -1464,6 +1489,7 @@ export class ManagedToolExecutor {
}
}
};
+ const launchStarted = entry.measure ? performance.now() : undefined;
try {
process = await this.backgroundSupervisor.start({
unitName,
@@ -1500,7 +1526,19 @@ export class ManagedToolExecutor {
applyPause(true);
},
});
+ executionDurationMs = Math.max(
+ 0,
+ Math.floor(
+ launchStarted === undefined ? 0 : performance.now() - launchStarted,
+ ),
+ );
} catch (cause) {
+ executionDurationMs = Math.max(
+ 0,
+ Math.floor(
+ launchStarted === undefined ? 0 : performance.now() - launchStarted,
+ ),
+ );
return settle({
executionStatus: 'not_started',
responseParts: [],
@@ -1555,6 +1593,7 @@ export class ManagedToolExecutor {
request: LocalShellCaptureRequest & {
readonly toolName: string;
readonly input: Record;
+ readonly measure?: true;
},
tools: ManagedToolSet,
normalized: Record,
@@ -1563,6 +1602,7 @@ export class ManagedToolExecutor {
if (this.entries.has(reference.callId)) return this.executeV3(request);
const entry: JournalEntry = {
version: 3,
+ measure: request.measure,
reference,
toolName: request.toolName,
input,
@@ -1573,7 +1613,15 @@ export class ManagedToolExecutor {
controller: new AbortController(),
};
this.entries.set(reference.callId, entry);
+ let executionDurationMs: number | null = null;
const settle = (result: ToolResultEnvelope): ManagedToolV3View => {
+ if (entry.measure)
+ entry.measurements = {
+ protocol: 'managed-tool-measurements/1',
+ executionDurationMs,
+ executionOutcome: result.executionStatus,
+ file: null,
+ };
entry.v3Result = result;
entry.state = 'settled';
entry.lastSequence = 1;
@@ -1702,6 +1750,7 @@ export class ManagedToolExecutor {
if (!paused && bufferedBytes >= MANAGED_BACKGROUND_OUTPUT_PAUSE_BYTES)
applyPause(true);
};
+ const launchStarted = entry.measure ? performance.now() : undefined;
try {
watchHandle = await this.monitorWatcher.start(
{ command },
@@ -1712,7 +1761,19 @@ export class ManagedToolExecutor {
() => undefined,
{ unitName, cwd: directory, onChunk: acceptChunk },
);
+ executionDurationMs = Math.max(
+ 0,
+ Math.floor(
+ launchStarted === undefined ? 0 : performance.now() - launchStarted,
+ ),
+ );
} catch (cause) {
+ executionDurationMs = Math.max(
+ 0,
+ Math.floor(
+ launchStarted === undefined ? 0 : performance.now() - launchStarted,
+ ),
+ );
return settle({
executionStatus: 'not_started',
responseParts: [],
@@ -1770,6 +1831,14 @@ export class ManagedToolExecutor {
return v3View(entry);
}
+ measurements(
+ reference: ManagedToolReference,
+ ): ManagedToolMeasurements | null {
+ const entry = this.entries.get(reference.callId);
+ if (!entry || !sameReference(entry.reference, reference)) return null;
+ return entry.measurements ? structuredClone(entry.measurements) : null;
+ }
+
cancelV3(reference: ManagedToolReference): ManagedToolV3View {
const entry = this.entries.get(reference.callId);
if (!entry) return { state: 'unknown' };
@@ -2079,6 +2148,7 @@ export class ManagedToolExecutor {
entry.input = undefined;
entry.inputJson = undefined;
entry.result = undefined;
+ entry.measurements = undefined;
entry.state = 'acknowledged';
entry.lastSequence += 1;
return view(entry);
@@ -2242,6 +2312,13 @@ export class ManagedToolExecutor {
/** The Shell's process-group leader, when this call is one with a ledger. */
let shellPgid: number | undefined;
let invocationStarted = false;
+ let executionStartedAt: number | undefined;
+ let executionDurationMs: number | null = null;
+ let fileMeasurement: ManagedFileMeasurement | null = null;
+ let nativeFileSucceeded = false;
+ let physicalOutcome:
+ | ManagedToolMeasurements['executionOutcome']
+ | undefined;
try {
// Runs start only from a fresh journal entry, which still carries its
// input; only an acknowledged entry loses it, and nothing runs that.
@@ -2412,68 +2489,77 @@ export class ManagedToolExecutor {
`Directory '${params['directory']}' is not within any of the registered workspace directories.`,
);
}
- const invoke = () => {
- this.assertAdmissionOpen();
- return sessionIdContext.run(sessionId, () => {
- const invocation = fileInvocation ?? tool.build(params);
- invocationStarted = true;
- if (entry.toolName === ShellTool.Name && tools.runShell)
- return tools.runShell(
- params,
- entry.controller.signal,
- entry.captureSink,
- );
- const ledger = this.options.ledger;
- const trackGroup = (pid: number) => {
- if (!ledger) return;
- // Before any settle step of this call can run, the group is
- // durable for the host's sweeps: the pid leads the group.
- shellPgid = pid;
- try {
- ledger.addGroup({
- pgid: pid,
- callId: entry.reference.callId,
- startedAt: Date.now(),
- });
- } catch (error) {
- this.ledgerIntegrityFailed = true;
- // A group that could not get into the ledger must not
- // outlive the failure: stop it first, then let the call
- // fail loudly instead of settling an outcome nothing
- // recorded.
- signalProcessGroup(pid, 'SIGKILL');
- void ledger.waitForGroupExit(pid, CLOSE_SWEEP_TIMEOUT_MS);
- throw error;
+ const invoke = async () => {
+ try {
+ this.assertAdmissionOpen();
+ return await sessionIdContext.run(sessionId, () => {
+ const invocation = fileInvocation ?? tool.build(params);
+ invocationStarted = true;
+ if (entry.measure) executionStartedAt = performance.now();
+ if (entry.toolName === ShellTool.Name && tools.runShell)
+ return tools.runShell(
+ params,
+ entry.controller.signal,
+ entry.captureSink,
+ );
+ const ledger = this.options.ledger;
+ const trackGroup = (pid: number) => {
+ if (!ledger) return;
+ // Before any settle step of this call can run, the group is
+ // durable for the host's sweeps: the pid leads the group.
+ shellPgid = pid;
+ try {
+ ledger.addGroup({
+ pgid: pid,
+ callId: entry.reference.callId,
+ startedAt: Date.now(),
+ });
+ } catch (error) {
+ this.ledgerIntegrityFailed = true;
+ // A group that could not get into the ledger must not
+ // outlive the failure: stop it first, then let the call
+ // fail loudly instead of settling an outcome nothing
+ // recorded.
+ signalProcessGroup(pid, 'SIGKILL');
+ void ledger.waitForGroupExit(pid, CLOSE_SWEEP_TIMEOUT_MS);
+ throw error;
+ }
+ };
+ if (
+ entry.toolName === ShellTool.Name &&
+ (ledger || entry.captureSink)
+ ) {
+ return (invocation as ShellToolInvocation).execute(
+ entry.controller.signal,
+ undefined,
+ undefined,
+ trackGroup,
+ undefined,
+ undefined,
+ entry.captureSink,
+ );
}
- };
- if (
- entry.toolName === ShellTool.Name &&
- (ledger || entry.captureSink)
- ) {
- return (invocation as ShellToolInvocation).execute(
- entry.controller.signal,
- undefined,
- undefined,
- trackGroup,
- undefined,
- undefined,
- entry.captureSink,
+ // A gate probe's command is a Shell's too: the same group
+ // tracking stops what it leaves behind.
+ if (entry.toolName === ManagedAutomationGateTool.Name && ledger) {
+ return (
+ invocation as unknown as ManagedAutomationGateExecution
+ ).execute(
+ entry.controller.signal,
+ undefined,
+ undefined,
+ trackGroup,
+ );
+ }
+ return invocation.execute(entry.controller.signal);
+ });
+ } finally {
+ if (executionStartedAt !== undefined)
+ executionDurationMs = Math.max(
+ 0,
+ Math.floor(performance.now() - executionStartedAt),
);
- }
- // A gate probe's command is a Shell's too: the same group
- // tracking stops what it leaves behind.
- if (entry.toolName === ManagedAutomationGateTool.Name && ledger) {
- return (
- invocation as unknown as ManagedAutomationGateExecution
- ).execute(
- entry.controller.signal,
- undefined,
- undefined,
- trackGroup,
- );
- }
- return invocation.execute(entry.controller.signal);
- });
+ }
};
const registeredHistory =
tools.extensionAssets === true &&
@@ -2522,6 +2608,77 @@ export class ManagedToolExecutor {
} else {
result = await invoke();
}
+ physicalOutcome = ManagedToolExecutor.isCancelRequested(entry)
+ ? 'cancelled'
+ : result.error
+ ? 'error'
+ : 'success';
+ if (
+ entry.toolName === ShellTool.Name &&
+ isShellResultDisplay(result.returnDisplay)
+ ) {
+ const outcome = result.returnDisplay.outcome;
+ physicalOutcome =
+ outcome === 'completed'
+ ? 'success'
+ : outcome === 'cancelled'
+ ? 'cancelled'
+ : 'error';
+ }
+ nativeFileSucceeded =
+ !result.error &&
+ [ReadFileTool.Name, WriteFileTool.Name, EditTool.Name].includes(
+ entry.toolName,
+ );
+ if (entry.measure && nativeFileSucceeded) {
+ if (entry.toolName === ReadFileTool.Name)
+ fileMeasurement = {
+ operation: 'read',
+ status: 'measured',
+ modelLinesAdded: null,
+ modelLinesRemoved: null,
+ appliedLinesAdded: null,
+ appliedLinesRemoved: null,
+ };
+ if ([WriteFileTool.Name, EditTool.Name].includes(entry.toolName)) {
+ const display = result.returnDisplay;
+ const diff =
+ display &&
+ typeof display === 'object' &&
+ 'originalContent' in display &&
+ 'newContent' in display &&
+ 'diffStat' in display
+ ? display
+ : undefined;
+ const applied =
+ diff &&
+ !diff.originalContentTruncated &&
+ !diff.newContentTruncated &&
+ typeof diff.newContent === 'string' &&
+ (typeof diff.originalContent === 'string' ||
+ diff.originalContent === null)
+ ? getDiffStat(
+ '',
+ diff.originalContent ?? '',
+ diff.newContent,
+ diff.newContent,
+ )
+ : undefined;
+ fileMeasurement = {
+ operation: entry.toolName === EditTool.Name ? 'edit' : 'write',
+ status: applied
+ ? applied.model_added_lines === 0 &&
+ applied.model_removed_lines === 0
+ ? 'unchanged'
+ : 'measured'
+ : 'unmeasured',
+ modelLinesAdded: diff?.diffStat?.model_added_lines ?? null,
+ modelLinesRemoved: diff?.diffStat?.model_removed_lines ?? null,
+ appliedLinesAdded: applied?.model_added_lines ?? null,
+ appliedLinesRemoved: applied?.model_removed_lines ?? null,
+ };
+ }
+ }
if (entry.toolName === GlobTool.Name) {
const root = tools.directory;
if (root === undefined)
@@ -2606,6 +2763,17 @@ export class ManagedToolExecutor {
},
};
}
+ if (entry.measure)
+ entry.measurements = {
+ protocol: 'managed-tool-measurements/1',
+ executionDurationMs,
+ executionOutcome: nativeFileSucceeded
+ ? 'success'
+ : !invocationStarted
+ ? 'not_started'
+ : (physicalOutcome ?? payload.executionStatus),
+ file: fileMeasurement,
+ };
if (
shellPgid !== undefined &&
(entry.controller.signal.aborted || this.options.sshForegroundOnly) &&
diff --git a/packages/cli/src/serve/managed-runtime-tool-v3-routes.ts b/packages/cli/src/serve/managed-runtime-tool-v3-routes.ts
index ead24b54635..fa1442092d3 100644
--- a/packages/cli/src/serve/managed-runtime-tool-v3-routes.ts
+++ b/packages/cli/src/serve/managed-runtime-tool-v3-routes.ts
@@ -70,6 +70,7 @@ function reference(value: unknown): ManagedToolReference | null {
export {
reference as parseManagedToolReference,
capture as parseManagedShellCapture,
+ receipt as parseManagedToolReceipt,
};
function body(
diff --git a/packages/cli/src/serve/managed-runtime-tool-v4-routes.test.ts b/packages/cli/src/serve/managed-runtime-tool-v4-routes.test.ts
new file mode 100644
index 00000000000..f73103f79f5
--- /dev/null
+++ b/packages/cli/src/serve/managed-runtime-tool-v4-routes.test.ts
@@ -0,0 +1,1086 @@
+/**
+ * @license
+ * Copyright 2026 Qwen Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { createServer, type Server } from 'node:http';
+import type { AddressInfo } from 'node:net';
+import * as fs from 'node:fs/promises';
+import * as os from 'node:os';
+import * as path from 'node:path';
+import express from 'express';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import { managedToolDigest } from '@qwen-code/qwen-code-core/tools/managed-tool-protocol.js';
+import { resetManagedRuntimeDispatchGatesForTest } from '@qwen-code/qwen-code-core/managed-runtime/managed-runtime-dispatch-gate.js';
+import type { ManagedToolMeasurements } from '@qwen-code/qwen-code-core/managed-runtime/managed-tool-measurements.js';
+import { LocalManagedSessionAuthority } from '@qwen-code/qwen-code-core/managed-runtime/managed-session-authority.js';
+import { LocalManagedSessionResourceStore } from '@qwen-code/qwen-code-core/managed-runtime/managed-session-resources.js';
+import { LocalToolResultSegmentStore } from '@qwen-code/qwen-code-core/managed-runtime/local-managed-tool-result-store.js';
+import { LocalShellResultCapture } from '@qwen-code/qwen-code-core/managed-runtime/local-shell-result-capture.js';
+import type { ToolResultExpectedIdentity } from '@qwen-code/qwen-code-core/managed-runtime/managed-tool-result-store.js';
+import {
+ parseToolResultManifestBytes,
+ type ToolResultEnvelope,
+} from '@qwen-code/qwen-code-core/managed-runtime/managed-tool-result.js';
+import type { SessionWriterLease } from '@qwen-code/qwen-code-core/services/session-writer-lease.js';
+import type {
+ AnyDeclarativeTool,
+ FileDiff,
+} from '@qwen-code/qwen-code-core/tools/tools.js';
+import {
+ createManagedToolSet,
+ ManagedToolExecutor,
+ type ManagedToolReference,
+ type ManagedToolResultPayload,
+ type ManagedToolSet,
+} from './managed-runtime-tool-executor.js';
+import { registerManagedRuntimeToolRoutes } from './managed-runtime-tool-routes.js';
+import { registerManagedRuntimeToolV3Routes } from './managed-runtime-tool-v3-routes.js';
+import { registerManagedRuntimeToolV4Routes } from './managed-runtime-tool-v4-routes.js';
+import {
+ startManagedRuntimeAttestationWorker,
+ type ManagedRuntimeAttestationWorkerHandle,
+} from './managed-runtime-attestation-worker.js';
+import type { ManagedContextBoot } from './managed-context-envelope.js';
+import { computeManagedContextDigest } from './managed-workspace-binding.js';
+import {
+ MEASURED_WORKSPACE_CAPABILITY_DIGEST,
+ MEASURED_WORKSPACE_EXECUTION_PROFILE,
+ WORKSPACE_CAPABILITY_DIGEST,
+ WORKSPACE_CONTEXT_CONFIG_REF,
+} from './managed-workspace-activation.js';
+
+const headers = {
+ authorization: 'Bearer test-measured-token',
+ 'cache-control': 'no-store',
+ 'content-type': 'application/json',
+ 'x-qwen-managed-lease-id': 'measured-lease',
+ 'x-qwen-managed-lease-epoch': '2',
+};
+const identity = {
+ token: 'test-measured-token',
+ leaseId: 'measured-lease',
+ epoch: 2,
+ runtimeIncarnation: 'measured-incarnation',
+ capabilityDigest: MEASURED_WORKSPACE_CAPABILITY_DIGEST,
+};
+
+interface MeasuredView {
+ protocolVersion: 4;
+ executionProtocol: 2 | 3;
+ state: string;
+ lastSequence?: number;
+ result?: ManagedToolResultPayload;
+ measurements?: ManagedToolMeasurements | null;
+ resultDigest?: string;
+}
+
+let server: Server | undefined;
+let executor: ManagedToolExecutor | undefined;
+let worker: ManagedRuntimeAttestationWorkerHandle | undefined;
+let captureStore: LocalToolResultSegmentStore | undefined;
+let captureLease: SessionWriterLease | undefined;
+let workspace: string;
+let origin: string;
+
+beforeEach(async () => {
+ resetManagedRuntimeDispatchGatesForTest();
+ workspace = await fs.mkdtemp(
+ path.join(os.tmpdir(), 'qwen-managed-diagnostics-v4-'),
+ );
+ await fs.writeFile(path.join(workspace, 'fixture.txt'), 'alpha\nbeta\n');
+});
+
+afterEach(async () => {
+ vi.restoreAllMocks();
+ await worker?.close();
+ await executor?.close();
+ await captureStore?.close();
+ await captureLease?.release();
+ if (server)
+ await new Promise((resolve) => server!.close(() => resolve()));
+ await fs.rm(workspace, { recursive: true, force: true });
+ executor = undefined;
+ server = undefined;
+ worker = undefined;
+ captureStore = undefined;
+ captureLease = undefined;
+});
+
+async function start(
+ capabilityDigest = MEASURED_WORKSPACE_CAPABILITY_DIGEST,
+ suppliedExecutor?: ManagedToolExecutor,
+) {
+ const app = express();
+ executor =
+ suppliedExecutor ??
+ ManagedToolExecutor.forWorkspace(workspace, 'worker-tools');
+ registerManagedRuntimeToolRoutes(app, identity, executor);
+ registerManagedRuntimeToolV3Routes(app, identity, executor);
+ registerManagedRuntimeToolV4Routes(
+ app,
+ { ...identity, capabilityDigest },
+ executor,
+ );
+ server = createServer(app);
+ await new Promise((resolve) => server!.listen(0, '127.0.0.1', resolve));
+ origin = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
+}
+
+function reference(
+ callId: string,
+ input: Record,
+): ManagedToolReference {
+ return {
+ sessionId: 'measured-session',
+ promptId: 'measured-turn',
+ callId,
+ argsDigest: managedToolDigest(input),
+ };
+}
+
+function body(reference: ManagedToolReference) {
+ return { protocolVersion: 4, executionProtocol: 2, reference };
+}
+
+async function post(
+ route: string,
+ value: Record,
+ suppliedHeaders = headers,
+) {
+ return fetch(origin + route, {
+ method: 'POST',
+ headers: suppliedHeaders,
+ body: JSON.stringify(value),
+ });
+}
+
+async function execute(
+ callId: string,
+ toolName: string,
+ input: Record,
+) {
+ const ref = reference(callId, input);
+ const reply = await post('/internal/managed-runtime/v4/execute', {
+ ...body(ref),
+ toolName,
+ input,
+ });
+ expect(reply.status).toBe(200);
+ expect(reply.headers.get('cache-control')).toBe('no-store');
+ expect(reply.headers.get('x-qwen-managed-runtime-incarnation')).toBe(
+ identity.runtimeIncarnation,
+ );
+ return { ref, value: (await reply.json()) as MeasuredView };
+}
+
+describe('Tool v4 measured HTTP routes', () => {
+ it.each([false, true])(
+ 'freezes a real v3 Shell capture while execute waits and retains the acknowledged identity (v4 ACK before execute returns: %s)',
+ async (ackBeforeReturn) => {
+ const sessionKey = {
+ tenantId: 'capture-tenant',
+ workspaceId: 'capture-workspace',
+ sessionId: 'capture-session',
+ };
+ const runtimeBaseDir = path.join(workspace, 'capture-runtime');
+ const transcriptPath = path.join(
+ runtimeBaseDir,
+ 'chats',
+ `${sessionKey.sessionId}.jsonl`,
+ );
+ await fs.mkdir(path.dirname(transcriptPath), { recursive: true });
+ captureLease = await LocalManagedSessionAuthority.acquireWriter({
+ runtimeBaseDir,
+ sessionId: sessionKey.sessionId,
+ transcriptPath,
+ });
+ captureStore = await LocalToolResultSegmentStore.openWritable({
+ sessionKey,
+ lease: captureLease,
+ });
+ const resources = LocalManagedSessionResourceStore.create({
+ runtimeBaseDir,
+ sessionKey,
+ });
+ const input = {
+ command: `"${process.execPath}" -e "require('node:fs').appendFileSync('shell-effect.txt','x'); process.stdout.write('capture-stdout'); process.stderr.write('capture-stderr')"`,
+ };
+ const ref = reference('capture-call', input);
+ const capture = {
+ tenantId: sessionKey.tenantId,
+ sessionId: sessionKey.sessionId,
+ turnId: 'capture-turn',
+ executionCallId: 'capture-execution',
+ bindingGeneration: '1',
+ capturePolicy: 'complete_required' as const,
+ };
+ const expectedIdentity: ToolResultExpectedIdentity = {
+ tenantId: capture.tenantId,
+ sessionId: capture.sessionId,
+ turnId: capture.turnId,
+ executionCallId: capture.executionCallId,
+ bindingGeneration: capture.bindingGeneration,
+ callId: ref.callId,
+ invocationDigest: ref.argsDigest,
+ captureId: 'capture-v4',
+ revision: 1,
+ };
+ let preparations = 0;
+ const toolSet = createManagedToolSet(workspace, ref.sessionId);
+ const measured = new ManagedToolExecutor(async () => toolSet, {
+ prepare: async (request) => {
+ preparations++;
+ expect(request.reference).toEqual(ref);
+ expect(request.capture).toEqual(capture);
+ return {
+ identity: expectedIdentity,
+ sink: new LocalShellResultCapture(
+ captureStore!,
+ resources,
+ expectedIdentity,
+ ),
+ };
+ },
+ });
+ await start(MEASURED_WORKSPACE_CAPABILITY_DIGEST, measured);
+ const request = {
+ protocolVersion: 4,
+ executionProtocol: 3,
+ reference: ref,
+ };
+ let executionReady!: () => void;
+ const ready = new Promise((resolve) => {
+ executionReady = resolve;
+ });
+ let releaseExecution!: () => void;
+ const released = new Promise((resolve) => {
+ releaseExecution = resolve;
+ });
+ const executeV3 = measured.executeV3.bind(measured);
+ const delayed = vi
+ .spyOn(measured, 'executeV3')
+ .mockImplementation(async (invocation) => {
+ await executeV3(invocation);
+ executionReady();
+ await released;
+ return measured.statusV3(invocation.reference);
+ });
+ const first = post('/internal/managed-runtime/v4/execute', {
+ ...request,
+ toolName: 'run_shell_command',
+ input,
+ capture,
+ });
+ try {
+ await ready;
+ const firstStatus = await post(
+ '/internal/managed-runtime/v4/status',
+ request,
+ );
+ expect(firstStatus.status).toBe(200);
+ const value = (await firstStatus.json()) as MeasuredView;
+ const result = value.result as unknown as ToolResultEnvelope;
+ expect(value.state).toBe('settled');
+ expect(value.lastSequence).toBe(executor!.statusV3(ref).lastSequence);
+ expect(value.lastSequence).toBeGreaterThan(0);
+ expect(result).toMatchObject({
+ executionStatus: 'success',
+ capture: { captureStatus: 'complete', deliveryStatus: 'pending' },
+ });
+ expect(value.measurements?.executionOutcome).toBe('success');
+ expect(value.resultDigest).toBe(
+ managedToolDigest(
+ { executionProtocol: 3, result, measurements: value.measurements },
+ 2 * 1024 * 1024,
+ ),
+ );
+ const manifestRef = result.capture!.manifest!;
+ const manifest = parseToolResultManifestBytes(
+ await resources.read(manifestRef),
+ );
+ expect(
+ manifest.contents.find((stream) => stream.streamId === 'stdout')
+ ?.byteLength,
+ ).toBe(14);
+ const captured = await captureStore.readRange({
+ manifestRef,
+ expectedIdentity,
+ streamId: 'stdout',
+ offset: 0,
+ length: 14,
+ });
+ expect(captured.status).toBe('ok');
+ if (captured.status !== 'ok')
+ throw new Error('Expected retained Shell stdout');
+ expect(captured.result.toString('utf8')).toBe('capture-stdout');
+
+ const receipt = {
+ executionCallId: capture.executionCallId,
+ manifest: manifestRef,
+ deliveryStatus: 'committed',
+ historyRevision: 1,
+ };
+ const delivery = await post(
+ '/internal/managed-runtime/v3/acknowledge',
+ {
+ protocolVersion: 3,
+ toolResult: 'managed-tool-result/1',
+ reference: ref,
+ receipt,
+ },
+ );
+ expect(delivery.status).toBe(200);
+ expect(executor!.statusV3(ref).result?.capture?.deliveryStatus).toBe(
+ 'committed',
+ );
+ const acknowledged = {
+ protocolVersion: 4,
+ executionProtocol: 3,
+ state: 'acknowledged',
+ };
+ const ack = { ...request, resultDigest: value.resultDigest, receipt };
+ if (ackBeforeReturn) {
+ const reply = await post(
+ '/internal/managed-runtime/v4/acknowledge',
+ ack,
+ );
+ expect(reply.status).toBe(200);
+ expect(await reply.json()).toEqual(acknowledged);
+ }
+ releaseExecution();
+ const completed = await first;
+ expect(completed.status).toBe(200);
+ expect(await completed.json()).toEqual(
+ ackBeforeReturn ? acknowledged : value,
+ );
+ expect(await delayed.mock.results[0]!.value).toMatchObject({
+ result: { capture: { deliveryStatus: 'committed' } },
+ });
+ delayed.mockRestore();
+ for (const route of ['status', 'cancel']) {
+ const frozen = await post(
+ `/internal/managed-runtime/v4/${route}`,
+ request,
+ );
+ expect(await frozen.json()).toEqual(
+ ackBeforeReturn ? acknowledged : value,
+ );
+ }
+ const repeated = await post('/internal/managed-runtime/v4/execute', {
+ ...request,
+ toolName: 'run_shell_command',
+ input,
+ capture,
+ });
+ expect(await repeated.json()).toEqual(
+ ackBeforeReturn ? acknowledged : value,
+ );
+ expect(preparations).toBe(1);
+ expect(
+ await fs.readFile(path.join(workspace, 'shell-effect.txt'), 'utf8'),
+ ).toBe('x');
+
+ for (let attempt = 0; attempt < 2; attempt++) {
+ const reply = await post(
+ '/internal/managed-runtime/v4/acknowledge',
+ ack,
+ );
+ expect(reply.status).toBe(200);
+ expect(await reply.json()).toEqual(acknowledged);
+ }
+ const status = await post(
+ '/internal/managed-runtime/v4/status',
+ request,
+ );
+ expect(await status.json()).toEqual(acknowledged);
+ const afterAck = await post('/internal/managed-runtime/v4/execute', {
+ ...request,
+ toolName: 'run_shell_command',
+ input,
+ capture,
+ });
+ expect(afterAck.status).toBe(200);
+ expect(await afterAck.json()).toEqual(acknowledged);
+ for (const changed of [
+ {
+ ...request,
+ toolName: 'run_shell_command',
+ input: { ...input, command: 'printf different' },
+ capture,
+ },
+ { ...request, toolName: 'monitor', input, capture },
+ {
+ ...request,
+ toolName: 'run_shell_command',
+ input,
+ capture: { ...capture, turnId: 'other-capture-turn' },
+ },
+ { ...body(ref), toolName: 'run_shell_command', input },
+ ]) {
+ const conflict = await post(
+ '/internal/managed-runtime/v4/execute',
+ changed,
+ );
+ expect(conflict.status).toBe(409);
+ expect(await conflict.json()).toMatchObject({
+ code: 'managed_tool_result_conflict',
+ });
+ }
+ const originalAfterConflicts = await post(
+ '/internal/managed-runtime/v4/execute',
+ {
+ ...request,
+ toolName: 'run_shell_command',
+ input,
+ capture,
+ },
+ );
+ expect(originalAfterConflicts.status).toBe(200);
+ expect(await originalAfterConflicts.json()).toEqual(acknowledged);
+ const conflicting = await post(
+ '/internal/managed-runtime/v4/acknowledge',
+ {
+ ...ack,
+ receipt: { ...receipt, historyRevision: 2 },
+ },
+ );
+ expect(conflicting.status).toBe(409);
+ expect(
+ await fs.readFile(path.join(workspace, 'shell-effect.txt'), 'utf8'),
+ ).toBe('x');
+ } finally {
+ releaseExecution();
+ }
+ },
+ );
+
+ it('serves v4 from the measured context Worker using its installed and activated Session', async () => {
+ const boot: ManagedContextBoot = {
+ type: 'boot',
+ version: 2,
+ managedContext: 'managed-context/1',
+ ...identity,
+ runtimeInstanceId: 'measured-runtime',
+ provisionRequestId: 'measured-provision',
+ isolationClass: 'workspace',
+ tenantId: 'measured-tenant',
+ workspaceId: 'measured-workspace',
+ workspaceGeneration: '1',
+ storageId: 'measured-storage',
+ mountRoot: workspace,
+ };
+ worker = await startManagedRuntimeAttestationWorker(boot);
+ origin = worker.ready.url;
+ const binding = {
+ tenantId: boot.tenantId,
+ workspaceId: boot.workspaceId,
+ workspaceGeneration: boot.workspaceGeneration,
+ storageId: boot.storageId,
+ cwdRelative: '.',
+ contextConfigRef: WORKSPACE_CONTEXT_CONFIG_REF,
+ contextRevision: '1',
+ };
+ const contextDigest = computeManagedContextDigest(binding);
+ const installed = await post('/internal/managed-runtime/v3/context', {
+ protocolVersion: 3,
+ managedContext: 'managed-context/1',
+ operationId: 'install-measured',
+ sessionId: 'measured-session',
+ binding,
+ contextDigest,
+ });
+ expect(installed.status).toBe(200);
+ const activated = await post('/internal/managed-runtime/v3/activation', {
+ protocolVersion: 1,
+ operation: 'activate',
+ sessionId: 'measured-session',
+ contextDigest,
+ contextConfigRef: WORKSPACE_CONTEXT_CONFIG_REF,
+ profile: MEASURED_WORKSPACE_EXECUTION_PROFILE,
+ });
+ expect(activated.status).toBe(200);
+ const result = await execute('worker-read', 'read_file', {
+ file_path: path.join(workspace, 'fixture.txt'),
+ });
+ expect(result.value.result?.executionStatus).toBe('success');
+ expect(result.value.measurements?.file?.operation).toBe('read');
+ });
+
+ it('returns bounded native read measurements and freezes the full result digest across retries/status/cancel', async () => {
+ await start();
+ const input = { file_path: path.join(workspace, 'fixture.txt') };
+ const { ref, value } = await execute('read-1', 'read_file', input);
+ expect(value.state).toBe('settled');
+ expect(value.lastSequence).toBe(executor!.status(ref)!.lastSequence);
+ expect(value.lastSequence).toBeGreaterThan(0);
+ expect(value.result?.executionStatus).toBe('success');
+ expect(JSON.stringify(value.result?.responseParts)).toContain('alpha');
+ expect(value.measurements).toMatchObject({
+ protocol: 'managed-tool-measurements/1',
+ executionOutcome: 'success',
+ file: {
+ operation: 'read',
+ status: 'measured',
+ modelLinesAdded: null,
+ appliedLinesAdded: null,
+ },
+ });
+ expect(Number.isSafeInteger(value.measurements?.executionDurationMs)).toBe(
+ true,
+ );
+ expect(value.measurements!.executionDurationMs).toBeGreaterThanOrEqual(0);
+ expect(JSON.stringify(value.measurements)).not.toContain(workspace);
+ expect(JSON.stringify(value.measurements)).not.toContain('alpha');
+ expect(value.resultDigest).toBe(
+ managedToolDigest(
+ {
+ executionProtocol: 2,
+ result: value.result,
+ measurements: value.measurements,
+ },
+ 2 * 1024 * 1024,
+ ),
+ );
+ await fs.writeFile(
+ path.join(workspace, 'fixture.txt'),
+ 'replacement after first read',
+ );
+ for (const route of ['status', 'cancel']) {
+ const response = await post(
+ `/internal/managed-runtime/v4/${route}`,
+ body(ref),
+ );
+ expect(response.status).toBe(200);
+ expect(await response.json()).toEqual(value);
+ }
+ const repeated = await execute('read-1', 'read_file', input);
+ expect(repeated.value).toEqual(value);
+ });
+
+ it('preserves the file write effect, model/applied line measurements and the original result on execute replay', async () => {
+ await start();
+ const file = path.join(workspace, 'created.txt');
+ const input = { file_path: file, content: 'one\ntwo\n' };
+ const { value } = await execute('write-1', 'write_file', input);
+ expect(value.result?.executionStatus).toBe('success');
+ expect(await fs.readFile(file, 'utf8')).toBe('one\ntwo\n');
+ expect(value.measurements?.file).toMatchObject({
+ operation: 'write',
+ status: 'measured',
+ modelLinesAdded: 2,
+ modelLinesRemoved: 0,
+ appliedLinesAdded: 2,
+ appliedLinesRemoved: 0,
+ });
+ await fs.writeFile(file, 'external edit after result');
+ expect((await execute('write-1', 'write_file', input)).value).toEqual(
+ value,
+ );
+ expect(await fs.readFile(file, 'utf8')).toBe('external edit after result');
+ });
+
+ it('preserves a proven native write and applied diff when cancellation arrives after the physical effect', async () => {
+ const tools = createManagedToolSet(workspace, 'measured-session');
+ const write = tools.tools.get('write_file')!;
+ const build = write.build.bind(write);
+ let executionReady!: () => void;
+ const ready = new Promise((resolve) => {
+ executionReady = resolve;
+ });
+ let releaseExecution!: () => void;
+ const released = new Promise((resolve) => {
+ releaseExecution = resolve;
+ });
+ let actualExecutions = 0;
+ vi.spyOn(write, 'build').mockImplementation((params) => {
+ const invocation = build(params);
+ const execute = invocation.execute.bind(invocation);
+ vi.spyOn(invocation, 'execute').mockImplementation(async (...args) => {
+ actualExecutions++;
+ const result = await execute(...args);
+ executionReady();
+ await released;
+ return result;
+ });
+ return invocation;
+ });
+ await start(
+ MEASURED_WORKSPACE_CAPABILITY_DIGEST,
+ new ManagedToolExecutor(async () => tools),
+ );
+ const file = path.join(workspace, 'cancel-after-write.txt');
+ const input = { file_path: file, content: 'one\ntwo\n' };
+ const ref = reference('cancel-after-write', input);
+ const first = post('/internal/managed-runtime/v4/execute', {
+ ...body(ref),
+ toolName: 'write_file',
+ input,
+ });
+ try {
+ await ready;
+ expect(await fs.readFile(file, 'utf8')).toBe('one\ntwo\n');
+ const cancellation = await post(
+ '/internal/managed-runtime/v4/cancel',
+ body(ref),
+ );
+ expect(cancellation.status).toBe(200);
+ expect(await cancellation.json()).toMatchObject({
+ state: 'cancel_requested',
+ });
+ releaseExecution();
+ const completed = await first;
+ expect(completed.status).toBe(200);
+ const value = (await completed.json()) as MeasuredView;
+ expect(value.result?.executionStatus).toBe('cancelled');
+ expect(value.measurements).toMatchObject({
+ executionOutcome: 'success',
+ file: {
+ operation: 'write',
+ status: 'measured',
+ modelLinesAdded: 2,
+ modelLinesRemoved: 0,
+ appliedLinesAdded: 2,
+ appliedLinesRemoved: 0,
+ },
+ });
+ expect(actualExecutions).toBe(1);
+ await fs.writeFile(file, 'external edit after cancelled delivery');
+ expect(
+ (await execute('cancel-after-write', 'write_file', input)).value,
+ ).toEqual(value);
+ expect(actualExecutions).toBe(1);
+ expect(await fs.readFile(file, 'utf8')).toBe(
+ 'external edit after cancelled delivery',
+ );
+ } finally {
+ releaseExecution();
+ }
+ });
+
+ it('preserves a proven Shell completion when cancellation arrives before result delivery', async () => {
+ const tools = createManagedToolSet(workspace, 'measured-session');
+ const write = tools.tools.get('run_shell_command')!;
+ const build = write.build.bind(write);
+ let executionReady!: () => void;
+ const ready = new Promise((resolve) => {
+ executionReady = resolve;
+ });
+ let releaseExecution!: () => void;
+ const released = new Promise((resolve) => {
+ releaseExecution = resolve;
+ });
+ let actualExecutions = 0;
+ vi.spyOn(write, 'build').mockImplementation((params) => {
+ const invocation = build(params);
+ const execute = invocation.execute.bind(invocation);
+ vi.spyOn(invocation, 'execute').mockImplementation(async (...args) => {
+ actualExecutions++;
+ const result = await execute(...args);
+ executionReady();
+ await released;
+ return result;
+ });
+ return invocation;
+ });
+ await start(
+ MEASURED_WORKSPACE_CAPABILITY_DIGEST,
+ new ManagedToolExecutor(async () => tools),
+ );
+ const file = path.join(workspace, 'cancel-after-shell.txt');
+ const input = {
+ command: `${JSON.stringify(process.execPath)} -e "require('node:fs').writeFileSync('cancel-after-shell.txt', 'one\\ntwo\\n')"`,
+ };
+ const ref = reference('cancel-after-shell', input);
+ const first = post('/internal/managed-runtime/v4/execute', {
+ ...body(ref),
+ toolName: 'run_shell_command',
+ input,
+ });
+ try {
+ await ready;
+ expect(await fs.readFile(file, 'utf8')).toBe('one\ntwo\n');
+ const cancellation = await post(
+ '/internal/managed-runtime/v4/cancel',
+ body(ref),
+ );
+ expect(cancellation.status).toBe(200);
+ expect(await cancellation.json()).toMatchObject({
+ state: 'cancel_requested',
+ });
+ releaseExecution();
+ const completed = await first;
+ expect(completed.status).toBe(200);
+ const value = (await completed.json()) as MeasuredView;
+ expect(value.result?.executionStatus).toBe('cancelled');
+ expect(value.measurements).toMatchObject({
+ executionOutcome: 'success',
+ file: null,
+ });
+ expect(actualExecutions).toBe(1);
+ await fs.writeFile(file, 'external edit after cancelled delivery');
+ expect(
+ (await execute('cancel-after-shell', 'run_shell_command', input)).value,
+ ).toEqual(value);
+ expect(actualExecutions).toBe(1);
+ expect(await fs.readFile(file, 'utf8')).toBe(
+ 'external edit after cancelled delivery',
+ );
+ } finally {
+ releaseExecution();
+ }
+ });
+
+ it('records not_started measurement for a native build failure while retaining the legacy error result', async () => {
+ const tools = createManagedToolSet(workspace, 'measured-session');
+ const build = vi
+ .spyOn(tools.tools.get('write_file')!, 'build')
+ .mockImplementation(() => {
+ throw new Error('test validation failed before invocation');
+ });
+ await start(
+ MEASURED_WORKSPACE_CAPABILITY_DIGEST,
+ new ManagedToolExecutor(async () => tools),
+ );
+ const file = path.join(workspace, 'must-not-be-written.txt');
+ const { value } = await execute('build-failed', 'write_file', {
+ file_path: file,
+ content: 'unwritten',
+ });
+ expect(value.result?.executionStatus).toBe('error');
+ expect(value.measurements).toEqual({
+ protocol: 'managed-tool-measurements/1',
+ executionOutcome: 'not_started',
+ executionDurationMs: null,
+ file: null,
+ });
+ expect(build).toHaveBeenCalledTimes(1);
+ await expect(fs.stat(file)).rejects.toMatchObject({ code: 'ENOENT' });
+ });
+
+ it('keeps a native write and diff when output conversion fails after execution', async () => {
+ const tools = createManagedToolSet(workspace, 'measured-session');
+ const write = tools.tools.get('write_file')!;
+ const build = write.build.bind(write);
+ vi.spyOn(write, 'build').mockImplementation((params) => {
+ const invocation = build(params);
+ const invoke = invocation.execute.bind(invocation);
+ vi.spyOn(invocation, 'execute').mockImplementation(async (...args) => {
+ const result = await invoke(...args);
+ return {
+ ...result,
+ get llmContent(): never {
+ throw new Error('output conversion failed');
+ },
+ };
+ });
+ return invocation;
+ });
+ await start(
+ MEASURED_WORKSPACE_CAPABILITY_DIGEST,
+ new ManagedToolExecutor(async () => tools),
+ );
+ const file = path.join(workspace, 'write-before-output-error.txt');
+ const { value } = await execute('output-error', 'write_file', {
+ file_path: file,
+ content: 'one\ntwo\n',
+ });
+ expect(await fs.readFile(file, 'utf8')).toBe('one\ntwo\n');
+ expect(value.result?.executionStatus).toBe('error');
+ expect(value.measurements).toMatchObject({
+ executionOutcome: 'success',
+ file: { operation: 'write', modelLinesAdded: 2, appliedLinesAdded: 2 },
+ });
+ });
+
+ it.each(['originalContentTruncated', 'newContentTruncated'] as const)(
+ 'does not calculate applied lines from a FileDiff whose %s preview is incomplete',
+ async (truncated) => {
+ const display: FileDiff = {
+ fileDiff: 'partial preview',
+ fileName: 'fixture.txt',
+ originalContent: 'alpha\n',
+ newContent: 'beta\n',
+ truncatedForSession: true,
+ [truncated]: true,
+ diffStat: {
+ model_added_lines: 13,
+ model_removed_lines: 7,
+ model_added_chars: 0,
+ model_removed_chars: 0,
+ user_added_lines: 0,
+ user_removed_lines: 0,
+ user_added_chars: 0,
+ user_removed_chars: 0,
+ },
+ };
+ const tools: ManagedToolSet = {
+ sessionId: 'measured-session',
+ admitsDirectory: () => true,
+ tools: new Map([
+ [
+ 'edit',
+ {
+ validateToolParams: () => null,
+ build: () => ({
+ execute: async () => ({
+ llmContent: 'edited',
+ returnDisplay: display,
+ }),
+ }),
+ } as unknown as AnyDeclarativeTool,
+ ],
+ ]),
+ };
+ await start(
+ MEASURED_WORKSPACE_CAPABILITY_DIGEST,
+ new ManagedToolExecutor(async () => tools),
+ );
+ const { value } = await execute('truncated-edit', 'edit', {
+ file_path: 'fixture.txt',
+ old_string: 'alpha',
+ new_string: 'beta',
+ });
+ expect(value.result?.executionStatus).toBe('success');
+ expect(value.measurements?.file).toEqual({
+ operation: 'edit',
+ status: 'unmeasured',
+ modelLinesAdded: 13,
+ modelLinesRemoved: 7,
+ appliedLinesAdded: null,
+ appliedLinesRemoved: null,
+ });
+ },
+ );
+
+ it('can measure full old/new file contents when only the textual patch preview is truncated', async () => {
+ const display: FileDiff = {
+ fileDiff: 'truncated patch',
+ fileName: 'fixture.txt',
+ originalContent: 'alpha\n',
+ newContent: 'beta\n',
+ truncatedForSession: true,
+ fileDiffTruncated: true,
+ originalContentTruncated: false,
+ newContentTruncated: false,
+ diffStat: {
+ model_added_lines: 13,
+ model_removed_lines: 7,
+ model_added_chars: 0,
+ model_removed_chars: 0,
+ user_added_lines: 0,
+ user_removed_lines: 0,
+ user_added_chars: 0,
+ user_removed_chars: 0,
+ },
+ };
+ const tools: ManagedToolSet = {
+ sessionId: 'measured-session',
+ admitsDirectory: () => true,
+ tools: new Map([
+ [
+ 'edit',
+ {
+ validateToolParams: () => null,
+ build: () => ({
+ execute: async () => ({
+ llmContent: 'edited',
+ returnDisplay: display,
+ }),
+ }),
+ } as unknown as AnyDeclarativeTool,
+ ],
+ ]),
+ };
+ await start(
+ MEASURED_WORKSPACE_CAPABILITY_DIGEST,
+ new ManagedToolExecutor(async () => tools),
+ );
+ const { value } = await execute('patch-truncated-edit', 'edit', {
+ file_path: 'fixture.txt',
+ old_string: 'alpha',
+ new_string: 'beta',
+ });
+ expect(value.measurements?.file).toEqual({
+ operation: 'edit',
+ status: 'measured',
+ modelLinesAdded: 13,
+ modelLinesRemoved: 7,
+ appliedLinesAdded: 1,
+ appliedLinesRemoved: 1,
+ });
+ });
+
+ it('rejects a wrong full-result ACK and makes the original ACK idempotent after payload reclamation', async () => {
+ await start();
+ const { ref, value } = await execute('ack-read', 'read_file', {
+ file_path: path.join(workspace, 'fixture.txt'),
+ });
+ const wrong = await post('/internal/managed-runtime/v4/acknowledge', {
+ ...body(ref),
+ resultDigest: '0'.repeat(64),
+ });
+ expect(wrong.status).toBe(409);
+ const stillPresent = await post(
+ '/internal/managed-runtime/v4/status',
+ body(ref),
+ );
+ expect(await stillPresent.json()).toEqual(value);
+ const ack = { ...body(ref), resultDigest: value.resultDigest };
+ for (let attempt = 0; attempt < 2; attempt++) {
+ const response = await post(
+ '/internal/managed-runtime/v4/acknowledge',
+ ack,
+ );
+ expect(response.status).toBe(200);
+ expect(await response.json()).toEqual({
+ protocolVersion: 4,
+ executionProtocol: 2,
+ state: 'acknowledged',
+ });
+ }
+ const status = await post('/internal/managed-runtime/v4/status', body(ref));
+ expect(await status.json()).toEqual({
+ protocolVersion: 4,
+ executionProtocol: 2,
+ state: 'acknowledged',
+ });
+ const conflict = await post('/internal/managed-runtime/v4/acknowledge', {
+ ...ack,
+ resultDigest: '0'.repeat(64),
+ });
+ expect(conflict.status).toBe(409);
+ });
+
+ it('retains the closed legacy v2 result and v3 status envelope on a new executor', async () => {
+ await start();
+ const input = { file_path: path.join(workspace, 'fixture.txt') };
+ const ref = reference('legacy-read', input);
+ const response = await post('/internal/managed-runtime/v2/execute', {
+ protocolVersion: 2,
+ reference: ref,
+ toolName: 'read_file',
+ input,
+ });
+ expect(response.status).toBe(200);
+ const value = (await response.json()) as Record;
+ expect(value).toMatchObject({
+ protocolVersion: 2,
+ state: 'settled',
+ result: { executionStatus: 'success' },
+ });
+ expect(value).not.toHaveProperty('measurements');
+ expect(value).not.toHaveProperty('resultDigest');
+ expect(value['result']).not.toHaveProperty('measurements');
+ const status = await post('/internal/managed-runtime/v3/status', {
+ protocolVersion: 3,
+ toolResult: 'managed-tool-result/1',
+ reference: reference('unknown-v3', input),
+ });
+ expect(status.status).toBe(200);
+ const v3 = (await status.json()) as Record;
+ expect(v3).toMatchObject({ protocolVersion: 3, state: 'unknown' });
+ expect(v3).not.toHaveProperty('measurements');
+ expect(v3).not.toHaveProperty('resultDigest');
+ });
+
+ it('does not register v4 for the original Workspace capability', async () => {
+ await start(WORKSPACE_CAPABILITY_DIGEST);
+ const input = { file_path: path.join(workspace, 'fixture.txt') };
+ const response = await post('/internal/managed-runtime/v4/execute', {
+ ...body(reference('old-profile', input)),
+ toolName: 'read_file',
+ input,
+ });
+ expect(response.status).toBe(404);
+ expect(executor!.measurements(reference('old-profile', input))).toBeNull();
+ });
+
+ it('rejects wrong credentials, lease identity and unknown fields before dispatch', async () => {
+ await start();
+ const file = path.join(workspace, 'must-not-exist.txt');
+ const input = { file_path: file, content: 'forbidden' };
+ const value = {
+ ...body(reference('blocked-write', input)),
+ toolName: 'write_file',
+ input,
+ };
+ const unauthenticated = await post(
+ '/internal/managed-runtime/v4/execute',
+ value,
+ { ...headers, authorization: 'Bearer wrong' },
+ );
+ expect(unauthenticated.status).toBe(401);
+ expect(
+ unauthenticated.headers.get('x-qwen-managed-runtime-incarnation'),
+ ).toBeNull();
+ const wrongLease = await post(
+ '/internal/managed-runtime/v4/execute',
+ value,
+ { ...headers, 'x-qwen-managed-lease-id': 'other-lease' },
+ );
+ expect(wrongLease.status).toBe(409);
+ const unknown = await post('/internal/managed-runtime/v4/execute', {
+ ...value,
+ arbitraryMeasurement: 1,
+ });
+ expect(unknown.status).toBe(400);
+ await expect(fs.stat(file)).rejects.toMatchObject({ code: 'ENOENT' });
+ });
+
+ it('treats a reference from another Session as unknown without exposing or acknowledging its result', async () => {
+ await start();
+ const { ref, value } = await execute('owned-read', 'read_file', {
+ file_path: path.join(workspace, 'fixture.txt'),
+ });
+ const response = await post(
+ '/internal/managed-runtime/v4/status',
+ body({ ...ref, sessionId: 'another-session' }),
+ );
+ expect(response.status).toBe(200);
+ expect(await response.json()).toEqual({
+ protocolVersion: 4,
+ executionProtocol: 2,
+ state: 'unknown',
+ });
+ const ack = await post('/internal/managed-runtime/v4/acknowledge', {
+ ...body({ ...ref, sessionId: 'another-session' }),
+ resultDigest: value.resultDigest,
+ });
+ expect(await ack.json()).toEqual({
+ protocolVersion: 4,
+ executionProtocol: 2,
+ state: 'unknown',
+ });
+ const original = await post(
+ '/internal/managed-runtime/v4/status',
+ body(ref),
+ );
+ expect(await original.json()).toEqual(value);
+ });
+
+ it('returns a protocol conflict when redispatch attempts to reuse an existing invocation under another Session', async () => {
+ await start();
+ const input = { file_path: path.join(workspace, 'fixture.txt') };
+ const { ref, value } = await execute(
+ 'conflicting-read',
+ 'read_file',
+ input,
+ );
+ const response = await post('/internal/managed-runtime/v4/execute', {
+ ...body({ ...ref, sessionId: 'another-session' }),
+ toolName: 'read_file',
+ input,
+ });
+ expect(response.status).toBe(409);
+ expect(await response.json()).toMatchObject({
+ code: 'managed_runtime_identity_conflict',
+ });
+ const original = await post(
+ '/internal/managed-runtime/v4/status',
+ body(ref),
+ );
+ expect(await original.json()).toEqual(value);
+ });
+});
diff --git a/packages/cli/src/serve/managed-runtime-tool-v4-routes.ts b/packages/cli/src/serve/managed-runtime-tool-v4-routes.ts
new file mode 100644
index 00000000000..3c04ad14ee6
--- /dev/null
+++ b/packages/cli/src/serve/managed-runtime-tool-v4-routes.ts
@@ -0,0 +1,321 @@
+/**
+ * @license
+ * Copyright 2026 Qwen Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import type { Application, Request, Response, NextFunction } from 'express';
+import { managedToolDigest } from '@qwen-code/qwen-code-core/tools/managed-tool-protocol.js';
+import {
+ authorizeManagedRuntime,
+ handleManagedRuntimeJsonError,
+ managedRuntimeJsonBody,
+ managedRuntimeNoStore,
+ nameManagedRuntimeIncarnation,
+ type ManagedRuntimeAttestationIdentity,
+ type ManagedRuntimeRequestIdentity,
+} from './managed-runtime-attestation-contract.js';
+import {
+ parseManagedToolReference,
+ parseManagedShellCapture,
+ parseManagedToolReceipt,
+} from './managed-runtime-tool-v3-routes.js';
+import { MEASURED_WORKSPACE_CAPABILITY_DIGEST } from './managed-workspace-activation.js';
+import type {
+ ManagedToolExecutor,
+ ManagedToolInvocationView,
+ ManagedToolV3View,
+} from './managed-runtime-tool-executor.js';
+import {
+ ManagedToolConflictError,
+ ManagedToolInvalidError,
+ ManagedToolUnavailableError,
+} from './managed-runtime-tool-executor.js';
+
+export const MANAGED_RUNTIME_TOOL_V4_ROUTES = (
+ ['execute', 'status', 'cancel', 'acknowledge'] as const
+).map((key) => ({
+ key: 'measured-' + key,
+ method: 'POST' as const,
+ path: '/internal/managed-runtime/v4/' + key,
+ protocolVersion: 4,
+ requestBodyLimitBytes: key === 'execute' ? 256 * 1024 : 16 * 1024,
+ responseBodyLimitBytes: 1024 * 1024 + 4096,
+ cacheControl: 'no-store' as const,
+}));
+
+function object(value: unknown): Record | null {
+ return value && typeof value === 'object' && !Array.isArray(value)
+ ? (value as Record)
+ : null;
+}
+
+export function measuredToolView(
+ executionProtocol: 2 | 3,
+ input: ManagedToolInvocationView | ManagedToolV3View | null,
+ measurements: ReturnType,
+): Record {
+ const view = input ?? { state: 'unknown' as const };
+ const result: Record = {
+ protocolVersion: 4,
+ executionProtocol,
+ ...view,
+ };
+ if (view.state === 'settled' && view.result) {
+ result['measurements'] = measurements;
+ result['resultDigest'] = managedToolDigest(
+ { executionProtocol, result: view.result, measurements },
+ 2 * 1024 * 1024,
+ );
+ }
+ return result;
+}
+
+export function registerManagedRuntimeToolV4Routes(
+ app: Application,
+ identity: ManagedRuntimeRequestIdentity &
+ Pick<
+ ManagedRuntimeAttestationIdentity,
+ 'runtimeIncarnation' | 'capabilityDigest'
+ >,
+ executor: ManagedToolExecutor,
+): void {
+ if (identity.capabilityDigest !== MEASURED_WORKSPACE_CAPABILITY_DIGEST)
+ return;
+ const frozen = new Map<
+ string,
+ {
+ mode: 2 | 3;
+ digest: string;
+ value?: Record;
+ receiptDigest?: string;
+ }
+ >();
+ const requests = new Map();
+ for (const route of MANAGED_RUNTIME_TOOL_V4_ROUTES) {
+ app.post(
+ route.path,
+ managedRuntimeNoStore,
+ authorizeManagedRuntime(identity),
+ nameManagedRuntimeIncarnation(identity),
+ managedRuntimeJsonBody(route.requestBodyLimitBytes),
+ async (req: Request, res: Response, next: NextFunction) => {
+ const body = object(req.body);
+ const reference = parseManagedToolReference(body?.['reference']);
+ const mode = body?.['executionProtocol'];
+ const execute = route.key === 'measured-execute';
+ const acknowledge = route.key === 'measured-acknowledge';
+ const allowed = [
+ 'protocolVersion',
+ 'executionProtocol',
+ 'reference',
+ ...(execute
+ ? ['toolName', 'input', ...(mode === 3 ? ['capture'] : [])]
+ : []),
+ ...(acknowledge
+ ? ['resultDigest', ...(mode === 3 ? ['receipt'] : [])]
+ : []),
+ ...(route.key === 'measured-status' ? ['afterSequence'] : []),
+ ];
+ if (
+ !body ||
+ !reference ||
+ body['protocolVersion'] !== 4 ||
+ (mode !== 2 && mode !== 3) ||
+ Object.keys(body).some((key) => !allowed.includes(key)) ||
+ (body['afterSequence'] !== undefined &&
+ (!Number.isSafeInteger(body['afterSequence']) ||
+ Number(body['afterSequence']) < 0))
+ ) {
+ res.status(400).json({ code: 'managed_runtime_measurement_invalid' });
+ return;
+ }
+ try {
+ const identityKey = managedToolDigest(reference);
+ const saved = frozen.get(identityKey);
+ if (saved && saved.mode !== mode) {
+ res.status(409).json({ code: 'managed_tool_result_conflict' });
+ return;
+ }
+ if (execute) {
+ if (
+ !object(body['input']) ||
+ typeof body['toolName'] !== 'string' ||
+ !body['toolName'].length ||
+ (mode === 3 && !parseManagedShellCapture(body['capture']))
+ ) {
+ res
+ .status(400)
+ .json({ code: 'managed_runtime_measurement_invalid' });
+ return;
+ }
+ const requestDigest = managedToolDigest({
+ executionProtocol: mode,
+ toolName: body['toolName'],
+ input: body['input'],
+ capture: body['capture'] ?? null,
+ });
+ const original = requests.get(identityKey);
+ if (original && original !== requestDigest) {
+ res.status(409).json({ code: 'managed_tool_result_conflict' });
+ return;
+ }
+ requests.set(identityKey, requestDigest);
+ }
+ if (saved && !saved.value) {
+ if (
+ acknowledge &&
+ (body['resultDigest'] !== saved.digest ||
+ managedToolDigest(body['receipt'] ?? null) !==
+ saved.receiptDigest)
+ ) {
+ res.status(409).json({ code: 'managed_tool_result_conflict' });
+ return;
+ }
+ res.json({
+ protocolVersion: 4,
+ executionProtocol: mode,
+ state: 'acknowledged',
+ });
+ return;
+ }
+ let view: ManagedToolInvocationView | ManagedToolV3View | null;
+ if (execute) {
+ const input = object(body['input']);
+ const toolName = body['toolName'];
+ const capture =
+ mode === 3
+ ? parseManagedShellCapture(body['capture'])
+ : undefined;
+ if (
+ !input ||
+ typeof toolName !== 'string' ||
+ !toolName.length ||
+ (mode === 3 && !capture)
+ ) {
+ res
+ .status(400)
+ .json({ code: 'managed_runtime_measurement_invalid' });
+ return;
+ }
+ if (mode === 2) {
+ await executor.execute(reference, toolName, input, true);
+ view = await executor.status(reference);
+ } else {
+ view = await executor.executeV3({
+ reference,
+ measure: true,
+ toolName,
+ input,
+ capture: {
+ ...capture!,
+ ...(input['is_background'] === true
+ ? { background: true as const }
+ : {}),
+ ...(toolName === 'monitor'
+ ? { monitoring: true as const }
+ : {}),
+ },
+ });
+ }
+ } else if (acknowledge) {
+ view =
+ mode === 2
+ ? await executor.status(reference)
+ : executor.statusV3(reference);
+ if (
+ !view ||
+ view.state === 'unknown' ||
+ view.state === 'acknowledged'
+ ) {
+ res.json({
+ protocolVersion: 4,
+ executionProtocol: mode,
+ state: view?.state ?? 'unknown',
+ });
+ return;
+ }
+ const measured =
+ saved?.value ??
+ measuredToolView(mode, view, executor.measurements(reference));
+ if (
+ view.state !== 'settled' ||
+ body['resultDigest'] !== measured['resultDigest']
+ ) {
+ res.status(409).json({ code: 'managed_tool_result_conflict' });
+ return;
+ }
+ if (mode === 2) view = executor.acknowledge(reference);
+ else {
+ const receipt = parseManagedToolReceipt(body['receipt']);
+ if (!receipt) {
+ res
+ .status(400)
+ .json({ code: 'managed_runtime_measurement_invalid' });
+ return;
+ }
+ view = await executor.acknowledgeV3(reference, receipt);
+ }
+ frozen.set(identityKey, {
+ mode,
+ digest: String(measured['resultDigest']),
+ receiptDigest: managedToolDigest(body['receipt'] ?? null),
+ });
+ res.json({
+ protocolVersion: 4,
+ executionProtocol: mode,
+ state: 'acknowledged',
+ });
+ return;
+ } else {
+ view =
+ route.key === 'measured-cancel'
+ ? mode === 2
+ ? await executor.cancel(reference)
+ : executor.cancelV3(reference)
+ : mode === 2
+ ? await executor.status(reference)
+ : executor.statusV3(reference);
+ }
+ const current = frozen.get(identityKey);
+ if (current && !current.value) {
+ res.json({
+ protocolVersion: 4,
+ executionProtocol: mode,
+ state: 'acknowledged',
+ });
+ return;
+ }
+ const measured =
+ current?.value ??
+ measuredToolView(mode, view, executor.measurements(reference));
+ if (measured['state'] === 'settled' && !current)
+ frozen.set(identityKey, {
+ mode,
+ digest: String(measured['resultDigest']),
+ value: structuredClone(measured),
+ });
+ res.json(measured);
+ } catch (error) {
+ if (error instanceof ManagedToolConflictError) {
+ res.status(409).json({ code: error.code, error: error.message });
+ return;
+ }
+ if (error instanceof ManagedToolInvalidError) {
+ res.status(400).json({
+ code: 'managed_runtime_measurement_invalid',
+ error: error.message,
+ });
+ return;
+ }
+ if (error instanceof ManagedToolUnavailableError) {
+ res.status(409).json({ code: error.code, error: error.message });
+ return;
+ }
+ next(error);
+ }
+ },
+ handleManagedRuntimeJsonError,
+ );
+ }
+}
diff --git a/packages/cli/src/serve/managed-workspace-activation.ts b/packages/cli/src/serve/managed-workspace-activation.ts
index 9deeb99bef4..984a7e27382 100644
--- a/packages/cli/src/serve/managed-workspace-activation.ts
+++ b/packages/cli/src/serve/managed-workspace-activation.ts
@@ -27,6 +27,17 @@ export const WORKSPACE_CONTEXT_CONFIG_REF =
'sha256:5fa15183dcfe582ce54c28bf25aff62801d19038a9ec649e9bfc67b06f2a389f';
export const WORKSPACE_CAPABILITY_DIGEST =
'sha256:7df3981fa14b397fe09f6ba84ed0c096c71902394da667bf47f3b315866e7f91';
+export const MEASURED_WORKSPACE_CAPABILITY_DIGEST =
+ 'sha256:b2ab403f278282eb8f2ca06c926be96bd3d85877714f64dc926eab535f544580';
+export const MEASURED_WORKSPACE_EXECUTION_PROFILE =
+ 'managed-workspace-execution/2';
+
+export function isWorkspaceToolCapability(digest: string): boolean {
+ return (
+ digest === WORKSPACE_CAPABILITY_DIGEST ||
+ digest === MEASURED_WORKSPACE_CAPABILITY_DIGEST
+ );
+}
export const RESIDENT_WORKSPACE_PROFILE = 'resident-workspace-files/1';
export const RESIDENT_WORKSPACE_CAPABILITY_DIGEST =
'sha256:2cbf6f3825230848a65a546ec70714fbb2a53d1862a6caba73ae4cebf307b4ad';
@@ -92,6 +103,7 @@ export class WorkspaceActivations {
request['profile'] !== RESIDENT_WORKSPACE_PROFILE
: ![
WORKSPACE_CAPABILITY_DIGEST,
+ MEASURED_WORKSPACE_CAPABILITY_DIGEST,
...(boot.managedExtensions
? [
MANAGED_EXTENSION_LINUX_CAPABILITY_DIGEST,
@@ -99,7 +111,10 @@ export class WorkspaceActivations {
]
: []),
].includes(boot.capabilityDigest) ||
- request['profile'] !== WORKSPACE_EXECUTION_PROFILE) ||
+ request['profile'] !==
+ (boot.capabilityDigest === MEASURED_WORKSPACE_CAPABILITY_DIGEST
+ ? MEASURED_WORKSPACE_EXECUTION_PROFILE
+ : WORKSPACE_EXECUTION_PROFILE)) ||
binding === undefined ||
binding.contextConfigRef !== WORKSPACE_CONTEXT_CONFIG_REF ||
request['contextConfigRef'] !== binding.contextConfigRef ||
diff --git a/packages/cli/src/serve/workspace-recovery-session.ts b/packages/cli/src/serve/workspace-recovery-session.ts
index 8c13f7102ca..d44dbdecdba 100644
--- a/packages/cli/src/serve/workspace-recovery-session.ts
+++ b/packages/cli/src/serve/workspace-recovery-session.ts
@@ -5,6 +5,10 @@
*/
import path from 'node:path';
+import {
+ MANAGED_TOOL_MEASUREMENTS_KIND,
+ parseManagedToolMeasurementEvidence,
+} from '@qwen-code/qwen-code-core/managed-runtime/managed-tool-measurements.js';
import {
EXECUTION_DIAGNOSTICS_KIND,
parseManagedExecutionEvidence,
@@ -185,6 +189,7 @@ const DOMAINS = [
];
const RESOURCE_KINDS = new Set([
EXECUTION_DIAGNOSTICS_KIND,
+ MANAGED_TOOL_MEASUREMENTS_KIND,
'managed-definition',
'managed-agent-instructions',
'managed-agent-skills',
@@ -350,7 +355,7 @@ export async function verifyRecoverySession(
typeof retiredHead.workspaceId === 'string' &&
retiredHead.workspaceId.length > 0 &&
retiredHead.state === 'DELETED' &&
- [1, 2, 3, 4].includes(retiredHead.storageVersion) &&
+ [1, 2, 3, 4, 5].includes(retiredHead.storageVersion) &&
retiredHead.writerId === null &&
retiredHead.writerLeaseUntil === null &&
retiredHead.latestCheckpointResourceId === null &&
@@ -403,7 +408,7 @@ export async function verifyRecoverySession(
!head ||
(head.sessionId === source.sessionId &&
head.tenantId === source.binding.tenantId &&
- [1, 2, 3, 4].includes(head.storageVersion) &&
+ [1, 2, 3, 4, 5].includes(head.storageVersion) &&
head.journalRevision >= 1 &&
head.compactedThroughRevision === 0 &&
head.recoveryStatus === 'READY'),
@@ -1076,6 +1081,12 @@ export async function verifyRecoverySession(
} else if (ref.kind === 'managed-checkpoint-history') {
for (const nested of collectNestedResourceRefs(ref, bytes))
await io.enqueue(nested);
+ } else if (ref.kind === MANAGED_TOOL_MEASUREMENTS_KIND) {
+ requireValue(
+ ref.schemaVersion === 1 && (head?.storageVersion ?? 0) >= 5,
+ 'unsupported tool measurements',
+ );
+ parseManagedToolMeasurementEvidence(bytes);
} else if (ref.kind === EXECUTION_DIAGNOSTICS_KIND) {
if (ref.schemaVersion !== 1)
throw new Error(
diff --git a/packages/core/src/managed-runtime/contracts/managed-event-envelope-v1.fixtures.json b/packages/core/src/managed-runtime/contracts/managed-event-envelope-v1.fixtures.json
index 85ecfc301f1..e424a448eb4 100644
--- a/packages/core/src/managed-runtime/contracts/managed-event-envelope-v1.fixtures.json
+++ b/packages/core/src/managed-runtime/contracts/managed-event-envelope-v1.fixtures.json
@@ -11,6 +11,7 @@
"tool.intent",
"action.changed",
"tool.receipt",
+ "tool.measured",
"checkpoint.committed",
"context.compacted",
"cancel.requested",
@@ -182,6 +183,24 @@
}
}
},
+ {
+ "id": "kind-tool-measured",
+ "valid": true,
+ "envelope": {
+ "v": 1,
+ "sessionId": "session-1",
+ "tenantId": "tenant-1",
+ "workspaceId": "workspace-1",
+ "stream": "authoritative_journal",
+ "sequence": 108,
+ "eventId": "evt-000108",
+ "kind": "tool.measured",
+ "occurredAt": 1760000000008,
+ "payloadRef": {
+ "digest": "631f42ee57f8e39d9491a76ba9477d32a11f8c2b8a3a4ff3c21b30760c600000"
+ }
+ }
+ },
{
"id": "kind-tool-receipt",
"valid": true,
diff --git a/packages/core/src/managed-runtime/contracts/managed-event-envelope-v1.schema.json b/packages/core/src/managed-runtime/contracts/managed-event-envelope-v1.schema.json
index 34115dda598..217923ab632 100644
--- a/packages/core/src/managed-runtime/contracts/managed-event-envelope-v1.schema.json
+++ b/packages/core/src/managed-runtime/contracts/managed-event-envelope-v1.schema.json
@@ -38,6 +38,7 @@
"tool.intent",
"action.changed",
"tool.receipt",
+ "tool.measured",
"checkpoint.committed",
"context.compacted",
"cancel.requested",
@@ -206,6 +207,7 @@
"tool.intent",
"action.changed",
"tool.receipt",
+ "tool.measured",
"checkpoint.committed",
"context.compacted",
"cancel.requested",
diff --git a/packages/core/src/managed-runtime/contracts/managed-session-store-v1.fixtures.json b/packages/core/src/managed-runtime/contracts/managed-session-store-v1.fixtures.json
index 13f612b8d6e..b03a8ab70db 100644
--- a/packages/core/src/managed-runtime/contracts/managed-session-store-v1.fixtures.json
+++ b/packages/core/src/managed-runtime/contracts/managed-session-store-v1.fixtures.json
@@ -26,6 +26,7 @@
"tool.intent",
"action.changed",
"tool.receipt",
+ "tool.measured",
"checkpoint.committed",
"context.compacted",
"cancel.requested",
diff --git a/packages/core/src/managed-runtime/contracts/managed-tool-measurements-v1.fixtures.json b/packages/core/src/managed-runtime/contracts/managed-tool-measurements-v1.fixtures.json
new file mode 100644
index 00000000000..70b348a6647
--- /dev/null
+++ b/packages/core/src/managed-runtime/contracts/managed-tool-measurements-v1.fixtures.json
@@ -0,0 +1,307 @@
+{
+ "cases": [
+ {
+ "id": "model-and-applied",
+ "valid": true,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": 0,
+ "executionOutcome": "success",
+ "file": {
+ "operation": "edit",
+ "status": "measured",
+ "modelLinesAdded": 3,
+ "modelLinesRemoved": 1,
+ "appliedLinesAdded": 2,
+ "appliedLinesRemoved": 1
+ }
+ }
+ },
+ {
+ "id": "zero-duration",
+ "valid": true,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": 0,
+ "executionOutcome": "success",
+ "file": null
+ }
+ },
+ {
+ "id": "maximum-safe-integer",
+ "valid": true,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": 9007199254740991,
+ "executionOutcome": "success",
+ "file": null
+ }
+ },
+ {
+ "id": "integral-decimal",
+ "valid": true,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": 1.0,
+ "executionOutcome": "success",
+ "file": null
+ }
+ },
+ {
+ "id": "not-started",
+ "valid": true,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": null,
+ "executionOutcome": "not_started",
+ "file": null
+ }
+ },
+ {
+ "id": "unknown",
+ "valid": true,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": null,
+ "executionOutcome": "unknown",
+ "file": null
+ }
+ },
+ {
+ "id": "read",
+ "valid": true,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": 0,
+ "executionOutcome": "success",
+ "file": {
+ "operation": "read",
+ "status": "measured",
+ "modelLinesAdded": null,
+ "modelLinesRemoved": null,
+ "appliedLinesAdded": null,
+ "appliedLinesRemoved": null
+ }
+ }
+ },
+ {
+ "id": "negative",
+ "valid": false,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": -1,
+ "executionOutcome": "success",
+ "file": {
+ "operation": "edit",
+ "status": "measured",
+ "modelLinesAdded": 3,
+ "modelLinesRemoved": 1,
+ "appliedLinesAdded": 2,
+ "appliedLinesRemoved": 1
+ }
+ }
+ },
+ {
+ "id": "fractional",
+ "valid": false,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": 1.5,
+ "executionOutcome": "success",
+ "file": {
+ "operation": "edit",
+ "status": "measured",
+ "modelLinesAdded": 3,
+ "modelLinesRemoved": 1,
+ "appliedLinesAdded": 2,
+ "appliedLinesRemoved": 1
+ }
+ }
+ },
+ {
+ "id": "unsafe",
+ "valid": false,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": 9007199254740992,
+ "executionOutcome": "success",
+ "file": {
+ "operation": "edit",
+ "status": "measured",
+ "modelLinesAdded": 3,
+ "modelLinesRemoved": 1,
+ "appliedLinesAdded": 2,
+ "appliedLinesRemoved": 1
+ }
+ }
+ },
+ {
+ "id": "opaque",
+ "valid": false,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": 0,
+ "executionOutcome": "success",
+ "file": {
+ "operation": "edit",
+ "status": "measured",
+ "modelLinesAdded": 3,
+ "modelLinesRemoved": 1,
+ "appliedLinesAdded": 2,
+ "appliedLinesRemoved": 1
+ },
+ "arguments": {
+ "path": "private"
+ }
+ }
+ },
+ {
+ "id": "wrong-protocol",
+ "valid": false,
+ "measurements": {
+ "protocol": "managed-tool-measurements/2",
+ "executionDurationMs": 0,
+ "executionOutcome": "success",
+ "file": {
+ "operation": "edit",
+ "status": "measured",
+ "modelLinesAdded": 3,
+ "modelLinesRemoved": 1,
+ "appliedLinesAdded": 2,
+ "appliedLinesRemoved": 1
+ }
+ }
+ },
+ {
+ "id": "file-without-success",
+ "valid": false,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": 0,
+ "executionOutcome": "error",
+ "file": {
+ "operation": "edit",
+ "status": "measured",
+ "modelLinesAdded": 3,
+ "modelLinesRemoved": 1,
+ "appliedLinesAdded": 2,
+ "appliedLinesRemoved": 1
+ }
+ }
+ },
+ {
+ "id": "read-with-lines",
+ "valid": false,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": 0,
+ "executionOutcome": "success",
+ "file": {
+ "operation": "read",
+ "status": "measured",
+ "modelLinesAdded": 3,
+ "modelLinesRemoved": 1,
+ "appliedLinesAdded": 2,
+ "appliedLinesRemoved": 1
+ }
+ }
+ },
+ {
+ "id": "unchanged-with-delta",
+ "valid": false,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": 0,
+ "executionOutcome": "success",
+ "file": {
+ "operation": "edit",
+ "status": "unchanged",
+ "modelLinesAdded": 3,
+ "modelLinesRemoved": 1,
+ "appliedLinesAdded": 2,
+ "appliedLinesRemoved": 1
+ }
+ }
+ },
+ {
+ "id": "unmeasured-with-applied",
+ "valid": false,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": 0,
+ "executionOutcome": "success",
+ "file": {
+ "operation": "edit",
+ "status": "unmeasured",
+ "modelLinesAdded": 3,
+ "modelLinesRemoved": 1,
+ "appliedLinesAdded": 2,
+ "appliedLinesRemoved": 1
+ }
+ }
+ },
+ {
+ "id": "missing-pair",
+ "valid": false,
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": 0,
+ "executionOutcome": "success",
+ "file": {
+ "operation": "edit",
+ "status": "measured",
+ "modelLinesAdded": 3,
+ "modelLinesRemoved": null,
+ "appliedLinesAdded": 2,
+ "appliedLinesRemoved": 1
+ }
+ }
+ }
+ ],
+ "evidence": {
+ "schemaVersion": 1,
+ "invocationId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "executionCallId": "调用-1",
+ "modelProposal": "original",
+ "resultDigest": "86e7d29ce46bee137e680bc1852cb5c6b00df8fea57eb971804e92746d213a19",
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": 0,
+ "executionOutcome": "success",
+ "file": {
+ "operation": "edit",
+ "status": "measured",
+ "modelLinesAdded": 3,
+ "modelLinesRemoved": 1,
+ "appliedLinesAdded": 2,
+ "appliedLinesRemoved": 1
+ }
+ }
+ },
+ "wire": {
+ "executionProtocol": 2,
+ "result": {
+ "executionStatus": "success",
+ "responseParts": [
+ {
+ "type": "text",
+ "text": "完成 ✓"
+ }
+ ]
+ },
+ "measurements": {
+ "protocol": "managed-tool-measurements/1",
+ "executionDurationMs": 0,
+ "executionOutcome": "success",
+ "file": {
+ "operation": "edit",
+ "status": "measured",
+ "modelLinesAdded": 3,
+ "modelLinesRemoved": 1,
+ "appliedLinesAdded": 2,
+ "appliedLinesRemoved": 1
+ }
+ }
+ },
+ "resultDigest": "86e7d29ce46bee137e680bc1852cb5c6b00df8fea57eb971804e92746d213a19"
+}
diff --git a/packages/core/src/managed-runtime/http-managed-session-store.test.ts b/packages/core/src/managed-runtime/http-managed-session-store.test.ts
index dcdf425bc5c..41569a276eb 100644
--- a/packages/core/src/managed-runtime/http-managed-session-store.test.ts
+++ b/packages/core/src/managed-runtime/http-managed-session-store.test.ts
@@ -2023,7 +2023,7 @@ describe('HTTP Managed Session store', () => {
expect(headers.get('X-Qwen-Tenant-Id')).toBe(SESSION_KEY.tenantId);
expect(headers.get('X-Qwen-Managed-Writer-Token')).toBe(TOKEN_A);
expect(headers.get('X-Qwen-Managed-Max-Readable-Storage-Version')).toBe(
- '4',
+ '5',
);
expect(init?.method).toBe('POST');
expect(JSON.parse(String(init?.body))).toEqual(request);
diff --git a/packages/core/src/managed-runtime/http-managed-session-store.ts b/packages/core/src/managed-runtime/http-managed-session-store.ts
index 6a12567c710..75c1d26fc11 100644
--- a/packages/core/src/managed-runtime/http-managed-session-store.ts
+++ b/packages/core/src/managed-runtime/http-managed-session-store.ts
@@ -4,6 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
+import {
+ MANAGED_TOOL_MEASUREMENTS_KIND,
+ parseManagedToolMeasurementEvidence,
+} from './managed-tool-measurements.js';
import {
EXECUTION_DIAGNOSTICS_KIND,
parseManagedExecutionEvidence,
@@ -317,7 +321,7 @@ export function readOnlyManagedSessionSnapshot(value: unknown) {
);
const head = parseRestoreHead(snapshot['head']);
if (
- ![1, 2, 3, 4].includes(head.storageVersion) ||
+ ![1, 2, 3, 4, 5].includes(head.storageVersion) ||
!['ACTIVE', 'SEALED'].includes(head.state) ||
head.recoveryStatus !== 'READY' ||
head.compactedThroughRevision !== 0
@@ -471,6 +475,13 @@ export function readOnlyManagedSessionSnapshot(value: unknown) {
if (resources.size !== references.size) {
throw corrupt('snapshot resource enumeration conflicts.');
}
+ if (
+ head.storageVersion < 5 &&
+ [...resources.values()].some(
+ ({ ref }) => ref.kind === MANAGED_TOOL_MEASUREMENTS_KIND,
+ )
+ )
+ throw corrupt('Tool measurements require storage version 5.');
const executionPresent = [...resources.values()].some(
({ ref }) => ref.kind === EXECUTION_DIAGNOSTICS_KIND,
);
@@ -1206,6 +1217,13 @@ class ManagedSessionStoreHttpClient {
this.sessionKey,
);
requireStoredTransactionMatches(transaction, descriptor);
+ if (
+ head.storageVersion < 5 &&
+ descriptor.refs.some(
+ (ref) => ref.kind === MANAGED_TOOL_MEASUREMENTS_KIND,
+ )
+ )
+ throw corrupt('Tool measurements require storage version 5.');
if (
head.storageVersion < 4 &&
descriptor.refs.some((ref) => ref.kind === EXECUTION_DIAGNOSTICS_KIND)
@@ -1442,7 +1460,7 @@ class ManagedSessionStoreHttpClient {
raw['sessionId'] !== this.sessionKey.sessionId ||
snapshot.snapshotId.length === 0 ||
Buffer.byteLength(snapshot.snapshotId, 'utf8') > 512 ||
- ![1, 2, 3, 4].includes(storageVersion) ||
+ ![1, 2, 3, 4, 5].includes(storageVersion) ||
storageVersion > head.storageVersion ||
snapshot.coveredJournalRevision < 1 ||
snapshot.coveredJournalRevision > head.journalRevision ||
@@ -1554,6 +1572,11 @@ class ManagedSessionStoreHttpClient {
const declared = append(transaction).refs;
for (const resource of added) {
const body = await this.readResource(resource);
+ if (
+ storageVersion < 5 &&
+ resource.kind === MANAGED_TOOL_MEASUREMENTS_KIND
+ )
+ throw corrupt('Tool measurements require storage version 5.');
if (
storageVersion < 4 &&
resource.kind === EXECUTION_DIAGNOSTICS_KIND
@@ -1978,6 +2001,13 @@ class ManagedSessionStoreHttpClient {
grant.supportedStorageVersion < 4
)
throw corrupt('Execution diagnostics require storage version 4.');
+ if (
+ descriptor.refs.some(
+ (ref) => ref.kind === MANAGED_TOOL_MEASUREMENTS_KIND,
+ ) &&
+ grant.supportedStorageVersion < 5
+ )
+ throw corrupt('Tool measurements require storage version 5.');
const publicationIds = new Set(
descriptor.refs.flatMap((ref) => {
const id = this.publicationAdmissions.get(ref.resourceId);
@@ -2255,7 +2285,7 @@ class ManagedSessionStoreHttpClient {
method,
headers: {
Accept: 'application/json',
- 'X-Qwen-Managed-Max-Readable-Storage-Version': '4',
+ 'X-Qwen-Managed-Max-Readable-Storage-Version': '5',
...(readOnly ? {} : { 'Content-Type': 'application/json' }),
[HTTP_MANAGED_SESSION_STORE_CONTRACT.tenantHeader]:
this.sessionKey.tenantId,
@@ -2797,7 +2827,7 @@ class ManagedSessionStoreHttpClient {
const grant = this.requireGrant();
if (
head.state !== 'ACTIVE' ||
- ![1, 2, 3, 4].includes(head.storageVersion) ||
+ ![1, 2, 3, 4, 5].includes(head.storageVersion) ||
head.storageVersion > grant.supportedStorageVersion ||
head.writerGeneration !== grant.writerGeneration ||
head.recoveryStatus !== 'READY' ||
@@ -3011,7 +3041,7 @@ class ManagedSessionStoreHttpClient {
redirect: 'error',
headers: {
Accept: accept,
- 'X-Qwen-Managed-Max-Readable-Storage-Version': '4',
+ 'X-Qwen-Managed-Max-Readable-Storage-Version': '5',
[HTTP_MANAGED_SESSION_STORE_CONTRACT.tenantHeader]:
this.sessionKey.tenantId,
[HTTP_MANAGED_SESSION_STORE_CONTRACT.writerTokenHeader]:
@@ -3510,6 +3540,12 @@ export function collectNestedResourceRefs(
parseHostedRecoveryResource(bytes, undefined, ref.kind),
);
}
+ if (ref.kind === MANAGED_TOOL_MEASUREMENTS_KIND) {
+ if (ref.schemaVersion !== 1)
+ throw corrupt('Unsupported tool measurements version.');
+ parseManagedToolMeasurementEvidence(bytes);
+ return [];
+ }
if (ref.kind === EXECUTION_DIAGNOSTICS_KIND) {
if (ref.schemaVersion !== 1)
throw corrupt('Unsupported execution diagnostics version.');
diff --git a/packages/core/src/managed-runtime/managed-execution-diagnostics.test.ts b/packages/core/src/managed-runtime/managed-execution-diagnostics.test.ts
index 1a49c6896fd..aa8fd9fe17d 100644
--- a/packages/core/src/managed-runtime/managed-execution-diagnostics.test.ts
+++ b/packages/core/src/managed-runtime/managed-execution-diagnostics.test.ts
@@ -22,6 +22,10 @@ import type { ChatRecord } from '../services/chatRecordingService.js';
import { assertManagedSessionDurableRef } from './managed-session-records.js';
import type { ManagedSessionKey } from './managed-session-records.js';
import { scanManagedSessionJournal } from './managed-session-storage.js';
+import {
+ recordManagedToolMeasurements,
+ parseManagedToolMeasurementEvidence,
+} from './managed-tool-measurements.js';
const fixture = JSON.parse(
await fs.readFile(
@@ -47,7 +51,13 @@ afterEach(async () => {
await fs.rm(dir, { recursive: true, force: true });
});
-async function setup(collect = true) {
+async function setup(
+ collect = true,
+ tool: { name: string; args: Record } = {
+ name: 'read_file',
+ args: { file_path: 'a.txt' },
+ },
+) {
const dir = await fs.mkdtemp(
path.join(os.tmpdir(), 'execution-diagnostics-'),
);
@@ -90,8 +100,7 @@ async function setup(collect = true) {
{
functionCall: {
id: 'reused',
- name: 'read_file',
- args: { file_path: 'a.txt' },
+ ...tool,
},
},
],
@@ -101,6 +110,151 @@ async function setup(collect = true) {
}
describe('Managed execution diagnostics', () => {
+ it.each(['original', 'modified', 'unknown'] as const)(
+ 'records executor evidence once across cancellation and reopen, with proposal provenance %s',
+ async (proposal) => {
+ const args = { file_path: 'a.txt', content: 'one\n' };
+ const { session, record, options } = await setup(true, {
+ name: 'write_file',
+ args,
+ });
+ await session.sink.write(record('measured'));
+ const authority = session.authority;
+ const observation =
+ getManagedExecutionDiagnostics(authority)!.observation('reused')!;
+ observation.executionCallId = 'exec-1';
+ const diagnosticsRef = await getManagedExecutionDiagnostics(
+ authority,
+ )!.publish('intent:exec-1', [observation]);
+ const ref = await session.resources.publish(
+ 'managed-tool-outcome',
+ Buffer.from(JSON.stringify({ executionCallId: 'exec-1' })),
+ );
+ const inputRef =
+ proposal === 'unknown'
+ ? ref
+ : await session.resources.publish(
+ 'managed-tool-input',
+ Buffer.from(
+ JSON.stringify({
+ payloadJson: JSON.stringify({
+ toolName: 'write_file',
+ input: {
+ ...args,
+ content:
+ proposal === 'modified' ? 'one\ntwo\n' : args.content,
+ },
+ }),
+ }),
+ ),
+ );
+ await authority.appendExecutionEvent(
+ {
+ operation: 'recordToolIntent',
+ commandId: 'exec-1',
+ sessionKey: options.sessionKey,
+ contentDigest: ref.digest,
+ },
+ (sequence) => ({
+ v: 1,
+ sequence,
+ eventId: 'intent:exec-1',
+ sessionKey: options.sessionKey,
+ kind: 'tool.intent',
+ occurredAt: Date.now(),
+ subject: {
+ type: 'activation',
+ scopeId: session.activation.activationId,
+ activationId: session.activation.activationId,
+ epoch: session.activation.epoch,
+ },
+ payload: {
+ executionCallId: 'exec-1',
+ batchId: 'measured',
+ ordinal: 0,
+ toolDefinitionRef: ref,
+ argsRef: inputRef,
+ outcomeSource: 'runtime',
+ diagnosticsRef,
+ },
+ }),
+ { class: 'harness', activation: session.activation },
+ );
+ const metadata = {
+ resultDigest: 'b'.repeat(64),
+ measurements: {
+ protocol: 'managed-tool-measurements/1' as const,
+ executionDurationMs: 0,
+ executionOutcome: 'success' as const,
+ file: {
+ operation: 'write' as const,
+ status: 'measured' as const,
+ modelLinesAdded: 2,
+ modelLinesRemoved: 0,
+ appliedLinesAdded: 2,
+ appliedLinesRemoved: 0,
+ },
+ },
+ };
+ await recordManagedToolMeasurements(session, 'exec-1', ref, metadata);
+ expect(
+ parseManagedToolMeasurementEvidence(
+ await session.resources.read(
+ assertManagedSessionDurableRef(
+ authority.lastEventOfKind('tool.measured')!.payload[
+ 'measurementsRef'
+ ],
+ 'measurement',
+ ),
+ ),
+ ).modelProposal,
+ ).toBe(proposal);
+ const sequence = authority.committedSequence;
+ await recordManagedToolMeasurements(session, 'exec-1', ref, metadata);
+ const cancelled = await session.resources.publish(
+ 'managed-tool-outcome',
+ Buffer.from(
+ JSON.stringify({ executionCallId: 'exec-1', status: 'cancelled' }),
+ ),
+ );
+ await recordManagedToolMeasurements(
+ session,
+ 'exec-1',
+ cancelled,
+ metadata,
+ );
+ expect(authority.committedSequence).toBe(sequence);
+ expect(authority.committedReceipt('exec-1')).toBeUndefined();
+ await expect(
+ recordManagedToolMeasurements(session, 'exec-1', ref, {
+ ...metadata,
+ resultDigest: 'c'.repeat(64),
+ }),
+ ).rejects.toThrow();
+ await session.close();
+ sessions.splice(sessions.indexOf(session), 1);
+ const restored = await openManagedSession(options);
+ sessions.push(restored);
+ await recordManagedToolMeasurements(restored, 'exec-1', ref, metadata);
+ await recordManagedToolMeasurements(
+ restored,
+ 'exec-1',
+ cancelled,
+ metadata,
+ );
+ expect(
+ restored.authority
+ .eventsInSequenceRange(1, restored.authority.committedSequence)
+ .filter((event) => event.kind === 'tool.measured'),
+ ).toHaveLength(1);
+ expect(
+ restored.authority.lastEventOfKind('tool.measured')?.payload[
+ 'toolOutcomeRef'
+ ],
+ ).toEqual(ref);
+ expect(restored.authority.committedReceipt('exec-1')).toBeUndefined();
+ },
+ );
it.each([false, true])(
'binds committed record digests to diagnostic evidence (collect=%s)',
async (collect) => {
diff --git a/packages/core/src/managed-runtime/managed-session-records.test.ts b/packages/core/src/managed-runtime/managed-session-records.test.ts
index ab35776b68c..d12bdd75795 100644
--- a/packages/core/src/managed-runtime/managed-session-records.test.ts
+++ b/packages/core/src/managed-runtime/managed-session-records.test.ts
@@ -996,7 +996,7 @@ describe('managed session header', () => {
it('refuses a newer format or reader requirement', () => {
headerError({ formatVersion: 2 }, /is not supported by this reader/);
headerError(
- { minimumReader: 'managed-session/3' },
+ { minimumReader: 'managed-session/4' },
/is not supported by this reader/,
);
});
diff --git a/packages/core/src/managed-runtime/managed-session-records.ts b/packages/core/src/managed-runtime/managed-session-records.ts
index e8733aafe94..29b114f269e 100644
--- a/packages/core/src/managed-runtime/managed-session-records.ts
+++ b/packages/core/src/managed-runtime/managed-session-records.ts
@@ -36,7 +36,7 @@ export const MANAGED_SESSION_FORMAT_VERSION = 1;
// created in between (H3 round-5 verification matrix), so the stamp
// rises only when a change genuinely breaks an older reader mid-scan.
export const MANAGED_SESSION_MINIMUM_READER = 'managed-session/1';
-export const MANAGED_SESSION_READER_CAPABILITY = 'managed-session/2';
+export const MANAGED_SESSION_READER_CAPABILITY = 'managed-session/3';
const MANAGED_SESSION_DOMAIN_RECORD_VERSION = 1;
const MAX_ERROR_VALUE_LENGTH = 4096;
@@ -54,6 +54,7 @@ export const MANAGED_SESSION_EVENT_KINDS = [
'tool.intent',
'action.changed',
'tool.receipt',
+ 'tool.measured',
'checkpoint.committed',
'context.compacted',
'cancel.requested',
@@ -1004,6 +1005,13 @@ const EVENT_SCHEMAS: Readonly> =
decisionRef: 'refOrNull',
},
},
+ 'tool.measured': {
+ fields: {
+ executionCallId: 'id',
+ toolOutcomeRef: 'ref',
+ measurementsRef: 'ref',
+ },
+ },
'tool.receipt': {
fields: {
executionCallId: 'id',
@@ -1104,6 +1112,7 @@ const EVENT_ACTORS: Readonly<
'hosted.cleanup': ['harness'],
'action.changed': ['harness', 'trusted_entry'],
'tool.receipt': ['trusted_entry'],
+ 'tool.measured': ['harness'],
'checkpoint.committed': ['harness'],
'context.compacted': ['harness'],
'cancel.requested': ['trusted_entry'],
@@ -1129,6 +1138,7 @@ const ACTIVATION_SUBJECT_KINDS: Readonly<
'hosted.cleanup': true,
'action.changed': false,
'tool.receipt': false,
+ 'tool.measured': true,
'checkpoint.committed': true,
'context.compacted': true,
'cancel.requested': false,
@@ -1471,6 +1481,7 @@ function assertPayloadRules(
case 'tool.intent':
case 'message.delta':
case 'tool.receipt':
+ case 'tool.measured':
case 'cancel.requested':
case 'turn.settled':
return;
diff --git a/packages/core/src/managed-runtime/managed-tool-measurements.test.ts b/packages/core/src/managed-runtime/managed-tool-measurements.test.ts
new file mode 100644
index 00000000000..2d0e6ac150b
--- /dev/null
+++ b/packages/core/src/managed-runtime/managed-tool-measurements.test.ts
@@ -0,0 +1,58 @@
+import { readFile } from 'node:fs/promises';
+import { describe, expect, it } from 'vitest';
+import { managedToolDigest } from '../tools/managed-tool-protocol.js';
+import {
+ parseManagedToolMeasurementEvidence,
+ parseManagedToolMeasurements,
+} from './managed-tool-measurements.js';
+
+const fixture = JSON.parse(
+ await readFile(
+ new URL(
+ './contracts/managed-tool-measurements-v1.fixtures.json',
+ import.meta.url,
+ ),
+ 'utf8',
+ ),
+) as {
+ cases: Array<{ id: string; valid: boolean; measurements: unknown }>;
+ evidence: Record;
+ wire: unknown;
+ resultDigest: string;
+};
+
+describe('Managed executor measurements shared contract', () => {
+ it.each(fixture.cases)('$id (valid=$valid)', ({ valid, measurements }) => {
+ if (valid)
+ expect(parseManagedToolMeasurements(measurements)).toEqual(measurements);
+ else expect(() => parseManagedToolMeasurements(measurements)).toThrow();
+ });
+
+ it('uses the same canonical UTF-8 result digest as Java', () => {
+ expect(managedToolDigest(fixture.wire)).toBe(fixture.resultDigest);
+ expect(
+ parseManagedToolMeasurementEvidence(
+ Buffer.from(JSON.stringify(fixture.evidence)),
+ ),
+ ).toEqual(fixture.evidence);
+ });
+
+ it('bounds evidence bytes and rejects opaque contents and missing bindings', () => {
+ for (const patch of [
+ { invocationId: 'wrong' },
+ { executionCallId: 'x'.repeat(513) },
+ { parameters: 'private' },
+ { schemaVersion: 2 },
+ { modelProposal: 'guessed' },
+ ]) {
+ expect(() =>
+ parseManagedToolMeasurementEvidence(
+ Buffer.from(JSON.stringify({ ...fixture.evidence, ...patch })),
+ ),
+ ).toThrow();
+ }
+ expect(() =>
+ parseManagedToolMeasurementEvidence(Buffer.alloc(4097, ' ')),
+ ).toThrow();
+ });
+});
diff --git a/packages/core/src/managed-runtime/managed-tool-measurements.ts b/packages/core/src/managed-runtime/managed-tool-measurements.ts
new file mode 100644
index 00000000000..57d2c36349b
--- /dev/null
+++ b/packages/core/src/managed-runtime/managed-tool-measurements.ts
@@ -0,0 +1,314 @@
+/**
+ * @license
+ * Copyright 2026 Qwen Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { createHash } from 'node:crypto';
+import { isDeepStrictEqual } from 'node:util';
+import type { ManagedSession } from './managed-session-assembly.js';
+import {
+ ManagedSessionRecordError,
+ assertManagedSessionDurableRef,
+ type ManagedSessionDurableRef,
+ type ManagedSessionEvent,
+} from './managed-session-records.js';
+import { managedInvocationId } from './managed-execution-diagnostics.js';
+import { readManagedMessageBody } from './managed-message-chunks.js';
+import type { ChatRecord } from '../services/chatRecordingService.js';
+
+export const MANAGED_TOOL_MEASUREMENTS_KIND = 'managed-tool-measurements';
+export const MANAGED_TOOL_MEASUREMENTS_MAX_BYTES = 4096;
+
+export interface ManagedFileMeasurement {
+ operation: 'read' | 'write' | 'edit';
+ status: 'measured' | 'unchanged' | 'unmeasured';
+ modelLinesAdded: number | null;
+ modelLinesRemoved: number | null;
+ appliedLinesAdded: number | null;
+ appliedLinesRemoved: number | null;
+}
+
+export interface ManagedToolMeasurements {
+ protocol: 'managed-tool-measurements/1';
+ executionDurationMs: number | null;
+ executionOutcome:
+ | 'not_started'
+ | 'success'
+ | 'error'
+ | 'cancelled'
+ | 'unknown';
+ file: ManagedFileMeasurement | null;
+}
+
+export interface ManagedToolMeasurementEvidence {
+ schemaVersion: 1;
+ invocationId: string;
+ executionCallId: string;
+ resultDigest: string;
+ modelProposal: 'original' | 'modified' | 'unknown';
+ measurements: ManagedToolMeasurements | null;
+}
+
+export interface ManagedToolResultMeasurements {
+ measurements: ManagedToolMeasurements | null;
+ resultDigest: string;
+}
+
+function invalid(): never {
+ throw new ManagedSessionRecordError('Invalid Managed tool measurements.');
+}
+
+function closed(value: unknown, keys: string[]): Record {
+ if (!value || typeof value !== 'object' || Array.isArray(value)) invalid();
+ const record = value as Record;
+ if (Object.keys(record).sort().join(',') !== keys.sort().join(',')) invalid();
+ return record;
+}
+
+function count(value: unknown): boolean {
+ return value === null || (Number.isSafeInteger(value) && Number(value) >= 0);
+}
+
+export function parseManagedToolMeasurements(
+ value: unknown,
+): ManagedToolMeasurements {
+ const body = closed(value, [
+ 'protocol',
+ 'executionDurationMs',
+ 'executionOutcome',
+ 'file',
+ ]);
+ if (
+ body['protocol'] !== 'managed-tool-measurements/1' ||
+ !count(body['executionDurationMs']) ||
+ !['not_started', 'success', 'error', 'cancelled', 'unknown'].includes(
+ String(body['executionOutcome']),
+ )
+ )
+ invalid();
+ if (body['file'] !== null) {
+ const file = closed(body['file'], [
+ 'operation',
+ 'status',
+ 'modelLinesAdded',
+ 'modelLinesRemoved',
+ 'appliedLinesAdded',
+ 'appliedLinesRemoved',
+ ]);
+ const fields = [
+ 'modelLinesAdded',
+ 'modelLinesRemoved',
+ 'appliedLinesAdded',
+ 'appliedLinesRemoved',
+ ];
+ if (
+ body['executionOutcome'] !== 'success' ||
+ !['read', 'write', 'edit'].includes(String(file['operation'])) ||
+ !['measured', 'unchanged', 'unmeasured'].includes(
+ String(file['status']),
+ ) ||
+ !fields.every((field) => count(file[field])) ||
+ (file['modelLinesAdded'] === null) !==
+ (file['modelLinesRemoved'] === null) ||
+ (file['appliedLinesAdded'] === null) !==
+ (file['appliedLinesRemoved'] === null)
+ )
+ invalid();
+ if (file['operation'] === 'read') {
+ if (
+ file['status'] !== 'measured' ||
+ !fields.every((field) => file[field] === null)
+ )
+ invalid();
+ } else if (file['status'] === 'unmeasured') {
+ if (file['appliedLinesAdded'] !== null) invalid();
+ } else if (
+ fields.some((field) => file[field] === null) ||
+ (file['status'] === 'unchanged') !==
+ (file['appliedLinesAdded'] === 0 && file['appliedLinesRemoved'] === 0)
+ )
+ invalid();
+ }
+ return structuredClone(body) as unknown as ManagedToolMeasurements;
+}
+
+export function parseManagedToolMeasurementEvidence(
+ bytes: Buffer,
+): ManagedToolMeasurementEvidence {
+ if (bytes.byteLength > MANAGED_TOOL_MEASUREMENTS_MAX_BYTES) invalid();
+ const body = closed(JSON.parse(bytes.toString('utf8')), [
+ 'schemaVersion',
+ 'invocationId',
+ 'executionCallId',
+ 'resultDigest',
+ 'modelProposal',
+ 'measurements',
+ ]);
+ if (
+ body['schemaVersion'] !== 1 ||
+ typeof body['invocationId'] !== 'string' ||
+ !/^[0-9a-f]{64}$/.test(body['invocationId']) ||
+ typeof body['resultDigest'] !== 'string' ||
+ !/^[0-9a-f]{64}$/.test(body['resultDigest']) ||
+ typeof body['executionCallId'] !== 'string' ||
+ !body['executionCallId'].length ||
+ Buffer.byteLength(body['executionCallId']) > 512 ||
+ /[\ud800-\udfff]/u.test(body['executionCallId']) ||
+ !['original', 'modified', 'unknown'].includes(String(body['modelProposal']))
+ )
+ invalid();
+ if (body['measurements'] !== null)
+ parseManagedToolMeasurements(body['measurements']);
+ return body as unknown as ManagedToolMeasurementEvidence;
+}
+
+export function toolMeasurementCommitDigest(
+ executionCallId: string,
+ outcome: ManagedSessionDurableRef,
+ measurements: ManagedSessionDurableRef,
+): string {
+ return createHash('sha256')
+ .update(
+ JSON.stringify([
+ 'managed-tool-measured/1',
+ executionCallId,
+ outcome.digest,
+ measurements.digest,
+ ]),
+ )
+ .digest('hex');
+}
+
+async function modelProposal(
+ session: ManagedSession,
+ intent: ManagedSessionEvent,
+ measurements: ManagedToolMeasurements | null,
+): Promise {
+ if (!measurements?.file || measurements.file.operation === 'read')
+ return 'unknown';
+ const source = session.authority.committedEvent(
+ `message:${String(intent.payload['batchId'])}`,
+ );
+ if (!source) return 'unknown';
+ const inputRef = assertManagedSessionDurableRef(
+ intent.payload['argsRef'],
+ 'tool input',
+ );
+ if (inputRef.kind !== 'managed-tool-input') return 'unknown';
+ const record = JSON.parse(
+ (
+ await readManagedMessageBody(
+ (ref) => session.resources.read(ref),
+ assertManagedSessionDurableRef(source.payload['contentRef'], 'message'),
+ )
+ ).toString('utf8'),
+ ) as ChatRecord;
+ const call = record.message?.parts?.filter((part) => part.functionCall)[
+ Number(intent.payload['ordinal'])
+ ]?.functionCall;
+ const routed = JSON.parse(
+ (await session.resources.read(inputRef)).toString(),
+ );
+ const dispatched = JSON.parse(routed.payloadJson) as {
+ toolName: string;
+ input: unknown;
+ };
+ if (!call || call.name !== dispatched.toolName) return 'unknown';
+ return isDeepStrictEqual(call.args, dispatched.input)
+ ? 'original'
+ : 'modified';
+}
+
+/** A separate source event preserves receipts already committed by v3 publication. */
+export async function recordManagedToolMeasurements(
+ session: ManagedSession,
+ executionCallId: string,
+ toolOutcomeRef: ManagedSessionDurableRef,
+ result: ManagedToolResultMeasurements | undefined,
+): Promise {
+ if (!result) return;
+ const authority = session.authority;
+ const events = authority.eventsInSequenceRange(
+ 1,
+ authority.committedSequence,
+ );
+ const intent = events.find(
+ (event) =>
+ event.kind === 'tool.intent' &&
+ event.payload['executionCallId'] === executionCallId,
+ );
+ // An execution admitted before collection started stays outside that window.
+ if (!intent?.payload['diagnosticsRef']) return;
+ const body: ManagedToolMeasurementEvidence = {
+ schemaVersion: 1,
+ invocationId: managedInvocationId(
+ authority.sessionHeader.sessionKey,
+ String(intent.payload['batchId']),
+ Number(intent.payload['ordinal']),
+ ),
+ executionCallId,
+ resultDigest: result.resultDigest,
+ modelProposal: await modelProposal(session, intent, result.measurements),
+ measurements: result.measurements,
+ };
+ const bytes = Buffer.from(JSON.stringify(body));
+ parseManagedToolMeasurementEvidence(bytes);
+ const existing = events.find(
+ (event) =>
+ event.kind === 'tool.measured' &&
+ event.payload['executionCallId'] === executionCallId,
+ );
+ if (existing) {
+ const ref = assertManagedSessionDurableRef(
+ existing.payload['measurementsRef'],
+ 'tool measurements',
+ );
+ if (
+ !isDeepStrictEqual(
+ parseManagedToolMeasurementEvidence(await session.resources.read(ref)),
+ body,
+ )
+ )
+ invalid();
+ return;
+ }
+ const measurementsRef = await session.resources.publish(
+ MANAGED_TOOL_MEASUREMENTS_KIND,
+ bytes,
+ );
+ await authority.appendExecutionEvent(
+ {
+ operation: 'recordToolMeasurements',
+ commandId: executionCallId,
+ sessionKey: authority.sessionHeader.sessionKey,
+ contentDigest: toolMeasurementCommitDigest(
+ executionCallId,
+ toolOutcomeRef,
+ measurementsRef,
+ ),
+ },
+ (sequence) => ({
+ v: 1,
+ sequence,
+ eventId: `tool-measured:${executionCallId}`,
+ sessionKey: authority.sessionHeader.sessionKey,
+ kind: 'tool.measured',
+ occurredAt: Date.now(),
+ subject: {
+ type: 'activation',
+ scopeId: session.activation.activationId,
+ activationId: session.activation.activationId,
+ epoch: session.activation.epoch,
+ },
+ payload: { executionCallId, toolOutcomeRef, measurementsRef },
+ }),
+ {
+ class: 'harness',
+ activation: {
+ activationId: session.activation.activationId,
+ epoch: session.activation.epoch,
+ },
+ },
+ );
+}
diff --git a/packages/managed-agent-server/configuration-descriptions.json b/packages/managed-agent-server/configuration-descriptions.json
index 1d4d0826b33..b0c59d5683e 100644
--- a/packages/managed-agent-server/configuration-descriptions.json
+++ b/packages/managed-agent-server/configuration-descriptions.json
@@ -829,6 +829,10 @@
"en": "The database user the control plane connects as. Besides rights on the control plane's database, it needs `SELECT` on `performance_schema.user_variables_by_thread`, which the preflight `--check` verifies.",
"zh": "控制面连接数据库所用的用户。除控制面数据库的权限外,还需要 `performance_schema.user_variables_by_thread` 的 `SELECT` 权限,预检 `--check` 会校验该权限。",
"type": "string"
+ },
+ "qwen.managed-agent.runtime-broker.tool-measurements": {
+ "en": "Whether newly resolved ordinary Workspace bindings select the attested managed-workspace-execution/2 profile and Tool v4 numeric execution/file measurements. Defaults to false; existing bindings, extension/MCP owners, resident and CSI profiles retain their contracts. Deploy compatible Worker/Broker readers first. Measurement commits reuse existing activity evidence and require a storage-version-5 grant; no separate collector flag is introduced. See the [execution diagnostics design](../design/2026-11-07-managed-execution-diagnostics.md).",
+ "zh": "是否让新解析的普通 Workspace binding 选择经过 attestation 的 managed-workspace-execution/2 profile 和 Tool v4 数值执行/文件测量。默认 false;已有 binding、extension/MCP owner、resident 和 CSI profile 保留原契约。先部署兼容 Worker/Broker reader。测量提交复用现有 activity evidence,并要求 storage grant 支持 version 5;不引入独立 collector 开关。参见[执行诊断设计](../design/2026-11-07-managed-execution-diagnostics.zh-CN.md)。"
}
}
}
diff --git a/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/event/ManagedEventEnvelopes.java b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/event/ManagedEventEnvelopes.java
index 39c43751325..a0d999660a0 100644
--- a/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/event/ManagedEventEnvelopes.java
+++ b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/event/ManagedEventEnvelopes.java
@@ -52,12 +52,12 @@ public final class ManagedEventEnvelopes {
private static final Pattern DIGEST = Pattern.compile("[0-9a-f]{64}");
public static final String KIND_WAKE_REQUESTED = "wake.requested";
- // Ordered (the failure message joins it) — the frozen contract's 20-kind
- // vocabulary in fixture order.
+ // Ordered (the failure message joins it) — the journal vocabulary
+ // in fixture order.
public static final List KINDS = List.of(
"input.accepted", KIND_WAKE_REQUESTED, "activation.changed",
"model.attempt", "message.committed", "tool.intent",
- "action.changed", "tool.receipt", "checkpoint.committed",
+ "action.changed", "tool.receipt", "tool.measured", "checkpoint.committed",
"context.compacted", "cancel.requested", "turn.settled",
"config.bound", "lifecycle.changed", "domain.committed",
"message.delta", "message.retracted", "operation.replayed",
diff --git a/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedActivityDiagnostics.java b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedActivityDiagnostics.java
index c7878e16b1d..24f291cd2fd 100644
--- a/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedActivityDiagnostics.java
+++ b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedActivityDiagnostics.java
@@ -68,6 +68,18 @@ public final class ManagedActivityDiagnostics {
return DigestUtils.sha256Hex(digest.toString().getBytes(StandardCharsets.UTF_8));
}
+ public static boolean matchesCommit(List events, String operation, String commandId, String digest) {
+ for (JsonNode event : events) {
+ if ("tool.measured".equals(event.path("kind").asText())) {
+ return events.size() == 1 && "recordToolMeasurements".equals(operation)
+ && event.path("payload").path("executionCallId").asText().equals(commandId)
+ && ManagedToolMeasurements.commitDigest(event.path("payload")).equals(digest);
+ }
+ }
+ return events.stream().noneMatch(event -> event.path("payload").has("diagnosticsRef"))
+ || carrierDigest(events).equals(digest);
+ }
+
public static void requireEvidence(JsonNode body) {
closed(body, Set.of("schemaVersion", "reducerVersion", "epochId", "startEventId", "sourceEventId",
"observations", "coverageReasons", "omittedObservationCount"), "execution diagnostics");
@@ -184,6 +196,9 @@ public final class ManagedActivityDiagnostics {
next.set(field, resource);
continue;
}
+ if ("execution".equals(field) && before.path("_measurementOutcomeKnown").asBoolean()) {
+ continue;
+ }
if ("execution".equals(field) && List.of("success", "error", "cancelled").contains(before.path(field).asText())
&& !before.path(field).equals(value) && !executorAuthority) {
next.put("conflictingEvidence", true);
@@ -232,11 +247,30 @@ public final class ManagedActivityDiagnostics {
contribution(summary, before, -1);
}
contribution(summary, after, 1);
+ measurementContribution(summary, before, -1);
+ measurementContribution(summary, after, 1);
+ for (String field : List.of("tools", "files")) {
+ ManagedToolMeasurements.availability((ObjectNode) summary.path(field));
+ }
+ for (String operation : List.of("read", "write", "edit")) {
+ ManagedToolMeasurements.availability((ObjectNode) summary.path("fileOperations").path(operation));
+ }
if (after.path("conflictingEvidence").asBoolean()) {
gap(summary, "conflicting_evidence");
}
}
+ private static void measurementContribution(ObjectNode summary, JsonNode fact, int delta) {
+ if (fact == null) {
+ return;
+ }
+ ManagedToolMeasurements.contribute((ObjectNode) summary.path("tools"), fact, delta);
+ if (!fact.path("file").isNull()) {
+ ManagedToolMeasurements.contribute((ObjectNode) summary.path("files"), fact, delta);
+ ManagedToolMeasurements.contribute((ObjectNode) summary.path("fileOperations").path(fact.path("file").path("operation").asText()), fact, delta);
+ }
+ }
+
private static void contribution(ObjectNode summary, JsonNode fact, int delta) {
add((ObjectNode) summary.path("tools"), fact, delta);
if ("unknown".equals(fact.path("toolKind").asText())) {
diff --git a/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedExtensionRecords.java b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedExtensionRecords.java
index 0cd6cd4a761..bb8803b8c9f 100644
--- a/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedExtensionRecords.java
+++ b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedExtensionRecords.java
@@ -44,7 +44,7 @@ public final class ManagedExtensionRecords {
public static final List EVENT_KINDS = List.of("input.accepted",
"wake.requested", "activation.changed", "model.attempt",
"message.committed", "tool.intent", "action.changed",
- "tool.receipt", "checkpoint.committed", "context.compacted",
+ "tool.receipt", "tool.measured", "checkpoint.committed", "context.compacted",
"cancel.requested", "turn.settled", "config.bound",
"lifecycle.changed", "domain.committed", "message.delta",
"message.retracted", "operation.replayed", "hosted.batch.planned", "hosted.cleanup");
diff --git a/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedStorageVersions.java b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedStorageVersions.java
index 28b9e82a659..b180413758a 100644
--- a/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedStorageVersions.java
+++ b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedStorageVersions.java
@@ -1,6 +1,6 @@
package com.alibaba.qwen.code.managedagent.contracts.persistence;
public final class ManagedStorageVersions {
- public static final int SUPPORTED = 4;
+ public static final int SUPPORTED = 5;
private ManagedStorageVersions() { }
}
diff --git a/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedToolMeasurements.java b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedToolMeasurements.java
new file mode 100644
index 00000000000..6d563dd89dd
--- /dev/null
+++ b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/contracts/persistence/ManagedToolMeasurements.java
@@ -0,0 +1,172 @@
+package com.alibaba.qwen.code.managedagent.contracts.persistence;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.commons.codec.digest.DigestUtils;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Set;
+
+import static com.alibaba.qwen.code.managedagent.contracts.persistence.ManagedExtensionRecords.closed;
+import static com.alibaba.qwen.code.managedagent.contracts.persistence.ManagedExtensionRecords.count;
+import static com.alibaba.qwen.code.managedagent.contracts.persistence.ManagedExtensionRecords.oneOf;
+
+/** Bounded executor evidence; observation v1 and legacy receipts stay unchanged. */
+public final class ManagedToolMeasurements {
+ public static final String KIND = "managed-tool-measurements";
+ public static final int MAX_BYTES = 4096;
+ private static final long MAX_COUNT = 9_007_199_254_740_991L;
+ private static final List LINES = List.of("modelLinesAdded", "modelLinesRemoved", "appliedLinesAdded", "appliedLinesRemoved");
+ private static final List COUNTERS = List.of("recorded", "unsupported", "partial", "executionDurationMs", "executionDurationSamples",
+ "fileSamples", "modelDiffSamples", "appliedDiffSamples", "modelLinesAdded", "modelLinesRemoved", "appliedLinesAdded", "appliedLinesRemoved");
+
+ private ManagedToolMeasurements() { }
+
+ public static void requireMeasurements(JsonNode body) {
+ closed(body, Set.of("protocol", "executionDurationMs", "executionOutcome", "file"), "tool measurements");
+ require("managed-tool-measurements/1".equals(body.path("protocol").asText()), "Invalid measurement protocol.");
+ nullableCount(body.get("executionDurationMs"));
+ oneOf(body.get("executionOutcome"), List.of("not_started", "success", "error", "cancelled", "unknown"), "executionOutcome");
+ JsonNode file = body.get("file");
+ require(file != null, "Missing file measurement.");
+ if (file.isNull()) {
+ return;
+ }
+ require("success".equals(body.path("executionOutcome").asText()), "File measurement requires execution success.");
+ closed(file, Set.of("operation", "status", "modelLinesAdded", "modelLinesRemoved", "appliedLinesAdded", "appliedLinesRemoved"), "file measurement");
+ oneOf(file.get("operation"), List.of("read", "write", "edit"), "operation");
+ oneOf(file.get("status"), List.of("measured", "unchanged", "unmeasured"), "status");
+ LINES.forEach(field -> nullableCount(file.get(field)));
+ require(file.path("modelLinesAdded").isNull() == file.path("modelLinesRemoved").isNull()
+ && file.path("appliedLinesAdded").isNull() == file.path("appliedLinesRemoved").isNull(), "Incomplete line measurement.");
+ if ("read".equals(file.path("operation").asText())) {
+ require("measured".equals(file.path("status").asText()) && LINES.stream().allMatch(field -> file.path(field).isNull()), "Invalid read measurement.");
+ } else if ("unmeasured".equals(file.path("status").asText())) {
+ require(file.path("appliedLinesAdded").isNull(), "Unmeasured changes have no applied diff.");
+ } else {
+ require(LINES.stream().noneMatch(field -> file.path(field).isNull())
+ && "unchanged".equals(file.path("status").asText()) == (file.path("appliedLinesAdded").longValue() == 0
+ && file.path("appliedLinesRemoved").longValue() == 0), "Invalid measured changes.");
+ }
+ }
+
+ public static void requireEvidence(JsonNode body) {
+ closed(body, Set.of("schemaVersion", "invocationId", "executionCallId", "resultDigest", "modelProposal", "measurements"), "tool measurement evidence");
+ count(body.get("schemaVersion"), 1, 1, "schemaVersion");
+ oneOf(body.get("modelProposal"), List.of("original", "modified", "unknown"), "modelProposal");
+ for (String field : List.of("invocationId", "resultDigest")) {
+ require(body.path(field).isTextual() && body.path(field).asText().matches("[0-9a-f]{64}"), "Invalid measurement digest.");
+ }
+ JsonNode execution = body.path("executionCallId");
+ require(execution.isTextual() && !execution.asText().isEmpty() && execution.asText().getBytes(StandardCharsets.UTF_8).length <= 512
+ && execution.asText().codePoints().noneMatch(code -> code >= 0xd800 && code <= 0xdfff), "Invalid measurement execution identity.");
+ require(body.has("measurements") && body.toString().getBytes(StandardCharsets.UTF_8).length <= MAX_BYTES, "Measurement evidence exceeds its limit.");
+ if (!body.path("measurements").isNull()) {
+ requireMeasurements(body.get("measurements"));
+ }
+ }
+
+ public static String commitDigest(JsonNode payload) {
+ return DigestUtils.sha256Hex(JsonNodeFactory.instance.arrayNode().add("managed-tool-measured/1")
+ .add(payload.path("executionCallId").asText()).add(payload.path("toolOutcomeRef").path("digest").asText())
+ .add(payload.path("measurementsRef").path("digest").asText()).toString().getBytes(StandardCharsets.UTF_8));
+ }
+
+ public static ObjectNode publicMeasurements(JsonNode body, long sequence) {
+ JsonNode packet = body.path("measurements");
+ ObjectNode result = JsonNodeFactory.instance.objectNode().put("sourceSequence", Long.toString(sequence));
+ result.put("availability", packet.isNull() ? "unsupported"
+ : packet.path("executionDurationMs").isNull() || "unmeasured".equals(packet.path("file").path("status").asText()) ? "partial" : "available");
+ if (packet.isNull() || packet.path("executionDurationMs").isNull()) {
+ result.putNull("executionDurationMs");
+ } else {
+ result.put("executionDurationMs", Long.toString(packet.path("executionDurationMs").longValue()));
+ }
+ result.set("file", packet.path("file").isMissingNode() ? JsonNodeFactory.instance.nullNode() : packet.path("file").deepCopy());
+ if (result.path("file").isObject()) {
+ ObjectNode file = (ObjectNode) result.path("file");
+ LINES.forEach(field -> {
+ if (!file.path(field).isNull()) {
+ file.put(field, Long.toString(file.path(field).longValue()));
+ }
+ });
+ if (!"read".equals(file.path("operation").asText()) && !"original".equals(body.path("modelProposal").asText())) {
+ file.putNull("modelLinesAdded").putNull("modelLinesRemoved");
+ result.put("availability", "partial");
+ }
+ }
+ return result;
+ }
+
+ public static void contribute(ObjectNode metrics, JsonNode fact, int sign) {
+ if (fact == null || !fact.has("measurements")) {
+ return;
+ }
+ ObjectNode totals;
+ if (metrics.path("measurements").isObject()) {
+ totals = (ObjectNode) metrics.path("measurements");
+ } else {
+ totals = metrics.putObject("measurements");
+ COUNTERS.forEach(field -> totals.put(field, "0"));
+ }
+ JsonNode measured = fact.path("measurements");
+ add(totals, "recorded", "1", sign);
+ if ("unsupported".equals(measured.path("availability").asText())) {
+ add(totals, "unsupported", "1", sign);
+ }
+ if ("partial".equals(measured.path("availability").asText())) {
+ add(totals, "partial", "1", sign);
+ }
+ if (!measured.path("executionDurationMs").isNull()) {
+ add(totals, "executionDurationMs", measured.path("executionDurationMs").asText(), sign);
+ add(totals, "executionDurationSamples", "1", sign);
+ }
+ JsonNode file = measured.path("file");
+ if (!file.isNull()) {
+ add(totals, "fileSamples", "1", sign);
+ for (String pair : List.of("model", "applied")) {
+ if (!file.path(pair + "LinesAdded").isNull()) {
+ add(totals, pair + "DiffSamples", "1", sign);
+ for (String direction : List.of("Added", "Removed")) {
+ add(totals, pair + "Lines" + direction, file.path(pair + "Lines" + direction).asText(), sign);
+ }
+ }
+ }
+ }
+ }
+
+ public static void availability(ObjectNode metrics) {
+ if (metrics.path("measurements").isObject()) {
+ ObjectNode totals = (ObjectNode) metrics.path("measurements");
+ String recorded = totals.path("recorded").asText();
+ totals.put("availability", "0".equals(recorded) ? "not_recorded"
+ : recorded.equals(totals.path("unsupported").asText()) ? "unsupported"
+ : recorded.equals(metrics.path("requested").asText()) && "0".equals(totals.path("unsupported").asText()) && "0".equals(totals.path("partial").asText()) ? "available" : "partial");
+ }
+ }
+
+ private static void add(ObjectNode totals, String field, String value, int sign) {
+ BigInteger next = new BigInteger(totals.path(field).asText()).add(new BigInteger(value).multiply(BigInteger.valueOf(sign)));
+ require(next.signum() >= 0, "Negative measurement contribution.");
+ totals.put(field, next.toString());
+ }
+
+ private static void nullableCount(JsonNode value) {
+ require(value != null, "Missing measurement count.");
+ if (!value.isNull()) {
+ BigDecimal number = value.isNumber() && Double.isFinite(value.doubleValue()) ? value.decimalValue() : null;
+ require(number != null && number.stripTrailingZeros().scale() <= 0 && number.signum() >= 0
+ && number.compareTo(BigDecimal.valueOf(MAX_COUNT)) <= 0, "Invalid measurement count.");
+ }
+ }
+
+ private static void require(boolean condition, String message) {
+ if (!condition) {
+ throw new ManagedExtensionRecords.InvalidRecordException(message);
+ }
+ }
+}
diff --git a/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/settings/ManagedAgentSettings.java b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/settings/ManagedAgentSettings.java
index 215153de891..94a1ad69d67 100644
--- a/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/settings/ManagedAgentSettings.java
+++ b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/managedagent/settings/ManagedAgentSettings.java
@@ -158,6 +158,10 @@ public interface ManagedAgentSettings {
public String getWorkspaceCwd();
public String getIsolationClass();
public String getBoundSessionIsolationClass();
+ public default boolean isToolMeasurements() {
+ return false;
+ }
+
public boolean isSessionCreateWarm();
public List extends WorkspaceMount> getWorkspaceMounts();
public String getWorkspaceCreationRoot();
diff --git a/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/runtimebroker/RuntimeToolMeasurements.java b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/runtimebroker/RuntimeToolMeasurements.java
new file mode 100644
index 00000000000..c52e55d7789
--- /dev/null
+++ b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/runtimebroker/RuntimeToolMeasurements.java
@@ -0,0 +1,100 @@
+package com.alibaba.qwen.code.runtimebroker;
+
+import com.alibaba.qwen.code.managedagent.contracts.persistence.ManagedToolMeasurements;
+import com.alibaba.qwen.code.managedagent.repository.ManagedExtensionRecordStore;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.apache.commons.codec.digest.DigestUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/** Private v4 transport metadata; never part of a legacy tool-result envelope. */
+public final class RuntimeToolMeasurements {
+ public static final String FIELD = "diagnostics";
+
+ private RuntimeToolMeasurements() {}
+
+ public static boolean selected(Map reference) {
+ return Integer.valueOf(4).equals(reference.get("resultProtocol"));
+ }
+
+ public static int executionProtocol(Map reference) {
+ return "deferred_v3".equals(reference.get("dispatchMode"))
+ || Integer.valueOf(3).equals(reference.get("runtimeProtocol"))
+ ? 3
+ : 2;
+ }
+
+ public static void requireProfile(RuntimeSession session) {
+ if (session == null
+ || !WorkspaceExecutionProfile.MEASURED_CAPABILITY_DIGEST.equals(
+ session.getScope().getCapabilityDigest())) {
+ throw new RuntimeBrokerException(
+ 501,
+ "runtime_tool_v4_unsupported",
+ "Runtime measurement profile is unavailable.",
+ false);
+ }
+ }
+
+ public static Map base(Map result) {
+ Map base = new LinkedHashMap<>(result);
+ base.remove(FIELD);
+ return BrokerValues.immutableMap(base);
+ }
+
+ public static Map attach(
+ int protocol, Map result, Object measurements, Object digest) {
+ if (!(digest instanceof String value) || !value.matches("[0-9a-f]{64}")) {
+ throw new RuntimeBrokerException(
+ 502, "runtime_tool_v4_invalid", "Measured result digest is invalid.", false);
+ }
+ Map body = new LinkedHashMap<>();
+ body.put("executionProtocol", protocol);
+ body.put("result", result);
+ body.put("measurements", measurements);
+ JsonNode node =
+ ManagedExtensionRecordStore.parse(
+ new String(JsonCodec.encode(body), StandardCharsets.UTF_8));
+ if (node == null
+ || !value.equals(DigestUtils.sha256Hex(CsiNativeActivationProof.canonical(node)))) {
+ throw new RuntimeBrokerException(
+ 502, "runtime_tool_v4_invalid", "Measured result digest conflicts.", false);
+ }
+ if (measurements != null) {
+ JsonNode measured = node.get("measurements");
+ ManagedToolMeasurements.requireMeasurements(measured);
+ }
+ Map metadata = new LinkedHashMap<>();
+ metadata.put("protocolVersion", 4);
+ metadata.put("executionProtocol", protocol);
+ metadata.put("measurements", measurements);
+ metadata.put("resultDigest", value);
+ Map combined = new LinkedHashMap<>(result);
+ combined.put(FIELD, BrokerValues.immutableMap(metadata));
+ return BrokerValues.immutableMap(combined);
+ }
+
+ public static Map metadata(Map result) {
+ Object raw = result.get(FIELD);
+ if (!(raw instanceof Map, ?> map)
+ || !map.keySet()
+ .equals(
+ Set.of(
+ "protocolVersion",
+ "executionProtocol",
+ "measurements",
+ "resultDigest"))) {
+ throw new RuntimeBrokerException(
+ 409,
+ "runtime_tool_measurements_missing",
+ "Original measured result is unavailable.",
+ false);
+ }
+ @SuppressWarnings("unchecked")
+ Map metadata = (Map) map;
+ return metadata;
+ }
+}
diff --git a/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/runtimebroker/RuntimeTransport.java b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/runtimebroker/RuntimeTransport.java
index c189c598a0f..e6d0755bfca 100644
--- a/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/runtimebroker/RuntimeTransport.java
+++ b/packages/managed-agent-server/contracts/src/main/java/com/alibaba/qwen/code/runtimebroker/RuntimeTransport.java
@@ -20,6 +20,57 @@ import java.util.concurrent.CompletionStage;
* completion only with {@code true}.
*/
public interface RuntimeTransport {
+ public default CompletionStage
))}
- {metrics && kind !== 'skills' && }
+ {metrics && kind !== 'skills' && (
+
+ )}
+ {kind !== 'skills' && statistics?.measurementStartSequence && (
+
+ {t('measurementStart')} · {statistics.measurementStartSequence}
+
+ )}
{kind === 'files' && statistics && (
{(['read', 'write', 'edit'] as const).map((operation) => (
@@ -209,7 +287,10 @@ export function ManagedActivityView({
{t(operation)} ·{' '}
{statistics.fileOperations[operation].requested}
-
+
))}
@@ -297,6 +378,45 @@ export function ManagedActivityView({
{t(item.capture === 'partial' ? 'partialState' : item.capture)}
+ {kind !== 'skills' && (
+ <>
+ {t('measurementCoverage')}
+
+ {item.measurements
+ ? t(
+ item.measurements.availability === 'partial'
+ ? 'partialState'
+ : item.measurements.availability,
+ )
+ : t('not_recorded')}
+
+ {t('executionDuration')}
+
+ {item.measurements?.executionDurationMs != null
+ ? `${item.measurements.executionDurationMs} ms`
+ : t('unmeasured')}
+
+ {item.measurements?.file &&
+ item.measurements.file.operation !== 'read' && (
+ <>
+ {t('fileChanges')}
+ {t(item.measurements.file.status)}
+ {t('modelLines')}
+
+ {item.measurements.file.modelLinesAdded != null
+ ? `+${item.measurements.file.modelLinesAdded} / −${item.measurements.file.modelLinesRemoved}`
+ : t('unmeasured')}
+
+ {t('appliedLines')}
+
+ {item.measurements.file.appliedLinesAdded != null
+ ? `+${item.measurements.file.appliedLinesAdded} / −${item.measurements.file.appliedLinesRemoved}`
+ : t('unmeasured')}
+
+ >
+ )}
+ >
+ )}
{item.skill && (
<>
{t(item.skill.resourceKind)}
diff --git a/packages/web-shell/client/components/managed/ManagedDiagnosticsPanel.test.tsx b/packages/web-shell/client/components/managed/ManagedDiagnosticsPanel.test.tsx
index f229708d1e5..feb63c02540 100644
--- a/packages/web-shell/client/components/managed/ManagedDiagnosticsPanel.test.tsx
+++ b/packages/web-shell/client/components/managed/ManagedDiagnosticsPanel.test.tsx
@@ -261,6 +261,60 @@ describe('ManagedDiagnosticsPanel', () => {
expect(document.body.textContent).toContain('assistant-1');
});
+ it('shows measured zero, independent model/applied lines and a sample-based duration mean', async () => {
+ const statistics = structuredClone(activityStatistics);
+ statistics.statistics!.tools.requested = 4;
+ statistics.statistics!.tools.measurements = {
+ availability: 'partial',
+ recorded: '2',
+ unsupported: '0',
+ partial: '0',
+ executionDurationMs: '6',
+ executionDurationSamples: '2',
+ fileSamples: '2',
+ modelDiffSamples: '1',
+ appliedDiffSamples: '1',
+ modelLinesAdded: '3',
+ modelLinesRemoved: '1',
+ appliedLinesAdded: '0',
+ appliedLinesRemoved: '0',
+ };
+ statistics.statistics!.measurementStartSequence = '9';
+ vi.mocked(provider.getActivityStatistics!).mockResolvedValue(statistics);
+ const page = structuredClone(activityPage);
+ page.items[0].delivery = 'unavailable';
+ page.items[0].measurements = {
+ availability: 'available',
+ sourceSequence: '9',
+ executionDurationMs: '0',
+ file: {
+ operation: 'edit',
+ status: 'unchanged',
+ modelLinesAdded: '3',
+ modelLinesRemoved: '1',
+ appliedLinesAdded: '0',
+ appliedLinesRemoved: '0',
+ },
+ };
+ vi.mocked(provider.listActivity!).mockResolvedValue(page);
+ await render();
+ await selectTab('Statistics');
+ await act(async () =>
+ [...document.querySelectorAll('button')]
+ .find((button) => button.textContent === 'Tools')!
+ .click(),
+ );
+ const text = document.body.textContent!;
+ expect(text).toContain('3.0 ms');
+ expect(text).not.toContain('1.5 ms');
+ expect(text).toContain('0 ms');
+ expect(text).toContain('+3 / −1');
+ expect(text).toContain('+0 / −0');
+ expect(text).toContain('Unchanged');
+ expect(text).toContain('First measurement sequence');
+ expect(text).toContain('DeliveryUnavailable');
+ });
+
it.each(['statistics-session', 'page-session', 'page-kind'])(
'rejects mismatched activity identity: %s',
async (mismatch) => {
diff --git a/packages/web-shell/client/components/managed/generated/managed-agent-api.ts b/packages/web-shell/client/components/managed/generated/managed-agent-api.ts
index 704f5ecae10..10c1da1e677 100644
--- a/packages/web-shell/client/components/managed/generated/managed-agent-api.ts
+++ b/packages/web-shell/client/components/managed/generated/managed-agent-api.ts
@@ -3076,6 +3076,7 @@ export interface components {
unavailable: number;
unknown: number;
};
+ measurements?: components["schemas"]["ManagedActivityMeasurementTotals"];
};
ManagedActivityFile: {
/** @enum {string} */
@@ -3120,6 +3121,7 @@ export interface components {
conflictingEvidence?: boolean;
taskState?: string;
taskRuntimeState?: string | null;
+ measurements?: components["schemas"]["ManagedActivityMeasurement"];
};
ManagedActivityStatistics: {
tools: components["schemas"]["ManagedActivityMetrics"];
@@ -3156,6 +3158,7 @@ export interface components {
write: components["schemas"]["ManagedActivityMetrics"];
edit: components["schemas"]["ManagedActivityMetrics"];
};
+ measurementStartSequence?: string;
};
ManagedDiagnosticActivityStats: {
/** @constant */
@@ -3233,6 +3236,7 @@ export interface components {
conflictingEvidence?: boolean;
taskState?: string;
taskRuntimeState?: string | null;
+ measurements?: components["schemas"]["ManagedActivityMeasurement"];
}[];
hasMore: boolean;
nextCursor: string | null;
@@ -3279,6 +3283,7 @@ export interface components {
conflictingEvidence?: boolean;
taskState?: string;
taskRuntimeState?: string | null;
+ measurements?: components["schemas"]["ManagedActivityMeasurement"];
}[];
hasMore: boolean;
nextCursor: string | null;
@@ -3293,6 +3298,38 @@ export interface components {
limit?: number;
};
ManagedDiagnosticActivityPage: components["schemas"]["ManagedActivityToolsPage"] | components["schemas"]["ManagedActivityFilesPage"] | components["schemas"]["ManagedActivitySkillsPage"];
+ ManagedActivityMeasurement: {
+ sourceSequence: string;
+ /** @enum {string} */
+ availability: "available" | "partial" | "unsupported";
+ executionDurationMs: string | null;
+ file: {
+ /** @enum {string} */
+ operation: "read" | "write" | "edit";
+ /** @enum {string} */
+ status: "measured" | "unchanged" | "unmeasured";
+ modelLinesAdded: string | null;
+ modelLinesRemoved: string | null;
+ appliedLinesAdded: string | null;
+ appliedLinesRemoved: string | null;
+ } | null;
+ };
+ ManagedActivityMeasurementTotals: {
+ /** @enum {string} */
+ availability: "available" | "partial" | "not_recorded" | "unsupported";
+ recorded: string;
+ unsupported: string;
+ executionDurationMs: string;
+ executionDurationSamples: string;
+ fileSamples: string;
+ modelDiffSamples: string;
+ appliedDiffSamples: string;
+ modelLinesAdded: string;
+ modelLinesRemoved: string;
+ appliedLinesAdded: string;
+ appliedLinesRemoved: string;
+ partial: string;
+ };
};
responses: {
/** @description The authenticated actor belongs to another tenant than X-Qwen-Tenant-Id or has an invalid ID (actor_scope_mismatch, which the tenant filter answers on every /v1/agents/ and WebShell route), or the resource is readable but the actor lacks this operation or the original Action responder permission. Artifact content reads may also return artifact_content_forbidden when the actor cannot read the original bytes. */
diff --git a/packages/web-shell/client/components/managed/managed-diagnostics-messages.ts b/packages/web-shell/client/components/managed/managed-diagnostics-messages.ts
index f38d04e7ddf..43e29c7027c 100644
--- a/packages/web-shell/client/components/managed/managed-diagnostics-messages.ts
+++ b/packages/web-shell/client/components/managed/managed-diagnostics-messages.ts
@@ -16,6 +16,25 @@ const messages = {
skillLoads: ['Skill loading', 'Skill 加载'],
collectionStart: ['Observed since', '自此时刻起统计'],
requested: ['Requests', '请求次数'],
+ available: ['Available', '已采集'],
+ measurementMeaning: [
+ 'Executor duration excludes approval, queues and delivery. Line counts accumulate native write/edit operations, not the current Git diff. Older and unsupported paths have no measured samples.',
+ '执行耗时不含审批、排队和交付。行数按原生写入/编辑操作累计,不代表当前 Git diff。旧数据与不支持的路径不计入测量样本。',
+ ],
+ measurementCoverage: ['Measurement coverage', '测量覆盖'],
+ executionDuration: ['Executor duration', '执行耗时'],
+ meanDuration: ['Mean measured duration', '有效样本平均耗时'],
+ samples: ['samples', '样本'],
+ modelLines: [
+ 'Model proposed lines (write/edit)',
+ '模型提议行数(写入/编辑)',
+ ],
+ appliedLines: ['Applied lines (write/edit)', '实际应用行数(写入/编辑)'],
+ unmeasured: ['Unmeasured', '未测量'],
+ measured: ['Measured', '已测量'],
+ unchanged: ['Unchanged', '无变化'],
+ fileChanges: ['Applied changes', '应用变更'],
+ measurementStart: ['First measurement sequence', '首个测量来源 sequence'],
allowed: ['Allowed', '已准入'],
denied: ['Denied', '已拒绝'],
invalid: ['Invalid arguments', '参数无效'],
diff --git a/packages/web-shell/client/e2e/managed-diagnostics.spec.ts b/packages/web-shell/client/e2e/managed-diagnostics.spec.ts
index 038699e8418..56e8aecff07 100644
--- a/packages/web-shell/client/e2e/managed-diagnostics.spec.ts
+++ b/packages/web-shell/client/e2e/managed-diagnostics.spec.ts
@@ -169,6 +169,15 @@ for (const width of [1280, 390]) {
page,
}, testInfo) => {
type Schemas = components['schemas'];
+ const measuredItem: Schemas['ManagedActivityInvocation'] = {
+ ...activityItem,
+ measurements: {
+ availability: 'available',
+ sourceSequence: '9',
+ executionDurationMs: '0',
+ file: null,
+ },
+ };
const errors: string[] = [];
const unexpected: string[] = [];
const activityRequests: Array<{ kind: string; cursor?: string }> = [];
@@ -219,6 +228,25 @@ for (const width of [1280, 390]) {
...activityStatistics,
statistics: {
...activityStatistics.statistics!,
+ measurementStartSequence: '9',
+ tools: {
+ ...activityStatistics.statistics!.tools,
+ measurements: {
+ availability: 'available',
+ recorded: '1',
+ unsupported: '0',
+ partial: '0',
+ executionDurationMs: '0',
+ executionDurationSamples: '1',
+ fileSamples: '0',
+ modelDiffSamples: '0',
+ appliedDiffSamples: '0',
+ modelLinesAdded: '0',
+ modelLinesRemoved: '0',
+ appliedLinesAdded: '0',
+ appliedLinesRemoved: '0',
+ },
+ },
skills: activityStatistics.statistics!.tools,
skillLoads: activityStatistics.statistics!.tools,
skillBuckets: [
@@ -304,7 +332,7 @@ for (const width of [1280, 390]) {
? [skillItem]
: body.cursor
? [deniedItem]
- : [activityItem];
+ : [measuredItem];
return route.fulfill({
json: {
...header,
@@ -366,6 +394,9 @@ for (const width of [1280, 390]) {
await dialog.getByRole('tab', { name: 'Statistics', exact: true }).click();
await dialog.getByRole('button', { name: 'Tools', exact: true }).click();
await expect(dialog).toContainText('Observed since');
+ await expect(dialog).toContainText('Mean measured duration');
+ await expect(dialog).toContainText('0.0 ms');
+ await expect(dialog).toContainText('First measurement sequence');
await expect(dialog).toContainText(
'Some observations are unavailable or incomplete.',
);
@@ -382,6 +413,9 @@ for (const width of [1280, 390]) {
await expect(
readCall.locator('dt:has-text("Output capture") + dd'),
).toHaveText('Complete');
+ await expect(
+ readCall.locator('dt:has-text("Executor duration") + dd'),
+ ).toHaveText('0 ms');
await page.screenshot({
path: testInfo.outputPath('activity-tools.png'),
animations: 'disabled',