@clawos-dev/clawd 0.2.19 → 0.2.20-beta.28.51ba742

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 +169 -4
  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) {
@@ -13460,6 +13570,9 @@ var SessionRunner = class {
13460
13570
  case "write-stdin":
13461
13571
  this.proc?.stdin?.write(effect.payload);
13462
13572
  break;
13573
+ case "send-control-response-allow-with-input":
13574
+ this.proc?.stdin?.write(encodeAllowWithInputControlResponse(effect.requestId, effect.updatedInput));
13575
+ break;
13463
13576
  case "persist-file":
13464
13577
  try {
13465
13578
  this.hooks.store.write(effect.file);
@@ -13586,6 +13699,7 @@ function makeInitialState(file) {
13586
13699
  bufferStartSeq: 0,
13587
13700
  turnOpen: false,
13588
13701
  pendingPermissions: {},
13702
+ pendingQuestions: {},
13589
13703
  freshSpawn: false,
13590
13704
  procAlive: false,
13591
13705
  seenUuids: []
@@ -14058,6 +14172,23 @@ var SessionManager = class {
14058
14172
  if (runner.getState().procAlive) return;
14059
14173
  runner.feedObserverEvents(events);
14060
14174
  }
14175
+ // AskUserQuestion 表单回写(plan: clawd-ask-user-question):UI 答完所有 question 后调用。
14176
+ // - session 不存在 / 无 runner → noop 幂等返回 ok(first-decider-wins)
14177
+ // - reducer noop(toolUseId 不存在或已答过)也保持幂等返回,handler 不抛
14178
+ answerQuestion(args) {
14179
+ const runner = this.runners.get(args.sessionId);
14180
+ if (!runner) {
14181
+ return { response: { ok: true }, broadcast: [] };
14182
+ }
14183
+ const { broadcast } = this.withCollector(() => {
14184
+ runner.input({
14185
+ kind: "answer-question",
14186
+ toolUseId: args.toolUseId,
14187
+ answers: args.answers
14188
+ });
14189
+ });
14190
+ return { response: { ok: true }, broadcast };
14191
+ }
14061
14192
  // 权限决定:dispatcher 收到 permission:respond 时调用
14062
14193
  // 先查 state.pendingPermissions:不存在 → 抛 PERMISSION_REQUEST_STALE
14063
14194
  respondPermission(args) {
@@ -14850,6 +14981,19 @@ function splitLines(text) {
14850
14981
  return result;
14851
14982
  }
14852
14983
 
14984
+ // src/tools/tool-result-extra.ts
14985
+ function extractAskQuestionAnswers(raw) {
14986
+ if (!raw || typeof raw !== "object") return void 0;
14987
+ const r = raw;
14988
+ const answers = r.answers;
14989
+ if (!answers || typeof answers !== "object" || Array.isArray(answers)) return void 0;
14990
+ const out = {};
14991
+ for (const [k, v] of Object.entries(answers)) {
14992
+ if (typeof v === "string") out[k] = v;
14993
+ }
14994
+ return Object.keys(out).length > 0 ? out : void 0;
14995
+ }
14996
+
14853
14997
  // src/tools/claude-history.ts
14854
14998
  function cwdToHashDir(cwd) {
14855
14999
  return "-" + cwd.replace(/^\//, "").replace(/\//g, "-");
@@ -14974,6 +15118,8 @@ function messageFromJsonl(obj) {
14974
15118
  };
14975
15119
  if (toolResultExtra) msg.toolResultExtra = toolResultExtra;
14976
15120
  if (sourceToolAssistantUUID) msg.sourceToolAssistantUUID = sourceToolAssistantUUID;
15121
+ const answers = extractAskQuestionAnswers(o.toolUseResult);
15122
+ if (answers) msg.askQuestionAnswers = answers;
14977
15123
  return withCommonFields(msg, o);
14978
15124
  }
14979
15125
  if (texts.length > 0) {
@@ -15625,10 +15771,21 @@ function parseClaudeObjectInner(obj) {
15625
15771
  } else if (partType === "thinking" && typeof part.thinking === "string") {
15626
15772
  events.push({ kind: "thinking", text: part.thinking });
15627
15773
  } else if (partType === "tool_use") {
15774
+ const toolName = String(part.name ?? "");
15775
+ if (toolName === ASK_USER_QUESTION_TOOL_NAME) {
15776
+ const qInput = part.input ?? {};
15777
+ const rawQs = Array.isArray(qInput.questions) ? qInput.questions : [];
15778
+ events.push({
15779
+ kind: "ask_user_question",
15780
+ toolUseId: String(part.id ?? ""),
15781
+ questions: rawQs
15782
+ });
15783
+ continue;
15784
+ }
15628
15785
  events.push({
15629
15786
  kind: "tool_call",
15630
15787
  toolUseId: String(part.id ?? ""),
15631
- tool: String(part.name ?? ""),
15788
+ tool: toolName,
15632
15789
  input: part.input
15633
15790
  });
15634
15791
  }
@@ -15641,6 +15798,7 @@ function parseClaudeObjectInner(obj) {
15641
15798
  if (!Array.isArray(content)) return events;
15642
15799
  const toolResultExtra = extractToolResultExtra(obj.toolUseResult ?? obj.tool_use_result);
15643
15800
  const sourceToolAssistantUUID = typeof obj.sourceToolAssistantUUID === "string" ? obj.sourceToolAssistantUUID : void 0;
15801
+ const askQuestionAnswers = extractAskQuestionAnswers(obj.toolUseResult ?? obj.tool_use_result);
15644
15802
  for (const part of content) {
15645
15803
  const partType = part.type;
15646
15804
  if (partType === "text" && typeof part.text === "string") {
@@ -15664,6 +15822,7 @@ function parseClaudeObjectInner(obj) {
15664
15822
  }
15665
15823
  if (toolResultExtra) ev.toolResultExtra = toolResultExtra;
15666
15824
  if (sourceToolAssistantUUID) ev.sourceToolAssistantUUID = sourceToolAssistantUUID;
15825
+ if (askQuestionAnswers) ev.askQuestionAnswers = askQuestionAnswers;
15667
15826
  events.push(ev);
15668
15827
  }
15669
15828
  }
@@ -17585,6 +17744,11 @@ function buildSessionHandlers(deps) {
17585
17744
  const { response, broadcast } = manager.pin(args);
17586
17745
  return { response: { type: "session:info", ...response }, broadcast };
17587
17746
  };
17747
+ const answerQuestion = async (frame) => {
17748
+ const args = AnswerQuestionArgs.parse(frame);
17749
+ const { response, broadcast } = manager.answerQuestion(args);
17750
+ return { response: { type: "session:answerQuestion", ...response }, broadcast };
17751
+ };
17588
17752
  return {
17589
17753
  "session:create": create,
17590
17754
  "session:list": list,
@@ -17604,7 +17768,8 @@ function buildSessionHandlers(deps) {
17604
17768
  "session:events": events,
17605
17769
  "session:subscribe": subscribe,
17606
17770
  "session:unsubscribe": unsubscribe,
17607
- "session:pin": pin
17771
+ "session:pin": pin,
17772
+ "session:answerQuestion": answerQuestion
17608
17773
  };
17609
17774
  }
17610
17775
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.19",
3
+ "version": "0.2.20-beta.28.51ba742",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",