@clawos-dev/clawd 0.2.195-beta.393.e8d2aa5 → 0.2.196-beta.394.2f2689d

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.
Files changed (2) hide show
  1. package/dist/cli.cjs +69 -20
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -44287,15 +44287,27 @@ var SessionManager = class {
44287
44287
  this.lastObserverEventAt.set(sessionId, (this.deps.now ?? Date.now)());
44288
44288
  let feedEvents = outEvents;
44289
44289
  if (outEvents.some((e) => e.kind === "turn_end")) {
44290
- const ev = this.peekTurnEvidence(runner);
44291
- if (!ev.turnHasContent) {
44292
- feedEvents = outEvents.filter((e) => e.kind !== "turn_end");
44293
- this.deps.logger?.info("[TE-PROBE] drop spurious observer turn_end", {
44290
+ const runnerState = runner.getState();
44291
+ const toolSessionId = runnerState.file.toolSessionId;
44292
+ const adapter = this.deps.getAdapter(runnerState.file.tool ?? "claude");
44293
+ const gateOpen = !adapter.canAcceptTurnEnd || !toolSessionId ? true : adapter.canAcceptTurnEnd(toolSessionId);
44294
+ if (!gateOpen) {
44295
+ this.deps.logger?.info("drop turn_end (adapter gate closed)", {
44294
44296
  sessionId,
44295
- src: "observer",
44296
- ...ev,
44297
- batchKinds: outEvents.map((e) => e.kind)
44297
+ toolSessionId,
44298
+ reason: "tui-screen-not-idle"
44298
44299
  });
44300
+ feedEvents = outEvents.filter((e) => e.kind !== "turn_end");
44301
+ } else {
44302
+ const ev = this.peekTurnEvidence(runner);
44303
+ if (!ev.turnHasContent) {
44304
+ this.deps.logger?.info("drop turn_end (empty turn, observer replay)", {
44305
+ sessionId,
44306
+ ...ev,
44307
+ batchKinds: outEvents.map((e) => e.kind)
44308
+ });
44309
+ feedEvents = outEvents.filter((e) => e.kind !== "turn_end");
44310
+ }
44299
44311
  }
44300
44312
  }
44301
44313
  if (feedEvents.length === 0) return;
@@ -44576,22 +44588,24 @@ var SessionManager = class {
44576
44588
  const runner = sid ? this.runners.get(sid) : void 0;
44577
44589
  if (!runner) return;
44578
44590
  const ev = this.peekTurnEvidence(runner);
44579
- const willInject = ev.turnEndSeenThisTurn;
44580
- this.deps.logger?.info("[TE-PROBE] screen-idle compensation", {
44581
- sessionId: sid,
44582
- src: "screen-idle",
44583
- willInject,
44584
- ...ev
44585
- });
44586
- if (!willInject) return;
44591
+ if (!ev.turnEndSeenThisTurn) {
44592
+ this.deps.logger?.debug("screen-idle compensation skipped (no prior turn_end this turn)", {
44593
+ sessionId: sid,
44594
+ ...ev
44595
+ });
44596
+ return;
44597
+ }
44587
44598
  runner.input({ kind: "inject-events", events: [{ kind: "turn_end" }] });
44588
44599
  }
44589
44600
  /**
44590
- * 读 runner 当前 turn 态快照,供两个 turn_end 注入源(屏幕静止补偿 / observer turn_duration)
44591
- * 判断误发 + [TE-PROBE] 日志。
44592
- * turnEndSeenThisTurn:从 buffer 末尾回扫到最近 user_text 期间是否已出现过 turn_end
44593
- * (已出现=本轮真结束过、合法尾随;未出现=本轮还没结束过)→ Fix A 复发闸。
44594
- * turnHasContent:末条是否 assistant 产出(非 user_text/turn_end/空)→ Fix B 空 turn 守卫闸。
44601
+ * 读 runner 当前 turn 态快照,供 turn_end 判定使用:
44602
+ * `turnEndSeenThisTurn`:从 buffer 末尾回扫到最近 user_text 期间是否已出现过 turn_end
44603
+ * (已出现=本轮真结束过、合法尾随;未出现=本轮还没结束过)→ 屏幕静止补偿的复发闸
44604
+ * (`dispatchTurnIdle`)。
44605
+ * `turnHasContent`:末条是否 assistant 产出(非 user_text/turn_end/空)→ observer 回灌
44606
+ * turn_end 的空 turn 守卫闸(`feedObserverEvents`)。
44607
+ * 权威 gate(TUI 屏幕是否 idle + 弹框态)在 `adapter.canAcceptTurnEnd` 里,这两条只覆盖
44608
+ * "buffer 完整性"独立维度。
44595
44609
  */
44596
44610
  peekTurnEvidence(runner) {
44597
44611
  const st = runner.getState();
@@ -47201,6 +47215,12 @@ function observeScreenIdle(surface, opts) {
47201
47215
  disposed = true;
47202
47216
  unsub();
47203
47217
  clear();
47218
+ },
47219
+ isIdle() {
47220
+ if (!armed) return false;
47221
+ if (opts.getPopupVisible()) return false;
47222
+ const obsWait = opts.getObserverWaitMs?.() ?? 0;
47223
+ return obsWait <= 0;
47204
47224
  }
47205
47225
  };
47206
47226
  }
@@ -47278,11 +47298,31 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
47278
47298
  // 用于 spawn / PtyChildProcess 链路打日志
47279
47299
  tuiLogger;
47280
47300
  tuiOpts;
47301
+ /**
47302
+ * per-toolSessionId 的 tui 观察者句柄,仅用于 turn_end gate 查询(`canAcceptTurnEnd`)。
47303
+ * onIdle / onPopupTransition 等回调仍走原有闭包(不复用这份 map),本 map 只承担
47304
+ * "manager 需要跨模块查屏幕/弹框状态"这单一职责。
47305
+ */
47306
+ tuiStates = /* @__PURE__ */ new Map();
47281
47307
  constructor(opts = {}) {
47282
47308
  super(opts);
47283
47309
  this.tuiLogger = opts.logger;
47284
47310
  this.tuiOpts = opts;
47285
47311
  }
47312
+ /**
47313
+ * TUI adapter 的 turn_end 权威判定:屏幕已 idle 且非弹框态才放行。
47314
+ *
47315
+ * `feedObserverEvents` 收到 observer 回灌 `turn_end` 时调用。屏幕仍在变(如后台 agent 在跑)
47316
+ * 时 drop 掉 turn_end,避免 `system/turn_duration` JSONL 帧误触发 running-idle 状态转换。
47317
+ *
47318
+ * 未跟踪的 toolSessionId(spawn 前 / spawn 失败 / 已 dispose)视为 pass —— gate 只 drop
47319
+ * "有证据判定为伪信号"的场景,不做 unknown → block。
47320
+ */
47321
+ canAcceptTurnEnd(toolSessionId) {
47322
+ const state = this.tuiStates.get(toolSessionId);
47323
+ if (!state) return true;
47324
+ return state.screenIdle.isIdle();
47325
+ }
47286
47326
  spawn(ctx) {
47287
47327
  const args = buildTuiSpawnArgs(ctx, jsonlExistsForCtx(ctx));
47288
47328
  const cmd = process.env.CLAUDE_BIN ?? "claude";
@@ -47352,6 +47392,12 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
47352
47392
  if (ctx.toolSessionId && this.tuiOpts.onSurfaceRegister) {
47353
47393
  this.tuiOpts.onSurfaceRegister(ctx.toolSessionId, surface);
47354
47394
  }
47395
+ if (ctx.toolSessionId) {
47396
+ this.tuiStates.set(ctx.toolSessionId, {
47397
+ screenIdle: screenIdleObserver,
47398
+ popup: popupObserver
47399
+ });
47400
+ }
47355
47401
  let chunkSeq = 0;
47356
47402
  if (ctx.toolSessionId && this.tuiOpts.onPtyReplayRegister) {
47357
47403
  this.tuiOpts.onPtyReplayRegister(ctx.toolSessionId, async () => {
@@ -47397,6 +47443,9 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
47397
47443
  readyObserver.dispose();
47398
47444
  popupObserver.dispose();
47399
47445
  screenIdleObserver.dispose();
47446
+ if (ctx.toolSessionId) {
47447
+ this.tuiStates.delete(ctx.toolSessionId);
47448
+ }
47400
47449
  if (ctx.toolSessionId && this.tuiOpts.onSurfaceUnregister) {
47401
47450
  this.tuiOpts.onSurfaceUnregister(ctx.toolSessionId);
47402
47451
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.195-beta.393.e8d2aa5",
3
+ "version": "0.2.196-beta.394.2f2689d",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",