@clawos-dev/clawd 0.2.102-beta.196.9ae3d02 → 0.2.102-beta.197.08025e2

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