dsh-lark-agent 0.1.2 → 0.3.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 +10 -6
- package/dist/bot-menu.d.ts +3 -1
- package/dist/bot-menu.js +4 -1
- package/dist/bot-service.js +4 -2
- package/dist/bridge.d.ts +71 -1
- package/dist/bridge.js +588 -46
- package/dist/card-actions.js +1 -1
- package/dist/client.cjs +3 -3
- package/dist/config.d.ts +8 -0
- package/dist/config.js +8 -2
- package/dist/dsh-runtime.d.ts +94 -3
- package/dist/dsh-runtime.js +450 -25
- package/dist/interaction-cards.d.ts +28 -0
- package/dist/interaction-cards.js +119 -0
- package/dist/lark-cli.d.ts +24 -0
- package/dist/lark-cli.js +92 -4
- package/dist/live-reply.d.ts +101 -0
- package/dist/live-reply.js +518 -0
- package/dist/model-controls.d.ts +9 -1
- package/dist/model-controls.js +45 -20
- package/dist/onboarding/controller.js +9 -7
- package/dist/onboarding/lark-platform.js +5 -5
- package/dist/permission-controls.d.ts +20 -0
- package/dist/permission-controls.js +37 -0
- package/dist/permission-preferences.d.ts +26 -0
- package/dist/permission-preferences.js +169 -0
- package/dist/presentation.d.ts +7 -0
- package/dist/replies.d.ts +15 -0
- package/dist/replies.js +27 -6
- package/dist/rich-scopes.d.ts +4 -0
- package/dist/rich-scopes.js +6 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ dsh-lark-agent adds a Feishu / Lark private chat to a running DeepSeek Harness:
|
|
|
16
16
|
|
|
17
17
|
Before you start:
|
|
18
18
|
|
|
19
|
-
- DeepSeek Harness is running, can chat, and has a default model selected: PC App `0.2.1-rc.2
|
|
19
|
+
- DeepSeek Harness is running, can chat, and has a default model selected: PC App `0.2.1-rc.3` (`0.2.1-rc.2` also works), or Web / `dsh` CLI `0.1.1-rc.2`.
|
|
20
20
|
- The official Lark CLI is installed on the same machine **before** you enable the plugin (needs Node.js / npm):
|
|
21
21
|
|
|
22
22
|
```sh
|
|
@@ -72,20 +72,24 @@ The Feishu entry works only while the host is online: not when the app is closed
|
|
|
72
72
|
|
|
73
73
|
| In Feishu, you… | The assistant… |
|
|
74
74
|
| --- | --- |
|
|
75
|
-
| Send a request |
|
|
75
|
+
| Send a request | Works on it with Harness's own tools (web search, reading pages, workspace files and more), streaming the reply live into one card; its thinking and tool steps are folded inside, step by step as in the app, and the card ends with the model, thinking depth and permission mode it used |
|
|
76
|
+
| Ask for something that needs your OK | Sends a confirmation card: tap **允许一次** (allow once) or **拒绝** (deny); no answer within 5 minutes means it won't run |
|
|
77
|
+
| Get asked to choose by Harness | Sends a choice card: tap an option, or fill it in and submit |
|
|
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 |
|
|
79
|
+
| Ask for the result file | Sends files from its workspace straight to the chat |
|
|
76
80
|
| Say "lead with the conclusion from now on" | Remembers the preference |
|
|
77
81
|
| Keep chatting, or use `/sessions`, `/new` | Continues the topic, returns to an earlier one, or starts fresh |
|
|
78
82
|
| Ask for a bar chart of some numbers | Replies with a native Feishu chart card |
|
|
79
83
|
| Ask it to let you pick between options first | Sends a form card and continues once you submit |
|
|
80
|
-
| 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`, opening that Feishu session in the app also shows a local preview |
|
|
81
|
-
| Use `/model`, `/thinking` | Switches between the host's configured models and thinking depth |
|
|
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 |
|
|
85
|
+
| Use `/model`, `/thinking` | Switches between the host's configured models and thinking depth, in sync with Harness on your computer (App `0.2.1-rc.2` / `rc.3`) |
|
|
82
86
|
|
|
83
|
-
By default it answers only your own private chat. Sessions and memory stay on the computer running Harness; messages pass through Feishu and your model provider.
|
|
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.
|
|
84
88
|
|
|
85
89
|
## Good to know
|
|
86
90
|
|
|
87
91
|
- **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).
|
|
88
|
-
- **Compatibility**: verified with PC App `0.2.1-rc.2` and `dsh` CLI / Web `0.1.1-rc.2
|
|
92
|
+
- **Compatibility**: fully verified with PC App `0.2.1-rc.2` and `dsh` CLI / Web `0.1.1-rc.2`. PC App `0.2.1-rc.3` passes the real-host contract tests, and the plugin loads and connects on it (first-time QR setup on rc.3 is not tested yet). Other versions, platforms and tenants are not yet verified.
|
|
89
93
|
- **Language**: the settings page, the bot's built-in messages (`/help`, status text, card buttons, bottom menu) and the intro video are in Chinese.
|
|
90
94
|
|
|
91
95
|
## License
|
package/dist/bot-menu.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare const BOT_MENU_COMMANDS: Readonly<{
|
|
|
6
6
|
readonly 'dsh.sessions': "/sessions";
|
|
7
7
|
readonly 'dsh.model': "/model";
|
|
8
8
|
readonly 'dsh.thinking': "/thinking";
|
|
9
|
+
readonly 'dsh.permission': "/permission";
|
|
9
10
|
}>;
|
|
10
11
|
export type BotMenuCommand = (typeof BOT_MENU_COMMANDS)[keyof typeof BOT_MENU_COMMANDS];
|
|
11
12
|
export declare function botMenuCommand(eventKey: unknown): BotMenuCommand | undefined;
|
|
@@ -19,7 +20,8 @@ export interface BotMenuNode {
|
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
22
|
* Public Application Ability PATCH payload. Publishing is a separate admin action.
|
|
22
|
-
*
|
|
23
|
+
* Three top-level nodes with three children fit both documented floating-menu
|
|
24
|
+
* limit variants (at most five per level and ten in all).
|
|
23
25
|
* Text actions cannot separate a friendly label from the text they send.
|
|
24
26
|
*/
|
|
25
27
|
export declare function buildBotMenuAbility(): {
|
package/dist/bot-menu.js
CHANGED
|
@@ -6,6 +6,7 @@ export const BOT_MENU_COMMANDS = Object.freeze({
|
|
|
6
6
|
'dsh.sessions': '/sessions',
|
|
7
7
|
'dsh.model': '/model',
|
|
8
8
|
'dsh.thinking': '/thinking',
|
|
9
|
+
'dsh.permission': '/permission',
|
|
9
10
|
});
|
|
10
11
|
export function botMenuCommand(eventKey) {
|
|
11
12
|
if (typeof eventKey !== 'string' || !Object.hasOwn(BOT_MENU_COMMANDS, eventKey))
|
|
@@ -14,7 +15,8 @@ export function botMenuCommand(eventKey) {
|
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
17
|
* Public Application Ability PATCH payload. Publishing is a separate admin action.
|
|
17
|
-
*
|
|
18
|
+
* Three top-level nodes with three children fit both documented floating-menu
|
|
19
|
+
* limit variants (at most five per level and ten in all).
|
|
18
20
|
* Text actions cannot separate a friendly label from the text they send.
|
|
19
21
|
*/
|
|
20
22
|
export function buildBotMenuAbility() {
|
|
@@ -24,6 +26,7 @@ export function buildBotMenuAbility() {
|
|
|
24
26
|
{ menu_id: 'dsh_settings', sort: 2, default_name: '设置', menu_content_type: 3 },
|
|
25
27
|
{ menu_id: 'dsh_model', parent_menu_id: 'dsh_settings', sort: 0, default_name: '模型', menu_content_type: 2, event_key: 'dsh.model' },
|
|
26
28
|
{ menu_id: 'dsh_thinking', parent_menu_id: 'dsh_settings', sort: 1, default_name: '思考深度', menu_content_type: 2, event_key: 'dsh.thinking' },
|
|
29
|
+
{ menu_id: 'dsh_permission', parent_menu_id: 'dsh_settings', sort: 2, default_name: '权限模式', menu_content_type: 2, event_key: 'dsh.permission' },
|
|
27
30
|
] } };
|
|
28
31
|
}
|
|
29
32
|
/** Pure review/export plan: this module never invokes an API or changes an app. */
|
package/dist/bot-service.js
CHANGED
|
@@ -13,6 +13,7 @@ import { DirectChatsStore } from './direct-chats.js';
|
|
|
13
13
|
import { ModelPreferencesStore } from './model-preferences.js';
|
|
14
14
|
import { ArtifactStore } from './artifacts.js';
|
|
15
15
|
import { LarkArtifactPublisher } from './artifact-publisher.js';
|
|
16
|
+
import { PermissionPreferencesStore } from './permission-preferences.js';
|
|
16
17
|
/** A failed close is still an owned service, never a disposable startup error. */
|
|
17
18
|
export class BotServiceStartError extends Error {
|
|
18
19
|
service;
|
|
@@ -36,6 +37,7 @@ export async function createBotService(ctx, config, onHealth = () => undefined,
|
|
|
36
37
|
SessionStore.open(config.stateDir), MemoryStore.open(config.stateDir),
|
|
37
38
|
config.cardInteractions ? CardActions.open(config.stateDir, JSON.stringify([config.profile, config.botOpenId])) : undefined,
|
|
38
39
|
DirectChatsStore.open(config.stateDir), ModelPreferencesStore.open(config.stateDir), ArtifactStore.open(config.stateDir),
|
|
40
|
+
PermissionPreferencesStore.open(config.stateDir),
|
|
39
41
|
];
|
|
40
42
|
// An opener may create signing state: finish all openers before releasing
|
|
41
43
|
// ownership after a sibling fails, so no old writer outlives the lock.
|
|
@@ -43,7 +45,7 @@ export async function createBotService(ctx, config, onHealth = () => undefined,
|
|
|
43
45
|
const failed = openedStores.find(result => result.status === 'rejected');
|
|
44
46
|
if (failed?.status === 'rejected')
|
|
45
47
|
throw failed.reason;
|
|
46
|
-
const [sessions, memories, actions, directChats, modelPreferences, artifacts] = await Promise.all(openingStores);
|
|
48
|
+
const [sessions, memories, actions, directChats, modelPreferences, artifacts, permissionPreferences] = await Promise.all(openingStores);
|
|
47
49
|
if (startup?.directChat) {
|
|
48
50
|
const { openId, chatId } = startup.directChat;
|
|
49
51
|
if (!config.users.some(user => user.openId === openId && user.direct))
|
|
@@ -59,7 +61,7 @@ export async function createBotService(ctx, config, onHealth = () => undefined,
|
|
|
59
61
|
? new LarkCli({ profile: config.profile, executable: config.executable, eventKey: 'application.bot.menu_v6' }) : undefined;
|
|
60
62
|
const progressTransport = config.loadingReaction
|
|
61
63
|
? new LarkCli({ profile: config.profile, executable: config.executable, commandTimeoutMs: 5_000 }) : undefined;
|
|
62
|
-
const bridge = new BotBridge(config, new DshRuntime(ctx), transport, ledger, { sessions, memories, actions, directChats, modelPreferences, artifacts }, event => console.info(`[im-larkbot] ${JSON.stringify(event)}`), progressTransport, new LarkArtifactPublisher(config.profile, config.botOpenId, { executable: config.executable }));
|
|
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 }));
|
|
63
65
|
let stopped = false, started = false, healthy = false;
|
|
64
66
|
const readiness = { messages: false, cards: !callbacks, menu: !menu };
|
|
65
67
|
let stopping;
|
package/dist/bridge.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BotConfig } from './config.js';
|
|
2
|
-
import type { AgentPolicy, RuntimeSession, ModelSelection, ModelCatalog } from './dsh-runtime.js';
|
|
2
|
+
import type { AgentPolicy, RuntimeSession, ModelSelection, ModelCatalog, PermissionOption } from './dsh-runtime.js';
|
|
3
3
|
import type { MessageLedger } from './ledger.js';
|
|
4
4
|
import { type MemoryStore } from './memory-store.js';
|
|
5
5
|
import { type SessionStore } from './session-store.js';
|
|
@@ -7,6 +7,7 @@ import type { CardActions } from './card-actions.js';
|
|
|
7
7
|
import { type ReplyPayload } from './replies.js';
|
|
8
8
|
import { type ProgressTransport } from './progress.js';
|
|
9
9
|
import { type ModelPreferencesStore } from './model-preferences.js';
|
|
10
|
+
import type { PermissionPreferencesStore } from './permission-preferences.js';
|
|
10
11
|
import type { ArtifactStore } from './artifacts.js';
|
|
11
12
|
import type { ArtifactPublisher } from './artifact-publisher.js';
|
|
12
13
|
export interface Runtime {
|
|
@@ -14,6 +15,11 @@ export interface Runtime {
|
|
|
14
15
|
resume(policy: AgentPolicy, id: string): Promise<RuntimeSession>;
|
|
15
16
|
catalog?(): Promise<ModelCatalog>;
|
|
16
17
|
resolveModelSelection?(selection?: ModelSelection): Promise<ModelSelection>;
|
|
18
|
+
permissionOptions?(): PermissionOption[];
|
|
19
|
+
/** Harness sessions share their model choice with the App (see RuntimeSession.sharesModelSelection). */
|
|
20
|
+
sharesModelSelection?(): boolean;
|
|
21
|
+
/** Make a choice the App's default for new sessions, as the App's model selector does. */
|
|
22
|
+
saveDefaultModel?(selection: ModelSelection): Promise<void>;
|
|
17
23
|
}
|
|
18
24
|
export interface BotState {
|
|
19
25
|
sessions: Pick<SessionStore, 'current' | 'list' | 'add' | 'activate' | 'archive' | 'rename' | 'touch'> & Partial<Pick<SessionStore, 'rebindNative'>>;
|
|
@@ -25,6 +31,7 @@ export interface BotState {
|
|
|
25
31
|
record(openId: string, chatId: string): Promise<void>;
|
|
26
32
|
};
|
|
27
33
|
artifacts?: Pick<ArtifactStore, 'add' | 'get' | 'list' | 'claimPublish' | 'publication' | 'submit'>;
|
|
34
|
+
permissionPreferences?: Pick<PermissionPreferencesStore, 'get' | 'set'>;
|
|
28
35
|
}
|
|
29
36
|
export interface Transport {
|
|
30
37
|
reply(messageId: string, text: string, options: {
|
|
@@ -50,6 +57,18 @@ export interface Transport {
|
|
|
50
57
|
idempotencyKey: string;
|
|
51
58
|
}): Promise<string>;
|
|
52
59
|
updateFeedback?(messageId: string, payload: ReplyPayload): Promise<void>;
|
|
60
|
+
createCardEntity?(card: Record<string, unknown>): Promise<string>;
|
|
61
|
+
replyCardEntity?(messageId: string, cardId: string, options: {
|
|
62
|
+
idempotencyKey: string;
|
|
63
|
+
inThread: boolean;
|
|
64
|
+
}): Promise<void>;
|
|
65
|
+
streamCardText?(cardId: string, elementId: string, content: string, sequence: number): Promise<void>;
|
|
66
|
+
updateCardEntity?(cardId: string, card: Record<string, unknown>, sequence: number): Promise<void>;
|
|
67
|
+
replyFile?(messageId: string, path: string, options: {
|
|
68
|
+
idempotencyKey: string;
|
|
69
|
+
inThread: boolean;
|
|
70
|
+
image: boolean;
|
|
71
|
+
}): Promise<void>;
|
|
53
72
|
}
|
|
54
73
|
export type Audit = (event: {
|
|
55
74
|
stage: string;
|
|
@@ -76,12 +95,41 @@ export declare class BotBridge {
|
|
|
76
95
|
private readonly failedDisposals;
|
|
77
96
|
private cardActionsReady;
|
|
78
97
|
private readonly progress;
|
|
98
|
+
private readonly interactions;
|
|
99
|
+
private liveUnavailableUntil;
|
|
79
100
|
constructor(config: BotConfig, runtime: Runtime, transport: Transport, ledger: MessageLedger, state: BotState, audit?: Audit, progressTransport?: ProgressTransport, artifactPublisher?: ArtifactPublisher | undefined);
|
|
80
101
|
setCardActionsReady(ready: boolean): void;
|
|
81
102
|
private parseIncoming;
|
|
82
103
|
accept(raw: unknown): Promise<void>;
|
|
83
104
|
private acceptMessage;
|
|
84
105
|
private process;
|
|
106
|
+
/** The model, thinking depth and permission mode a reply runs with, by name. */
|
|
107
|
+
private settingsLine;
|
|
108
|
+
/** Whether this message's reply streams into a live card, which makes the typing reaction redundant. */
|
|
109
|
+
private streams;
|
|
110
|
+
private liveTransport;
|
|
111
|
+
private canInteract;
|
|
112
|
+
private issueAction;
|
|
113
|
+
/** Ask the owner about one Harness approval. Anything but an explicit allow denies the call. */
|
|
114
|
+
private approve;
|
|
115
|
+
/** Ask the owner a Harness question. Without an answer the host gets an error and continues. */
|
|
116
|
+
private ask;
|
|
117
|
+
private waitForAnswer;
|
|
118
|
+
private openInteraction;
|
|
119
|
+
private closeInteraction;
|
|
120
|
+
private answerInteraction;
|
|
121
|
+
/** Send files the host's delivery tool presented, from this user's workspace only. */
|
|
122
|
+
private deliverFiles;
|
|
123
|
+
/** A private-chat click on a settings or topic panel, which can update the clicked card. */
|
|
124
|
+
private inPlace;
|
|
125
|
+
/**
|
|
126
|
+
* Run a panel click and show its progress and result on the clicked card. That
|
|
127
|
+
* card exists, so a failed progress frame never holds back the result, and an
|
|
128
|
+
* unconfirmed update is never followed by another message.
|
|
129
|
+
*/
|
|
130
|
+
private panelInPlace;
|
|
131
|
+
/** Redraw a stale card with the current state, without running its command. */
|
|
132
|
+
private refreshPanel;
|
|
85
133
|
private commandFeedback;
|
|
86
134
|
private context;
|
|
87
135
|
private sessionContext;
|
|
@@ -90,8 +138,30 @@ export declare class BotBridge {
|
|
|
90
138
|
private bindRestoredSession;
|
|
91
139
|
private unload;
|
|
92
140
|
private command;
|
|
141
|
+
private harness;
|
|
142
|
+
/**
|
|
143
|
+
* The owner's harness chat on the App host shares model and thinking depth
|
|
144
|
+
* with the App, like any App session; an administrator's fixed route does not.
|
|
145
|
+
*/
|
|
146
|
+
private sharedModel;
|
|
147
|
+
/** Record what an opened session runs as the chat's copy: the first capture, or a shared session's own record. */
|
|
148
|
+
private mirrorModel;
|
|
149
|
+
/** The host's presets, from the live session when there is one. Undefined when the host offers none. */
|
|
150
|
+
private permissionView;
|
|
151
|
+
/** What this chat runs: the live session's preset, else the chat's saved choice. */
|
|
152
|
+
private permissionChoice;
|
|
153
|
+
/** Owner-only: switch this private chat's Harness permission preset, like the App's selector. */
|
|
154
|
+
private permissionCommand;
|
|
155
|
+
/** Bring a newly opened harness session to the chat's choice, or a new one without a choice to the App's default. */
|
|
156
|
+
private applyPermission;
|
|
157
|
+
/** A preset changed in the App on this chat's live session becomes the chat's choice, so new sessions keep it. */
|
|
158
|
+
private adoptPermission;
|
|
93
159
|
private modelState;
|
|
94
160
|
private modelCommand;
|
|
161
|
+
/**
|
|
162
|
+
* The management card for a command. `card` describes a click being shown on
|
|
163
|
+
* its own card: the switch in flight, its refusal, or a refresh of a stale card.
|
|
164
|
+
*/
|
|
95
165
|
private commandPanel;
|
|
96
166
|
private reply;
|
|
97
167
|
private artifactContext;
|