@clawos-dev/clawd 0.2.93-beta.173.df765cf → 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 +135 -57
  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
  }
@@ -22600,6 +22586,11 @@ var SessionManager = class {
22600
22586
  // 新 client 通过 session:subscribe 接入时由 onSubscribe hook 取 getPtyReplay(sessionId)
22601
22587
  // 把屏幕快照定向 emit,解决"刷新页面 / 第二个 tab 接入时 xterm 白屏"
22602
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();
22603
22594
  // 仅 mode='tui' 需要:index.ts 装配阶段在 observer 构造后注入。
22604
22595
  // observer.onEvent 要回灌 manager.feedObserverEvents,所以 observer 必须晚于 manager
22605
22596
  // 构造;反过来 manager 又要在 mode='tui' 下 spawn session 时 attach observer。setter 解循环
@@ -23812,7 +23803,7 @@ var SessionManager = class {
23812
23803
  * - session 存在但 pty 还没 spawn / 已 exit → null
23813
23804
  * 调用方(onSubscribe hook)按返回值非空时构造 session:pty + session:control 帧定向回放
23814
23805
  */
23815
- getPtyReplay(sessionId) {
23806
+ async getPtyReplay(sessionId) {
23816
23807
  if (this.deps.mode !== "tui") {
23817
23808
  probeEvent("replay.request", { sid: sessionId, result: "not-tui-mode" });
23818
23809
  return null;
@@ -23841,7 +23832,7 @@ var SessionManager = class {
23841
23832
  });
23842
23833
  return null;
23843
23834
  }
23844
- const snap = replay();
23835
+ const snap = await replay();
23845
23836
  const dumpFile = probeDumpReplay(sessionId, snap.data);
23846
23837
  probeEvent("replay.dump", {
23847
23838
  sid: sessionId,
@@ -23867,18 +23858,50 @@ var SessionManager = class {
23867
23858
  if (!tsid) return void 0;
23868
23859
  return this.ptyTransports.get(tsid);
23869
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
+ }
23870
23891
  /**
23871
23892
  * ClaudeTuiAdapter onPtyData callback:把 pty 字节流以 session:pty wire 帧广播给订阅者。
23893
+ * seq 来自 adapter spawn 路径维护的 per-session counter,与 detector.parseSeq 同源。
23894
+ * UI 端 snapshot/incremental dedup 必须靠这个 seq。
23872
23895
  * 仅 mode='tui' 时发——SDK spawn 模式根本没 pty。
23873
- * payload 永远只有 `{ sessionId, data }`,data = base64 UTF-8 字节流。
23896
+ * payload 永远只有 `{ sessionId, data, seq }`,data = base64 UTF-8 字节流。
23874
23897
  * 详见 protocol/src/events.ts session:pty JSDoc
23875
23898
  */
23876
- emitPtyData(toolSessionId, b64) {
23899
+ emitPtyChunk(toolSessionId, b64, seq) {
23877
23900
  if (this.deps.mode !== "tui") return;
23878
23901
  const sid = this.sessionIdByToolSid(toolSessionId);
23879
23902
  if (!sid) return;
23880
23903
  this.routeFromRunner(
23881
- { type: "session:pty", sessionId: sid, data: b64 },
23904
+ { type: "session:pty", sessionId: sid, data: b64, seq },
23882
23905
  "broadcast"
23883
23906
  );
23884
23907
  }
@@ -25225,6 +25248,10 @@ function createPopupDetector(opts) {
25225
25248
  let pendingClear = null;
25226
25249
  let quietTimer = null;
25227
25250
  const QUIET_MS = opts.quietMs ?? 300;
25251
+ let parseSeq = 0;
25252
+ let pendingParse = 0;
25253
+ let drainWaiters = [];
25254
+ let disposed = false;
25228
25255
  const scanScreen = () => {
25229
25256
  const buf = term.buffer.active;
25230
25257
  const lines = [];
@@ -25270,15 +25297,43 @@ function createPopupDetector(opts) {
25270
25297
  get visibleKind() {
25271
25298
  return visible;
25272
25299
  },
25273
- writeBytes(data) {
25300
+ get parseSeq() {
25301
+ return parseSeq;
25302
+ },
25303
+ writeBytes(data, seq) {
25304
+ pendingParse++;
25274
25305
  return new Promise((resolve6) => {
25275
25306
  term.write(data, () => {
25307
+ if (disposed) {
25308
+ resolve6();
25309
+ return;
25310
+ }
25311
+ if (typeof seq === "number" && seq > parseSeq) parseSeq = seq;
25312
+ pendingParse--;
25276
25313
  tick();
25314
+ if (pendingParse === 0 && drainWaiters.length > 0) {
25315
+ const waiters = drainWaiters;
25316
+ drainWaiters = [];
25317
+ for (const w2 of waiters) w2();
25318
+ }
25277
25319
  resolve6();
25278
25320
  });
25279
25321
  });
25280
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
+ },
25281
25335
  dispose() {
25336
+ disposed = true;
25282
25337
  if (quietTimer) {
25283
25338
  clearTimeout(quietTimer);
25284
25339
  quietTimer = null;
@@ -25287,6 +25342,9 @@ function createPopupDetector(opts) {
25287
25342
  clearTimeout(pendingClear);
25288
25343
  pendingClear = null;
25289
25344
  }
25345
+ const waiters = drainWaiters;
25346
+ drainWaiters = [];
25347
+ for (const w2 of waiters) w2();
25290
25348
  serializeAddon.dispose();
25291
25349
  term.dispose();
25292
25350
  },
@@ -25352,23 +25410,52 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
25352
25410
  this.tuiOpts.onPopupTransition(ctx.toolSessionId, kind, visible);
25353
25411
  }
25354
25412
  });
25413
+ if (ctx.toolSessionId && this.tuiOpts.onDetectorRegister) {
25414
+ this.tuiOpts.onDetectorRegister(ctx.toolSessionId, detector);
25415
+ }
25416
+ let chunkSeq = 0;
25355
25417
  if (ctx.toolSessionId && this.tuiOpts.onPtyReplayRegister) {
25356
- this.tuiOpts.onPtyReplayRegister(ctx.toolSessionId, () => ({
25357
- data: detector.serialize(),
25358
- popupKind: detector.visibleKind
25359
- }));
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
+ });
25360
25426
  }
25361
- 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;
25362
25436
  if (ctx.toolSessionId && this.tuiOpts.onPtyData) {
25363
- this.tuiOpts.onPtyData(ctx.toolSessionId, data.toString("base64"));
25437
+ this.tuiOpts.onPtyData(ctx.toolSessionId, merged.toString("base64"), seq);
25364
25438
  }
25365
- void detector.writeBytes(data.toString("utf8"));
25439
+ void detector.writeBytes(merged.toString("utf8"), seq);
25366
25440
  if (ctx.toolSessionId) {
25367
- 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);
25368
25449
  }
25369
25450
  });
25370
25451
  ptyChild.on("exit", () => {
25371
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
+ }
25372
25459
  if (ctx.toolSessionId && this.tuiOpts.onPtyUnregister) {
25373
25460
  this.tuiOpts.onPtyUnregister(ctx.toolSessionId);
25374
25461
  }
@@ -29155,14 +29242,8 @@ function buildSessionHandlers(deps) {
29155
29242
  return { response: { type: "session:pty:resize", ok: false } };
29156
29243
  }
29157
29244
  ensureSessionAccess(ctx, sid, "send");
29158
- const pty = manager.getPtyBySessionId(sid);
29159
- if (!pty) return { response: { type: "session:pty:resize", ok: false } };
29160
- try {
29161
- pty.resize(cols, rows);
29162
- } catch {
29163
- return { response: { type: "session:pty:resize", ok: false } };
29164
- }
29165
- return { response: { type: "session:pty:resize", ok: true } };
29245
+ const ok = manager.resizePty(sid, cols, rows);
29246
+ return { response: { type: "session:pty:resize", ok } };
29166
29247
  };
29167
29248
  const ptySnapshot = async (frame, _client, ctx) => {
29168
29249
  const sid = frame.sessionId;
@@ -29170,13 +29251,14 @@ function buildSessionHandlers(deps) {
29170
29251
  return { response: { type: "session:pty:snapshot", ok: false } };
29171
29252
  }
29172
29253
  ensureSessionAccess(ctx, sid, "read");
29173
- const snap = manager.getPtyReplay(sid);
29254
+ const snap = await manager.getPtyReplay(sid);
29174
29255
  if (!snap) {
29175
29256
  return {
29176
29257
  response: {
29177
29258
  type: "session:pty:snapshot",
29178
29259
  ok: true,
29179
29260
  data: null,
29261
+ atSeq: 0,
29180
29262
  popupKind: null
29181
29263
  }
29182
29264
  };
@@ -29186,6 +29268,7 @@ function buildSessionHandlers(deps) {
29186
29268
  type: "session:pty:snapshot",
29187
29269
  ok: true,
29188
29270
  data: Buffer.from(snap.data, "utf8").toString("base64"),
29271
+ atSeq: snap.atSeq,
29189
29272
  popupKind: snap.popupKind
29190
29273
  }
29191
29274
  };
@@ -30584,9 +30667,11 @@ async function startDaemon(config) {
30584
30667
  historyReader: new ClaudeHistoryReader(),
30585
30668
  onPtyRegister: (tsid, pty) => manager.registerPty(tsid, pty),
30586
30669
  onPtyUnregister: (tsid) => manager.unregisterPty(tsid),
30587
- onPtyData: (tsid, b64) => manager.emitPtyData(tsid, b64),
30670
+ onPtyData: (tsid, b64, seq) => manager.emitPtyChunk(tsid, b64, seq),
30588
30671
  onPopupTransition: (tsid, kind, visible) => manager.emitPopupTransition(tsid, kind, visible),
30589
- 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)
30590
30675
  }) : new ClaudeAdapter({ logger, historyReader: new ClaudeHistoryReader() });
30591
30676
  registerAdapter("claude", claudeAdapter);
30592
30677
  const personaRegistry = new PersonaRegistry(personaStore);
@@ -30752,16 +30837,8 @@ async function startDaemon(config) {
30752
30837
  questions: Array.isArray(questions) ? questions : []
30753
30838
  });
30754
30839
  }
30755
- const replay = manager.getPtyReplay(sessionId);
30756
- if (replay) {
30757
- if (replay.data) {
30758
- client.send({
30759
- type: "session:pty",
30760
- sessionId,
30761
- data: Buffer.from(replay.data, "utf8").toString("base64")
30762
- });
30763
- }
30764
- if (replay.popupKind) {
30840
+ void manager.getPtyReplay(sessionId).then((replay) => {
30841
+ if (replay && replay.popupKind) {
30765
30842
  client.send({
30766
30843
  type: "session:control",
30767
30844
  sessionId,
@@ -30769,7 +30846,8 @@ async function startDaemon(config) {
30769
30846
  popupKind: replay.popupKind
30770
30847
  });
30771
30848
  }
30772
- }
30849
+ }).catch(() => {
30850
+ });
30773
30851
  }
30774
30852
  });
30775
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.173.df765cf",
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",