lark-remote 0.5.20 → 0.5.21
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/CHANGELOG.md +10 -0
- package/dist/bridge/inbound-media.js +71 -45
- package/dist/bridge/index.d.ts +9 -0
- package/dist/bridge/index.js +230 -203
- package/dist/card/approval-render.js +32 -33
- package/dist/card/card-budget.js +123 -98
- package/dist/card/run-renderer.js +82 -68
- package/dist/card/run-state.d.ts +0 -14
- package/dist/card/run-state.js +335 -207
- package/dist/card/tool-render.js +20 -16
- package/dist/card/usage-format.d.ts +12 -9
- package/dist/card/usage-format.js +111 -97
- package/dist/common/text-width.js +36 -24
- package/dist/common/tool-summary.js +132 -90
- package/dist/index.js +226 -190
- package/dist/message-handlers.js +253 -236
- package/dist/router/commands/ls.d.ts +3 -0
- package/dist/router/commands/ls.js +256 -175
- package/dist/router/commands/order.js +184 -159
- package/dist/router/commands/resume.js +189 -133
- package/dist/router/commands/ws.js +219 -174
- package/dist/router/index.js +192 -201
- package/dist/runner/bash/runner.js +2 -4
- package/dist/runner/codex/app-server/translator.d.ts +2 -0
- package/dist/runner/codex/app-server/translator.js +287 -221
- package/dist/runner/common/acp/base-translator.js +19 -17
- package/dist/runner/common/jsonl-stream.js +154 -187
- package/dist/runner/common/jsonrpc/client.d.ts +29 -0
- package/dist/runner/common/jsonrpc/client.js +43 -12
- package/dist/runner/common/spawning-runner.d.ts +5 -0
- package/dist/runner/common/spawning-runner.js +8 -3
- package/dist/runner/common/turn-started.d.ts +8 -0
- package/dist/runner/common/turn-started.js +11 -0
- package/dist/runner/dsh/approval.js +84 -51
- package/dist/runner/dsh/client.d.ts +7 -9
- package/dist/runner/dsh/client.js +356 -287
- package/dist/runner/dsh/mux-stream.js +28 -8
- package/dist/runner/dsh/runner.d.ts +4 -3
- package/dist/runner/dsh/runner.js +88 -64
- package/dist/runner/dsh/translator.d.ts +1 -31
- package/dist/runner/dsh/translator.js +180 -178
- package/dist/runner/kimi/acp/runner.js +81 -44
- package/dist/runner/opencode/acp/error-monitor.js +5 -5
- package/dist/runner/pi/rpc/client.js +4 -13
- package/dist/runner/pi/rpc/translator.js +61 -60
- package/dist/session/claude/sessions.js +113 -101
- package/dist/session/codex/rollout-reader.js +179 -180
- package/dist/session/dsh/sessions.js +281 -231
- package/dist/session/kimi/sessions.d.ts +0 -7
- package/dist/session/kimi/sessions.js +129 -109
- package/dist/session/opencode/sessions.d.ts +0 -11
- package/dist/session/opencode/sessions.js +161 -137
- package/dist/session/session-store.d.ts +8 -0
- package/dist/session/session-store.js +98 -98
- package/package.json +1 -1
|
@@ -23,191 +23,193 @@
|
|
|
23
23
|
* → ResultEvent.usage: input_tokens/output_tokens/cache_read_tokens/
|
|
24
24
|
* cache_creation_tokens (cacheWrite = cache creation) + total_tokens sum.
|
|
25
25
|
*/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
recordMessageUsage(usage) {
|
|
62
|
-
this.turnUsage.input += usage.inputTokens ?? 0;
|
|
63
|
-
this.turnUsage.output += usage.outputTokens ?? 0;
|
|
64
|
-
this.turnUsage.cacheRead += usage.cacheReadTokens ?? 0;
|
|
65
|
-
this.turnUsage.cacheWrite += usage.cacheWriteTokens ?? 0;
|
|
66
|
-
this.lastUsage = usage;
|
|
67
|
-
this.hasMessageUsage = true;
|
|
26
|
+
function newTurnState() {
|
|
27
|
+
return {
|
|
28
|
+
turnUsage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
29
|
+
hasMessageUsage: false,
|
|
30
|
+
hasStreamUsage: false,
|
|
31
|
+
attemptId: '0',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// --- 模块级状态操作原语 ---
|
|
35
|
+
/**
|
|
36
|
+
* Record durable per-step usage — the accumulation source. assistant/message
|
|
37
|
+
* carries exactly one usage per model call and is seq-deduped by the client,
|
|
38
|
+
* so summing it gives the turn total without double counting.
|
|
39
|
+
*/
|
|
40
|
+
function recordMessageUsage(state, usage) {
|
|
41
|
+
state.turnUsage.input += usage.inputTokens ?? 0;
|
|
42
|
+
state.turnUsage.output += usage.outputTokens ?? 0;
|
|
43
|
+
state.turnUsage.cacheRead += usage.cacheReadTokens ?? 0;
|
|
44
|
+
state.turnUsage.cacheWrite += usage.cacheWriteTokens ?? 0;
|
|
45
|
+
state.lastUsage = usage;
|
|
46
|
+
state.hasMessageUsage = true;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Record a live usage chunk. Never summed: the same step also arrives as a
|
|
50
|
+
* durable assistant/message.usage, so summing both would double count. It only
|
|
51
|
+
* refreshes the watermark and covers a turn whose messages never persisted.
|
|
52
|
+
*/
|
|
53
|
+
function recordStreamUsage(state, usage) {
|
|
54
|
+
state.lastUsage = usage;
|
|
55
|
+
state.hasStreamUsage = true;
|
|
56
|
+
}
|
|
57
|
+
function chunkToAgentEvents(state, chunk, time, attemptId = '0') {
|
|
58
|
+
if (chunk.type === 'usage' && chunk.usage && typeof chunk.usage === 'object') {
|
|
59
|
+
recordStreamUsage(state, chunk.usage);
|
|
60
|
+
return [];
|
|
68
61
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
62
|
+
const ts = new Date(time).toISOString();
|
|
63
|
+
if (chunk.type === 'text-delta' && typeof chunk.text === 'string' && chunk.text) {
|
|
64
|
+
return [
|
|
65
|
+
{
|
|
66
|
+
type: 'assistant',
|
|
67
|
+
message: { content: [{ type: 'text', text: chunk.text }] },
|
|
68
|
+
timestamp: ts,
|
|
69
|
+
},
|
|
70
|
+
];
|
|
77
71
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
];
|
|
92
|
-
}
|
|
93
|
-
if (chunk.type === 'reasoning-delta' && typeof chunk.text === 'string' && chunk.text) {
|
|
94
|
-
return [
|
|
95
|
-
{
|
|
96
|
-
type: 'assistant',
|
|
97
|
-
message: {
|
|
98
|
-
content: [
|
|
99
|
-
{
|
|
100
|
-
type: 'thinking',
|
|
101
|
-
thinking: chunk.text,
|
|
102
|
-
// 同一 attempt 的同一 reasoning block:token delta 共享 itemId。
|
|
103
|
-
itemId: `r:${attemptId}:${chunk.index}`,
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
},
|
|
107
|
-
timestamp: ts,
|
|
72
|
+
if (chunk.type === 'reasoning-delta' && typeof chunk.text === 'string' && chunk.text) {
|
|
73
|
+
return [
|
|
74
|
+
{
|
|
75
|
+
type: 'assistant',
|
|
76
|
+
message: {
|
|
77
|
+
content: [
|
|
78
|
+
{
|
|
79
|
+
type: 'thinking',
|
|
80
|
+
thinking: chunk.text,
|
|
81
|
+
// 同一 attempt 的同一 reasoning block:token delta 共享 itemId。
|
|
82
|
+
itemId: `r:${attemptId}:${chunk.index}`,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
108
85
|
},
|
|
109
|
-
|
|
110
|
-
|
|
86
|
+
timestamp: ts,
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
/** Translate one live assistant-stream chunk into AgentEvents. */
|
|
93
|
+
function translateStreamFrame(state, frame, _sessionId) {
|
|
94
|
+
if (frame.type === 'start') {
|
|
95
|
+
if (frame.attemptId)
|
|
96
|
+
state.attemptId = frame.attemptId;
|
|
111
97
|
return [];
|
|
112
98
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
99
|
+
if (frame.type !== 'chunk' || !frame.chunk)
|
|
100
|
+
return [];
|
|
101
|
+
return chunkToAgentEvents(state, frame.chunk, frame.time ?? Date.now(), frame.attemptId ?? state.attemptId);
|
|
102
|
+
}
|
|
103
|
+
function handleAssistantChunk(state, ev, _sessionId) {
|
|
104
|
+
const chunk = ev.data.chunk;
|
|
105
|
+
if (!chunk)
|
|
106
|
+
return [];
|
|
107
|
+
return chunkToAgentEvents(state, chunk, ev.time);
|
|
108
|
+
}
|
|
109
|
+
function handleAssistantMessage(state, ev, _sessionId) {
|
|
110
|
+
const raw = ev.data.usage;
|
|
111
|
+
if (raw && typeof raw === 'object') {
|
|
112
|
+
recordMessageUsage(state, raw);
|
|
113
|
+
}
|
|
114
|
+
// 每条 durable 消息都带完整正文;记最后一条非空的就是本轮回答。
|
|
115
|
+
const text = messageText(ev.data.message);
|
|
116
|
+
if (text.trim())
|
|
117
|
+
state.lastMessageText = text;
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
function handleToolCall(_state, ev, _sessionId) {
|
|
121
|
+
// DSH 把「模型发起调用」落成独立事件(name/arguments 是模型原始输出)。
|
|
122
|
+
// 这是卡片工具行的唯一来源:assistant/message 里只有完成后的正文块,
|
|
123
|
+
// 不含调用轨迹。
|
|
124
|
+
const call = ev.data;
|
|
125
|
+
const id = typeof call.callId === 'string' ? call.callId : '';
|
|
126
|
+
const name = typeof call.name === 'string' ? call.name : '';
|
|
127
|
+
if (!id || !name)
|
|
128
|
+
return [];
|
|
129
|
+
return [
|
|
130
|
+
{
|
|
131
|
+
type: 'assistant',
|
|
132
|
+
message: {
|
|
133
|
+
content: [{ type: 'tool_use', id, name, input: parseToolArguments(call.arguments) }],
|
|
134
|
+
},
|
|
135
|
+
timestamp: new Date(ev.time).toISOString(),
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
function handleToolResult(_state, ev, _sessionId) {
|
|
140
|
+
const data = ev.data;
|
|
141
|
+
const msg = data.message;
|
|
142
|
+
const toolCallId = typeof msg?.toolCallId === 'string' ? msg.toolCallId : '';
|
|
143
|
+
if (!toolCallId)
|
|
144
|
+
return [];
|
|
145
|
+
return [
|
|
146
|
+
{
|
|
147
|
+
type: 'user',
|
|
148
|
+
message: {
|
|
149
|
+
content: [
|
|
159
150
|
{
|
|
160
|
-
type: '
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
type: 'tool_result',
|
|
165
|
-
tool_use_id: toolCallId,
|
|
166
|
-
content: toolResultText(msg?.content, data.error),
|
|
167
|
-
is_error: msg?.isError === true,
|
|
168
|
-
},
|
|
169
|
-
],
|
|
170
|
-
},
|
|
171
|
-
timestamp: new Date(ev.time).toISOString(),
|
|
151
|
+
type: 'tool_result',
|
|
152
|
+
tool_use_id: toolCallId,
|
|
153
|
+
content: toolResultText(msg?.content, data.error),
|
|
154
|
+
is_error: msg?.isError === true,
|
|
172
155
|
},
|
|
173
|
-
]
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
timestamp: new Date(ev.time).toISOString(),
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
function handleTurnEnd(state, ev, sessionId) {
|
|
163
|
+
const turnReason = ev.data.reason;
|
|
164
|
+
const reason = turnReason?.kind;
|
|
165
|
+
const subtype = reason === 'completed'
|
|
166
|
+
? 'success'
|
|
167
|
+
: reason === 'aborted' || reason === 'interrupted' || reason === 'forked'
|
|
168
|
+
? 'interrupted'
|
|
169
|
+
: 'error';
|
|
170
|
+
const result = {
|
|
171
|
+
type: 'result',
|
|
172
|
+
subtype,
|
|
173
|
+
session_id: sessionId,
|
|
174
|
+
timestamp: new Date(ev.time).toISOString(),
|
|
175
|
+
};
|
|
176
|
+
if (state.hasMessageUsage) {
|
|
177
|
+
// 本 turn 各采样之和(多步工具往返 = 多条 assistant/message)。
|
|
178
|
+
result.usage = mapAggregatedUsage(state.turnUsage);
|
|
179
|
+
}
|
|
180
|
+
else if (state.hasStreamUsage && state.lastUsage) {
|
|
181
|
+
// 只有 live usage chunk(消息未落盘,如启动期中断):退回末次采样。
|
|
182
|
+
result.usage = mapUsage(state.lastUsage);
|
|
183
|
+
}
|
|
184
|
+
if (subtype === 'error') {
|
|
185
|
+
const detail = turnReason?.error?.message;
|
|
186
|
+
result.errorMessage = detail
|
|
187
|
+
? `turn ended with reason: ${reason}: ${detail}`
|
|
188
|
+
: `turn ended with reason: ${reason}`;
|
|
189
|
+
}
|
|
190
|
+
if (state.lastMessageText) {
|
|
191
|
+
result.finalAnswerText = state.lastMessageText;
|
|
192
|
+
}
|
|
193
|
+
return [result];
|
|
194
|
+
}
|
|
195
|
+
/** 事件类型 → handler。用 Map 而非对象字面量,避免 prototype 键命中。 */
|
|
196
|
+
const SESSION_EVENT_HANDLERS = new Map([
|
|
197
|
+
['assistant/chunk', handleAssistantChunk],
|
|
198
|
+
['assistant/message', handleAssistantMessage],
|
|
199
|
+
['tool/call', handleToolCall],
|
|
200
|
+
['tool/result', handleToolResult],
|
|
201
|
+
['turn/end', handleTurnEnd],
|
|
202
|
+
]);
|
|
203
|
+
export class DshTranslator {
|
|
204
|
+
state = newTurnState();
|
|
205
|
+
/** Translate one live assistant-stream chunk into AgentEvents. */
|
|
206
|
+
streamFrameToAgentEvents(frame, _sessionId) {
|
|
207
|
+
return translateStreamFrame(this.state, frame, _sessionId);
|
|
208
|
+
}
|
|
209
|
+
/** Translate one session event into AgentEvents (may be empty). */
|
|
210
|
+
eventToAgentEvents(ev, sessionId) {
|
|
211
|
+
const handler = SESSION_EVENT_HANDLERS.get(ev.type);
|
|
212
|
+
return handler ? handler(this.state, ev, sessionId) : [];
|
|
211
213
|
}
|
|
212
214
|
}
|
|
213
215
|
/**
|
|
@@ -73,6 +73,85 @@ function toAcpMode(mode) {
|
|
|
73
73
|
return 'yolo';
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// buildQuestionResponse 的模块级命名 handler(闭包搬出,显式传状态)
|
|
78
|
+
// =============================================================================
|
|
79
|
+
function nonEmptyString(v) {
|
|
80
|
+
return typeof v === 'string' && v.length > 0;
|
|
81
|
+
}
|
|
82
|
+
/** 单题答案 → content[qN](多选清洗空串、单选取首个)。 */
|
|
83
|
+
function collectOneAnswer(q, i, byIndex, content) {
|
|
84
|
+
const value = byIndex[i];
|
|
85
|
+
if (value === undefined)
|
|
86
|
+
return;
|
|
87
|
+
if (q.multiSelect) {
|
|
88
|
+
const values = Array.isArray(value) ? value : [value];
|
|
89
|
+
const cleaned = values.filter(nonEmptyString);
|
|
90
|
+
if (cleaned.length > 0)
|
|
91
|
+
content[`q${i}`] = cleaned;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const single = Array.isArray(value) ? value[0] : value;
|
|
95
|
+
if (typeof single === 'string' && single.length > 0)
|
|
96
|
+
content[`q${i}`] = single;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** elicitation form:{问题文本: 值} → 按问题顺序展开的 q0..qn content。 */
|
|
100
|
+
function elicitationContent(questions, answers) {
|
|
101
|
+
const byIndex = mapAnswersByIndex(questions, answers);
|
|
102
|
+
const content = {};
|
|
103
|
+
for (let i = 0; i < questions.length; i++) {
|
|
104
|
+
collectOneAnswer(questions[i], i, byIndex, content);
|
|
105
|
+
}
|
|
106
|
+
return content;
|
|
107
|
+
}
|
|
108
|
+
function elicitationResponse(action, pending, response) {
|
|
109
|
+
if (action === 'answer') {
|
|
110
|
+
const answers = response.answers;
|
|
111
|
+
const questions = pending.view.questions ?? [];
|
|
112
|
+
return { action: 'accept', content: elicitationContent(questions, answers ?? {}) };
|
|
113
|
+
}
|
|
114
|
+
if (action === 'decline')
|
|
115
|
+
return { action: 'decline' };
|
|
116
|
+
return { action: 'cancel' };
|
|
117
|
+
}
|
|
118
|
+
/** request_permission 兜底桥:按 label 回显 qN_opt_M optionId。 */
|
|
119
|
+
function selectedOptionId(options, label) {
|
|
120
|
+
for (const opt of options) {
|
|
121
|
+
if (/^q\d+_opt_\d+$/.test(opt.optionId) && opt.name === label)
|
|
122
|
+
return opt.optionId;
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
/** request_permission 兜底桥:找 qN_skip optionId。 */
|
|
127
|
+
function skipOptionId(options) {
|
|
128
|
+
for (const opt of options) {
|
|
129
|
+
if (/^q\d+_skip$/.test(opt.optionId))
|
|
130
|
+
return opt.optionId;
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
function cancelledOutcome() {
|
|
135
|
+
return { outcome: { outcome: 'cancelled' } };
|
|
136
|
+
}
|
|
137
|
+
function selectedOutcome(optionId) {
|
|
138
|
+
return { outcome: { outcome: 'selected', optionId } };
|
|
139
|
+
}
|
|
140
|
+
function permissionResponse(action, pending, response) {
|
|
141
|
+
if (action === 'answer') {
|
|
142
|
+
const answers = response.answers;
|
|
143
|
+
const first = pending.view.questions?.[0];
|
|
144
|
+
const value = first ? answers?.[first.question] : undefined;
|
|
145
|
+
const label = Array.isArray(value) ? value[0] : value;
|
|
146
|
+
const optionId = selectedOptionId(pending.options ?? [], label);
|
|
147
|
+
return optionId ? selectedOutcome(optionId) : cancelledOutcome();
|
|
148
|
+
}
|
|
149
|
+
if (action === 'decline') {
|
|
150
|
+
const skipId = skipOptionId(pending.options ?? []);
|
|
151
|
+
return skipId ? selectedOutcome(skipId) : cancelledOutcome();
|
|
152
|
+
}
|
|
153
|
+
return cancelledOutcome();
|
|
154
|
+
}
|
|
76
155
|
/**
|
|
77
156
|
* AskUserQuestion 回编(kind === 'question'):
|
|
78
157
|
* - elicitation form:answer → {action:'accept', content:{q0..qn}}(多选数组/
|
|
@@ -84,51 +163,9 @@ function toAcpMode(mode) {
|
|
|
84
163
|
*/
|
|
85
164
|
function buildQuestionResponse(action, pending, response) {
|
|
86
165
|
if (pending.proto === 'elicitation') {
|
|
87
|
-
|
|
88
|
-
const answers = response.answers;
|
|
89
|
-
const questions = pending.view.questions ?? [];
|
|
90
|
-
const byIndex = mapAnswersByIndex(questions, answers ?? {});
|
|
91
|
-
const content = {};
|
|
92
|
-
questions.forEach((q, i) => {
|
|
93
|
-
const value = byIndex[i];
|
|
94
|
-
if (value === undefined)
|
|
95
|
-
return;
|
|
96
|
-
if (q.multiSelect) {
|
|
97
|
-
const values = Array.isArray(value) ? value : [value];
|
|
98
|
-
const cleaned = values.filter((v) => typeof v === 'string' && v.length > 0);
|
|
99
|
-
if (cleaned.length > 0)
|
|
100
|
-
content[`q${i}`] = cleaned;
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
const single = Array.isArray(value) ? value[0] : value;
|
|
104
|
-
if (typeof single === 'string' && single.length > 0)
|
|
105
|
-
content[`q${i}`] = single;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
return { action: 'accept', content };
|
|
109
|
-
}
|
|
110
|
-
if (action === 'decline')
|
|
111
|
-
return { action: 'decline' };
|
|
112
|
-
return { action: 'cancel' };
|
|
113
|
-
}
|
|
114
|
-
// request_permission 兜底桥(单题单选)
|
|
115
|
-
if (action === 'answer') {
|
|
116
|
-
const answers = response.answers;
|
|
117
|
-
const first = pending.view.questions?.[0];
|
|
118
|
-
const value = first ? answers?.[first.question] : undefined;
|
|
119
|
-
const label = Array.isArray(value) ? value[0] : value;
|
|
120
|
-
const option = (pending.options ?? []).find((opt) => /^q\d+_opt_\d+$/.test(opt.optionId) && opt.name === label);
|
|
121
|
-
if (option)
|
|
122
|
-
return { outcome: { outcome: 'selected', optionId: option.optionId } };
|
|
123
|
-
return { outcome: { outcome: 'cancelled' } };
|
|
166
|
+
return elicitationResponse(action, pending, response);
|
|
124
167
|
}
|
|
125
|
-
|
|
126
|
-
const skip = (pending.options ?? []).find((opt) => /^q\d+_skip$/.test(opt.optionId));
|
|
127
|
-
if (skip)
|
|
128
|
-
return { outcome: { outcome: 'selected', optionId: skip.optionId } };
|
|
129
|
-
return { outcome: { outcome: 'cancelled' } };
|
|
130
|
-
}
|
|
131
|
-
return { outcome: { outcome: 'cancelled' } };
|
|
168
|
+
return permissionResponse(action, pending, response);
|
|
132
169
|
}
|
|
133
170
|
// =============================================================================
|
|
134
171
|
// Runner
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* 5xx/network errors retry in seconds and must be left to opencode's own
|
|
25
25
|
* retry loop (the idle watchdog remains the fallback for silent hangs).
|
|
26
26
|
*/
|
|
27
|
-
import
|
|
27
|
+
import fs from 'node:fs';
|
|
28
28
|
import { homedir } from 'node:os';
|
|
29
29
|
import { join } from 'node:path';
|
|
30
30
|
import { getLogger } from '../../../logger/index.js';
|
|
@@ -88,7 +88,7 @@ export class OpencodeLogErrorMonitor {
|
|
|
88
88
|
}
|
|
89
89
|
currentSize() {
|
|
90
90
|
try {
|
|
91
|
-
return statSync(this.logPath).size;
|
|
91
|
+
return fs.statSync(this.logPath).size;
|
|
92
92
|
}
|
|
93
93
|
catch (err) {
|
|
94
94
|
this.noteIoError(err);
|
|
@@ -124,14 +124,14 @@ export class OpencodeLogErrorMonitor {
|
|
|
124
124
|
if (size > this.offset) {
|
|
125
125
|
let chunk;
|
|
126
126
|
try {
|
|
127
|
-
const fd = openSync(this.logPath, 'r');
|
|
127
|
+
const fd = fs.openSync(this.logPath, 'r');
|
|
128
128
|
try {
|
|
129
129
|
const buf = Buffer.alloc(size - this.offset);
|
|
130
|
-
readSync(fd, buf, 0, buf.length, this.offset);
|
|
130
|
+
fs.readSync(fd, buf, 0, buf.length, this.offset);
|
|
131
131
|
chunk = buf.toString('utf8');
|
|
132
132
|
}
|
|
133
133
|
finally {
|
|
134
|
-
closeSync(fd);
|
|
134
|
+
fs.closeSync(fd);
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
catch (err) {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Response correlation is by command id; every non-response line is forwarded
|
|
11
11
|
* as an event to the active hooks (the runner's translator).
|
|
12
12
|
*/
|
|
13
|
-
import { ConnectionLostError } from '../../common/jsonrpc/client.js';
|
|
13
|
+
import { ConnectionLostError, disposeRpcClientInstance, rejectAllPending, } from '../../common/jsonrpc/client.js';
|
|
14
14
|
export class PiRpcClient {
|
|
15
15
|
transport;
|
|
16
16
|
requestTimeoutMs;
|
|
@@ -78,13 +78,8 @@ export class PiRpcClient {
|
|
|
78
78
|
return;
|
|
79
79
|
this.transport.write({ ...command, id: `req_${this.nextId++}` });
|
|
80
80
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return;
|
|
84
|
-
this._disposed = true;
|
|
85
|
-
this._ready = false;
|
|
86
|
-
this.failPending(new ConnectionLostError('pi rpc client disposed'));
|
|
87
|
-
await this.transport.close();
|
|
81
|
+
dispose() {
|
|
82
|
+
return disposeRpcClientInstance(this, this.pending, () => this.transport.close(), 'pi rpc client disposed');
|
|
88
83
|
}
|
|
89
84
|
// =========================================================================
|
|
90
85
|
// Internal
|
|
@@ -104,11 +99,7 @@ export class PiRpcClient {
|
|
|
104
99
|
this.runHooks?.onEvent?.(message);
|
|
105
100
|
}
|
|
106
101
|
failPending(err) {
|
|
107
|
-
|
|
108
|
-
clearTimeout(pending.timer);
|
|
109
|
-
pending.reject(err);
|
|
110
|
-
}
|
|
111
|
-
this.pending.clear();
|
|
102
|
+
rejectAllPending(this.pending, err);
|
|
112
103
|
}
|
|
113
104
|
}
|
|
114
105
|
//# sourceMappingURL=client.js.map
|