@clawos-dev/clawd 0.2.161 → 0.2.162-beta.338.ff6197c

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 +77 -1
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -4927,6 +4927,13 @@ var init_schemas = __esm({
4927
4927
  toolUseId: external_exports.string(),
4928
4928
  questions: external_exports.array(AskQuestionItemSchema)
4929
4929
  }),
4930
+ // turn_end —— 一轮 AI 响应结束。消费者:reducer pushEventToBuffer 翻 turnOpen=false;
4931
+ // UI useChatPanelState 收 "Claude is working" spinner(lastEventKind===turn_end)。
4932
+ // 三个产生来源:
4933
+ // ① SDK 模式 stream-json `result` 帧(claude.ts)—— stdout 最后一帧,可靠
4934
+ // ② TUI 模式 observer 读 JSONL `system/turn_duration` 行(claude-tui.ts parseLine)
4935
+ // ⚠️ CC 写此行早于该轮尾段正文落盘,可能被尾随 text 覆盖 lastEventKind(③ 存在的原因)
4936
+ // ③ TUI 模式屏幕内容静止(observeScreenIdle → manager.dispatchTurnIdle inject)—— 兜 ② 的覆盖
4930
4937
  external_exports.object({
4931
4938
  ...ParsedEventBase,
4932
4939
  kind: external_exports.literal("turn_end")
@@ -35008,6 +35015,21 @@ var SessionManager = class {
35008
35015
  if (!runner) return;
35009
35016
  runner.input({ kind: "ready-detected" });
35010
35017
  }
35018
+ /**
35019
+ * ClaudeTuiAdapter onTurnIdle callback:屏幕内容静止(turn 结束)时 inject 一个权威 turn_end。
35020
+ * 修 turn_duration 写盘早于尾段正文 → observer 尾随 text 覆盖 lastEventKind → spinner 不熄。
35021
+ * 静止触发(~1.2s)远晚于 observer poll(200ms),故此 turn_end 必排在尾随 text 之后 = buffer 最后一个。
35022
+ * 仅 tui 模式;runner 缺失 noop。turn_end 无 uuid 不参与 dedup(放行)。
35023
+ * 注:重复 inject(屏幕静止边缘多 fire)不产生错误状态——turnOpen 已 false、idle-kill 有
35024
+ * running-idle guard——但仍 append buffer + 刷 updatedAt + 广播 session:info(非真 no-op,churn 可接受)。
35025
+ */
35026
+ dispatchTurnIdle(toolSessionId) {
35027
+ if (this.deps.mode !== "tui") return;
35028
+ const sid = this.sessionIdByToolSid(toolSessionId);
35029
+ const runner = sid ? this.runners.get(sid) : void 0;
35030
+ if (!runner) return;
35031
+ runner.input({ kind: "inject-events", events: [{ kind: "turn_end" }] });
35032
+ }
35011
35033
  /** toolSessionId → sessionId 反查(遍历 runners);session 数典型 < 10,O(n) 可接受 */
35012
35034
  sessionIdByToolSid(toolSessionId) {
35013
35035
  for (const [sid, runner] of this.runners) {
@@ -37372,7 +37394,49 @@ function observeReady(surface, opts) {
37372
37394
  }
37373
37395
  };
37374
37396
  }
37397
+ function observeScreenIdle(surface, opts) {
37398
+ const visibleRows = opts.visibleRows ?? 44;
37399
+ let lastSnap = null;
37400
+ let armed = false;
37401
+ let timer = null;
37402
+ let disposed = false;
37403
+ const snapOf = (lines) => lines.slice(-visibleRows).join("\n").replace(/[ \t]+$/gm, "");
37404
+ const clear = () => {
37405
+ if (timer) {
37406
+ clearTimeout(timer);
37407
+ timer = null;
37408
+ }
37409
+ };
37410
+ const fire = () => {
37411
+ timer = null;
37412
+ if (disposed) return;
37413
+ if (opts.getPopupVisible()) {
37414
+ timer = setTimeout(fire, opts.idleMs);
37415
+ return;
37416
+ }
37417
+ if (armed) return;
37418
+ armed = true;
37419
+ opts.onIdle();
37420
+ };
37421
+ const unsub = surface.onTick((lines) => {
37422
+ if (disposed) return;
37423
+ const snap = snapOf(lines);
37424
+ if (snap === lastSnap) return;
37425
+ lastSnap = snap;
37426
+ armed = false;
37427
+ clear();
37428
+ timer = setTimeout(fire, opts.idleMs);
37429
+ });
37430
+ return {
37431
+ dispose() {
37432
+ disposed = true;
37433
+ unsub();
37434
+ clear();
37435
+ }
37436
+ };
37437
+ }
37375
37438
  var BYPASS_SETTLE_MS = 300;
37439
+ var SCREEN_IDLE_MS = 1200;
37376
37440
  function createBootGate(pty, logger) {
37377
37441
  const stripAnsi = (s) => s.replace(/\x1b\[[0-9;?]*C/g, " ").replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "").replace(/\x1b[NOPM=>78]/g, "").replace(/[\x00-\x1f\x7f]/g, " ");
37378
37442
  const popups = {
@@ -37504,6 +37568,15 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
37504
37568
  // popup_close 信号源:ready observer 扫到 ❯ + ─{20,} 输入框结构 → 输入框可见 → popup 已关
37505
37569
  getInputFrameVisible: () => readyObserver.inputFrameVisible
37506
37570
  });
37571
+ const screenIdleObserver = observeScreenIdle(surface, {
37572
+ idleMs: SCREEN_IDLE_MS,
37573
+ onIdle: () => {
37574
+ if (!ctx.toolSessionId || !this.tuiOpts.onTurnIdle) return;
37575
+ this.tuiLogger?.debug("screen-idle \u2192 turn_end", { toolSessionId: ctx.toolSessionId });
37576
+ this.tuiOpts.onTurnIdle(ctx.toolSessionId);
37577
+ },
37578
+ getPopupVisible: () => popupObserver.visibleKind !== null
37579
+ });
37507
37580
  if (ctx.toolSessionId && this.tuiOpts.onSurfaceRegister) {
37508
37581
  this.tuiOpts.onSurfaceRegister(ctx.toolSessionId, surface);
37509
37582
  }
@@ -37551,6 +37624,7 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
37551
37624
  bootGate.dispose();
37552
37625
  readyObserver.dispose();
37553
37626
  popupObserver.dispose();
37627
+ screenIdleObserver.dispose();
37554
37628
  if (ctx.toolSessionId && this.tuiOpts.onSurfaceUnregister) {
37555
37629
  this.tuiOpts.onSurfaceUnregister(ctx.toolSessionId);
37556
37630
  }
@@ -46820,7 +46894,9 @@ async function startDaemon(config) {
46820
46894
  onSurfaceRegister: (tsid, surface) => manager.registerSurface(tsid, surface),
46821
46895
  onSurfaceUnregister: (tsid) => manager.unregisterSurface(tsid),
46822
46896
  // ReadyGate v2:ReadyDetector emit ready 时投递 reducer 'ready-detected' input
46823
- onReady: (tsid) => manager.dispatchReadyDetected(tsid)
46897
+ onReady: (tsid) => manager.dispatchReadyDetected(tsid),
46898
+ // 屏幕静止 → 补权威 turn_end(修 turn_duration 尾随 text 覆盖 lastEventKind)
46899
+ onTurnIdle: (tsid) => manager.dispatchTurnIdle(tsid)
46824
46900
  }) : new ClaudeAdapter({ logger, historyReader: new ClaudeHistoryReader() });
46825
46901
  registerAdapter("claude", claudeAdapter);
46826
46902
  registerAdapter("codex", new CodexAdapter({ logger, historyReader: new CodexHistoryReader() }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.161",
3
+ "version": "0.2.162-beta.338.ff6197c",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",