@sparkelf/dsh-group-chat 0.0.0-stage → 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/README.md +90 -2
- package/cordis.patch.yml +3 -0
- package/lib/client.js +4856 -0
- package/lib/client.js.map +1 -0
- package/lib/index.d.ts +357 -0
- package/lib/index.js +10087 -0
- package/package.json +98 -4
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { CredentialRef } from "@deepseek-ai/dsh-credentials";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
import { Agent } from "@deepseek-ai/dsh-agent";
|
|
4
|
+
import { Session, SessionEvent } from "@deepseek-ai/dsh-session";
|
|
5
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
6
|
+
//#region src/context.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Host services this plugin needs.
|
|
9
|
+
*
|
|
10
|
+
* `agents` and `sessions` carry the room subscription, `webServer` exposes the
|
|
11
|
+
* browser-facing room API, and `credentials` resolves the token this workspace
|
|
12
|
+
* presents — the secret is never read from configuration or cached in the plugin.
|
|
13
|
+
*/
|
|
14
|
+
interface GroupChatHostServices {
|
|
15
|
+
readonly agents: {
|
|
16
|
+
get(id: string): Agent | undefined;
|
|
17
|
+
list(): readonly Agent[];
|
|
18
|
+
};
|
|
19
|
+
readonly sessions: {
|
|
20
|
+
get(id: string): Session | undefined;
|
|
21
|
+
list(): readonly Session[];
|
|
22
|
+
create(id: string, options?: {
|
|
23
|
+
meta?: {
|
|
24
|
+
cwd?: string;
|
|
25
|
+
};
|
|
26
|
+
}): Session;
|
|
27
|
+
};
|
|
28
|
+
readonly credentials: {
|
|
29
|
+
resolve(ref: CredentialRef): Promise<{
|
|
30
|
+
value: string;
|
|
31
|
+
source: string;
|
|
32
|
+
} | undefined>;
|
|
33
|
+
};
|
|
34
|
+
readonly webServer: {
|
|
35
|
+
register(route: {
|
|
36
|
+
kind: 'exact' | 'prefix';
|
|
37
|
+
path: string;
|
|
38
|
+
handler(req: unknown, res: unknown): void | Promise<void>;
|
|
39
|
+
}): () => void;
|
|
40
|
+
};
|
|
41
|
+
readonly logger?: {
|
|
42
|
+
error(...args: unknown[]): void;
|
|
43
|
+
warn?(...args: unknown[]): void;
|
|
44
|
+
info?(...args: unknown[]): void;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
type GroupChatContext = Context & GroupChatHostServices;
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/identity.d.ts
|
|
50
|
+
/**
|
|
51
|
+
* How this plugin learns where its server is and who it is.
|
|
52
|
+
*
|
|
53
|
+
* The **protocol**, not DataOps, is the contract: any server implementing the
|
|
54
|
+
* documented room endpoints works, so nothing here is specific to DataOps. A
|
|
55
|
+
* deployment supplies values through the plugin's own settings, and DataOps
|
|
56
|
+
* additionally injects environment defaults into its workspace containers so the
|
|
57
|
+
* first-run experience needs no typing.
|
|
58
|
+
*
|
|
59
|
+
* Resolution order is settings first, environment second:
|
|
60
|
+
*
|
|
61
|
+
* 1. An explicit setting always wins — that is what an operator edits, and it is
|
|
62
|
+
* how a workspace points at any conforming server.
|
|
63
|
+
* 2. The DataOps environment variables fill whatever the settings leave blank.
|
|
64
|
+
*/
|
|
65
|
+
/** Environment variables DataOps injects into a managed workspace container. */
|
|
66
|
+
declare const MANAGED_ENV: {
|
|
67
|
+
/** Base URL of the server, e.g. `http://host.docker.internal:3101`. */
|
|
68
|
+
readonly baseUrl: "DATAOPS_AI_WORKSPACE_BACKEND_BASE_URL";
|
|
69
|
+
/** Workspace root, used as a room session's `cwd`. */
|
|
70
|
+
readonly mountPath: "DATAOPS_AI_WORKSPACE_MOUNT_PATH";
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The default credential reference.
|
|
74
|
+
*
|
|
75
|
+
* It names the DataOps access token the workspace already holds in the DSH
|
|
76
|
+
* credential store — the same reference `dataops-managed` and `knowledge-base`
|
|
77
|
+
* use. That is what makes this work in a managed workspace: the companion
|
|
78
|
+
* container has no credential in its environment by design, but the credential
|
|
79
|
+
* store does carry this entry, and a deployment can point the setting at any other
|
|
80
|
+
* reference for a different server.
|
|
81
|
+
*/
|
|
82
|
+
declare const DEFAULT_CREDENTIAL_REF = "DATAOPS_ACCESS_TOKEN";
|
|
83
|
+
/** What the plugin needs in hand before it can talk to a server. */
|
|
84
|
+
interface ServerTarget {
|
|
85
|
+
/** Origin with any trailing slash removed. */
|
|
86
|
+
readonly baseUrl: string;
|
|
87
|
+
/** Credential reference to resolve per request. */
|
|
88
|
+
readonly credentialRef: string;
|
|
89
|
+
}
|
|
90
|
+
interface ResolveTargetInput {
|
|
91
|
+
/** Value from the settings panel, if the operator set one. */
|
|
92
|
+
readonly baseUrl?: string;
|
|
93
|
+
readonly credentialRef?: string;
|
|
94
|
+
/** Environment bag; defaults to `process.env`. */
|
|
95
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Resolves the server target, or `undefined` when this workspace is not configured.
|
|
99
|
+
*
|
|
100
|
+
* `undefined` means "do not activate": no route, no listener, no timer. A
|
|
101
|
+
* half-configured workspace is treated as unconfigured rather than failing loudly,
|
|
102
|
+
* because a plugin that cannot reach a server has nothing useful to do at startup
|
|
103
|
+
* and must not disturb ordinary sessions.
|
|
104
|
+
*
|
|
105
|
+
* The actor is deliberately absent: a bearer credential identifies the account
|
|
106
|
+
* server-side, so the plugin never states who it is and cannot disagree with the
|
|
107
|
+
* server about it.
|
|
108
|
+
*/
|
|
109
|
+
declare const resolveServerTarget: (input?: ResolveTargetInput) => ServerTarget | undefined;
|
|
110
|
+
/** The workspace root a room session uses as its `cwd`. */
|
|
111
|
+
declare const resolveWorkspaceRoot: (env?: Readonly<Record<string, string | undefined>>) => string;
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/backend.d.ts
|
|
114
|
+
/** One durable room event as the backend stores it. */
|
|
115
|
+
interface RoomEventRecord {
|
|
116
|
+
/** Global order across every container, as a decimal string. */
|
|
117
|
+
readonly id: string;
|
|
118
|
+
/** The complete DSH event envelope, stored without translation. */
|
|
119
|
+
readonly event: RoomEvent;
|
|
120
|
+
readonly createdAt?: string;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The envelope half of a DSH event. `data` is `unknown` on purpose: the backend
|
|
124
|
+
* stores it verbatim and this plugin re-appends it verbatim, so nothing here needs
|
|
125
|
+
* to understand content blocks.
|
|
126
|
+
*/
|
|
127
|
+
interface RoomEvent {
|
|
128
|
+
readonly type: string;
|
|
129
|
+
readonly seq?: number;
|
|
130
|
+
readonly time?: number;
|
|
131
|
+
readonly data: unknown;
|
|
132
|
+
readonly surfaceOp?: string;
|
|
133
|
+
}
|
|
134
|
+
interface RoomSummary {
|
|
135
|
+
readonly id: string;
|
|
136
|
+
readonly name: string;
|
|
137
|
+
readonly ownerUserId: string;
|
|
138
|
+
readonly invitePolicy: 'anyone' | 'admin';
|
|
139
|
+
readonly discoverability: 'discoverable' | 'invite_only';
|
|
140
|
+
readonly memberCount: number;
|
|
141
|
+
readonly joined: boolean;
|
|
142
|
+
readonly createdAt: string;
|
|
143
|
+
readonly updatedAt: string;
|
|
144
|
+
}
|
|
145
|
+
interface RoomMember {
|
|
146
|
+
readonly userId: string;
|
|
147
|
+
readonly displayName: string;
|
|
148
|
+
readonly owner: boolean;
|
|
149
|
+
readonly joinedAt: string;
|
|
150
|
+
}
|
|
151
|
+
interface EventPage {
|
|
152
|
+
readonly events: readonly RoomEventRecord[];
|
|
153
|
+
/** Cursor to resume from; `null` when the room has no events yet. */
|
|
154
|
+
readonly nextCursor: string | null;
|
|
155
|
+
readonly hasMore: boolean;
|
|
156
|
+
}
|
|
157
|
+
/** A backend refusal that carries the HTTP status so callers can distinguish them. */
|
|
158
|
+
declare class BackendError extends Error {
|
|
159
|
+
readonly status: number;
|
|
160
|
+
readonly body: unknown;
|
|
161
|
+
constructor(message: string, status: number, body: unknown);
|
|
162
|
+
}
|
|
163
|
+
/** True when the request never reached the backend, so retrying cannot duplicate state. */
|
|
164
|
+
declare const isTransportFailure: (error: unknown) => boolean;
|
|
165
|
+
interface BackendClientOptions {
|
|
166
|
+
/** Per-request timeout; a stalled backend must not hold a session append path. */
|
|
167
|
+
readonly timeoutMs?: number;
|
|
168
|
+
/** Injected for tests. */
|
|
169
|
+
readonly fetchImpl?: typeof fetch;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Resolves the credential for one request.
|
|
173
|
+
*
|
|
174
|
+
* The secret is fetched per request rather than captured at activation: a
|
|
175
|
+
* credential can be rotated or added while the plugin is loaded, and caching it
|
|
176
|
+
* would keep sending a stale value. Returning `undefined` means "no credential
|
|
177
|
+
* available", which fails the request instead of sending an anonymous one.
|
|
178
|
+
*/
|
|
179
|
+
type CredentialResolver = () => Promise<string | undefined>;
|
|
180
|
+
/** Thin typed wrapper over the group-chat server protocol. */
|
|
181
|
+
declare class GroupChatBackend {
|
|
182
|
+
private readonly target;
|
|
183
|
+
private readonly resolveCredential;
|
|
184
|
+
private readonly timeoutMs;
|
|
185
|
+
private readonly fetchImpl;
|
|
186
|
+
constructor(target: ServerTarget, resolveCredential: CredentialResolver, options?: BackendClientOptions);
|
|
187
|
+
/**
|
|
188
|
+
* Builds the headers for one call.
|
|
189
|
+
*
|
|
190
|
+
* The credential is a bearer token, so the request carries the deployment's own
|
|
191
|
+
* identity rather than a plugin-specific one. The room routes are the same ones
|
|
192
|
+
* a browser uses, which is why the plugin needs no parallel server surface.
|
|
193
|
+
*/
|
|
194
|
+
private headers;
|
|
195
|
+
/** Rooms this workspace's member has joined. */
|
|
196
|
+
listRooms(signal?: AbortSignal): Promise<readonly RoomSummary[]>;
|
|
197
|
+
/**
|
|
198
|
+
* The account this workspace acts as, asked of the server rather than configured.
|
|
199
|
+
*
|
|
200
|
+
* The bearer credential already identifies the caller, so the server is the only
|
|
201
|
+
* correct source for "who am I": a configured value could disagree with the
|
|
202
|
+
* credential and silently change which messages trigger this agent.
|
|
203
|
+
*/
|
|
204
|
+
whoami(signal?: AbortSignal): Promise<{
|
|
205
|
+
readonly id: string;
|
|
206
|
+
} | undefined>;
|
|
207
|
+
listMembers(roomId: string, signal?: AbortSignal): Promise<readonly RoomMember[]>;
|
|
208
|
+
/**
|
|
209
|
+
* Pull events after `cursor`. Omitting the cursor asks for the room from the start,
|
|
210
|
+
* which is what a fresh container does on its first sync.
|
|
211
|
+
*/
|
|
212
|
+
listEvents(roomId: string, cursor?: string, limit?: number, signal?: AbortSignal): Promise<EventPage>;
|
|
213
|
+
/**
|
|
214
|
+
* Send one human message. `rpcId` is the idempotency key: a retry that reuses it
|
|
215
|
+
* returns the original row instead of creating a second one, so a timed-out
|
|
216
|
+
* request is safe to repeat.
|
|
217
|
+
*/
|
|
218
|
+
sendMessage(roomId: string, input: {
|
|
219
|
+
readonly content: string;
|
|
220
|
+
readonly rpcId: string;
|
|
221
|
+
readonly mentions?: readonly string[];
|
|
222
|
+
}, signal?: AbortSignal): Promise<{
|
|
223
|
+
readonly id: string;
|
|
224
|
+
readonly event: RoomEvent;
|
|
225
|
+
readonly deduplicated: boolean;
|
|
226
|
+
}>;
|
|
227
|
+
/**
|
|
228
|
+
* Report one agent reply. Only the turn's final `assistant/message` is sent —
|
|
229
|
+
* intermediate steps stay in this container by construction.
|
|
230
|
+
*/
|
|
231
|
+
reportAgentEvent(roomId: string, input: {
|
|
232
|
+
readonly rpcId: string;
|
|
233
|
+
readonly event: RoomEvent;
|
|
234
|
+
}, signal?: AbortSignal): Promise<{
|
|
235
|
+
readonly id: string;
|
|
236
|
+
readonly event: RoomEvent;
|
|
237
|
+
readonly deduplicated: boolean;
|
|
238
|
+
}>;
|
|
239
|
+
private request;
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/routing.d.ts
|
|
243
|
+
/** Identifies a member mentioned by an event. */
|
|
244
|
+
interface MentionTarget {
|
|
245
|
+
readonly memberId: string;
|
|
246
|
+
/** `all` is reserved for `@everyone`; it never equals a member id, so it never triggers. */
|
|
247
|
+
readonly kind: 'member' | 'all';
|
|
248
|
+
}
|
|
249
|
+
/** The DataOps user a message came from, when the event identifies one. */
|
|
250
|
+
declare const senderId: (event: RoomEvent) => string | undefined;
|
|
251
|
+
/** True when the event is a member's agent reply rather than a human message. */
|
|
252
|
+
declare const isAgentReply: (event: RoomEvent) => boolean;
|
|
253
|
+
/** The global backend id carried on the event, used for local deduplication. */
|
|
254
|
+
declare const messageId: (event: RoomEvent) => string | undefined;
|
|
255
|
+
/**
|
|
256
|
+
* Reads structured mentions off an event.
|
|
257
|
+
*
|
|
258
|
+
* Mentions are declared by the sender's client as member ids — never parsed out of
|
|
259
|
+
* message text. Parsing text would misread a code sample containing `@`, and it
|
|
260
|
+
* would let any message smuggle itself into an execution path.
|
|
261
|
+
*/
|
|
262
|
+
declare const mentionsOf: (event: RoomEvent) => readonly MentionTarget[];
|
|
263
|
+
/**
|
|
264
|
+
* The decision the whole design turns on.
|
|
265
|
+
*
|
|
266
|
+
* `selfMention` requires the sender to be this workspace's member *and* to have
|
|
267
|
+
* mentioned that same member, which is the only combination that may start a turn.
|
|
268
|
+
*/
|
|
269
|
+
declare const shouldTriggerAgent: (event: RoomEvent, workspaceUserId: string, roomMembers: readonly RoomMember[]) => boolean;
|
|
270
|
+
/**
|
|
271
|
+
* Renders the name prefix for a message this workspace did not author.
|
|
272
|
+
*
|
|
273
|
+
* DSH's compaction summarizes raw `role` + `content` and carries no speaker field,
|
|
274
|
+
* so identity must live inside `content` to survive summarization. This mirrors the
|
|
275
|
+
* shipped `agent-team` plugin, which prefixes `Team message from <name>:`.
|
|
276
|
+
*/
|
|
277
|
+
declare const speakerPrefix: (event: RoomEvent, roomMembers: readonly RoomMember[]) => string;
|
|
278
|
+
/** Prepends the speaker to each text block so the model can attribute the message. */
|
|
279
|
+
declare const withSpeakerPrefix: (event: RoomEvent, prefix: string) => RoomEvent;
|
|
280
|
+
//#endregion
|
|
281
|
+
//#region src/index.d.ts
|
|
282
|
+
declare const name = "group-chat";
|
|
283
|
+
/** Host services this plugin needs beyond the room runtime. */
|
|
284
|
+
declare const inject: string[];
|
|
285
|
+
/**
|
|
286
|
+
* Plugin configuration, rendered as editable fields in the DSH settings panel.
|
|
287
|
+
*
|
|
288
|
+
* Every field is optional: leaving them blank uses the environment defaults a
|
|
289
|
+
* managed workspace already carries, so the common case needs no editing while a
|
|
290
|
+
* workspace pointed at any other conforming server can be configured from the UI.
|
|
291
|
+
*/
|
|
292
|
+
interface Config {
|
|
293
|
+
/** Origin of the room server, e.g. `http://host.docker.internal:3101`. */
|
|
294
|
+
baseUrl?: string;
|
|
295
|
+
/** Credential reference resolved per request; never a secret value. */
|
|
296
|
+
credentialRef?: string;
|
|
297
|
+
}
|
|
298
|
+
declare const Config: z<Config>;
|
|
299
|
+
/** Browser-facing route the sidebar calls to list and read rooms. */
|
|
300
|
+
declare const GROUP_CHAT_ROUTE = "/dsh-group-chat/api";
|
|
301
|
+
/**
|
|
302
|
+
* Milliseconds between cursor pulls.
|
|
303
|
+
*
|
|
304
|
+
* Pulling is the fallback and currently the only downlink: it needs no second
|
|
305
|
+
* transport, and a cursor is the whole synchronization state, so it survives a
|
|
306
|
+
* restart without re-appending history. The interval trades a bounded delay for a
|
|
307
|
+
* request the backend already serves.
|
|
308
|
+
*/
|
|
309
|
+
declare const POLL_INTERVAL_MS = 3000;
|
|
310
|
+
/**
|
|
311
|
+
* Every room session id starts with this, so a room's session is recognizable
|
|
312
|
+
* after a restart and an ordinary session never matches.
|
|
313
|
+
*
|
|
314
|
+
* The binding lives in the id rather than in `SessionHeader` because the header
|
|
315
|
+
* carries no free-form metadata field.
|
|
316
|
+
*/
|
|
317
|
+
declare const ROOM_SESSION_PREFIX = "group-room-";
|
|
318
|
+
/** Deterministic session id for a room. */
|
|
319
|
+
declare const roomSessionId: (roomId: string) => string;
|
|
320
|
+
/** Reads the room a session is bound to, or `undefined` for an ordinary session. */
|
|
321
|
+
declare const roomIdOfSession: (session: unknown) => string | undefined;
|
|
322
|
+
/** The most recently appended event belonging to a room. */
|
|
323
|
+
declare const lastRoomEvent: (session: Session, roomId: string) => SessionEvent | undefined;
|
|
324
|
+
/** Narrows the payload `followup` accepts; the check is structural because JSON crossed a boundary. */
|
|
325
|
+
declare const isUserMessageData: (data: unknown) => data is Parameters<Agent["followup"]>[0];
|
|
326
|
+
/**
|
|
327
|
+
* Tracks which room events this workspace already appended.
|
|
328
|
+
*
|
|
329
|
+
* The key is the backend's global id, never the local sequence: the backend owns
|
|
330
|
+
* cross-container order, and the same event can arrive twice (a broadcast and a
|
|
331
|
+
* catch-up pull). Without this the model would read one message twice.
|
|
332
|
+
*/
|
|
333
|
+
declare class RoomEventTracker {
|
|
334
|
+
private readonly seen;
|
|
335
|
+
/** Records an event and reports whether it was new. */
|
|
336
|
+
accept(roomId: string, globalId: string): boolean;
|
|
337
|
+
has(roomId: string, globalId: string): boolean;
|
|
338
|
+
/** Highest global id seen for a room, as the cursor to resume from. */
|
|
339
|
+
cursorOf(roomId: string): string | undefined;
|
|
340
|
+
/**
|
|
341
|
+
* Rebuilds the tracker from a room's own session log, so a restart resumes from
|
|
342
|
+
* what this workspace already holds instead of re-appending the whole room.
|
|
343
|
+
*/
|
|
344
|
+
seedFromSession(roomId: string, events: readonly SessionEvent[]): void;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Wire the room runtime.
|
|
348
|
+
*
|
|
349
|
+
* Returns `undefined` on an unmanaged DSH: no route, no listener, no timer. That is
|
|
350
|
+
* the mechanism behind "ordinary sessions are byte-identical with and without this
|
|
351
|
+
* plugin".
|
|
352
|
+
*/
|
|
353
|
+
declare const apply: (ctx: GroupChatContext, config?: Config) => (() => void) | undefined;
|
|
354
|
+
/** The activated runtime; separated so target resolution stays independently testable. */
|
|
355
|
+
declare const activate: (ctx: GroupChatContext, target: ServerTarget) => (() => void);
|
|
356
|
+
//#endregion
|
|
357
|
+
export { BackendError, Config, DEFAULT_CREDENTIAL_REF, type EventPage, GROUP_CHAT_ROUTE, GroupChatBackend, MANAGED_ENV, POLL_INTERVAL_MS, ROOM_SESSION_PREFIX, type RoomEvent, type RoomEventRecord, RoomEventTracker, type RoomMember, type RoomSummary, type ServerTarget, activate, apply, inject, isAgentReply, isTransportFailure, isUserMessageData, lastRoomEvent, mentionsOf, messageId, name, resolveServerTarget, resolveWorkspaceRoot, roomIdOfSession, roomSessionId, senderId, shouldTriggerAgent, speakerPrefix, withSpeakerPrefix };
|