dsh-workbuddy-connect 0.2.1
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 +21 -0
- package/README.en.md +58 -0
- package/README.md +64 -0
- package/cordis.patch.yml +6 -0
- package/lib/bin.d.ts +6 -0
- package/lib/bin.js +167 -0
- package/lib/client.js +439 -0
- package/lib/host-heartbeat-DehQgrb9.js +830 -0
- package/lib/index.d.ts +332 -0
- package/lib/index.js +556 -0
- package/package.json +102 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
3
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
4
|
+
import { AttachmentStore } from "@deepseek-ai/dsh-attachment";
|
|
5
|
+
//#region src/upstream.d.ts
|
|
6
|
+
/** WorkBuddy region selected by the credential's login domain. */
|
|
7
|
+
type WorkBuddyRegion = 'cn' | 'global';
|
|
8
|
+
/** Upstream failure classes the shim maps onto distinct HTTP answers. */
|
|
9
|
+
type UpstreamErrorKind = 'hard_credit' | 'soft_rate' | 'session_dead' | 'not_found' | 'server' | 'client';
|
|
10
|
+
/** One CLI-usable model as the upstream catalog describes it. */
|
|
11
|
+
interface WorkBuddyUpstreamModel {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
contextWindow: number;
|
|
15
|
+
maxTokens: number;
|
|
16
|
+
}
|
|
17
|
+
/** One billing package and its remaining credit. */
|
|
18
|
+
interface WorkBuddyCreditAccount {
|
|
19
|
+
packageName: string;
|
|
20
|
+
remain: number;
|
|
21
|
+
size: number;
|
|
22
|
+
}
|
|
23
|
+
/** Aggregated credit answer for one credential. */
|
|
24
|
+
interface WorkBuddyCredits {
|
|
25
|
+
total: number;
|
|
26
|
+
accounts: readonly WorkBuddyCreditAccount[];
|
|
27
|
+
}
|
|
28
|
+
/** Token refresh answer; fields the upstream omits stay absent. */
|
|
29
|
+
interface WorkBuddyRefreshOutcome {
|
|
30
|
+
accessToken: string;
|
|
31
|
+
refreshToken?: string;
|
|
32
|
+
expiresInSec?: number;
|
|
33
|
+
domain?: string;
|
|
34
|
+
}
|
|
35
|
+
/** Chat answer: either a live SSE response or a classified failure. */
|
|
36
|
+
type WorkBuddyChatResult = {
|
|
37
|
+
ok: true;
|
|
38
|
+
response: Response;
|
|
39
|
+
} | {
|
|
40
|
+
ok: false;
|
|
41
|
+
status: number;
|
|
42
|
+
kind: UpstreamErrorKind;
|
|
43
|
+
message: string;
|
|
44
|
+
};
|
|
45
|
+
/** Classify an upstream failure from its HTTP status and body excerpt. */
|
|
46
|
+
declare function classifyUpstreamError(status: number, body: string): UpstreamErrorKind;
|
|
47
|
+
/** Region for a login domain; an empty domain means CN (matching upstream tooling). */
|
|
48
|
+
declare function regionOf(domain: string): WorkBuddyRegion;
|
|
49
|
+
/**
|
|
50
|
+
* Normalize an OpenAI chat-completions body for the WorkBuddy upstream:
|
|
51
|
+
* force `stream: true` (the upstream rejects non-streaming) and flatten
|
|
52
|
+
* `tool_choice` (the upstream's field is a string; object forms return 400).
|
|
53
|
+
*/
|
|
54
|
+
declare function prepareChatBody(source: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* Upstream HTTP client. One instance serves the whole plugin; requests take
|
|
57
|
+
* the credential explicitly so token refreshes apply on the next call.
|
|
58
|
+
*/
|
|
59
|
+
declare class WorkBuddyUpstreamClient {
|
|
60
|
+
/** POST the chat endpoint; a successful answer is the raw SSE response. */
|
|
61
|
+
chatStream(credential: WorkBuddyCredential, bodyJson: string, signal?: AbortSignal): Promise<WorkBuddyChatResult>;
|
|
62
|
+
/** POST the token-refresh endpoint; the caller merges the outcome. */
|
|
63
|
+
refreshToken(credential: WorkBuddyCredential): Promise<WorkBuddyRefreshOutcome>;
|
|
64
|
+
/** GET the personal model catalog and keep the `cli` agent's models only. */
|
|
65
|
+
fetchModels(credential: WorkBuddyCredential): Promise<readonly WorkBuddyUpstreamModel[]>;
|
|
66
|
+
/** POST the billing endpoint for the aggregated remaining credit. */
|
|
67
|
+
fetchCredits(credential: WorkBuddyCredential): Promise<WorkBuddyCredits>;
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/auth.d.ts
|
|
71
|
+
/** Normalized WorkBuddy credential, timestamps in epoch milliseconds. */
|
|
72
|
+
interface WorkBuddyCredential {
|
|
73
|
+
accessToken: string;
|
|
74
|
+
refreshToken: string;
|
|
75
|
+
expiresAtMs: number;
|
|
76
|
+
refreshExpiresAtMs?: number;
|
|
77
|
+
domain: string;
|
|
78
|
+
uid: string;
|
|
79
|
+
enterpriseId?: string;
|
|
80
|
+
nickname?: string;
|
|
81
|
+
/** Which storage the credential was read from; refreshes are always `dsh`. */
|
|
82
|
+
source: 'desktop' | 'dsh';
|
|
83
|
+
}
|
|
84
|
+
/** Read-only sign-in summary for status and doctor output. */
|
|
85
|
+
interface WorkBuddyAuthStatus {
|
|
86
|
+
state: 'signed-in' | 'signed-out';
|
|
87
|
+
expiresAtMs?: number;
|
|
88
|
+
refreshExpiresAtMs?: number;
|
|
89
|
+
nickname?: string;
|
|
90
|
+
domain?: string;
|
|
91
|
+
source?: 'desktop' | 'dsh';
|
|
92
|
+
}
|
|
93
|
+
/** Constructor options; only {@link refresh} is required. */
|
|
94
|
+
interface WorkBuddyStoreOptions {
|
|
95
|
+
/** Explicit desktop auth-file path, overriding env and platform defaults. */
|
|
96
|
+
desktopPath?: string;
|
|
97
|
+
/** Explicit plugin-owned copy path, defaulting under `$DSH_HOME`. */
|
|
98
|
+
ownPath?: string;
|
|
99
|
+
/** Performs the upstream token refresh. */
|
|
100
|
+
refresh: (credential: WorkBuddyCredential) => Promise<WorkBuddyRefreshOutcome>;
|
|
101
|
+
/** Refresh this long before actual expiry; default five minutes. */
|
|
102
|
+
refreshMarginMs?: number;
|
|
103
|
+
}
|
|
104
|
+
/** Basename of the plugin-owned credential copy inside the Harness home. */
|
|
105
|
+
declare const WORKBUDDY_AUTH_FILENAME = ".workbuddy-auth.json";
|
|
106
|
+
/** Env variable that overrides the desktop auth-file location. */
|
|
107
|
+
declare const WORKBUDDY_AUTH_FILE_ENV = "WORKBUDDY_AUTH_FILE";
|
|
108
|
+
/** Plugin-owned copy path inside the Harness home. */
|
|
109
|
+
declare function workbuddyOwnAuthPath(): string;
|
|
110
|
+
/** Platform default for the WorkBuddy desktop app's auth file. */
|
|
111
|
+
declare function defaultDesktopAuthPath(): string | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* Parse a WorkBuddy auth document in either on-disk shape: the plugin OAuth
|
|
114
|
+
* nested form `{"auth":{...},"account":{...}}` and the flat panel form.
|
|
115
|
+
* Returns undefined when the document carries no access token.
|
|
116
|
+
*/
|
|
117
|
+
declare function parseWorkBuddyAuth(text: string): WorkBuddyCredential | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* Read-only credential store with demand-driven refresh.
|
|
120
|
+
*
|
|
121
|
+
* Refresh policy: refresh only when the access token is inside the margin
|
|
122
|
+
* (or already expired), keep the refreshed credential in the plugin-owned
|
|
123
|
+
* copy, and never write the desktop app's file. A failed refresh still
|
|
124
|
+
* returns a not-yet-expired token so an unreachable refresh endpoint does
|
|
125
|
+
* not take down a working session.
|
|
126
|
+
*/
|
|
127
|
+
declare class WorkBuddyCredentialStore {
|
|
128
|
+
private readonly refresh;
|
|
129
|
+
private readonly refreshMarginMs;
|
|
130
|
+
private readonly ownPath;
|
|
131
|
+
private desktopPathOverride;
|
|
132
|
+
private inflight;
|
|
133
|
+
constructor(options: WorkBuddyStoreOptions);
|
|
134
|
+
/**
|
|
135
|
+
* Configuration precedence for the desktop file: the plugin's configured
|
|
136
|
+
* path, then the environment variable, then the platform default.
|
|
137
|
+
*/
|
|
138
|
+
private resolveDesktopPath;
|
|
139
|
+
/**
|
|
140
|
+
* Repoint the desktop file; a settings change applies on the next read.
|
|
141
|
+
*/
|
|
142
|
+
setDesktopPath(path: string | undefined): void;
|
|
143
|
+
/** The resolved desktop auth-file path, for diagnostics. */
|
|
144
|
+
desktopAuthPath(): string | undefined;
|
|
145
|
+
/** The plugin-owned copy path, for diagnostics. */
|
|
146
|
+
ownAuthPath(): string;
|
|
147
|
+
/** Read the freshest stored credential without refreshing anything. */
|
|
148
|
+
current(): Promise<WorkBuddyCredential | undefined>;
|
|
149
|
+
/**
|
|
150
|
+
* The credential to send upstream: {@link current}, refreshed on demand.
|
|
151
|
+
* Single-flight, so parallel requests share one refresh.
|
|
152
|
+
*/
|
|
153
|
+
resolve(): Promise<WorkBuddyCredential>;
|
|
154
|
+
/** Read-only sign-in summary; never refreshes and never throws. */
|
|
155
|
+
status(): Promise<WorkBuddyAuthStatus>;
|
|
156
|
+
/** Remove the plugin-owned copy; the desktop file is untouched. */
|
|
157
|
+
logout(): Promise<void>;
|
|
158
|
+
private needsRefresh;
|
|
159
|
+
private refreshNow;
|
|
160
|
+
private saveOwn;
|
|
161
|
+
private readDesktop;
|
|
162
|
+
private readOwn;
|
|
163
|
+
/** Whether the desktop file exists and is a regular file; diagnostics only. */
|
|
164
|
+
desktopFilePresent(): Promise<boolean>;
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/catalog.d.ts
|
|
168
|
+
/** One model entry the adapter exposes. */
|
|
169
|
+
type WorkBuddyModelInfo = WorkBuddyUpstreamModel;
|
|
170
|
+
/**
|
|
171
|
+
* Static CLI models observed on the CN endpoint (2026-08-17). The upstream
|
|
172
|
+
* refresh replaces this list at startup; it exists so the provider registers
|
|
173
|
+
* with a usable catalog even while the first fetch is in flight or offline.
|
|
174
|
+
*/
|
|
175
|
+
declare const FALLBACK_WORKBUDDY_MODELS: readonly WorkBuddyModelInfo[];
|
|
176
|
+
/** Mutable catalog shared by the shim's `/v1/models` and the adapter. */
|
|
177
|
+
declare class WorkBuddyCatalog {
|
|
178
|
+
private models;
|
|
179
|
+
/** Current entries; the fallback list until the upstream answer lands. */
|
|
180
|
+
current(): readonly WorkBuddyModelInfo[];
|
|
181
|
+
/** Replace the list; callers invalidate their adapter snapshot after this. */
|
|
182
|
+
set(models: readonly WorkBuddyModelInfo[]): void;
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/shim.d.ts
|
|
186
|
+
/** Minimal logger surface the plugin context already provides. */
|
|
187
|
+
interface ShimLogger {
|
|
188
|
+
warn(...args: unknown[]): void;
|
|
189
|
+
error(...args: unknown[]): void;
|
|
190
|
+
}
|
|
191
|
+
/** What the plugin needs from a running shim. */
|
|
192
|
+
interface WorkBuddyShim {
|
|
193
|
+
/** Resolves once the listener is up; rejects if listening failed. */
|
|
194
|
+
ready: Promise<void>;
|
|
195
|
+
/** The shim origin, e.g. `http://127.0.0.1:39271`; valid after ready. */
|
|
196
|
+
baseUrl(): string;
|
|
197
|
+
/**
|
|
198
|
+
* The per-process shared secret the plugin's own client must carry as
|
|
199
|
+
* `Authorization: Bearer <token>`. Lives only in memory; the adapter
|
|
200
|
+
* resolves this instead of the upstream access token, because the shim
|
|
201
|
+
* resolves the real credential itself via the store.
|
|
202
|
+
*/
|
|
203
|
+
token(): string;
|
|
204
|
+
/** Stop serving and destroy open connections. */
|
|
205
|
+
close(): Promise<void>;
|
|
206
|
+
}
|
|
207
|
+
/** Constructor dependencies. */
|
|
208
|
+
interface WorkBuddyShimOptions {
|
|
209
|
+
store: WorkBuddyCredentialStore;
|
|
210
|
+
client: Pick<WorkBuddyUpstreamClient, 'chatStream'>;
|
|
211
|
+
catalog: WorkBuddyCatalog;
|
|
212
|
+
logger?: ShimLogger;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Start the loopback endpoint. Requests carry any bearer; the loopback bind
|
|
216
|
+
* is the boundary, and the upstream credential comes from the store alone.
|
|
217
|
+
*/
|
|
218
|
+
declare function createWorkBuddyShim(options: WorkBuddyShimOptions): WorkBuddyShim;
|
|
219
|
+
//#endregion
|
|
220
|
+
//#region src/adapter.d.ts
|
|
221
|
+
/** Provider route this bundle owns. */
|
|
222
|
+
declare const WORKBUDDY_PROVIDER = "workbuddy";
|
|
223
|
+
/** Provider idle ceiling while one stream read is outstanding. */
|
|
224
|
+
declare const WORKBUDDY_STREAM_IDLE_TIMEOUT_MS = 300000;
|
|
225
|
+
/** Constructor dependencies. */
|
|
226
|
+
interface WorkBuddyAdapterOptions {
|
|
227
|
+
shim: WorkBuddyShim;
|
|
228
|
+
store: WorkBuddyCredentialStore;
|
|
229
|
+
catalog: WorkBuddyCatalog;
|
|
230
|
+
/** Resolve the durable attachment service at request time, when present. */
|
|
231
|
+
resolveAttachments?: () => AttachmentStore | undefined;
|
|
232
|
+
}
|
|
233
|
+
/** What {@link createWorkBuddyAdapter} hands back. */
|
|
234
|
+
interface WorkBuddyAdapter {
|
|
235
|
+
adapter: PiAiAdapter;
|
|
236
|
+
/** Rebuild the adapter's provider snapshot; call after a catalog update. */
|
|
237
|
+
invalidate: () => void;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Assemble the adapter. The provider's `getModels` reads the live catalog,
|
|
241
|
+
* and every model's `baseUrl` is re-resolved per read so the shim's
|
|
242
|
+
* ephemeral port applies from the first snapshot after startup.
|
|
243
|
+
*/
|
|
244
|
+
declare function createWorkBuddyAdapter(options: WorkBuddyAdapterOptions): WorkBuddyAdapter;
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/host-heartbeat.d.ts
|
|
247
|
+
/**
|
|
248
|
+
* Host-side heartbeat: a small JSON file written under `$DSH_HOME` once the
|
|
249
|
+
* `workbuddy` provider is registered. The status CLI reads it to report
|
|
250
|
+
* whether the host bundle is alive, independent of the browser card.
|
|
251
|
+
*
|
|
252
|
+
* The browser (client) bundle cannot write files; its health is reported
|
|
253
|
+
* only through `console.error` on failure (see `src/client/index.tsx`).
|
|
254
|
+
* This asymmetry is intentional: the host is the load-bearing half, and
|
|
255
|
+
* a missing heartbeat unambiguously means the host never started.
|
|
256
|
+
*
|
|
257
|
+
* @module dsh-workbuddy-connect/host-heartbeat
|
|
258
|
+
*/
|
|
259
|
+
/** Basename of the host heartbeat file inside the Harness home. */
|
|
260
|
+
declare const WORKBUDDY_HOST_HEARTBEAT_FILENAME = ".workbuddy-host-heartbeat.json";
|
|
261
|
+
/** Current on-disk heartbeat format; readers reject others. */
|
|
262
|
+
declare const HEARTBEAT_FORMAT_VERSION = 1;
|
|
263
|
+
/** On-disk shape of the heartbeat. */
|
|
264
|
+
interface WorkBuddyHostHeartbeat {
|
|
265
|
+
version: typeof HEARTBEAT_FORMAT_VERSION;
|
|
266
|
+
package: 'dsh-workbuddy-connect';
|
|
267
|
+
pluginVersion: string;
|
|
268
|
+
/** Epoch milliseconds when the host registered the provider. */
|
|
269
|
+
registeredAt: number;
|
|
270
|
+
/** Host process PID, to distinguish a stale heartbeat after a crash. */
|
|
271
|
+
pid: number;
|
|
272
|
+
}
|
|
273
|
+
/** Absolute path of the host heartbeat file. */
|
|
274
|
+
declare function workbuddyHostHeartbeatPath(): string;
|
|
275
|
+
/** Remove the heartbeat on plugin disposal so a stale file does not linger. */
|
|
276
|
+
declare function clearHostHeartbeat(): Promise<void>;
|
|
277
|
+
/** Read and validate the heartbeat; returns `undefined` when absent or malformed. */
|
|
278
|
+
declare function readHostHeartbeat(): Promise<WorkBuddyHostHeartbeat | undefined>;
|
|
279
|
+
/**
|
|
280
|
+
* Absolute start time (epoch ms) of the process holding `pid`, or `undefined`
|
|
281
|
+
* when it cannot be determined (no such PID, platform lacks a readable source).
|
|
282
|
+
*
|
|
283
|
+
* - macOS / Linux: `ps -o lstart=` prints a local-time "EEE MMM DD HH:MM:SS YYYY";
|
|
284
|
+
* `Date.parse` resolves it against the local clock, which matches how
|
|
285
|
+
* `registeredAt` (a `Date.now()` absolute value) is expressed.
|
|
286
|
+
* - Windows: WMI `CreationDate` is UTC (`YYYYMMDDHHMMSS.mmm+zzzz`); parsed with
|
|
287
|
+
* `Date.UTC`, again comparable to `registeredAt`.
|
|
288
|
+
*
|
|
289
|
+
* Failures return `undefined` so callers can fall back to plain PID liveness
|
|
290
|
+
* rather than mis-report a running host as dead.
|
|
291
|
+
*/
|
|
292
|
+
declare function processStartTimeMs(pid: number): number | undefined;
|
|
293
|
+
/**
|
|
294
|
+
* Whether the heartbeat's PID is still alive *and* still the same process that
|
|
295
|
+
* registered it. A stale heartbeat (host crashed without clearing the file)
|
|
296
|
+
* is distinguished from a live host by two checks:
|
|
297
|
+
*
|
|
298
|
+
* 1. `process.kill(pid, 0)` — the PID exists (signal 0 tests existence).
|
|
299
|
+
* 2. The process holding that PID started at or before `registeredAt`. A host
|
|
300
|
+
* that registered the heartbeat must have been started before writing it,
|
|
301
|
+
* so `start <= registeredAt`; a recycled PID belongs to an unrelated process
|
|
302
|
+
* started after the host died, so `start > registeredAt` correctly reads dead.
|
|
303
|
+
*
|
|
304
|
+
* PID-only detection is not enough: after a crash the OS may hand the same PID
|
|
305
|
+
* to an unrelated process, and the un-cleared stale heartbeat would otherwise
|
|
306
|
+
* produce a false "Host running". When the process start time cannot be read
|
|
307
|
+
* (e.g. unsupported platform) the check degrades to plain PID liveness.
|
|
308
|
+
*/
|
|
309
|
+
declare function isHeartbeatProcessAlive(heartbeat: WorkBuddyHostHeartbeat): boolean;
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/index.d.ts
|
|
312
|
+
/** Stable Cordis plugin name. */
|
|
313
|
+
declare const name = "llm-workbuddy";
|
|
314
|
+
/** The model registry required before the provider can register. */
|
|
315
|
+
declare const inject: string[];
|
|
316
|
+
/** Settings namespace reserved for the future configuration card. */
|
|
317
|
+
declare const WORKBUDDY_SETTINGS_NS: import("@deepseek-ai/dsh-settings").SettingsNamespace;
|
|
318
|
+
/** Plugin configuration. */
|
|
319
|
+
interface Config {
|
|
320
|
+
/** Explicit WorkBuddy desktop auth-file path, overriding env and platform defaults. */
|
|
321
|
+
authFile?: string;
|
|
322
|
+
}
|
|
323
|
+
declare const Config: z<Config>;
|
|
324
|
+
/**
|
|
325
|
+
* Start the loopback endpoint, register the `workbuddy` provider, and
|
|
326
|
+
* refresh the model catalog from the upstream once credentials allow it.
|
|
327
|
+
* The static fallback catalog serves from the first moment, so an offline
|
|
328
|
+
* upstream never leaves the provider empty.
|
|
329
|
+
*/
|
|
330
|
+
declare function apply(ctx: Context, config: Config): void;
|
|
331
|
+
//#endregion
|
|
332
|
+
export { Config, FALLBACK_WORKBUDDY_MODELS, type UpstreamErrorKind, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, type WorkBuddyAdapter, type WorkBuddyAuthStatus, WorkBuddyCatalog, type WorkBuddyChatResult, type WorkBuddyCredential, WorkBuddyCredentialStore, type WorkBuddyCredits, type WorkBuddyHostHeartbeat, type WorkBuddyModelInfo, type WorkBuddyRefreshOutcome, type WorkBuddyShim, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, apply, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthPath, inject, isHeartbeatProcessAlive, name, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readHostHeartbeat, regionOf, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath };
|