@clawos-dev/clawd 0.2.199-beta.400.ba99f40 → 0.2.200
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
CHANGED
|
@@ -29030,7 +29030,7 @@ var require_websocket = __commonJS({
|
|
|
29030
29030
|
var http3 = require("http");
|
|
29031
29031
|
var net3 = require("net");
|
|
29032
29032
|
var tls = require("tls");
|
|
29033
|
-
var { randomBytes, createHash:
|
|
29033
|
+
var { randomBytes, createHash: createHash3 } = require("crypto");
|
|
29034
29034
|
var { Duplex, Readable: Readable3 } = require("stream");
|
|
29035
29035
|
var { URL: URL2 } = require("url");
|
|
29036
29036
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -29690,7 +29690,7 @@ var require_websocket = __commonJS({
|
|
|
29690
29690
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
29691
29691
|
return;
|
|
29692
29692
|
}
|
|
29693
|
-
const digest =
|
|
29693
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
29694
29694
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
29695
29695
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
29696
29696
|
return;
|
|
@@ -30057,7 +30057,7 @@ var require_websocket_server = __commonJS({
|
|
|
30057
30057
|
var EventEmitter3 = require("events");
|
|
30058
30058
|
var http3 = require("http");
|
|
30059
30059
|
var { Duplex } = require("stream");
|
|
30060
|
-
var { createHash:
|
|
30060
|
+
var { createHash: createHash3 } = require("crypto");
|
|
30061
30061
|
var extension2 = require_extension();
|
|
30062
30062
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
30063
30063
|
var subprotocol2 = require_subprotocol();
|
|
@@ -30358,7 +30358,7 @@ var require_websocket_server = __commonJS({
|
|
|
30358
30358
|
);
|
|
30359
30359
|
}
|
|
30360
30360
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
30361
|
-
const digest =
|
|
30361
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
30362
30362
|
const headers = [
|
|
30363
30363
|
"HTTP/1.1 101 Switching Protocols",
|
|
30364
30364
|
"Upgrade: websocket",
|
|
@@ -40843,6 +40843,18 @@ function createLogger(opts = {}) {
|
|
|
40843
40843
|
);
|
|
40844
40844
|
return wrap(base);
|
|
40845
40845
|
}
|
|
40846
|
+
function createFileOnlyLogger(opts) {
|
|
40847
|
+
const level = opts.level ?? "debug";
|
|
40848
|
+
try {
|
|
40849
|
+
import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(opts.file), { recursive: true });
|
|
40850
|
+
} catch {
|
|
40851
|
+
}
|
|
40852
|
+
const base = (0, import_pino.default)(
|
|
40853
|
+
{ level, base: {} },
|
|
40854
|
+
import_pino.default.destination({ dest: opts.file, mkdir: true, sync: true })
|
|
40855
|
+
);
|
|
40856
|
+
return wrap(base);
|
|
40857
|
+
}
|
|
40846
40858
|
function pinoLevelToString(n) {
|
|
40847
40859
|
if (typeof n !== "number") return null;
|
|
40848
40860
|
if (n >= 50) return "error";
|
|
@@ -43008,9 +43020,18 @@ var SessionManager = class {
|
|
|
43008
43020
|
// 由 observer 监听 jsonl user 行后调 recordRealUserUuid 建立映射;rewind 系列 RPC 在
|
|
43009
43021
|
// 入参 / 出参做转译,保证 UI 看到的 uuid 始终是 events 流里的 synth uuid
|
|
43010
43022
|
realUuidBySynth = /* @__PURE__ */ new Map();
|
|
43011
|
-
//
|
|
43012
|
-
//
|
|
43013
|
-
|
|
43023
|
+
// observer 收到 `turn_duration` 信号但屏幕还没稳定 5s → 挂进 pending,等 `notifyScreenIdle`
|
|
43024
|
+
// (屏幕 armed=true 触发点)flush 成真正的 turn_end 进 reducer。
|
|
43025
|
+
//
|
|
43026
|
+
// 语义澄清:`turn_duration` 是 CC 报的原始信号("本轮 API 调用完了"),**不代表 turn 真的结束**
|
|
43027
|
+
//(背景 agent 可能还在跑)。屏幕 5s 稳定 + 无 popup 才是"确认信号"。
|
|
43028
|
+
// 两者 AND 后 daemon 才产生真正的 `turn_end` 事件送给 reducer。
|
|
43029
|
+
//
|
|
43030
|
+
// pending 不设截止时间:屏幕稳定的时机由 CC UI 决定,可能几秒也可能几分钟。观察者以
|
|
43031
|
+
// "屏幕真的稳定"作为触发点,signal-driven 而非 timer-driven。
|
|
43032
|
+
//
|
|
43033
|
+
// 清理点:newSession / stop / session-delete / stopAll。
|
|
43034
|
+
pendingTurnDurationSignals = /* @__PURE__ */ new Set();
|
|
43014
43035
|
// SessionStore 按 scope 派生(root = <dataDir>/sessions/<scopeSubPath>/)。
|
|
43015
43036
|
// default scope 直接复用 deps.store;persona scope(owner / listener)第一次访问时按需创建并缓存。
|
|
43016
43037
|
// 取代旧的 storesByAgent —— agentId 概念由 SessionScope 取代,路径即身份,
|
|
@@ -43350,6 +43371,14 @@ var SessionManager = class {
|
|
|
43350
43371
|
routeFromRunner(frame, target) {
|
|
43351
43372
|
const compressed = compressFrameForWire(frame);
|
|
43352
43373
|
if (!compressed) return;
|
|
43374
|
+
if (compressed.type === "session:status") {
|
|
43375
|
+
const s = compressed;
|
|
43376
|
+
this.deps.screenIdleProbeLogger?.info("session:status wire emit", {
|
|
43377
|
+
sessionId: s.sessionId,
|
|
43378
|
+
status: s.status,
|
|
43379
|
+
target
|
|
43380
|
+
});
|
|
43381
|
+
}
|
|
43353
43382
|
if (compressed.type === "session:event" || compressed.type === "session:status") {
|
|
43354
43383
|
const sid = compressed.sessionId;
|
|
43355
43384
|
if (sid) {
|
|
@@ -43623,7 +43652,7 @@ var SessionManager = class {
|
|
|
43623
43652
|
this.runners.delete(args.sessionId);
|
|
43624
43653
|
this.realUuidBySynth.delete(args.sessionId);
|
|
43625
43654
|
this.lastUiSizeBySessionId.delete(args.sessionId);
|
|
43626
|
-
this.
|
|
43655
|
+
this.clearPendingTurnEnd(args.sessionId);
|
|
43627
43656
|
return { response: { sessionId: args.sessionId }, broadcast };
|
|
43628
43657
|
}
|
|
43629
43658
|
this.deleteOwned(args.sessionId);
|
|
@@ -43653,6 +43682,7 @@ var SessionManager = class {
|
|
|
43653
43682
|
async stop(args) {
|
|
43654
43683
|
const runner = this.runners.get(args.sessionId);
|
|
43655
43684
|
if (!runner) return { response: { ok: true }, broadcast: [] };
|
|
43685
|
+
this.clearPendingTurnEnd(args.sessionId);
|
|
43656
43686
|
const { broadcast } = this.withCollector(() => {
|
|
43657
43687
|
runner.input({ kind: "command", command: { kind: "stop" } });
|
|
43658
43688
|
});
|
|
@@ -43786,6 +43816,7 @@ var SessionManager = class {
|
|
|
43786
43816
|
newSession(args) {
|
|
43787
43817
|
const existingFile = this.getFile(args.sessionId);
|
|
43788
43818
|
const nextToolSessionId = this.deps.mode === "tui" && (existingFile.tool ?? "claude") === "claude" ? v4_default() : void 0;
|
|
43819
|
+
this.clearPendingTurnEnd(args.sessionId);
|
|
43789
43820
|
const runner = this.runners.get(args.sessionId);
|
|
43790
43821
|
if (runner) {
|
|
43791
43822
|
const { value, broadcast } = this.withCollector(() => {
|
|
@@ -43886,6 +43917,7 @@ var SessionManager = class {
|
|
|
43886
43917
|
for (const r of this.runners.values()) {
|
|
43887
43918
|
r.input({ kind: "command", command: { kind: "stop" } });
|
|
43888
43919
|
}
|
|
43920
|
+
this.pendingTurnDurationSignals.clear();
|
|
43889
43921
|
}
|
|
43890
43922
|
// 给 observer 用:拿已存在的 runner
|
|
43891
43923
|
getActive(sessionId) {
|
|
@@ -44102,7 +44134,7 @@ var SessionManager = class {
|
|
|
44102
44134
|
this.runners.delete(args.sessionId);
|
|
44103
44135
|
this.realUuidBySynth.delete(args.sessionId);
|
|
44104
44136
|
this.lastUiSizeBySessionId.delete(args.sessionId);
|
|
44105
|
-
this.
|
|
44137
|
+
this.clearPendingTurnEnd(args.sessionId);
|
|
44106
44138
|
return { response: { sessionId: args.sessionId }, broadcast };
|
|
44107
44139
|
}
|
|
44108
44140
|
this.storeFor(args.scope).delete(args.sessionId);
|
|
@@ -44348,23 +44380,93 @@ var SessionManager = class {
|
|
|
44348
44380
|
return;
|
|
44349
44381
|
}
|
|
44350
44382
|
}
|
|
44351
|
-
this.lastObserverEventAt.set(sessionId, (this.deps.now ?? Date.now)());
|
|
44352
44383
|
let feedEvents = outEvents;
|
|
44353
44384
|
if (outEvents.some((e) => e.kind === "turn_end")) {
|
|
44354
|
-
const
|
|
44355
|
-
|
|
44385
|
+
const runnerState = runner.getState();
|
|
44386
|
+
const toolSessionId = runnerState.file.toolSessionId;
|
|
44387
|
+
const adapter = this.deps.getAdapter(runnerState.file.tool ?? "claude");
|
|
44388
|
+
const gateOpen = !adapter.canAcceptTurnEnd || !toolSessionId ? true : adapter.canAcceptTurnEnd(toolSessionId);
|
|
44389
|
+
this.deps.screenIdleProbeLogger?.info("turn_duration signal received", {
|
|
44390
|
+
sessionId,
|
|
44391
|
+
toolSessionId,
|
|
44392
|
+
batchKinds: outEvents.map((e) => e.kind),
|
|
44393
|
+
gateOpen,
|
|
44394
|
+
hasCanAcceptGate: !!adapter.canAcceptTurnEnd
|
|
44395
|
+
});
|
|
44396
|
+
if (gateOpen) {
|
|
44397
|
+
this.deps.screenIdleProbeLogger?.info(
|
|
44398
|
+
"turn_duration \u2192 turn_end confirmed (gate open) \u2192 fed to reducer",
|
|
44399
|
+
{ sessionId, toolSessionId }
|
|
44400
|
+
);
|
|
44401
|
+
} else {
|
|
44356
44402
|
feedEvents = outEvents.filter((e) => e.kind !== "turn_end");
|
|
44357
|
-
this.
|
|
44358
|
-
|
|
44359
|
-
|
|
44360
|
-
|
|
44361
|
-
|
|
44362
|
-
}
|
|
44403
|
+
if (this.pendingTurnDurationSignals.has(sessionId)) {
|
|
44404
|
+
this.deps.screenIdleProbeLogger?.info(
|
|
44405
|
+
"turn_duration dedup: pending already set (repeated observer signal)",
|
|
44406
|
+
{ sessionId, toolSessionId }
|
|
44407
|
+
);
|
|
44408
|
+
} else {
|
|
44409
|
+
this.pendingTurnDurationSignals.add(sessionId);
|
|
44410
|
+
this.deps.screenIdleProbeLogger?.info(
|
|
44411
|
+
"turn_duration pending: gate closed \u2192 waiting for screen-idle signal",
|
|
44412
|
+
{ sessionId, toolSessionId }
|
|
44413
|
+
);
|
|
44414
|
+
}
|
|
44363
44415
|
}
|
|
44364
44416
|
}
|
|
44365
44417
|
if (feedEvents.length === 0) return;
|
|
44366
44418
|
runner.feedObserverEvents(feedEvents);
|
|
44367
44419
|
}
|
|
44420
|
+
/**
|
|
44421
|
+
* `ClaudeTuiAdapter.observeScreenIdle` fire triggered → armed=true 时调用(一次性触发点)。
|
|
44422
|
+
*
|
|
44423
|
+
* 语义:屏幕真的稳定了 5s。查有没有 pending 的 turn_duration 信号:
|
|
44424
|
+
* - 有 → 之前收到的 turn_duration 被屏幕稳定**确认**了,flush 成 turn_end 进 reducer
|
|
44425
|
+
* - 无 → 屏幕稳定但从没收到 turn_duration → noop(不生成 turn_end;补偿路径的错误做法)
|
|
44426
|
+
*
|
|
44427
|
+
* pending 集合是**必要前提**:turn_end 只能来自"observer 收 turn_duration + 屏幕后续稳定"
|
|
44428
|
+
* 双源确认,任何一个缺少都不能 emit。这跟 PR #962 拆掉的 dispatchTurnIdle "屏幕静止就补
|
|
44429
|
+
* turn_end" 语义不同。
|
|
44430
|
+
*
|
|
44431
|
+
* 仅 TUI 模式;SDK / codex 没有屏幕信号也就不会触发本方法。
|
|
44432
|
+
*/
|
|
44433
|
+
notifyScreenIdle(toolSessionId) {
|
|
44434
|
+
if (this.deps.mode !== "tui") return;
|
|
44435
|
+
const sid = this.sessionIdByToolSid(toolSessionId);
|
|
44436
|
+
if (!sid) {
|
|
44437
|
+
this.deps.screenIdleProbeLogger?.warn("notifyScreenIdle: no session for toolSessionId", {
|
|
44438
|
+
toolSessionId
|
|
44439
|
+
});
|
|
44440
|
+
return;
|
|
44441
|
+
}
|
|
44442
|
+
if (!this.pendingTurnDurationSignals.has(sid)) {
|
|
44443
|
+
this.deps.screenIdleProbeLogger?.info(
|
|
44444
|
+
"notifyScreenIdle: no pending turn_duration \u2192 noop",
|
|
44445
|
+
{ sessionId: sid, toolSessionId }
|
|
44446
|
+
);
|
|
44447
|
+
return;
|
|
44448
|
+
}
|
|
44449
|
+
const runner = this.runners.get(sid);
|
|
44450
|
+
if (!runner) {
|
|
44451
|
+
this.pendingTurnDurationSignals.delete(sid);
|
|
44452
|
+
this.deps.screenIdleProbeLogger?.warn(
|
|
44453
|
+
"notifyScreenIdle: pending but no runner \u2192 cleared without inject",
|
|
44454
|
+
{ sessionId: sid, toolSessionId }
|
|
44455
|
+
);
|
|
44456
|
+
return;
|
|
44457
|
+
}
|
|
44458
|
+
this.pendingTurnDurationSignals.delete(sid);
|
|
44459
|
+
this.deps.screenIdleProbeLogger?.info(
|
|
44460
|
+
"notifyScreenIdle: pending turn_duration + screen idle confirmed \u2192 inject turn_end",
|
|
44461
|
+
{ sessionId: sid, toolSessionId }
|
|
44462
|
+
);
|
|
44463
|
+
runner.input({ kind: "inject-events", events: [{ kind: "turn_end" }] });
|
|
44464
|
+
}
|
|
44465
|
+
clearPendingTurnEnd(sessionId) {
|
|
44466
|
+
if (this.pendingTurnDurationSignals.delete(sessionId)) {
|
|
44467
|
+
this.deps.screenIdleProbeLogger?.info("pending turn_duration cleared", { sessionId });
|
|
44468
|
+
}
|
|
44469
|
+
}
|
|
44368
44470
|
// AskUserQuestion 表单回写(plan: clawd-ask-user-question):UI 答完所有 question 后调用。
|
|
44369
44471
|
// - session 不存在 / 无 runner → noop 幂等返回 ok(first-decider-wins)
|
|
44370
44472
|
// - reducer noop(toolUseId 不存在或已答过)也保持幂等返回,handler 不抛
|
|
@@ -44623,70 +44725,6 @@ var SessionManager = class {
|
|
|
44623
44725
|
if (!runner) return;
|
|
44624
44726
|
runner.input({ kind: "ready-detected" });
|
|
44625
44727
|
}
|
|
44626
|
-
/**
|
|
44627
|
-
* ClaudeTuiAdapter onTurnIdle callback:屏幕内容静止时**复发**本轮已出现过的权威 turn_end。
|
|
44628
|
-
* 本意:turn_duration 写盘早于尾段正文 → observer 把尾随 text 推进 buffer 盖掉 lastEventKind →
|
|
44629
|
-
* spinner 不熄;屏幕静止时再补一条 turn_end 排到尾随 text 之后。
|
|
44630
|
-
*
|
|
44631
|
-
* Fix A(修 bug1 "UI 还在变却显示结束" / bug2 "发消息后无 spinner"):补偿**只复发不 originate**——
|
|
44632
|
-
* 仅当本轮已出现过 turn_end(turnEndSeenThisTurn,即真有过尾随 text 覆盖场景)才补。turnEndSeenThisTurn
|
|
44633
|
-
* ===false 说明本轮 CC 从未结束过(仍在工作 / 刚发消息没产出 / 漏检弹框等用户),屏幕静止 ≠ turn 结束,
|
|
44634
|
-
* 凭空 inject turn_end 会误灭 spinner——故跳过,让真正的 turn_duration 来时再正常收。
|
|
44635
|
-
* 仅 tui 模式;runner 缺失 noop。turn_end 无 uuid 不参与 dedup。
|
|
44636
|
-
*/
|
|
44637
|
-
dispatchTurnIdle(toolSessionId) {
|
|
44638
|
-
if (this.deps.mode !== "tui") return;
|
|
44639
|
-
const sid = this.sessionIdByToolSid(toolSessionId);
|
|
44640
|
-
const runner = sid ? this.runners.get(sid) : void 0;
|
|
44641
|
-
if (!runner) return;
|
|
44642
|
-
const ev = this.peekTurnEvidence(runner);
|
|
44643
|
-
const willInject = ev.turnEndSeenThisTurn;
|
|
44644
|
-
this.deps.logger?.info("[TE-PROBE] screen-idle compensation", {
|
|
44645
|
-
sessionId: sid,
|
|
44646
|
-
src: "screen-idle",
|
|
44647
|
-
willInject,
|
|
44648
|
-
...ev
|
|
44649
|
-
});
|
|
44650
|
-
if (!willInject) return;
|
|
44651
|
-
runner.input({ kind: "inject-events", events: [{ kind: "turn_end" }] });
|
|
44652
|
-
}
|
|
44653
|
-
/**
|
|
44654
|
-
* 读 runner 当前 turn 态快照,供两个 turn_end 注入源(屏幕静止补偿 / observer turn_duration)
|
|
44655
|
-
* 判断误发 + 打 [TE-PROBE] 日志。
|
|
44656
|
-
* turnEndSeenThisTurn:从 buffer 末尾回扫到最近 user_text 期间是否已出现过 turn_end
|
|
44657
|
-
* (已出现=本轮真结束过、合法尾随;未出现=本轮还没结束过)→ Fix A 复发闸。
|
|
44658
|
-
* turnHasContent:末条是否 assistant 产出(非 user_text/turn_end/空)→ Fix B 空 turn 守卫闸。
|
|
44659
|
-
*/
|
|
44660
|
-
peekTurnEvidence(runner) {
|
|
44661
|
-
const st = runner.getState();
|
|
44662
|
-
const buf = st.buffer;
|
|
44663
|
-
const lastEventKindBefore = buf.length > 0 ? buf[buf.length - 1].event.kind : null;
|
|
44664
|
-
let turnEndSeenThisTurn = false;
|
|
44665
|
-
for (let i = buf.length - 1; i >= 0; i--) {
|
|
44666
|
-
const k2 = buf[i].event.kind;
|
|
44667
|
-
if (k2 === "user_text") break;
|
|
44668
|
-
if (k2 === "turn_end") {
|
|
44669
|
-
turnEndSeenThisTurn = true;
|
|
44670
|
-
break;
|
|
44671
|
-
}
|
|
44672
|
-
}
|
|
44673
|
-
const turnHasContent = lastEventKindBefore !== null && lastEventKindBefore !== "user_text" && lastEventKindBefore !== "turn_end";
|
|
44674
|
-
return { turnOpenBefore: st.turnOpen, lastEventKindBefore, turnEndSeenThisTurn, turnHasContent };
|
|
44675
|
-
}
|
|
44676
|
-
/**
|
|
44677
|
-
* observer 还需静止多久(ms)才满 idleMs,0 = 已满。observeScreenIdle 复合条件闸:屏幕静止后
|
|
44678
|
-
* 精确等这段剩余再补 turn_end —— turn_duration 写盘早于尾段正文,observer 把尾随 text poll 落盘
|
|
44679
|
-
* 期间屏幕可能已静止,仅看屏幕会早 fire(补的 turn_end 盖不到尾随 text 之后)。
|
|
44680
|
-
* 找不到 runner / 从无事件 → 0(不阻塞 fire)。idleMs 由装配处传 SCREEN_IDLE_MS。
|
|
44681
|
-
*/
|
|
44682
|
-
observerIdleWaitMs(toolSessionId, idleMs) {
|
|
44683
|
-
const sid = this.sessionIdByToolSid(toolSessionId);
|
|
44684
|
-
if (!sid) return 0;
|
|
44685
|
-
const last = this.lastObserverEventAt.get(sid);
|
|
44686
|
-
if (last === void 0) return 0;
|
|
44687
|
-
const elapsed = (this.deps.now ?? Date.now)() - last;
|
|
44688
|
-
return Math.max(0, idleMs - elapsed);
|
|
44689
|
-
}
|
|
44690
44728
|
/** toolSessionId → sessionId 反查(遍历 runners);session 数典型 < 10,O(n) 可接受 */
|
|
44691
44729
|
sessionIdByToolSid(toolSessionId) {
|
|
44692
44730
|
for (const [sid, runner] of this.runners) {
|
|
@@ -46429,6 +46467,7 @@ var CodexAdapter = class {
|
|
|
46429
46467
|
};
|
|
46430
46468
|
|
|
46431
46469
|
// src/tools/claude-tui.ts
|
|
46470
|
+
var import_node_crypto5 = require("crypto");
|
|
46432
46471
|
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
46433
46472
|
var import_node_os7 = __toESM(require("os"), 1);
|
|
46434
46473
|
var import_node_path14 = __toESM(require("path"), 1);
|
|
@@ -47239,22 +47278,56 @@ function observeScreenIdle(surface, opts) {
|
|
|
47239
47278
|
timer = null;
|
|
47240
47279
|
if (disposed) return;
|
|
47241
47280
|
if (opts.getPopupVisible()) {
|
|
47281
|
+
opts.probeLogger?.info("screen-idle fire suppressed: popup visible", {
|
|
47282
|
+
label: opts.probeLabel
|
|
47283
|
+
});
|
|
47242
47284
|
timer = setTimeout(fire, opts.idleMs);
|
|
47243
47285
|
return;
|
|
47244
47286
|
}
|
|
47245
47287
|
const obsWait = opts.getObserverWaitMs?.() ?? 0;
|
|
47246
47288
|
if (obsWait > 0) {
|
|
47289
|
+
opts.probeLogger?.info("screen-idle fire suppressed: observer not idle", {
|
|
47290
|
+
label: opts.probeLabel,
|
|
47291
|
+
obsWait
|
|
47292
|
+
});
|
|
47247
47293
|
timer = setTimeout(fire, Math.max(obsWait, REWAIT_MIN_MS));
|
|
47248
47294
|
return;
|
|
47249
47295
|
}
|
|
47250
|
-
if (armed)
|
|
47296
|
+
if (armed) {
|
|
47297
|
+
opts.probeLogger?.debug("screen-idle fire noop: already armed", {
|
|
47298
|
+
label: opts.probeLabel
|
|
47299
|
+
});
|
|
47300
|
+
return;
|
|
47301
|
+
}
|
|
47251
47302
|
armed = true;
|
|
47303
|
+
opts.probeLogger?.info("screen-idle fire triggered \u2192 armed=true, calling onIdle", {
|
|
47304
|
+
label: opts.probeLabel
|
|
47305
|
+
});
|
|
47252
47306
|
opts.onIdle();
|
|
47253
47307
|
};
|
|
47254
47308
|
const unsub = surface.onTick((lines) => {
|
|
47255
47309
|
if (disposed) return;
|
|
47256
47310
|
const snap = snapOf(lines);
|
|
47257
47311
|
if (snap === lastSnap) return;
|
|
47312
|
+
if (opts.probeLogger) {
|
|
47313
|
+
const prev = lastSnap;
|
|
47314
|
+
const meta = {
|
|
47315
|
+
label: opts.probeLabel,
|
|
47316
|
+
prevHash: prev === null ? null : shortHash(prev),
|
|
47317
|
+
nextHash: shortHash(snap),
|
|
47318
|
+
prevLen: prev?.length ?? 0,
|
|
47319
|
+
nextLen: snap.length
|
|
47320
|
+
};
|
|
47321
|
+
if (prev !== null) {
|
|
47322
|
+
const diff2 = firstLineDiff(prev, snap);
|
|
47323
|
+
if (diff2) {
|
|
47324
|
+
meta.diffRow = diff2.row;
|
|
47325
|
+
meta.prevRow = diff2.prev;
|
|
47326
|
+
meta.nextRow = diff2.next;
|
|
47327
|
+
}
|
|
47328
|
+
}
|
|
47329
|
+
opts.probeLogger.info("screen-idle tick snap changed", meta);
|
|
47330
|
+
}
|
|
47258
47331
|
lastSnap = snap;
|
|
47259
47332
|
armed = false;
|
|
47260
47333
|
clear();
|
|
@@ -47265,9 +47338,38 @@ function observeScreenIdle(surface, opts) {
|
|
|
47265
47338
|
disposed = true;
|
|
47266
47339
|
unsub();
|
|
47267
47340
|
clear();
|
|
47341
|
+
},
|
|
47342
|
+
isIdle() {
|
|
47343
|
+
const popupVisible = opts.getPopupVisible();
|
|
47344
|
+
const idle = armed && !popupVisible;
|
|
47345
|
+
if (opts.probeLogger) {
|
|
47346
|
+
opts.probeLogger.info("screen-idle isIdle check", {
|
|
47347
|
+
label: opts.probeLabel,
|
|
47348
|
+
idle,
|
|
47349
|
+
armed,
|
|
47350
|
+
popupVisible
|
|
47351
|
+
});
|
|
47352
|
+
}
|
|
47353
|
+
return idle;
|
|
47268
47354
|
}
|
|
47269
47355
|
};
|
|
47270
47356
|
}
|
|
47357
|
+
function shortHash(s) {
|
|
47358
|
+
return (0, import_node_crypto5.createHash)("sha1").update(s).digest("hex").slice(0, 8);
|
|
47359
|
+
}
|
|
47360
|
+
function firstLineDiff(prev, next) {
|
|
47361
|
+
const p2 = prev.split("\n");
|
|
47362
|
+
const n = next.split("\n");
|
|
47363
|
+
const rows = Math.max(p2.length, n.length);
|
|
47364
|
+
for (let i = 0; i < rows; i++) {
|
|
47365
|
+
const pl = p2[i] ?? "";
|
|
47366
|
+
const nl = n[i] ?? "";
|
|
47367
|
+
if (pl !== nl) {
|
|
47368
|
+
return { row: i, prev: pl.slice(0, 60), next: nl.slice(0, 60) };
|
|
47369
|
+
}
|
|
47370
|
+
}
|
|
47371
|
+
return null;
|
|
47372
|
+
}
|
|
47271
47373
|
var BYPASS_SETTLE_MS = 300;
|
|
47272
47374
|
var SCREEN_IDLE_MS = 5e3;
|
|
47273
47375
|
function createBootGate(pty, logger) {
|
|
@@ -47342,11 +47444,42 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
47342
47444
|
// 用于 spawn / PtyChildProcess 链路打日志
|
|
47343
47445
|
tuiLogger;
|
|
47344
47446
|
tuiOpts;
|
|
47447
|
+
/**
|
|
47448
|
+
* per-toolSessionId 的 tui 观察者句柄,仅用于 turn_end gate 查询(`canAcceptTurnEnd`)。
|
|
47449
|
+
* onIdle / onPopupTransition 等回调仍走原有闭包(不复用这份 map),本 map 只承担
|
|
47450
|
+
* "manager 需要跨模块查屏幕/弹框状态"这单一职责。
|
|
47451
|
+
*/
|
|
47452
|
+
tuiStates = /* @__PURE__ */ new Map();
|
|
47345
47453
|
constructor(opts = {}) {
|
|
47346
47454
|
super(opts);
|
|
47347
47455
|
this.tuiLogger = opts.logger;
|
|
47348
47456
|
this.tuiOpts = opts;
|
|
47349
47457
|
}
|
|
47458
|
+
/**
|
|
47459
|
+
* TUI adapter 的 turn_end 权威判定:屏幕已 idle 且非弹框态才放行。
|
|
47460
|
+
*
|
|
47461
|
+
* `feedObserverEvents` 收到 observer 回灌 `turn_end` 时调用。屏幕仍在变(如后台 agent 在跑)
|
|
47462
|
+
* 时 drop 掉 turn_end,避免 `system/turn_duration` JSONL 帧误触发 running-idle 状态转换。
|
|
47463
|
+
*
|
|
47464
|
+
* 未跟踪的 toolSessionId(spawn 前 / spawn 失败 / 已 dispose)视为 pass —— gate 只 drop
|
|
47465
|
+
* "有证据判定为伪信号"的场景,不做 unknown → block。
|
|
47466
|
+
*/
|
|
47467
|
+
canAcceptTurnEnd(toolSessionId) {
|
|
47468
|
+
const state = this.tuiStates.get(toolSessionId);
|
|
47469
|
+
if (!state) {
|
|
47470
|
+
this.tuiOpts.screenIdleProbeLogger?.info(
|
|
47471
|
+
"canAcceptTurnEnd: no tuiState \u2192 pass (\u672A\u8DDF\u8E2A)",
|
|
47472
|
+
{ toolSessionId }
|
|
47473
|
+
);
|
|
47474
|
+
return true;
|
|
47475
|
+
}
|
|
47476
|
+
const result = state.screenIdle.isIdle();
|
|
47477
|
+
this.tuiOpts.screenIdleProbeLogger?.info("canAcceptTurnEnd", {
|
|
47478
|
+
toolSessionId,
|
|
47479
|
+
result
|
|
47480
|
+
});
|
|
47481
|
+
return result;
|
|
47482
|
+
}
|
|
47350
47483
|
spawn(ctx) {
|
|
47351
47484
|
const args = buildTuiSpawnArgs(ctx, jsonlExistsForCtx(ctx));
|
|
47352
47485
|
const cmd = process.env.CLAUDE_BIN ?? "claude";
|
|
@@ -47404,18 +47537,26 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
47404
47537
|
const screenIdleObserver = observeScreenIdle(surface, {
|
|
47405
47538
|
idleMs: SCREEN_IDLE_MS,
|
|
47406
47539
|
onIdle: () => {
|
|
47407
|
-
if (!ctx.toolSessionId || !this.tuiOpts.
|
|
47408
|
-
this.tuiLogger?.debug("screen-idle \u2192
|
|
47409
|
-
this.tuiOpts.
|
|
47540
|
+
if (!ctx.toolSessionId || !this.tuiOpts.onScreenIdle) return;
|
|
47541
|
+
this.tuiLogger?.debug("screen-idle \u2192 notifyScreenIdle", { toolSessionId: ctx.toolSessionId });
|
|
47542
|
+
this.tuiOpts.onScreenIdle(ctx.toolSessionId);
|
|
47410
47543
|
},
|
|
47411
47544
|
getPopupVisible: () => popupObserver.visibleKind !== null,
|
|
47412
|
-
//
|
|
47413
|
-
|
|
47414
|
-
|
|
47545
|
+
// 取证 probe(可选,装配处传独立 file-only logger,跟主 daemon.log 解耦)
|
|
47546
|
+
...this.tuiOpts.screenIdleProbeLogger ? {
|
|
47547
|
+
probeLogger: this.tuiOpts.screenIdleProbeLogger,
|
|
47548
|
+
probeLabel: ctx.toolSessionId ?? "<no-tsid>"
|
|
47549
|
+
} : {}
|
|
47415
47550
|
});
|
|
47416
47551
|
if (ctx.toolSessionId && this.tuiOpts.onSurfaceRegister) {
|
|
47417
47552
|
this.tuiOpts.onSurfaceRegister(ctx.toolSessionId, surface);
|
|
47418
47553
|
}
|
|
47554
|
+
if (ctx.toolSessionId) {
|
|
47555
|
+
this.tuiStates.set(ctx.toolSessionId, {
|
|
47556
|
+
screenIdle: screenIdleObserver,
|
|
47557
|
+
popup: popupObserver
|
|
47558
|
+
});
|
|
47559
|
+
}
|
|
47419
47560
|
let chunkSeq = 0;
|
|
47420
47561
|
if (ctx.toolSessionId && this.tuiOpts.onPtyReplayRegister) {
|
|
47421
47562
|
this.tuiOpts.onPtyReplayRegister(ctx.toolSessionId, async () => {
|
|
@@ -47461,6 +47602,9 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
47461
47602
|
readyObserver.dispose();
|
|
47462
47603
|
popupObserver.dispose();
|
|
47463
47604
|
screenIdleObserver.dispose();
|
|
47605
|
+
if (ctx.toolSessionId) {
|
|
47606
|
+
this.tuiStates.delete(ctx.toolSessionId);
|
|
47607
|
+
}
|
|
47464
47608
|
if (ctx.toolSessionId && this.tuiOpts.onSurfaceUnregister) {
|
|
47465
47609
|
this.tuiOpts.onSurfaceUnregister(ctx.toolSessionId);
|
|
47466
47610
|
}
|
|
@@ -47743,7 +47887,7 @@ async function writeInboxMcpConfig(args) {
|
|
|
47743
47887
|
// src/shift/store.ts
|
|
47744
47888
|
var import_promises = __toESM(require("fs/promises"), 1);
|
|
47745
47889
|
var import_node_path19 = __toESM(require("path"), 1);
|
|
47746
|
-
var
|
|
47890
|
+
var import_node_crypto6 = require("crypto");
|
|
47747
47891
|
|
|
47748
47892
|
// src/shift/constants.ts
|
|
47749
47893
|
var MAX_RUNS_PER_SHIFT = 30;
|
|
@@ -47839,7 +47983,7 @@ function createShiftStore(deps) {
|
|
|
47839
47983
|
const nextRunAtMs = computeNextRunAtMs(input.schedule, now) ?? void 0;
|
|
47840
47984
|
const shift = {
|
|
47841
47985
|
...input,
|
|
47842
|
-
id: (0,
|
|
47986
|
+
id: (0, import_node_crypto6.randomUUID)(),
|
|
47843
47987
|
createdAtMs: now,
|
|
47844
47988
|
updatedAtMs: now,
|
|
47845
47989
|
state: { nextRunAtMs },
|
|
@@ -50810,7 +50954,7 @@ function lookupMime(filePathOrName) {
|
|
|
50810
50954
|
}
|
|
50811
50955
|
|
|
50812
50956
|
// src/attachment/sign-url.ts
|
|
50813
|
-
var
|
|
50957
|
+
var import_node_crypto7 = __toESM(require("crypto"), 1);
|
|
50814
50958
|
var HMAC_ALGO = "sha256";
|
|
50815
50959
|
function base64urlEncode(buf) {
|
|
50816
50960
|
const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
|
|
@@ -50827,7 +50971,7 @@ function decodeAbsPathFromUrl(encoded) {
|
|
|
50827
50971
|
}
|
|
50828
50972
|
function computeSig(secret, absPath, e) {
|
|
50829
50973
|
const msg = e === null ? absPath : `${absPath}|${e}`;
|
|
50830
|
-
return
|
|
50974
|
+
return import_node_crypto7.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
|
|
50831
50975
|
}
|
|
50832
50976
|
function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
|
|
50833
50977
|
const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
|
|
@@ -50862,7 +51006,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
50862
51006
|
if (provided.length !== expected.length) {
|
|
50863
51007
|
return { ok: false, code: "BAD_SIG" };
|
|
50864
51008
|
}
|
|
50865
|
-
if (!
|
|
51009
|
+
if (!import_node_crypto7.default.timingSafeEqual(provided, expected)) {
|
|
50866
51010
|
return { ok: false, code: "BAD_SIG" };
|
|
50867
51011
|
}
|
|
50868
51012
|
if (e !== null && now() / 1e3 > e) {
|
|
@@ -50874,7 +51018,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
50874
51018
|
// src/attachment/upload.ts
|
|
50875
51019
|
var import_node_fs25 = __toESM(require("fs"), 1);
|
|
50876
51020
|
var import_node_path25 = __toESM(require("path"), 1);
|
|
50877
|
-
var
|
|
51021
|
+
var import_node_crypto8 = __toESM(require("crypto"), 1);
|
|
50878
51022
|
var import_promises2 = require("stream/promises");
|
|
50879
51023
|
var UploadError = class extends Error {
|
|
50880
51024
|
constructor(code, message) {
|
|
@@ -50898,11 +51042,11 @@ async function writeUploadedAttachment(args) {
|
|
|
50898
51042
|
} catch (err) {
|
|
50899
51043
|
throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
|
|
50900
51044
|
}
|
|
50901
|
-
const hasher =
|
|
51045
|
+
const hasher = import_node_crypto8.default.createHash("sha256");
|
|
50902
51046
|
let actualSize = 0;
|
|
50903
51047
|
const tmpPath = import_node_path25.default.join(
|
|
50904
51048
|
attachmentsRoot,
|
|
50905
|
-
`.upload-${process.pid}-${Date.now()}-${
|
|
51049
|
+
`.upload-${process.pid}-${Date.now()}-${import_node_crypto8.default.randomBytes(4).toString("hex")}`
|
|
50906
51050
|
);
|
|
50907
51051
|
try {
|
|
50908
51052
|
await (0, import_promises2.pipeline)(
|
|
@@ -51778,7 +51922,7 @@ function runAttachmentGc(args) {
|
|
|
51778
51922
|
// src/attachment/group.ts
|
|
51779
51923
|
var import_node_fs28 = __toESM(require("fs"), 1);
|
|
51780
51924
|
var import_node_path29 = __toESM(require("path"), 1);
|
|
51781
|
-
var
|
|
51925
|
+
var import_node_crypto9 = __toESM(require("crypto"), 1);
|
|
51782
51926
|
init_protocol();
|
|
51783
51927
|
var GroupFileStore = class {
|
|
51784
51928
|
dataDir;
|
|
@@ -51867,7 +52011,7 @@ var GroupFileStore = class {
|
|
|
51867
52011
|
entries[idx] = next;
|
|
51868
52012
|
} else {
|
|
51869
52013
|
next = {
|
|
51870
|
-
id: `gf-${
|
|
52014
|
+
id: `gf-${import_node_crypto9.default.randomBytes(6).toString("base64url")}`,
|
|
51871
52015
|
relPath: input.relPath,
|
|
51872
52016
|
from: input.from,
|
|
51873
52017
|
label: input.label,
|
|
@@ -51986,7 +52130,7 @@ function readDaemonSourceFromEnv(env = process.env) {
|
|
|
51986
52130
|
// src/tunnel/tunnel-manager.ts
|
|
51987
52131
|
var import_node_fs33 = __toESM(require("fs"), 1);
|
|
51988
52132
|
var import_node_path34 = __toESM(require("path"), 1);
|
|
51989
|
-
var
|
|
52133
|
+
var import_node_crypto10 = __toESM(require("crypto"), 1);
|
|
51990
52134
|
var import_node_child_process9 = require("child_process");
|
|
51991
52135
|
|
|
51992
52136
|
// src/tunnel/tunnel-store.ts
|
|
@@ -52485,7 +52629,7 @@ var TunnelManager = class {
|
|
|
52485
52629
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
52486
52630
|
});
|
|
52487
52631
|
const tomlPath = import_node_path34.default.join(this.deps.dataDir, "frpc.toml");
|
|
52488
|
-
const proxyName = `clawd-${t.subdomain}-${localPort}-${
|
|
52632
|
+
const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto10.default.randomBytes(3).toString("hex")}`;
|
|
52489
52633
|
const toml = buildFrpcToml({
|
|
52490
52634
|
serverAddr: t.frpsHost,
|
|
52491
52635
|
serverPort: t.frpsPort,
|
|
@@ -53065,7 +53209,7 @@ function readIssuedPubkey(sshdDir, deviceId) {
|
|
|
53065
53209
|
// src/tunnel/device-key.ts
|
|
53066
53210
|
var import_node_os14 = __toESM(require("os"), 1);
|
|
53067
53211
|
var import_node_path39 = __toESM(require("path"), 1);
|
|
53068
|
-
var
|
|
53212
|
+
var import_node_crypto11 = __toESM(require("crypto"), 1);
|
|
53069
53213
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
53070
53214
|
function deriveStableDeviceKey(opts = {}) {
|
|
53071
53215
|
const hostname = opts.hostname ?? import_node_os14.default.hostname();
|
|
@@ -53075,13 +53219,13 @@ function deriveStableDeviceKey(opts = {}) {
|
|
|
53075
53219
|
const normalizedDataDir = opts.dataDir ? import_node_path39.default.resolve(opts.dataDir) : null;
|
|
53076
53220
|
const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
|
|
53077
53221
|
const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
|
|
53078
|
-
return
|
|
53222
|
+
return import_node_crypto11.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
53079
53223
|
}
|
|
53080
53224
|
|
|
53081
53225
|
// src/auth-store.ts
|
|
53082
53226
|
var import_node_fs38 = __toESM(require("fs"), 1);
|
|
53083
53227
|
var import_node_path40 = __toESM(require("path"), 1);
|
|
53084
|
-
var
|
|
53228
|
+
var import_node_crypto12 = __toESM(require("crypto"), 1);
|
|
53085
53229
|
var AUTH_FILE_NAME = "auth.json";
|
|
53086
53230
|
function authFilePath(dataDir) {
|
|
53087
53231
|
return import_node_path40.default.join(dataDir, AUTH_FILE_NAME);
|
|
@@ -53113,10 +53257,10 @@ function loadOrCreateAuthFile(opts) {
|
|
|
53113
53257
|
return next;
|
|
53114
53258
|
}
|
|
53115
53259
|
function defaultGenerateToken() {
|
|
53116
|
-
return
|
|
53260
|
+
return import_node_crypto12.default.randomBytes(32).toString("base64url");
|
|
53117
53261
|
}
|
|
53118
53262
|
function defaultGenerateOwnerPrincipalId() {
|
|
53119
|
-
return `owner-${
|
|
53263
|
+
return `owner-${import_node_crypto12.default.randomUUID()}`;
|
|
53120
53264
|
}
|
|
53121
53265
|
function readAuthFile(file) {
|
|
53122
53266
|
try {
|
|
@@ -53238,7 +53382,7 @@ var OwnerIdentityStore = class {
|
|
|
53238
53382
|
};
|
|
53239
53383
|
|
|
53240
53384
|
// src/feishu-auth/login-flow.ts
|
|
53241
|
-
var
|
|
53385
|
+
var import_node_crypto13 = __toESM(require("crypto"), 1);
|
|
53242
53386
|
var STATE_TTL_MS = 5 * 60 * 1e3;
|
|
53243
53387
|
var LoginFlow = class {
|
|
53244
53388
|
constructor(deps) {
|
|
@@ -53247,7 +53391,7 @@ var LoginFlow = class {
|
|
|
53247
53391
|
deps;
|
|
53248
53392
|
pendingStates = /* @__PURE__ */ new Map();
|
|
53249
53393
|
start() {
|
|
53250
|
-
const state =
|
|
53394
|
+
const state = import_node_crypto13.default.randomBytes(16).toString("base64url");
|
|
53251
53395
|
const now = (this.deps.now ?? Date.now)();
|
|
53252
53396
|
this.pendingStates.set(state, now);
|
|
53253
53397
|
this.gcExpired(now);
|
|
@@ -55249,7 +55393,7 @@ init_protocol();
|
|
|
55249
55393
|
// src/extension/bundle-zip.ts
|
|
55250
55394
|
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
55251
55395
|
var import_node_path47 = __toESM(require("path"), 1);
|
|
55252
|
-
var
|
|
55396
|
+
var import_node_crypto14 = __toESM(require("crypto"), 1);
|
|
55253
55397
|
var import_jszip2 = __toESM(require_lib3(), 1);
|
|
55254
55398
|
async function bundleExtensionDir(dir) {
|
|
55255
55399
|
const entries = await listFilesSorted(dir);
|
|
@@ -55264,7 +55408,7 @@ async function bundleExtensionDir(dir) {
|
|
|
55264
55408
|
compression: "DEFLATE",
|
|
55265
55409
|
compressionOptions: { level: 6 }
|
|
55266
55410
|
});
|
|
55267
|
-
const sha256 =
|
|
55411
|
+
const sha256 = import_node_crypto14.default.createHash("sha256").update(buffer).digest("hex");
|
|
55268
55412
|
return { buffer, sha256 };
|
|
55269
55413
|
}
|
|
55270
55414
|
var FIXED_DATE = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
|
|
@@ -55332,7 +55476,7 @@ function computePublishCheck(args) {
|
|
|
55332
55476
|
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
55333
55477
|
var import_node_path49 = __toESM(require("path"), 1);
|
|
55334
55478
|
var import_node_os19 = __toESM(require("os"), 1);
|
|
55335
|
-
var
|
|
55479
|
+
var import_node_crypto15 = __toESM(require("crypto"), 1);
|
|
55336
55480
|
var import_jszip3 = __toESM(require_lib3(), 1);
|
|
55337
55481
|
|
|
55338
55482
|
// src/extension/paths.ts
|
|
@@ -55361,7 +55505,7 @@ var InstallError = class extends Error {
|
|
|
55361
55505
|
};
|
|
55362
55506
|
async function installFromChannel(args, deps) {
|
|
55363
55507
|
const { channelRef, snapshotHash, bundleZip } = args;
|
|
55364
|
-
const computed =
|
|
55508
|
+
const computed = import_node_crypto15.default.createHash("sha256").update(bundleZip).digest("hex");
|
|
55365
55509
|
if (computed !== snapshotHash) {
|
|
55366
55510
|
throw new InstallError(
|
|
55367
55511
|
"HASH_MISMATCH",
|
|
@@ -55453,7 +55597,7 @@ async function installFromChannel(args, deps) {
|
|
|
55453
55597
|
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
55454
55598
|
var import_node_path50 = __toESM(require("path"), 1);
|
|
55455
55599
|
var import_node_os20 = __toESM(require("os"), 1);
|
|
55456
|
-
var
|
|
55600
|
+
var import_node_crypto16 = __toESM(require("crypto"), 1);
|
|
55457
55601
|
var import_jszip4 = __toESM(require_lib3(), 1);
|
|
55458
55602
|
var UpdateError = class extends Error {
|
|
55459
55603
|
constructor(code, message) {
|
|
@@ -55491,7 +55635,7 @@ async function updateFromChannel(args, deps) {
|
|
|
55491
55635
|
if (e instanceof UpdateError) throw e;
|
|
55492
55636
|
throw e;
|
|
55493
55637
|
}
|
|
55494
|
-
const computed =
|
|
55638
|
+
const computed = import_node_crypto16.default.createHash("sha256").update(bundleZip).digest("hex");
|
|
55495
55639
|
if (computed !== snapshotHash) {
|
|
55496
55640
|
throw new UpdateError(
|
|
55497
55641
|
"HASH_MISMATCH",
|
|
@@ -56196,7 +56340,7 @@ function listPidsOnPort(port) {
|
|
|
56196
56340
|
}
|
|
56197
56341
|
|
|
56198
56342
|
// src/app-builder/publish-registry.ts
|
|
56199
|
-
var
|
|
56343
|
+
var import_node_crypto17 = require("crypto");
|
|
56200
56344
|
var PublishJobRegistry = class {
|
|
56201
56345
|
jobs = /* @__PURE__ */ new Map();
|
|
56202
56346
|
has(name) {
|
|
@@ -56213,7 +56357,7 @@ var PublishJobRegistry = class {
|
|
|
56213
56357
|
if (this.jobs.has(args.name)) {
|
|
56214
56358
|
throw new Error(`already publishing: ${args.name}`);
|
|
56215
56359
|
}
|
|
56216
|
-
const jobId = args.jobId ?? `job-${(0,
|
|
56360
|
+
const jobId = args.jobId ?? `job-${(0, import_node_crypto17.randomUUID)()}`;
|
|
56217
56361
|
this.jobs.set(args.name, {
|
|
56218
56362
|
jobId,
|
|
56219
56363
|
name: args.name,
|
|
@@ -57173,7 +57317,7 @@ async function uninstall(deps) {
|
|
|
57173
57317
|
}
|
|
57174
57318
|
|
|
57175
57319
|
// src/handlers/index.ts
|
|
57176
|
-
var
|
|
57320
|
+
var import_node_crypto18 = require("crypto");
|
|
57177
57321
|
function buildMethodHandlers(deps) {
|
|
57178
57322
|
return {
|
|
57179
57323
|
...buildSessionHandlers({
|
|
@@ -57206,7 +57350,7 @@ function buildMethodHandlers(deps) {
|
|
|
57206
57350
|
const c = deps.contactStore.get(deviceId);
|
|
57207
57351
|
return c ? { deviceId: c.deviceId, remoteUrl: c.remoteUrl, connectToken: c.connectToken } : null;
|
|
57208
57352
|
},
|
|
57209
|
-
genId: () => (0,
|
|
57353
|
+
genId: () => (0, import_node_crypto18.randomUUID)(),
|
|
57210
57354
|
now: () => Date.now(),
|
|
57211
57355
|
forwardInboxPostToPeer,
|
|
57212
57356
|
logger: deps.logger
|
|
@@ -58119,6 +58263,13 @@ async function startDaemon(config) {
|
|
|
58119
58263
|
logClient
|
|
58120
58264
|
});
|
|
58121
58265
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
58266
|
+
const screenIdleProbeLogger = createFileOnlyLogger({
|
|
58267
|
+
file: import_node_path60.default.join(config.dataDir, "screen-idle-probe.log"),
|
|
58268
|
+
level: "debug"
|
|
58269
|
+
});
|
|
58270
|
+
logger.info("screen-idle probe logger enabled", {
|
|
58271
|
+
file: import_node_path60.default.join(config.dataDir, "screen-idle-probe.log")
|
|
58272
|
+
});
|
|
58122
58273
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
58123
58274
|
const pre = stateMgr.preflight();
|
|
58124
58275
|
if (pre.status === "active") {
|
|
@@ -58395,6 +58546,10 @@ async function startDaemon(config) {
|
|
|
58395
58546
|
// 新布局派生 (sessions/* + personas/<pid>/.clawd/sessions/owner/*)
|
|
58396
58547
|
storeFactory: sessionStoreFactory,
|
|
58397
58548
|
logger,
|
|
58549
|
+
// 取证 probe(可选,CLAWD_SCREEN_IDLE_PROBE=1 时启用):manager turn_end 判定链
|
|
58550
|
+
// 的所有决策点打到独立文件,跟 adapter 的 observeScreenIdle probe 共用同一份 file logger,
|
|
58551
|
+
// 便于 grep sessionId 时 tui 层 + manager 层交叉时序都在同一文件里
|
|
58552
|
+
...screenIdleProbeLogger ? { screenIdleProbeLogger } : {},
|
|
58398
58553
|
getAdapter,
|
|
58399
58554
|
historyReader: history,
|
|
58400
58555
|
dataDir: config.dataDir,
|
|
@@ -58512,10 +58667,10 @@ async function startDaemon(config) {
|
|
|
58512
58667
|
onSurfaceUnregister: (tsid) => manager.unregisterSurface(tsid),
|
|
58513
58668
|
// ReadyGate v2:ReadyDetector emit ready 时投递 reducer 'ready-detected' input
|
|
58514
58669
|
onReady: (tsid) => manager.dispatchReadyDetected(tsid),
|
|
58515
|
-
//
|
|
58516
|
-
|
|
58517
|
-
//
|
|
58518
|
-
|
|
58670
|
+
// 屏幕真稳定 5s 的一次性信号 → manager 查 pending turn_duration 并 flush 成 turn_end
|
|
58671
|
+
onScreenIdle: (tsid) => manager.notifyScreenIdle(tsid),
|
|
58672
|
+
// 取证 probe(默认无条件启用;见 createFileOnlyLogger)
|
|
58673
|
+
screenIdleProbeLogger
|
|
58519
58674
|
}) : new ClaudeAdapter({ logger, historyReader: new ClaudeHistoryReader() });
|
|
58520
58675
|
registerAdapter("claude", claudeAdapter);
|
|
58521
58676
|
registerAdapter("codex", new CodexAdapter({ logger, historyReader: new CodexHistoryReader() }));
|