@clawos-dev/clawd 0.2.93-beta.174.78b779b → 0.2.93-beta.175.b162fd0

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 +136 -69
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -114,8 +114,13 @@ var init_methods = __esm({
114
114
  "session:pty:resize",
115
115
  // session:pty:snapshot — UI XtermPanel 重新挂载(切 session 回来 / 刷新)时主动拉一次当前
116
116
  // 屏幕全量快照,再开始消费 incremental session:pty。daemon 调 manager.getPtyReplay(sessionId)
117
- // 取该 session @xterm/headless detector serialize 结果。response.datasession:pty
118
- // push 帧同格式(base64-utf8);session 不存在 / 非 tui / pty 已 exit 时 data=null。
117
+ // drain detector parse queue serialize,保证 atSeqbuffer 内容严格对齐。
118
+ // response 字段:
119
+ // - data:当前屏幕 + scrollback 的 ANSI 字节串(base64-utf8),与 session:pty push 帧同格式
120
+ // - atSeq:此 snapshot 覆盖到的最后一帧 session:pty 的 seq(即 detector 已 parse 完成的最大 seq)
121
+ // UI 端 flush 时丢弃 pending 中 seq ≤ atSeq 的帧
122
+ // - popupKind:当前 detector 检测到的 popup 类型(null = 无 popup)
123
+ // session 不存在 / 非 tui / pty 已 exit 时 data=null, atSeq=0。
119
124
  "session:pty:snapshot",
120
125
  // ---- attachment.* file-sharing(详见 attachment-schemas.ts) ----
121
126
  // 命名警告:这里的 `attachment.*` RPC 与 CC v2.x 上行 `type:"attachment"` 系统行
@@ -10554,20 +10559,6 @@ function withCommonFields(msg, o) {
10554
10559
  if (typeof o.uuid === "string" && o.uuid.length > 0) msg.uuid = o.uuid;
10555
10560
  return msg;
10556
10561
  }
10557
- function isRejectSentinelLine(obj) {
10558
- if (!obj || typeof obj !== "object") return false;
10559
- return typeof obj.toolUseResult === "string";
10560
- }
10561
- function isUserPlainTextLine(obj) {
10562
- if (!obj || typeof obj !== "object") return false;
10563
- const o = obj;
10564
- if (o.type !== "user") return false;
10565
- const message = o.message;
10566
- const content = message?.content;
10567
- if (!Array.isArray(content) || content.length !== 1) return false;
10568
- const part = content[0];
10569
- return part?.type === "text";
10570
- }
10571
10562
  function messageFromJsonl(obj) {
10572
10563
  if (!obj || typeof obj !== "object") return null;
10573
10564
  const o = obj;
@@ -10575,7 +10566,6 @@ function messageFromJsonl(obj) {
10575
10566
  const ts = o.timestamp ?? o.ts;
10576
10567
  if (type === "user" || type === "assistant") {
10577
10568
  const message = o.message;
10578
- if (type === "assistant" && message?.model === "<synthetic>") return null;
10579
10569
  const content = message?.content;
10580
10570
  const toolResultExtra = type === "user" ? extractToolResultExtraFromRaw(o.toolUseResult) : void 0;
10581
10571
  const sourceToolAssistantUUID = type === "user" && typeof o.sourceToolAssistantUUID === "string" ? o.sourceToolAssistantUUID : void 0;
@@ -10889,11 +10879,7 @@ var init_claude_history = __esm({
10889
10879
  );
10890
10880
  const lines = readJsonlLines(file);
10891
10881
  const messages = [];
10892
- let prevIsRejectSentinel = false;
10893
10882
  for (const line of lines) {
10894
- const skipInterruptedMarker = prevIsRejectSentinel && isUserPlainTextLine(line);
10895
- prevIsRejectSentinel = isRejectSentinelLine(line);
10896
- if (skipInterruptedMarker) continue;
10897
10883
  const msg = messageFromJsonl(line);
10898
10884
  if (msg) messages.push(msg);
10899
10885
  }
@@ -11319,7 +11305,6 @@ function parseClaudeObjectInner(obj) {
11319
11305
  }
11320
11306
  if (type === "assistant") {
11321
11307
  const message = obj.message;
11322
- if (message?.model === "<synthetic>") return events;
11323
11308
  if (message) {
11324
11309
  const mModel = message.model;
11325
11310
  if (typeof mModel === "string" && mModel.length > 0) {
@@ -22601,6 +22586,11 @@ var SessionManager = class {
22601
22586
  // 新 client 通过 session:subscribe 接入时由 onSubscribe hook 取 getPtyReplay(sessionId)
22602
22587
  // 把屏幕快照定向 emit,解决"刷新页面 / 第二个 tab 接入时 xterm 白屏"
22603
22588
  ptyReplaysByToolSid = /* @__PURE__ */ new Map();
22589
+ // TUI 模式:toolSessionId → @xterm/headless PopupDetector。
22590
+ // ClaudeTuiAdapter spawn 时通过 onDetectorRegister callback 写入;exit 时 onDetectorUnregister 清理。
22591
+ // manager.resizePty 用此映射把 UI 的 session:pty:resize 同步到 detector 内部 buffer,
22592
+ // 保证 snapshot serialize 时 cursor positioning 与 UI xterm 实际尺寸一致。
22593
+ detectorsByToolSid = /* @__PURE__ */ new Map();
22604
22594
  // 仅 mode='tui' 需要:index.ts 装配阶段在 observer 构造后注入。
22605
22595
  // observer.onEvent 要回灌 manager.feedObserverEvents,所以 observer 必须晚于 manager
22606
22596
  // 构造;反过来 manager 又要在 mode='tui' 下 spawn session 时 attach observer。setter 解循环
@@ -23813,7 +23803,7 @@ var SessionManager = class {
23813
23803
  * - session 存在但 pty 还没 spawn / 已 exit → null
23814
23804
  * 调用方(onSubscribe hook)按返回值非空时构造 session:pty + session:control 帧定向回放
23815
23805
  */
23816
- getPtyReplay(sessionId) {
23806
+ async getPtyReplay(sessionId) {
23817
23807
  if (this.deps.mode !== "tui") {
23818
23808
  probeEvent("replay.request", { sid: sessionId, result: "not-tui-mode" });
23819
23809
  return null;
@@ -23842,7 +23832,7 @@ var SessionManager = class {
23842
23832
  });
23843
23833
  return null;
23844
23834
  }
23845
- const snap = replay();
23835
+ const snap = await replay();
23846
23836
  const dumpFile = probeDumpReplay(sessionId, snap.data);
23847
23837
  probeEvent("replay.dump", {
23848
23838
  sid: sessionId,
@@ -23868,18 +23858,50 @@ var SessionManager = class {
23868
23858
  if (!tsid) return void 0;
23869
23859
  return this.ptyTransports.get(tsid);
23870
23860
  }
23861
+ /** ClaudeTuiAdapter spawn 时注册 detector ref;同一 tsid 重 spawn 会覆盖 */
23862
+ registerDetector(toolSessionId, detector) {
23863
+ this.detectorsByToolSid.set(toolSessionId, detector);
23864
+ }
23865
+ /** spawn 进程 exit 时清理 */
23866
+ unregisterDetector(toolSessionId) {
23867
+ this.detectorsByToolSid.delete(toolSessionId);
23868
+ }
23869
+ /**
23870
+ * 同时 resize pty 子进程和 detector buffer:UI session:pty:resize 入口走这个。
23871
+ * 顺序:pty.resize 在前,detector.resize 在后 —— pty 是真正的 CC 子进程视图,必须先成功;
23872
+ * detector 只是 daemon 端镜像,任一步失败都返 false。
23873
+ * 找不到 runner / toolSessionId / pty 任一缺失 → 返 false,handler 静默给 UI 报 ok=false 不抛错。
23874
+ */
23875
+ resizePty(sessionId, cols, rows) {
23876
+ const runner = this.runners.get(sessionId);
23877
+ if (!runner) return false;
23878
+ const tsid = runner.getState().file.toolSessionId;
23879
+ if (!tsid) return false;
23880
+ const pty = this.ptyTransports.get(tsid);
23881
+ if (!pty) return false;
23882
+ try {
23883
+ pty.resize(cols, rows);
23884
+ } catch {
23885
+ return false;
23886
+ }
23887
+ const detector = this.detectorsByToolSid.get(tsid);
23888
+ if (detector) detector.resize(cols, rows);
23889
+ return true;
23890
+ }
23871
23891
  /**
23872
23892
  * ClaudeTuiAdapter onPtyData callback:把 pty 字节流以 session:pty wire 帧广播给订阅者。
23893
+ * seq 来自 adapter spawn 路径维护的 per-session counter,与 detector.parseSeq 同源。
23894
+ * UI 端 snapshot/incremental dedup 必须靠这个 seq。
23873
23895
  * 仅 mode='tui' 时发——SDK spawn 模式根本没 pty。
23874
- * payload 永远只有 `{ sessionId, data }`,data = base64 UTF-8 字节流。
23896
+ * payload 永远只有 `{ sessionId, data, seq }`,data = base64 UTF-8 字节流。
23875
23897
  * 详见 protocol/src/events.ts session:pty JSDoc
23876
23898
  */
23877
- emitPtyData(toolSessionId, b64) {
23899
+ emitPtyChunk(toolSessionId, b64, seq) {
23878
23900
  if (this.deps.mode !== "tui") return;
23879
23901
  const sid = this.sessionIdByToolSid(toolSessionId);
23880
23902
  if (!sid) return;
23881
23903
  this.routeFromRunner(
23882
- { type: "session:pty", sessionId: sid, data: b64 },
23904
+ { type: "session:pty", sessionId: sid, data: b64, seq },
23883
23905
  "broadcast"
23884
23906
  );
23885
23907
  }
@@ -25226,6 +25248,10 @@ function createPopupDetector(opts) {
25226
25248
  let pendingClear = null;
25227
25249
  let quietTimer = null;
25228
25250
  const QUIET_MS = opts.quietMs ?? 300;
25251
+ let parseSeq = 0;
25252
+ let pendingParse = 0;
25253
+ let drainWaiters = [];
25254
+ let disposed = false;
25229
25255
  const scanScreen = () => {
25230
25256
  const buf = term.buffer.active;
25231
25257
  const lines = [];
@@ -25271,15 +25297,43 @@ function createPopupDetector(opts) {
25271
25297
  get visibleKind() {
25272
25298
  return visible;
25273
25299
  },
25274
- writeBytes(data) {
25300
+ get parseSeq() {
25301
+ return parseSeq;
25302
+ },
25303
+ writeBytes(data, seq) {
25304
+ pendingParse++;
25275
25305
  return new Promise((resolve6) => {
25276
25306
  term.write(data, () => {
25307
+ if (disposed) {
25308
+ resolve6();
25309
+ return;
25310
+ }
25311
+ if (typeof seq === "number" && seq > parseSeq) parseSeq = seq;
25312
+ pendingParse--;
25277
25313
  tick();
25314
+ if (pendingParse === 0 && drainWaiters.length > 0) {
25315
+ const waiters = drainWaiters;
25316
+ drainWaiters = [];
25317
+ for (const w2 of waiters) w2();
25318
+ }
25278
25319
  resolve6();
25279
25320
  });
25280
25321
  });
25281
25322
  },
25323
+ drain() {
25324
+ if (pendingParse === 0) return Promise.resolve();
25325
+ return new Promise((resolve6) => {
25326
+ drainWaiters.push(resolve6);
25327
+ });
25328
+ },
25329
+ resize(cols, rows) {
25330
+ try {
25331
+ term.resize(cols, rows);
25332
+ } catch {
25333
+ }
25334
+ },
25282
25335
  dispose() {
25336
+ disposed = true;
25283
25337
  if (quietTimer) {
25284
25338
  clearTimeout(quietTimer);
25285
25339
  quietTimer = null;
@@ -25288,6 +25342,9 @@ function createPopupDetector(opts) {
25288
25342
  clearTimeout(pendingClear);
25289
25343
  pendingClear = null;
25290
25344
  }
25345
+ const waiters = drainWaiters;
25346
+ drainWaiters = [];
25347
+ for (const w2 of waiters) w2();
25291
25348
  serializeAddon.dispose();
25292
25349
  term.dispose();
25293
25350
  },
@@ -25353,23 +25410,52 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
25353
25410
  this.tuiOpts.onPopupTransition(ctx.toolSessionId, kind, visible);
25354
25411
  }
25355
25412
  });
25413
+ if (ctx.toolSessionId && this.tuiOpts.onDetectorRegister) {
25414
+ this.tuiOpts.onDetectorRegister(ctx.toolSessionId, detector);
25415
+ }
25416
+ let chunkSeq = 0;
25356
25417
  if (ctx.toolSessionId && this.tuiOpts.onPtyReplayRegister) {
25357
- this.tuiOpts.onPtyReplayRegister(ctx.toolSessionId, () => ({
25358
- data: detector.serialize(),
25359
- popupKind: detector.visibleKind
25360
- }));
25418
+ this.tuiOpts.onPtyReplayRegister(ctx.toolSessionId, async () => {
25419
+ await detector.drain();
25420
+ return {
25421
+ data: detector.serialize(),
25422
+ popupKind: detector.visibleKind,
25423
+ atSeq: detector.parseSeq
25424
+ };
25425
+ });
25361
25426
  }
25362
- ptyChild.stdout.on("data", (data) => {
25427
+ let coalesceBuf = [];
25428
+ let coalesceScheduled = false;
25429
+ const flushCoalesced = () => {
25430
+ coalesceScheduled = false;
25431
+ if (coalesceBuf.length === 0) return;
25432
+ const merged = Buffer.concat(coalesceBuf);
25433
+ coalesceBuf = [];
25434
+ chunkSeq++;
25435
+ const seq = chunkSeq;
25363
25436
  if (ctx.toolSessionId && this.tuiOpts.onPtyData) {
25364
- this.tuiOpts.onPtyData(ctx.toolSessionId, data.toString("base64"));
25437
+ this.tuiOpts.onPtyData(ctx.toolSessionId, merged.toString("base64"), seq);
25365
25438
  }
25366
- void detector.writeBytes(data.toString("utf8"));
25439
+ void detector.writeBytes(merged.toString("utf8"), seq);
25367
25440
  if (ctx.toolSessionId) {
25368
- probePtyDataThrottled(ctx.toolSessionId, data.length, data);
25441
+ probePtyDataThrottled(ctx.toolSessionId, merged.length, merged);
25442
+ }
25443
+ };
25444
+ ptyChild.stdout.on("data", (data) => {
25445
+ coalesceBuf.push(data);
25446
+ if (!coalesceScheduled) {
25447
+ coalesceScheduled = true;
25448
+ queueMicrotask(flushCoalesced);
25369
25449
  }
25370
25450
  });
25371
25451
  ptyChild.on("exit", () => {
25372
25452
  probeEvent("pty.exit", { tsid: ctx.toolSessionId });
25453
+ if (coalesceBuf.length > 0) {
25454
+ flushCoalesced();
25455
+ }
25456
+ if (ctx.toolSessionId && this.tuiOpts.onDetectorUnregister) {
25457
+ this.tuiOpts.onDetectorUnregister(ctx.toolSessionId);
25458
+ }
25373
25459
  if (ctx.toolSessionId && this.tuiOpts.onPtyUnregister) {
25374
25460
  this.tuiOpts.onPtyUnregister(ctx.toolSessionId);
25375
25461
  }
@@ -25884,8 +25970,7 @@ var SessionObserver = class {
25884
25970
  pollTimer: null,
25885
25971
  lastSize: size,
25886
25972
  buf: "",
25887
- adapter: args.adapter,
25888
- prevIsRejectSentinel: false
25973
+ adapter: args.adapter
25889
25974
  };
25890
25975
  try {
25891
25976
  import_node_fs14.default.mkdirSync(import_node_path15.default.dirname(filePath), { recursive: true });
@@ -25958,15 +26043,6 @@ var SessionObserver = class {
25958
26043
  while ((newlineIndex = w2.buf.indexOf("\n")) >= 0) {
25959
26044
  const line = w2.buf.slice(0, newlineIndex);
25960
26045
  w2.buf = w2.buf.slice(newlineIndex + 1);
25961
- let parsed = null;
25962
- try {
25963
- parsed = line.trim() ? JSON.parse(line) : null;
25964
- } catch {
25965
- parsed = null;
25966
- }
25967
- const skipAsMarker = w2.prevIsRejectSentinel && parsed !== null && isUserPlainTextLine(parsed);
25968
- w2.prevIsRejectSentinel = parsed !== null ? isRejectSentinelLine(parsed) : false;
25969
- if (skipAsMarker) continue;
25970
26046
  const events = w2.adapter.parseLine(line);
25971
26047
  this.maybeReportUserMessage(w2.sessionId, line);
25972
26048
  if (events.length > 0) {
@@ -29166,14 +29242,8 @@ function buildSessionHandlers(deps) {
29166
29242
  return { response: { type: "session:pty:resize", ok: false } };
29167
29243
  }
29168
29244
  ensureSessionAccess(ctx, sid, "send");
29169
- const pty = manager.getPtyBySessionId(sid);
29170
- if (!pty) return { response: { type: "session:pty:resize", ok: false } };
29171
- try {
29172
- pty.resize(cols, rows);
29173
- } catch {
29174
- return { response: { type: "session:pty:resize", ok: false } };
29175
- }
29176
- return { response: { type: "session:pty:resize", ok: true } };
29245
+ const ok = manager.resizePty(sid, cols, rows);
29246
+ return { response: { type: "session:pty:resize", ok } };
29177
29247
  };
29178
29248
  const ptySnapshot = async (frame, _client, ctx) => {
29179
29249
  const sid = frame.sessionId;
@@ -29181,13 +29251,14 @@ function buildSessionHandlers(deps) {
29181
29251
  return { response: { type: "session:pty:snapshot", ok: false } };
29182
29252
  }
29183
29253
  ensureSessionAccess(ctx, sid, "read");
29184
- const snap = manager.getPtyReplay(sid);
29254
+ const snap = await manager.getPtyReplay(sid);
29185
29255
  if (!snap) {
29186
29256
  return {
29187
29257
  response: {
29188
29258
  type: "session:pty:snapshot",
29189
29259
  ok: true,
29190
29260
  data: null,
29261
+ atSeq: 0,
29191
29262
  popupKind: null
29192
29263
  }
29193
29264
  };
@@ -29197,6 +29268,7 @@ function buildSessionHandlers(deps) {
29197
29268
  type: "session:pty:snapshot",
29198
29269
  ok: true,
29199
29270
  data: Buffer.from(snap.data, "utf8").toString("base64"),
29271
+ atSeq: snap.atSeq,
29200
29272
  popupKind: snap.popupKind
29201
29273
  }
29202
29274
  };
@@ -30595,9 +30667,11 @@ async function startDaemon(config) {
30595
30667
  historyReader: new ClaudeHistoryReader(),
30596
30668
  onPtyRegister: (tsid, pty) => manager.registerPty(tsid, pty),
30597
30669
  onPtyUnregister: (tsid) => manager.unregisterPty(tsid),
30598
- onPtyData: (tsid, b64) => manager.emitPtyData(tsid, b64),
30670
+ onPtyData: (tsid, b64, seq) => manager.emitPtyChunk(tsid, b64, seq),
30599
30671
  onPopupTransition: (tsid, kind, visible) => manager.emitPopupTransition(tsid, kind, visible),
30600
- onPtyReplayRegister: (tsid, replay) => manager.registerPtyReplay(tsid, replay)
30672
+ onPtyReplayRegister: (tsid, replay) => manager.registerPtyReplay(tsid, replay),
30673
+ onDetectorRegister: (tsid, detector) => manager.registerDetector(tsid, detector),
30674
+ onDetectorUnregister: (tsid) => manager.unregisterDetector(tsid)
30601
30675
  }) : new ClaudeAdapter({ logger, historyReader: new ClaudeHistoryReader() });
30602
30676
  registerAdapter("claude", claudeAdapter);
30603
30677
  const personaRegistry = new PersonaRegistry(personaStore);
@@ -30763,16 +30837,8 @@ async function startDaemon(config) {
30763
30837
  questions: Array.isArray(questions) ? questions : []
30764
30838
  });
30765
30839
  }
30766
- const replay = manager.getPtyReplay(sessionId);
30767
- if (replay) {
30768
- if (replay.data) {
30769
- client.send({
30770
- type: "session:pty",
30771
- sessionId,
30772
- data: Buffer.from(replay.data, "utf8").toString("base64")
30773
- });
30774
- }
30775
- if (replay.popupKind) {
30840
+ void manager.getPtyReplay(sessionId).then((replay) => {
30841
+ if (replay && replay.popupKind) {
30776
30842
  client.send({
30777
30843
  type: "session:control",
30778
30844
  sessionId,
@@ -30780,7 +30846,8 @@ async function startDaemon(config) {
30780
30846
  popupKind: replay.popupKind
30781
30847
  });
30782
30848
  }
30783
- }
30849
+ }).catch(() => {
30850
+ });
30784
30851
  }
30785
30852
  });
30786
30853
  transport = wsServer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.93-beta.174.78b779b",
3
+ "version": "0.2.93-beta.175.b162fd0",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",