lark-remote 0.1.7 → 0.1.8
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 +28 -0
- package/dist/bridge/approval-coordinator.d.ts +15 -1
- package/dist/bridge/approval-coordinator.js +35 -2
- package/dist/bridge/index.d.ts +19 -6
- package/dist/bridge/index.js +25 -8
- package/dist/card/approval-render.js +187 -45
- 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 +28 -14
- 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 +20 -1
- package/dist/router/index.js +113 -7
- package/dist/router/utils.js +12 -2
- package/dist/runner/agent-choices-common.js +3 -0
- package/dist/runner/claude/session.js +24 -5
- 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/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 +191 -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 +69 -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 +2 -11
- 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 +63 -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 +39 -0
- package/dist/session/dsh/sessions.js +237 -0
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.js +1 -0
- package/dist/workspace/index.d.ts +4 -0
- package/dist/workspace/index.js +8 -1
- package/package.json +2 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DshSessionReader — AgentSessionReader backed by the DSH Web Host over HTTP.
|
|
3
|
+
*
|
|
4
|
+
* The AgentSessionReader contract is fully synchronous (file-style readers),
|
|
5
|
+
* but DSH exposes only async HTTP. The reader therefore bridges with
|
|
6
|
+
* `spawnSync('curl', ...)` for its unary calls (session.list / session.history)
|
|
7
|
+
* — a synchronous HTTP round-trip to the local DSH host. This keeps the
|
|
8
|
+
* router/bridge interface contract intact without touching any upstream logic.
|
|
9
|
+
*
|
|
10
|
+
* listSessions: session.list → filter by cwd → sort by updatedAt desc → local
|
|
11
|
+
* pagination (DSH v1 has no cursor pagination).
|
|
12
|
+
* readSessionContent: session.history → map SessionEvents to display events +
|
|
13
|
+
* session-wide usage accumulation (assistant/message.usage).
|
|
14
|
+
*/
|
|
15
|
+
import { spawnSync } from 'node:child_process';
|
|
16
|
+
import { getLogger } from '../../logger/index.js';
|
|
17
|
+
import { DEFAULT_DSH_HOST } from '../../config/index.js';
|
|
18
|
+
import { capEvents, paginate } from '../common/pagination.js';
|
|
19
|
+
import { truncateUtf8, TOOL_RESULT_MAX_BYTES } from '../../common/truncate.js';
|
|
20
|
+
/** Synchronous unary POST via curl (the reader interface is synchronous). */
|
|
21
|
+
function defaultSyncRequest(baseUrl, method, payload) {
|
|
22
|
+
const body = JSON.stringify({
|
|
23
|
+
type: 'client-request',
|
|
24
|
+
rpcId: `rpc-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`,
|
|
25
|
+
method,
|
|
26
|
+
payload,
|
|
27
|
+
});
|
|
28
|
+
const res = spawnSync('curl', [
|
|
29
|
+
'-s',
|
|
30
|
+
'-X',
|
|
31
|
+
'POST',
|
|
32
|
+
`${baseUrl}/api/${method}`,
|
|
33
|
+
'-H',
|
|
34
|
+
'content-type: application/json',
|
|
35
|
+
'--data',
|
|
36
|
+
body,
|
|
37
|
+
], { encoding: 'utf-8', timeout: 15_000, maxBuffer: 64 * 1024 * 1024 });
|
|
38
|
+
if (res.error || res.status !== 0) {
|
|
39
|
+
throw new Error(`DSH ${method} failed: ${(res.error?.message ?? res.stderr) || 'unknown'}`);
|
|
40
|
+
}
|
|
41
|
+
let parsed;
|
|
42
|
+
try {
|
|
43
|
+
parsed = JSON.parse(res.stdout);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
throw new Error(`DSH ${method} returned non-JSON: ${err.message}`, {
|
|
47
|
+
cause: err,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (parsed.result?.ok !== true) {
|
|
51
|
+
throw new Error(parsed.result?.error?.message ?? `DSH ${method} failed`);
|
|
52
|
+
}
|
|
53
|
+
return parsed.result.value;
|
|
54
|
+
}
|
|
55
|
+
export class DshSessionReader {
|
|
56
|
+
baseUrl;
|
|
57
|
+
syncRequest;
|
|
58
|
+
constructor(opts = {}) {
|
|
59
|
+
this.baseUrl = (opts.host ?? DEFAULT_DSH_HOST).replace(/\/$/, '');
|
|
60
|
+
this.syncRequest = opts.syncRequest ?? defaultSyncRequest;
|
|
61
|
+
}
|
|
62
|
+
unary(method, payload) {
|
|
63
|
+
return this.syncRequest(this.baseUrl, method, payload);
|
|
64
|
+
}
|
|
65
|
+
listSessions(cwd, opts) {
|
|
66
|
+
try {
|
|
67
|
+
const value = this.unary('session.list', {});
|
|
68
|
+
const items = value.items ?? [];
|
|
69
|
+
const filtered = items
|
|
70
|
+
.filter((i) => i.cwd === cwd)
|
|
71
|
+
.sort((a, b) => b.updatedAt - a.updatedAt || String(a.sessionId).localeCompare(String(b.sessionId)))
|
|
72
|
+
.map((i) => ({
|
|
73
|
+
sessionId: String(i.sessionId),
|
|
74
|
+
summary: '',
|
|
75
|
+
mtime: i.updatedAt,
|
|
76
|
+
...(i.agentPreset ? { agentPreset: i.agentPreset } : {}),
|
|
77
|
+
}));
|
|
78
|
+
const { items: page, total } = paginate(filtered, opts ?? {});
|
|
79
|
+
return { sessions: page, total };
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
getLogger().error(`[dsh-session-reader] listSessions error: ${error.message}`);
|
|
83
|
+
return { sessions: [], total: 0 };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
getNewestSession(cwd) {
|
|
87
|
+
return this.listSessions(cwd, { limit: 1 }).sessions[0] ?? null;
|
|
88
|
+
}
|
|
89
|
+
readSessionContent(sessionId, cwd, opts) {
|
|
90
|
+
try {
|
|
91
|
+
const value = this.unary('session.history', {
|
|
92
|
+
sessionId,
|
|
93
|
+
maxMessages: opts?.maxEvents ?? 50,
|
|
94
|
+
});
|
|
95
|
+
const events = [];
|
|
96
|
+
let displayTitle;
|
|
97
|
+
let cumInput = 0;
|
|
98
|
+
let cumOutput = 0;
|
|
99
|
+
let cumCacheRead = 0;
|
|
100
|
+
let cumCacheWrite = 0;
|
|
101
|
+
let lastUsage;
|
|
102
|
+
for (const entry of value.events ?? []) {
|
|
103
|
+
const ev = entry.event;
|
|
104
|
+
if (!ev)
|
|
105
|
+
continue;
|
|
106
|
+
const contentEvents = mapEvent(ev);
|
|
107
|
+
events.push(...contentEvents.events);
|
|
108
|
+
if (contentEvents.title)
|
|
109
|
+
displayTitle = contentEvents.title;
|
|
110
|
+
const u = contentEvents.usage;
|
|
111
|
+
if (u) {
|
|
112
|
+
// Cumulative (session-wide): sum across ALL assistant/message usage.
|
|
113
|
+
cumInput += u.inputTokens ?? 0;
|
|
114
|
+
cumOutput += u.outputTokens ?? 0;
|
|
115
|
+
cumCacheRead += u.cacheReadTokens ?? 0;
|
|
116
|
+
cumCacheWrite += u.cacheWriteTokens ?? 0;
|
|
117
|
+
// Per-turn "current window" fields reflect the LAST assistant/message
|
|
118
|
+
// with usage (ccusage/`last_token_usage` 口径, AGENTS.md §9.21) — a
|
|
119
|
+
// multi-turn session's current context water-mark is the last turn's,
|
|
120
|
+
// not the accumulated total.
|
|
121
|
+
lastUsage = u;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
let sessionUsage;
|
|
125
|
+
if (lastUsage) {
|
|
126
|
+
const input = lastUsage.inputTokens ?? 0;
|
|
127
|
+
const output = lastUsage.outputTokens ?? 0;
|
|
128
|
+
const cacheRead = lastUsage.cacheReadTokens ?? 0;
|
|
129
|
+
const cacheWrite = lastUsage.cacheWriteTokens ?? 0;
|
|
130
|
+
sessionUsage = {
|
|
131
|
+
inputTokens: input,
|
|
132
|
+
outputTokens: output,
|
|
133
|
+
contextLength: input + cacheRead + cacheWrite,
|
|
134
|
+
cacheReadTokens: cacheRead,
|
|
135
|
+
cacheCreationTokens: cacheWrite,
|
|
136
|
+
totalTokens: input + output + cacheRead + cacheWrite,
|
|
137
|
+
cumulativeTotalTokens: cumInput + cumOutput + cumCacheRead + cumCacheWrite,
|
|
138
|
+
cumulativeInputTokens: cumInput,
|
|
139
|
+
cumulativeOutputTokens: cumOutput,
|
|
140
|
+
cumulativeCacheReadTokens: cumCacheRead,
|
|
141
|
+
cumulativeCacheCreationTokens: cumCacheWrite,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
events: capEvents(events, opts?.maxEvents),
|
|
146
|
+
...(sessionUsage ? { usage: sessionUsage } : {}),
|
|
147
|
+
...(displayTitle ? { displayTitle } : {}),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
getLogger().error(`[dsh-session-reader] readSessionContent error: ${error.message}`);
|
|
152
|
+
return { events: [] };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
isSessionActive(sessionId, cwd) {
|
|
156
|
+
try {
|
|
157
|
+
const value = this.unary('session.list', {});
|
|
158
|
+
const found = (value.items ?? []).find((i) => String(i.sessionId) === sessionId && i.cwd === cwd);
|
|
159
|
+
return found?.running === true;
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
getLogger().error(`[dsh-session-reader] isSessionActive error: ${error.message}`);
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/** Map one DSH SessionEvent to display content events (+ title/usage side effects). */
|
|
168
|
+
function mapEvent(ev) {
|
|
169
|
+
const out = { events: [] };
|
|
170
|
+
switch (ev.type) {
|
|
171
|
+
case 'user/message': {
|
|
172
|
+
const content = ev.data.content ?? [];
|
|
173
|
+
const text = content
|
|
174
|
+
.filter((c) => c.type === 'text')
|
|
175
|
+
.map((c) => c.text ?? '')
|
|
176
|
+
.join('');
|
|
177
|
+
out.events.push({ type: 'user', content: text, timestamp: new Date(ev.time).toISOString() });
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case 'assistant/message': {
|
|
181
|
+
const content = ev.data.message
|
|
182
|
+
?.content ?? [];
|
|
183
|
+
const text = content
|
|
184
|
+
.filter((c) => c.type === 'text')
|
|
185
|
+
.map((c) => c.text ?? '')
|
|
186
|
+
.join('');
|
|
187
|
+
if (text)
|
|
188
|
+
out.events.push({
|
|
189
|
+
type: 'assistant',
|
|
190
|
+
content: text,
|
|
191
|
+
timestamp: new Date(ev.time).toISOString(),
|
|
192
|
+
});
|
|
193
|
+
const usage = ev.data.usage;
|
|
194
|
+
if (usage && typeof usage === 'object')
|
|
195
|
+
out.usage = usage;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case 'tool/call': {
|
|
199
|
+
const name = String(ev.data.name ?? '');
|
|
200
|
+
const args = typeof ev.data.arguments === 'string' ? ev.data.arguments : '';
|
|
201
|
+
out.events.push({
|
|
202
|
+
type: 'tool_use',
|
|
203
|
+
content: `${name}(${truncateUtf8(args, 200)})`,
|
|
204
|
+
timestamp: new Date(ev.time).toISOString(),
|
|
205
|
+
});
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case 'tool/result': {
|
|
209
|
+
const msg = ev.data.message;
|
|
210
|
+
const raw = stringifyContent(msg?.content);
|
|
211
|
+
out.events.push({
|
|
212
|
+
type: 'tool_result',
|
|
213
|
+
content: truncateUtf8(raw, TOOL_RESULT_MAX_BYTES),
|
|
214
|
+
timestamp: new Date(ev.time).toISOString(),
|
|
215
|
+
});
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
default:
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
function stringifyContent(content) {
|
|
224
|
+
if (content == null)
|
|
225
|
+
return '[tool result]';
|
|
226
|
+
if (typeof content === 'string')
|
|
227
|
+
return content;
|
|
228
|
+
if (Array.isArray(content)) {
|
|
229
|
+
return content
|
|
230
|
+
.map((c) => c && typeof c === 'object' && 'text' in c
|
|
231
|
+
? String(c.text)
|
|
232
|
+
: JSON.stringify(c))
|
|
233
|
+
.join('');
|
|
234
|
+
}
|
|
235
|
+
return JSON.stringify(content);
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=sessions.js.map
|
package/dist/session/index.d.ts
CHANGED
|
@@ -5,3 +5,4 @@ export { CodexSessionReader } from './codex/index.js';
|
|
|
5
5
|
export { OpencodeSessionReader } from './opencode/index.js';
|
|
6
6
|
export { PiSessionReader } from './pi/index.js';
|
|
7
7
|
export { KimiSessionReader } from './kimi/index.js';
|
|
8
|
+
export { DshSessionReader } from './dsh/index.js';
|
package/dist/session/index.js
CHANGED
|
@@ -8,4 +8,5 @@ export { CodexSessionReader } from './codex/index.js';
|
|
|
8
8
|
export { OpencodeSessionReader } from './opencode/index.js';
|
|
9
9
|
export { PiSessionReader } from './pi/index.js';
|
|
10
10
|
export { KimiSessionReader } from './kimi/index.js';
|
|
11
|
+
export { DshSessionReader } from './dsh/index.js';
|
|
11
12
|
//# sourceMappingURL=index.js.map
|
|
@@ -11,6 +11,10 @@ export interface WorkspaceEntry {
|
|
|
11
11
|
* mid-write cannot leave a truncated JSON file. On load, a corrupt file
|
|
12
12
|
* is treated as empty (with a warning) so the bridge can still start.
|
|
13
13
|
*
|
|
14
|
+
* lastUsedAt 语义(2026-08-19 语义对齐):保存(save)与使用(use/touch)
|
|
15
|
+
* 都会把时间戳更新为当前时间。save 内部调用 touch,保证「最近使用」排序
|
|
16
|
+
* 对新保存的 workspace 同样生效(新条目应排在列表最前,而非沉底)。
|
|
17
|
+
*
|
|
14
18
|
* Data migration: old format `{ alias: "path" }` is normalized to
|
|
15
19
|
* `{ alias: { path, lastUsedAt: 0 } }` on first load and persisted.
|
|
16
20
|
*/
|
package/dist/workspace/index.js
CHANGED
|
@@ -10,6 +10,10 @@ import { getLogger } from '../logger/index.js';
|
|
|
10
10
|
* mid-write cannot leave a truncated JSON file. On load, a corrupt file
|
|
11
11
|
* is treated as empty (with a warning) so the bridge can still start.
|
|
12
12
|
*
|
|
13
|
+
* lastUsedAt 语义(2026-08-19 语义对齐):保存(save)与使用(use/touch)
|
|
14
|
+
* 都会把时间戳更新为当前时间。save 内部调用 touch,保证「最近使用」排序
|
|
15
|
+
* 对新保存的 workspace 同样生效(新条目应排在列表最前,而非沉底)。
|
|
16
|
+
*
|
|
13
17
|
* Data migration: old format `{ alias: "path" }` is normalized to
|
|
14
18
|
* `{ alias: { path, lastUsedAt: 0 } }` on first load and persisted.
|
|
15
19
|
*/
|
|
@@ -60,8 +64,11 @@ export class WorkspaceStore {
|
|
|
60
64
|
atomicWriteJson(this.filePath, obj);
|
|
61
65
|
}
|
|
62
66
|
save(name, dirPath) {
|
|
67
|
+
// 保存即视为使用(语义对齐):新建/覆盖的 workspace 都应拿到当前
|
|
68
|
+
// lastUsedAt,而不是 0,否则「最近使用」排序下新条目会沉底。
|
|
69
|
+
// touch() 负责统一写入时间戳并 persist,消除 save 写 0 / use 写 now 的不对称。
|
|
63
70
|
this.data.set(name, { path: dirPath, lastUsedAt: 0 });
|
|
64
|
-
this.
|
|
71
|
+
this.touch(name);
|
|
65
72
|
}
|
|
66
73
|
get(name) {
|
|
67
74
|
return this.data.get(name)?.path;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lark-remote",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Feishu (Lark) private-chat bridge to local coding agents — Claude Code / Codex / opencode / pi / kimi, with live-streaming CardKit 2.0 cards",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"test:opencode": "vitest run --project agent-opencode",
|
|
48
48
|
"test:pi": "vitest run --project agent-pi",
|
|
49
49
|
"test:kimi": "vitest run --project agent-kimi",
|
|
50
|
+
"test:dsh": "vitest run --project agent-dsh",
|
|
50
51
|
"test:bash": "vitest run --project agent-bash",
|
|
51
52
|
"test:infra": "vitest run --project 'infra-*'",
|
|
52
53
|
"test:agents": "vitest run --project 'agent-*'",
|