@clawos-dev/clawd 0.2.333 → 0.2.335

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -6905,7 +6905,7 @@ var init_principal = __esm({
6905
6905
  });
6906
6906
 
6907
6907
  // ../protocol/src/schemas.ts
6908
- var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, SelectOptionSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, ToolFeaturesSchema, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionImportExternalArgs, SessionListArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionInterruptArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionCurrentTurnSenderArgs, SessionSubscribeArgs, SessionPinArgs, SessionArchiveArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, ClientInfoSchema, AuthRequestFrameSchema, AuthOkFrameSchema, DeviceConnectionEntrySchema, DeviceConnectionsResponseSchema, TunnelReadyEventSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema, PROJECT_PORT_MIN, PROJECT_PORT_MAX, projectNameRegex, DEFAULT_DEV_COMMAND, ProjectMetadataSchema, PROJECT_STAGE_VALUES, ProjectStageSchema, ProjectWithStatusSchema, ListProjectsArgsSchema, ListProjectsResultSchema, GetProjectArgsSchema, GetProjectResultSchema, CreateProjectArgsSchema, CreateProjectResultSchema, DeleteProjectArgsSchema, DeleteProjectResultSchema, UpdateProjectPortArgsSchema, UpdateProjectPortResultSchema, SetProdUrlArgsSchema, SetProdUrlResultSchema, StartDevServerArgsSchema, StartDevServerResultSchema, ASSISTANT_REPORTABLE_STAGES, ReportStageArgsSchema, ReportStageResultSchema, StopDevServerArgsSchema, StopDevServerResultSchema, AppBuilderProjectUpdatedEventSchema, PublishArgsSchema, PublishResultSchema, DismissPublishJobArgsSchema, DismissPublishJobResultSchema, AppBuilderPublishProgressEventSchema, AppBuilderPublishFailedEventSchema;
6908
+ var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, SelectOptionSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, ToolFeaturesSchema, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, TURN_END_REASONS, ParsedEventSchema, SessionCreateArgs, SessionImportExternalArgs, SessionListArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionInterruptArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionCurrentTurnSenderArgs, SessionSubscribeArgs, SessionPinArgs, SessionArchiveArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, ClientInfoSchema, AuthRequestFrameSchema, AuthOkFrameSchema, DeviceConnectionEntrySchema, DeviceConnectionsResponseSchema, TunnelReadyEventSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema, PROJECT_PORT_MIN, PROJECT_PORT_MAX, projectNameRegex, DEFAULT_DEV_COMMAND, ProjectMetadataSchema, PROJECT_STAGE_VALUES, ProjectStageSchema, ProjectWithStatusSchema, ListProjectsArgsSchema, ListProjectsResultSchema, GetProjectArgsSchema, GetProjectResultSchema, CreateProjectArgsSchema, CreateProjectResultSchema, DeleteProjectArgsSchema, DeleteProjectResultSchema, UpdateProjectPortArgsSchema, UpdateProjectPortResultSchema, SetProdUrlArgsSchema, SetProdUrlResultSchema, StartDevServerArgsSchema, StartDevServerResultSchema, ASSISTANT_REPORTABLE_STAGES, ReportStageArgsSchema, ReportStageResultSchema, StopDevServerArgsSchema, StopDevServerResultSchema, AppBuilderProjectUpdatedEventSchema, PublishArgsSchema, PublishResultSchema, DismissPublishJobArgsSchema, DismissPublishJobResultSchema, AppBuilderPublishProgressEventSchema, AppBuilderPublishFailedEventSchema;
6909
6909
  var init_schemas = __esm({
6910
6910
  "../protocol/src/schemas.ts"() {
6911
6911
  "use strict";
@@ -7198,6 +7198,7 @@ var init_schemas = __esm({
7198
7198
  multiSelect: external_exports.boolean(),
7199
7199
  options: external_exports.array(AskQuestionOptionSchema).min(1)
7200
7200
  });
7201
+ TURN_END_REASONS = ["completed", "interrupted"];
7201
7202
  ParsedEventSchema = external_exports.discriminatedUnion("kind", [
7202
7203
  // session_init 仅携带 session 标识;resolvedModel 等元信息走 meta_update
7203
7204
  external_exports.object({
@@ -7302,11 +7303,14 @@ var init_schemas = __esm({
7302
7303
  // ① SDK 模式 stream-json `result` 帧(claude.ts)—— stdout 最后一帧,可靠
7303
7304
  // ② TUI 模式 observer 读 JSONL `system/turn_duration` 行(claude-tui.ts parseLine)
7304
7305
  // ⚠️ CC 写此行早于该轮尾段正文落盘,可能被尾随 text 覆盖 lastEventKind(③ 存在的原因)
7305
- // ③ TUI 模式屏幕静止 AND observer 静止(observeScreenIdle + manager.observerIdleWaitMs 复合闸 →
7306
- // dispatchTurnIdle inject)—— 的覆盖;等尾随 text 全部 poll 落盘后补,排在 buffer 末条
7306
+ // ③ TUI 模式 gate 关闭期被 drop ② 信号,屏幕稳定后由 manager.notifyScreenIdle flush
7307
+ // 注入(PR #962 双源确认模型;旧 dispatchTurnIdle 已拆除)—— 排在 buffer 末条
7307
7308
  external_exports.object({
7308
7309
  ...ParsedEventBase,
7309
- kind: external_exports.literal("turn_end")
7310
+ kind: external_exports.literal("turn_end"),
7311
+ // 本轮耗时(ms)。SDK=result.duration_ms;TUI=turn_duration.durationMs;缺失时 reducer 按 turnStartedAt 兜底
7312
+ durationMs: external_exports.number().int().nonnegative().optional(),
7313
+ reason: external_exports.enum(TURN_END_REASONS).optional()
7310
7314
  }),
7311
7315
  // SDK system 帧 task_started / task_progress / task_notification 的归一化产物:
7312
7316
  // 子 agent 执行过程中实时上报 agentId / lastToolName / stats,UI pending 期就能开抽屉、看进度。
@@ -14001,6 +14005,11 @@ function messageFromJsonl(obj) {
14001
14005
  }
14002
14006
  return null;
14003
14007
  }
14008
+ if (type === "system" && o.subtype === "turn_duration") {
14009
+ const msg = { kind: "turn-end", ts, raw: obj };
14010
+ if (typeof o.durationMs === "number") msg.durationMs = o.durationMs;
14011
+ return msg;
14012
+ }
14004
14013
  if (type === "summary" && typeof o.summary === "string") {
14005
14014
  return { kind: "summary", text: o.summary, ts, raw: obj };
14006
14015
  }
@@ -14803,7 +14812,12 @@ function parseClaudeObjectInner(obj) {
14803
14812
  }
14804
14813
  if (type === "usage") return events;
14805
14814
  if (type === "result") {
14806
- events.push({ kind: "turn_end" });
14815
+ const ev = {
14816
+ kind: "turn_end",
14817
+ reason: typeof subtype === "string" && subtype !== "success" ? "interrupted" : "completed"
14818
+ };
14819
+ if (typeof obj.duration_ms === "number") ev.durationMs = obj.duration_ms;
14820
+ events.push(ev);
14807
14821
  return events;
14808
14822
  }
14809
14823
  if (type === "error") {
@@ -44374,6 +44388,7 @@ function cloneState(s) {
44374
44388
  nextSeq: s.nextSeq,
44375
44389
  bufferStartSeq: s.bufferStartSeq,
44376
44390
  turnOpen: s.turnOpen,
44391
+ turnStartedAt: s.turnStartedAt,
44377
44392
  pendingPermissions: s.pendingPermissions,
44378
44393
  pendingQuestions: s.pendingQuestions,
44379
44394
  freshSpawn: s.freshSpawn,
@@ -44392,6 +44407,14 @@ function idleKillDelayOf(state) {
44392
44407
  const ms = state.subSessionMeta?.idleKillDelayMs;
44393
44408
  return ms !== void 0 && ms > 0 ? ms : void 0;
44394
44409
  }
44410
+ var STALE_TURN_END_TOLERANCE_MS = 2e3;
44411
+ function isLateTurnEnd(event, turnStartedAt, now) {
44412
+ if (event.kind !== "turn_end") return false;
44413
+ const durationMs = event.durationMs;
44414
+ if (typeof durationMs !== "number" || typeof turnStartedAt !== "number") return false;
44415
+ const elapsed = now - turnStartedAt;
44416
+ return durationMs > elapsed + STALE_TURN_END_TOLERANCE_MS;
44417
+ }
44395
44418
  function tryFlushPending(state, deps) {
44396
44419
  if (!state.readyForSend || state.pendingSend.length === 0) return [];
44397
44420
  const text = state.pendingSend.shift();
@@ -44632,6 +44655,10 @@ function applyParsedEvent(state, event, deps) {
44632
44655
  }
44633
44656
  function pushEventToBuffer(state, event, deps) {
44634
44657
  const next = cloneState(state);
44658
+ const effects = pushEventToBufferInPlace(next, event, deps);
44659
+ return { state: next, effects };
44660
+ }
44661
+ function pushEventToBufferInPlace(next, event, deps) {
44635
44662
  const seq = next.nextSeq;
44636
44663
  const withSeq = { ...event, seq };
44637
44664
  const entry = { seq, event: withSeq };
@@ -44641,8 +44668,20 @@ function pushEventToBuffer(state, event, deps) {
44641
44668
  if (newBuffer.length === 1) next.bufferStartSeq = seq;
44642
44669
  const isTurnEnd = withSeq.kind === "turn_end";
44643
44670
  const isUserText = withSeq.kind === "user_text";
44644
- if (isTurnEnd) next.turnOpen = false;
44645
- else if (!next.turnOpen) next.turnOpen = true;
44671
+ if (isTurnEnd && next.turnOpen && isLateTurnEnd(withSeq, next.turnStartedAt, deps.now())) {
44672
+ return [emitSessionEvent(next.file.sessionId, withSeq)];
44673
+ }
44674
+ if (isTurnEnd) {
44675
+ const te = withSeq;
44676
+ if (te.durationMs === void 0 && typeof next.turnStartedAt === "number") {
44677
+ te.durationMs = Math.max(0, deps.now() - next.turnStartedAt);
44678
+ }
44679
+ next.turnOpen = false;
44680
+ next.turnStartedAt = null;
44681
+ } else if (!next.turnOpen) {
44682
+ next.turnOpen = true;
44683
+ next.turnStartedAt = deps.now();
44684
+ }
44646
44685
  if (!next.turnOpen && next.buffer.length > deps.bufferCap) {
44647
44686
  const remove = next.buffer.length - deps.bufferCap;
44648
44687
  const trimmed = next.buffer.slice(remove);
@@ -44675,7 +44714,7 @@ function pushEventToBuffer(state, event, deps) {
44675
44714
  });
44676
44715
  }
44677
44716
  }
44678
- return { state: next, effects };
44717
+ return effects;
44679
44718
  }
44680
44719
  var RUNTIME_PATCH_KEYS = [
44681
44720
  "model",
@@ -44714,6 +44753,7 @@ function applyCommand(state, command, deps) {
44714
44753
  next.bufferStartSeq = 0;
44715
44754
  next.nextSeq = 0;
44716
44755
  next.turnOpen = false;
44756
+ next.turnStartedAt = null;
44717
44757
  next.status = "running";
44718
44758
  if (deps.mode === "tui" && (next.file.tool ?? "claude") === "claude") {
44719
44759
  next.readyForSend = false;
@@ -44780,6 +44820,11 @@ function applyCommand(state, command, deps) {
44780
44820
  next.status = "stopping";
44781
44821
  effects.push({ kind: "kill", signal: "SIGKILL" });
44782
44822
  }
44823
+ if (next.turnOpen) {
44824
+ effects.push(
44825
+ ...pushEventToBufferInPlace(next, { kind: "turn_end", reason: "interrupted" }, deps)
44826
+ );
44827
+ }
44783
44828
  return { state: next, effects };
44784
44829
  }
44785
44830
  case "spawn-if-needed": {
@@ -44792,6 +44837,7 @@ function applyCommand(state, command, deps) {
44792
44837
  next.bufferStartSeq = 0;
44793
44838
  next.nextSeq = 0;
44794
44839
  next.turnOpen = false;
44840
+ next.turnStartedAt = null;
44795
44841
  next.status = "running";
44796
44842
  if (deps.mode === "tui" && (next.file.tool ?? "claude") === "claude") {
44797
44843
  next.readyForSend = false;
@@ -44864,6 +44910,7 @@ function applyCommand(state, command, deps) {
44864
44910
  next.nextSeq = 0;
44865
44911
  next.bufferStartSeq = 0;
44866
44912
  next.turnOpen = false;
44913
+ next.turnStartedAt = null;
44867
44914
  next.pendingPermissions = {};
44868
44915
  next.freshSpawn = false;
44869
44916
  next.seenUuids = [];
@@ -44902,6 +44949,7 @@ function applyCommand(state, command, deps) {
44902
44949
  next.nextSeq = 0;
44903
44950
  next.bufferStartSeq = 0;
44904
44951
  next.turnOpen = false;
44952
+ next.turnStartedAt = null;
44905
44953
  next.pendingPermissions = {};
44906
44954
  next.freshSpawn = false;
44907
44955
  next.seenUuids = [];
@@ -44958,6 +45006,7 @@ function reduceSessionInner(state, input, deps) {
44958
45006
  const next = cloneState(state);
44959
45007
  next.procAlive = false;
44960
45008
  next.status = "stopped";
45009
+ const interruptEffects = next.turnOpen ? pushEventToBufferInPlace(next, { kind: "turn_end", reason: "interrupted" }, deps) : [];
44961
45010
  resetReadyGate(next);
44962
45011
  const sessionId = next.file.sessionId;
44963
45012
  const clearedEffects = Object.keys(state.pendingQuestions ?? {}).map(
@@ -44974,6 +45023,7 @@ function reduceSessionInner(state, input, deps) {
44974
45023
  return {
44975
45024
  state: next,
44976
45025
  effects: [
45026
+ ...interruptEffects,
44977
45027
  {
44978
45028
  kind: "emit-frame",
44979
45029
  frame: {
@@ -45786,6 +45836,7 @@ function makeInitialState(file, subSessionMeta) {
45786
45836
  nextSeq: 0,
45787
45837
  bufferStartSeq: 0,
45788
45838
  turnOpen: false,
45839
+ turnStartedAt: null,
45789
45840
  pendingPermissions: {},
45790
45841
  pendingQuestions: {},
45791
45842
  freshSpawn: false,
@@ -45829,7 +45880,8 @@ var SessionManager = class {
45829
45880
  // "屏幕真的稳定"作为触发点,signal-driven 而非 timer-driven。
45830
45881
  //
45831
45882
  // 清理点:newSession / stop / session-delete / stopAll。
45832
- pendingTurnDurationSignals = /* @__PURE__ */ new Set();
45883
+ // sessionId gate drop 的 turn_end 携带的 durationMs(可能 undefined);flush 注入时回填
45884
+ pendingTurnDurationSignals = /* @__PURE__ */ new Map();
45833
45885
  // SessionStore 按 scope 派生(root = <dataDir>/sessions/<scopeSubPath>/)。
45834
45886
  // default scope 直接复用 deps.store;persona scope(owner / listener)第一次访问时按需创建并缓存。
45835
45887
  // 取代旧的 storesByAgent —— agentId 概念由 SessionScope 取代,路径即身份,
@@ -47588,14 +47640,18 @@ var SessionManager = class {
47588
47640
  { sessionId, toolSessionId }
47589
47641
  );
47590
47642
  } else {
47643
+ const dropped = outEvents.find((e) => e.kind === "turn_end");
47591
47644
  feedEvents = outEvents.filter((e) => e.kind !== "turn_end");
47592
47645
  if (this.pendingTurnDurationSignals.has(sessionId)) {
47593
47646
  this.deps.screenIdleProbeLogger?.info(
47594
47647
  "turn_duration dedup: pending already set (repeated observer signal)",
47595
47648
  { sessionId, toolSessionId }
47596
47649
  );
47650
+ if (dropped?.durationMs !== void 0) {
47651
+ this.pendingTurnDurationSignals.set(sessionId, dropped.durationMs);
47652
+ }
47597
47653
  } else {
47598
- this.pendingTurnDurationSignals.add(sessionId);
47654
+ this.pendingTurnDurationSignals.set(sessionId, dropped?.durationMs);
47599
47655
  this.deps.screenIdleProbeLogger?.info(
47600
47656
  "turn_duration pending: gate closed \u2192 waiting for screen-idle signal",
47601
47657
  { sessionId, toolSessionId }
@@ -47644,12 +47700,16 @@ var SessionManager = class {
47644
47700
  );
47645
47701
  return;
47646
47702
  }
47703
+ const durationMs = this.pendingTurnDurationSignals.get(sid);
47647
47704
  this.pendingTurnDurationSignals.delete(sid);
47648
47705
  this.deps.screenIdleProbeLogger?.info(
47649
47706
  "notifyScreenIdle: pending turn_duration + screen idle confirmed \u2192 inject turn_end",
47650
47707
  { sessionId: sid, toolSessionId }
47651
47708
  );
47652
- runner.input({ kind: "inject-events", events: [{ kind: "turn_end" }] });
47709
+ runner.input({
47710
+ kind: "inject-events",
47711
+ events: [{ kind: "turn_end", ...durationMs !== void 0 ? { durationMs } : {} }]
47712
+ });
47653
47713
  }
47654
47714
  clearPendingTurnEnd(sessionId) {
47655
47715
  if (this.pendingTurnDurationSignals.delete(sessionId)) {
@@ -51251,7 +51311,9 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
51251
51311
  return super.parseLine(line);
51252
51312
  }
51253
51313
  if (obj.type === "system" && obj.subtype === "turn_duration") {
51254
- return [{ kind: "turn_end" }];
51314
+ const ev = { kind: "turn_end" };
51315
+ if (typeof obj.durationMs === "number") ev.durationMs = obj.durationMs;
51316
+ return [ev];
51255
51317
  }
51256
51318
  if (obj.type !== "user") return super.parseLine(line);
51257
51319
  const message = obj.message;
@@ -58894,6 +58956,7 @@ function slimHistoryMessages(messages) {
58894
58956
  if (m2.parentToolUseId !== void 0) out.parentToolUseId = m2.parentToolUseId;
58895
58957
  if (m2.isSidechain !== void 0) out.isSidechain = m2.isSidechain;
58896
58958
  if (m2.askQuestionAnswers !== void 0) out.askQuestionAnswers = m2.askQuestionAnswers;
58959
+ if (m2.durationMs !== void 0) out.durationMs = m2.durationMs;
58897
58960
  const isErrorPair = m2.toolUseId !== void 0 && errorToolUseIds.has(m2.toolUseId);
58898
58961
  if (m2.kind === "tool_call" && isErrorPair && m2.input !== void 0) out.input = m2.input;
58899
58962
  if (m2.kind === "tool_result" && m2.error !== void 0 && m2.output !== void 0) out.output = m2.output;
@@ -60071,7 +60134,7 @@ function computeMethodAccess(args) {
60071
60134
  }
60072
60135
 
60073
60136
  // src/version.ts
60074
- var version = "0.2.333".length > 0 ? "0.2.333" : "dev";
60137
+ var version = "0.2.335".length > 0 ? "0.2.335" : "dev";
60075
60138
 
60076
60139
  // src/cli-probe/probe.ts
60077
60140
  var fs59 = __toESM(require("fs"), 1);
@@ -22326,6 +22326,7 @@ var AskQuestionItemSchema = external_exports.object({
22326
22326
  multiSelect: external_exports.boolean(),
22327
22327
  options: external_exports.array(AskQuestionOptionSchema).min(1)
22328
22328
  });
22329
+ var TURN_END_REASONS = ["completed", "interrupted"];
22329
22330
  var ParsedEventSchema = external_exports.discriminatedUnion("kind", [
22330
22331
  // session_init 仅携带 session 标识;resolvedModel 等元信息走 meta_update
22331
22332
  external_exports.object({
@@ -22430,11 +22431,14 @@ var ParsedEventSchema = external_exports.discriminatedUnion("kind", [
22430
22431
  // ① SDK 模式 stream-json `result` 帧(claude.ts)—— stdout 最后一帧,可靠
22431
22432
  // ② TUI 模式 observer 读 JSONL `system/turn_duration` 行(claude-tui.ts parseLine)
22432
22433
  // ⚠️ CC 写此行早于该轮尾段正文落盘,可能被尾随 text 覆盖 lastEventKind(③ 存在的原因)
22433
- // ③ TUI 模式屏幕静止 AND observer 静止(observeScreenIdle + manager.observerIdleWaitMs 复合闸 →
22434
- // dispatchTurnIdle inject)—— 的覆盖;等尾随 text 全部 poll 落盘后补,排在 buffer 末条
22434
+ // ③ TUI 模式 gate 关闭期被 drop ② 信号,屏幕稳定后由 manager.notifyScreenIdle flush
22435
+ // 注入(PR #962 双源确认模型;旧 dispatchTurnIdle 已拆除)—— 排在 buffer 末条
22435
22436
  external_exports.object({
22436
22437
  ...ParsedEventBase,
22437
- kind: external_exports.literal("turn_end")
22438
+ kind: external_exports.literal("turn_end"),
22439
+ // 本轮耗时(ms)。SDK=result.duration_ms;TUI=turn_duration.durationMs;缺失时 reducer 按 turnStartedAt 兜底
22440
+ durationMs: external_exports.number().int().nonnegative().optional(),
22441
+ reason: external_exports.enum(TURN_END_REASONS).optional()
22438
22442
  }),
22439
22443
  // SDK system 帧 task_started / task_progress / task_notification 的归一化产物:
22440
22444
  // 子 agent 执行过程中实时上报 agentId / lastToolName / stats,UI pending 期就能开抽屉、看进度。