@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.
- package/dist/cli.cjs +263 -5
- 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
|
|
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:
|
|
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