@clawos-dev/clawd 0.2.93 → 0.2.94-beta.176.8dff088

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 +102 -153
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -222,11 +222,7 @@ var init_errors = __esm({
222
222
  PERMISSION_REQUEST_STALE: "PERMISSION_REQUEST_STALE",
223
223
  FILE_TOO_LARGE: "FILE_TOO_LARGE",
224
224
  INTERNAL: "INTERNAL",
225
- UNAUTHORIZED: "UNAUTHORIZED",
226
- // Ready gate:用户在 pendingSend 已存在时再次 send,daemon 拒绝(不覆盖暂存)。
227
- // UI ChatInput 应在 popup 态 disable,daemon 兜底;error 帧 broadcast 通知所有 tab。
228
- // Spec: clawd/superpowers/specs/2026-05-22-cc-ready-gate-design.md §错误处理
229
- SEND_PENDING: "SEND_PENDING"
225
+ UNAUTHORIZED: "UNAUTHORIZED"
230
226
  };
231
227
  ClawdError = class extends Error {
232
228
  constructor(code, message) {
@@ -21293,9 +21289,7 @@ function cloneState(s) {
21293
21289
  freshSpawn: s.freshSpawn,
21294
21290
  procAlive: s.procAlive,
21295
21291
  seenUuids: s.seenUuids,
21296
- subSessionMeta: s.subSessionMeta,
21297
- readyForSend: s.readyForSend,
21298
- pendingSend: s.pendingSend
21292
+ subSessionMeta: s.subSessionMeta
21299
21293
  };
21300
21294
  }
21301
21295
  var IDLE_KILL_DELAY_MS = 6e4;
@@ -21530,23 +21524,6 @@ function applyCommand(state, command, deps) {
21530
21524
  effects.push({ kind: "cancel-idle-kill", sessionId });
21531
21525
  next.status = next.procAlive ? "running" : "idle";
21532
21526
  }
21533
- if (next.pendingSend) {
21534
- return {
21535
- state: next,
21536
- effects: [
21537
- ...effects,
21538
- {
21539
- kind: "emit-frame",
21540
- frame: {
21541
- type: "error",
21542
- sessionId,
21543
- code: ERROR_CODES.SEND_PENDING,
21544
- message: "a previous message is still pending; cc not yet ready"
21545
- }
21546
- }
21547
- ]
21548
- };
21549
- }
21550
21527
  if (!next.procAlive) {
21551
21528
  next.procAlive = true;
21552
21529
  next.freshSpawn = true;
@@ -21555,9 +21532,6 @@ function applyCommand(state, command, deps) {
21555
21532
  next.nextSeq = 0;
21556
21533
  next.turnOpen = false;
21557
21534
  next.status = "running";
21558
- if (deps.mode === "tui") {
21559
- next.readyForSend = false;
21560
- }
21561
21535
  effects.push({ kind: "spawn", ctx: buildSpawnContext(next, deps) });
21562
21536
  effects.push({
21563
21537
  kind: "emit-frame",
@@ -21574,15 +21548,10 @@ function applyCommand(state, command, deps) {
21574
21548
  uuid: deps.genUuid?.()
21575
21549
  };
21576
21550
  const pushed = pushEventToBuffer(next, userEvent, deps);
21577
- let nextState = pushed.state;
21578
21551
  effects.push(...pushed.effects);
21579
- if (nextState.readyForSend) {
21580
- const payload = deps.encodeStdin(command.text, { sessionId });
21581
- effects.push({ kind: "write-stdin", payload });
21582
- } else {
21583
- nextState = { ...nextState, pendingSend: { text: command.text } };
21584
- }
21585
- return { state: nextState, effects };
21552
+ const payload = deps.encodeStdin(command.text, { sessionId });
21553
+ effects.push({ kind: "write-stdin", payload });
21554
+ return { state: pushed.state, effects };
21586
21555
  }
21587
21556
  case "stop": {
21588
21557
  for (const toolUseId of Object.keys(state.pendingQuestions ?? {})) {
@@ -21597,7 +21566,6 @@ function applyCommand(state, command, deps) {
21597
21566
  }
21598
21567
  next.pendingPermissions = {};
21599
21568
  next.pendingQuestions = {};
21600
- next.pendingSend = void 0;
21601
21569
  if (next.procAlive) {
21602
21570
  next.status = "stopping";
21603
21571
  effects.push({ kind: "kill", signal: "SIGKILL" });
@@ -21635,8 +21603,6 @@ function applyCommand(state, command, deps) {
21635
21603
  }
21636
21604
  next.pendingPermissions = {};
21637
21605
  next.pendingQuestions = {};
21638
- next.pendingSend = void 0;
21639
- next.readyForSend = false;
21640
21606
  if (next.procAlive) {
21641
21607
  effects.push({ kind: "kill", signal: "SIGKILL" });
21642
21608
  }
@@ -21680,7 +21646,6 @@ function applyCommand(state, command, deps) {
21680
21646
  next.pendingPermissions = {};
21681
21647
  next.freshSpawn = false;
21682
21648
  next.seenUuids = [];
21683
- next.pendingSend = void 0;
21684
21649
  effects.push({
21685
21650
  kind: "emit-frame",
21686
21651
  frame: { type: "session:cleared", sessionId }
@@ -21718,7 +21683,6 @@ function applyCommand(state, command, deps) {
21718
21683
  next.pendingPermissions = {};
21719
21684
  next.freshSpawn = false;
21720
21685
  next.seenUuids = [];
21721
- next.pendingSend = void 0;
21722
21686
  effects.push({ kind: "persist-file", file: nextFile });
21723
21687
  effects.push(sessionInfoFrame(nextFile));
21724
21688
  effects.push({
@@ -21753,8 +21717,6 @@ function reduceSession(state, input, deps) {
21753
21717
  const next = cloneState(state);
21754
21718
  next.procAlive = false;
21755
21719
  next.status = "stopped";
21756
- next.pendingSend = void 0;
21757
- next.readyForSend = false;
21758
21720
  const sessionId = next.file.sessionId;
21759
21721
  const clearedEffects = Object.keys(state.pendingQuestions ?? {}).map(
21760
21722
  (toolUseId) => ({
@@ -21786,8 +21748,6 @@ function reduceSession(state, input, deps) {
21786
21748
  case "proc-error": {
21787
21749
  const next = cloneState(state);
21788
21750
  next.status = "error";
21789
- next.pendingSend = void 0;
21790
- next.readyForSend = false;
21791
21751
  return {
21792
21752
  state: next,
21793
21753
  effects: [
@@ -21914,30 +21874,6 @@ function reduceSession(state, input, deps) {
21914
21874
  effects: [{ kind: "kill", signal: "SIGKILL" }]
21915
21875
  };
21916
21876
  }
21917
- case "ready-detected": {
21918
- if (!state.procAlive) {
21919
- return { state, effects: [] };
21920
- }
21921
- const next = cloneState(state);
21922
- next.readyForSend = true;
21923
- const flushEffects = [];
21924
- if (state.pendingSend) {
21925
- const payload = deps.encodeStdin(state.pendingSend.text, {
21926
- sessionId: next.file.sessionId
21927
- });
21928
- flushEffects.push({ kind: "write-stdin", payload });
21929
- next.pendingSend = void 0;
21930
- }
21931
- return { state: next, effects: flushEffects };
21932
- }
21933
- case "popup-detected": {
21934
- if (!state.procAlive) {
21935
- return { state, effects: [] };
21936
- }
21937
- const next = cloneState(state);
21938
- next.readyForSend = false;
21939
- return { state: next, effects: [] };
21940
- }
21941
21877
  case "inject-owner-text": {
21942
21878
  const ownerEvent = {
21943
21879
  kind: "meta-text",
@@ -22126,8 +22062,7 @@ var SessionRunner = class {
22126
22062
  resolveContextWindow: this.hooks.resolveContextWindow,
22127
22063
  genUuid: this.hooks.genUuid ?? v4_default,
22128
22064
  ownerDisplayName: this.hooks.ownerDisplayName,
22129
- personaRoot: this.hooks.personaRoot,
22130
- mode: this.hooks.mode ?? "sdk"
22065
+ personaRoot: this.hooks.personaRoot
22131
22066
  };
22132
22067
  const { state, effects } = reduceSession(this.state, inputMsg, deps);
22133
22068
  this.state = state;
@@ -22557,14 +22492,7 @@ function makeInitialState(file, subSessionMeta) {
22557
22492
  freshSpawn: false,
22558
22493
  procAlive: false,
22559
22494
  seenUuids: [],
22560
- subSessionMeta,
22561
- // Ready gate:保守起手 true,让 SDK 模式与 TUI 接线未完成时 send 仍走旧的"立即写 stdin"
22562
- // 路径不破坏 SDK 用户体验。TUI 模式 manager 集成(Task 6)会在 spawn 时显式 dispatch
22563
- // 'popup-detected' 把 flag 翻 false → 等 ReadyGate emit ready 再 dispatch 'ready-detected'
22564
- // 翻回 true 并 flush pendingSend。
22565
- // daemon-only 字段,不上 wire / 不写 SessionFile(compressFrameForWire 只处理
22566
- // session:status,SessionState 不直接序列化)。
22567
- readyForSend: true
22495
+ subSessionMeta
22568
22496
  };
22569
22497
  }
22570
22498
  var SessionManager = class {
@@ -22807,9 +22735,6 @@ var SessionManager = class {
22807
22735
  // Phase 2 capability platform (plan §3): 透传 personaRoot, buildSpawnArgs 据
22808
22736
  // cwd 是否在 personaRoot 下自动决定是否注入 --settings sandbox-settings.json.
22809
22737
  personaRoot: this.deps.personaRoot,
22810
- // 透传给 ReducerDeps.mode:reducer send !procAlive spawn 路径据此判断要不要把
22811
- // readyForSend 翻 false(TUI 翻 false 让 user_text 走 pendingSend 暂存;SDK 不动保持直写)
22812
- mode: this.deps.mode,
22813
22738
  // file-sharing (spec §6 PR 3):闭包 scope + sessionId,runner 只暴露 tool/relPath/cwd
22814
22739
  onFileEdit: attachmentGroup ? (input) => attachmentGroup.onFileEdit({
22815
22740
  scope,
@@ -23945,9 +23870,6 @@ var SessionManager = class {
23945
23870
  },
23946
23871
  "broadcast"
23947
23872
  );
23948
- const runner = this.runners.get(sid);
23949
- if (!runner) return;
23950
- runner.input({ kind: visible ? "popup-detected" : "ready-detected" });
23951
23873
  }
23952
23874
  /** toolSessionId → sessionId 反查(遍历 runners);session 数典型 < 10,O(n) 可接受 */
23953
23875
  sessionIdByToolSid(toolSessionId) {
@@ -25232,29 +25154,6 @@ var POPUP_FOOTER_PATTERNS = [
25232
25154
  { kind: "permission", regex: /Tab to amend|Do you want to proceed/i },
25233
25155
  { kind: "question", regex: /Enter to select|↑\/↓ to navigate/i }
25234
25156
  ];
25235
- function matchPopupFooterKind(bufferText) {
25236
- for (const p2 of POPUP_FOOTER_PATTERNS) {
25237
- if (p2.regex.test(bufferText)) return p2.kind;
25238
- }
25239
- return null;
25240
- }
25241
- function hasInputFrame(bufferText) {
25242
- const lines = bufferText.split("\n");
25243
- for (let i = 1; i < lines.length; i++) {
25244
- const cur = lines[i];
25245
- if (!/^\s*❯\s/.test(cur)) continue;
25246
- const prev = lines[i - 1];
25247
- if (/─{20,}/.test(prev)) return true;
25248
- }
25249
- return false;
25250
- }
25251
- var READY_SIGNATURE_PATTERNS = [
25252
- { name: "input-frame", match: hasInputFrame }
25253
- ];
25254
- function isReadySignature(bufferText) {
25255
- if (matchPopupFooterKind(bufferText) !== null) return false;
25256
- return READY_SIGNATURE_PATTERNS.some((p2) => p2.match(bufferText));
25257
- }
25258
25157
  function createPopupDetector(opts) {
25259
25158
  const term = new Terminal({
25260
25159
  cols: opts.cols ?? 120,
@@ -25266,52 +25165,43 @@ function createPopupDetector(opts) {
25266
25165
  term.loadAddon(serializeAddon);
25267
25166
  let visible = null;
25268
25167
  let pendingClear = null;
25269
- let quietTimer = null;
25270
- const QUIET_MS = opts.quietMs ?? 300;
25271
25168
  let parseSeq = 0;
25272
25169
  let pendingParse = 0;
25273
25170
  let drainWaiters = [];
25274
25171
  let disposed = false;
25275
- const scanScreen = () => {
25172
+ const scanFooter = () => {
25276
25173
  const buf = term.buffer.active;
25277
- const lines = [];
25278
25174
  for (let i = 0; i < buf.length; i++) {
25279
25175
  const line = buf.getLine(i);
25280
25176
  if (!line) continue;
25281
- lines.push(line.translateToString(true));
25177
+ const text = line.translateToString(true);
25178
+ if (!text) continue;
25179
+ for (const p2 of POPUP_FOOTER_PATTERNS) {
25180
+ if (p2.regex.test(text)) return p2.kind;
25181
+ }
25282
25182
  }
25283
- const text = lines.join("\n");
25284
- return { ready: isReadySignature(text), footerKind: matchPopupFooterKind(text) };
25183
+ return null;
25285
25184
  };
25286
25185
  const tick = () => {
25287
- if (quietTimer) clearTimeout(quietTimer);
25288
- quietTimer = setTimeout(() => {
25289
- quietTimer = null;
25290
- const { ready, footerKind } = scanScreen();
25291
- const popupKind = footerKind ?? "unknown";
25292
- if (!ready && visible === null) {
25293
- visible = popupKind;
25294
- opts.onTransition(popupKind, true);
25295
- } else if (!ready && visible !== null) {
25296
- if (pendingClear) {
25297
- clearTimeout(pendingClear);
25298
- pendingClear = null;
25299
- }
25300
- } else if (ready && visible !== null) {
25301
- if (pendingClear) {
25302
- clearTimeout(pendingClear);
25303
- pendingClear = null;
25304
- }
25305
- const toClear = visible;
25306
- pendingClear = setTimeout(() => {
25307
- pendingClear = null;
25308
- if (visible === toClear) {
25309
- visible = null;
25310
- opts.onTransition(toClear, false);
25311
- }
25312
- }, opts.clearQuietMs);
25313
- }
25314
- }, QUIET_MS);
25186
+ const matched = scanFooter();
25187
+ if (matched && visible === null) {
25188
+ visible = matched;
25189
+ opts.onTransition(matched, true);
25190
+ } else if (matched && visible !== null) {
25191
+ if (pendingClear) {
25192
+ clearTimeout(pendingClear);
25193
+ pendingClear = null;
25194
+ }
25195
+ } else if (!matched && visible !== null && !pendingClear) {
25196
+ const toClear = visible;
25197
+ pendingClear = setTimeout(() => {
25198
+ pendingClear = null;
25199
+ if (visible === toClear) {
25200
+ visible = null;
25201
+ opts.onTransition(toClear, false);
25202
+ }
25203
+ }, opts.clearQuietMs);
25204
+ }
25315
25205
  };
25316
25206
  return {
25317
25207
  get visibleKind() {
@@ -25354,10 +25244,6 @@ function createPopupDetector(opts) {
25354
25244
  },
25355
25245
  dispose() {
25356
25246
  disposed = true;
25357
- if (quietTimer) {
25358
- clearTimeout(quietTimer);
25359
- quietTimer = null;
25360
- }
25361
25247
  if (pendingClear) {
25362
25248
  clearTimeout(pendingClear);
25363
25249
  pendingClear = null;
@@ -25373,6 +25259,64 @@ function createPopupDetector(opts) {
25373
25259
  }
25374
25260
  };
25375
25261
  }
25262
+ function createBootGate(pty, ptyChild, logger, toolSessionId) {
25263
+ const FALLBACK_MS = 4e3;
25264
+ const POST_POPUP_QUIET_MS = 800;
25265
+ 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
+ const popups = {
25267
+ trust: { handled: false, regex: /trust this folder/i, accept: "\r", label: "trust-folder" },
25268
+ bypass: {
25269
+ handled: false,
25270
+ regex: /Bypass Permissions mode|By proceeding/i,
25271
+ accept: "2\r",
25272
+ label: "bypass-warning"
25273
+ }
25274
+ };
25275
+ 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
+ return {
25289
+ get opened() {
25290
+ return opened;
25291
+ },
25292
+ feedBytes(data) {
25293
+ scanBuf += data.toString("utf8");
25294
+ const cleanBuf = stripAnsi(scanBuf).replace(/\s+/g, " ");
25295
+ for (const popup of Object.values(popups)) {
25296
+ if (popup.handled) continue;
25297
+ if (popup.regex.test(cleanBuf)) {
25298
+ popup.handled = true;
25299
+ logger?.debug(`${popup.label} popup auto-yes`, { toolSessionId });
25300
+ try {
25301
+ pty.write(popup.accept);
25302
+ } catch (err) {
25303
+ logger?.warn(`${popup.label} auto-yes write failed`, {
25304
+ error: err.message
25305
+ });
25306
+ }
25307
+ scanBuf = "";
25308
+ if (postPopupQuiet) clearTimeout(postPopupQuiet);
25309
+ postPopupQuiet = setTimeout(() => openGate("post-popup-quiet"), POST_POPUP_QUIET_MS);
25310
+ }
25311
+ }
25312
+ if (scanBuf.length > 8192) scanBuf = scanBuf.slice(-4096);
25313
+ },
25314
+ dispose() {
25315
+ clearTimeout(fallbackTimer);
25316
+ if (postPopupQuiet) clearTimeout(postPopupQuiet);
25317
+ }
25318
+ };
25319
+ }
25376
25320
  var ClaudeTuiAdapter = class extends ClaudeAdapter {
25377
25321
  // id 仍是 'claude'(继承)— UI / SessionFile.tool / capabilities:get RPC 字面量不变,
25378
25322
  // 完全兼容 SDK 模式 wiring。TUI 行为通过 daemon mode 流转,不通过 tool id 区分
@@ -25412,14 +25356,14 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
25412
25356
  const ptyChild = new PtyChildProcess(pty, {
25413
25357
  logger: this.tuiLogger,
25414
25358
  tag: ctx.toolSessionId ?? "no-tsid",
25415
- // MVP 阶段:boot gate 自动应答 + 4s 硬超时已废弃(由 ReadyGate 反向判定接管),
25416
- // stdin buffer 关闸 —— 用户 query 直发 CC。
25417
- // boot gate 废弃;Task 5/6 在 reducer 层接 ReadyGate ready 信号做 pending-send 调度
25418
- bootGate: false
25359
+ // boot gate:CC TUI 启动 + popup 处理前 stdin.write 一律 buffer,防止用户 prompt 在
25360
+ // CC 加载 node runtime 时被吞。bootGate scanner popup 处理完成 / 4s 兜底任一条件
25361
+ // 触发后调 setReady() 开闸
25362
+ bootGate: true
25419
25363
  });
25364
+ const bootGate = createBootGate(pty, ptyChild, this.tuiLogger, ctx.toolSessionId);
25420
25365
  const detector = createPopupDetector({
25421
25366
  clearQuietMs: 200,
25422
- // quietMs 走默认 300ms — 屏幕静止判定时长,对 cc TUI 启动期重绘节奏足够
25423
25367
  onTransition: (kind, visible) => {
25424
25368
  if (!ctx.toolSessionId || !this.tuiOpts.onPopupTransition) return;
25425
25369
  this.tuiLogger?.debug("popup transition", {
@@ -25456,7 +25400,11 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
25456
25400
  if (ctx.toolSessionId && this.tuiOpts.onPtyData) {
25457
25401
  this.tuiOpts.onPtyData(ctx.toolSessionId, merged.toString("base64"), seq);
25458
25402
  }
25459
- void detector.writeBytes(merged.toString("utf8"), seq);
25403
+ if (bootGate.opened) {
25404
+ void detector.writeBytes(merged.toString("utf8"), seq);
25405
+ } else {
25406
+ bootGate.feedBytes(merged);
25407
+ }
25460
25408
  if (ctx.toolSessionId) {
25461
25409
  probePtyDataThrottled(ctx.toolSessionId, merged.length, merged);
25462
25410
  }
@@ -25473,6 +25421,7 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
25473
25421
  if (coalesceBuf.length > 0) {
25474
25422
  flushCoalesced();
25475
25423
  }
25424
+ bootGate.dispose();
25476
25425
  if (ctx.toolSessionId && this.tuiOpts.onDetectorUnregister) {
25477
25426
  this.tuiOpts.onDetectorUnregister(ctx.toolSessionId);
25478
25427
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.93",
3
+ "version": "0.2.94-beta.176.8dff088",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",