lark-remote 0.1.7 → 0.1.9
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 +46 -0
- package/dist/bridge/approval-coordinator.d.ts +30 -1
- package/dist/bridge/approval-coordinator.js +93 -5
- package/dist/bridge/index.d.ts +36 -6
- package/dist/bridge/index.js +56 -10
- package/dist/card/approval-render.js +287 -48
- package/dist/card/card-shared.d.ts +6 -4
- package/dist/card/card-shared.js +6 -4
- package/dist/card/run-renderer.d.ts +7 -0
- package/dist/card/run-renderer.js +22 -11
- package/dist/config/index.d.ts +24 -1
- package/dist/config/index.js +34 -3
- package/dist/index.js +40 -16
- package/dist/order/alias-resolve.d.ts +6 -2
- package/dist/order/index.d.ts +9 -2
- package/dist/order/index.js +33 -0
- package/dist/router/card-action-payload.d.ts +30 -0
- package/dist/router/card-action-payload.js +39 -0
- package/dist/router/config/claude.js +8 -3
- package/dist/router/config/dsh.d.ts +51 -0
- package/dist/router/config/dsh.js +179 -0
- package/dist/router/config/index.d.ts +1 -1
- package/dist/router/config/index.js +4 -1
- package/dist/router/config/types.d.ts +6 -0
- package/dist/router/index.d.ts +39 -4
- package/dist/router/index.js +471 -90
- package/dist/router/utils.js +12 -2
- package/dist/runner/agent-choices-common.js +3 -0
- package/dist/runner/claude/runner.js +22 -0
- package/dist/runner/claude/session.d.ts +26 -0
- package/dist/runner/claude/session.js +105 -9
- package/dist/runner/codex/app-server/protocol-types.d.ts +35 -0
- package/dist/runner/codex/app-server/protocol-types.js +1 -1
- package/dist/runner/codex/app-server/runner.d.ts +10 -0
- package/dist/runner/codex/app-server/runner.js +56 -1
- package/dist/runner/codex/app-server/translator.d.ts +7 -0
- package/dist/runner/codex/app-server/translator.js +45 -0
- package/dist/runner/common/acp/protocol-types.d.ts +33 -0
- package/dist/runner/common/acp/protocol-types.js +2 -0
- package/dist/runner/common/connection-based-runner.js +10 -5
- package/dist/runner/dsh/client.d.ts +95 -0
- package/dist/runner/dsh/client.js +418 -0
- package/dist/runner/dsh/fake-dsh-server.d.ts +60 -0
- package/dist/runner/dsh/fake-dsh-server.js +156 -0
- package/dist/runner/dsh/index.d.ts +3 -0
- package/dist/runner/dsh/index.js +4 -0
- package/dist/runner/dsh/runner.d.ts +57 -0
- package/dist/runner/dsh/runner.js +211 -0
- package/dist/runner/dsh/translator.d.ts +28 -0
- package/dist/runner/dsh/translator.js +98 -0
- package/dist/runner/dsh/types.d.ts +155 -0
- package/dist/runner/dsh/types.js +14 -0
- package/dist/runner/index.d.ts +1 -0
- package/dist/runner/index.js +1 -0
- package/dist/runner/kimi/acp/runner.js +84 -1
- package/dist/runner/kimi/acp/translator.d.ts +14 -0
- package/dist/runner/kimi/acp/translator.js +108 -3
- package/dist/runner/pi/rpc/runner.d.ts +1 -7
- package/dist/runner/pi/rpc/runner.js +8 -12
- package/dist/runner/probe.js +6 -1
- package/dist/runner/question-common.d.ts +26 -0
- package/dist/runner/question-common.js +42 -0
- package/dist/runner/types.d.ts +94 -10
- package/dist/session/claude/sessions.js +37 -15
- package/dist/session/dsh/index.d.ts +1 -0
- package/dist/session/dsh/index.js +2 -0
- package/dist/session/dsh/sessions.d.ts +44 -0
- package/dist/session/dsh/sessions.js +252 -0
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.js +1 -0
- package/dist/workspace/index.d.ts +12 -0
- package/dist/workspace/index.js +28 -2
- package/package.json +2 -1
- package/dist/order/alias-store.d.ts +0 -38
- package/dist/order/alias-store.js +0 -106
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH wire type shapes (DeepSeek Harness HTTP proxy contract).
|
|
3
|
+
*
|
|
4
|
+
* Source of truth: packages/host/apiproxy/src/api/{rpc,sessions,events}.ts and
|
|
5
|
+
* packages/core/session/src/types.ts in the deepseek-harness repo. Only the
|
|
6
|
+
* fields DshClient/DshRunner/DshSessionReader consume are declared here.
|
|
7
|
+
*
|
|
8
|
+
* Unary: POST /api/<method>, body `{type:'client-request',rpcId,method,payload}`,
|
|
9
|
+
* response `{type:'server-response',rpcId,result:{ok:true,value}|{ok:false,error}}`.
|
|
10
|
+
* Stream: SSE GET /api/events.mux, frames `data: {type:'server-request',rpcId,
|
|
11
|
+
* method:<frame.type>, payload:<MuxFrame>}\n\n`.
|
|
12
|
+
*/
|
|
13
|
+
/** One SessionEvent entry (core SessionEvent<T> — seq/time/data discriminated on type). */
|
|
14
|
+
export interface DshSessionEvent {
|
|
15
|
+
type: string;
|
|
16
|
+
seq: number;
|
|
17
|
+
time: number;
|
|
18
|
+
data: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
/** assistant/chunk chunk payload (StreamChunk). */
|
|
21
|
+
export interface DshChunk {
|
|
22
|
+
type: string;
|
|
23
|
+
index?: number;
|
|
24
|
+
text?: string;
|
|
25
|
+
}
|
|
26
|
+
/** TokenUsage from assistant/message.usage (dsh-llm TokenUsage). */
|
|
27
|
+
export interface DshTokenUsage {
|
|
28
|
+
inputTokens: number;
|
|
29
|
+
outputTokens: number;
|
|
30
|
+
cacheReadTokens?: number;
|
|
31
|
+
cacheWriteTokens?: number;
|
|
32
|
+
reasoningTokens?: number;
|
|
33
|
+
}
|
|
34
|
+
/** SessionSummary row from session.list items[]. */
|
|
35
|
+
export interface DshSessionSummary {
|
|
36
|
+
sessionId: string;
|
|
37
|
+
updatedAt: number;
|
|
38
|
+
running: boolean;
|
|
39
|
+
blank: boolean;
|
|
40
|
+
cwd?: string;
|
|
41
|
+
/** 创建时固定的 preset(/resume preset 一致性校验用)。 */
|
|
42
|
+
agentPreset?: string;
|
|
43
|
+
}
|
|
44
|
+
/** session.list → value. */
|
|
45
|
+
export interface DshSessionListValue {
|
|
46
|
+
items: DshSessionSummary[];
|
|
47
|
+
}
|
|
48
|
+
/** session.history → value.events[].event. */
|
|
49
|
+
export interface DshHistoryEntry {
|
|
50
|
+
event: DshSessionEvent;
|
|
51
|
+
view?: unknown;
|
|
52
|
+
}
|
|
53
|
+
/** session.history → value. */
|
|
54
|
+
export interface DshSessionHistoryValue {
|
|
55
|
+
events: DshHistoryEntry[];
|
|
56
|
+
hasMore: boolean;
|
|
57
|
+
}
|
|
58
|
+
/** One model entry inside a provider group (llm.models / session.models groups[].models[]). */
|
|
59
|
+
export interface DshCatalogModel {
|
|
60
|
+
id: string;
|
|
61
|
+
name: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
reasoning?: {
|
|
64
|
+
efforts: Array<{
|
|
65
|
+
id: string;
|
|
66
|
+
name: string;
|
|
67
|
+
}>;
|
|
68
|
+
defaultEffort?: string;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** One provider group of the model catalog (llm.models groups[]). */
|
|
72
|
+
export interface DshModelGroup {
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
models: DshCatalogModel[];
|
|
76
|
+
}
|
|
77
|
+
/** llm.models → value. */
|
|
78
|
+
export interface DshModelCatalogValue {
|
|
79
|
+
groups: DshModelGroup[];
|
|
80
|
+
failures?: Array<{
|
|
81
|
+
id: string;
|
|
82
|
+
name: string;
|
|
83
|
+
message: string;
|
|
84
|
+
}>;
|
|
85
|
+
}
|
|
86
|
+
/** Current model selection (session.models current / session.selectModel selected). */
|
|
87
|
+
export interface DshModelSelection {
|
|
88
|
+
provider: string;
|
|
89
|
+
model: string;
|
|
90
|
+
reasoningEffort?: string;
|
|
91
|
+
}
|
|
92
|
+
/** session.models → value. */
|
|
93
|
+
export interface DshSessionModelsValue {
|
|
94
|
+
current: DshModelSelection;
|
|
95
|
+
routable: boolean;
|
|
96
|
+
groups: DshModelGroup[];
|
|
97
|
+
failures?: Array<{
|
|
98
|
+
id: string;
|
|
99
|
+
name: string;
|
|
100
|
+
message: string;
|
|
101
|
+
}>;
|
|
102
|
+
}
|
|
103
|
+
/** One preset row of agentPreset.list. */
|
|
104
|
+
export interface DshPresetEntry {
|
|
105
|
+
id: string;
|
|
106
|
+
trust: 'system' | 'user';
|
|
107
|
+
isDefault: boolean;
|
|
108
|
+
name?: string;
|
|
109
|
+
description?: string;
|
|
110
|
+
broken?: string;
|
|
111
|
+
}
|
|
112
|
+
/** agentPreset.list → value. */
|
|
113
|
+
export interface DshPresetListValue {
|
|
114
|
+
presets: DshPresetEntry[];
|
|
115
|
+
authorable: boolean;
|
|
116
|
+
hasDocument: boolean;
|
|
117
|
+
}
|
|
118
|
+
/** Mux frame payloads we care about (subset of MuxFrame). */
|
|
119
|
+
export type DshMuxFrame = {
|
|
120
|
+
type: 'session/event';
|
|
121
|
+
sessionId: string;
|
|
122
|
+
event: DshSessionEvent;
|
|
123
|
+
} | {
|
|
124
|
+
type: 'approval/requested';
|
|
125
|
+
sessionId: string;
|
|
126
|
+
approvalId: string;
|
|
127
|
+
toolName: string;
|
|
128
|
+
callId?: string;
|
|
129
|
+
reason?: string;
|
|
130
|
+
} | {
|
|
131
|
+
type: 'stream/error';
|
|
132
|
+
error: {
|
|
133
|
+
code: string;
|
|
134
|
+
message: string;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
/** ServerRequest envelope (frame.method === frame.payload.type). */
|
|
138
|
+
export interface DshServerRequest {
|
|
139
|
+
type: 'server-request';
|
|
140
|
+
rpcId: string;
|
|
141
|
+
method: string;
|
|
142
|
+
payload: unknown;
|
|
143
|
+
}
|
|
144
|
+
/** Unified per-session subscription item: a session event or an approval notice. */
|
|
145
|
+
export type DshStreamItem = {
|
|
146
|
+
kind: 'event';
|
|
147
|
+
sessionId: string;
|
|
148
|
+
event: DshSessionEvent;
|
|
149
|
+
} | {
|
|
150
|
+
kind: 'approval';
|
|
151
|
+
sessionId: string;
|
|
152
|
+
approvalId: string;
|
|
153
|
+
toolName: string;
|
|
154
|
+
reason?: string;
|
|
155
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH wire type shapes (DeepSeek Harness HTTP proxy contract).
|
|
3
|
+
*
|
|
4
|
+
* Source of truth: packages/host/apiproxy/src/api/{rpc,sessions,events}.ts and
|
|
5
|
+
* packages/core/session/src/types.ts in the deepseek-harness repo. Only the
|
|
6
|
+
* fields DshClient/DshRunner/DshSessionReader consume are declared here.
|
|
7
|
+
*
|
|
8
|
+
* Unary: POST /api/<method>, body `{type:'client-request',rpcId,method,payload}`,
|
|
9
|
+
* response `{type:'server-response',rpcId,result:{ok:true,value}|{ok:false,error}}`.
|
|
10
|
+
* Stream: SSE GET /api/events.mux, frames `data: {type:'server-request',rpcId,
|
|
11
|
+
* method:<frame.type>, payload:<MuxFrame>}\n\n`.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=types.js.map
|
package/dist/runner/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { CodexAppServerRunner } from './codex/index.js';
|
|
|
12
12
|
export { OpencodeAcpRunner } from './opencode/index.js';
|
|
13
13
|
export { PiRpcRunner } from './pi/index.js';
|
|
14
14
|
export { KimiAcpRunner } from './kimi/index.js';
|
|
15
|
+
export { DshRunner } from './dsh/index.js';
|
|
15
16
|
export { BashProcessRunner, type BashRunner } from './bash/index.js';
|
|
16
17
|
export { AgentRegistry } from './registry.js';
|
|
17
18
|
export type { ResultEvent, PlanEvent, FileChangeEvent, AgentEvent, Runner, RunnerLifetime, AgentKind, AgentSession, AgentSessionContentEvent, AgentSessionUsage, SessionContent, AgentSessionReader, AgentRunner, TurnStartedEvent, TurnDiffEvent, ApprovalView, ApprovalRequestedEvent, ApprovalResolvedEvent, } from './types.js';
|
package/dist/runner/index.js
CHANGED
|
@@ -13,6 +13,7 @@ export { CodexAppServerRunner } from './codex/index.js';
|
|
|
13
13
|
export { OpencodeAcpRunner } from './opencode/index.js';
|
|
14
14
|
export { PiRpcRunner } from './pi/index.js';
|
|
15
15
|
export { KimiAcpRunner } from './kimi/index.js';
|
|
16
|
+
export { DshRunner } from './dsh/index.js';
|
|
16
17
|
export { BashProcessRunner } from './bash/index.js';
|
|
17
18
|
// Re-export registry
|
|
18
19
|
export { AgentRegistry } from './registry.js';
|
|
@@ -15,6 +15,7 @@ import { ConnectionManager as KimiAcpConnectionManager, } from '../../common/jso
|
|
|
15
15
|
import { KimiAcpTranslator } from './translator.js';
|
|
16
16
|
import { RpcErrorCode, ServerRequestMethod, } from '../../common/acp/protocol-types.js';
|
|
17
17
|
import { findOptionIdByKind } from '../../common/acp/protocol-helpers.js';
|
|
18
|
+
import { mapAnswersByIndex } from '../../question-common.js';
|
|
18
19
|
import { getLogger } from '../../../logger/index.js';
|
|
19
20
|
import { ConnectionBasedRunner } from '../../common/connection-based-runner.js';
|
|
20
21
|
/** R2: how long to wait for the background compaction record after the
|
|
@@ -92,6 +93,63 @@ function buildApprovalResponse(action, pending) {
|
|
|
92
93
|
}
|
|
93
94
|
return { outcome: { outcome: 'cancelled' } };
|
|
94
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* AskUserQuestion 回编(kind === 'question'):
|
|
98
|
+
* - elicitation form:answer → {action:'accept', content:{q0..qn}}(多选数组/
|
|
99
|
+
* 单选字符串,key 按问题顺序);decline → {action:'decline'};cancel →
|
|
100
|
+
* {action:'cancel'}。
|
|
101
|
+
* - request_permission 兜底桥:answer → 按 label 回显 q0_opt_{i} optionId;
|
|
102
|
+
* decline → q0_skip optionId(无则 cancelled);cancel → cancelled。
|
|
103
|
+
* 答案以 {问题文本: 值} 回传,先按问题顺序展开再协议编码。
|
|
104
|
+
*/
|
|
105
|
+
function buildQuestionResponse(action, pending, response) {
|
|
106
|
+
if (pending.proto === 'elicitation') {
|
|
107
|
+
if (action === 'answer') {
|
|
108
|
+
const answers = response.answers;
|
|
109
|
+
const questions = pending.view.questions ?? [];
|
|
110
|
+
const byIndex = mapAnswersByIndex(questions, answers ?? {});
|
|
111
|
+
const content = {};
|
|
112
|
+
questions.forEach((q, i) => {
|
|
113
|
+
const value = byIndex[i];
|
|
114
|
+
if (value === undefined)
|
|
115
|
+
return;
|
|
116
|
+
if (q.multiSelect) {
|
|
117
|
+
const values = Array.isArray(value) ? value : [value];
|
|
118
|
+
const cleaned = values.filter((v) => typeof v === 'string' && v.length > 0);
|
|
119
|
+
if (cleaned.length > 0)
|
|
120
|
+
content[`q${i}`] = cleaned;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const single = Array.isArray(value) ? value[0] : value;
|
|
124
|
+
if (typeof single === 'string' && single.length > 0)
|
|
125
|
+
content[`q${i}`] = single;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return { action: 'accept', content };
|
|
129
|
+
}
|
|
130
|
+
if (action === 'decline')
|
|
131
|
+
return { action: 'decline' };
|
|
132
|
+
return { action: 'cancel' };
|
|
133
|
+
}
|
|
134
|
+
// request_permission 兜底桥(单题单选)
|
|
135
|
+
if (action === 'answer') {
|
|
136
|
+
const answers = response.answers;
|
|
137
|
+
const first = pending.view.questions?.[0];
|
|
138
|
+
const value = first ? answers?.[first.question] : undefined;
|
|
139
|
+
const label = Array.isArray(value) ? value[0] : value;
|
|
140
|
+
const option = (pending.options ?? []).find((opt) => /^q\d+_opt_\d+$/.test(opt.optionId) && opt.name === label);
|
|
141
|
+
if (option)
|
|
142
|
+
return { outcome: { outcome: 'selected', optionId: option.optionId } };
|
|
143
|
+
return { outcome: { outcome: 'cancelled' } };
|
|
144
|
+
}
|
|
145
|
+
if (action === 'decline') {
|
|
146
|
+
const skip = (pending.options ?? []).find((opt) => /^q\d+_skip$/.test(opt.optionId));
|
|
147
|
+
if (skip)
|
|
148
|
+
return { outcome: { outcome: 'selected', optionId: skip.optionId } };
|
|
149
|
+
return { outcome: { outcome: 'cancelled' } };
|
|
150
|
+
}
|
|
151
|
+
return { outcome: { outcome: 'cancelled' } };
|
|
152
|
+
}
|
|
95
153
|
// =============================================================================
|
|
96
154
|
// Runner
|
|
97
155
|
// =============================================================================
|
|
@@ -122,6 +180,13 @@ export class KimiAcpRunner extends ConnectionBasedRunner {
|
|
|
122
180
|
clientCapabilities: {
|
|
123
181
|
fs: { readTextFile: false, writeTextFile: false },
|
|
124
182
|
terminal: false,
|
|
183
|
+
// AskUserQuestion 走 elicitation form(原生多题+多选;form 失败时
|
|
184
|
+
// kimi 服务端自动回退 request_permission 桥,客户端两者都处理)。
|
|
185
|
+
// 注意:ACP SDK 对 elicitation.form 的 zod schema 是对象
|
|
186
|
+
// (z.record(z.string(), z.any())),布尔 true 会被 kimi 服务端静默
|
|
187
|
+
// 丢弃 → elicitationForm=false → 多选回退成 request_permission 单选
|
|
188
|
+
// 桥(勾一个选项即提交)。必须用空对象 {}。
|
|
189
|
+
elicitation: { form: {} },
|
|
125
190
|
},
|
|
126
191
|
},
|
|
127
192
|
};
|
|
@@ -261,7 +326,9 @@ export class KimiAcpRunner extends ConnectionBasedRunner {
|
|
|
261
326
|
if (!client || !pending)
|
|
262
327
|
return;
|
|
263
328
|
const action = response?.action ?? 'decline';
|
|
264
|
-
const acpResponse =
|
|
329
|
+
const acpResponse = pending.kind === 'question'
|
|
330
|
+
? buildQuestionResponse(action, pending, response)
|
|
331
|
+
: buildApprovalResponse(action, pending);
|
|
265
332
|
client.respond(requestId, acpResponse);
|
|
266
333
|
this.pendingApprovals.delete(requestId);
|
|
267
334
|
getLogger().info(`[${this.logTag}] approval responded requestId=${requestId} action=${action}`);
|
|
@@ -338,6 +405,21 @@ export class KimiAcpRunner extends ConnectionBasedRunner {
|
|
|
338
405
|
sessionId = newResult.sessionId;
|
|
339
406
|
}
|
|
340
407
|
this.activeSessionId = sessionId;
|
|
408
|
+
// CC-07: 下发配置的模型(provider/model)。session/new|resume 只带 cwd/mcpServers,
|
|
409
|
+
// 不带 model;不主动下发则实际跑 kimi 服务端默认模型(旧 KimiRunner 通过 -m 传模型,
|
|
410
|
+
// 迁移到纯 ACP 后丢失)。仿 opencode 用 session/set_config_option。失败仅告警不阻断。
|
|
411
|
+
if (this.model) {
|
|
412
|
+
try {
|
|
413
|
+
await client.request('session/set_config_option', {
|
|
414
|
+
sessionId,
|
|
415
|
+
configId: 'model',
|
|
416
|
+
value: this.model,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
getLogger().warn(`[${this.logTag}] set_config_option model failed (non-fatal): ${err.message}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
341
423
|
// Set permission mode (§5.1): EVERY fresh ACP session starts in
|
|
342
424
|
// 'default' (= manual approvals, acp-server modes.ts DEFAULT_MODE_ID),
|
|
343
425
|
// so the mode must be sent unconditionally — skipping yolo leaves the
|
|
@@ -438,6 +520,7 @@ export class KimiAcpRunner extends ConnectionBasedRunner {
|
|
|
438
520
|
kind: ev.kind,
|
|
439
521
|
view: ev.view,
|
|
440
522
|
options: params.options,
|
|
523
|
+
proto: method === ServerRequestMethod.ELICITATION_CREATE ? 'elicitation' : 'permission',
|
|
441
524
|
});
|
|
442
525
|
getLogger().info(`[${this.logTag}] approval requested requestId=${ev.requestId} kind=${ev.kind}`);
|
|
443
526
|
}
|
|
@@ -81,6 +81,14 @@ export declare class KimiAcpTranslator {
|
|
|
81
81
|
* Handle a server request (reverse RPC from the ACP server).
|
|
82
82
|
*/
|
|
83
83
|
handleServerRequest(id: number | string, method: string, params: unknown): AcpTranslatorEvent[];
|
|
84
|
+
/**
|
|
85
|
+
* Kimi elicitation form:requestedSchema.properties 按顺序解析为 UserQuestion[]
|
|
86
|
+
* (type:array → 多选;oneOf/anyOf const → 选项 label;title → header??question)。
|
|
87
|
+
* 题面完整文本只在 form 级 message 里合并出现(逐字段 title 可能只是短 header),
|
|
88
|
+
* 因此 message 作为卡片概要行(view.intro)透传。
|
|
89
|
+
* form 会丢弃非声明选项值 → isOther=false(卡片隐藏自定义答案输入)。
|
|
90
|
+
*/
|
|
91
|
+
private handleElicitationCreate;
|
|
84
92
|
/**
|
|
85
93
|
* Produce a turn_started event. Called by the runner when session/prompt
|
|
86
94
|
* is sent (no wire notification for this — self-produced).
|
|
@@ -108,6 +116,12 @@ export declare class KimiAcpTranslator {
|
|
|
108
116
|
private handleToolCallUpdate;
|
|
109
117
|
private handleUsageUpdate;
|
|
110
118
|
private handleRequestPermission;
|
|
119
|
+
/**
|
|
120
|
+
* request_permission 兜底桥(elicitation/create 失败或旧版 kimi):
|
|
121
|
+
* 选项为 q{n}_opt_{i}(allow_once,label 直取 name)+ q{n}_skip(reject_once,
|
|
122
|
+
* 跳过语义)。单题单选、无 Other(kimi 桥丢弃非声明值)。
|
|
123
|
+
*/
|
|
124
|
+
private handlePermissionQuestion;
|
|
111
125
|
/**
|
|
112
126
|
* Derive the approval decision list from the server's offered options.
|
|
113
127
|
* accept/decline/cancel are universal; acceptForSession requires an
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* turn_started (self-produced) → operationKind 'turn'/'compaction'
|
|
29
29
|
* session/request_permission → approval_requested
|
|
30
30
|
*/
|
|
31
|
+
import { makeQuestionApprovalEvent } from '../../question-common.js';
|
|
31
32
|
import { NotificationMethod, ServerRequestMethod, SessionEventType, } from '../../common/acp/protocol-types.js';
|
|
32
33
|
import { getLogger } from '../../../logger/index.js';
|
|
33
34
|
/**
|
|
@@ -96,8 +97,41 @@ export class KimiAcpTranslator {
|
|
|
96
97
|
if (method === ServerRequestMethod.REQUEST_PERMISSION) {
|
|
97
98
|
return this.handleRequestPermission(id, params);
|
|
98
99
|
}
|
|
100
|
+
if (method === ServerRequestMethod.ELICITATION_CREATE) {
|
|
101
|
+
return this.handleElicitationCreate(id, params);
|
|
102
|
+
}
|
|
99
103
|
return [];
|
|
100
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Kimi elicitation form:requestedSchema.properties 按顺序解析为 UserQuestion[]
|
|
107
|
+
* (type:array → 多选;oneOf/anyOf const → 选项 label;title → header??question)。
|
|
108
|
+
* 题面完整文本只在 form 级 message 里合并出现(逐字段 title 可能只是短 header),
|
|
109
|
+
* 因此 message 作为卡片概要行(view.intro)透传。
|
|
110
|
+
* form 会丢弃非声明选项值 → isOther=false(卡片隐藏自定义答案输入)。
|
|
111
|
+
*/
|
|
112
|
+
handleElicitationCreate(requestId, params) {
|
|
113
|
+
const properties = params.requestedSchema?.properties ?? {};
|
|
114
|
+
const questions = [];
|
|
115
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
116
|
+
const q = elicitationPropertyToQuestion(prop);
|
|
117
|
+
if (!q) {
|
|
118
|
+
getLogger().warn(`[kimi-acp-translator] elicitation property skipped: key=${key}`);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
questions.push(q);
|
|
122
|
+
}
|
|
123
|
+
if (questions.length === 0) {
|
|
124
|
+
// 无法解析 → 交还 runner 自动响应(不悬挂服务端)。
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
const message = params.message?.trim();
|
|
128
|
+
const single = questions.length === 1;
|
|
129
|
+
const intro = message && (!single || message !== questions[0].question) ? message : undefined;
|
|
130
|
+
const event = makeQuestionApprovalEvent(requestId, questions, params.sessionId);
|
|
131
|
+
if (intro)
|
|
132
|
+
event.view.intro = intro;
|
|
133
|
+
return [event];
|
|
134
|
+
}
|
|
101
135
|
/**
|
|
102
136
|
* Produce a turn_started event. Called by the runner when session/prompt
|
|
103
137
|
* is sent (no wire notification for this — self-produced).
|
|
@@ -274,9 +308,20 @@ export class KimiAcpTranslator {
|
|
|
274
308
|
// Private approval handlers
|
|
275
309
|
// =========================================================================
|
|
276
310
|
handleRequestPermission(requestId, params) {
|
|
277
|
-
// §5.4
|
|
278
|
-
|
|
279
|
-
|
|
311
|
+
// §5.4 升级:question elicitation 识别(isQuestion 标记 / AskUserQuestion
|
|
312
|
+
// toolCall 标题 / q{n}_opt_* 选项命名空间)→ 渲染为提问卡;无法解析的
|
|
313
|
+
// 退化形态(无 toolCall 也无选项)仍返回 [] 由 runner 自动响应 cancelled。
|
|
314
|
+
const toolCall = params.toolCall;
|
|
315
|
+
const questionMarker = params.isQuestion === true ||
|
|
316
|
+
toolCall?.title === 'AskUserQuestion' ||
|
|
317
|
+
optionIdsMatchQuestionNamespace(params.options);
|
|
318
|
+
if (questionMarker) {
|
|
319
|
+
return this.handlePermissionQuestion(requestId, params);
|
|
320
|
+
}
|
|
321
|
+
// §5.4:无 toolCall 且无提问标记 → 无法构成审批视图,返回 [] 由 runner
|
|
322
|
+
// 自动响应 cancelled(服务端不会悬挂)。
|
|
323
|
+
if (!toolCall) {
|
|
324
|
+
getLogger().info(`[kimi-acp-translator] permission request without toolCall, auto-responding cancelled (requestId=${requestId})`);
|
|
280
325
|
return [];
|
|
281
326
|
}
|
|
282
327
|
const view = {
|
|
@@ -301,6 +346,27 @@ export class KimiAcpTranslator {
|
|
|
301
346
|
},
|
|
302
347
|
];
|
|
303
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* request_permission 兜底桥(elicitation/create 失败或旧版 kimi):
|
|
351
|
+
* 选项为 q{n}_opt_{i}(allow_once,label 直取 name)+ q{n}_skip(reject_once,
|
|
352
|
+
* 跳过语义)。单题单选、无 Other(kimi 桥丢弃非声明值)。
|
|
353
|
+
*/
|
|
354
|
+
handlePermissionQuestion(requestId, params) {
|
|
355
|
+
const selectable = (params.options ?? []).filter((opt) => /^q\d+_opt_\d+$/.test(opt.optionId));
|
|
356
|
+
if (selectable.length === 0) {
|
|
357
|
+
getLogger().info(`[kimi-acp-translator] question elicitation unparseable, auto-responding cancelled (requestId=${requestId})`);
|
|
358
|
+
return [];
|
|
359
|
+
}
|
|
360
|
+
const questionText = extractQuestionText(params.toolCall);
|
|
361
|
+
const questions = [
|
|
362
|
+
{
|
|
363
|
+
question: questionText,
|
|
364
|
+
isOther: false,
|
|
365
|
+
options: selectable.map((opt) => ({ label: opt.name })),
|
|
366
|
+
},
|
|
367
|
+
];
|
|
368
|
+
return [makeQuestionApprovalEvent(requestId, questions, params.sessionId)];
|
|
369
|
+
}
|
|
304
370
|
/**
|
|
305
371
|
* Derive the approval decision list from the server's offered options.
|
|
306
372
|
* accept/decline/cancel are universal; acceptForSession requires an
|
|
@@ -333,4 +399,43 @@ export class KimiAcpTranslator {
|
|
|
333
399
|
function truncate(text, maxLen) {
|
|
334
400
|
return text.length <= maxLen ? text : text.slice(0, maxLen) + '…';
|
|
335
401
|
}
|
|
402
|
+
/** elicitation schema property → UserQuestion(无法解析返回 null)。 */
|
|
403
|
+
function elicitationPropertyToQuestion(prop) {
|
|
404
|
+
const title = prop.title?.trim();
|
|
405
|
+
if (!title)
|
|
406
|
+
return null;
|
|
407
|
+
const multiSelect = prop.type === 'array';
|
|
408
|
+
const rawOptions = multiSelect ? prop.items?.anyOf : prop.oneOf;
|
|
409
|
+
const options = (rawOptions ?? [])
|
|
410
|
+
.filter((opt) => typeof opt?.const === 'string')
|
|
411
|
+
.map((opt) => ({
|
|
412
|
+
label: opt.const,
|
|
413
|
+
...(opt.description ? { description: opt.description } : {}),
|
|
414
|
+
}));
|
|
415
|
+
if (options.length === 0)
|
|
416
|
+
return null;
|
|
417
|
+
return {
|
|
418
|
+
question: title,
|
|
419
|
+
header: title,
|
|
420
|
+
multiSelect,
|
|
421
|
+
isOther: false,
|
|
422
|
+
options,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/** 选项命名空间是否匹配 kimi question 桥(q{n}_opt_{i} / q{n}_skip)。 */
|
|
426
|
+
function optionIdsMatchQuestionNamespace(options) {
|
|
427
|
+
return (options ?? []).some((opt) => /^q\d+_(opt_\d+|skip)$/.test(opt.optionId));
|
|
428
|
+
}
|
|
429
|
+
/** 从 AskUserQuestion toolCall 提取题面(content 文本优先,标题兜底)。 */
|
|
430
|
+
function extractQuestionText(toolCall) {
|
|
431
|
+
const content = toolCall?.content;
|
|
432
|
+
if (Array.isArray(content)) {
|
|
433
|
+
for (const block of content) {
|
|
434
|
+
const text = block.content?.text;
|
|
435
|
+
if (typeof text === 'string' && text.trim())
|
|
436
|
+
return text.trim();
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return toolCall?.title?.trim() || '请选择';
|
|
440
|
+
}
|
|
336
441
|
//# sourceMappingURL=translator.js.map
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
* turn idle watchdog, connection-lost retry). Differences:
|
|
15
15
|
* - pi binds the session at spawn via `--session-id` (no RPC resume-by-id);
|
|
16
16
|
* the PiRpcConnectionManager respawns when the requested session changes.
|
|
17
|
-
* -
|
|
18
|
-
* respondApproval is a no-op.
|
|
17
|
+
* - pi,极简风格,没有 Ask User question。
|
|
19
18
|
* - `prompt` ack ≠ turn completion; the turn ends on `agent_settled`.
|
|
20
19
|
*/
|
|
21
20
|
import type { AgentSessionReader, AgentEvent, AgentStatusInfo, SpawnOptions } from '../../types.js';
|
|
@@ -71,10 +70,5 @@ export declare class PiRpcRunner extends ConnectionBasedRunner<PiRpcClient, PiRp
|
|
|
71
70
|
* completion (the completion signal); `compaction_*` events stream meanwhile.
|
|
72
71
|
*/
|
|
73
72
|
runCompact(_message: string, opts: SpawnOptions): AsyncGenerator<AgentEvent>;
|
|
74
|
-
/**
|
|
75
|
-
* Approvals are out of scope for pi RPC (no server requests surface in this
|
|
76
|
-
* integration); the bridge's duck-typed respondApproval call is a no-op.
|
|
77
|
-
*/
|
|
78
|
-
respondApproval(_requestId: number | string, _response: unknown): Promise<void>;
|
|
79
73
|
private handleEvent;
|
|
80
74
|
}
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
* turn idle watchdog, connection-lost retry). Differences:
|
|
15
15
|
* - pi binds the session at spawn via `--session-id` (no RPC resume-by-id);
|
|
16
16
|
* the PiRpcConnectionManager respawns when the requested session changes.
|
|
17
|
-
* -
|
|
18
|
-
* respondApproval is a no-op.
|
|
17
|
+
* - pi,极简风格,没有 Ask User question。
|
|
19
18
|
* - `prompt` ack ≠ turn completion; the turn ends on `agent_settled`.
|
|
20
19
|
*/
|
|
21
20
|
import { ConnectionBasedRunner } from '../../common/connection-based-runner.js';
|
|
@@ -135,7 +134,12 @@ export class PiRpcRunner extends ConnectionBasedRunner {
|
|
|
135
134
|
this.currentTurnId = turnId;
|
|
136
135
|
this.pushEvents([translator.produceTurnStarted(sessionId, turnId)]);
|
|
137
136
|
this.promptSettled = false;
|
|
138
|
-
await client.request({ type: 'prompt', message });
|
|
137
|
+
const response = await client.request({ type: 'prompt', message });
|
|
138
|
+
if (!response.success) {
|
|
139
|
+
// CC-08: pi 返回 success:false(无效模型/provider 错误/忙)时立即失败,
|
|
140
|
+
// 不能当成成功 ACK 继续等 agent_settled(否则最长 turn idle timeout)。
|
|
141
|
+
throw new Error(`pi prompt failed: ${response.error ?? 'unknown error'}`);
|
|
142
|
+
}
|
|
139
143
|
this.promptSettled = true;
|
|
140
144
|
}
|
|
141
145
|
/**
|
|
@@ -172,16 +176,8 @@ export class PiRpcRunner extends ConnectionBasedRunner {
|
|
|
172
176
|
this.pushEvents([translator.produceCompactResult(opts.sessionId, response)]);
|
|
173
177
|
});
|
|
174
178
|
}
|
|
175
|
-
/**
|
|
176
|
-
* Approvals are out of scope for pi RPC (no server requests surface in this
|
|
177
|
-
* integration); the bridge's duck-typed respondApproval call is a no-op.
|
|
178
|
-
*/
|
|
179
|
-
async respondApproval(_requestId, _response) {
|
|
180
|
-
// no-op
|
|
181
|
-
}
|
|
182
179
|
handleEvent(evt) {
|
|
183
|
-
|
|
184
|
-
this.pushEvents(events);
|
|
180
|
+
this.pushEvents(this.currentTranslator?.handleEvent(evt) ?? []);
|
|
185
181
|
}
|
|
186
182
|
}
|
|
187
183
|
/** Build pi `--mode rpc` spawn args. `sessionId` is bound at spawn (pi has no
|
package/dist/runner/probe.js
CHANGED
|
@@ -28,6 +28,9 @@ const BINARY_MAP = {
|
|
|
28
28
|
opencode: 'opencode',
|
|
29
29
|
pi: 'pi',
|
|
30
30
|
kimi: 'kimi',
|
|
31
|
+
// DSH is an HTTP-only agent (no CLI). `which ''` fails → reported unavailable,
|
|
32
|
+
// which is semantically correct for the /config availability display.
|
|
33
|
+
dsh: '',
|
|
31
34
|
};
|
|
32
35
|
const CACHE_TTL_MS = 5 * 60_000;
|
|
33
36
|
/** Module-level cache, survives across calls within the process. */
|
|
@@ -65,7 +68,9 @@ export async function probeAgentAvailability(kind) {
|
|
|
65
68
|
* on resource-constrained devices.
|
|
66
69
|
*/
|
|
67
70
|
export async function probeAllAgents() {
|
|
68
|
-
|
|
71
|
+
// Skip agents with no CLI binary (DSH is HTTP-only, binary === ''): the
|
|
72
|
+
// probe / availability display only covers spawn-able agents.
|
|
73
|
+
const kinds = Object.keys(BINARY_MAP).filter((k) => BINARY_MAP[k] !== '');
|
|
69
74
|
const entries = await Promise.all(kinds.map(async (k) => [k, await probeAgentAvailability(k)]));
|
|
70
75
|
return new Map(entries);
|
|
71
76
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AskUserQuestion 公共工厂与映射(agent 无关)。
|
|
3
|
+
*
|
|
4
|
+
* 接入约定(types.ts UserQuestion 注释的落点):
|
|
5
|
+
* - 各 runner 用 makeQuestionApprovalEvent 构造标准 question 审批事件,
|
|
6
|
+
* 避免四处手拼事件形状(claude/codex/kimi/pi 共用);
|
|
7
|
+
* - 用户答案经 ApprovalCoordinator 以 {问题文本: 值} 回传 runner 后,
|
|
8
|
+
* 用 mapAnswersByIndex 按问题顺序展开,再做协议级编码
|
|
9
|
+
* (Codex 问题 id、Kimi q{i}、Pi 单题取值)。
|
|
10
|
+
*/
|
|
11
|
+
import type { ApprovalRequestedEvent, UserQuestion } from './types.js';
|
|
12
|
+
export interface QuestionEventOptions {
|
|
13
|
+
/** Per-request 审批超时覆盖(Codex autoResolutionMs)。 */
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 构造标准 question approval_requested 事件。
|
|
18
|
+
* availableDecisions 恒为空数组:提问只能作答或跳过(decline/cancel 是
|
|
19
|
+
* coordinator 的通用安全兜底),不允许 accept/decline 按钮混入提问卡。
|
|
20
|
+
*/
|
|
21
|
+
export declare function makeQuestionApprovalEvent(requestId: number | string, questions: UserQuestion[], threadId: string, opts?: QuestionEventOptions): ApprovalRequestedEvent;
|
|
22
|
+
/**
|
|
23
|
+
* 按问题顺序把 coordinator 回传的 {问题文本: 值} 展开成索引对齐数组。
|
|
24
|
+
* 未作答的问题对应 undefined,由各 runner 决定协议编码(跳过/空 answers)。
|
|
25
|
+
*/
|
|
26
|
+
export declare function mapAnswersByIndex(questions: UserQuestion[], answers: Record<string, string | string[]>): Array<string | string[] | undefined>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AskUserQuestion 公共工厂与映射(agent 无关)。
|
|
3
|
+
*
|
|
4
|
+
* 接入约定(types.ts UserQuestion 注释的落点):
|
|
5
|
+
* - 各 runner 用 makeQuestionApprovalEvent 构造标准 question 审批事件,
|
|
6
|
+
* 避免四处手拼事件形状(claude/codex/kimi/pi 共用);
|
|
7
|
+
* - 用户答案经 ApprovalCoordinator 以 {问题文本: 值} 回传 runner 后,
|
|
8
|
+
* 用 mapAnswersByIndex 按问题顺序展开,再做协议级编码
|
|
9
|
+
* (Codex 问题 id、Kimi q{i}、Pi 单题取值)。
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* 构造标准 question approval_requested 事件。
|
|
13
|
+
* availableDecisions 恒为空数组:提问只能作答或跳过(decline/cancel 是
|
|
14
|
+
* coordinator 的通用安全兜底),不允许 accept/decline 按钮混入提问卡。
|
|
15
|
+
*/
|
|
16
|
+
export function makeQuestionApprovalEvent(requestId, questions, threadId, opts) {
|
|
17
|
+
const view = {
|
|
18
|
+
requestId,
|
|
19
|
+
kind: 'question',
|
|
20
|
+
questions,
|
|
21
|
+
availableDecisions: [],
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
type: 'approval_requested',
|
|
25
|
+
requestId,
|
|
26
|
+
kind: 'question',
|
|
27
|
+
threadId,
|
|
28
|
+
turnId: '',
|
|
29
|
+
itemId: '',
|
|
30
|
+
view,
|
|
31
|
+
...(opts?.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
|
|
32
|
+
timestamp: new Date().toISOString(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 按问题顺序把 coordinator 回传的 {问题文本: 值} 展开成索引对齐数组。
|
|
37
|
+
* 未作答的问题对应 undefined,由各 runner 决定协议编码(跳过/空 answers)。
|
|
38
|
+
*/
|
|
39
|
+
export function mapAnswersByIndex(questions, answers) {
|
|
40
|
+
return questions.map((q) => answers[q.question]);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=question-common.js.map
|