@clawos-dev/clawd 0.2.93-beta.174.78b779b → 0.2.93
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.
- package/dist/cli.cjs +135 -38
- 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
|
-
//
|
|
118
|
-
//
|
|
117
|
+
// 先 drain detector parse queue 再 serialize,保证 atSeq 与 buffer 内容严格对齐。
|
|
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"` 系统行
|
|
@@ -22601,6 +22606,11 @@ var SessionManager = class {
|
|
|
22601
22606
|
// 新 client 通过 session:subscribe 接入时由 onSubscribe hook 取 getPtyReplay(sessionId)
|
|
22602
22607
|
// 把屏幕快照定向 emit,解决"刷新页面 / 第二个 tab 接入时 xterm 白屏"
|
|
22603
22608
|
ptyReplaysByToolSid = /* @__PURE__ */ new Map();
|
|
22609
|
+
// TUI 模式:toolSessionId → @xterm/headless PopupDetector。
|
|
22610
|
+
// ClaudeTuiAdapter spawn 时通过 onDetectorRegister callback 写入;exit 时 onDetectorUnregister 清理。
|
|
22611
|
+
// manager.resizePty 用此映射把 UI 的 session:pty:resize 同步到 detector 内部 buffer,
|
|
22612
|
+
// 保证 snapshot serialize 时 cursor positioning 与 UI xterm 实际尺寸一致。
|
|
22613
|
+
detectorsByToolSid = /* @__PURE__ */ new Map();
|
|
22604
22614
|
// 仅 mode='tui' 需要:index.ts 装配阶段在 observer 构造后注入。
|
|
22605
22615
|
// observer.onEvent 要回灌 manager.feedObserverEvents,所以 observer 必须晚于 manager
|
|
22606
22616
|
// 构造;反过来 manager 又要在 mode='tui' 下 spawn session 时 attach observer。setter 解循环
|
|
@@ -23813,7 +23823,7 @@ var SessionManager = class {
|
|
|
23813
23823
|
* - session 存在但 pty 还没 spawn / 已 exit → null
|
|
23814
23824
|
* 调用方(onSubscribe hook)按返回值非空时构造 session:pty + session:control 帧定向回放
|
|
23815
23825
|
*/
|
|
23816
|
-
getPtyReplay(sessionId) {
|
|
23826
|
+
async getPtyReplay(sessionId) {
|
|
23817
23827
|
if (this.deps.mode !== "tui") {
|
|
23818
23828
|
probeEvent("replay.request", { sid: sessionId, result: "not-tui-mode" });
|
|
23819
23829
|
return null;
|
|
@@ -23842,7 +23852,7 @@ var SessionManager = class {
|
|
|
23842
23852
|
});
|
|
23843
23853
|
return null;
|
|
23844
23854
|
}
|
|
23845
|
-
const snap = replay();
|
|
23855
|
+
const snap = await replay();
|
|
23846
23856
|
const dumpFile = probeDumpReplay(sessionId, snap.data);
|
|
23847
23857
|
probeEvent("replay.dump", {
|
|
23848
23858
|
sid: sessionId,
|
|
@@ -23868,18 +23878,50 @@ var SessionManager = class {
|
|
|
23868
23878
|
if (!tsid) return void 0;
|
|
23869
23879
|
return this.ptyTransports.get(tsid);
|
|
23870
23880
|
}
|
|
23881
|
+
/** ClaudeTuiAdapter spawn 时注册 detector ref;同一 tsid 重 spawn 会覆盖 */
|
|
23882
|
+
registerDetector(toolSessionId, detector) {
|
|
23883
|
+
this.detectorsByToolSid.set(toolSessionId, detector);
|
|
23884
|
+
}
|
|
23885
|
+
/** spawn 进程 exit 时清理 */
|
|
23886
|
+
unregisterDetector(toolSessionId) {
|
|
23887
|
+
this.detectorsByToolSid.delete(toolSessionId);
|
|
23888
|
+
}
|
|
23889
|
+
/**
|
|
23890
|
+
* 同时 resize pty 子进程和 detector buffer:UI session:pty:resize 入口走这个。
|
|
23891
|
+
* 顺序:pty.resize 在前,detector.resize 在后 —— pty 是真正的 CC 子进程视图,必须先成功;
|
|
23892
|
+
* detector 只是 daemon 端镜像,任一步失败都返 false。
|
|
23893
|
+
* 找不到 runner / toolSessionId / pty 任一缺失 → 返 false,handler 静默给 UI 报 ok=false 不抛错。
|
|
23894
|
+
*/
|
|
23895
|
+
resizePty(sessionId, cols, rows) {
|
|
23896
|
+
const runner = this.runners.get(sessionId);
|
|
23897
|
+
if (!runner) return false;
|
|
23898
|
+
const tsid = runner.getState().file.toolSessionId;
|
|
23899
|
+
if (!tsid) return false;
|
|
23900
|
+
const pty = this.ptyTransports.get(tsid);
|
|
23901
|
+
if (!pty) return false;
|
|
23902
|
+
try {
|
|
23903
|
+
pty.resize(cols, rows);
|
|
23904
|
+
} catch {
|
|
23905
|
+
return false;
|
|
23906
|
+
}
|
|
23907
|
+
const detector = this.detectorsByToolSid.get(tsid);
|
|
23908
|
+
if (detector) detector.resize(cols, rows);
|
|
23909
|
+
return true;
|
|
23910
|
+
}
|
|
23871
23911
|
/**
|
|
23872
23912
|
* ClaudeTuiAdapter onPtyData callback:把 pty 字节流以 session:pty wire 帧广播给订阅者。
|
|
23913
|
+
* seq 来自 adapter spawn 路径维护的 per-session counter,与 detector.parseSeq 同源。
|
|
23914
|
+
* UI 端 snapshot/incremental dedup 必须靠这个 seq。
|
|
23873
23915
|
* 仅 mode='tui' 时发——SDK spawn 模式根本没 pty。
|
|
23874
|
-
* payload 永远只有 `{ sessionId, data }`,data = base64 UTF-8 字节流。
|
|
23916
|
+
* payload 永远只有 `{ sessionId, data, seq }`,data = base64 UTF-8 字节流。
|
|
23875
23917
|
* 详见 protocol/src/events.ts session:pty JSDoc
|
|
23876
23918
|
*/
|
|
23877
|
-
|
|
23919
|
+
emitPtyChunk(toolSessionId, b64, seq) {
|
|
23878
23920
|
if (this.deps.mode !== "tui") return;
|
|
23879
23921
|
const sid = this.sessionIdByToolSid(toolSessionId);
|
|
23880
23922
|
if (!sid) return;
|
|
23881
23923
|
this.routeFromRunner(
|
|
23882
|
-
{ type: "session:pty", sessionId: sid, data: b64 },
|
|
23924
|
+
{ type: "session:pty", sessionId: sid, data: b64, seq },
|
|
23883
23925
|
"broadcast"
|
|
23884
23926
|
);
|
|
23885
23927
|
}
|
|
@@ -25226,6 +25268,10 @@ function createPopupDetector(opts) {
|
|
|
25226
25268
|
let pendingClear = null;
|
|
25227
25269
|
let quietTimer = null;
|
|
25228
25270
|
const QUIET_MS = opts.quietMs ?? 300;
|
|
25271
|
+
let parseSeq = 0;
|
|
25272
|
+
let pendingParse = 0;
|
|
25273
|
+
let drainWaiters = [];
|
|
25274
|
+
let disposed = false;
|
|
25229
25275
|
const scanScreen = () => {
|
|
25230
25276
|
const buf = term.buffer.active;
|
|
25231
25277
|
const lines = [];
|
|
@@ -25271,15 +25317,43 @@ function createPopupDetector(opts) {
|
|
|
25271
25317
|
get visibleKind() {
|
|
25272
25318
|
return visible;
|
|
25273
25319
|
},
|
|
25274
|
-
|
|
25320
|
+
get parseSeq() {
|
|
25321
|
+
return parseSeq;
|
|
25322
|
+
},
|
|
25323
|
+
writeBytes(data, seq) {
|
|
25324
|
+
pendingParse++;
|
|
25275
25325
|
return new Promise((resolve6) => {
|
|
25276
25326
|
term.write(data, () => {
|
|
25327
|
+
if (disposed) {
|
|
25328
|
+
resolve6();
|
|
25329
|
+
return;
|
|
25330
|
+
}
|
|
25331
|
+
if (typeof seq === "number" && seq > parseSeq) parseSeq = seq;
|
|
25332
|
+
pendingParse--;
|
|
25277
25333
|
tick();
|
|
25334
|
+
if (pendingParse === 0 && drainWaiters.length > 0) {
|
|
25335
|
+
const waiters = drainWaiters;
|
|
25336
|
+
drainWaiters = [];
|
|
25337
|
+
for (const w2 of waiters) w2();
|
|
25338
|
+
}
|
|
25278
25339
|
resolve6();
|
|
25279
25340
|
});
|
|
25280
25341
|
});
|
|
25281
25342
|
},
|
|
25343
|
+
drain() {
|
|
25344
|
+
if (pendingParse === 0) return Promise.resolve();
|
|
25345
|
+
return new Promise((resolve6) => {
|
|
25346
|
+
drainWaiters.push(resolve6);
|
|
25347
|
+
});
|
|
25348
|
+
},
|
|
25349
|
+
resize(cols, rows) {
|
|
25350
|
+
try {
|
|
25351
|
+
term.resize(cols, rows);
|
|
25352
|
+
} catch {
|
|
25353
|
+
}
|
|
25354
|
+
},
|
|
25282
25355
|
dispose() {
|
|
25356
|
+
disposed = true;
|
|
25283
25357
|
if (quietTimer) {
|
|
25284
25358
|
clearTimeout(quietTimer);
|
|
25285
25359
|
quietTimer = null;
|
|
@@ -25288,6 +25362,9 @@ function createPopupDetector(opts) {
|
|
|
25288
25362
|
clearTimeout(pendingClear);
|
|
25289
25363
|
pendingClear = null;
|
|
25290
25364
|
}
|
|
25365
|
+
const waiters = drainWaiters;
|
|
25366
|
+
drainWaiters = [];
|
|
25367
|
+
for (const w2 of waiters) w2();
|
|
25291
25368
|
serializeAddon.dispose();
|
|
25292
25369
|
term.dispose();
|
|
25293
25370
|
},
|
|
@@ -25353,23 +25430,52 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
25353
25430
|
this.tuiOpts.onPopupTransition(ctx.toolSessionId, kind, visible);
|
|
25354
25431
|
}
|
|
25355
25432
|
});
|
|
25433
|
+
if (ctx.toolSessionId && this.tuiOpts.onDetectorRegister) {
|
|
25434
|
+
this.tuiOpts.onDetectorRegister(ctx.toolSessionId, detector);
|
|
25435
|
+
}
|
|
25436
|
+
let chunkSeq = 0;
|
|
25356
25437
|
if (ctx.toolSessionId && this.tuiOpts.onPtyReplayRegister) {
|
|
25357
|
-
this.tuiOpts.onPtyReplayRegister(ctx.toolSessionId, () =>
|
|
25358
|
-
|
|
25359
|
-
|
|
25360
|
-
|
|
25438
|
+
this.tuiOpts.onPtyReplayRegister(ctx.toolSessionId, async () => {
|
|
25439
|
+
await detector.drain();
|
|
25440
|
+
return {
|
|
25441
|
+
data: detector.serialize(),
|
|
25442
|
+
popupKind: detector.visibleKind,
|
|
25443
|
+
atSeq: detector.parseSeq
|
|
25444
|
+
};
|
|
25445
|
+
});
|
|
25361
25446
|
}
|
|
25362
|
-
|
|
25447
|
+
let coalesceBuf = [];
|
|
25448
|
+
let coalesceScheduled = false;
|
|
25449
|
+
const flushCoalesced = () => {
|
|
25450
|
+
coalesceScheduled = false;
|
|
25451
|
+
if (coalesceBuf.length === 0) return;
|
|
25452
|
+
const merged = Buffer.concat(coalesceBuf);
|
|
25453
|
+
coalesceBuf = [];
|
|
25454
|
+
chunkSeq++;
|
|
25455
|
+
const seq = chunkSeq;
|
|
25363
25456
|
if (ctx.toolSessionId && this.tuiOpts.onPtyData) {
|
|
25364
|
-
this.tuiOpts.onPtyData(ctx.toolSessionId,
|
|
25457
|
+
this.tuiOpts.onPtyData(ctx.toolSessionId, merged.toString("base64"), seq);
|
|
25365
25458
|
}
|
|
25366
|
-
void detector.writeBytes(
|
|
25459
|
+
void detector.writeBytes(merged.toString("utf8"), seq);
|
|
25367
25460
|
if (ctx.toolSessionId) {
|
|
25368
|
-
probePtyDataThrottled(ctx.toolSessionId,
|
|
25461
|
+
probePtyDataThrottled(ctx.toolSessionId, merged.length, merged);
|
|
25462
|
+
}
|
|
25463
|
+
};
|
|
25464
|
+
ptyChild.stdout.on("data", (data) => {
|
|
25465
|
+
coalesceBuf.push(data);
|
|
25466
|
+
if (!coalesceScheduled) {
|
|
25467
|
+
coalesceScheduled = true;
|
|
25468
|
+
queueMicrotask(flushCoalesced);
|
|
25369
25469
|
}
|
|
25370
25470
|
});
|
|
25371
25471
|
ptyChild.on("exit", () => {
|
|
25372
25472
|
probeEvent("pty.exit", { tsid: ctx.toolSessionId });
|
|
25473
|
+
if (coalesceBuf.length > 0) {
|
|
25474
|
+
flushCoalesced();
|
|
25475
|
+
}
|
|
25476
|
+
if (ctx.toolSessionId && this.tuiOpts.onDetectorUnregister) {
|
|
25477
|
+
this.tuiOpts.onDetectorUnregister(ctx.toolSessionId);
|
|
25478
|
+
}
|
|
25373
25479
|
if (ctx.toolSessionId && this.tuiOpts.onPtyUnregister) {
|
|
25374
25480
|
this.tuiOpts.onPtyUnregister(ctx.toolSessionId);
|
|
25375
25481
|
}
|
|
@@ -29166,14 +29272,8 @@ function buildSessionHandlers(deps) {
|
|
|
29166
29272
|
return { response: { type: "session:pty:resize", ok: false } };
|
|
29167
29273
|
}
|
|
29168
29274
|
ensureSessionAccess(ctx, sid, "send");
|
|
29169
|
-
const
|
|
29170
|
-
|
|
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 } };
|
|
29275
|
+
const ok = manager.resizePty(sid, cols, rows);
|
|
29276
|
+
return { response: { type: "session:pty:resize", ok } };
|
|
29177
29277
|
};
|
|
29178
29278
|
const ptySnapshot = async (frame, _client, ctx) => {
|
|
29179
29279
|
const sid = frame.sessionId;
|
|
@@ -29181,13 +29281,14 @@ function buildSessionHandlers(deps) {
|
|
|
29181
29281
|
return { response: { type: "session:pty:snapshot", ok: false } };
|
|
29182
29282
|
}
|
|
29183
29283
|
ensureSessionAccess(ctx, sid, "read");
|
|
29184
|
-
const snap = manager.getPtyReplay(sid);
|
|
29284
|
+
const snap = await manager.getPtyReplay(sid);
|
|
29185
29285
|
if (!snap) {
|
|
29186
29286
|
return {
|
|
29187
29287
|
response: {
|
|
29188
29288
|
type: "session:pty:snapshot",
|
|
29189
29289
|
ok: true,
|
|
29190
29290
|
data: null,
|
|
29291
|
+
atSeq: 0,
|
|
29191
29292
|
popupKind: null
|
|
29192
29293
|
}
|
|
29193
29294
|
};
|
|
@@ -29197,6 +29298,7 @@ function buildSessionHandlers(deps) {
|
|
|
29197
29298
|
type: "session:pty:snapshot",
|
|
29198
29299
|
ok: true,
|
|
29199
29300
|
data: Buffer.from(snap.data, "utf8").toString("base64"),
|
|
29301
|
+
atSeq: snap.atSeq,
|
|
29200
29302
|
popupKind: snap.popupKind
|
|
29201
29303
|
}
|
|
29202
29304
|
};
|
|
@@ -30595,9 +30697,11 @@ async function startDaemon(config) {
|
|
|
30595
30697
|
historyReader: new ClaudeHistoryReader(),
|
|
30596
30698
|
onPtyRegister: (tsid, pty) => manager.registerPty(tsid, pty),
|
|
30597
30699
|
onPtyUnregister: (tsid) => manager.unregisterPty(tsid),
|
|
30598
|
-
onPtyData: (tsid, b64) => manager.
|
|
30700
|
+
onPtyData: (tsid, b64, seq) => manager.emitPtyChunk(tsid, b64, seq),
|
|
30599
30701
|
onPopupTransition: (tsid, kind, visible) => manager.emitPopupTransition(tsid, kind, visible),
|
|
30600
|
-
onPtyReplayRegister: (tsid, replay) => manager.registerPtyReplay(tsid, replay)
|
|
30702
|
+
onPtyReplayRegister: (tsid, replay) => manager.registerPtyReplay(tsid, replay),
|
|
30703
|
+
onDetectorRegister: (tsid, detector) => manager.registerDetector(tsid, detector),
|
|
30704
|
+
onDetectorUnregister: (tsid) => manager.unregisterDetector(tsid)
|
|
30601
30705
|
}) : new ClaudeAdapter({ logger, historyReader: new ClaudeHistoryReader() });
|
|
30602
30706
|
registerAdapter("claude", claudeAdapter);
|
|
30603
30707
|
const personaRegistry = new PersonaRegistry(personaStore);
|
|
@@ -30763,16 +30867,8 @@ async function startDaemon(config) {
|
|
|
30763
30867
|
questions: Array.isArray(questions) ? questions : []
|
|
30764
30868
|
});
|
|
30765
30869
|
}
|
|
30766
|
-
|
|
30767
|
-
|
|
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) {
|
|
30870
|
+
void manager.getPtyReplay(sessionId).then((replay) => {
|
|
30871
|
+
if (replay && replay.popupKind) {
|
|
30776
30872
|
client.send({
|
|
30777
30873
|
type: "session:control",
|
|
30778
30874
|
sessionId,
|
|
@@ -30780,7 +30876,8 @@ async function startDaemon(config) {
|
|
|
30780
30876
|
popupKind: replay.popupKind
|
|
30781
30877
|
});
|
|
30782
30878
|
}
|
|
30783
|
-
}
|
|
30879
|
+
}).catch(() => {
|
|
30880
|
+
});
|
|
30784
30881
|
}
|
|
30785
30882
|
});
|
|
30786
30883
|
transport = wsServer;
|
package/package.json
CHANGED