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/dist/card-actions.js
CHANGED
|
@@ -5,7 +5,7 @@ import { join } from 'node:path';
|
|
|
5
5
|
const TTL_MS = 24 * 60 * 60 * 1000;
|
|
6
6
|
const MAX_TOKEN_LENGTH = 3000;
|
|
7
7
|
const HEX_ID = /^[a-f0-9]{12}$/;
|
|
8
|
-
const COMMAND = /^(?:\/(?:help|status|sessions|memory|new|model|thinking|results)|\/(?:model|thinking) (?:use [a-f0-9]{16}|page [0-9]{1,4})|\/memory (?:global|chat|show [a-f0-9]{12})|\/(?:resume|forget|result|publish|answer) [a-f0-9]{12})$/;
|
|
8
|
+
const COMMAND = /^(?:\/(?:help|status|sessions|memory|new|model|thinking|results)|\/(?:model|thinking) (?:use [a-f0-9]{16}|page [0-9]{1,4})|\/memory (?:global|chat|show [a-f0-9]{12})|\/(?:resume|forget|result|publish|answer|approve|deny|reply) [a-f0-9]{12}|\/choose [a-f0-9]{12} [0-9]{1,2}|\/permission(?: use [a-z][a-z0-9-]{0,39})?)$/;
|
|
9
9
|
const PAYLOAD_KEYS = ['version', 'namespace', 'issuedAt', 'expiresAt', 'nonce', 'binding'];
|
|
10
10
|
const BINDING_KEYS = ['ownerId', 'chatId', 'chatType', 'threadId', 'sourceMessageId', 'command', 'expectedSessionId', 'expectedModelRevision'];
|
|
11
11
|
function record(value) {
|
package/dist/client.cjs
CHANGED
|
@@ -531,8 +531,8 @@ function ConnectionCard({ api }) {
|
|
|
531
531
|
] }),
|
|
532
532
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lark-connected-row", children: [
|
|
533
533
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
534
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "\u6587\u6863\u4E0E\u753B\u677F" }),
|
|
535
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: snapshot.capabilities.documents ? "\u5DF2\u542F\u7528 \xB7 \u79C1\u804A\u4F5C\u54C1\u4EC5\u4F9B\u672C\u4EBA\u67E5\u770B" : "\u628A\u590D\u6742\u65B9\u6848\u6574\u7406\u6210\
|
|
534
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "\u5B9E\u65F6\u56DE\u590D\u3001\u6587\u6863\u4E0E\u753B\u677F" }),
|
|
535
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: snapshot.capabilities.documents ? "\u5DF2\u542F\u7528 \xB7 \u56DE\u590D\u5B9E\u65F6\u663E\u793A\uFF0C\u79C1\u804A\u4F5C\u54C1\u4EC5\u4F9B\u672C\u4EBA\u67E5\u770B" : "\u5B9E\u65F6\u663E\u793A\u56DE\u590D\u548C\u601D\u8003\u8FC7\u7A0B\uFF0C\u5E76\u628A\u590D\u6742\u65B9\u6848\u6574\u7406\u6210\u6587\u6863\u548C\u56FE\u793A" })
|
|
536
536
|
] }),
|
|
537
537
|
!snapshot.capabilities.documents && !snapshot.documentSetup && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "lark-secondary", disabled, onClick: () => void mutate("enable-documents"), children: "\u8865\u5145\u9ED8\u8BA4\u6743\u9650" })
|
|
538
538
|
] }),
|
|
@@ -540,7 +540,7 @@ function ConnectionCard({ api }) {
|
|
|
540
540
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: snapshot.documentSetup.message }),
|
|
541
541
|
snapshot.documentSetup.phase === "preparing" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "lark-spinner", "aria-label": "\u6B63\u5728\u51C6\u5907\u6388\u6743" }),
|
|
542
542
|
snapshot.documentSetup.qr && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
543
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lark-qr", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: snapshot.documentSetup.qr.image, alt: "\
|
|
543
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lark-qr", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: snapshot.documentSetup.qr.image, alt: "\u9ED8\u8BA4\u6743\u9650\u8865\u5145\u6388\u6743\u4E8C\u7EF4\u7801" }) }),
|
|
544
544
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: snapshot.documentSetup.qr.expiresAt <= now ? "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u53D6\u6D88\u540E\u91CD\u8BD5\u3002" : "\u626B\u7801\u786E\u8BA4\u540E\u81EA\u52A8\u5B8C\u6210\uFF0C\u804A\u5929\u8FDE\u63A5\u7EE7\u7EED\u4FDD\u6301\u3002" }),
|
|
545
545
|
snapshot.documentSetup.qr.expiresAt > now && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(External, { url: snapshot.documentSetup.qr.url, children: "\u6253\u5F00\u98DE\u4E66\u6388\u6743" })
|
|
546
546
|
] }),
|
package/dist/config.d.ts
CHANGED
|
@@ -4,6 +4,12 @@ export interface UserPolicy {
|
|
|
4
4
|
groupChats: string[];
|
|
5
5
|
cwd: string;
|
|
6
6
|
allowedTools: string[];
|
|
7
|
+
/**
|
|
8
|
+
* 'harness': this user's private chat is a channel into an ordinary Harness
|
|
9
|
+
* session, with the host's prompt, tools and approvals. Group chats always
|
|
10
|
+
* use the restricted assistant and allowedTools.
|
|
11
|
+
*/
|
|
12
|
+
directMode: 'harness' | 'restricted';
|
|
7
13
|
}
|
|
8
14
|
export interface BotConfig {
|
|
9
15
|
profile: string;
|
|
@@ -26,6 +32,8 @@ export interface BotConfig {
|
|
|
26
32
|
cardInteractions: boolean;
|
|
27
33
|
loadingReaction: boolean;
|
|
28
34
|
botMenu: boolean;
|
|
35
|
+
/** Stream harness-mode private replies into a CardKit card (needs cardkit:card:write). */
|
|
36
|
+
streamingCards: boolean;
|
|
29
37
|
}
|
|
30
38
|
export declare function parseConfig(value: unknown, baseDir?: string): BotConfig;
|
|
31
39
|
export declare function loadConfig(path: string): Promise<BotConfig>;
|
package/dist/config.js
CHANGED
|
@@ -45,11 +45,13 @@ export function parseConfig(value, baseDir = process.cwd()) {
|
|
|
45
45
|
const allowedTools = strings(user.allowedTools ?? [], 'user.allowedTools');
|
|
46
46
|
if (allowedTools.some(t => !/^[a-zA-Z0-9_.:-]+$/.test(t) || t === 'run_code'))
|
|
47
47
|
throw new Error('allowedTools requires exact tool names; run_code and wildcards are forbidden');
|
|
48
|
+
if (user.directMode !== undefined && user.directMode !== 'harness' && user.directMode !== 'restricted')
|
|
49
|
+
throw new Error('user.directMode must be harness or restricted');
|
|
48
50
|
return {
|
|
49
51
|
openId: id(user.openId, 'ou', 'user.openId'),
|
|
50
52
|
direct: user.direct === true,
|
|
51
53
|
groupChats: strings(user.groupChats ?? [], 'user.groupChats').map(v => id(v, 'oc', 'group chat')),
|
|
52
|
-
cwd: resolve(cwd), allowedTools,
|
|
54
|
+
cwd: resolve(cwd), allowedTools, directMode: user.directMode ?? 'restricted',
|
|
53
55
|
};
|
|
54
56
|
});
|
|
55
57
|
if (new Set(users.map(u => u.openId)).size !== users.length)
|
|
@@ -62,6 +64,8 @@ export function parseConfig(value, baseDir = process.cwd()) {
|
|
|
62
64
|
throw new Error('loadingReaction must be boolean');
|
|
63
65
|
if (data.botMenu !== undefined && typeof data.botMenu !== 'boolean')
|
|
64
66
|
throw new Error('botMenu must be boolean');
|
|
67
|
+
if (data.streamingCards !== undefined && typeof data.streamingCards !== 'boolean')
|
|
68
|
+
throw new Error('streamingCards must be boolean');
|
|
65
69
|
return {
|
|
66
70
|
profile, botOpenId: id(data.botOpenId, 'ou', 'botOpenId'),
|
|
67
71
|
executable: string(data.executable ?? 'lark-cli', 'executable'),
|
|
@@ -69,7 +73,8 @@ export function parseConfig(value, baseDir = process.cwd()) {
|
|
|
69
73
|
provider: data.provider === undefined ? undefined : string(data.provider, 'provider'),
|
|
70
74
|
model: data.model === undefined ? undefined : string(data.model, 'model'),
|
|
71
75
|
users,
|
|
72
|
-
|
|
76
|
+
// A harness-mode chat does research and tool work, and may wait minutes for an approval.
|
|
77
|
+
turnTimeoutMs: integer(data.turnTimeoutMs, users.some(user => user.directMode === 'harness') ? 900_000 : 120_000, 1000, 1_800_000, 'turnTimeoutMs'),
|
|
73
78
|
idleTimeoutMs: integer(data.idleTimeoutMs, 1_800_000, 1000, 86_400_000, 'idleTimeoutMs'),
|
|
74
79
|
maxSessions: integer(data.maxSessions, 100, 1, 1000, 'maxSessions'),
|
|
75
80
|
maxSessionsPerUser: integer(data.maxSessionsPerUser, 10, 1, 100, 'maxSessionsPerUser'),
|
|
@@ -82,6 +87,7 @@ export function parseConfig(value, baseDir = process.cwd()) {
|
|
|
82
87
|
cardInteractions: data.cardInteractions === true,
|
|
83
88
|
loadingReaction: data.loadingReaction === true,
|
|
84
89
|
botMenu: data.botMenu === true,
|
|
90
|
+
streamingCards: data.streamingCards === true,
|
|
85
91
|
};
|
|
86
92
|
}
|
|
87
93
|
export async function loadConfig(path) {
|
package/dist/dsh-runtime.d.ts
CHANGED
|
@@ -24,6 +24,75 @@ export interface AgentPolicy {
|
|
|
24
24
|
reasoningEffort?: string;
|
|
25
25
|
memory?: MemoryAccess;
|
|
26
26
|
presentation?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* 'harness' keeps the host's own prompt, tools and approval policy, so the
|
|
29
|
+
* chat is just another channel into the session. Only an owner's private
|
|
30
|
+
* chat may use it; every other conversation stays 'restricted'.
|
|
31
|
+
*/
|
|
32
|
+
mode?: 'harness' | 'restricted';
|
|
33
|
+
}
|
|
34
|
+
/** Live presentation data for one turn. The completed turn result stays authoritative. */
|
|
35
|
+
export type TurnStreamEvent = {
|
|
36
|
+
type: 'attempt';
|
|
37
|
+
} | {
|
|
38
|
+
type: 'discard';
|
|
39
|
+
} | {
|
|
40
|
+
type: 'reasoning';
|
|
41
|
+
text: string;
|
|
42
|
+
} | {
|
|
43
|
+
type: 'text';
|
|
44
|
+
text: string;
|
|
45
|
+
} | {
|
|
46
|
+
type: 'tool';
|
|
47
|
+
callId: string;
|
|
48
|
+
name: string;
|
|
49
|
+
detail?: string;
|
|
50
|
+
code?: boolean;
|
|
51
|
+
} | {
|
|
52
|
+
type: 'tool-done';
|
|
53
|
+
callId: string;
|
|
54
|
+
ok: boolean;
|
|
55
|
+
exitCode?: number;
|
|
56
|
+
};
|
|
57
|
+
export interface ApprovalPrompt {
|
|
58
|
+
toolName: string;
|
|
59
|
+
reason?: string;
|
|
60
|
+
detail?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface UserQuestion {
|
|
63
|
+
id: string;
|
|
64
|
+
question: string;
|
|
65
|
+
header?: string;
|
|
66
|
+
options: Array<{
|
|
67
|
+
label: string;
|
|
68
|
+
description?: string;
|
|
69
|
+
}>;
|
|
70
|
+
multiSelect: boolean;
|
|
71
|
+
detail?: string;
|
|
72
|
+
}
|
|
73
|
+
export interface QuestionAnswer {
|
|
74
|
+
id: string;
|
|
75
|
+
selected: string[];
|
|
76
|
+
custom?: string;
|
|
77
|
+
}
|
|
78
|
+
/** A host permission preset, such as read-only, workspace-write or full access. */
|
|
79
|
+
export interface PermissionOption {
|
|
80
|
+
id: string;
|
|
81
|
+
name: string;
|
|
82
|
+
description?: string;
|
|
83
|
+
}
|
|
84
|
+
/** defaultPreset is the App's choice for new sessions, when it is one of the options. */
|
|
85
|
+
export interface PermissionState {
|
|
86
|
+
current: string;
|
|
87
|
+
options: PermissionOption[];
|
|
88
|
+
defaultPreset?: string;
|
|
89
|
+
}
|
|
90
|
+
/** One turn's channel hooks. A harness-mode session asks its owner through them. */
|
|
91
|
+
export interface TurnObserver {
|
|
92
|
+
stream?(event: TurnStreamEvent): void;
|
|
93
|
+
/** Resolve true to allow this one call. Rejection or failure denies it. */
|
|
94
|
+
approve?(prompt: ApprovalPrompt, signal: AbortSignal): Promise<boolean>;
|
|
95
|
+
ask?(questions: UserQuestion[], signal: AbortSignal): Promise<QuestionAnswer[]>;
|
|
27
96
|
}
|
|
28
97
|
/** Complete chat-owned choice. No credential or provider endpoint belongs here. */
|
|
29
98
|
export interface ModelSelection {
|
|
@@ -53,15 +122,29 @@ export interface RuntimeSession {
|
|
|
53
122
|
run(text: string, signal: AbortSignal, context?: {
|
|
54
123
|
memoryText?: string;
|
|
55
124
|
artifactsText?: string;
|
|
56
|
-
}): Promise<string | TurnResult>;
|
|
125
|
+
}, observer?: TurnObserver): Promise<string | TurnResult>;
|
|
57
126
|
checkpoint(): Promise<void>;
|
|
58
127
|
cancel(): void;
|
|
59
128
|
dispose(): Promise<void>;
|
|
60
129
|
modelSelection?(): ModelSelection;
|
|
61
|
-
/**
|
|
130
|
+
/**
|
|
131
|
+
* Call only after the bridge validates and durably saves the complete choice.
|
|
132
|
+
* In a session that shares its model with the App it is recorded in the
|
|
133
|
+
* session itself, so checkpoint before confirming it.
|
|
134
|
+
*/
|
|
62
135
|
setModelSelection?(next: ModelSelection): void;
|
|
136
|
+
/**
|
|
137
|
+
* True when this session decides its model like the App's own sessions: its
|
|
138
|
+
* latest pick (from Feishu or the App), else its last request's model, else
|
|
139
|
+
* the host default. The App then shows and changes the same choice.
|
|
140
|
+
*/
|
|
141
|
+
sharesModelSelection?: boolean;
|
|
142
|
+
/** Harness sessions on a host with permission presets; undefined otherwise. */
|
|
143
|
+
permissionState?(): PermissionState | undefined;
|
|
144
|
+
/** Switch this session's permission preset, as the App's own selector does. */
|
|
145
|
+
setPermission?(id: string): void;
|
|
63
146
|
}
|
|
64
|
-
export type RuntimeErrorCode = 'SETUP_FAILED' | 'RESTORE_FAILED' | 'SESSION_IN_USE' | 'PERSISTENCE_FAILED' | 'TURN_FAILED' | 'CANCELLED' | 'OUTPUT_LIMIT' | 'BUSY' | 'DISPOSED' | 'MODEL_UNAVAILABLE' | 'INVALID_MODEL_SELECTION';
|
|
147
|
+
export type RuntimeErrorCode = 'SETUP_FAILED' | 'RESTORE_FAILED' | 'SESSION_IN_USE' | 'PERSISTENCE_FAILED' | 'TURN_FAILED' | 'CANCELLED' | 'OUTPUT_LIMIT' | 'BUSY' | 'DISPOSED' | 'MODEL_UNAVAILABLE' | 'INVALID_MODEL_SELECTION' | 'MODEL_NOT_SAVED' | 'PERMISSION_UNAVAILABLE';
|
|
65
148
|
/** Safe to show to a chat user: never contains a provider, tool, or host error. */
|
|
66
149
|
export declare class DshRuntimeError extends Error {
|
|
67
150
|
readonly code: RuntimeErrorCode;
|
|
@@ -87,6 +170,14 @@ export declare class DshRuntime {
|
|
|
87
170
|
resolveModelSelection(selection?: ModelSelection, signal?: AbortSignal): Promise<ModelSelection>;
|
|
88
171
|
private effectiveSelection;
|
|
89
172
|
create(policy: AgentPolicy): Promise<RuntimeSession>;
|
|
173
|
+
/** The host's selectable permission presets, for a chat that has no live session yet. */
|
|
174
|
+
permissionOptions(): PermissionOption[];
|
|
175
|
+
/** Whether harness sessions here share their model choice with the App (see RuntimeSession.sharesModelSelection). */
|
|
176
|
+
sharesModelSelection(): boolean;
|
|
177
|
+
/** Make a choice the App's default for new sessions, as the App's own model selector does. */
|
|
178
|
+
saveDefaultModel(selection: ModelSelection): Promise<void>;
|
|
179
|
+
/** The preset a stored session runs on: its current projection, else its header. */
|
|
180
|
+
private recordedPreset;
|
|
90
181
|
resume(policy: AgentPolicy, sessionId: string): Promise<RuntimeSession>;
|
|
91
182
|
private build;
|
|
92
183
|
}
|