@clawos-dev/clawd 0.2.198 → 0.2.199-beta.399.3cb813c
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 +91 -17
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -42956,6 +42956,18 @@ var SessionManager = class {
|
|
|
42956
42956
|
// 由 observer 监听 jsonl user 行后调 recordRealUserUuid 建立映射;rewind 系列 RPC 在
|
|
42957
42957
|
// 入参 / 出参做转译,保证 UI 看到的 uuid 始终是 events 流里的 synth uuid
|
|
42958
42958
|
realUuidBySynth = /* @__PURE__ */ new Map();
|
|
42959
|
+
// observer 收到 `turn_duration` 信号但屏幕还没稳定 5s → 挂进 pending,等 `notifyScreenIdle`
|
|
42960
|
+
// (屏幕 armed=true 触发点)flush 成真正的 turn_end 进 reducer。
|
|
42961
|
+
//
|
|
42962
|
+
// 语义澄清:`turn_duration` 是 CC 报的原始信号("本轮 API 调用完了"),**不代表 turn 真的结束**
|
|
42963
|
+
//(背景 agent 可能还在跑)。屏幕 5s 稳定 + 无 popup 才是"确认信号"。
|
|
42964
|
+
// 两者 AND 后 daemon 才产生真正的 `turn_end` 事件送给 reducer。
|
|
42965
|
+
//
|
|
42966
|
+
// pending 不设截止时间:屏幕稳定的时机由 CC UI 决定,可能几秒也可能几分钟。观察者以
|
|
42967
|
+
// "屏幕真的稳定"作为触发点,signal-driven 而非 timer-driven。
|
|
42968
|
+
//
|
|
42969
|
+
// 清理点:newSession / stop / session-delete / stopAll。
|
|
42970
|
+
pendingTurnDurationSignals = /* @__PURE__ */ new Set();
|
|
42959
42971
|
// SessionStore 按 scope 派生(root = <dataDir>/sessions/<scopeSubPath>/)。
|
|
42960
42972
|
// default scope 直接复用 deps.store;persona scope(owner / listener)第一次访问时按需创建并缓存。
|
|
42961
42973
|
// 取代旧的 storesByAgent —— agentId 概念由 SessionScope 取代,路径即身份,
|
|
@@ -43576,6 +43588,7 @@ var SessionManager = class {
|
|
|
43576
43588
|
this.runners.delete(args.sessionId);
|
|
43577
43589
|
this.realUuidBySynth.delete(args.sessionId);
|
|
43578
43590
|
this.lastUiSizeBySessionId.delete(args.sessionId);
|
|
43591
|
+
this.clearPendingTurnEnd(args.sessionId);
|
|
43579
43592
|
return { response: { sessionId: args.sessionId }, broadcast };
|
|
43580
43593
|
}
|
|
43581
43594
|
this.deleteOwned(args.sessionId);
|
|
@@ -43605,6 +43618,7 @@ var SessionManager = class {
|
|
|
43605
43618
|
async stop(args) {
|
|
43606
43619
|
const runner = this.runners.get(args.sessionId);
|
|
43607
43620
|
if (!runner) return { response: { ok: true }, broadcast: [] };
|
|
43621
|
+
this.clearPendingTurnEnd(args.sessionId);
|
|
43608
43622
|
const { broadcast } = this.withCollector(() => {
|
|
43609
43623
|
runner.input({ kind: "command", command: { kind: "stop" } });
|
|
43610
43624
|
});
|
|
@@ -43738,6 +43752,7 @@ var SessionManager = class {
|
|
|
43738
43752
|
newSession(args) {
|
|
43739
43753
|
const existingFile = this.getFile(args.sessionId);
|
|
43740
43754
|
const nextToolSessionId = this.deps.mode === "tui" && (existingFile.tool ?? "claude") === "claude" ? v4_default() : void 0;
|
|
43755
|
+
this.clearPendingTurnEnd(args.sessionId);
|
|
43741
43756
|
const runner = this.runners.get(args.sessionId);
|
|
43742
43757
|
if (runner) {
|
|
43743
43758
|
const { value, broadcast } = this.withCollector(() => {
|
|
@@ -43838,6 +43853,7 @@ var SessionManager = class {
|
|
|
43838
43853
|
for (const r of this.runners.values()) {
|
|
43839
43854
|
r.input({ kind: "command", command: { kind: "stop" } });
|
|
43840
43855
|
}
|
|
43856
|
+
this.pendingTurnDurationSignals.clear();
|
|
43841
43857
|
}
|
|
43842
43858
|
// 给 observer 用:拿已存在的 runner
|
|
43843
43859
|
getActive(sessionId) {
|
|
@@ -44054,6 +44070,7 @@ var SessionManager = class {
|
|
|
44054
44070
|
this.runners.delete(args.sessionId);
|
|
44055
44071
|
this.realUuidBySynth.delete(args.sessionId);
|
|
44056
44072
|
this.lastUiSizeBySessionId.delete(args.sessionId);
|
|
44073
|
+
this.clearPendingTurnEnd(args.sessionId);
|
|
44057
44074
|
return { response: { sessionId: args.sessionId }, broadcast };
|
|
44058
44075
|
}
|
|
44059
44076
|
this.storeFor(args.scope).delete(args.sessionId);
|
|
@@ -44305,35 +44322,87 @@ var SessionManager = class {
|
|
|
44305
44322
|
const toolSessionId = runnerState.file.toolSessionId;
|
|
44306
44323
|
const adapter = this.deps.getAdapter(runnerState.file.tool ?? "claude");
|
|
44307
44324
|
const gateOpen = !adapter.canAcceptTurnEnd || !toolSessionId ? true : adapter.canAcceptTurnEnd(toolSessionId);
|
|
44308
|
-
this.deps.screenIdleProbeLogger?.info("
|
|
44325
|
+
this.deps.screenIdleProbeLogger?.info("turn_duration signal received", {
|
|
44309
44326
|
sessionId,
|
|
44310
44327
|
toolSessionId,
|
|
44311
44328
|
batchKinds: outEvents.map((e) => e.kind),
|
|
44312
44329
|
gateOpen,
|
|
44313
44330
|
hasCanAcceptGate: !!adapter.canAcceptTurnEnd
|
|
44314
44331
|
});
|
|
44315
|
-
if (
|
|
44316
|
-
this.deps.
|
|
44317
|
-
|
|
44318
|
-
toolSessionId
|
|
44319
|
-
|
|
44320
|
-
});
|
|
44321
|
-
this.deps.screenIdleProbeLogger?.info("drop turn_end: adapter gate closed", {
|
|
44322
|
-
sessionId,
|
|
44323
|
-
toolSessionId,
|
|
44324
|
-
reason: "tui-screen-not-idle"
|
|
44325
|
-
});
|
|
44326
|
-
feedEvents = outEvents.filter((e) => e.kind !== "turn_end");
|
|
44332
|
+
if (gateOpen) {
|
|
44333
|
+
this.deps.screenIdleProbeLogger?.info(
|
|
44334
|
+
"turn_duration \u2192 turn_end confirmed (gate open) \u2192 fed to reducer",
|
|
44335
|
+
{ sessionId, toolSessionId }
|
|
44336
|
+
);
|
|
44327
44337
|
} else {
|
|
44328
|
-
|
|
44329
|
-
|
|
44330
|
-
|
|
44331
|
-
|
|
44338
|
+
feedEvents = outEvents.filter((e) => e.kind !== "turn_end");
|
|
44339
|
+
if (this.pendingTurnDurationSignals.has(sessionId)) {
|
|
44340
|
+
this.deps.screenIdleProbeLogger?.info(
|
|
44341
|
+
"turn_duration dedup: pending already set (repeated observer signal)",
|
|
44342
|
+
{ sessionId, toolSessionId }
|
|
44343
|
+
);
|
|
44344
|
+
} else {
|
|
44345
|
+
this.pendingTurnDurationSignals.add(sessionId);
|
|
44346
|
+
this.deps.screenIdleProbeLogger?.info(
|
|
44347
|
+
"turn_duration pending: gate closed \u2192 waiting for screen-idle signal",
|
|
44348
|
+
{ sessionId, toolSessionId }
|
|
44349
|
+
);
|
|
44350
|
+
}
|
|
44332
44351
|
}
|
|
44333
44352
|
}
|
|
44334
44353
|
if (feedEvents.length === 0) return;
|
|
44335
44354
|
runner.feedObserverEvents(feedEvents);
|
|
44336
44355
|
}
|
|
44356
|
+
/**
|
|
44357
|
+
* `ClaudeTuiAdapter.observeScreenIdle` fire triggered → armed=true 时调用(一次性触发点)。
|
|
44358
|
+
*
|
|
44359
|
+
* 语义:屏幕真的稳定了 5s。查有没有 pending 的 turn_duration 信号:
|
|
44360
|
+
* - 有 → 之前收到的 turn_duration 被屏幕稳定**确认**了,flush 成 turn_end 进 reducer
|
|
44361
|
+
* - 无 → 屏幕稳定但从没收到 turn_duration → noop(不生成 turn_end;补偿路径的错误做法)
|
|
44362
|
+
*
|
|
44363
|
+
* pending 集合是**必要前提**:turn_end 只能来自"observer 收 turn_duration + 屏幕后续稳定"
|
|
44364
|
+
* 双源确认,任何一个缺少都不能 emit。这跟 PR #962 拆掉的 dispatchTurnIdle "屏幕静止就补
|
|
44365
|
+
* turn_end" 语义不同。
|
|
44366
|
+
*
|
|
44367
|
+
* 仅 TUI 模式;SDK / codex 没有屏幕信号也就不会触发本方法。
|
|
44368
|
+
*/
|
|
44369
|
+
notifyScreenIdle(toolSessionId) {
|
|
44370
|
+
if (this.deps.mode !== "tui") return;
|
|
44371
|
+
const sid = this.sessionIdByToolSid(toolSessionId);
|
|
44372
|
+
if (!sid) {
|
|
44373
|
+
this.deps.screenIdleProbeLogger?.warn("notifyScreenIdle: no session for toolSessionId", {
|
|
44374
|
+
toolSessionId
|
|
44375
|
+
});
|
|
44376
|
+
return;
|
|
44377
|
+
}
|
|
44378
|
+
if (!this.pendingTurnDurationSignals.has(sid)) {
|
|
44379
|
+
this.deps.screenIdleProbeLogger?.info(
|
|
44380
|
+
"notifyScreenIdle: no pending turn_duration \u2192 noop",
|
|
44381
|
+
{ sessionId: sid, toolSessionId }
|
|
44382
|
+
);
|
|
44383
|
+
return;
|
|
44384
|
+
}
|
|
44385
|
+
const runner = this.runners.get(sid);
|
|
44386
|
+
if (!runner) {
|
|
44387
|
+
this.pendingTurnDurationSignals.delete(sid);
|
|
44388
|
+
this.deps.screenIdleProbeLogger?.warn(
|
|
44389
|
+
"notifyScreenIdle: pending but no runner \u2192 cleared without inject",
|
|
44390
|
+
{ sessionId: sid, toolSessionId }
|
|
44391
|
+
);
|
|
44392
|
+
return;
|
|
44393
|
+
}
|
|
44394
|
+
this.pendingTurnDurationSignals.delete(sid);
|
|
44395
|
+
this.deps.screenIdleProbeLogger?.info(
|
|
44396
|
+
"notifyScreenIdle: pending turn_duration + screen idle confirmed \u2192 inject turn_end",
|
|
44397
|
+
{ sessionId: sid, toolSessionId }
|
|
44398
|
+
);
|
|
44399
|
+
runner.input({ kind: "inject-events", events: [{ kind: "turn_end" }] });
|
|
44400
|
+
}
|
|
44401
|
+
clearPendingTurnEnd(sessionId) {
|
|
44402
|
+
if (this.pendingTurnDurationSignals.delete(sessionId)) {
|
|
44403
|
+
this.deps.screenIdleProbeLogger?.info("pending turn_duration cleared", { sessionId });
|
|
44404
|
+
}
|
|
44405
|
+
}
|
|
44337
44406
|
// AskUserQuestion 表单回写(plan: clawd-ask-user-question):UI 答完所有 question 后调用。
|
|
44338
44407
|
// - session 不存在 / 无 runner → noop 幂等返回 ok(first-decider-wins)
|
|
44339
44408
|
// - reducer noop(toolUseId 不存在或已答过)也保持幂等返回,handler 不抛
|
|
@@ -47404,6 +47473,9 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
47404
47473
|
const screenIdleObserver = observeScreenIdle(surface, {
|
|
47405
47474
|
idleMs: SCREEN_IDLE_MS,
|
|
47406
47475
|
onIdle: () => {
|
|
47476
|
+
if (!ctx.toolSessionId || !this.tuiOpts.onScreenIdle) return;
|
|
47477
|
+
this.tuiLogger?.debug("screen-idle \u2192 notifyScreenIdle", { toolSessionId: ctx.toolSessionId });
|
|
47478
|
+
this.tuiOpts.onScreenIdle(ctx.toolSessionId);
|
|
47407
47479
|
},
|
|
47408
47480
|
getPopupVisible: () => popupObserver.visibleKind !== null,
|
|
47409
47481
|
// 取证 probe(可选,装配处传独立 file-only logger,跟主 daemon.log 解耦)
|
|
@@ -57896,6 +57968,8 @@ async function startDaemon(config) {
|
|
|
57896
57968
|
onSurfaceUnregister: (tsid) => manager.unregisterSurface(tsid),
|
|
57897
57969
|
// ReadyGate v2:ReadyDetector emit ready 时投递 reducer 'ready-detected' input
|
|
57898
57970
|
onReady: (tsid) => manager.dispatchReadyDetected(tsid),
|
|
57971
|
+
// 屏幕真稳定 5s 的一次性信号 → manager 查 pending turn_duration 并 flush 成 turn_end
|
|
57972
|
+
onScreenIdle: (tsid) => manager.notifyScreenIdle(tsid),
|
|
57899
57973
|
// 取证 probe(默认无条件启用;见 createFileOnlyLogger)
|
|
57900
57974
|
screenIdleProbeLogger
|
|
57901
57975
|
}) : new ClaudeAdapter({ logger, historyReader: new ClaudeHistoryReader() });
|
package/package.json
CHANGED