@clawos-dev/clawd 0.2.101-beta.194.a406ba4 → 0.2.102-beta.196.9ae3d02

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
@@ -32546,11 +32546,12 @@ var SessionManager = class {
32546
32546
  // 新 client 通过 session:subscribe 接入时由 onSubscribe hook 取 getPtyReplay(sessionId)
32547
32547
  // 把屏幕快照定向 emit,解决"刷新页面 / 第二个 tab 接入时 xterm 白屏"
32548
32548
  ptyReplaysByToolSid = /* @__PURE__ */ new Map();
32549
- // TUI 模式:toolSessionId → @xterm/headless PopupDetector。
32550
- // ClaudeTuiAdapter spawn 时通过 onDetectorRegister callback 写入;exit 时 onDetectorUnregister 清理。
32551
- // manager.resizePty 用此映射把 UI session:pty:resize 同步到 detector 内部 buffer,
32549
+ // TUI 模式:toolSessionId → @xterm/headless TerminalSurface(单一虚拟终端,popup / ready
32550
+ // observer 都从这一份 buffer 派生状态)。ClaudeTuiAdapter spawn 时通过 onSurfaceRegister
32551
+ // callback 写入;exit onSurfaceUnregister 清理。
32552
+ // manager.resizePty 用此映射把 UI 的 session:pty:resize 同步到 surface 内部 buffer,
32552
32553
  // 保证 snapshot serialize 时 cursor positioning 与 UI xterm 实际尺寸一致。
32553
- detectorsByToolSid = /* @__PURE__ */ new Map();
32554
+ surfacesByToolSid = /* @__PURE__ */ new Map();
32554
32555
  // 仅 mode='tui' 需要:index.ts 装配阶段在 observer 构造后注入。
32555
32556
  // observer.onEvent 要回灌 manager.feedObserverEvents,所以 observer 必须晚于 manager
32556
32557
  // 构造;反过来 manager 又要在 mode='tui' 下 spawn session 时 attach observer。setter 解循环
@@ -33824,18 +33825,18 @@ var SessionManager = class {
33824
33825
  if (!tsid) return void 0;
33825
33826
  return this.ptyTransports.get(tsid);
33826
33827
  }
33827
- /** ClaudeTuiAdapter spawn 时注册 detector ref;同一 tsid 重 spawn 会覆盖 */
33828
- registerDetector(toolSessionId, detector) {
33829
- this.detectorsByToolSid.set(toolSessionId, detector);
33828
+ /** ClaudeTuiAdapter spawn 时注册 surface ref;同一 tsid 重 spawn 会覆盖 */
33829
+ registerSurface(toolSessionId, surface) {
33830
+ this.surfacesByToolSid.set(toolSessionId, surface);
33830
33831
  }
33831
33832
  /** spawn 进程 exit 时清理 */
33832
- unregisterDetector(toolSessionId) {
33833
- this.detectorsByToolSid.delete(toolSessionId);
33833
+ unregisterSurface(toolSessionId) {
33834
+ this.surfacesByToolSid.delete(toolSessionId);
33834
33835
  }
33835
33836
  /**
33836
- * 同时 resize pty 子进程和 detector buffer:UI session:pty:resize 入口走这个。
33837
- * 顺序:pty.resize 在前,detector.resize 在后 —— pty 是真正的 CC 子进程视图,必须先成功;
33838
- * detector 只是 daemon 端镜像,任一步失败都返 false。
33837
+ * 同时 resize pty 子进程和 surface buffer:UI session:pty:resize 入口走这个。
33838
+ * 顺序:pty.resize 在前,surface.resize 在后 —— pty 是真正的 CC 子进程视图,必须先成功;
33839
+ * surface 只是 daemon 端镜像,任一步失败都返 false。
33839
33840
  * 找不到 runner / toolSessionId / pty 任一缺失 → 返 false,handler 静默给 UI 报 ok=false 不抛错。
33840
33841
  */
33841
33842
  resizePty(sessionId, cols, rows) {
@@ -33850,8 +33851,8 @@ var SessionManager = class {
33850
33851
  } catch {
33851
33852
  return false;
33852
33853
  }
33853
- const detector = this.detectorsByToolSid.get(tsid);
33854
- if (detector) detector.resize(cols, rows);
33854
+ const surface = this.surfacesByToolSid.get(tsid);
33855
+ if (surface) surface.resize(cols, rows);
33855
33856
  return true;
33856
33857
  }
33857
33858
  /**
@@ -35149,59 +35150,30 @@ var POPUP_FOOTER_PATTERNS = [
35149
35150
  { kind: "permission", regex: /Tab to amend|Do you want to proceed/i },
35150
35151
  { kind: "question", regex: /Enter to select|↑\/↓ to navigate/i }
35151
35152
  ];
35152
- function createPopupDetector(opts) {
35153
+ function createTerminalSurface(opts) {
35153
35154
  const term = new Terminal({
35154
- cols: opts.cols ?? 120,
35155
- rows: opts.rows ?? 40,
35156
- scrollback: 1e3,
35155
+ cols: opts?.cols ?? 120,
35156
+ rows: opts?.rows ?? 40,
35157
+ scrollback: opts?.scrollback ?? 1e3,
35157
35158
  allowProposedApi: true
35158
35159
  });
35159
35160
  const serializeAddon = new D();
35160
35161
  term.loadAddon(serializeAddon);
35161
- let visible = null;
35162
- let pendingClear = null;
35163
35162
  let parseSeq = 0;
35164
35163
  let pendingParse = 0;
35165
35164
  let drainWaiters = [];
35166
35165
  let disposed = false;
35167
- const scanFooter = () => {
35166
+ const observers = [];
35167
+ const collectLines = () => {
35168
35168
  const buf = term.buffer.active;
35169
+ const lines = new Array(buf.length);
35169
35170
  for (let i = 0; i < buf.length; i++) {
35170
35171
  const line = buf.getLine(i);
35171
- if (!line) continue;
35172
- const text = line.translateToString(true);
35173
- if (!text) continue;
35174
- for (const p2 of POPUP_FOOTER_PATTERNS) {
35175
- if (p2.regex.test(text)) return p2.kind;
35176
- }
35177
- }
35178
- return null;
35179
- };
35180
- const tick = () => {
35181
- const matched = scanFooter();
35182
- if (matched && visible === null) {
35183
- visible = matched;
35184
- opts.onTransition(matched, true);
35185
- } else if (matched && visible !== null) {
35186
- if (pendingClear) {
35187
- clearTimeout(pendingClear);
35188
- pendingClear = null;
35189
- }
35190
- } else if (!matched && visible !== null && !pendingClear) {
35191
- const toClear = visible;
35192
- pendingClear = setTimeout(() => {
35193
- pendingClear = null;
35194
- if (visible === toClear) {
35195
- visible = null;
35196
- opts.onTransition(toClear, false);
35197
- }
35198
- }, opts.clearQuietMs);
35172
+ lines[i] = line ? line.translateToString(true) : "";
35199
35173
  }
35174
+ return lines;
35200
35175
  };
35201
35176
  return {
35202
- get visibleKind() {
35203
- return visible;
35204
- },
35205
35177
  get parseSeq() {
35206
35178
  return parseSeq;
35207
35179
  },
@@ -35215,7 +35187,10 @@ function createPopupDetector(opts) {
35215
35187
  }
35216
35188
  if (typeof seq === "number" && seq > parseSeq) parseSeq = seq;
35217
35189
  pendingParse--;
35218
- tick();
35190
+ if (observers.length > 0) {
35191
+ const lines = collectLines();
35192
+ for (const cb of [...observers]) cb(lines);
35193
+ }
35219
35194
  if (pendingParse === 0 && drainWaiters.length > 0) {
35220
35195
  const waiters = drainWaiters;
35221
35196
  drainWaiters = [];
@@ -35237,42 +35212,93 @@ function createPopupDetector(opts) {
35237
35212
  } catch {
35238
35213
  }
35239
35214
  },
35215
+ serialize() {
35216
+ return serializeAddon.serialize({ scrollback: 1e3 });
35217
+ },
35240
35218
  dispose() {
35241
35219
  disposed = true;
35242
- if (pendingClear) {
35243
- clearTimeout(pendingClear);
35244
- pendingClear = null;
35245
- }
35220
+ pendingParse = 0;
35246
35221
  const waiters = drainWaiters;
35247
35222
  drainWaiters = [];
35248
35223
  for (const w2 of waiters) w2();
35224
+ observers.length = 0;
35249
35225
  serializeAddon.dispose();
35250
35226
  term.dispose();
35251
35227
  },
35252
- serialize() {
35253
- return serializeAddon.serialize({ scrollback: 1e3 });
35228
+ onTick(cb) {
35229
+ observers.push(cb);
35230
+ return () => {
35231
+ const i = observers.indexOf(cb);
35232
+ if (i >= 0) observers.splice(i, 1);
35233
+ };
35254
35234
  }
35255
35235
  };
35256
35236
  }
35257
- function createReadyDetector(opts) {
35258
- const term = new Terminal({
35259
- cols: opts.cols ?? 120,
35260
- rows: opts.rows ?? 40,
35261
- scrollback: 200,
35262
- allowProposedApi: true
35237
+ function observePopup(surface, opts) {
35238
+ let visible = null;
35239
+ let pendingClear = null;
35240
+ const scanFooter = (lines) => {
35241
+ for (const text of lines) {
35242
+ if (!text) continue;
35243
+ for (const p2 of POPUP_FOOTER_PATTERNS) {
35244
+ if (p2.regex.test(text)) return p2.kind;
35245
+ }
35246
+ }
35247
+ return null;
35248
+ };
35249
+ const unsubscribe = surface.onTick((lines) => {
35250
+ const matched = scanFooter(lines);
35251
+ const inputFrameVisible = opts.getInputFrameVisible();
35252
+ if (visible === null) {
35253
+ if (matched && !inputFrameVisible) {
35254
+ visible = matched;
35255
+ opts.onTransition(matched, true);
35256
+ }
35257
+ return;
35258
+ }
35259
+ if (!inputFrameVisible) {
35260
+ if (pendingClear) {
35261
+ clearTimeout(pendingClear);
35262
+ pendingClear = null;
35263
+ }
35264
+ return;
35265
+ }
35266
+ if (!pendingClear) {
35267
+ const toClear = visible;
35268
+ pendingClear = setTimeout(() => {
35269
+ pendingClear = null;
35270
+ if (visible === toClear) {
35271
+ visible = null;
35272
+ opts.onTransition(toClear, false);
35273
+ }
35274
+ }, opts.clearQuietMs);
35275
+ }
35263
35276
  });
35277
+ return {
35278
+ get visibleKind() {
35279
+ return visible;
35280
+ },
35281
+ dispose() {
35282
+ unsubscribe();
35283
+ if (pendingClear) {
35284
+ clearTimeout(pendingClear);
35285
+ pendingClear = null;
35286
+ }
35287
+ }
35288
+ };
35289
+ }
35290
+ function observeReady(surface, opts) {
35291
+ let prevReady = false;
35264
35292
  let quietTimer = null;
35265
- let disposed = false;
35266
- const scanReadyFrame = () => {
35267
- const buf = term.buffer.active;
35293
+ let unsubscribed = false;
35294
+ let latestLines = [];
35295
+ const scanReadyFrame = (lines) => {
35268
35296
  let prevHasDivider = false;
35269
- for (let i = 0; i < buf.length; i++) {
35270
- const line = buf.getLine(i);
35271
- if (!line) {
35297
+ for (const text of lines) {
35298
+ if (!text) {
35272
35299
  prevHasDivider = false;
35273
35300
  continue;
35274
35301
  }
35275
- const text = line.translateToString(true);
35276
35302
  const hasPrompt = /^❯/.test(text);
35277
35303
  const hasDivider = /─{20,}/.test(text);
35278
35304
  if (hasPrompt && prevHasDivider) return true;
@@ -35295,18 +35321,18 @@ function createReadyDetector(opts) {
35295
35321
  };
35296
35322
  const quietFire = () => {
35297
35323
  quietTimer = null;
35298
- if (disposed) return;
35299
- if (!scanReadyFrame()) {
35324
+ if (unsubscribed) return;
35325
+ if (!scanReadyFrame(latestLines)) {
35300
35326
  dbg({ event: "quiet-fire-but-not-ready-anymore" });
35301
35327
  return;
35302
35328
  }
35303
35329
  dbg({ event: "quiet-fire \u2192 onReady" });
35304
35330
  opts.onReady();
35305
35331
  };
35306
- let prevReady = false;
35307
- const tick = () => {
35308
- if (disposed) return;
35309
- const ready = scanReadyFrame();
35332
+ const unsubscribe = surface.onTick((lines) => {
35333
+ if (unsubscribed) return;
35334
+ latestLines = lines;
35335
+ const ready = scanReadyFrame(lines);
35310
35336
  if (!ready) {
35311
35337
  clearQuiet();
35312
35338
  if (prevReady) {
@@ -35321,19 +35347,15 @@ function createReadyDetector(opts) {
35321
35347
  }
35322
35348
  clearQuiet();
35323
35349
  quietTimer = setTimeout(quietFire, opts.quietMs);
35324
- };
35350
+ });
35325
35351
  return {
35326
- writeBytes(data) {
35327
- if (disposed) return;
35328
- term.write(data, () => {
35329
- if (disposed) return;
35330
- tick();
35331
- });
35352
+ get inputFrameVisible() {
35353
+ return prevReady;
35332
35354
  },
35333
35355
  dispose() {
35334
- disposed = true;
35356
+ unsubscribed = true;
35357
+ unsubscribe();
35335
35358
  clearQuiet();
35336
- term.dispose();
35337
35359
  }
35338
35360
  };
35339
35361
  }
@@ -35415,7 +35437,18 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35415
35437
  tag: ctx.toolSessionId ?? "no-tsid"
35416
35438
  });
35417
35439
  const bootGate = createBootGate(pty, this.tuiLogger);
35418
- const detector = createPopupDetector({
35440
+ const surface = createTerminalSurface();
35441
+ const readyObserver = observeReady(surface, {
35442
+ quietMs: 500,
35443
+ onReady: () => {
35444
+ if (!ctx.toolSessionId || !this.tuiOpts.onReady) return;
35445
+ this.tuiLogger?.debug("ready-detected", { toolSessionId: ctx.toolSessionId });
35446
+ this.tuiOpts.onReady(ctx.toolSessionId);
35447
+ },
35448
+ // [RG-DBG] 排查 ReadyGate 偶现卡输入用;定位完跟所有 [RG] 日志一起删
35449
+ logger: this.tuiLogger
35450
+ });
35451
+ const popupObserver = observePopup(surface, {
35419
35452
  clearQuietMs: 200,
35420
35453
  onTransition: (kind, visible) => {
35421
35454
  if (!ctx.toolSessionId || !this.tuiOpts.onPopupTransition) return;
@@ -35425,29 +35458,21 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35425
35458
  visible
35426
35459
  });
35427
35460
  this.tuiOpts.onPopupTransition(ctx.toolSessionId, kind, visible);
35428
- }
35429
- });
35430
- const readyDetector = createReadyDetector({
35431
- quietMs: 500,
35432
- onReady: () => {
35433
- if (!ctx.toolSessionId || !this.tuiOpts.onReady) return;
35434
- this.tuiLogger?.debug("ready-detected", { toolSessionId: ctx.toolSessionId });
35435
- this.tuiOpts.onReady(ctx.toolSessionId);
35436
35461
  },
35437
- // [RG-DBG] 排查 ReadyGate 偶现卡输入用;定位完跟所有 [RG] 日志一起删
35438
- logger: this.tuiLogger
35462
+ // popup_close 信号源:ready observer 扫到 + ─{20,} 输入框结构 → 输入框可见 → popup 已关
35463
+ getInputFrameVisible: () => readyObserver.inputFrameVisible
35439
35464
  });
35440
- if (ctx.toolSessionId && this.tuiOpts.onDetectorRegister) {
35441
- this.tuiOpts.onDetectorRegister(ctx.toolSessionId, detector);
35465
+ if (ctx.toolSessionId && this.tuiOpts.onSurfaceRegister) {
35466
+ this.tuiOpts.onSurfaceRegister(ctx.toolSessionId, surface);
35442
35467
  }
35443
35468
  let chunkSeq = 0;
35444
35469
  if (ctx.toolSessionId && this.tuiOpts.onPtyReplayRegister) {
35445
35470
  this.tuiOpts.onPtyReplayRegister(ctx.toolSessionId, async () => {
35446
- await detector.drain();
35471
+ await surface.drain();
35447
35472
  return {
35448
- data: detector.serialize(),
35449
- popupKind: detector.visibleKind,
35450
- atSeq: detector.parseSeq
35473
+ data: surface.serialize(),
35474
+ popupKind: popupObserver.visibleKind,
35475
+ atSeq: surface.parseSeq
35451
35476
  };
35452
35477
  });
35453
35478
  }
@@ -35464,8 +35489,7 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35464
35489
  this.tuiOpts.onPtyData(ctx.toolSessionId, merged.toString("base64"), seq);
35465
35490
  }
35466
35491
  bootGate.feedBytes(merged);
35467
- void detector.writeBytes(merged.toString("utf8"), seq);
35468
- readyDetector.writeBytes(merged.toString("utf8"));
35492
+ void surface.writeBytes(merged.toString("utf8"), seq);
35469
35493
  if (ctx.toolSessionId) {
35470
35494
  probePtyDataThrottled(ctx.toolSessionId, merged.length, merged);
35471
35495
  }
@@ -35483,14 +35507,15 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35483
35507
  flushCoalesced();
35484
35508
  }
35485
35509
  bootGate.dispose();
35486
- readyDetector.dispose();
35487
- if (ctx.toolSessionId && this.tuiOpts.onDetectorUnregister) {
35488
- this.tuiOpts.onDetectorUnregister(ctx.toolSessionId);
35510
+ readyObserver.dispose();
35511
+ popupObserver.dispose();
35512
+ if (ctx.toolSessionId && this.tuiOpts.onSurfaceUnregister) {
35513
+ this.tuiOpts.onSurfaceUnregister(ctx.toolSessionId);
35489
35514
  }
35490
35515
  if (ctx.toolSessionId && this.tuiOpts.onPtyUnregister) {
35491
35516
  this.tuiOpts.onPtyUnregister(ctx.toolSessionId);
35492
35517
  }
35493
- detector.dispose();
35518
+ surface.dispose();
35494
35519
  });
35495
35520
  return ptyChild;
35496
35521
  }
@@ -41943,8 +41968,8 @@ async function startDaemon(config) {
41943
41968
  onPtyData: (tsid, b64, seq) => manager.emitPtyChunk(tsid, b64, seq),
41944
41969
  onPopupTransition: (tsid, kind, visible) => manager.emitPopupTransition(tsid, kind, visible),
41945
41970
  onPtyReplayRegister: (tsid, replay) => manager.registerPtyReplay(tsid, replay),
41946
- onDetectorRegister: (tsid, detector) => manager.registerDetector(tsid, detector),
41947
- onDetectorUnregister: (tsid) => manager.unregisterDetector(tsid),
41971
+ onSurfaceRegister: (tsid, surface) => manager.registerSurface(tsid, surface),
41972
+ onSurfaceUnregister: (tsid) => manager.unregisterSurface(tsid),
41948
41973
  // ReadyGate v2:ReadyDetector emit ready 时投递 reducer 'ready-detected' input
41949
41974
  onReady: (tsid) => manager.dispatchReadyDetected(tsid)
41950
41975
  }) : 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.101-beta.194.a406ba4",
3
+ "version": "0.2.102-beta.196.9ae3d02",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",