@clawos-dev/clawd 0.2.103-beta.198.c756b33 → 0.2.103

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 +137 -112
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -32580,11 +32580,12 @@ var SessionManager = class {
32580
32580
  // 新 client 通过 session:subscribe 接入时由 onSubscribe hook 取 getPtyReplay(sessionId)
32581
32581
  // 把屏幕快照定向 emit,解决"刷新页面 / 第二个 tab 接入时 xterm 白屏"
32582
32582
  ptyReplaysByToolSid = /* @__PURE__ */ new Map();
32583
- // TUI 模式:toolSessionId → @xterm/headless PopupDetector。
32584
- // ClaudeTuiAdapter spawn 时通过 onDetectorRegister callback 写入;exit 时 onDetectorUnregister 清理。
32585
- // manager.resizePty 用此映射把 UI session:pty:resize 同步到 detector 内部 buffer,
32583
+ // TUI 模式:toolSessionId → @xterm/headless TerminalSurface(单一虚拟终端,popup / ready
32584
+ // observer 都从这一份 buffer 派生状态)。ClaudeTuiAdapter spawn 时通过 onSurfaceRegister
32585
+ // callback 写入;exit onSurfaceUnregister 清理。
32586
+ // manager.resizePty 用此映射把 UI 的 session:pty:resize 同步到 surface 内部 buffer,
32586
32587
  // 保证 snapshot serialize 时 cursor positioning 与 UI xterm 实际尺寸一致。
32587
- detectorsByToolSid = /* @__PURE__ */ new Map();
32588
+ surfacesByToolSid = /* @__PURE__ */ new Map();
32588
32589
  // 仅 mode='tui' 需要:index.ts 装配阶段在 observer 构造后注入。
32589
32590
  // observer.onEvent 要回灌 manager.feedObserverEvents,所以 observer 必须晚于 manager
32590
32591
  // 构造;反过来 manager 又要在 mode='tui' 下 spawn session 时 attach observer。setter 解循环
@@ -33858,18 +33859,18 @@ var SessionManager = class {
33858
33859
  if (!tsid) return void 0;
33859
33860
  return this.ptyTransports.get(tsid);
33860
33861
  }
33861
- /** ClaudeTuiAdapter spawn 时注册 detector ref;同一 tsid 重 spawn 会覆盖 */
33862
- registerDetector(toolSessionId, detector) {
33863
- this.detectorsByToolSid.set(toolSessionId, detector);
33862
+ /** ClaudeTuiAdapter spawn 时注册 surface ref;同一 tsid 重 spawn 会覆盖 */
33863
+ registerSurface(toolSessionId, surface) {
33864
+ this.surfacesByToolSid.set(toolSessionId, surface);
33864
33865
  }
33865
33866
  /** spawn 进程 exit 时清理 */
33866
- unregisterDetector(toolSessionId) {
33867
- this.detectorsByToolSid.delete(toolSessionId);
33867
+ unregisterSurface(toolSessionId) {
33868
+ this.surfacesByToolSid.delete(toolSessionId);
33868
33869
  }
33869
33870
  /**
33870
- * 同时 resize pty 子进程和 detector buffer:UI session:pty:resize 入口走这个。
33871
- * 顺序:pty.resize 在前,detector.resize 在后 —— pty 是真正的 CC 子进程视图,必须先成功;
33872
- * detector 只是 daemon 端镜像,任一步失败都返 false。
33871
+ * 同时 resize pty 子进程和 surface buffer:UI session:pty:resize 入口走这个。
33872
+ * 顺序:pty.resize 在前,surface.resize 在后 —— pty 是真正的 CC 子进程视图,必须先成功;
33873
+ * surface 只是 daemon 端镜像,任一步失败都返 false。
33873
33874
  * 找不到 runner / toolSessionId / pty 任一缺失 → 返 false,handler 静默给 UI 报 ok=false 不抛错。
33874
33875
  */
33875
33876
  resizePty(sessionId, cols, rows) {
@@ -33884,8 +33885,8 @@ var SessionManager = class {
33884
33885
  } catch {
33885
33886
  return false;
33886
33887
  }
33887
- const detector = this.detectorsByToolSid.get(tsid);
33888
- if (detector) detector.resize(cols, rows);
33888
+ const surface = this.surfacesByToolSid.get(tsid);
33889
+ if (surface) surface.resize(cols, rows);
33889
33890
  return true;
33890
33891
  }
33891
33892
  /**
@@ -35183,59 +35184,30 @@ var POPUP_FOOTER_PATTERNS = [
35183
35184
  { kind: "permission", regex: /Tab to amend|Do you want to proceed/i },
35184
35185
  { kind: "question", regex: /Enter to select|↑\/↓ to navigate/i }
35185
35186
  ];
35186
- function createPopupDetector(opts) {
35187
+ function createTerminalSurface(opts) {
35187
35188
  const term = new Terminal({
35188
- cols: opts.cols ?? 120,
35189
- rows: opts.rows ?? 40,
35190
- scrollback: 1e3,
35189
+ cols: opts?.cols ?? 120,
35190
+ rows: opts?.rows ?? 40,
35191
+ scrollback: opts?.scrollback ?? 1e3,
35191
35192
  allowProposedApi: true
35192
35193
  });
35193
35194
  const serializeAddon = new D();
35194
35195
  term.loadAddon(serializeAddon);
35195
- let visible = null;
35196
- let pendingClear = null;
35197
35196
  let parseSeq = 0;
35198
35197
  let pendingParse = 0;
35199
35198
  let drainWaiters = [];
35200
35199
  let disposed = false;
35201
- const scanFooter = () => {
35200
+ const observers = [];
35201
+ const collectLines = () => {
35202
35202
  const buf = term.buffer.active;
35203
+ const lines = new Array(buf.length);
35203
35204
  for (let i = 0; i < buf.length; i++) {
35204
35205
  const line = buf.getLine(i);
35205
- if (!line) continue;
35206
- const text = line.translateToString(true);
35207
- if (!text) continue;
35208
- for (const p2 of POPUP_FOOTER_PATTERNS) {
35209
- if (p2.regex.test(text)) return p2.kind;
35210
- }
35211
- }
35212
- return null;
35213
- };
35214
- const tick = () => {
35215
- const matched = scanFooter();
35216
- if (matched && visible === null) {
35217
- visible = matched;
35218
- opts.onTransition(matched, true);
35219
- } else if (matched && visible !== null) {
35220
- if (pendingClear) {
35221
- clearTimeout(pendingClear);
35222
- pendingClear = null;
35223
- }
35224
- } else if (!matched && visible !== null && !pendingClear) {
35225
- const toClear = visible;
35226
- pendingClear = setTimeout(() => {
35227
- pendingClear = null;
35228
- if (visible === toClear) {
35229
- visible = null;
35230
- opts.onTransition(toClear, false);
35231
- }
35232
- }, opts.clearQuietMs);
35206
+ lines[i] = line ? line.translateToString(true) : "";
35233
35207
  }
35208
+ return lines;
35234
35209
  };
35235
35210
  return {
35236
- get visibleKind() {
35237
- return visible;
35238
- },
35239
35211
  get parseSeq() {
35240
35212
  return parseSeq;
35241
35213
  },
@@ -35249,7 +35221,10 @@ function createPopupDetector(opts) {
35249
35221
  }
35250
35222
  if (typeof seq === "number" && seq > parseSeq) parseSeq = seq;
35251
35223
  pendingParse--;
35252
- tick();
35224
+ if (observers.length > 0) {
35225
+ const lines = collectLines();
35226
+ for (const cb of [...observers]) cb(lines);
35227
+ }
35253
35228
  if (pendingParse === 0 && drainWaiters.length > 0) {
35254
35229
  const waiters = drainWaiters;
35255
35230
  drainWaiters = [];
@@ -35271,42 +35246,93 @@ function createPopupDetector(opts) {
35271
35246
  } catch {
35272
35247
  }
35273
35248
  },
35249
+ serialize() {
35250
+ return serializeAddon.serialize({ scrollback: 1e3 });
35251
+ },
35274
35252
  dispose() {
35275
35253
  disposed = true;
35276
- if (pendingClear) {
35277
- clearTimeout(pendingClear);
35278
- pendingClear = null;
35279
- }
35254
+ pendingParse = 0;
35280
35255
  const waiters = drainWaiters;
35281
35256
  drainWaiters = [];
35282
35257
  for (const w2 of waiters) w2();
35258
+ observers.length = 0;
35283
35259
  serializeAddon.dispose();
35284
35260
  term.dispose();
35285
35261
  },
35286
- serialize() {
35287
- return serializeAddon.serialize({ scrollback: 1e3 });
35262
+ onTick(cb) {
35263
+ observers.push(cb);
35264
+ return () => {
35265
+ const i = observers.indexOf(cb);
35266
+ if (i >= 0) observers.splice(i, 1);
35267
+ };
35288
35268
  }
35289
35269
  };
35290
35270
  }
35291
- function createReadyDetector(opts) {
35292
- const term = new Terminal({
35293
- cols: opts.cols ?? 120,
35294
- rows: opts.rows ?? 40,
35295
- scrollback: 200,
35296
- allowProposedApi: true
35271
+ function observePopup(surface, opts) {
35272
+ let visible = null;
35273
+ let pendingClear = null;
35274
+ const scanFooter = (lines) => {
35275
+ for (const text of lines) {
35276
+ if (!text) continue;
35277
+ for (const p2 of POPUP_FOOTER_PATTERNS) {
35278
+ if (p2.regex.test(text)) return p2.kind;
35279
+ }
35280
+ }
35281
+ return null;
35282
+ };
35283
+ const unsubscribe = surface.onTick((lines) => {
35284
+ const matched = scanFooter(lines);
35285
+ const inputFrameVisible = opts.getInputFrameVisible();
35286
+ if (visible === null) {
35287
+ if (matched && !inputFrameVisible) {
35288
+ visible = matched;
35289
+ opts.onTransition(matched, true);
35290
+ }
35291
+ return;
35292
+ }
35293
+ if (!inputFrameVisible) {
35294
+ if (pendingClear) {
35295
+ clearTimeout(pendingClear);
35296
+ pendingClear = null;
35297
+ }
35298
+ return;
35299
+ }
35300
+ if (!pendingClear) {
35301
+ const toClear = visible;
35302
+ pendingClear = setTimeout(() => {
35303
+ pendingClear = null;
35304
+ if (visible === toClear) {
35305
+ visible = null;
35306
+ opts.onTransition(toClear, false);
35307
+ }
35308
+ }, opts.clearQuietMs);
35309
+ }
35297
35310
  });
35311
+ return {
35312
+ get visibleKind() {
35313
+ return visible;
35314
+ },
35315
+ dispose() {
35316
+ unsubscribe();
35317
+ if (pendingClear) {
35318
+ clearTimeout(pendingClear);
35319
+ pendingClear = null;
35320
+ }
35321
+ }
35322
+ };
35323
+ }
35324
+ function observeReady(surface, opts) {
35325
+ let prevReady = false;
35298
35326
  let quietTimer = null;
35299
- let disposed = false;
35300
- const scanReadyFrame = () => {
35301
- const buf = term.buffer.active;
35327
+ let unsubscribed = false;
35328
+ let latestLines = [];
35329
+ const scanReadyFrame = (lines) => {
35302
35330
  let prevHasDivider = false;
35303
- for (let i = 0; i < buf.length; i++) {
35304
- const line = buf.getLine(i);
35305
- if (!line) {
35331
+ for (const text of lines) {
35332
+ if (!text) {
35306
35333
  prevHasDivider = false;
35307
35334
  continue;
35308
35335
  }
35309
- const text = line.translateToString(true);
35310
35336
  const hasPrompt = /^❯/.test(text);
35311
35337
  const hasDivider = /─{20,}/.test(text);
35312
35338
  if (hasPrompt && prevHasDivider) return true;
@@ -35329,18 +35355,18 @@ function createReadyDetector(opts) {
35329
35355
  };
35330
35356
  const quietFire = () => {
35331
35357
  quietTimer = null;
35332
- if (disposed) return;
35333
- if (!scanReadyFrame()) {
35358
+ if (unsubscribed) return;
35359
+ if (!scanReadyFrame(latestLines)) {
35334
35360
  dbg({ event: "quiet-fire-but-not-ready-anymore" });
35335
35361
  return;
35336
35362
  }
35337
35363
  dbg({ event: "quiet-fire \u2192 onReady" });
35338
35364
  opts.onReady();
35339
35365
  };
35340
- let prevReady = false;
35341
- const tick = () => {
35342
- if (disposed) return;
35343
- const ready = scanReadyFrame();
35366
+ const unsubscribe = surface.onTick((lines) => {
35367
+ if (unsubscribed) return;
35368
+ latestLines = lines;
35369
+ const ready = scanReadyFrame(lines);
35344
35370
  if (!ready) {
35345
35371
  clearQuiet();
35346
35372
  if (prevReady) {
@@ -35355,19 +35381,15 @@ function createReadyDetector(opts) {
35355
35381
  }
35356
35382
  clearQuiet();
35357
35383
  quietTimer = setTimeout(quietFire, opts.quietMs);
35358
- };
35384
+ });
35359
35385
  return {
35360
- writeBytes(data) {
35361
- if (disposed) return;
35362
- term.write(data, () => {
35363
- if (disposed) return;
35364
- tick();
35365
- });
35386
+ get inputFrameVisible() {
35387
+ return prevReady;
35366
35388
  },
35367
35389
  dispose() {
35368
- disposed = true;
35390
+ unsubscribed = true;
35391
+ unsubscribe();
35369
35392
  clearQuiet();
35370
- term.dispose();
35371
35393
  }
35372
35394
  };
35373
35395
  }
@@ -35449,7 +35471,18 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35449
35471
  tag: ctx.toolSessionId ?? "no-tsid"
35450
35472
  });
35451
35473
  const bootGate = createBootGate(pty, this.tuiLogger);
35452
- const detector = createPopupDetector({
35474
+ const surface = createTerminalSurface();
35475
+ const readyObserver = observeReady(surface, {
35476
+ quietMs: 500,
35477
+ onReady: () => {
35478
+ if (!ctx.toolSessionId || !this.tuiOpts.onReady) return;
35479
+ this.tuiLogger?.debug("ready-detected", { toolSessionId: ctx.toolSessionId });
35480
+ this.tuiOpts.onReady(ctx.toolSessionId);
35481
+ },
35482
+ // [RG-DBG] 排查 ReadyGate 偶现卡输入用;定位完跟所有 [RG] 日志一起删
35483
+ logger: this.tuiLogger
35484
+ });
35485
+ const popupObserver = observePopup(surface, {
35453
35486
  clearQuietMs: 200,
35454
35487
  onTransition: (kind, visible) => {
35455
35488
  if (!ctx.toolSessionId || !this.tuiOpts.onPopupTransition) return;
@@ -35459,29 +35492,21 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35459
35492
  visible
35460
35493
  });
35461
35494
  this.tuiOpts.onPopupTransition(ctx.toolSessionId, kind, visible);
35462
- }
35463
- });
35464
- const readyDetector = createReadyDetector({
35465
- quietMs: 500,
35466
- onReady: () => {
35467
- if (!ctx.toolSessionId || !this.tuiOpts.onReady) return;
35468
- this.tuiLogger?.debug("ready-detected", { toolSessionId: ctx.toolSessionId });
35469
- this.tuiOpts.onReady(ctx.toolSessionId);
35470
35495
  },
35471
- // [RG-DBG] 排查 ReadyGate 偶现卡输入用;定位完跟所有 [RG] 日志一起删
35472
- logger: this.tuiLogger
35496
+ // popup_close 信号源:ready observer 扫到 + ─{20,} 输入框结构 → 输入框可见 → popup 已关
35497
+ getInputFrameVisible: () => readyObserver.inputFrameVisible
35473
35498
  });
35474
- if (ctx.toolSessionId && this.tuiOpts.onDetectorRegister) {
35475
- this.tuiOpts.onDetectorRegister(ctx.toolSessionId, detector);
35499
+ if (ctx.toolSessionId && this.tuiOpts.onSurfaceRegister) {
35500
+ this.tuiOpts.onSurfaceRegister(ctx.toolSessionId, surface);
35476
35501
  }
35477
35502
  let chunkSeq = 0;
35478
35503
  if (ctx.toolSessionId && this.tuiOpts.onPtyReplayRegister) {
35479
35504
  this.tuiOpts.onPtyReplayRegister(ctx.toolSessionId, async () => {
35480
- await detector.drain();
35505
+ await surface.drain();
35481
35506
  return {
35482
- data: detector.serialize(),
35483
- popupKind: detector.visibleKind,
35484
- atSeq: detector.parseSeq
35507
+ data: surface.serialize(),
35508
+ popupKind: popupObserver.visibleKind,
35509
+ atSeq: surface.parseSeq
35485
35510
  };
35486
35511
  });
35487
35512
  }
@@ -35498,8 +35523,7 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35498
35523
  this.tuiOpts.onPtyData(ctx.toolSessionId, merged.toString("base64"), seq);
35499
35524
  }
35500
35525
  bootGate.feedBytes(merged);
35501
- void detector.writeBytes(merged.toString("utf8"), seq);
35502
- readyDetector.writeBytes(merged.toString("utf8"));
35526
+ void surface.writeBytes(merged.toString("utf8"), seq);
35503
35527
  if (ctx.toolSessionId) {
35504
35528
  probePtyDataThrottled(ctx.toolSessionId, merged.length, merged);
35505
35529
  }
@@ -35517,14 +35541,15 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35517
35541
  flushCoalesced();
35518
35542
  }
35519
35543
  bootGate.dispose();
35520
- readyDetector.dispose();
35521
- if (ctx.toolSessionId && this.tuiOpts.onDetectorUnregister) {
35522
- this.tuiOpts.onDetectorUnregister(ctx.toolSessionId);
35544
+ readyObserver.dispose();
35545
+ popupObserver.dispose();
35546
+ if (ctx.toolSessionId && this.tuiOpts.onSurfaceUnregister) {
35547
+ this.tuiOpts.onSurfaceUnregister(ctx.toolSessionId);
35523
35548
  }
35524
35549
  if (ctx.toolSessionId && this.tuiOpts.onPtyUnregister) {
35525
35550
  this.tuiOpts.onPtyUnregister(ctx.toolSessionId);
35526
35551
  }
35527
- detector.dispose();
35552
+ surface.dispose();
35528
35553
  });
35529
35554
  return ptyChild;
35530
35555
  }
@@ -41992,8 +42017,8 @@ async function startDaemon(config) {
41992
42017
  onPtyData: (tsid, b64, seq) => manager.emitPtyChunk(tsid, b64, seq),
41993
42018
  onPopupTransition: (tsid, kind, visible) => manager.emitPopupTransition(tsid, kind, visible),
41994
42019
  onPtyReplayRegister: (tsid, replay) => manager.registerPtyReplay(tsid, replay),
41995
- onDetectorRegister: (tsid, detector) => manager.registerDetector(tsid, detector),
41996
- onDetectorUnregister: (tsid) => manager.unregisterDetector(tsid),
42020
+ onSurfaceRegister: (tsid, surface) => manager.registerSurface(tsid, surface),
42021
+ onSurfaceUnregister: (tsid) => manager.unregisterSurface(tsid),
41997
42022
  // ReadyGate v2:ReadyDetector emit ready 时投递 reducer 'ready-detected' input
41998
42023
  onReady: (tsid) => manager.dispatchReadyDetected(tsid)
41999
42024
  }) : new ClaudeAdapter({ logger, historyReader: new ClaudeHistoryReader() });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.103-beta.198.c756b33",
3
+ "version": "0.2.103",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",