dsh-lark-agent 0.1.1 → 0.2.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 dsh-larkbot contributors
3
+ Copyright (c) 2026 dsh-lark-agent contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,16 +1,16 @@
1
- <img src="https://raw.githubusercontent.com/Nirvana-Jie/dsh-larkbot/main/assets/plugin-icon.png" width="80" align="right" alt="dsh-larkbot icon">
1
+ <img src="https://raw.githubusercontent.com/Nirvana-Jie/dsh-lark-agent/main/assets/plugin-icon.png" width="80" align="right" alt="dsh-lark-agent icon">
2
2
 
3
- # dsh-larkbot
3
+ # dsh-lark-agent
4
4
 
5
- [中文](https://github.com/Nirvana-Jie/dsh-larkbot/blob/main/README_zh.md)
5
+ [中文](https://github.com/Nirvana-Jie/dsh-lark-agent/blob/main/README_zh.md)
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/dsh-lark-agent)](https://www.npmjs.com/package/dsh-lark-agent)
8
8
 
9
9
  **Reach your [DeepSeek Harness](https://www.deepseek.com/harness/) assistant from Feishu / Lark when you're away from your computer.**
10
10
 
11
- dsh-larkbot (npm package [`dsh-lark-agent`](https://www.npmjs.com/package/dsh-lark-agent)) adds a Feishu / Lark private chat to a running DeepSeek Harness: hand off tasks, talk through plans, and pick up earlier topics from your phone. Feishu gets its own sessions and never takes over chats already open in the desktop app or on the web.
11
+ dsh-lark-agent adds a Feishu / Lark private chat to a running DeepSeek Harness: hand off tasks, talk through plans, and pick up earlier topics from your phone. Feishu gets its own sessions and never takes over chats already open in the desktop app or on the web.
12
12
 
13
- [![dsh-larkbot illustrated walkthrough (Feishu chat redrawn with demo data, not a screen recording): from the Harness settings page into a Feishu private chat that returns chart and form cards](https://raw.githubusercontent.com/Nirvana-Jie/dsh-larkbot/main/assets/readme/intro.gif)](https://github.com/Nirvana-Jie/dsh-larkbot/blob/main/assets/readme/intro.mp4)
13
+ [![dsh-lark-agent illustrated walkthrough (Feishu chat redrawn with demo data, not a screen recording): from the Harness settings page into a Feishu private chat that returns chart and form cards](https://raw.githubusercontent.com/Nirvana-Jie/dsh-lark-agent/main/assets/readme/intro.gif)](https://github.com/Nirvana-Jie/dsh-lark-agent/blob/main/assets/readme/intro.mp4)
14
14
 
15
15
  ## Install / Update
16
16
 
@@ -30,7 +30,7 @@ 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-larkbot/main/assets/readme/desktop-install-02.jpg" width="720" alt="The Add plugin dialog in the DeepSeek Harness desktop app">
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
34
 
35
35
  ### Install with the `dsh` CLI
36
36
 
@@ -53,7 +53,7 @@ Then restart `dsh web`.
53
53
 
54
54
  ### Update
55
55
 
56
- 1. On the Feishu settings page, click **暂停连接** (pause) and back up as described in [Upgrade](https://github.com/Nirvana-Jie/dsh-larkbot/blob/main/DEVELOPMENT.md#升级).
56
+ 1. On the Feishu settings page, click **暂停连接** (pause) and back up as described in [Upgrade](https://github.com/Nirvana-Jie/dsh-lark-agent/blob/main/DEVELOPMENT.md#升级).
57
57
  2. Desktop app: the Add plugin dialog says third-party plugins must be uninstalled before a new version is installed, so uninstall `dsh-lark-agent` on the Plugins page and add it again (this in-app upgrade hasn't been tested end to end yet; an earlier source build is listed as `dsh-larkbot`). CLI: run `dsh plugin --profile web update dsh-lark-agent@latest`, then restart `dsh web`.
58
58
  3. Click **恢复连接** (resume) and check that the bot replies.
59
59
 
@@ -72,7 +72,11 @@ 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 | Shows a `Typing` reaction while it works (on by default for new bots), then replies in segments |
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, with its thinking and tool steps folded inside |
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 权限模式 in the model settings card | Switches between read-only, workspace edits and full access, like the app's permission selector; full access stops asking |
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 |
@@ -80,14 +84,14 @@ The Feishu entry works only while the host is online: not when the app is closed
80
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`, opening that Feishu session in the app also shows a local preview |
81
85
  | Use `/model`, `/thinking` | Switches between the host's configured models and thinking depth |
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
- - **More docs**: commands, privacy, upgrades, the chat bottom menu, team deployment and troubleshooting are in [DEVELOPMENT.md](https://github.com/Nirvana-Jie/dsh-larkbot/blob/main/DEVELOPMENT.md) (Chinese).
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
92
  - **Compatibility**: verified with PC App `0.2.1-rc.2` and `dsh` CLI / Web `0.1.1-rc.2`; 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
92
96
 
93
- [MIT](https://github.com/Nirvana-Jie/dsh-larkbot/blob/main/LICENSE). DeepSeek and Feishu / Lark brand assets belong to their owners and are not covered by the MIT license; this is an independent open-source plugin, not an official product.
97
+ [MIT](https://github.com/Nirvana-Jie/dsh-lark-agent/blob/main/LICENSE). DeepSeek and Feishu / Lark brand assets belong to their owners and are not covered by the MIT license; this is an independent open-source plugin, not an official product.
@@ -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;
package/dist/bot-menu.js CHANGED
@@ -6,6 +6,8 @@ export const BOT_MENU_COMMANDS = Object.freeze({
6
6
  'dsh.sessions': '/sessions',
7
7
  'dsh.model': '/model',
8
8
  'dsh.thinking': '/thinking',
9
+ // Optional: not in the default five-node menu; an owner may add it under 设置.
10
+ 'dsh.permission': '/permission',
9
11
  });
10
12
  export function botMenuCommand(eventKey) {
11
13
  if (typeof eventKey !== 'string' || !Object.hasOwn(BOT_MENU_COMMANDS, eventKey))
@@ -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,7 @@ 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[];
17
19
  }
18
20
  export interface BotState {
19
21
  sessions: Pick<SessionStore, 'current' | 'list' | 'add' | 'activate' | 'archive' | 'rename' | 'touch'> & Partial<Pick<SessionStore, 'rebindNative'>>;
@@ -25,6 +27,7 @@ export interface BotState {
25
27
  record(openId: string, chatId: string): Promise<void>;
26
28
  };
27
29
  artifacts?: Pick<ArtifactStore, 'add' | 'get' | 'list' | 'claimPublish' | 'publication' | 'submit'>;
30
+ permissionPreferences?: Pick<PermissionPreferencesStore, 'get' | 'set'>;
28
31
  }
29
32
  export interface Transport {
30
33
  reply(messageId: string, text: string, options: {
@@ -50,6 +53,18 @@ export interface Transport {
50
53
  idempotencyKey: string;
51
54
  }): Promise<string>;
52
55
  updateFeedback?(messageId: string, payload: ReplyPayload): Promise<void>;
56
+ createCardEntity?(card: Record<string, unknown>): Promise<string>;
57
+ replyCardEntity?(messageId: string, cardId: string, options: {
58
+ idempotencyKey: string;
59
+ inThread: boolean;
60
+ }): Promise<void>;
61
+ streamCardText?(cardId: string, elementId: string, content: string, sequence: number): Promise<void>;
62
+ updateCardEntity?(cardId: string, card: Record<string, unknown>, sequence: number): Promise<void>;
63
+ replyFile?(messageId: string, path: string, options: {
64
+ idempotencyKey: string;
65
+ inThread: boolean;
66
+ image: boolean;
67
+ }): Promise<void>;
53
68
  }
54
69
  export type Audit = (event: {
55
70
  stage: string;
@@ -76,12 +91,29 @@ export declare class BotBridge {
76
91
  private readonly failedDisposals;
77
92
  private cardActionsReady;
78
93
  private readonly progress;
94
+ private readonly interactions;
95
+ private liveUnavailableUntil;
79
96
  constructor(config: BotConfig, runtime: Runtime, transport: Transport, ledger: MessageLedger, state: BotState, audit?: Audit, progressTransport?: ProgressTransport, artifactPublisher?: ArtifactPublisher | undefined);
80
97
  setCardActionsReady(ready: boolean): void;
81
98
  private parseIncoming;
82
99
  accept(raw: unknown): Promise<void>;
83
100
  private acceptMessage;
84
101
  private process;
102
+ /** Whether this message's reply streams into a live card, which makes the typing reaction redundant. */
103
+ private streams;
104
+ private liveTransport;
105
+ private canInteract;
106
+ private issueAction;
107
+ /** Ask the owner about one Harness approval. Anything but an explicit allow denies the call. */
108
+ private approve;
109
+ /** Ask the owner a Harness question. Without an answer the host gets an error and continues. */
110
+ private ask;
111
+ private waitForAnswer;
112
+ private openInteraction;
113
+ private closeInteraction;
114
+ private answerInteraction;
115
+ /** Send files the host's delivery tool presented, from this user's workspace only. */
116
+ private deliverFiles;
85
117
  private commandFeedback;
86
118
  private context;
87
119
  private sessionContext;
@@ -90,6 +122,15 @@ export declare class BotBridge {
90
122
  private bindRestoredSession;
91
123
  private unload;
92
124
  private command;
125
+ private harness;
126
+ /** The host's presets, from the live session when there is one. Undefined when the host offers none. */
127
+ private permissionView;
128
+ /** What this chat runs: the live session's preset, else the chat's saved choice. */
129
+ private permissionChoice;
130
+ /** Owner-only: switch this private chat's Harness permission preset, like the App's selector. */
131
+ private permissionCommand;
132
+ /** Bring a newly opened harness session to the chat's saved permission preset. */
133
+ private applyPermission;
93
134
  private modelState;
94
135
  private modelCommand;
95
136
  private commandPanel;