@clawos-dev/clawd 0.2.19-beta.26.3dd6b2e → 0.2.20-beta.27.e8cd6cc

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 +263 -5
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -8145,6 +8145,9 @@ var METHOD_NAMES = [
8145
8145
  "session:unsubscribe",
8146
8146
  "session:pin",
8147
8147
  "permission:respond",
8148
+ // AskUserQuestion 表单回写:UI 答完所有 question 后调用,daemon 把答案合并进 updated_input
8149
+ // 写一条 control_response 到 CC stdin(详见 events.ts session:question JSDoc)
8150
+ "session:answerQuestion",
8148
8151
  "history:projects",
8149
8152
  "history:list",
8150
8153
  "history:read",
@@ -8176,6 +8179,7 @@ var HISTORY_USER_META_VALUES = [
8176
8179
  "attachment-skills",
8177
8180
  "attachment-deferred-tools"
8178
8181
  ];
8182
+ var ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
8179
8183
 
8180
8184
  // ../protocol/src/errors.ts
8181
8185
  var ERROR_CODES = {
@@ -12378,6 +12382,16 @@ var MemoryEntrySchema = external_exports.object({
12378
12382
  content: external_exports.string(),
12379
12383
  mtimeMs: external_exports.number().optional()
12380
12384
  });
12385
+ var AskQuestionOptionSchema = external_exports.object({
12386
+ label: external_exports.string().min(1),
12387
+ description: external_exports.string().optional()
12388
+ });
12389
+ var AskQuestionItemSchema = external_exports.object({
12390
+ question: external_exports.string().min(1),
12391
+ // multiSelect 必填(boolean 不给默认值):daemon emit 时必须显式传,避免 UI 端漂移
12392
+ multiSelect: external_exports.boolean(),
12393
+ options: external_exports.array(AskQuestionOptionSchema).min(1)
12394
+ });
12381
12395
  var ParsedEventSchema = external_exports.discriminatedUnion("kind", [
12382
12396
  // session_init 仅携带 session 标识;resolvedModel 等元信息走 meta_update
12383
12397
  external_exports.object({
@@ -12430,7 +12444,13 @@ var ParsedEventSchema = external_exports.discriminatedUnion("kind", [
12430
12444
  // 老 CC 走 <task-notification> user 消息携带统计,新 CC 直接挂在这里——兼容期两者可能并存
12431
12445
  toolResultExtra: ToolResultExtraSchema.optional(),
12432
12446
  // 精确指向发起 tool 的 assistant 消息顶层 uuid;toolUseId 已够 UI 聚合,这个仅作调试辅助
12433
- sourceToolAssistantUUID: external_exports.string().optional()
12447
+ sourceToolAssistantUUID: external_exports.string().optional(),
12448
+ // AskUserQuestion 实时回放(plan: clawd-ask-user-question 任务 B 修复):
12449
+ // CC 用户答完后把 answers 写在 user 帧顶层 toolUseResult.answers,daemon parser
12450
+ // 抽到这里。UI eventsToLines 用 toolUseId 配对到 question-card line,把卡片转为
12451
+ // 只读态显示用户的答案。仅 AskUserQuestion 配对的 tool_result 行携带;普通工具不带。
12452
+ // 等价于 HistoryMessage.askQuestionAnswers(历史回放路径),两端字段名对齐。
12453
+ askQuestionAnswers: external_exports.record(external_exports.string(), external_exports.string()).optional()
12434
12454
  }),
12435
12455
  // CC 命中项目记忆(`type:"attachment"`,`attachment.type:"memories"`)的独立事件;
12436
12456
  // 其它 attachment 子类型(skills / hook_context / deferred_tools / hook_success / unknown)
@@ -12451,6 +12471,20 @@ var ParsedEventSchema = external_exports.discriminatedUnion("kind", [
12451
12471
  // 选项 ii)。老 daemon / legacy CC 不发该字段时保持向后兼容(optional)。
12452
12472
  toolUseId: external_exports.string().optional()
12453
12473
  }),
12474
+ // AskUserQuestion 工具调用的内联锚点(plan: clawd-ask-user-question 问题 1 修复):
12475
+ // daemon parser 把 assistant tool_use AskUserQuestion 翻译成本 kind(不是 permission_request、
12476
+ // 也不是 tool_call),让 UI 在事件流自然位置渲染问答卡(紧跟 assistant 消息 / 在后续消息之前),
12477
+ // 而不是被强行挂在末尾。permission_request 帧仍正常下发驱动 pendingQuestions 状态机,
12478
+ // 但只走 wire 帧不入 ring buffer——状态由 daemon pendingQuestions 持有,
12479
+ // 渲染锚点由本 kind 的 ParsedEvent 提供。
12480
+ // 历史回放:history-to-lines.ts 在 jsonl tool_use AskUserQuestion 处插入对应 'question-card' line,
12481
+ // 配对的 tool_result 携带 toolUseResult.answers,已答完的展示只读态。
12482
+ external_exports.object({
12483
+ ...ParsedEventBase,
12484
+ kind: external_exports.literal("ask_user_question"),
12485
+ toolUseId: external_exports.string(),
12486
+ questions: external_exports.array(AskQuestionItemSchema)
12487
+ }),
12454
12488
  external_exports.object({
12455
12489
  ...ParsedEventBase,
12456
12490
  kind: external_exports.literal("turn_end")
@@ -12658,6 +12692,20 @@ var RecentDirEntrySchema = external_exports.object({
12658
12692
  var HistoryRecentDirsResponseSchema = external_exports.object({
12659
12693
  dirs: external_exports.array(RecentDirEntrySchema)
12660
12694
  });
12695
+ var SessionQuestionFrameSchema = external_exports.object({
12696
+ type: external_exports.literal("session:question"),
12697
+ sessionId: external_exports.string().min(1),
12698
+ toolUseId: external_exports.string().min(1),
12699
+ requestId: external_exports.string().min(1),
12700
+ // UI 单卡承载多 question;空数组语义无效,schema 必拒
12701
+ questions: external_exports.array(AskQuestionItemSchema).min(1)
12702
+ });
12703
+ var AnswerQuestionArgs = external_exports.object({
12704
+ sessionId: external_exports.string().min(1),
12705
+ toolUseId: external_exports.string().min(1),
12706
+ answers: external_exports.record(external_exports.string(), external_exports.string())
12707
+ });
12708
+ var AnswerQuestionResponseSchema = external_exports.object({ ok: external_exports.literal(true) });
12661
12709
  var AuthRequestFrameSchema = external_exports.object({
12662
12710
  type: external_exports.literal("auth"),
12663
12711
  token: external_exports.string().min(1),
@@ -12847,6 +12895,7 @@ function cloneState(s) {
12847
12895
  bufferStartSeq: s.bufferStartSeq,
12848
12896
  turnOpen: s.turnOpen,
12849
12897
  pendingPermissions: s.pendingPermissions,
12898
+ pendingQuestions: s.pendingQuestions,
12850
12899
  freshSpawn: s.freshSpawn,
12851
12900
  procAlive: s.procAlive,
12852
12901
  seenUuids: s.seenUuids
@@ -12947,10 +12996,29 @@ function applyParsedEvent(state, event, deps) {
12947
12996
  const tool = event.tool;
12948
12997
  const input = event.input;
12949
12998
  const requestId = event.requestId;
12950
- const hit = matchesAnyRule(next.file.permissionRules, tool, input);
12999
+ const toolUseId = event.toolUseId;
13000
+ if (tool === ASK_USER_QUESTION_TOOL_NAME) {
13001
+ const questions = input && typeof input === "object" && "questions" in input ? input.questions : void 0;
13002
+ const frame = {
13003
+ type: "session:question",
13004
+ sessionId,
13005
+ toolUseId: toolUseId ?? "",
13006
+ requestId,
13007
+ questions: Array.isArray(questions) ? questions : []
13008
+ };
13009
+ effects.push({ kind: "emit-frame", frame });
13010
+ if (toolUseId) {
13011
+ next.pendingQuestions = {
13012
+ ...next.pendingQuestions,
13013
+ [toolUseId]: { requestId, input }
13014
+ };
13015
+ }
13016
+ return { state: next, effects };
13017
+ }
12951
13018
  const pushed2 = pushEventToBuffer(next, event, deps);
12952
13019
  next = pushed2.state;
12953
13020
  effects.push(...pushed2.effects);
13021
+ const hit = matchesAnyRule(next.file.permissionRules, tool, input);
12954
13022
  if (hit) {
12955
13023
  effects.push({
12956
13024
  kind: "write-stdin",
@@ -13071,6 +13139,7 @@ function applyCommand(state, command, deps) {
13071
13139
  }
13072
13140
  case "stop": {
13073
13141
  next.pendingPermissions = {};
13142
+ next.pendingQuestions = {};
13074
13143
  if (next.procAlive) {
13075
13144
  next.status = "stopping";
13076
13145
  effects.push({ kind: "kill", signal: "SIGKILL" });
@@ -13097,6 +13166,7 @@ function applyCommand(state, command, deps) {
13097
13166
  }
13098
13167
  case "delete": {
13099
13168
  next.pendingPermissions = {};
13169
+ next.pendingQuestions = {};
13100
13170
  if (next.procAlive) {
13101
13171
  effects.push({ kind: "kill", signal: "SIGKILL" });
13102
13172
  }
@@ -13206,6 +13276,7 @@ function reduceSession(state, input, deps) {
13206
13276
  const next = cloneState(state);
13207
13277
  next.procAlive = false;
13208
13278
  next.status = "stopped";
13279
+ next.pendingQuestions = {};
13209
13280
  return {
13210
13281
  state: next,
13211
13282
  effects: [
@@ -13277,6 +13348,31 @@ function reduceSession(state, input, deps) {
13277
13348
  }
13278
13349
  return { state: next, effects };
13279
13350
  }
13351
+ case "answer-question": {
13352
+ const pending = state.pendingQuestions?.[input.toolUseId];
13353
+ if (!pending) {
13354
+ return { state, effects: [] };
13355
+ }
13356
+ const next = cloneState(state);
13357
+ const nextPending = { ...next.pendingQuestions };
13358
+ delete nextPending[input.toolUseId];
13359
+ next.pendingQuestions = nextPending;
13360
+ const baseInput = pending.input && typeof pending.input === "object" && !Array.isArray(pending.input) ? pending.input : {};
13361
+ const updatedInput = {
13362
+ ...baseInput,
13363
+ answers: input.answers
13364
+ };
13365
+ return {
13366
+ state: next,
13367
+ effects: [
13368
+ {
13369
+ kind: "send-control-response-allow-with-input",
13370
+ requestId: pending.requestId,
13371
+ updatedInput
13372
+ }
13373
+ ]
13374
+ };
13375
+ }
13280
13376
  case "tick": {
13281
13377
  const staleMs = 10 * 60 * 1e3;
13282
13378
  const now = input.nowMs;
@@ -13301,6 +13397,20 @@ function reduceSession(state, input, deps) {
13301
13397
 
13302
13398
  // src/session/runner.ts
13303
13399
  var DEFAULT_CONTROL_REQUEST_TIMEOUT_MS = 1e4;
13400
+ function encodeAllowWithInputControlResponse(requestId, updatedInput) {
13401
+ const payload = {
13402
+ type: "control_response",
13403
+ response: {
13404
+ subtype: "success",
13405
+ request_id: requestId,
13406
+ response: {
13407
+ behavior: "allow",
13408
+ updatedInput
13409
+ }
13410
+ }
13411
+ };
13412
+ return JSON.stringify(payload) + "\n";
13413
+ }
13304
13414
  var DEFAULT_WAIT_STOP_TIMEOUT_MS = 3e3;
13305
13415
  var SessionRunner = class {
13306
13416
  constructor(initial, hooks) {
@@ -13336,6 +13446,11 @@ var SessionRunner = class {
13336
13446
  this.stopWaiters = [];
13337
13447
  for (const w of waiters) w();
13338
13448
  }
13449
+ try {
13450
+ this.hooks.onAfterInput?.(this.state, inputMsg);
13451
+ } catch (err) {
13452
+ this.hooks.logger?.warn("onAfterInput hook threw", { err: err.message });
13453
+ }
13339
13454
  }
13340
13455
  // 等子进程退出(procAlive=false)。manager.stop 在发完 SIGTERM 后调它确保
13341
13456
  // 真停下来再 ack 给前端,从而避免"删 worktree 时 CC 还活着持有文件锁"的 race。
@@ -13460,6 +13575,9 @@ var SessionRunner = class {
13460
13575
  case "write-stdin":
13461
13576
  this.proc?.stdin?.write(effect.payload);
13462
13577
  break;
13578
+ case "send-control-response-allow-with-input":
13579
+ this.proc?.stdin?.write(encodeAllowWithInputControlResponse(effect.requestId, effect.updatedInput));
13580
+ break;
13463
13581
  case "persist-file":
13464
13582
  try {
13465
13583
  this.hooks.store.write(effect.file);
@@ -13525,6 +13643,63 @@ var SessionRunner = class {
13525
13643
  }
13526
13644
  };
13527
13645
 
13646
+ // src/session/phase.ts
13647
+ var PHASE_DESCRIPTIONS = {
13648
+ idle: "\u8FD8\u6CA1\u542F\u52A8",
13649
+ spawning: "\u8FDB\u7A0B\u542F\u52A8\u4E2D",
13650
+ "turn-idle": "\u8FDB\u7A0B\u6D3B\u7740\uFF0C\u7B49\u8F93\u5165",
13651
+ "turn-running": "\u6B63\u5728 run \u4E2D",
13652
+ stopping: "\u6B63\u5728\u6740\u8FDB\u7A0B",
13653
+ exited: "\u8FDB\u7A0B\u5DF2\u9000\u51FA",
13654
+ crashed: "\u542F\u52A8\u5931\u8D25 / \u5F02\u5E38\u9000\u51FA",
13655
+ observing: "\u5916\u90E8 CC \u5728\u8DD1\uFF0Cdaemon \u65C1\u89C2"
13656
+ };
13657
+ function derivePhase(state) {
13658
+ switch (state.status) {
13659
+ case "idle":
13660
+ return "idle";
13661
+ case "spawning":
13662
+ return "spawning";
13663
+ case "running":
13664
+ return state.turnOpen ? "turn-running" : "turn-idle";
13665
+ case "stopping":
13666
+ return "stopping";
13667
+ case "stopped":
13668
+ return "exited";
13669
+ case "error":
13670
+ return "crashed";
13671
+ }
13672
+ }
13673
+ function deriveReason(input) {
13674
+ switch (input.kind) {
13675
+ case "command":
13676
+ return `cmd:${input.command.kind}`;
13677
+ case "proc-exit":
13678
+ return "proc-exit";
13679
+ case "proc-error":
13680
+ return "proc-error";
13681
+ case "inject-events":
13682
+ return "observer-events";
13683
+ case "stdout-line":
13684
+ return "stdout";
13685
+ case "permission-decision":
13686
+ return "permission";
13687
+ case "answer-question":
13688
+ return "answer-question";
13689
+ case "tick":
13690
+ return "tick";
13691
+ }
13692
+ }
13693
+ function buildPhaseLogFields(args) {
13694
+ return {
13695
+ sessionId: args.sessionId,
13696
+ phase: args.phase,
13697
+ prev: args.prev,
13698
+ desc: PHASE_DESCRIPTIONS[args.phase],
13699
+ ...args.reason ? { reason: args.reason } : {}
13700
+ };
13701
+ }
13702
+
13528
13703
  // src/session/manager.ts
13529
13704
  function compressFrameForWire(frame) {
13530
13705
  if (frame.type !== "session:status") return frame;
@@ -13586,6 +13761,7 @@ function makeInitialState(file) {
13586
13761
  bufferStartSeq: 0,
13587
13762
  turnOpen: false,
13588
13763
  pendingPermissions: {},
13764
+ pendingQuestions: {},
13589
13765
  freshSpawn: false,
13590
13766
  procAlive: false,
13591
13767
  seenUuids: []
@@ -13609,6 +13785,9 @@ var SessionManager = class {
13609
13785
  // 由 observer 监听 jsonl user 行后调 recordRealUserUuid 建立映射;rewind 系列 RPC 在
13610
13786
  // 入参 / 出参做转译,保证 UI 看到的 uuid 始终是 events 流里的 synth uuid
13611
13787
  realUuidBySynth = /* @__PURE__ */ new Map();
13788
+ // sessionId → 最近一次打过 log 的 phase;diff 命中才再打,避免每次 input 刷一行。
13789
+ // observer 路径的 'observing' 也写到这张表,跟 reducer 派生 phase 共享 last 值
13790
+ lastPhase = /* @__PURE__ */ new Map();
13612
13791
  async getCapabilities(tool) {
13613
13792
  const cached = this.capabilitiesCache.get(tool);
13614
13793
  if (cached) return cached;
@@ -13630,10 +13809,35 @@ var SessionManager = class {
13630
13809
  now: this.deps.now,
13631
13810
  bufferCap: this.deps.bufferCap,
13632
13811
  // adapter 自己持有模型表 + 兜底逻辑(contains / opus-1M / [1m] 等),manager 不再 cache 转发
13633
- resolveContextWindow: (tool, modelId) => this.deps.getAdapter(tool).resolveContextWindow(modelId)
13812
+ resolveContextWindow: (tool, modelId) => this.deps.getAdapter(tool).resolveContextWindow(modelId),
13813
+ // phase 派生:每次 input 应用完后比对,变化才打一行 log。observer 路径的
13814
+ // 'observing' 由 onObserverPhase 单独写入 lastPhase 表,此处不会回退
13815
+ onAfterInput: (state, input) => this.recordPhaseFromState(file.sessionId, state, deriveReason(input))
13634
13816
  });
13635
13817
  return runner;
13636
13818
  }
13819
+ // 给 reducer state 派生当前 phase,跟上次 diff,变化时打一行带中文说明的 log
13820
+ recordPhaseFromState(sessionId, state, reason) {
13821
+ const next = derivePhase(state);
13822
+ const prev = this.lastPhase.get(sessionId) ?? null;
13823
+ if (prev === next) return;
13824
+ this.lastPhase.set(sessionId, next);
13825
+ this.deps.logger?.info(
13826
+ "session-phase",
13827
+ buildPhaseLogFields({ sessionId, phase: next, prev, reason })
13828
+ );
13829
+ }
13830
+ // observer 路径:外部 CC 在跑、daemon 只 tail JSONL;走单独的 'observing' phase
13831
+ recordObserverPhase(sessionId, status) {
13832
+ const next = status === "observing" ? "observing" : "exited";
13833
+ const prev = this.lastPhase.get(sessionId) ?? null;
13834
+ if (prev === next) return;
13835
+ this.lastPhase.set(sessionId, next);
13836
+ this.deps.logger?.info(
13837
+ "session-phase",
13838
+ buildPhaseLogFields({ sessionId, phase: next, prev, reason: "observer" })
13839
+ );
13840
+ }
13637
13841
  // 统一 runner 出口:先压 wire,然后按当前上下文路由
13638
13842
  // 同步命令路径(currentCollector 非空) → push 进 collector,命令方法返回给 dispatcher
13639
13843
  // 异步路径(stdout/exit/observer 主动调用) → 走 deps.broadcastFrame
@@ -13786,9 +13990,11 @@ var SessionManager = class {
13786
13990
  });
13787
13991
  this.runners.delete(args.sessionId);
13788
13992
  this.realUuidBySynth.delete(args.sessionId);
13993
+ this.lastPhase.delete(args.sessionId);
13789
13994
  return { response: { sessionId: args.sessionId }, broadcast };
13790
13995
  }
13791
13996
  this.deps.store.delete(args.sessionId);
13997
+ this.lastPhase.delete(args.sessionId);
13792
13998
  return {
13793
13999
  response: { sessionId: args.sessionId },
13794
14000
  broadcast: [
@@ -14058,6 +14264,23 @@ var SessionManager = class {
14058
14264
  if (runner.getState().procAlive) return;
14059
14265
  runner.feedObserverEvents(events);
14060
14266
  }
14267
+ // AskUserQuestion 表单回写(plan: clawd-ask-user-question):UI 答完所有 question 后调用。
14268
+ // - session 不存在 / 无 runner → noop 幂等返回 ok(first-decider-wins)
14269
+ // - reducer noop(toolUseId 不存在或已答过)也保持幂等返回,handler 不抛
14270
+ answerQuestion(args) {
14271
+ const runner = this.runners.get(args.sessionId);
14272
+ if (!runner) {
14273
+ return { response: { ok: true }, broadcast: [] };
14274
+ }
14275
+ const { broadcast } = this.withCollector(() => {
14276
+ runner.input({
14277
+ kind: "answer-question",
14278
+ toolUseId: args.toolUseId,
14279
+ answers: args.answers
14280
+ });
14281
+ });
14282
+ return { response: { ok: true }, broadcast };
14283
+ }
14061
14284
  // 权限决定:dispatcher 收到 permission:respond 时调用
14062
14285
  // 先查 state.pendingPermissions:不存在 → 抛 PERMISSION_REQUEST_STALE
14063
14286
  respondPermission(args) {
@@ -14850,6 +15073,19 @@ function splitLines(text) {
14850
15073
  return result;
14851
15074
  }
14852
15075
 
15076
+ // src/tools/tool-result-extra.ts
15077
+ function extractAskQuestionAnswers(raw) {
15078
+ if (!raw || typeof raw !== "object") return void 0;
15079
+ const r = raw;
15080
+ const answers = r.answers;
15081
+ if (!answers || typeof answers !== "object" || Array.isArray(answers)) return void 0;
15082
+ const out = {};
15083
+ for (const [k, v] of Object.entries(answers)) {
15084
+ if (typeof v === "string") out[k] = v;
15085
+ }
15086
+ return Object.keys(out).length > 0 ? out : void 0;
15087
+ }
15088
+
14853
15089
  // src/tools/claude-history.ts
14854
15090
  function cwdToHashDir(cwd) {
14855
15091
  return "-" + cwd.replace(/^\//, "").replace(/\//g, "-");
@@ -14974,6 +15210,8 @@ function messageFromJsonl(obj) {
14974
15210
  };
14975
15211
  if (toolResultExtra) msg.toolResultExtra = toolResultExtra;
14976
15212
  if (sourceToolAssistantUUID) msg.sourceToolAssistantUUID = sourceToolAssistantUUID;
15213
+ const answers = extractAskQuestionAnswers(o.toolUseResult);
15214
+ if (answers) msg.askQuestionAnswers = answers;
14977
15215
  return withCommonFields(msg, o);
14978
15216
  }
14979
15217
  if (texts.length > 0) {
@@ -15625,10 +15863,21 @@ function parseClaudeObjectInner(obj) {
15625
15863
  } else if (partType === "thinking" && typeof part.thinking === "string") {
15626
15864
  events.push({ kind: "thinking", text: part.thinking });
15627
15865
  } else if (partType === "tool_use") {
15866
+ const toolName = String(part.name ?? "");
15867
+ if (toolName === ASK_USER_QUESTION_TOOL_NAME) {
15868
+ const qInput = part.input ?? {};
15869
+ const rawQs = Array.isArray(qInput.questions) ? qInput.questions : [];
15870
+ events.push({
15871
+ kind: "ask_user_question",
15872
+ toolUseId: String(part.id ?? ""),
15873
+ questions: rawQs
15874
+ });
15875
+ continue;
15876
+ }
15628
15877
  events.push({
15629
15878
  kind: "tool_call",
15630
15879
  toolUseId: String(part.id ?? ""),
15631
- tool: String(part.name ?? ""),
15880
+ tool: toolName,
15632
15881
  input: part.input
15633
15882
  });
15634
15883
  }
@@ -15641,6 +15890,7 @@ function parseClaudeObjectInner(obj) {
15641
15890
  if (!Array.isArray(content)) return events;
15642
15891
  const toolResultExtra = extractToolResultExtra(obj.toolUseResult ?? obj.tool_use_result);
15643
15892
  const sourceToolAssistantUUID = typeof obj.sourceToolAssistantUUID === "string" ? obj.sourceToolAssistantUUID : void 0;
15893
+ const askQuestionAnswers = extractAskQuestionAnswers(obj.toolUseResult ?? obj.tool_use_result);
15644
15894
  for (const part of content) {
15645
15895
  const partType = part.type;
15646
15896
  if (partType === "text" && typeof part.text === "string") {
@@ -15664,6 +15914,7 @@ function parseClaudeObjectInner(obj) {
15664
15914
  }
15665
15915
  if (toolResultExtra) ev.toolResultExtra = toolResultExtra;
15666
15916
  if (sourceToolAssistantUUID) ev.sourceToolAssistantUUID = sourceToolAssistantUUID;
15917
+ if (askQuestionAnswers) ev.askQuestionAnswers = askQuestionAnswers;
15667
15918
  events.push(ev);
15668
15919
  }
15669
15920
  }
@@ -17585,6 +17836,11 @@ function buildSessionHandlers(deps) {
17585
17836
  const { response, broadcast } = manager.pin(args);
17586
17837
  return { response: { type: "session:info", ...response }, broadcast };
17587
17838
  };
17839
+ const answerQuestion = async (frame) => {
17840
+ const args = AnswerQuestionArgs.parse(frame);
17841
+ const { response, broadcast } = manager.answerQuestion(args);
17842
+ return { response: { type: "session:answerQuestion", ...response }, broadcast };
17843
+ };
17588
17844
  return {
17589
17845
  "session:create": create,
17590
17846
  "session:list": list,
@@ -17604,7 +17860,8 @@ function buildSessionHandlers(deps) {
17604
17860
  "session:events": events,
17605
17861
  "session:subscribe": subscribe,
17606
17862
  "session:unsubscribe": unsubscribe,
17607
- "session:pin": pin
17863
+ "session:pin": pin,
17864
+ "session:answerQuestion": answerQuestion
17608
17865
  };
17609
17866
  }
17610
17867
 
@@ -18142,6 +18399,7 @@ async function startDaemon(config) {
18142
18399
  hasRunner: !!r,
18143
18400
  runnerProcAlive: r ? r.getState().procAlive : null
18144
18401
  });
18402
+ manager.recordObserverPhase(sessionId, status);
18145
18403
  transport?.broadcastToSession(sessionId, { type: "session:status", sessionId, status });
18146
18404
  },
18147
18405
  // jsonl user 行的 real uuid 映射到 buffer 里 synth user_text,rewind 系列 RPC 在
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.19-beta.26.3dd6b2e",
3
+ "version": "0.2.20-beta.27.e8cd6cc",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",