dsh-lark-agent 0.3.2 → 0.4.0
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 +4 -4
- package/dist/bot-menu.d.ts +12 -0
- package/dist/bot-menu.js +14 -0
- package/dist/bot-service.js +4 -2
- package/dist/bridge.d.ts +25 -0
- package/dist/bridge.js +316 -57
- package/dist/card-actions.js +3 -1
- package/dist/client.cjs +23 -1
- package/dist/config.d.ts +2 -1
- package/dist/config.js +2 -2
- package/dist/dsh-runtime.js +2 -0
- package/dist/lark-cli.d.ts +13 -1
- package/dist/lark-cli.js +63 -1
- package/dist/live-reply.js +8 -5
- package/dist/onboarding/controller.d.ts +2 -0
- package/dist/onboarding/controller.js +7 -1
- package/dist/onboarding/host.js +5 -17
- package/dist/onboarding/lark-platform.d.ts +2 -0
- package/dist/onboarding/lark-platform.js +5 -0
- package/dist/onboarding/model-check.d.ts +5 -0
- package/dist/onboarding/model-check.js +51 -0
- package/dist/onboarding/protocol.d.ts +9 -0
- package/dist/replies.d.ts +16 -0
- package/dist/replies.js +37 -11
- package/dist/session-controls.d.ts +34 -0
- package/dist/session-controls.js +66 -0
- package/dist/session-store.d.ts +2 -0
- package/dist/session-store.js +12 -7
- package/dist/topic-cards.d.ts +3 -0
- package/dist/topic-cards.js +8 -0
- package/dist/topic-store.d.ts +40 -0
- package/dist/topic-store.js +192 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,8 +30,6 @@ Before you start:
|
|
|
30
30
|
|
|
31
31
|
In the sidebar, open **插件 → 添加插件** (Plugins → Add plugin), enter `dsh-lark-agent`, click **安装** (Install), then **立即启用** (Enable now). Restart if the app asks.
|
|
32
32
|
|
|
33
|
-
<img src="https://raw.githubusercontent.com/Nirvana-Jie/dsh-lark-agent/main/assets/readme/desktop-install-02.jpg" width="720" alt="The Add plugin dialog in the DeepSeek Harness desktop app">
|
|
34
|
-
|
|
35
33
|
### Install with the `dsh` CLI
|
|
36
34
|
|
|
37
35
|
[`dsh plugin`](https://www.npmjs.com/package/@deepseek-ai/dsh) runs pnpm, so pnpm must be on your PATH:
|
|
@@ -68,7 +66,7 @@ Button labels are quoted in Chinese, as they appear, with an English gloss.
|
|
|
68
66
|
3. Click **打开飞书私聊** (open private chat) and, from the account that scanned, send the pairing word (`DSH-…`, valid for 10 minutes) as a plain-text message on its own, exactly as shown. If asked, confirm with **这是我,完成连接** (this is me).
|
|
69
67
|
4. When the page shows **已连接** (connected), send the bot a message.
|
|
70
68
|
|
|
71
|
-
The Feishu entry works only while the host is online: not when the app is closed, the web host is stopped, or the computer sleeps.
|
|
69
|
+
The connection's model check does not create a “飞书连接测试” conversation in Harness. The Feishu entry works only while the host is online: not when the app is closed, the web host is stopped, or the computer sleeps.
|
|
72
70
|
|
|
73
71
|
## What it can do
|
|
74
72
|
|
|
@@ -80,7 +78,7 @@ The Feishu entry works only while the host is online: not when the app is closed
|
|
|
80
78
|
| Use `/permission`, or **设置 → 权限模式** (Settings → Permission mode) in the bottom menu | Switches between read-only, workspace edits and full access, like the app's permission selector; full access stops asking. The card you tap updates in place and turns green when done |
|
|
81
79
|
| Ask for the result file | Sends files from its workspace straight to the chat |
|
|
82
80
|
| Say "lead with the conclusion from now on" | Remembers the preference |
|
|
83
|
-
|
|
|
81
|
+
| Continue inside a topic, or use `/sessions`, `/new` | Keeps each conversation in its own native Feishu topic; reply beneath a new topic's entry card, or use the searchable, paged dropdown to reopen an earlier topic |
|
|
84
82
|
| Ask for a bar chart of some numbers | Replies with a native Feishu chart card |
|
|
85
83
|
| Ask it to let you pick between options first | Sends a form card and continues once you submit |
|
|
86
84
|
| Ask for an architecture diagram or a report | Creates a Feishu doc (with a whiteboard for diagrams) shared read-only with you alone; in App `0.2.1-rc.2` / `rc.3`, opening that Feishu session in the app also shows a local preview |
|
|
@@ -88,6 +86,8 @@ The Feishu entry works only while the host is online: not when the app is closed
|
|
|
88
86
|
|
|
89
87
|
By default it answers only your own private chat. In that chat the assistant is your Harness itself: the tools it can use and the actions that need your confirmation follow the permission settings on your computer. Sessions and memory stay on the computer running Harness; messages pass through Feishu and your model provider.
|
|
90
88
|
|
|
89
|
+
A new request in the main chat opens a topic; continue inside that topic to keep its context. Authorized group chats work the same way and still require an @mention on each message. `/new` opens another topic and keeps the earlier ones available.
|
|
90
|
+
|
|
91
91
|
## Good to know
|
|
92
92
|
|
|
93
93
|
- **More docs**: commands, privacy, upgrades, the chat bottom menu, team deployment and troubleshooting are in [DEVELOPMENT.md](https://github.com/Nirvana-Jie/dsh-lark-agent/blob/main/DEVELOPMENT.md) (Chinese).
|
package/dist/bot-menu.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ export declare const BOT_MENU_COMMANDS: Readonly<{
|
|
|
10
10
|
}>;
|
|
11
11
|
export type BotMenuCommand = (typeof BOT_MENU_COMMANDS)[keyof typeof BOT_MENU_COMMANDS];
|
|
12
12
|
export declare function botMenuCommand(eventKey: unknown): BotMenuCommand | undefined;
|
|
13
|
+
/** A menu entry as the owner adds it in the developer console. */
|
|
14
|
+
export interface BotMenuEntry {
|
|
15
|
+
label: string;
|
|
16
|
+
eventKey: string;
|
|
17
|
+
parent?: string;
|
|
18
|
+
}
|
|
13
19
|
export interface BotMenuNode {
|
|
14
20
|
menu_id: string;
|
|
15
21
|
parent_menu_id?: string;
|
|
@@ -32,6 +38,12 @@ export declare function buildBotMenuAbility(): {
|
|
|
32
38
|
bot_menus: BotMenuNode[];
|
|
33
39
|
};
|
|
34
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* The entries a published menu lacks, by label. A menu configured by hand
|
|
43
|
+
* before a release added an entry stays without it; none is reported when
|
|
44
|
+
* no dsh entry was configured at all.
|
|
45
|
+
*/
|
|
46
|
+
export declare function missingBotMenuEntries(eventKeys: readonly string[]): BotMenuEntry[];
|
|
35
47
|
/** Pure review/export plan: this module never invokes an API or changes an app. */
|
|
36
48
|
export declare function buildBotMenuPlan(appId: string): {
|
|
37
49
|
scope: string;
|
package/dist/bot-menu.js
CHANGED
|
@@ -29,6 +29,20 @@ export function buildBotMenuAbility() {
|
|
|
29
29
|
{ menu_id: 'dsh_permission', parent_menu_id: 'dsh_settings', sort: 2, default_name: '权限模式', menu_content_type: 2, event_key: 'dsh.permission' },
|
|
30
30
|
] } };
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The entries a published menu lacks, by label. A menu configured by hand
|
|
34
|
+
* before a release added an entry stays without it; none is reported when
|
|
35
|
+
* no dsh entry was configured at all.
|
|
36
|
+
*/
|
|
37
|
+
export function missingBotMenuEntries(eventKeys) {
|
|
38
|
+
const nodes = buildBotMenuAbility().bot.bot_menus, entries = nodes.filter(node => node.event_key);
|
|
39
|
+
if (!entries.some(node => eventKeys.includes(node.event_key)))
|
|
40
|
+
return [];
|
|
41
|
+
return entries.filter(node => !eventKeys.includes(node.event_key)).map(node => {
|
|
42
|
+
const parent = nodes.find(candidate => candidate.menu_id === node.parent_menu_id)?.default_name;
|
|
43
|
+
return { label: node.default_name, eventKey: node.event_key, ...(parent ? { parent } : {}) };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
32
46
|
/** Pure review/export plan: this module never invokes an API or changes an app. */
|
|
33
47
|
export function buildBotMenuPlan(appId) {
|
|
34
48
|
if (typeof appId !== 'string' || !/^cli_[A-Za-z0-9_-]{1,128}$/.test(appId))
|
package/dist/bot-service.js
CHANGED
|
@@ -14,6 +14,7 @@ import { ModelPreferencesStore } from './model-preferences.js';
|
|
|
14
14
|
import { ArtifactStore } from './artifacts.js';
|
|
15
15
|
import { LarkArtifactPublisher } from './artifact-publisher.js';
|
|
16
16
|
import { PermissionPreferencesStore } from './permission-preferences.js';
|
|
17
|
+
import { TopicStore } from './topic-store.js';
|
|
17
18
|
/** A failed close is still an owned service, never a disposable startup error. */
|
|
18
19
|
export class BotServiceStartError extends Error {
|
|
19
20
|
service;
|
|
@@ -38,6 +39,7 @@ export async function createBotService(ctx, config, onHealth = () => undefined,
|
|
|
38
39
|
config.cardInteractions ? CardActions.open(config.stateDir, JSON.stringify([config.profile, config.botOpenId])) : undefined,
|
|
39
40
|
DirectChatsStore.open(config.stateDir), ModelPreferencesStore.open(config.stateDir), ArtifactStore.open(config.stateDir),
|
|
40
41
|
PermissionPreferencesStore.open(config.stateDir),
|
|
42
|
+
TopicStore.open(config.stateDir),
|
|
41
43
|
];
|
|
42
44
|
// An opener may create signing state: finish all openers before releasing
|
|
43
45
|
// ownership after a sibling fails, so no old writer outlives the lock.
|
|
@@ -45,7 +47,7 @@ export async function createBotService(ctx, config, onHealth = () => undefined,
|
|
|
45
47
|
const failed = openedStores.find(result => result.status === 'rejected');
|
|
46
48
|
if (failed?.status === 'rejected')
|
|
47
49
|
throw failed.reason;
|
|
48
|
-
const [sessions, memories, actions, directChats, modelPreferences, artifacts, permissionPreferences] = await Promise.all(openingStores);
|
|
50
|
+
const [sessions, memories, actions, directChats, modelPreferences, artifacts, permissionPreferences, topics] = await Promise.all(openingStores);
|
|
49
51
|
if (startup?.directChat) {
|
|
50
52
|
const { openId, chatId } = startup.directChat;
|
|
51
53
|
if (!config.users.some(user => user.openId === openId && user.direct))
|
|
@@ -61,7 +63,7 @@ export async function createBotService(ctx, config, onHealth = () => undefined,
|
|
|
61
63
|
? new LarkCli({ profile: config.profile, executable: config.executable, eventKey: 'application.bot.menu_v6' }) : undefined;
|
|
62
64
|
const progressTransport = config.loadingReaction
|
|
63
65
|
? new LarkCli({ profile: config.profile, executable: config.executable, commandTimeoutMs: 5_000 }) : undefined;
|
|
64
|
-
const bridge = new BotBridge(config, new DshRuntime(ctx), transport, ledger, { sessions, memories, actions, directChats, modelPreferences, artifacts, permissionPreferences }, event => console.info(`[im-larkbot] ${JSON.stringify(event)}`), progressTransport, new LarkArtifactPublisher(config.profile, config.botOpenId, { executable: config.executable }));
|
|
66
|
+
const bridge = new BotBridge(config, new DshRuntime(ctx), transport, ledger, { sessions, memories, actions, directChats, modelPreferences, artifacts, permissionPreferences, topics }, event => console.info(`[im-larkbot] ${JSON.stringify(event)}`), progressTransport, new LarkArtifactPublisher(config.profile, config.botOpenId, { executable: config.executable }));
|
|
65
67
|
let stopped = false, started = false, healthy = false;
|
|
66
68
|
const readiness = { messages: false, cards: !callbacks, menu: !menu };
|
|
67
69
|
let stopping;
|
package/dist/bridge.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { type ModelPreferencesStore } from './model-preferences.js';
|
|
|
10
10
|
import type { PermissionPreferencesStore } from './permission-preferences.js';
|
|
11
11
|
import type { ArtifactStore } from './artifacts.js';
|
|
12
12
|
import type { ArtifactPublisher } from './artifact-publisher.js';
|
|
13
|
+
import { type TopicStore, type TopicReceipt } from './topic-store.js';
|
|
13
14
|
export interface Runtime {
|
|
14
15
|
create(policy: AgentPolicy): Promise<RuntimeSession>;
|
|
15
16
|
resume(policy: AgentPolicy, id: string): Promise<RuntimeSession>;
|
|
@@ -32,6 +33,7 @@ export interface BotState {
|
|
|
32
33
|
};
|
|
33
34
|
artifacts?: Pick<ArtifactStore, 'add' | 'get' | 'list' | 'claimPublish' | 'publication' | 'submit'>;
|
|
34
35
|
permissionPreferences?: Pick<PermissionPreferencesStore, 'get' | 'set'>;
|
|
36
|
+
topics?: Pick<TopicStore, 'get' | 'list' | 'record' | 'occupied'>;
|
|
35
37
|
}
|
|
36
38
|
export interface Transport {
|
|
37
39
|
reply(messageId: string, text: string, options: {
|
|
@@ -69,6 +71,15 @@ export interface Transport {
|
|
|
69
71
|
inThread: boolean;
|
|
70
72
|
image: boolean;
|
|
71
73
|
}): Promise<void>;
|
|
74
|
+
openTopic?(target: {
|
|
75
|
+
chatId: string;
|
|
76
|
+
messageId?: string;
|
|
77
|
+
}, title: string, options: {
|
|
78
|
+
idempotencyKey: string;
|
|
79
|
+
chatType?: 'p2p' | 'group';
|
|
80
|
+
textOnly?: boolean;
|
|
81
|
+
}): Promise<TopicReceipt>;
|
|
82
|
+
topicLink?(messageId: string): Promise<string | undefined>;
|
|
72
83
|
}
|
|
73
84
|
export type Audit = (event: {
|
|
74
85
|
stage: string;
|
|
@@ -97,6 +108,8 @@ export declare class BotBridge {
|
|
|
97
108
|
private readonly progress;
|
|
98
109
|
private readonly interactions;
|
|
99
110
|
private liveUnavailableUntil;
|
|
111
|
+
private readonly topicCreations;
|
|
112
|
+
private readonly topicResumes;
|
|
100
113
|
constructor(config: BotConfig, runtime: Runtime, transport: Transport, ledger: MessageLedger, state: BotState, audit?: Audit, progressTransport?: ProgressTransport, artifactPublisher?: ArtifactPublisher | undefined);
|
|
101
114
|
setCardActionsReady(ready: boolean): void;
|
|
102
115
|
private parseIncoming;
|
|
@@ -131,8 +144,19 @@ export declare class BotBridge {
|
|
|
131
144
|
/** Redraw a stale card with the current state, without running its command. */
|
|
132
145
|
private refreshPanel;
|
|
133
146
|
private commandFeedback;
|
|
147
|
+
private topicsEnabled;
|
|
148
|
+
private topicScope;
|
|
149
|
+
private newTopicKey;
|
|
150
|
+
private entryKey;
|
|
151
|
+
private reserveTopic;
|
|
152
|
+
private createTopic;
|
|
134
153
|
private context;
|
|
135
154
|
private sessionContext;
|
|
155
|
+
private catalogContext;
|
|
156
|
+
/** Only this user's chat and current disclosure epoch participate in the picker. */
|
|
157
|
+
private topicSessions;
|
|
158
|
+
private continueTopic;
|
|
159
|
+
private topicNavigation;
|
|
136
160
|
private policy;
|
|
137
161
|
private ensureSession;
|
|
138
162
|
private bindRestoredSession;
|
|
@@ -154,6 +178,7 @@ export declare class BotBridge {
|
|
|
154
178
|
private permissionCommand;
|
|
155
179
|
/** Bring a newly opened harness session to the chat's choice, or a new one without a choice to the App's default. */
|
|
156
180
|
private applyPermission;
|
|
181
|
+
private permissionContext;
|
|
157
182
|
/** A preset changed in the App on this chat's live session becomes the chat's choice, so new sessions keep it. */
|
|
158
183
|
private adoptPermission;
|
|
159
184
|
private modelState;
|