diff --git a/config/hwlab-node-lanes.yaml b/config/hwlab-node-lanes.yaml index 0c303961..708354e2 100644 --- a/config/hwlab-node-lanes.yaml +++ b/config/hwlab-node-lanes.yaml @@ -164,6 +164,12 @@ lanes: - cluster.local clientId: hwlab-v03-workbench-native-l1 hwlabEventConsumerGroupId: hwlab-v03-workbench-native-l1-live-sse + refreshReplay: + groupIdPrefix: hwlab-v03-workbench-native-l1-refresh + timeoutMs: 30000 + scanLimit: 1000000 + matchedEventLimit: 2000 + liveBufferLimit: 2000 api: bindHost: 0.0.0.0 probeHost: 127.0.0.1 @@ -185,7 +191,7 @@ lanes: workbench: realtimeFeatures: liveKafkaSse: true - kafkaRefreshReplay: false + kafkaRefreshReplay: true projectionRealtime: false debugCapabilities: isolatedKafka: false diff --git a/project-management/PJ2026-01/specs/PJ2026-010401080313-workbench-realtime-authority.md b/project-management/PJ2026-01/specs/PJ2026-010401080313-workbench-realtime-authority.md index 90532cfc..468f47dc 100644 --- a/project-management/PJ2026-01/specs/PJ2026-010401080313-workbench-realtime-authority.md +++ b/project-management/PJ2026-01/specs/PJ2026-010401080313-workbench-realtime-authority.md @@ -19,7 +19,7 @@ | 短名 | Workbench实时权威 | | 层级 | L4 专项规格切片 | | 状态 | 已生效 | -| 实现引用版本 | draft-2026-07-08-p0-workbench-realtime-authority-v2; draft-2026-07-09-p1-single-step-debug; draft-2026-07-14-p0-pure-kafka-authority | +| 实现引用版本 | draft-2026-07-08-p0-workbench-realtime-authority-v2; draft-2026-07-09-p1-single-step-debug; draft-2026-07-14-p0-pure-kafka-authority; draft-2026-07-20-p0-kafka-sse-single-projection | | 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) | | 上级规格 | [PJ2026-010401 Web工作台](PJ2026-010401-web-workbench.md) | | 废弃规格 | [PJ2026-0104010803 Workbench唯一投影](PJ2026-0104010803-workbench-unique-projection.md) | @@ -45,10 +45,11 @@ PostgreSQL 可以保存查询优化所需的派生读模型,但不是 `agentru ### 2.2 范围内 -- `/v1/workbench/events` typed event 的 authority metadata、cursor、entity family/id/version、projectionRevision 和 terminal seal 语义。 +- `/v1/workbench/events` 交付的 `hwlab.event.v1` schema、Kafka topic/partition/offset、 + stable event identity 和 terminal event 语义。 - Kafka retention 回放 SSE 的 cursor、去重、顺序和回放到 live tail 的切换语义。 -- initial snapshot、explicit detail/history、trace-detail-only payload、session list/detail/messages read model 和主状态投影之间的合并边界。 -- 前端 reducer/adapter 的 authority gate、sealed guard、detail-only rejection、late stale rejection 和 diagnostic 输出。 +- 首次进入、刷新、切页和重连使用同一 Kafka retention→live SSE 重建 session、message、turn、terminal、Final Response 和 Trace 主状态。 +- 前端 reducer/adapter 的事件去重、顺序、detail-only rejection 和 diagnostic 输出。 - 自动恢复、SSE error、cursor gap、cross-tab projection signal 和 refresh/reconnect 的允许动作与禁止动作。 - fake SSE 单步调试页、fixture 来源、调试子标签页、请求禁用规则、可视化证据和 Playwright 回归口径。 - web-probe/OTel/analyzer 对 legacy fan-out、sync/replay authority、cross-page convergence 和 fresh submit terminal/final 的判定口径。 @@ -65,30 +66,30 @@ PostgreSQL 可以保存查询优化所需的派生读模型,但不是 `agentru | 术语 | 定义 | | --- | --- | -| Realtime Authority v2 | Workbench 主状态只能由 initial snapshot、`hwlab.event.v1` 的 live/replay SSE typed event 和显式 detail/history 中各自被授权的输入更新的规则集合。 | -| 高纯度 SSE | Workbench live correctness 优先由 SSE typed event 驱动;断线、重复、乱序和缺口通过同一 durable cursor/replay 语义处理,不回退到多个业务 REST endpoint 自行补事实。 | +| Realtime Authority v2 | Workbench 主状态只能由 `hwlab.event.v1` 的 retention/live SSE typed event 更新;首次进入、刷新、切页和重连不接受 HTTP 业务快照。 | +| 纯 Kafka+SSE | Workbench 主状态只由 Kafka retention→live SSE 驱动;断线、重复、乱序和缺口通过同一事件合同与 reducer 处理,不存在业务 REST 快照参与、竞争或仲裁。 | | Kafka replay SSE | SSE 服务按客户端 cursor 从 `hwlab.event.v1` retention 回放 typed event,追上水位后在同一连接继续 live tail;replay 与 live 使用同一事件 schema 和 reducer。 | | 多源补洞 | 前端在 automatic recovery 中同时或顺序调用 `/turns`、`/sessions/:id/messages`、`/traces/:id/events`、旧 `/v1/agent/*` 等端点来推断 terminal/final/message/session 主状态。该模式禁止。 | | detail-only | 只服务 Trace detail、历史页、诊断和审计的 payload。它可以展示过程,不能覆盖主 message/finalResponse/turn/session authority。 | -| terminal seal | 同一 durable projection revision 内写入的 message/part terminal status、turn terminal、Final Response、timing、session running=false 和 cursor checkpoint。 | -| cross-page convergence | control page、observer page、刷新页和多标签页在同一 durable event/sync 输入下收敛到同一 message count、trace ids、turn status 和 Final Response digest。 | -| 单步调试工作台 | Cloud Web 内的独立调试路由,用 fake fixture 逐条喂入 snapshot/SSE/sync/detail 输入,展示 reducer 决策和 UI 投影,不访问真实 Workbench mutation。 | +| terminal event | 在同一 `hwlab.event.v1` 顺序中写入 message terminal status、turn terminal、Final Response、timing 和 session running=false 的业务事件;它与其他业务事件使用同一路径,不建立第二种状态权威。 | +| cross-page convergence | control page、observer page、刷新页和多标签页在同一 Kafka retention/live SSE 输入下收敛到同一 message count、trace ids、turn status 和 Final Response digest。 | +| 单步调试工作台 | Cloud Web 内的独立调试路由,用 fake fixture 逐条喂入 Kafka SSE typed event,展示 reducer 决策和 UI 投影,不访问真实 Workbench mutation。 | ## 4. 权威输入和禁止路径 Workbench 主状态只接受以下输入: -- `initial snapshot`:页面初始加载或 explicit user refresh 的水位输入,只能写入其声明的 session/message/turn/trace bucket。 -- `SSE typed event`:live delivery 主路径,必须携带 `contractVersion`、`realtimeAuthority`、`entity.family/id/version`、cursor 和 `projectionRevision`。 -- `Kafka replay SSE`:服务端按 cursor 从 `hwlab.event.v1` retention 回放,与 live event 使用同一 schema、authority metadata 和 reducer action。 +- `Kafka replay SSE`:首次进入、刷新、切页和重连时,服务端从 `hwlab.event.v1` retention 回放当前授权 session 的完整事件序列。 +- `live SSE`:回放追上 barrier 后,在同一连接、同一 event schema 和同一 reducer 上继续交付 `hwlab.event.v1`。 - `explicit detail/history`:用户显式打开的 trace detail、历史分页或详情读取,只能写 detail bucket 或 diagnostic bucket;声明 `detailProjection=true` 或 `authority=trace-detail-only` 时必须拒绝写主状态。 -- `optimistic local echo`:submit admission 前后的本地临时投影,必须有 stable id,对账后由 durable event/sync 覆盖;不得成为跨页面或刷新后的事实源。 +- `optimistic local echo`:submit admission 后的瞬时交互反馈;首个正式 Kafka 事件到达后必须移除或以同一 stable id 收敛,且不得进入跨页面、刷新或重连状态。 禁止路径: +- 页面初始加载、刷新、切页或重连通过 session list/detail/messages、turn、trace 或 result HTTP 响应写入 Workbench 主状态。 - automatic recovery、SSE error、cursor gap、submit reattach、terminal projection 或 cross-tab signal 直接调用 `/v1/workbench/turns/:id`、`/v1/workbench/sessions/:id/messages`、`/v1/workbench/traces/:id/events` 或旧 `/v1/agent/turns|traces|chat/result` 写主状态。 - 用 trace tail、trace detail 末行、message text、session list preview、elapsed timeout、localStorage、DOM active card 或 analyzer fallback 推断 `running`、terminal、Final Response 或 session active。 -- 让 late session list/detail/messages、detail-only trace payload、transport diagnostic、requestfailed 或 stale running snapshot 覆盖 sealed entity version。 +- 为 session list/detail/messages、detail-only trace payload、transport diagnostic、requestfailed 或 stale running snapshot 建立主状态仲裁、优先级或 sealed guard。 - 用 reload、切换 session、自动 repair helper、测试后门或 probe analyzer suppress 把已经分裂的页面补成通过。 - 用 `/v1/workbench/sync`、数据库 outbox replay、HTTP polling 或读模型查询替代 Kafka retention replay,或在 Kafka replay 可用前要求 PostgreSQL schema 就绪。 - 用 Temporal workflow status、activity result、worker 日志、CLI local result 或 native fixture 推断或覆盖 Workbench running、terminal、Final Response、Trace 和 session rail 主状态。 @@ -104,18 +105,28 @@ Cloud Web 应新增独立根导航入口,例如 `/workbench/debug`,navId 可 | 子标签页 | 目标 | 必备能力 | | --- | --- | --- | | 纯 SSE 单步 | 单独测试 fake SSE 数据且禁用补洞 | 加载事件序列,`Next`/`Back`/`Run`/`Reset`,每步只调用 reducer,禁止 `/sync` 和 detail REST,显示 state diff、message order、turn status、Final Response digest。 | -| Authority Gate | 验证 authority metadata | 输入缺 `realtimeAuthority`、缺 entity、旧 version、detail-only、乱序 cursor 等样本,展示 accept/reject reason 和 diagnostic。 | -| Terminal Seal | 验证 sealed guard | 先写 completed/final,再喂 late running、trace error、detail-only、transport error,断言主正文、status、duration 不回退。 | -| Reconnect/Cursor | 验证重复、断线和 replay | 模拟 duplicate、missing seq、out-of-order、reconnect afterSeq;默认只标 diagnostic,不自动补洞;显式打开 sync 模式时只允许 `/workbench/sync` 等价 replay。 | -| Cross Page | 验证 control/observer 收敛 | 两个隔离 reducer/page model 接收同一 durable SSE/sync 输入或不同到达顺序,比较 message count、trace ids、turn status、Final Response digest。 | +| Schema Gate | 验证唯一业务事件合同 | 输入非 `hwlab.event.v1`、缺 stable identity、detail-only、重复或乱序 Kafka transport identity 等样本,展示 accept/reject reason 和 diagnostic。 | +| Terminal Event | 验证同一路径终态 | 从空 store 顺序重放 user、progress、assistant、terminal 事件,断言正文、status、duration 和 Final Response 由同一 reducer 形成。 | +| Reconnect/Cursor | 验证重复、断线和 replay | 模拟 duplicate、missing seq、out-of-order 和 reconnect;只允许重新建立 retention→live SSE,不调用 `/sync` 或业务 REST 补洞。 | +| Cross Page | 验证 control/observer 收敛 | 两个隔离 reducer/page model 接收同一 Kafka retention/live SSE 输入,比较 message count、trace ids、turn status 和 Final Response digest。 | | Detail Only | 验证 Trace detail 隔离 | 显式展开 trace detail,喂 `trace-detail-only` payload,证明只更新 detail/diagnostic,不写主 message/final/turn/session。 | -| Snapshot Merge | 验证 initial snapshot 与 late read model | 构造 initial snapshot、late list/detail/messages 与 SSE interleave,证明 bucket 合并、stale rejection 和 active session 不变。 | +| Fresh Store Replay | 验证刷新重建 | 每次从空 store 开始,仅重放 Kafka SSE typed event,证明 session、message、turn、terminal、Final Response 和 Trace 完整重建。 | | Fake Provider | 验证 fake-echo/fake model 场景 | 使用 redacted fixture 或 fake provider 生成事件序列,检查 submit -> event -> terminal 的高纯度 SSE 路径,不以真实 provider 成败作为通过条件。 | | Request Ledger | 验证禁止请求 | 记录 debug harness 中所有网络意图;纯 SSE 模式下任何 `/sync`、`/turns`、`/messages`、`/traces` 自动请求都必须红灯。 | ### 5.1 fake 数据来源 -fake 数据优先来自真实受控样本脱敏后的 fixture。合成 fixture 只能补足边界条件,例如 authority 字段缺失、version 回退、duplicate cursor、late running snapshot、detail-only payload、cross-page 到达顺序差异和 provider stream disconnect。每个 fixture 必须记录 `fixtureId`、`schemaVersion`、`capturedFrom` 或 `derivedFrom`、`syntheticReason`、redaction 状态和 expected verdict。 +fake 数据优先来自真实受控样本脱敏后的 fixture。合成 fixture 只能补足边界条件: + +- 非 `hwlab.event.v1` 输入; +- stable identity 缺失或冲突; +- Kafka topic/partition/offset 重复或乱序; +- detail-only payload; +- retention→live handoff 重叠; +- provider stream disconnect。 + +每个 fixture 必须记录 `fixtureId`、`schemaVersion`、`capturedFrom` 或 +`derivedFrom`、`syntheticReason`、redaction 状态和 expected verdict。 纯 SSE 调试的 fixture 应使用 typed event 数组作为第一等输入,不要求启动 fake server。需要浏览器级验证时,可以用 fake EventSource adapter 或 Playwright route 提供 `text/event-stream`,但调试页 reducer 必须能脱离真实 Cloud API 独立运行。 @@ -123,8 +134,9 @@ fake 数据优先来自真实受控样本脱敏后的 fixture。合成 fixture 每个单步场景必须至少输出: -- event header:eventName、type、sessionId、traceId、entity family/id/version、cursor、projectionRevision、detail-only 状态。 -- authority decision:accepted/rejected、reason、diagnostic code、是否写主状态。 +- event header:schema、eventName、type、sessionId、traceId、eventId、 + sourceEventId、Kafka topic/partition/offset 和 detail-only 状态。 +- event decision:accepted/rejected、reason、diagnostic code、是否写主状态。 - state diff:session rail、message list、turn status、Final Response digest、trace detail rows、transport diagnostics。 - request ledger:本步允许和实际发生的网络请求族;纯 SSE 模式必须显示 no-sync/no-backfill。 - triad verdict:DOM-like projection、server-state projection、expected fixture verdict 三者是否一致。 @@ -135,15 +147,19 @@ fake 数据优先来自真实受控样本脱敏后的 fixture。合成 fixture 静态验收: - 自动恢复路径只允许 live/replay `SSE typed event` 或 diagnostic;不得出现 `/v1/workbench/sync`、旧 `/turns`、`/sessions/:id/messages`、`/traces/:id/events` automatic fan-out 写主状态。 +- 首次进入、刷新和切页不得用 session list/detail/messages、turn、trace 或 result HTTP 响应初始化或修改主状态。 +- 前端主状态代码不得保留 HTTP 快照与 Kafka SSE 之间的优先级、sealed guard 或 merge 仲裁。 - `traceHydration*` 命名和运行时口径必须收敛为 `traceDetail*` 或显式 detail read;旧配置字段如保留,只能作为 deprecated alias。 -- `detailProjection=true`、`authority=trace-detail-only` 和缺 authority metadata 的输入必须被 reducer gate 拒绝写主状态,并产生可见 diagnostic。 +- 非 `hwlab.event.v1` 业务输入以及 `detailProjection=true`、 + `authority=trace-detail-only` 的输入必须被 reducer 拒绝写主状态, + 并产生可见 diagnostic。 行为验收: - fresh submit 能在真实入口中自然 terminal/final 收敛,或明确暴露 provider/adapter blocker;不能靠旧补洞掩盖。 - refresh/reconnect/cross-tab signal 后,control/observer/fresh page 不出现 persistent `cross-page-projection-divergence`。 - `workbench-automatic-recovery-fanout-authority`、旧 agent read-through 和旧 Workbench detail fan-out 不回归。 -- fake SSE 单步调试可以在无真实 API、无 `/sync`、无补洞的模式下完成 terminal seal、late stale rejection、detail-only rejection 和两页收敛测试。 +- fake SSE 单步调试可以在无真实 API、无 `/sync`、无补洞的模式下从空 store 完成 session、message、terminal、Final Response、detail-only rejection 和两页收敛测试。 - Cloud API 在 Workbench 数据库 schema 缺失或读模型迁移未完成时仍能启动,并继续 direct publish、live SSE 与 Kafka retention replay;退化项只形成可见 warning/diagnostic。 ## 7. 过程控制 diff --git a/scripts/src/hwlab-native-development.test.ts b/scripts/src/hwlab-native-development.test.ts index e45dc659..bd27bb54 100644 --- a/scripts/src/hwlab-native-development.test.ts +++ b/scripts/src/hwlab-native-development.test.ts @@ -26,7 +26,7 @@ test("L1 Workbench exposure uses YAML HTTPS origin and separate bind/probe addre expect(env.HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT).toBe("0123456789abcdef0123456789abcdef01234567"); expect(env.HWLAB_KAFKA_DIRECT_PUBLISH_ENABLED).toBe("false"); expect(env.HWLAB_WORKBENCH_LIVE_KAFKA_SSE_ENABLED).toBe("true"); - expect(env.HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED).toBe("false"); + expect(env.HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED).toBe("true"); expect(env.HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED).toBe("false"); expect(env.HWLAB_KAFKA_BOOTSTRAP_SERVERS).toBe(native!.kafka.bootstrapServers); expect(native!.kafka.dnsServers).toEqual(["10.43.0.10"]); @@ -38,11 +38,13 @@ test("L1 Workbench exposure uses YAML HTTPS origin and separate bind/probe addre expect(env.HWLAB_KAFKA_CLIENT_ID).toBe(native!.kafka.clientId); expect(env.HWLAB_KAFKA_HWLAB_EVENT_GROUP_ID).toBe(native!.kafka.hwlabEventConsumerGroupId); expect(env.HWLAB_KAFKA_HWLAB_EVENT_GROUP_ID).not.toBe(spec.codeAgentRuntime!.kafkaEventBridge!.hwlabEventConsumerGroupId); + expect(env.HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_GROUP_PREFIX).toBe(native!.kafka.refreshReplay.groupIdPrefix); + expect(env.HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_TIMEOUT_MS).toBe("30000"); expect(env.WORKBENCH_API_BIND_HOST).toBe(native!.api.bindHost); expect(env.WORKBENCH_API_PROBE_HOST).toBe(native!.api.probeHost); expect(env.WORKBENCH_PUBLIC_BASE_URL).toBe("https://lab-dev-workbench.hwpod.com"); expect(env.WORKBENCH_API_IDLE_TIMEOUT_SECONDS).toBe(String(native!.api.idleTimeoutSeconds)); expect(JSON.parse(env.WORKBENCH_WEB_RUNTIME_CONFIG!)).toEqual(native!.web.runtimeConfig); - expect(native!.web.runtimeConfig.workbench.realtimeFeatures).toEqual({ liveKafkaSse: true, kafkaRefreshReplay: false, projectionRealtime: false }); + expect(native!.web.runtimeConfig.workbench.realtimeFeatures).toEqual({ liveKafkaSse: true, kafkaRefreshReplay: true, projectionRealtime: false }); expect(hwlabNativeDevelopmentHelp()).toMatchObject({ ok: true, command: "hwlab nodes native-development workbench" }); }); diff --git a/scripts/src/hwlab-native-development.ts b/scripts/src/hwlab-native-development.ts index fb69094b..dab416ee 100644 --- a/scripts/src/hwlab-native-development.ts +++ b/scripts/src/hwlab-native-development.ts @@ -129,7 +129,7 @@ export function hwlabNativeDevelopmentEnvironment(native: HwlabRuntimeNativeDeve HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: "1", HWLAB_KAFKA_DIRECT_PUBLISH_ENABLED: "false", HWLAB_WORKBENCH_LIVE_KAFKA_SSE_ENABLED: "true", - HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED: "false", + HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED: "true", HWLAB_KAFKA_TRANSACTIONAL_PROJECTOR_ENABLED: "false", HWLAB_KAFKA_PROJECTION_OUTBOX_RELAY_ENABLED: "false", HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED: "false", @@ -140,6 +140,11 @@ export function hwlabNativeDevelopmentEnvironment(native: HwlabRuntimeNativeDeve HWLAB_KAFKA_EVENT_TOPIC: kafkaBridge.hwlabEventTopic, HWLAB_KAFKA_CLIENT_ID: native.kafka.clientId, HWLAB_KAFKA_HWLAB_EVENT_GROUP_ID: native.kafka.hwlabEventConsumerGroupId, + HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_GROUP_PREFIX: native.kafka.refreshReplay.groupIdPrefix, + HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_TIMEOUT_MS: String(native.kafka.refreshReplay.timeoutMs), + HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_SCAN_LIMIT: String(native.kafka.refreshReplay.scanLimit), + HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_MATCHED_EVENT_LIMIT: String(native.kafka.refreshReplay.matchedEventLimit), + HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_LIVE_BUFFER_LIMIT: String(native.kafka.refreshReplay.liveBufferLimit), WORKBENCH_NATIVE_SERVICE_STATE_DIR: native.stateDir, WORKBENCH_NATIVE_STATE_FILE: resolve(spec.workspace, native.stateFile), WORKBENCH_API_BIND_HOST: native.api.bindHost, diff --git a/scripts/src/hwlab-node-lanes.ts b/scripts/src/hwlab-node-lanes.ts index a2ba3f42..ecc4bc81 100644 --- a/scripts/src/hwlab-node-lanes.ts +++ b/scripts/src/hwlab-node-lanes.ts @@ -701,6 +701,13 @@ export interface HwlabRuntimeNativeDevelopmentSpec { readonly dnsSearchDomains: readonly string[]; readonly clientId: string; readonly hwlabEventConsumerGroupId: string; + readonly refreshReplay: { + readonly groupIdPrefix: string; + readonly timeoutMs: number; + readonly scanLimit: number; + readonly matchedEventLimit: number; + readonly liveBufferLimit: number; + }; }; readonly api: { readonly bindHost: string; readonly probeHost: string; readonly port: number; readonly idleTimeoutSeconds: number }; readonly worker: { readonly bindHost: string; readonly probeHost: string; readonly healthPort: number }; @@ -1492,6 +1499,7 @@ function nativeDevelopmentConfig(value: unknown, path: string): HwlabRuntimeNati const displayTime = asRecord(runtimeConfig.displayTime, `${path}.workbench.web.runtimeConfig.displayTime`); const workbenchRuntime = asRecord(runtimeConfig.workbench, `${path}.workbench.web.runtimeConfig.workbench`); const realtimeFeatures = asRecord(workbenchRuntime.realtimeFeatures, `${path}.workbench.web.runtimeConfig.workbench.realtimeFeatures`); + const refreshReplay = asRecord(kafka.refreshReplay, `${path}.workbench.kafka.refreshReplay`); const debugCapabilities = asRecord(workbenchRuntime.debugCapabilities, `${path}.workbench.web.runtimeConfig.workbench.debugCapabilities`); const rawHwlabEventWindow = asRecord(debugCapabilities.rawHwlabEventWindow, `${path}.workbench.web.runtimeConfig.workbench.debugCapabilities.rawHwlabEventWindow`); const traceTimeline = asRecord(workbenchRuntime.traceTimeline, `${path}.workbench.web.runtimeConfig.workbench.traceTimeline`); @@ -1525,6 +1533,13 @@ function nativeDevelopmentConfig(value: unknown, path: string): HwlabRuntimeNati dnsSearchDomains: stringArrayField(kafka, "dnsSearchDomains", `${path}.workbench.kafka`), clientId: stringField(kafka, "clientId", `${path}.workbench.kafka`), hwlabEventConsumerGroupId: stringField(kafka, "hwlabEventConsumerGroupId", `${path}.workbench.kafka`), + refreshReplay: { + groupIdPrefix: stringField(refreshReplay, "groupIdPrefix", `${path}.workbench.kafka.refreshReplay`), + timeoutMs: numberField(refreshReplay, "timeoutMs", `${path}.workbench.kafka.refreshReplay`), + scanLimit: numberField(refreshReplay, "scanLimit", `${path}.workbench.kafka.refreshReplay`), + matchedEventLimit: numberField(refreshReplay, "matchedEventLimit", `${path}.workbench.kafka.refreshReplay`), + liveBufferLimit: numberField(refreshReplay, "liveBufferLimit", `${path}.workbench.kafka.refreshReplay`), + }, }, api: { bindHost: stringField(api, "bindHost", `${path}.workbench.api`), probeHost: stringField(api, "probeHost", `${path}.workbench.api`), port: numberField(api, "port", `${path}.workbench.api`), idleTimeoutSeconds: numberField(api, "idleTimeoutSeconds", `${path}.workbench.api`) }, worker: { bindHost: stringField(worker, "bindHost", `${path}.workbench.worker`), probeHost: stringField(worker, "probeHost", `${path}.workbench.worker`), healthPort: numberField(worker, "healthPort", `${path}.workbench.worker`) },