@clawos-dev/clawd 0.2.95 → 0.2.96-beta.178.1cde6b7

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 +209 -92
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -21289,10 +21289,28 @@ function cloneState(s) {
21289
21289
  freshSpawn: s.freshSpawn,
21290
21290
  procAlive: s.procAlive,
21291
21291
  seenUuids: s.seenUuids,
21292
- subSessionMeta: s.subSessionMeta
21292
+ subSessionMeta: s.subSessionMeta,
21293
+ // ReadyGate v2:readyForSend 是基础类型直接复制;pendingSend 必须浅拷贝防止
21294
+ // reduce 内 push/shift 在调用方原 state 上副作用(reducer 纯函数契约)
21295
+ readyForSend: s.readyForSend,
21296
+ pendingSend: s.pendingSend.slice()
21293
21297
  };
21294
21298
  }
21295
21299
  var IDLE_KILL_DELAY_MS = 6e4;
21300
+ function tryFlushPending(state, deps) {
21301
+ if (!state.readyForSend || state.pendingSend.length === 0) return [];
21302
+ const text = state.pendingSend.shift();
21303
+ state.readyForSend = false;
21304
+ const payload = deps.encodeStdin(text, { sessionId: state.file.sessionId });
21305
+ return [{ kind: "write-stdin", payload }];
21306
+ }
21307
+ function rgDbg(event, info) {
21308
+ console.debug(`[RG] ${event}`, info);
21309
+ }
21310
+ function resetReadyGate(next) {
21311
+ next.pendingSend = [];
21312
+ next.readyForSend = true;
21313
+ }
21296
21314
  var SEEN_UUID_CAP = 2e3;
