dsh-lark-agent 0.2.0 → 0.3.1
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/README.md +8 -6
- package/dist/bot-menu.d.ts +2 -1
- package/dist/bot-menu.js +3 -2
- package/dist/bridge.d.ts +30 -1
- package/dist/bridge.js +269 -49
- package/dist/client/ConnectionCard.d.ts +6 -1
- package/dist/client/index.d.ts +2 -0
- package/dist/client.cjs +77 -7
- package/dist/dsh-runtime.d.ts +20 -2
- package/dist/dsh-runtime.js +113 -11
- package/dist/interaction-cards.d.ts +8 -3
- package/dist/interaction-cards.js +4 -4
- package/dist/live-reply.d.ts +23 -9
- package/dist/live-reply.js +183 -54
- package/dist/model-controls.d.ts +9 -1
- package/dist/model-controls.js +45 -20
- package/dist/onboarding/host.d.ts +5 -1
- package/dist/onboarding/host.js +30 -3
- package/dist/onboarding/protocol.d.ts +5 -0
- package/dist/onboarding/updates.d.ts +23 -0
- package/dist/onboarding/updates.js +67 -0
- package/dist/permission-controls.d.ts +8 -1
- package/dist/permission-controls.js +18 -7
- package/dist/replies.d.ts +10 -0
- package/dist/replies.js +18 -5
- package/package.json +7 -7
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** True when `next` is a later release than `current`; a prerelease comes before its release. */
|
|
2
|
+
export declare function newerVersion(next: string, current: string): boolean;
|
|
3
|
+
/** npm's `latest` tag for a package, when it names a stable release. Sends only the package name. */
|
|
4
|
+
export declare function npmLatest(name: string, signal: AbortSignal): Promise<string | undefined>;
|
|
5
|
+
/**
|
|
6
|
+
* Whether a newer release of this plugin is available, for the settings page.
|
|
7
|
+
* A status read never waits for the registry: it returns what is known and
|
|
8
|
+
* starts a background check when one is due. Failures just show no notice.
|
|
9
|
+
*/
|
|
10
|
+
export declare class UpdateCheck {
|
|
11
|
+
private readonly current;
|
|
12
|
+
private readonly lookup;
|
|
13
|
+
private readonly now;
|
|
14
|
+
private latest?;
|
|
15
|
+
private nextCheck;
|
|
16
|
+
private running;
|
|
17
|
+
constructor(current: string | undefined, lookup: (signal: AbortSignal) => Promise<string | undefined>, now?: () => number);
|
|
18
|
+
status(): {
|
|
19
|
+
current: string;
|
|
20
|
+
latest: string;
|
|
21
|
+
} | undefined;
|
|
22
|
+
private refresh;
|
|
23
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Read only while the settings page polls, and at most this often once a check succeeds.
|
|
2
|
+
const CHECK_EVERY_MS = 6 * 60 * 60_000;
|
|
3
|
+
// A failed check (offline, blocked registry) waits this long before the next try.
|
|
4
|
+
const RETRY_AFTER_MS = 30 * 60_000;
|
|
5
|
+
const VERSION = /^(\d{1,9})\.(\d{1,9})\.(\d{1,9})(?:-([0-9A-Za-z.-]{1,40}))?$/;
|
|
6
|
+
/** True when `next` is a later release than `current`; a prerelease comes before its release. */
|
|
7
|
+
export function newerVersion(next, current) {
|
|
8
|
+
const a = VERSION.exec(next), b = VERSION.exec(current);
|
|
9
|
+
if (!a || !b)
|
|
10
|
+
return false;
|
|
11
|
+
for (let index = 1; index <= 3; index++) {
|
|
12
|
+
const left = Number(a[index]), right = Number(b[index]);
|
|
13
|
+
if (left !== right)
|
|
14
|
+
return left > right;
|
|
15
|
+
}
|
|
16
|
+
return !a[4] && Boolean(b[4]);
|
|
17
|
+
}
|
|
18
|
+
/** npm's `latest` tag for a package, when it names a stable release. Sends only the package name. */
|
|
19
|
+
export async function npmLatest(name, signal) {
|
|
20
|
+
const response = await fetch(`https://registry.npmjs.org/-/package/${name.replace('/', '%2f')}/dist-tags`, { signal, headers: { accept: 'application/json' } });
|
|
21
|
+
if (!response.ok)
|
|
22
|
+
return;
|
|
23
|
+
const tags = await response.json();
|
|
24
|
+
const latest = tags?.latest;
|
|
25
|
+
return typeof latest === 'string' && VERSION.test(latest) && !latest.includes('-') ? latest : undefined;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Whether a newer release of this plugin is available, for the settings page.
|
|
29
|
+
* A status read never waits for the registry: it returns what is known and
|
|
30
|
+
* starts a background check when one is due. Failures just show no notice.
|
|
31
|
+
*/
|
|
32
|
+
export class UpdateCheck {
|
|
33
|
+
current;
|
|
34
|
+
lookup;
|
|
35
|
+
now;
|
|
36
|
+
latest;
|
|
37
|
+
nextCheck = 0;
|
|
38
|
+
running = false;
|
|
39
|
+
constructor(current, lookup, now = Date.now) {
|
|
40
|
+
this.current = current;
|
|
41
|
+
this.lookup = lookup;
|
|
42
|
+
this.now = now;
|
|
43
|
+
}
|
|
44
|
+
status() {
|
|
45
|
+
if (!this.current || !VERSION.test(this.current))
|
|
46
|
+
return;
|
|
47
|
+
if (!this.running && this.now() >= this.nextCheck)
|
|
48
|
+
void this.refresh();
|
|
49
|
+
return this.latest && newerVersion(this.latest, this.current) ? { current: this.current, latest: this.latest } : undefined;
|
|
50
|
+
}
|
|
51
|
+
async refresh() {
|
|
52
|
+
this.running = true;
|
|
53
|
+
this.nextCheck = this.now() + RETRY_AFTER_MS;
|
|
54
|
+
try {
|
|
55
|
+
const latest = await this.lookup(AbortSignal.timeout(10_000));
|
|
56
|
+
if (latest && VERSION.test(latest)) {
|
|
57
|
+
this.latest = latest;
|
|
58
|
+
this.nextCheck = this.now() + CHECK_EVERY_MS;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch { /* Offline or blocked: no notice, and a later poll tries again. */ }
|
|
62
|
+
finally {
|
|
63
|
+
this.running = false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=updates.js.map
|
|
@@ -5,9 +5,16 @@ export interface PermissionView {
|
|
|
5
5
|
options: PermissionOption[];
|
|
6
6
|
current?: string;
|
|
7
7
|
}
|
|
8
|
+
/** Where the card is: idle, switching to an option, switched, refused with a reason, or refreshed with a notice. */
|
|
9
|
+
export interface PanelState {
|
|
10
|
+
changed?: boolean;
|
|
11
|
+
pending?: string;
|
|
12
|
+
error?: string;
|
|
13
|
+
notice?: string;
|
|
14
|
+
}
|
|
8
15
|
/** Presets that let Harness act without asking get a second confirmation. */
|
|
9
16
|
export declare const unattended: (id: string) => boolean;
|
|
10
17
|
export declare function permissionStateText(view: PermissionView): string;
|
|
11
18
|
type Issue = (label: string, command: string, primary: boolean, confirm?: ReplyAction['confirm']) => ReplyAction | undefined;
|
|
12
|
-
export declare function permissionPanel(view: PermissionView, issue: Issue,
|
|
19
|
+
export declare function permissionPanel(view: PermissionView, issue: Issue, state?: PanelState): ReplyPanel;
|
|
13
20
|
export {};
|
|
@@ -3,24 +3,35 @@ export const unattended = (id) => /full|danger|auto/.test(id);
|
|
|
3
3
|
function currentName(view) {
|
|
4
4
|
if (!view.current)
|
|
5
5
|
return '跟随电脑上的默认设置';
|
|
6
|
-
return view
|
|
6
|
+
return optionName(view, view.current) ?? '自定义(在电脑上调整过)';
|
|
7
7
|
}
|
|
8
|
+
const optionName = (view, id) => view.options.find(option => option.id === id)?.name;
|
|
8
9
|
export function permissionStateText(view) {
|
|
9
10
|
return `当前权限模式:${currentName(view)}。\n可选:${view.options.map(option => option.name).join('、')}。只影响这个私聊里的对话。`;
|
|
10
11
|
}
|
|
11
|
-
export function permissionPanel(view, issue,
|
|
12
|
+
export function permissionPanel(view, issue, state = {}) {
|
|
12
13
|
const actions = [];
|
|
13
14
|
for (const option of view.options) {
|
|
14
15
|
const current = option.id === view.current;
|
|
15
|
-
|
|
16
|
+
// While a switch runs nothing is clickable; afterwards the current option is shown, not offered.
|
|
17
|
+
if (state.pending || current) {
|
|
18
|
+
actions.push({ label: `${option.id === state.pending ? '⏳ ' : current ? '✓ ' : ''}${option.name}`, value: {}, disabled: true });
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const action = issue(option.name, `/permission use ${option.id}`, false, unattended(option.id) ? { title: `切换到「${option.name}」?`,
|
|
16
22
|
text: '之后 Harness 执行命令、修改文件都不再询问,在飞书发一句话就能直接操作这台电脑。随时可以切回。' } : undefined);
|
|
17
23
|
if (action)
|
|
18
24
|
actions.push(action);
|
|
19
25
|
}
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
const pendingName = state.pending ? optionName(view, state.pending) ?? state.pending : undefined;
|
|
27
|
+
const status = state.error ? { text: state.error, tone: 'error' }
|
|
28
|
+
: pendingName ? { text: `正在切换到「${pendingName}」…`, tone: 'pending' }
|
|
29
|
+
: state.changed ? { text: `已切换到「${currentName(view)}」`, tone: 'success' }
|
|
30
|
+
: { text: state.notice ? `${state.notice}\n当前:${currentName(view)}` : `当前:${currentName(view)}` };
|
|
31
|
+
return { title: state.changed && !state.error ? '权限模式已更新' : '权限模式', subtitle: '只影响这个私聊里的对话',
|
|
32
|
+
tone: status.tone ?? 'normal', status, summary: status.text,
|
|
33
|
+
sections: view.options.filter(option => option.description).map(option => ({ title: option.name, text: option.description })),
|
|
23
34
|
actions, actionColumns: 1,
|
|
24
|
-
note: '需要确认的操作由这里的模式决定;电脑上其他会话和默认设置不受影响。' };
|
|
35
|
+
note: state.pending ? '完成后会在这张卡片上更新,无需重复点击。' : '需要确认的操作由这里的模式决定;电脑上其他会话和默认设置不受影响。' };
|
|
25
36
|
}
|
|
26
37
|
//# sourceMappingURL=permission-controls.js.map
|
package/dist/replies.d.ts
CHANGED
|
@@ -17,11 +17,21 @@ export interface ReplyAction {
|
|
|
17
17
|
title: string;
|
|
18
18
|
text: string;
|
|
19
19
|
};
|
|
20
|
+
/** Shown but not clickable, such as the current choice or every option while a switch runs. */
|
|
21
|
+
disabled?: boolean;
|
|
20
22
|
}
|
|
23
|
+
/** normal: blue; pending: a switch is running; success: green; error: red; muted: closed or expired. */
|
|
24
|
+
export type PanelTone = 'normal' | 'pending' | 'success' | 'error' | 'muted';
|
|
21
25
|
export interface ReplyPanel {
|
|
22
26
|
title: string;
|
|
23
27
|
summary: string;
|
|
24
28
|
subtitle?: string;
|
|
29
|
+
tone?: PanelTone;
|
|
30
|
+
/** One highlighted line under the header: the current choice, a running switch, or its result. */
|
|
31
|
+
status?: {
|
|
32
|
+
text: string;
|
|
33
|
+
tone?: PanelTone;
|
|
34
|
+
};
|
|
25
35
|
metrics?: {
|
|
26
36
|
label: string;
|
|
27
37
|
value: string;
|
package/dist/replies.js
CHANGED
|
@@ -157,15 +157,27 @@ const column = (elements, extra = {}) => ({
|
|
|
157
157
|
tag: 'column', width: 'weighted', weight: 1, direction: 'vertical',
|
|
158
158
|
vertical_spacing: '4px', padding: '0px', margin: '0px', elements, ...extra,
|
|
159
159
|
});
|
|
160
|
+
const TONES = {
|
|
161
|
+
normal: { header: 'blue', background: 'grey-50', icon: 'info_outlined', color: 'grey' },
|
|
162
|
+
pending: { header: 'wathet', background: 'blue-50', icon: 'loading_outlined', color: 'blue' },
|
|
163
|
+
success: { header: 'green', background: 'green-50', icon: 'done_outlined', color: 'green' },
|
|
164
|
+
error: { header: 'red', background: 'red-50', icon: 'warning_outlined', color: 'red' },
|
|
165
|
+
muted: { header: 'grey', background: 'grey-50', icon: 'time_outlined', color: 'grey' },
|
|
166
|
+
};
|
|
160
167
|
/** Trusted application panels only. Model strings never choose components or callback values. */
|
|
161
168
|
export function renderPanel(panel) {
|
|
162
169
|
const metrics = (panel.metrics ?? []).filter(metric => metric.label.trim() || metric.value.trim());
|
|
163
|
-
const fallbackText = safePlainText([panel.title, panel.subtitle,
|
|
170
|
+
const fallbackText = safePlainText([panel.title, panel.subtitle, panel.status?.text,
|
|
164
171
|
...metrics.map(metric => [`${metric.label}:${metric.value}`, metric.note].filter(Boolean).join('\n')),
|
|
165
172
|
...panel.sections.map(section => [section.title, section.text].filter(Boolean).join('\n')), panel.note]
|
|
166
173
|
.filter(Boolean).join('\n\n'));
|
|
167
174
|
const elements = [];
|
|
168
175
|
const actions = panel.actions ?? [];
|
|
176
|
+
if (panel.status?.text.trim()) {
|
|
177
|
+
const tone = TONES[panel.status.tone ?? 'normal'];
|
|
178
|
+
elements.push({ tag: 'column_set', flex_mode: 'none', margin: '0px', columns: [column([{ ...md(panel.status.text),
|
|
179
|
+
icon: { tag: 'standard_icon', token: tone.icon, color: tone.color } }], { background_style: tone.background, padding: '8px 12px 8px 12px' })] });
|
|
180
|
+
}
|
|
169
181
|
for (let start = 0; start < metrics.length; start += 2) {
|
|
170
182
|
elements.push({ tag: 'column_set', flex_mode: 'none', horizontal_spacing: '8px', margin: '0px',
|
|
171
183
|
columns: metrics.slice(start, start + 2).map(metric => column([
|
|
@@ -206,8 +218,8 @@ export function renderPanel(panel) {
|
|
|
206
218
|
for (let start = 0; start < actions.length; start += actionColumns) {
|
|
207
219
|
elements.push({ tag: 'column_set', flex_mode: panel.actionColumns ? 'none' : 'flow', horizontal_spacing: '8px', margin: '0px', columns: actions.slice(start, start + actionColumns).map(action => column([
|
|
208
220
|
{ tag: 'button', text: title(plainLabel(action.label, panel.actionColumns === 1 ? 64 : 24)), type: action.primary ? 'primary' : 'default', width: 'fill', margin: '0px',
|
|
209
|
-
behaviors: [{ type: 'callback', value: { ...action.value } }],
|
|
210
|
-
...(action.confirm ? {
|
|
221
|
+
...(action.disabled ? { disabled: true } : { behaviors: [{ type: 'callback', value: { ...action.value } }] }),
|
|
222
|
+
...(action.confirm && !action.disabled ? {
|
|
211
223
|
icon: { tag: 'standard_icon', token: 'warning_outlined' },
|
|
212
224
|
confirm: { title: title(plainLabel(action.confirm.title, 80)), text: title(plainLabel(action.confirm.text, 300)) },
|
|
213
225
|
} : {}),
|
|
@@ -222,9 +234,10 @@ export function renderPanel(panel) {
|
|
|
222
234
|
return { kind: 'text', text: fallbackText };
|
|
223
235
|
const card = {
|
|
224
236
|
schema: '2.0',
|
|
225
|
-
|
|
237
|
+
// Shared from the start, so a click can update this same card in place.
|
|
238
|
+
config: { update_multi: true, compact_width: false, width_mode: 'default', enable_forward: false, enable_forward_interaction: false,
|
|
226
239
|
enable_markdown_autolink: false, summary: { content: summary } },
|
|
227
|
-
header: { template: '
|
|
240
|
+
header: { template: TONES[panel.tone ?? 'normal'].header, title: title(plainLabel(panel.title, 80)), padding: '10px 16px 10px 16px',
|
|
228
241
|
...(panel.subtitle?.trim() ? { subtitle: title(safePlainText(panel.subtitle)), vertical_spacing: '4px' } : {}) },
|
|
229
242
|
body: { direction: 'vertical', vertical_spacing: '8px', padding: '12px 16px 12px 16px', elements },
|
|
230
243
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-lark-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Reach your DeepSeek Harness assistant from Feishu / Lark: a dsh plugin built on the official Lark CLI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek",
|
|
@@ -73,13 +73,13 @@
|
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
75
|
"@deepseek-ai/cordis": "4.0.1 || 4.0.5",
|
|
76
|
-
"@deepseek-ai/dsh-agent": "0.1.1-rc.2 || 0.2.1-rc.2",
|
|
77
|
-
"@deepseek-ai/dsh-llm": "0.1.1-rc.2 || 0.2.1-rc.2",
|
|
78
|
-
"@deepseek-ai/dsh-session": "0.1.1-rc.2 || 0.2.1-rc.2",
|
|
79
|
-
"@deepseek-ai/dsh-system-prompt": "0.1.1-rc.2 || 0.2.1-rc.2",
|
|
80
|
-
"@deepseek-ai/dsh-tools": "0.1.1-rc.2 || 0.2.1-rc.2",
|
|
76
|
+
"@deepseek-ai/dsh-agent": "0.1.1-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.3",
|
|
77
|
+
"@deepseek-ai/dsh-llm": "0.1.1-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.3",
|
|
78
|
+
"@deepseek-ai/dsh-session": "0.1.1-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.3",
|
|
79
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.1-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.3",
|
|
80
|
+
"@deepseek-ai/dsh-tools": "0.1.1-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.3",
|
|
81
81
|
"@deepseek-ai/schemastery": "3.18.1 || 3.18.5",
|
|
82
|
-
"@deepseek-ai/dsh-session-persistence": "0.1.1-rc.2 || 0.2.1-rc.2"
|
|
82
|
+
"@deepseek-ai/dsh-session-persistence": "0.1.1-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.3"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@deepseek-ai/cordis": "4.0.1",
|