21297
21315
  function applyEventBatch(state, events, deps) {
21298
21316
  if (events.length === 0) return { state, effects: [] };
@@ -21532,6 +21550,9 @@ function applyCommand(state, command, deps) {
21532
21550
  next.nextSeq = 0;
21533
21551
  next.turnOpen = false;
21534
21552
  next.status = "running";
21553
+ if (deps.mode === "tui") {
21554
+ next.readyForSend = false;
21555
+ }
21535
21556
  effects.push({ kind: "spawn", ctx: buildSpawnContext(next, deps) });
21536
21557
  effects.push({
21537
21558
  kind: "emit-frame",
@@ -21549,8 +21570,29 @@ function applyCommand(state, command, deps) {
21549
21570
  };
21550
21571
  const pushed = pushEventToBuffer(next, userEvent, deps);
21551
21572
  effects.push(...pushed.effects);
21552
- const payload = deps.encodeStdin(command.text, { sessionId });
21553
- effects.push({ kind: "write-stdin", payload });
21573
+ const procAliveBefore = state.procAlive;
21574
+ const readyBefore = state.readyForSend;
21575
+ const queueBefore = state.pendingSend.length;
21576
+ if (deps.mode === "tui") {
21577
+ pushed.state.pendingSend.push(command.text);
21578
+ effects.push(...tryFlushPending(pushed.state, deps));
21579
+ } else {
21580
+ const payload = deps.encodeStdin(command.text, { sessionId });
21581
+ effects.push({ kind: "write-stdin", payload });
21582
+ }
21583
+ rgDbg("send", {
21584
+ sid: sessionId,
21585
+ mode: deps.mode ?? "sdk",
21586
+ procAliveBefore,
21587
+ spawned: !procAliveBefore,
21588
+ readyBefore,
21589
+ queueBefore,
21590
+ textLen: command.text.length,
21591
+ preview: command.text.slice(0, 40),
21592
+ wroteStdin: effects.some((e) => e.kind === "write-stdin"),
21593
+ readyAfter: pushed.state.readyForSend,
21594
+ queueAfter: pushed.state.pendingSend.length
21595
+ });
21554
21596
  return { state: pushed.state, effects };
21555
21597
  }
21556
21598
  case "stop": {
@@ -21566,6 +21608,7 @@ function applyCommand(state, command, deps) {
21566
21608
  }
21567
21609
  next.pendingPermissions = {};
21568
21610
  next.pendingQuestions = {};
21611
+ resetReadyGate(next);
21569
21612
  if (next.procAlive) {
21570
21613
  next.status = "stopping";
21571
21614
  effects.push({ kind: "kill", signal: "SIGKILL" });
@@ -21583,6 +21626,9 @@ function applyCommand(state, command, deps) {
21583
21626
  next.nextSeq = 0;
21584
21627
  next.turnOpen = false;
21585
21628
  next.status = "running";
21629
+ if (deps.mode === "tui") {
21630
+ next.readyForSend = false;
21631
+ }
21586
21632
  effects.push({ kind: "spawn", ctx: buildSpawnContext(next, deps) });
21587
21633
  effects.push({
21588
21634
  kind: "emit-frame",
@@ -21603,6 +21649,7 @@ function applyCommand(state, command, deps) {
21603
21649
  }
21604
21650
  next.pendingPermissions = {};
21605
21651
  next.pendingQuestions = {};
21652
+ resetReadyGate(next);
21606
21653
  if (next.procAlive) {
21607
21654
  effects.push({ kind: "kill", signal: "SIGKILL" });
21608
21655
  }
@@ -21651,6 +21698,7 @@ function applyCommand(state, command, deps) {
21651
21698
  frame: { type: "session:cleared", sessionId }
21652
21699
  });
21653
21700
  }
21701
+ resetReadyGate(next);
21654
21702
  if (next.procAlive) {
21655
21703
  effects.push({ kind: "kill", signal: "SIGKILL" });
21656
21704
  }
@@ -21683,6 +21731,7 @@ function applyCommand(state, command, deps) {
21683
21731
  next.pendingPermissions = {};
21684
21732
  next.freshSpawn = false;
21685
21733
  next.seenUuids = [];
21734
+ resetReadyGate(next);
21686
21735
  effects.push({ kind: "persist-file", file: nextFile });
21687
21736
  effects.push(sessionInfoFrame(nextFile));
21688
21737
  effects.push({
@@ -21717,6 +21766,7 @@ function reduceSession(state, input, deps) {
21717
21766
  const next = cloneState(state);
21718
21767
  next.procAlive = false;
21719
21768
  next.status = "stopped";
21769
+ resetReadyGate(next);
21720
21770
  const sessionId = next.file.sessionId;
21721
21771
  const clearedEffects = Object.keys(state.pendingQuestions ?? {}).map(
21722
21772
  (toolUseId) => ({
@@ -21748,6 +21798,7 @@ function reduceSession(state, input, deps) {
21748
21798
  case "proc-error": {
21749
21799
  const next = cloneState(state);
21750
21800
  next.status = "error";
21801
+ resetReadyGate(next);
21751
21802
  return {
21752
21803
  state: next,
21753
21804
  effects: [
@@ -21899,6 +21950,22 @@ function reduceSession(state, input, deps) {
21899
21950
  next.pendingPermissions = keep;
21900
21951
  return { state: next, effects: [] };
21901
21952
  }
21953
+ case "ready-detected": {
21954
+ if (deps.mode !== "tui") {
21955
+ rgDbg("ready-input", { sid: state.file.sessionId, skipped: "non-tui-mode" });
21956
+ return { state, effects: [] };
21957
+ }
21958
+ const next = cloneState(state);
21959
+ next.readyForSend = true;
21960
+ const flushEffects = tryFlushPending(next, deps);
21961
+ rgDbg("ready-input", {
21962
+ sid: next.file.sessionId,
21963
+ wroteStdin: flushEffects.length > 0,
21964
+ readyAfter: next.readyForSend,
21965
+ queueAfter: next.pendingSend.length
21966
+ });
21967
+ return { state: next, effects: flushEffects };
21968
+ }
21902
21969
  default:
21903
21970
  assertNever(input);
21904
21971
  }
@@ -22062,7 +22129,9 @@ var SessionRunner = class {
22062
22129
  resolveContextWindow: this.hooks.resolveContextWindow,
22063
22130
  genUuid: this.hooks.genUuid ?? v4_default,
22064
22131
  ownerDisplayName: this.hooks.ownerDisplayName,
22065
- personaRoot: this.hooks.personaRoot
22132
+ personaRoot: this.hooks.personaRoot,
22133
+ // ReadyGate v2:透传 mode 让 reducer send / ready-detected 分支决定走暂存队列还是直写
22134
+ mode: this.hooks.mode
22066
22135
  };
22067
22136
  const { state, effects } = reduceSession(this.state, inputMsg, deps);
22068
22137
  this.state = state;
@@ -22241,6 +22310,12 @@ var SessionRunner = class {
22241
22310
  this.doKill(effect.signal);
22242
22311
  break;
22243
22312
  case "write-stdin":
22313
+ console.debug("[RG] write-stdin", {
22314
+ procAlive: !!this.proc,
22315
+ stdinWritable: !!this.proc?.stdin,
22316
+ len: effect.payload.length,
22317
+ preview: effect.payload.replace(/\x1b\[20[01]~/g, "\xB7paste\xB7").slice(0, 60)
22318
+ });
22244
22319
  this.proc?.stdin?.write(effect.payload);
22245
22320
  this.recorder?.tapStdinWrite(effect.payload);
22246
22321
  break;
@@ -22492,7 +22567,11 @@ function makeInitialState(file, subSessionMeta) {
22492
22567
  freshSpawn: false,
22493
22568
  procAlive: false,
22494
22569
  seenUuids: [],
22495
- subSessionMeta
22570
+ subSessionMeta,
22571
+ // ReadyGate v2:默认值兼容 SDK happy path(直写 stdin,不走队列)。
22572
+ // TUI 模式由 reducer send 分支按 deps.mode='tui' 主动翻 readyForSend=false
22573
+ readyForSend: true,
22574
+ pendingSend: []
22496
22575
  };
22497
22576
  }
22498
22577
  var SessionManager = class {
@@ -22742,7 +22821,10 @@ var SessionManager = class {
22742
22821
  tool: input.tool,
22743
22822
  relPath: input.relPath,
22744
22823
  cwd: input.cwd
22745
- }) : void 0
22824
+ }) : void 0,
22825
+ // ReadyGate v2:透传 daemon mode,runner 装配 ReducerDeps 时让 reducer 判断
22826
+ // send / ready-detected 分支走暂存队列还是直写
22827
+ mode: this.deps.mode
22746
22828
  });
22747
22829
  if (this.deps.mode === "tui" && !file.toolSessionId) {
22748
22830
  const newTsid = v4_default();
@@ -23871,6 +23953,26 @@ var SessionManager = class {
23871
23953
  "broadcast"
23872
23954
  );
23873
23955
  }
23956
+ /**
23957
+ * ClaudeTuiAdapter onReady callback(ReadyGate v2):ReadyDetector 扫到 cc TUI 主输入框 +
23958
+ * quiet window 通过时投递 reducer 'ready-detected' input,触发 pendingSend 队列出队 flush。
23959
+ * 仅 mode='tui' 时启用;SDK 模式 reducer 内 early return 也无害(双层兜底)。
23960
+ */
23961
+ dispatchReadyDetected(toolSessionId) {
23962
+ if (this.deps.mode !== "tui") {
23963
+ console.debug("[RG] mgr-disp", { tsid: toolSessionId, skipped: "non-tui-mode" });
23964
+ return;
23965
+ }
23966
+ const sid = this.sessionIdByToolSid(toolSessionId);
23967
+ const runner = sid ? this.runners.get(sid) : void 0;
23968
+ console.debug("[RG] mgr-disp", {
23969
+ tsid: toolSessionId,
23970
+ sid: sid ?? null,
23971
+ runnerFound: !!runner
23972
+ });
23973
+ if (!runner) return;
23974
+ runner.input({ kind: "ready-detected" });
23975
+ }
23874
23976
  /** toolSessionId → sessionId 反查(遍历 runners);session 数典型 < 10,O(n) 可接受 */
23875
23977
  sessionIdByToolSid(toolSessionId) {
23876
23978
  for (const [sid, runner] of this.runners) {
@@ -24985,26 +25087,27 @@ var PtyChildProcess = class extends import_node_events.EventEmitter {
24985
25087
  super();
24986
25088
  this.pty = pty;
24987
25089
  this.pid = pty.pid;
24988
- const { logger, tag, bootGate } = opts;
24989
- this.bootReady = !bootGate;
24990
- this.bootGateLogger = logger;
24991
- this.bootGateTag = tag;
24992
- logger?.debug("pty-child constructed", { pid: this.pid, tag, gated: !!bootGate });
25090
+ const { logger, tag } = opts;
25091
+ logger?.debug("pty-child constructed", { pid: this.pid, tag });
24993
25092
  const writeToPty = (data) => {
24994
25093
  const match = data.match(/^(\x1b\[200~[\s\S]*\x1b\[201~)(\r+)$/);
24995
25094
  if (match) {
24996
25095
  const paste = match[1];
24997
25096
  const enter = match[2];
25097
+ console.debug("[RG] pty-paste", { pasteLen: paste.length, enterLen: enter.length });
24998
25098
  pty.write(paste);
24999
25099
  setTimeout(() => {
25000
25100
  try {
25001
25101
  pty.write(enter);
25102
+ console.debug("[RG] pty-paste-enter", { wrote: "\\r", disposed: false });
25002
25103
  logger?.debug("pty bracketed paste submit \\r delayed", { tag, pid: this.pid });
25003
25104
  } catch (err) {
25105
+ console.debug("[RG] pty-paste-enter", { error: err.message });
25004
25106
  logger?.warn("pty delayed \\r failed", { tag, error: err.message });
25005
25107
  }
25006
25108
  }, 120);
25007
25109
  } else {
25110
+ console.debug("[RG] pty-write-direct", { len: data.length });
25008
25111
  pty.write(data);
25009
25112
  }
25010
25113
  };
@@ -25012,17 +25115,6 @@ var PtyChildProcess = class extends import_node_events.EventEmitter {
25012
25115
  decodeStrings: false,
25013
25116
  write: (chunk, _enc, cb) => {
25014
25117
  const s = typeof chunk === "string" ? chunk : chunk.toString("utf8");
25015
- if (!this.bootReady) {
25016
- this.stdinBuffer.push(s);
25017
- logger?.debug("pty stdin.write buffered (boot gate)", {
25018
- tag,
25019
- pid: this.pid,
25020
- bytes: s.length,
25021
- preview: s.replace(/\x1b\[20[01]~/g, "\xB7paste\xB7").slice(0, 80)
25022
- });
25023
- cb();
25024
- return;
25025
- }
25026
25118
  try {
25027
25119
  writeToPty(s);
25028
25120
  logger?.debug("pty stdin.write", {
@@ -25038,7 +25130,6 @@ var PtyChildProcess = class extends import_node_events.EventEmitter {
25038
25130
  }
25039
25131
  }
25040
25132
  });
25041
- this._writeToPty = writeToPty;
25042
25133
  this.stdout = new import_node_stream.Readable({ read() {
25043
25134
  } });
25044
25135
  pty.onData((data) => {
@@ -25069,40 +25160,6 @@ var PtyChildProcess = class extends import_node_events.EventEmitter {
25069
25160
  stderr;
25070
25161
  exitCode = null;
25071
25162
  firstDataLogged = false;
25072
- // Boot gate buffer:bootGate 启用且 ready=false 时,stdin.write 入这个队列等待 flush
25073
- stdinBuffer = [];
25074
- bootReady;
25075
- bootGateLogger;
25076
- bootGateTag;
25077
- // bracketed paste 拆分写入实现;构造器内闭包初始化,setReady flush 也用同一份
25078
- _writeToPty = () => {
25079
- };
25080
- /**
25081
- * 标记 boot gate 已通过(CC TUI 启动完成,可以接受用户输入)。
25082
- * 立刻 flush 之前 buffered 的 stdin 字节到 pty。幂等。外部由 ClaudeTuiAdapter 在
25083
- * 看到 banner / popup 处理完成 / 超时兜底时调用
25084
- */
25085
- setReady() {
25086
- if (this.bootReady) return;
25087
- this.bootReady = true;
25088
- const count = this.stdinBuffer.length;
25089
- this.bootGateLogger?.debug("pty boot gate cleared, flushing buffered stdin", {
25090
- tag: this.bootGateTag,
25091
- pid: this.pid,
25092
- bufferedWrites: count
25093
- });
25094
- while (this.stdinBuffer.length > 0) {
25095
- const data = this.stdinBuffer.shift();
25096
- try {
25097
- this._writeToPty(data);
25098
- } catch (err) {
25099
- this.bootGateLogger?.warn("pty flush stdin write failed", {
25100
- tag: this.bootGateTag,
25101
- error: err.message
25102
- });
25103
- }
25104
- }
25105
- }
25106
25163
  /** 与 ChildProcess.kill 签名对齐,返回是否成功发送信号 */
25107
25164
  kill(signal) {
25108
25165
  try {
@@ -25259,9 +25316,83 @@ function createPopupDetector(opts) {
25259
25316
  }
25260
25317
  };
25261
25318
  }
25262
- function createBootGate(pty, ptyChild, logger, toolSessionId) {
25263
- const FALLBACK_MS = 4e3;
25264
- const POST_POPUP_QUIET_MS = 800;
25319
+ function createReadyDetector(opts) {
25320
+ const term = new Terminal({
25321
+ cols: opts.cols ?? 120,
25322
+ rows: opts.rows ?? 40,
25323
+ scrollback: 200,
25324
+ allowProposedApi: true
25325
+ });
25326
+ let quietTimer = null;
25327
+ let disposed = false;
25328
+ const scanReadyFrame = () => {
25329
+ const buf = term.buffer.active;
25330
+ let prevHasDivider = false;
25331
+ for (let i = 0; i < buf.length; i++) {
25332
+ const line = buf.getLine(i);
25333
+ if (!line) {
25334
+ prevHasDivider = false;
25335
+ continue;
25336
+ }
25337
+ const text = line.translateToString(true);
25338
+ const hasPrompt = /^❯/.test(text);
25339
+ const hasDivider = /─{20,}/.test(text);
25340
+ if (hasPrompt && prevHasDivider) return true;
25341
+ prevHasDivider = hasDivider;
25342
+ }
25343
+ return false;
25344
+ };
25345
+ const clearQuiet = () => {
25346
+ if (quietTimer) {
25347
+ clearTimeout(quietTimer);
25348
+ quietTimer = null;
25349
+ }
25350
+ };
25351
+ const quietFire = () => {
25352
+ quietTimer = null;
25353
+ if (disposed) return;
25354
+ if (!scanReadyFrame()) {
25355
+ console.debug("[RG] det-state", { event: "quiet-fire-but-not-ready-anymore" });
25356
+ return;
25357
+ }
25358
+ console.debug("[RG] det-state", { event: "quiet-fire \u2192 onReady" });
25359
+ opts.onReady();
25360
+ };
25361
+ let prevReady = false;
25362
+ const tick = () => {
25363
+ if (disposed) return;
25364
+ const ready = scanReadyFrame();
25365
+ if (!ready) {
25366
+ clearQuiet();
25367
+ if (prevReady) {
25368
+ console.debug("[RG] det-state", { event: "ready \u2192 not-ready" });
25369
+ prevReady = false;
25370
+ }
25371
+ return;
25372
+ }
25373
+ if (!prevReady) {
25374
+ console.debug("[RG] det-state", { event: "not-ready \u2192 ready-candidate (quiet timer set)" });
25375
+ prevReady = true;
25376
+ }
25377
+ clearQuiet();
25378
+ quietTimer = setTimeout(quietFire, opts.quietMs);
25379
+ };
25380
+ return {
25381
+ writeBytes(data) {
25382
+ if (disposed) return;
25383
+ term.write(data, () => {
25384
+ if (disposed) return;
25385
+ tick();
25386
+ });
25387
+ },
25388
+ dispose() {
25389
+ disposed = true;
25390
+ clearQuiet();
25391
+ term.dispose();
25392
+ }
25393
+ };
25394
+ }
25395
+ function createBootGate(pty, logger) {
25265
25396
  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, " ");
25266
25397
  const popups = {
25267
25398
  trust: { handled: false, regex: /trust this folder/i, accept: "\r", label: "trust-folder" },
@@ -25273,22 +25404,7 @@ function createBootGate(pty, ptyChild, logger, toolSessionId) {
25273
25404
  }
25274
25405
  };
25275
25406
  let scanBuf = "";
25276
- let opened = false;
25277
- let postPopupQuiet = null;
25278
- const openGate = (reason) => {
25279
- if (opened) return;
25280
- opened = true;
25281
- logger?.debug("boot gate setReady", { toolSessionId, reason });
25282
- clearTimeout(fallbackTimer);
25283
- if (postPopupQuiet) clearTimeout(postPopupQuiet);
25284
- scanBuf = "";
25285
- ptyChild.setReady();
25286
- };
25287
- const fallbackTimer = setTimeout(() => openGate("timeout-4s"), FALLBACK_MS);
25288
25407
  return {
25289
- get opened() {
25290
- return opened;
25291
- },
25292
25408
  feedBytes(data) {
25293
25409
  scanBuf += data.toString("utf8");
25294
25410
  const cleanBuf = stripAnsi(scanBuf).replace(/\s+/g, " ");
@@ -25296,7 +25412,7 @@ function createBootGate(pty, ptyChild, logger, toolSessionId) {
25296
25412
  if (popup.handled) continue;
25297
25413
  if (popup.regex.test(cleanBuf)) {
25298
25414
  popup.handled = true;
25299
- logger?.debug(`${popup.label} popup auto-yes`, { toolSessionId });
25415
+ logger?.debug(`${popup.label} popup auto-yes`);
25300
25416
  try {
25301
25417
  pty.write(popup.accept);
25302
25418
  } catch (err) {
@@ -25305,15 +25421,11 @@ function createBootGate(pty, ptyChild, logger, toolSessionId) {
25305
25421
  });
25306
25422
  }
25307
25423
  scanBuf = "";
25308
- if (postPopupQuiet) clearTimeout(postPopupQuiet);
25309
- postPopupQuiet = setTimeout(() => openGate("post-popup-quiet"), POST_POPUP_QUIET_MS);
25310
25424
  }
25311
25425
  }
25312
25426
  if (scanBuf.length > 8192) scanBuf = scanBuf.slice(-4096);
25313
25427
  },
25314
25428
  dispose() {
25315
- clearTimeout(fallbackTimer);
25316
- if (postPopupQuiet) clearTimeout(postPopupQuiet);
25317
25429
  }
25318
25430
  };
25319
25431
  }
@@ -25355,13 +25467,9 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
25355
25467
  }
25356
25468
  const ptyChild = new PtyChildProcess(pty, {
25357
25469
  logger: this.tuiLogger,
25358
- tag: ctx.toolSessionId ?? "no-tsid",
25359
- // boot gate:CC TUI 启动 + popup 处理前 stdin.write 一律 buffer,防止用户 prompt 在
25360
- // CC 加载 node runtime 时被吞。bootGate scanner 在 popup 处理完成 / 4s 兜底任一条件
25361
- // 触发后调 setReady() 开闸
25362
- bootGate: true
25470
+ tag: ctx.toolSessionId ?? "no-tsid"
25363
25471
  });
25364
- const bootGate = createBootGate(pty, ptyChild, this.tuiLogger, ctx.toolSessionId);
25472
+ const bootGate = createBootGate(pty, this.tuiLogger);
25365
25473
  const detector = createPopupDetector({
25366
25474
  clearQuietMs: 200,
25367
25475
  onTransition: (kind, visible) => {
@@ -25374,6 +25482,14 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
25374
25482
  this.tuiOpts.onPopupTransition(ctx.toolSessionId, kind, visible);
25375
25483
  }
25376
25484
  });
25485
+ const readyDetector = createReadyDetector({
25486
+ quietMs: 500,
25487
+ onReady: () => {
25488
+ if (!ctx.toolSessionId || !this.tuiOpts.onReady) return;
25489
+ this.tuiLogger?.debug("ready-detected", { toolSessionId: ctx.toolSessionId });
25490
+ this.tuiOpts.onReady(ctx.toolSessionId);
25491
+ }
25492
+ });
25377
25493
  if (ctx.toolSessionId && this.tuiOpts.onDetectorRegister) {
25378
25494
  this.tuiOpts.onDetectorRegister(ctx.toolSessionId, detector);
25379
25495
  }
@@ -25400,11 +25516,9 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
25400
25516
  if (ctx.toolSessionId && this.tuiOpts.onPtyData) {
25401
25517
  this.tuiOpts.onPtyData(ctx.toolSessionId, merged.toString("base64"), seq);
25402
25518
  }
25403
- if (bootGate.opened) {
25404
- void detector.writeBytes(merged.toString("utf8"), seq);
25405
- } else {
25406
- bootGate.feedBytes(merged);
25407
- }
25519
+ bootGate.feedBytes(merged);
25520
+ void detector.writeBytes(merged.toString("utf8"), seq);
25521
+ readyDetector.writeBytes(merged.toString("utf8"));
25408
25522
  if (ctx.toolSessionId) {
25409
25523
  probePtyDataThrottled(ctx.toolSessionId, merged.length, merged);
25410
25524
  }
@@ -25422,6 +25536,7 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
25422
25536
  flushCoalesced();
25423
25537
  }
25424
25538
  bootGate.dispose();
25539
+ readyDetector.dispose();
25425
25540
  if (ctx.toolSessionId && this.tuiOpts.onDetectorUnregister) {
25426
25541
  this.tuiOpts.onDetectorUnregister(ctx.toolSessionId);
25427
25542
  }
@@ -30655,7 +30770,9 @@ async function startDaemon(config) {
30655
30770
  onPopupTransition: (tsid, kind, visible) => manager.emitPopupTransition(tsid, kind, visible),
30656
30771
  onPtyReplayRegister: (tsid, replay) => manager.registerPtyReplay(tsid, replay),
30657
30772
  onDetectorRegister: (tsid, detector) => manager.registerDetector(tsid, detector),
30658
- onDetectorUnregister: (tsid) => manager.unregisterDetector(tsid)
30773
+ onDetectorUnregister: (tsid) => manager.unregisterDetector(tsid),
30774
+ // ReadyGate v2:ReadyDetector emit ready 时投递 reducer 'ready-detected' input
30775
+ onReady: (tsid) => manager.dispatchReadyDetected(tsid)
30659
30776
  }) : new ClaudeAdapter({ logger, historyReader: new ClaudeHistoryReader() });
30660
30777
  registerAdapter("claude", claudeAdapter);
30661
30778
  const personaRegistry = new PersonaRegistry(personaStore);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.95",
3
+ "version": "0.2.96-beta.178.1cde6b7",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",