@gatherthread/dsh-host 0.1.0-alpha.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.
@@ -0,0 +1,3713 @@
1
+ // dist/src/config.js
2
+ import { createHash } from "node:crypto";
3
+ import { lstat as lstat2 } from "node:fs/promises";
4
+ import path2 from "node:path";
5
+
6
+ // dist/src/path-security.js
7
+ import { lstat, realpath } from "node:fs/promises";
8
+ import path from "node:path";
9
+ async function prospectiveRealPath(candidate) {
10
+ let current = path.resolve(candidate);
11
+ const missing = [];
12
+ while (true) {
13
+ try {
14
+ return path.join(await realpath(current), ...missing);
15
+ } catch (error) {
16
+ if (!isNodeError(error) || error.code !== "ENOENT")
17
+ throw error;
18
+ const parent = path.dirname(current);
19
+ if (parent === current)
20
+ throw new Error(`No existing ancestor for path: ${candidate}`);
21
+ missing.unshift(path.basename(current));
22
+ current = parent;
23
+ }
24
+ }
25
+ }
26
+ async function secureDirectoryChain(directory) {
27
+ const absolute = path.resolve(directory);
28
+ const root = path.parse(absolute).root;
29
+ const relative = path.relative(root, absolute);
30
+ const components = relative ? relative.split(path.sep) : [];
31
+ const identities = [];
32
+ let current = root;
33
+ for (const component of components) {
34
+ current = path.join(current, component);
35
+ let metadata;
36
+ try {
37
+ metadata = await lstat(current);
38
+ } catch (error) {
39
+ if (isNodeError(error) && error.code === "ENOENT")
40
+ break;
41
+ throw error;
42
+ }
43
+ if (metadata.isSymbolicLink()) {
44
+ throw new Error("DSH connector state path must not traverse a symbolic-link ancestor");
45
+ }
46
+ if (!metadata.isDirectory()) {
47
+ throw new Error("DSH connector state path ancestor must be a directory");
48
+ }
49
+ identities.push({ path: current, dev: metadata.dev, ino: metadata.ino });
50
+ }
51
+ return identities;
52
+ }
53
+ function assertSameDirectoryChain(before, after) {
54
+ if (before.length !== after.length || before.some((entry, index) => {
55
+ const candidate = after[index];
56
+ return candidate === void 0 || candidate.path !== entry.path || candidate.dev !== entry.dev || candidate.ino !== entry.ino;
57
+ })) {
58
+ throw new Error("DSH connector state path ancestors changed during file access");
59
+ }
60
+ }
61
+ function isNodeError(error) {
62
+ return error instanceof Error && "code" in error;
63
+ }
64
+
65
+ // dist/src/config.js
66
+ var CONFIG_KEYS = /* @__PURE__ */ new Set([
67
+ "enabled",
68
+ "bindingMode",
69
+ "apiUrl",
70
+ "credentialReference",
71
+ "projectId",
72
+ "projectName",
73
+ "sessionId",
74
+ "deviceId",
75
+ "workspacePath",
76
+ "statePath",
77
+ "stateRoot",
78
+ "provider",
79
+ "model",
80
+ "pollIntervalMs",
81
+ "pollLimit",
82
+ "shareToolEvents",
83
+ "refreshIntervalMs",
84
+ "maxConcurrentSessions",
85
+ "retryBaseMs",
86
+ "retryMaxMs"
87
+ ]);
88
+ var SINGLE_ONLY_KEYS = ["sessionId", "statePath"];
89
+ var PROJECT_ONLY_KEYS = [
90
+ "stateRoot",
91
+ "refreshIntervalMs",
92
+ "maxConcurrentSessions",
93
+ "retryBaseMs",
94
+ "retryMaxMs"
95
+ ];
96
+ function parseDshHostConfig(input) {
97
+ if (input === void 0)
98
+ return { enabled: false };
99
+ const value = requiredObject(input, "config");
100
+ rejectUnknownKeys(value, CONFIG_KEYS, "config");
101
+ if (value.enabled !== true) {
102
+ if (value.enabled !== void 0 && value.enabled !== false) {
103
+ throw new Error("config.enabled must be true or false");
104
+ }
105
+ return { enabled: false };
106
+ }
107
+ const bindingMode = value.bindingMode === void 0 ? "single" : value.bindingMode;
108
+ if (bindingMode !== "single" && bindingMode !== "project") {
109
+ throw new Error("config.bindingMode must be single or project");
110
+ }
111
+ rejectModeKeys(value, bindingMode === "single" ? PROJECT_ONLY_KEYS : SINGLE_ONLY_KEYS, bindingMode);
112
+ const workspacePath = absolutePath(value.workspacePath, "config.workspacePath");
113
+ const credential = requiredObject(value.credentialReference, "config.credentialReference");
114
+ rejectUnknownKeys(credential, /* @__PURE__ */ new Set(["kind", "variable"]), "config.credentialReference");
115
+ if (credential.kind !== "environment") {
116
+ throw new Error("config.credentialReference.kind must be environment");
117
+ }
118
+ const variable = requiredString(credential.variable, "config.credentialReference.variable", 128);
119
+ if (!/^[A-Z][A-Z0-9_]*$/.test(variable)) {
120
+ throw new Error("config.credentialReference.variable must be an uppercase environment variable name");
121
+ }
122
+ const projectId = safeIdentifier(value.projectId, "config.projectId");
123
+ const projectName = value.projectName === void 0 ? void 0 : safeText(value.projectName, "config.projectName", 160);
124
+ const deviceId = safeIdentifier(value.deviceId, "config.deviceId");
125
+ const provider = safeText(value.provider, "config.provider", 80);
126
+ const model = safeText(value.model, "config.model", 160);
127
+ const common = {
128
+ enabled: true,
129
+ apiUrl: normalizeApiUrl(requiredString(value.apiUrl, "config.apiUrl", 2048)),
130
+ credentialReference: { kind: "environment", variable },
131
+ projectId,
132
+ ...projectName === void 0 ? {} : { projectName },
133
+ deviceId,
134
+ workspacePath,
135
+ provider,
136
+ model,
137
+ pollIntervalMs: boundedInteger(value.pollIntervalMs, 1e3, 50, 6e4, "config.pollIntervalMs"),
138
+ pollLimit: boundedInteger(value.pollLimit, 200, 1, 500, "config.pollLimit"),
139
+ shareToolEvents: optionalBoolean(value.shareToolEvents, false, "config.shareToolEvents")
140
+ };
141
+ if (bindingMode === "project") {
142
+ const stateRoot = absolutePath(value.stateRoot, "config.stateRoot");
143
+ if (isWithin(workspacePath, stateRoot)) {
144
+ throw new Error("config.stateRoot must be outside the read-only workspace");
145
+ }
146
+ const retryBaseMs = boundedInteger(value.retryBaseMs, 1e3, 50, 6e4, "config.retryBaseMs");
147
+ const retryMaxMs = boundedInteger(value.retryMaxMs, 3e4, 50, 3e5, "config.retryMaxMs");
148
+ if (retryMaxMs < retryBaseMs) {
149
+ throw new Error("config.retryMaxMs must be greater than or equal to config.retryBaseMs");
150
+ }
151
+ return {
152
+ ...common,
153
+ bindingMode,
154
+ stateRoot,
155
+ refreshIntervalMs: boundedInteger(value.refreshIntervalMs, 5e3, 250, 3e5, "config.refreshIntervalMs"),
156
+ maxConcurrentSessions: boundedInteger(value.maxConcurrentSessions, 4, 1, 32, "config.maxConcurrentSessions"),
157
+ retryBaseMs,
158
+ retryMaxMs
159
+ };
160
+ }
161
+ const sessionId = safeIdentifier(value.sessionId, "config.sessionId");
162
+ const statePath = absolutePath(value.statePath, "config.statePath");
163
+ if (isWithin(workspacePath, statePath)) {
164
+ throw new Error("config.statePath must be outside the read-only workspace");
165
+ }
166
+ return {
167
+ ...common,
168
+ bindingMode,
169
+ sessionId,
170
+ statePath,
171
+ dshSessionId: deriveDshSessionId(projectId, sessionId, workspacePath)
172
+ };
173
+ }
174
+ function assertSafeDshEnvironment(env = process.env) {
175
+ const telemetryDisabled = env.DSH_TELEMETRY_DISABLED === "1" || env.DSH_TELEMETRY_MODE?.trim().toUpperCase() === "DISABLED";
176
+ if (!telemetryDisabled) {
177
+ throw new Error("DeepSeek Harness telemetry must be disabled for the GatherThread host plugin");
178
+ }
179
+ if (env.DSH_PERMISSION_MODE?.trim().toLowerCase() !== "read-only") {
180
+ throw new Error("DSH_PERMISSION_MODE must be read-only for the GatherThread host plugin MVP");
181
+ }
182
+ }
183
+ async function assertSafeDshPaths(config) {
184
+ await assertRealStateSeparation(config.workspacePath, config.statePath, "config.statePath");
185
+ await secureDirectoryChain(path2.dirname(config.statePath));
186
+ try {
187
+ const target = await lstat2(config.statePath);
188
+ if (target.isSymbolicLink()) {
189
+ throw new Error("config.statePath must never be a symbolic link");
190
+ }
191
+ } catch (error) {
192
+ if (isNodeError2(error) && error.code === "ENOENT")
193
+ return;
194
+ throw error;
195
+ }
196
+ }
197
+ async function assertSafeDshStateRoot(config) {
198
+ await assertRealStateSeparation(config.workspacePath, config.stateRoot, "config.stateRoot");
199
+ await secureDirectoryChain(config.stateRoot);
200
+ try {
201
+ const target = await lstat2(config.stateRoot);
202
+ if (target.isSymbolicLink() || !target.isDirectory()) {
203
+ throw new Error("config.stateRoot must be a real directory, never a symbolic link");
204
+ }
205
+ if (process.platform !== "win32" && (Number(target.mode) & 511) !== 448) {
206
+ throw new Error("config.stateRoot permissions must be 0700");
207
+ }
208
+ } catch (error) {
209
+ if (isNodeError2(error) && error.code === "ENOENT")
210
+ return;
211
+ throw error;
212
+ }
213
+ }
214
+ async function assertRealStateSeparation(workspacePath, stateLocation, label) {
215
+ const [workspaceRealPath, stateRealPath] = await Promise.all([
216
+ prospectiveRealPath(workspacePath),
217
+ prospectiveRealPath(stateLocation)
218
+ ]);
219
+ if (isWithin(workspaceRealPath, stateRealPath)) {
220
+ throw new Error(`${label} real path must be outside the read-only workspace`);
221
+ }
222
+ }
223
+ function rejectModeKeys(value, forbidden, bindingMode) {
224
+ const present = forbidden.filter((key) => value[key] !== void 0);
225
+ if (present.length > 0) {
226
+ throw new Error(`config bindingMode ${bindingMode} does not accept: ${present.join(", ")}`);
227
+ }
228
+ }
229
+ function resolveCredentialReference(reference, env = process.env) {
230
+ if (reference.kind !== "environment") {
231
+ throw new Error("DSH grant credentials must be resolved by the native Host boundary");
232
+ }
233
+ const credential = env[reference.variable];
234
+ if (!credential || !credential.trim() || /[\r\n]/.test(credential)) {
235
+ throw new Error(`credential reference ${reference.variable} is missing or invalid`);
236
+ }
237
+ return credential;
238
+ }
239
+ function deriveDshSessionId(projectId, sessionId, workspacePath) {
240
+ const digest2 = createHash("sha256").update(JSON.stringify([projectId, sessionId, path2.resolve(workspacePath)])).digest("hex").slice(0, 32);
241
+ return `gatherthread-${digest2}`;
242
+ }
243
+ function normalizeApiUrl(value) {
244
+ let url;
245
+ try {
246
+ url = new URL(value);
247
+ } catch {
248
+ throw new Error("config.apiUrl must be an absolute HTTP(S) URL");
249
+ }
250
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
251
+ throw new Error("config.apiUrl must use HTTP or HTTPS");
252
+ }
253
+ if (url.username || url.password || url.search || url.hash) {
254
+ throw new Error("config.apiUrl cannot contain credentials, a query, or a fragment");
255
+ }
256
+ if (url.protocol === "http:" && !isLoopback(url.hostname)) {
257
+ throw new Error("config.apiUrl must use HTTPS except for a loopback host");
258
+ }
259
+ return url.toString().replace(/\/$/, "");
260
+ }
261
+ function isLoopback(hostname) {
262
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
263
+ }
264
+ function safeIdentifier(value, label) {
265
+ const text = requiredString(value, label, 128);
266
+ if (!/^[A-Za-z0-9._:-]+$/.test(text))
267
+ throw new Error(`${label} contains unsupported characters`);
268
+ return text;
269
+ }
270
+ function safeText(value, label, maximum) {
271
+ const text = requiredString(value, label, maximum);
272
+ if (/[\u0000-\u001f\u007f-\u009f]/u.test(text))
273
+ throw new Error(`${label} contains control characters`);
274
+ return text;
275
+ }
276
+ function absolutePath(value, label) {
277
+ const text = requiredString(value, label, 4096);
278
+ if (!path2.isAbsolute(text))
279
+ throw new Error(`${label} must be absolute`);
280
+ return path2.resolve(text);
281
+ }
282
+ function isWithin(parent, candidate) {
283
+ const relative = path2.relative(parent, candidate);
284
+ return relative === "" || !relative.startsWith(`..${path2.sep}`) && relative !== "..";
285
+ }
286
+ function isNodeError2(error) {
287
+ return error instanceof Error && "code" in error;
288
+ }
289
+ function boundedInteger(value, fallback, minimum, maximum, label) {
290
+ if (value === void 0)
291
+ return fallback;
292
+ if (!Number.isSafeInteger(value) || Number(value) < minimum || Number(value) > maximum) {
293
+ throw new Error(`${label} must be an integer from ${minimum} to ${maximum}`);
294
+ }
295
+ return Number(value);
296
+ }
297
+ function optionalBoolean(value, fallback, label) {
298
+ if (value === void 0)
299
+ return fallback;
300
+ if (typeof value !== "boolean")
301
+ throw new Error(`${label} must be true or false`);
302
+ return value;
303
+ }
304
+ function requiredString(value, label, maximum) {
305
+ if (typeof value !== "string" || !value.trim() || value.length > maximum) {
306
+ throw new Error(`${label} must be a non-empty string no longer than ${maximum} characters`);
307
+ }
308
+ return value.trim();
309
+ }
310
+ function requiredObject(value, label) {
311
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
312
+ throw new Error(`${label} must be an object`);
313
+ }
314
+ return value;
315
+ }
316
+ function rejectUnknownKeys(value, allowed, label) {
317
+ const unknown = Object.keys(value).filter((key) => !allowed.has(key));
318
+ if (unknown.length > 0)
319
+ throw new Error(`${label} contains unsupported keys: ${unknown.join(", ")}`);
320
+ }
321
+
322
+ // ../bridge/dist/src/http-client.js
323
+ import { createHash as createHash2 } from "node:crypto";
324
+ var CollaborationHttpError = class extends Error {
325
+ status;
326
+ code;
327
+ constructor(status, message, code) {
328
+ super(message);
329
+ this.name = "CollaborationHttpError";
330
+ this.status = status;
331
+ this.code = code;
332
+ }
333
+ };
334
+ var HttpCollaborationClient = class {
335
+ #baseUrl;
336
+ #bearerToken;
337
+ #fetch;
338
+ #signal;
339
+ #requestTimeoutMs;
340
+ constructor(options) {
341
+ const parsedBaseUrl = validateBaseUrl(options.baseUrl);
342
+ if (!options.bearerToken || /[\r\n]/.test(options.bearerToken)) {
343
+ throw new Error("bearerToken must be a non-empty HTTP bearer token");
344
+ }
345
+ if (options.requestTimeoutMs !== void 0 && (!Number.isSafeInteger(options.requestTimeoutMs) || options.requestTimeoutMs < 1)) {
346
+ throw new Error("requestTimeoutMs must be a positive integer");
347
+ }
348
+ this.#baseUrl = parsedBaseUrl;
349
+ this.#bearerToken = options.bearerToken;
350
+ this.#fetch = options.fetch ?? globalThis.fetch;
351
+ this.#signal = options.signal;
352
+ this.#requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
353
+ }
354
+ async listSessions() {
355
+ const body = await this.#request("/sessions");
356
+ const sessions = isObject(body) && Array.isArray(body.sessions) ? body.sessions : body;
357
+ if (!Array.isArray(sessions))
358
+ throw new Error("Collaboration API returned an invalid session list");
359
+ return sessions.map(fromWireSession);
360
+ }
361
+ async listProjects() {
362
+ const body = await this.#request("/projects");
363
+ const projects = isObject(body) && Array.isArray(body.projects) ? body.projects : body;
364
+ if (!Array.isArray(projects))
365
+ throw new Error("Collaboration API returned an invalid project list");
366
+ return projects.map(fromWireProject);
367
+ }
368
+ async listProjectSessions(projectId) {
369
+ const body = await this.#request(`/projects/${encodeURIComponent(projectId)}/sessions`);
370
+ const sessions = isObject(body) && Array.isArray(body.sessions) ? body.sessions : body;
371
+ if (!Array.isArray(sessions))
372
+ throw new Error("Collaboration API returned an invalid project session list");
373
+ return sessions.map(fromWireSession);
374
+ }
375
+ async listSessionMembers(sessionId) {
376
+ const body = await this.#request(`/sessions/${encodeURIComponent(sessionId)}/members`);
377
+ const members = isObject(body) && Array.isArray(body.members) ? body.members : body;
378
+ if (!Array.isArray(members))
379
+ throw new Error("Collaboration API returned an invalid member list");
380
+ return members.map(fromWireSessionMember);
381
+ }
382
+ async createSession(projectId, input) {
383
+ const body = requiredObject2(await this.#request(`/projects/${encodeURIComponent(projectId)}/sessions`, {
384
+ method: "POST",
385
+ body: JSON.stringify({
386
+ ...input.sessionId === void 0 ? {} : { session_id: input.sessionId },
387
+ title: input.title,
388
+ mode: input.mode,
389
+ idempotency_key: input.idempotencyKey
390
+ })
391
+ }));
392
+ return fromWireSession(body.session ?? body);
393
+ }
394
+ async updateSession(sessionId, input) {
395
+ const body = requiredObject2(await this.#request(`/sessions/${encodeURIComponent(sessionId)}`, {
396
+ method: "PATCH",
397
+ body: JSON.stringify({
398
+ title: input.title,
399
+ idempotency_key: input.idempotencyKey
400
+ })
401
+ }));
402
+ return fromWireSession(body.session ?? body);
403
+ }
404
+ async getCurrentActor() {
405
+ const body = requiredObject2(await this.#request("/me"));
406
+ return {
407
+ id: requiredString2(body.id, "actor.id"),
408
+ displayName: requiredString2(body.username, "actor.username"),
409
+ deviceId: requiredString2(body.device_id, "actor.device_id")
410
+ };
411
+ }
412
+ async readEvents(sessionId, afterSequence, limit = 200) {
413
+ const query = new URLSearchParams({
414
+ after_sequence: String(afterSequence),
415
+ limit: String(limit)
416
+ });
417
+ const body = requiredObject2(await this.#request(`/sessions/${encodeURIComponent(sessionId)}/events?${query}`));
418
+ const events = requiredArray(body.events).map(fromWireEvent);
419
+ return {
420
+ events,
421
+ nextSequence: requiredNumber(body.cursor, "cursor"),
422
+ hasMore: body.has_more === true
423
+ };
424
+ }
425
+ async appendEvent(sessionId, event) {
426
+ const body = requiredObject2(await this.#request(`/sessions/${encodeURIComponent(sessionId)}/events`, {
427
+ method: "POST",
428
+ body: JSON.stringify(toWireEvent(event))
429
+ }));
430
+ return fromWireEvent(body.event ?? body);
431
+ }
432
+ async registerRuntime(runtime) {
433
+ const body = requiredObject2(await this.#request("/runtimes", {
434
+ method: "POST",
435
+ body: JSON.stringify(toSnakeCase(runtime))
436
+ }));
437
+ return fromWireRuntime(body.runtime ?? body);
438
+ }
439
+ async heartbeatRuntime(runtimeId) {
440
+ const body = requiredObject2(await this.#request(`/runtimes/${encodeURIComponent(runtimeId)}/heartbeat`, {
441
+ method: "POST",
442
+ body: "{}"
443
+ }));
444
+ return fromWireRuntime(body.runtime ?? body);
445
+ }
446
+ async claimAgentRequest(sessionId, requestId, runtimeId) {
447
+ const body = requiredObject2(await this.#request(`/sessions/${encodeURIComponent(sessionId)}/agent-requests/${encodeURIComponent(requestId)}/claim`, {
448
+ method: "POST",
449
+ body: JSON.stringify({ runtime_id: runtimeId })
450
+ }));
451
+ const status = body.status === "completed" ? "completed" : "claimed";
452
+ return {
453
+ claimed: status === "claimed",
454
+ status,
455
+ requestId: requiredString2(body.request_event_id, "claim.request_event_id"),
456
+ runtimeId: requiredString2(body.runtime_id, "claim.runtime_id")
457
+ };
458
+ }
459
+ async completeAgentRequest(sessionId, requestId, input) {
460
+ const body = requiredObject2(await this.#request(`/sessions/${encodeURIComponent(sessionId)}/agent-requests/${encodeURIComponent(requestId)}/complete`, {
461
+ method: "POST",
462
+ body: JSON.stringify({
463
+ runtime_id: input.runtimeId,
464
+ idempotency_key: input.idempotencyKey,
465
+ payload: truncateJsonValue(input.payload, 160 * 1024),
466
+ ...input.observedModel === void 0 ? {} : { observed_model: input.observedModel },
467
+ ...input.observedReasoningEffort === void 0 ? {} : { observed_reasoning_effort: input.observedReasoningEffort }
468
+ })
469
+ }));
470
+ return fromWireEvent(body.event ?? body);
471
+ }
472
+ async appendAgentProgress(sessionId, requestId, input) {
473
+ const body = requiredObject2(await this.#request(`/sessions/${encodeURIComponent(sessionId)}/agent-requests/${encodeURIComponent(requestId)}/progress`, {
474
+ method: "POST",
475
+ body: JSON.stringify({
476
+ runtime_id: input.runtimeId,
477
+ idempotency_key: input.idempotencyKey,
478
+ payload: truncateJsonValue(input.payload, 32 * 1024),
479
+ ...input.observedModel === void 0 ? {} : { observed_model: input.observedModel },
480
+ ...input.observedReasoningEffort === void 0 ? {} : { observed_reasoning_effort: input.observedReasoningEffort }
481
+ })
482
+ }));
483
+ return fromWireEvent(body.event ?? body);
484
+ }
485
+ async commitLocalTurn(sessionId, input) {
486
+ const toolEvents = (input.toolEvents ?? []).slice(0, 32).map(boundLocalToolEvent);
487
+ const body = requiredObject2(await this.#request(`/sessions/${encodeURIComponent(sessionId)}/local-turns`, {
488
+ method: "POST",
489
+ body: JSON.stringify({
490
+ local_turn_id: input.localTurnId,
491
+ runtime_id: input.runtimeId,
492
+ based_on_sequence: input.basedOnSequence,
493
+ occurred_at: input.occurredAt,
494
+ ...input.observedModel === void 0 ? {} : { observed_model: input.observedModel },
495
+ ...input.observedReasoningEffort === void 0 ? {} : { observed_reasoning_effort: input.observedReasoningEffort },
496
+ request_payload: truncateJsonValue(input.requestPayload, 32 * 1024),
497
+ response_payload: truncateJsonValue(input.responsePayload, 112 * 1024),
498
+ ...input.toolEvents === void 0 ? {} : { tool_events: toolEvents }
499
+ })
500
+ }));
501
+ return {
502
+ localTurnId: requiredString2(body.local_turn_id, "local_turn.local_turn_id"),
503
+ runtimeId: requiredString2(body.runtime_id, "local_turn.runtime_id"),
504
+ headBeforeCommit: requiredNumber(body.head_before_commit, "local_turn.head_before_commit"),
505
+ reconciliationRequired: body.reconciliation_required === true,
506
+ requestEvent: fromWireEvent(body.request_event),
507
+ responseEvent: fromWireEvent(body.response_event),
508
+ toolEvents: Array.isArray(body.tool_events) ? body.tool_events.map(fromWireEvent) : []
509
+ };
510
+ }
511
+ async getSnapshotRequest(requestId) {
512
+ const body = requiredObject2(await this.#request(`/snapshot-requests/${encodeURIComponent(requestId)}`));
513
+ return fromWireSnapshotRequest(body.snapshot_request ?? body);
514
+ }
515
+ async createSnapshotRequest(sessionId) {
516
+ const body = requiredObject2(await this.#request(`/sessions/${encodeURIComponent(sessionId)}/snapshot-requests`, {
517
+ method: "POST",
518
+ body: "{}"
519
+ }));
520
+ return fromWireSnapshotRequest(body.snapshot_request ?? body);
521
+ }
522
+ async listSnapshotRequests(status, limit = 20) {
523
+ const query = new URLSearchParams({ status, limit: String(limit) });
524
+ const body = await this.#request(`/snapshot-requests?${query}`);
525
+ const requests = isObject(body) && Array.isArray(body.snapshot_requests) ? body.snapshot_requests : body;
526
+ if (!Array.isArray(requests))
527
+ throw new Error("Collaboration API returned an invalid snapshot request list");
528
+ return requests.map(fromWireSnapshotRequest);
529
+ }
530
+ async claimSnapshotRequest(requestId, runtimeId) {
531
+ return this.#mutateSnapshotRequest(requestId, "claim", { runtime_id: runtimeId });
532
+ }
533
+ async completeSnapshotRequest(requestId, runtimeId, result) {
534
+ return this.#mutateSnapshotRequest(requestId, "complete", { runtime_id: runtimeId, result });
535
+ }
536
+ async failSnapshotRequest(requestId, runtimeId, error) {
537
+ return this.#mutateSnapshotRequest(requestId, "fail", { runtime_id: runtimeId, error });
538
+ }
539
+ async #mutateSnapshotRequest(requestId, action, input) {
540
+ const body = requiredObject2(await this.#request(`/snapshot-requests/${encodeURIComponent(requestId)}/${action}`, {
541
+ method: "POST",
542
+ body: JSON.stringify(input)
543
+ }));
544
+ return fromWireSnapshotRequest(body.snapshot_request ?? body);
545
+ }
546
+ async #request(path5, init = {}) {
547
+ const signal = combineSignals(init.signal ?? void 0, this.#signal, AbortSignal.timeout(this.#requestTimeoutMs));
548
+ const response = await this.#fetch(`${this.#baseUrl}${path5}`, {
549
+ ...init,
550
+ redirect: "error",
551
+ signal,
552
+ headers: {
553
+ accept: "application/json",
554
+ authorization: `Bearer ${this.#bearerToken}`,
555
+ ...init.body ? { "content-type": "application/json" } : {},
556
+ ...init.headers
557
+ }
558
+ });
559
+ const body = await readResponseBody(response);
560
+ if (!response.ok) {
561
+ const detail = isObject(body) && typeof body.error === "string" ? body.error : isObject(body) && isObject(body.error) && typeof body.error.message === "string" ? body.error.message : response.statusText;
562
+ const code = isObject(body) && isObject(body.error) && typeof body.error.code === "string" ? body.error.code : void 0;
563
+ throw new CollaborationHttpError(response.status, `Collaboration API ${response.status}: ${redactCredential(detail, this.#bearerToken)}`, code);
564
+ }
565
+ return isObject(body) && "data" in body ? body.data : body;
566
+ }
567
+ };
568
+ function validateBaseUrl(value) {
569
+ let url;
570
+ try {
571
+ url = new URL(value);
572
+ } catch {
573
+ throw new Error("baseUrl must be an absolute HTTP(S) URL");
574
+ }
575
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
576
+ throw new Error("baseUrl must use HTTP or HTTPS");
577
+ }
578
+ if (url.username || url.password || url.search || url.hash) {
579
+ throw new Error("baseUrl cannot contain credentials, a query, or a fragment");
580
+ }
581
+ return url.toString().replace(/\/$/, "");
582
+ }
583
+ function combineSignals(...signals) {
584
+ const present = signals.filter((signal) => signal !== void 0);
585
+ return present.length === 1 ? present[0] : AbortSignal.any(present);
586
+ }
587
+ function redactCredential(value, credential) {
588
+ return value.replaceAll(credential, "[REDACTED]").replace(/[\r\n]+/g, " ");
589
+ }
590
+ function toWireEvent(event) {
591
+ return {
592
+ type: event.type,
593
+ idempotency_key: event.idempotencyKey,
594
+ payload: event.payload,
595
+ reply_to_event_id: event.replyTo,
596
+ visibility: event.visibility ?? "session",
597
+ runtime_id: event.runtimeId ?? event.runtime?.runtimeId,
598
+ observed_model: event.observedModel,
599
+ observed_reasoning_effort: event.observedReasoningEffort
600
+ };
601
+ }
602
+ function toSnakeCase(value) {
603
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
604
+ key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`),
605
+ item
606
+ ]));
607
+ }
608
+ function fromWireEvent(value) {
609
+ const input = requiredObject2(value);
610
+ const actorDisplayName = optionalWireString(input.actor_display_name) ?? optionalWireString(input.actor_username);
611
+ const runtime = input.runtime_provenance === null || input.runtime_provenance === void 0 ? void 0 : fromWireProvenance(input.runtime_provenance);
612
+ return {
613
+ id: requiredString2(input.id, "event.id"),
614
+ sessionId: requiredString2(input.session_id, "event.session_id"),
615
+ sequence: requiredNumber(input.sequence, "event.sequence"),
616
+ type: requiredString2(input.type, "event.type"),
617
+ actorId: requiredString2(input.actor_user_id, "event.actor_user_id"),
618
+ ...actorDisplayName === void 0 ? {} : { actorDisplayName },
619
+ timestamp: requiredString2(input.created_at, "event.created_at"),
620
+ payload: input.payload,
621
+ ...runtime === void 0 ? {} : { runtime }
622
+ };
623
+ }
624
+ function fromWireSnapshotRequest(value) {
625
+ const input = requiredObject2(value);
626
+ const status = requiredString2(input.status, "snapshot_request.status");
627
+ if (status !== "pending" && status !== "claimed" && status !== "completed" && status !== "failed") {
628
+ throw new Error("Collaboration API returned an invalid snapshot request status");
629
+ }
630
+ const failureInput = isObject(input.failure) ? input.failure : isObject(input.error) ? input.error : void 0;
631
+ const failure = failureInput ? {
632
+ code: requiredString2(failureInput.code, "snapshot_request.failure.code"),
633
+ message: requiredString2(failureInput.message, "snapshot_request.failure.message")
634
+ } : void 0;
635
+ const createdAt = optionalWireString(input.created_at) ?? optionalWireString(input.requested_at);
636
+ return {
637
+ id: requiredString2(input.id, "snapshot_request.id"),
638
+ sessionId: requiredString2(input.session_id, "snapshot_request.session_id"),
639
+ throughSequence: requiredNumber(input.through_sequence, "snapshot_request.through_sequence"),
640
+ status,
641
+ ...createdAt === void 0 ? {} : { createdAt },
642
+ ..."result" in input ? { result: input.result } : {},
643
+ ...failure === void 0 ? {} : { failure }
644
+ };
645
+ }
646
+ function fromWireSession(value) {
647
+ const input = requiredObject2(value);
648
+ const name2 = optionalWireString(input.title) ?? optionalWireString(input.name);
649
+ const role = optionalWireString(input.role);
650
+ const latestSequence = typeof input.current_sequence === "number" ? input.current_sequence : typeof input.next_sequence === "number" ? input.next_sequence : void 0;
651
+ return {
652
+ id: requiredString2(input.id, "session.id"),
653
+ ...typeof input.project_id === "string" ? { projectId: input.project_id } : {},
654
+ ...typeof input.owner_user_id === "string" ? { ownerUserId: input.owner_user_id } : {},
655
+ mode: requiredString2(input.mode, "session.mode"),
656
+ ...input.state === "active" || input.state === "archived" ? { state: input.state } : {},
657
+ ...name2 === void 0 ? {} : { name: name2 },
658
+ ...role === void 0 ? {} : { role },
659
+ ...latestSequence === void 0 ? {} : { latestSequence }
660
+ };
661
+ }
662
+ function fromWireProject(value) {
663
+ const input = requiredObject2(value);
664
+ return {
665
+ id: requiredString2(input.id, "project.id"),
666
+ name: requiredString2(input.title, "project.title"),
667
+ role: requiredString2(input.role, "project.role"),
668
+ state: requiredString2(input.state, "project.state"),
669
+ sessionCount: requiredNumber(input.session_count, "project.session_count")
670
+ };
671
+ }
672
+ function fromWireSessionMember(value) {
673
+ const input = requiredObject2(value);
674
+ let runtime = null;
675
+ if (input.runtime !== null && input.runtime !== void 0) {
676
+ const wireRuntime = requiredObject2(input.runtime);
677
+ runtime = {
678
+ purpose: wireRuntime.purpose === "snapshot_connector" ? "snapshot_connector" : "execution",
679
+ harness: requiredString2(wireRuntime.harness, "member.runtime.harness"),
680
+ provider: requiredString2(wireRuntime.provider, "member.runtime.provider"),
681
+ model: requiredString2(wireRuntime.model, "member.runtime.model"),
682
+ status: wireRuntime.status === "online" ? "online" : "offline"
683
+ };
684
+ }
685
+ return {
686
+ displayName: requiredString2(input.display_name, "member.display_name"),
687
+ role: requiredString2(input.role, "member.role"),
688
+ runtime
689
+ };
690
+ }
691
+ function fromWireRuntime(value) {
692
+ const input = requiredObject2(value);
693
+ const purpose = input.purpose === "snapshot_connector" ? "snapshot_connector" : "execution";
694
+ return {
695
+ id: requiredString2(input.id, "runtime.id"),
696
+ userId: requiredString2(input.user_id, "runtime.user_id"),
697
+ runtimeId: requiredString2(input.id, "runtime.id"),
698
+ sessionId: requiredString2(input.session_id, "runtime.session_id"),
699
+ deviceId: requiredString2(input.device_id, "runtime.device_id"),
700
+ harness: requiredString2(input.harness, "runtime.harness"),
701
+ provider: requiredString2(input.provider, "runtime.provider"),
702
+ model: requiredString2(input.model, "runtime.model"),
703
+ localSessionId: requiredString2(input.local_session_id, "runtime.local_session_id"),
704
+ captureFidelity: requiredString2(input.capture_fidelity, "runtime.capture_fidelity"),
705
+ purpose
706
+ };
707
+ }
708
+ function fromWireProvenance(value) {
709
+ const input = requiredObject2(value);
710
+ return {
711
+ userId: requiredString2(input.user_id, "runtime_provenance.user_id"),
712
+ deviceId: requiredString2(input.device_id, "runtime_provenance.device_id"),
713
+ runtimeId: requiredString2(input.runtime_id, "runtime_provenance.runtime_id"),
714
+ harness: requiredString2(input.harness, "runtime_provenance.harness"),
715
+ provider: requiredString2(input.provider, "runtime_provenance.provider"),
716
+ model: requiredString2(input.model, "runtime_provenance.model"),
717
+ ...typeof input.reasoning_effort === "string" ? { reasoningEffort: input.reasoning_effort } : {},
718
+ localSessionId: requiredString2(input.local_session_id, "runtime_provenance.local_session_id"),
719
+ captureFidelity: requiredString2(input.capture_fidelity, "runtime_provenance.capture_fidelity")
720
+ };
721
+ }
722
+ function requiredObject2(value) {
723
+ if (!isObject(value))
724
+ throw new Error("Collaboration API returned an invalid object");
725
+ return value;
726
+ }
727
+ function requiredArray(value) {
728
+ if (!Array.isArray(value))
729
+ throw new Error("Collaboration API returned an invalid array");
730
+ return value;
731
+ }
732
+ function requiredString2(value, field) {
733
+ if (typeof value !== "string")
734
+ throw new Error(`Collaboration API omitted ${field}`);
735
+ return value;
736
+ }
737
+ function requiredNumber(value, field) {
738
+ if (!Number.isSafeInteger(value))
739
+ throw new Error(`Collaboration API omitted ${field}`);
740
+ return Number(value);
741
+ }
742
+ function optionalWireString(value) {
743
+ return typeof value === "string" ? value : void 0;
744
+ }
745
+ function truncateJsonValue(value, maxBytes) {
746
+ let encoded;
747
+ try {
748
+ encoded = JSON.stringify(value);
749
+ } catch {
750
+ encoded = JSON.stringify("[unserializable]");
751
+ }
752
+ if (Buffer.byteLength(encoded) <= maxBytes)
753
+ return value;
754
+ const originalBytes = Buffer.byteLength(encoded);
755
+ const sha256 = createHash2("sha256").update(encoded).digest("hex");
756
+ let end = Math.min(encoded.length, Math.max(0, maxBytes - 256));
757
+ while (end > 0 && Buffer.byteLength(encoded.slice(0, end)) > maxBytes - 256)
758
+ end -= 1;
759
+ return { truncated: true, original_bytes: originalBytes, sha256, preview: encoded.slice(0, end) };
760
+ }
761
+ function boundLocalToolEvent(value) {
762
+ if (!isObject(value) || value.type !== "tool_call" && value.type !== "tool_result") {
763
+ return { type: "tool_result", payload: truncateJsonValue(value, 768) };
764
+ }
765
+ return {
766
+ type: value.type,
767
+ payload: truncateJsonValue(value.payload, 768),
768
+ ...typeof value.occurred_at === "string" ? { occurred_at: value.occurred_at } : {}
769
+ };
770
+ }
771
+ async function readResponseBody(response) {
772
+ const text = await response.text();
773
+ if (!text)
774
+ return void 0;
775
+ try {
776
+ return JSON.parse(text);
777
+ } catch {
778
+ return text;
779
+ }
780
+ }
781
+ function isObject(value) {
782
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
783
+ }
784
+
785
+ // ../adapters/dist/src/redaction.js
786
+ var REDACTED = "[REDACTED]";
787
+ var DEFAULT_SECRET_PATTERNS = [
788
+ /-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/g,
789
+ /\bAKIA[0-9A-Z]{16}\b/g,
790
+ /\b(?:ghp|github_pat|glpat|sk|xox[baprs])[-_][A-Za-z0-9_-]{16,}\b/g,
791
+ /\bBearer\s+[A-Za-z0-9._~+/=-]{8,}\b/gi,
792
+ /\b(?:gt[abid]|acp(?:i|d)?)_[A-Za-z0-9_-]{20,}\b/g,
793
+ /\b((?:api[_-]?key|access[_-]?token|auth[_-]?token|client[_-]?secret|password|passwd|secret|token)\s*[=:]\s*)[^\s,;]+/gi,
794
+ /\b([A-Z][A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|PASSWD|API_KEY|AUTH_TOKEN_PEPPER)\s*[=:]\s*)[^\s,;]+/g
795
+ ];
796
+ var SENSITIVE_KEY_NAMES = /* @__PURE__ */ new Set([
797
+ "apikey",
798
+ "accesstoken",
799
+ "authtoken",
800
+ "authorization",
801
+ "bearer",
802
+ "clientsecret",
803
+ "password",
804
+ "passwd",
805
+ "privatekey",
806
+ "refreshtoken",
807
+ "secret",
808
+ "token",
809
+ "thinking",
810
+ "rawthinking",
811
+ "systemprompt",
812
+ "developerprompt",
813
+ "localsessionid"
814
+ ]);
815
+ function redactText(value, options = {}) {
816
+ const replacement = options.replacement ?? REDACTED;
817
+ return [...DEFAULT_SECRET_PATTERNS, ...options.extraPatterns ?? []].reduce((redacted, pattern) => redacted.replace(cloneGlobal(pattern), (match, prefix) => typeof prefix === "string" && prefix.length < match.length ? `${prefix}${replacement}` : replacement), value);
818
+ }
819
+ function redactValue(value, options = {}) {
820
+ if (typeof value === "string")
821
+ return redactText(value, options);
822
+ if (Array.isArray(value))
823
+ return value.map((item) => redactValue(item, options));
824
+ if (!value || typeof value !== "object")
825
+ return value;
826
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
827
+ key,
828
+ isSensitiveKey(key) ? options.replacement ?? REDACTED : redactValue(item, options)
829
+ ]));
830
+ }
831
+ function isSensitiveKey(key) {
832
+ const normalized = key.replace(/[^a-z0-9]/gi, "").toLowerCase();
833
+ return SENSITIVE_KEY_NAMES.has(normalized) || normalized.endsWith("token") || normalized.endsWith("secret") || normalized.endsWith("password") || normalized.endsWith("passwd") || normalized.endsWith("apikey") || normalized.endsWith("privatekey") || normalized.endsWith("tokenpepper") || normalized.endsWith("systemprompt") || normalized.endsWith("developerprompt") || normalized.endsWith("rawthinking");
834
+ }
835
+ function cloneGlobal(pattern) {
836
+ return new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
837
+ }
838
+
839
+ // ../adapters/dist/src/jsonl.js
840
+ var DEFAULT_MAX_READ_BYTES = 4 * 1024 * 1024;
841
+
842
+ // ../bridge/dist/src/codex-executor.js
843
+ var DEFAULT_TIMEOUT_MS = 30 * 60 * 1e3;
844
+ var DEFAULT_MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
845
+ var DEFAULT_MAX_PROMPT_BYTES = 8 * 1024 * 1024;
846
+ var DEFAULT_MAX_TOOL_OUTPUT_BYTES = 32 * 1024;
847
+
848
+ // ../bridge/dist/src/codex-hooks.js
849
+ var DEFAULT_SPOOL_MAX_BYTES = 8 * 1024 * 1024;
850
+
851
+ // ../bridge/dist/src/codex-app-server.js
852
+ var DEFAULT_TURN_TIMEOUT_MS = 30 * 60 * 1e3;
853
+ var DEFAULT_MAX_MESSAGE_BYTES = 16 * 1024 * 1024;
854
+ var DEFAULT_MAX_PROMPT_BYTES2 = 8 * 1024 * 1024;
855
+ var DEFAULT_MAX_INJECTION_ITEM_BYTES = 64 * 1024;
856
+ var DEFAULT_MAX_TOOL_OUTPUT_BYTES2 = 32 * 1024;
857
+ var MAX_LOCAL_TURN_UPLOAD_BYTES = 180 * 1024;
858
+ var DEFAULT_DESKTOP_RELAY_CONTEXT_BYTES = 7 * 1024;
859
+ var DESKTOP_RELAY_EVENT_CHUNK_BYTES = 16 * 1024;
860
+
861
+ // ../bridge/dist/src/project-session-permissions.js
862
+ function isSessionWritableBy(session, actorUserId) {
863
+ if (session.role === "viewer")
864
+ return false;
865
+ if (session.mode === "solo") {
866
+ return session.ownerUserId === void 0 ? session.role === "owner" : session.ownerUserId === actorUserId;
867
+ }
868
+ return session.role === "owner" || session.role === "participant";
869
+ }
870
+ async function reconcileProjectSessionPermissions(input) {
871
+ const visibleSessions = input.sessions.filter((session) => session.state !== "archived");
872
+ const eligibleSessions = visibleSessions.filter((session) => isSessionWritableBy(session, input.actorUserId));
873
+ const eligibleIds = new Set(eligibleSessions.map((session) => session.id));
874
+ const sessionsById = new Map(input.sessions.map((session) => [session.id, session]));
875
+ const errors = [];
876
+ for (const [sessionId, current] of [...input.managed]) {
877
+ if (eligibleIds.has(sessionId))
878
+ continue;
879
+ input.managed.delete(sessionId);
880
+ try {
881
+ await current.deactivateLocalPublishing?.(projectSessionDeactivationReason(sessionsById.get(sessionId)));
882
+ } catch (error) {
883
+ errors.push(error instanceof Error ? error : new Error("Local publishing deactivation failed"));
884
+ }
885
+ }
886
+ try {
887
+ await input.harness.deactivateExecutionBindings?.({
888
+ retainSessionIds: [...input.managed.keys()],
889
+ preserveSessionIds: [...eligibleIds]
890
+ });
891
+ } catch (error) {
892
+ errors.push(error instanceof Error ? error : new Error("Execution allowlist reconciliation failed"));
893
+ }
894
+ return { visibleSessions, eligibleSessions, errors };
895
+ }
896
+ async function refreshProjectSessionPermissions(input) {
897
+ let sessions;
898
+ try {
899
+ sessions = await input.loadSessions();
900
+ } catch (error) {
901
+ return isProjectAccessRevoked(error) ? { status: "project_inaccessible", error } : { status: "transient_failure", error };
902
+ }
903
+ return {
904
+ status: "updated",
905
+ ...await reconcileProjectSessionPermissions({
906
+ sessions,
907
+ ...input.actorUserId === void 0 ? {} : { actorUserId: input.actorUserId },
908
+ managed: input.managed,
909
+ harness: input.harness
910
+ })
911
+ };
912
+ }
913
+ function projectSessionDeactivationReason(session) {
914
+ if (!session)
915
+ return "removed";
916
+ if (session.state === "archived")
917
+ return "archived";
918
+ return "read_only";
919
+ }
920
+ function isProjectAccessRevoked(error) {
921
+ return error instanceof CollaborationHttpError && (error.status === 403 || error.status === 404);
922
+ }
923
+
924
+ // ../bridge/dist/src/local-api-relay.js
925
+ var MAX_FRAME_BYTES = 1024 * 1024;
926
+
927
+ // dist/src/collaboration-api.js
928
+ function createHttpDshCollaborationApi(options) {
929
+ const client = new HttpCollaborationClient({
930
+ baseUrl: options.baseUrl,
931
+ bearerToken: options.credential,
932
+ ...options.requestTimeoutMs === void 0 ? {} : { requestTimeoutMs: options.requestTimeoutMs },
933
+ ...options.fetch === void 0 ? {} : { fetch: options.fetch },
934
+ ...options.signal === void 0 ? {} : { signal: options.signal }
935
+ });
936
+ return {
937
+ ...adaptCollaborationApi(client),
938
+ getCurrentActor: () => client.getCurrentActor(),
939
+ createSession: (projectId, input) => client.createSession(projectId, input)
940
+ };
941
+ }
942
+ function adaptCollaborationApi(client) {
943
+ const listProjectSessions = client.listProjectSessions;
944
+ const heartbeatRuntime = client.heartbeatRuntime;
945
+ const appendAgentProgress = client.appendAgentProgress;
946
+ const commitLocalTurn = client.commitLocalTurn;
947
+ if (listProjectSessions === void 0 || heartbeatRuntime === void 0 || appendAgentProgress === void 0 || commitLocalTurn === void 0) {
948
+ throw new Error("GatherThread Collaboration API lacks required session, heartbeat, progress, or local-turn support");
949
+ }
950
+ return {
951
+ listProjectSessions: (projectId) => listProjectSessions.call(client, projectId),
952
+ readEvents: (sessionId, afterSequence, limit) => client.readEvents(sessionId, afterSequence, limit),
953
+ registerRuntime: async (input) => client.registerRuntime(input),
954
+ heartbeatRuntime: async (runtimeId) => heartbeatRuntime.call(client, runtimeId),
955
+ claimAgentRequest: (sessionId, requestId, runtimeId) => client.claimAgentRequest(sessionId, requestId, runtimeId),
956
+ appendAgentProgress: (sessionId, requestId, input) => appendAgentProgress.call(client, sessionId, requestId, input),
957
+ appendEvent: (sessionId, input) => client.appendEvent(sessionId, input),
958
+ completeAgentRequest: (sessionId, requestId, input) => client.completeAgentRequest(sessionId, requestId, input),
959
+ commitLocalTurn: (sessionId, input) => commitLocalTurn.call(client, sessionId, input)
960
+ };
961
+ }
962
+ function isTerminalClaimConflict(error) {
963
+ if (!error || typeof error !== "object")
964
+ return false;
965
+ const candidate = error;
966
+ return candidate.status === 409 && (candidate.code === "agent_request_already_claimed" || candidate.code === "agent_request_already_completed");
967
+ }
968
+
969
+ // dist/src/connector.js
970
+ import { createHash as createHash3 } from "node:crypto";
971
+
972
+ // dist/src/canonical-prompt.js
973
+ var MAX_EVENT_TEXT_BYTES = 8 * 1024;
974
+ var MAX_REQUEST_TEXT_BYTES = 32 * 1024;
975
+ var MAX_PROMPT_BYTES = 128 * 1024;
976
+ function requestedDshProfile(event) {
977
+ if (event.type !== "agent_request")
978
+ return void 0;
979
+ const payload = asObject(event.payload);
980
+ const profile = asObject(payload?.execution_profile);
981
+ if (profile === void 0)
982
+ return void 0;
983
+ const harness = safeProfileText(profile.harness, 80).toLowerCase();
984
+ if (harness !== "deepseek-harness")
985
+ return void 0;
986
+ const model = safeProfileText(profile.model, 160);
987
+ if (!model)
988
+ throw new Error("DeepSeek Harness Agent request must name a model");
989
+ const provider = profile.provider === void 0 ? void 0 : safeProfileText(profile.provider, 80);
990
+ if (profile.provider !== void 0 && !provider) {
991
+ throw new Error("DeepSeek Harness Agent request has an invalid provider target");
992
+ }
993
+ const runtimeId = profile.runtime_id === void 0 ? void 0 : safeRuntimeId(profile.runtime_id);
994
+ return {
995
+ harness,
996
+ ...provider === void 0 ? {} : { provider },
997
+ model,
998
+ ...runtimeId === void 0 ? {} : { runtimeId }
999
+ };
1000
+ }
1001
+ function buildDshCanonicalPrompt(history, request, ownRuntimeId) {
1002
+ if (request.type !== "agent_request")
1003
+ throw new Error("Expected an agent_request event");
1004
+ const requestText = extractPublicText(request.payload);
1005
+ if (!requestText)
1006
+ throw new Error("DeepSeek Harness Agent request has no public text content");
1007
+ const boundedRequest = boundText(redactText(requestText), MAX_REQUEST_TEXT_BYTES);
1008
+ const contextLines = history.filter((event) => event.sequence < request.sequence).filter((event) => !isAlreadyPresentLocalOutput(event, ownRuntimeId)).filter((event) => [
1009
+ "human_chat",
1010
+ "agent_progress",
1011
+ "agent_response",
1012
+ "tool_call",
1013
+ "tool_result"
1014
+ ].includes(event.type)).flatMap((event) => {
1015
+ const text = extractPublicEventText(event);
1016
+ if (!text)
1017
+ return [];
1018
+ const actor = boundText(redactText(event.actorDisplayName ?? event.actorId), 512);
1019
+ return [`[${event.sequence}] ${event.type} \xB7 ${actor}: ${boundText(redactText(text), MAX_EVENT_TEXT_BYTES)}`];
1020
+ });
1021
+ const header = [
1022
+ "GatherThread canonical context follows. It is collaboration data for this request.",
1023
+ "Do not reveal hidden reasoning, credentials, local paths, or private tool metadata in your answer.",
1024
+ "Context updates:"
1025
+ ].join("\n");
1026
+ const footer = [
1027
+ "Current GatherThread Agent request:",
1028
+ `[${request.sequence}] ${boundedRequest}`,
1029
+ "Return a public final answer for the shared GatherThread session."
1030
+ ].join("\n");
1031
+ const fixedBytes = Buffer.byteLength(`${header}
1032
+
1033
+ ${footer}`, "utf8");
1034
+ const contextBudget = Math.max(0, MAX_PROMPT_BYTES - fixedBytes - 64);
1035
+ const retained = [];
1036
+ let used = 0;
1037
+ for (const line of contextLines.toReversed()) {
1038
+ const bytes = Buffer.byteLength(`${line}
1039
+ `, "utf8");
1040
+ if (used + bytes > contextBudget)
1041
+ continue;
1042
+ retained.push(line);
1043
+ used += bytes;
1044
+ }
1045
+ retained.reverse();
1046
+ const omitted = retained.length < contextLines.length ? [`[${contextLines.length - retained.length} older public context update(s) omitted by size limit]`] : [];
1047
+ const prompt = `${header}
1048
+ ${[...omitted, ...retained].join("\n") || "[none]"}
1049
+
1050
+ ${footer}`;
1051
+ if (Buffer.byteLength(prompt, "utf8") > MAX_PROMPT_BYTES) {
1052
+ throw new Error("GatherThread canonical prompt exceeds the connector safety limit");
1053
+ }
1054
+ return prompt;
1055
+ }
1056
+ function buildDshRequestPrompt(request) {
1057
+ if (request.type !== "agent_request")
1058
+ throw new Error("Expected an agent_request event");
1059
+ const requestText = extractPublicText(request.payload);
1060
+ if (!requestText)
1061
+ throw new Error("DeepSeek Harness Agent request has no public text content");
1062
+ return boundText(redactText(requestText), MAX_REQUEST_TEXT_BYTES);
1063
+ }
1064
+ function extractPublicText(value) {
1065
+ if (typeof value === "string")
1066
+ return value;
1067
+ const payload = asObject(value);
1068
+ if (payload === void 0)
1069
+ return "";
1070
+ for (const key of ["content", "text", "message", "prompt", "response"]) {
1071
+ if (typeof payload[key] === "string")
1072
+ return payload[key];
1073
+ }
1074
+ return "";
1075
+ }
1076
+ function extractPublicEventText(event) {
1077
+ if (event.type !== "tool_call" && event.type !== "tool_result") {
1078
+ return extractPublicText(event.payload);
1079
+ }
1080
+ const payload = asObject(event.payload);
1081
+ if (payload === void 0)
1082
+ return "";
1083
+ const selected = event.type === "tool_call" ? compact({
1084
+ tool_name: stringValue(payload.tool_name),
1085
+ tool_call_id: stringValue(payload.tool_call_id),
1086
+ arguments: payload.arguments
1087
+ }) : compact({
1088
+ tool_call_id: stringValue(payload.tool_call_id),
1089
+ result: payload.result,
1090
+ is_error: typeof payload.is_error === "boolean" ? payload.is_error : void 0,
1091
+ error_code: stringValue(payload.error_code)
1092
+ });
1093
+ if (Object.keys(selected).length === 0)
1094
+ return "";
1095
+ return JSON.stringify(redactValue(selected));
1096
+ }
1097
+ function isAlreadyPresentLocalOutput(event, runtimeId) {
1098
+ return runtimeId !== void 0 && event.runtime?.runtimeId === runtimeId && ["agent_progress", "agent_response", "tool_call", "tool_result"].includes(event.type);
1099
+ }
1100
+ function safeProfileText(value, maximum) {
1101
+ if (typeof value !== "string")
1102
+ return "";
1103
+ const text = value.trim();
1104
+ if (!text || text.length > maximum || /[\u0000-\u001f\u007f-\u009f]/u.test(text)) {
1105
+ throw new Error("Agent request contains an invalid execution profile");
1106
+ }
1107
+ return text;
1108
+ }
1109
+ function safeRuntimeId(value) {
1110
+ if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value.trim())) {
1111
+ throw new Error("Agent request contains an invalid DeepSeek Harness runtime target");
1112
+ }
1113
+ return value.trim();
1114
+ }
1115
+ function stringValue(value) {
1116
+ return typeof value === "string" ? value : void 0;
1117
+ }
1118
+ function compact(value) {
1119
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
1120
+ }
1121
+ function boundText(value, maximumBytes) {
1122
+ if (Buffer.byteLength(value, "utf8") <= maximumBytes)
1123
+ return value;
1124
+ const suffix = "\n[TRUNCATED]";
1125
+ const target = maximumBytes - Buffer.byteLength(suffix, "utf8");
1126
+ let low = 0;
1127
+ let high = value.length;
1128
+ while (low < high) {
1129
+ const middle = Math.ceil((low + high) / 2);
1130
+ if (Buffer.byteLength(value.slice(0, middle), "utf8") <= target)
1131
+ low = middle;
1132
+ else
1133
+ high = middle - 1;
1134
+ }
1135
+ return `${value.slice(0, low)}${suffix}`;
1136
+ }
1137
+ function asObject(value) {
1138
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1139
+ }
1140
+
1141
+ // dist/src/event-mapper.js
1142
+ var DEFAULT_ASSISTANT_BYTES = 112 * 1024;
1143
+ var DEFAULT_TOOL_BYTES = 32 * 1024;
1144
+ function mapDshSessionEvent(event, options = {}) {
1145
+ const timestamp2 = safeTimestamp(event.time);
1146
+ if (timestamp2 === void 0)
1147
+ return void 0;
1148
+ const data = asObject2(event.data);
1149
+ if (data === void 0)
1150
+ return void 0;
1151
+ const assistantBytes = checkedLimit(options.maxAssistantBytes, DEFAULT_ASSISTANT_BYTES);
1152
+ const toolBytes = checkedLimit(options.maxToolBytes, DEFAULT_TOOL_BYTES);
1153
+ if (event.type === "assistant/message") {
1154
+ const message = asObject2(data.message);
1155
+ const content = visibleTextBlocks(message?.content);
1156
+ if (!content)
1157
+ return void 0;
1158
+ const mapped = {
1159
+ kind: "assistant",
1160
+ localEventId: `dsh-assistant-${event.seq}`,
1161
+ sequence: event.seq,
1162
+ timestamp: timestamp2,
1163
+ content: boundText2(redactText(content), assistantBytes)
1164
+ };
1165
+ return mapped;
1166
+ }
1167
+ if (event.type === "tool/call") {
1168
+ if (typeof data.callId !== "string" || typeof data.name !== "string" || typeof data.arguments !== "string") {
1169
+ return void 0;
1170
+ }
1171
+ const mapped = {
1172
+ kind: "tool_call",
1173
+ localEventId: `dsh-tool-call-${event.seq}`,
1174
+ sequence: event.seq,
1175
+ timestamp: timestamp2,
1176
+ toolName: boundText2(redactText(data.name), 512),
1177
+ toolCallId: boundText2(redactText(data.callId), 512),
1178
+ arguments: redactAndBound(parseToolArguments(data.arguments), toolBytes)
1179
+ };
1180
+ return mapped;
1181
+ }
1182
+ if (event.type === "tool/result") {
1183
+ const message = asObject2(data.message);
1184
+ const block = firstToolResultBlock(message?.content);
1185
+ if (block === void 0 || typeof block.toolCallId !== "string")
1186
+ return void 0;
1187
+ const result = visibleTextBlocks(block.content);
1188
+ const error = asObject2(data.error);
1189
+ const mapped = {
1190
+ kind: "tool_result",
1191
+ localEventId: `dsh-tool-result-${event.seq}`,
1192
+ sequence: event.seq,
1193
+ timestamp: timestamp2,
1194
+ toolCallId: boundText2(redactText(block.toolCallId), 512),
1195
+ result: redactAndBound(result, toolBytes),
1196
+ isError: block.isError === true || error !== void 0,
1197
+ ...typeof error?.code === "string" ? { errorCode: boundText2(redactText(error.code), 256) } : {}
1198
+ };
1199
+ return mapped;
1200
+ }
1201
+ return void 0;
1202
+ }
1203
+ function mapDshSessionEvents(events, options = {}) {
1204
+ const seen = /* @__PURE__ */ new Set();
1205
+ const mapped = [];
1206
+ for (const event of events) {
1207
+ const candidate = mapDshSessionEvent(event, options);
1208
+ if (candidate === void 0 || seen.has(candidate.localEventId))
1209
+ continue;
1210
+ seen.add(candidate.localEventId);
1211
+ mapped.push(candidate);
1212
+ }
1213
+ return mapped.sort((left, right) => left.sequence - right.sequence);
1214
+ }
1215
+ function finalVisibleAssistant(events) {
1216
+ return events.findLast((event) => event.kind === "assistant" && event.content.trim().length > 0);
1217
+ }
1218
+ function visibleTextBlocks(value) {
1219
+ if (!Array.isArray(value))
1220
+ return "";
1221
+ return value.flatMap((block) => {
1222
+ const candidate = asObject2(block);
1223
+ return candidate?.type === "text" && typeof candidate.text === "string" ? [candidate.text] : [];
1224
+ }).join("");
1225
+ }
1226
+ function firstToolResultBlock(value) {
1227
+ if (!Array.isArray(value))
1228
+ return void 0;
1229
+ return value.map(asObject2).find((block) => block?.type === "tool-result");
1230
+ }
1231
+ function parseToolArguments(value) {
1232
+ try {
1233
+ return JSON.parse(value);
1234
+ } catch {
1235
+ return value;
1236
+ }
1237
+ }
1238
+ function redactAndBound(value, maximumBytes) {
1239
+ const redacted = redactValue(value);
1240
+ const encoded = safeJson(redacted);
1241
+ if (Buffer.byteLength(encoded, "utf8") <= maximumBytes)
1242
+ return redacted;
1243
+ return {
1244
+ truncated: true,
1245
+ preview: boundText2(encoded, Math.max(256, maximumBytes - 128))
1246
+ };
1247
+ }
1248
+ function safeJson(value) {
1249
+ try {
1250
+ return JSON.stringify(value) ?? "null";
1251
+ } catch {
1252
+ return "[Unserializable tool value]";
1253
+ }
1254
+ }
1255
+ function boundText2(value, maximumBytes) {
1256
+ if (Buffer.byteLength(value, "utf8") <= maximumBytes)
1257
+ return value;
1258
+ const suffix = "\n[TRUNCATED]";
1259
+ const target = Math.max(0, maximumBytes - Buffer.byteLength(suffix, "utf8"));
1260
+ let low = 0;
1261
+ let high = value.length;
1262
+ while (low < high) {
1263
+ const middle = Math.ceil((low + high) / 2);
1264
+ if (Buffer.byteLength(value.slice(0, middle), "utf8") <= target)
1265
+ low = middle;
1266
+ else
1267
+ high = middle - 1;
1268
+ }
1269
+ return `${value.slice(0, low)}${suffix}`;
1270
+ }
1271
+ function checkedLimit(value, fallback) {
1272
+ if (value === void 0)
1273
+ return fallback;
1274
+ if (!Number.isSafeInteger(value) || value < 1024 || value > 2 * 1024 * 1024) {
1275
+ throw new Error("DSH event mapping byte limits must be integers from 1024 to 2097152");
1276
+ }
1277
+ return value;
1278
+ }
1279
+ function safeTimestamp(value) {
1280
+ try {
1281
+ const timestamp2 = new Date(value);
1282
+ if (!Number.isFinite(timestamp2.getTime()))
1283
+ return void 0;
1284
+ return timestamp2.toISOString();
1285
+ } catch {
1286
+ return void 0;
1287
+ }
1288
+ }
1289
+ function asObject2(value) {
1290
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1291
+ }
1292
+
1293
+ // dist/src/connector.js
1294
+ var DshHostConnector = class {
1295
+ #config;
1296
+ #api;
1297
+ #host;
1298
+ #stateStore;
1299
+ #onBackgroundError;
1300
+ #heartbeatIntervalMs;
1301
+ #actorUserId;
1302
+ #executionGate;
1303
+ #onLifecycle;
1304
+ #adoptExistingLocalSession;
1305
+ #state;
1306
+ #runtime;
1307
+ #started = false;
1308
+ #stopped = false;
1309
+ #timer;
1310
+ #heartbeatTimer;
1311
+ #pollPromise;
1312
+ #heartbeatPromise;
1313
+ #stopPromise;
1314
+ #backgroundFatalError;
1315
+ #lifecycleAbort = new AbortController();
1316
+ #listenerDisposers = [];
1317
+ #activeStatuses = [];
1318
+ #liveEventSequences = /* @__PURE__ */ new Set();
1319
+ #session;
1320
+ #visibleState = "connecting";
1321
+ #automaticPolling = false;
1322
+ constructor(options) {
1323
+ if (options.host.sessionId !== options.config.dshSessionId) {
1324
+ throw new Error("DSH Host facade Session does not match the configured binding");
1325
+ }
1326
+ this.#config = options.config;
1327
+ this.#api = options.api;
1328
+ this.#host = options.host;
1329
+ this.#stateStore = options.stateStore;
1330
+ this.#onBackgroundError = options.onBackgroundError;
1331
+ this.#actorUserId = options.actorUserId;
1332
+ this.#executionGate = options.executionGate;
1333
+ this.#onLifecycle = options.onLifecycle;
1334
+ this.#adoptExistingLocalSession = options.adoptExistingLocalSession === true;
1335
+ if (options.heartbeatIntervalMs !== void 0 && (!Number.isSafeInteger(options.heartbeatIntervalMs) || options.heartbeatIntervalMs < 1)) {
1336
+ throw new Error("DSH connector heartbeat interval must be a positive integer");
1337
+ }
1338
+ this.#heartbeatIntervalMs = options.heartbeatIntervalMs ?? 1e4;
1339
+ }
1340
+ get stopped() {
1341
+ return this.#stopped;
1342
+ }
1343
+ async start(options = {}) {
1344
+ if (this.#started)
1345
+ throw new Error("DSH connector is already started");
1346
+ if (this.#stopped)
1347
+ throw new Error("DSH connector cannot restart after disposal");
1348
+ this.#started = true;
1349
+ this.#automaticPolling = options.schedule !== false;
1350
+ this.#notifyLifecycle("connecting");
1351
+ this.#listenerDisposers.push(this.#host.onSessionEvent((event) => {
1352
+ const active = this.#state?.activeRequest;
1353
+ if (active !== void 0 && event.seq >= active.dshFromSequence) {
1354
+ this.#liveEventSequences.add(event.seq);
1355
+ }
1356
+ if (active === void 0 && event.type === "turn/end" && turnSettlement([event]) === "completed" && this.#automaticPolling) {
1357
+ queueMicrotask(() => {
1358
+ if (this.#started && !this.#stopped) {
1359
+ void this.pollOnce().catch((error) => {
1360
+ this.#onBackgroundError?.(publicError(error));
1361
+ });
1362
+ }
1363
+ });
1364
+ }
1365
+ }));
1366
+ this.#listenerDisposers.push(this.#host.onStatus((status) => {
1367
+ if (this.#state?.activeRequest === void 0)
1368
+ return;
1369
+ if (this.#activeStatuses.at(-1) !== status)
1370
+ this.#activeStatuses.push(status);
1371
+ this.#notifyLifecycle(status);
1372
+ }));
1373
+ try {
1374
+ const session = await this.#verifySessionBinding();
1375
+ this.#session = session;
1376
+ this.#notifyLifecycle("connecting");
1377
+ const storedState = await this.#stateStore.load();
1378
+ if (storedState !== void 0 && session.latestSequence !== void 0 && storedState.serverCursor > session.latestSequence) {
1379
+ throw new Error("GatherThread Session head precedes the persisted DSH cursor; refusing identity reuse");
1380
+ }
1381
+ const mode = await this.#host.open();
1382
+ this.#state = this.#reconcileState(storedState, mode);
1383
+ await this.#stateStore.save(this.#state);
1384
+ this.#runtime = await this.#api.registerRuntime({
1385
+ sessionId: this.#config.sessionId,
1386
+ deviceId: this.#config.deviceId,
1387
+ harness: "deepseek-harness",
1388
+ provider: this.#config.provider,
1389
+ model: this.#config.model,
1390
+ localSessionId: this.#config.dshSessionId,
1391
+ captureFidelity: "harness_transcript",
1392
+ capabilities: [
1393
+ "agent_request",
1394
+ "agent_progress",
1395
+ "durable_session_events",
1396
+ "native_session_resume",
1397
+ "outbox_replay",
1398
+ "canonical_history_projection",
1399
+ "bidirectional_local_turns"
1400
+ ],
1401
+ purpose: "execution"
1402
+ });
1403
+ this.#assertRuntime(this.#runtime);
1404
+ this.#notifyLifecycle("idle");
1405
+ if (options.schedule !== false)
1406
+ this.#scheduleHeartbeat();
1407
+ await this.#flushOutbox();
1408
+ await this.#finalizeDeliveredRequest();
1409
+ if (this.#state.activeRequest !== void 0) {
1410
+ await this.#withExecutionPermit(() => this.#recoverActiveRequest());
1411
+ }
1412
+ if (options.runImmediately !== false)
1413
+ await this.pollOnce();
1414
+ if (this.#backgroundFatalError !== void 0)
1415
+ throw this.#backgroundFatalError;
1416
+ if (options.schedule !== false)
1417
+ this.#schedulePoll();
1418
+ } catch (error) {
1419
+ this.#notifyLifecycle("error");
1420
+ await this.stop();
1421
+ throw this.#backgroundFatalError ?? error;
1422
+ }
1423
+ }
1424
+ pollOnce() {
1425
+ if (!this.#started || this.#stopped) {
1426
+ return Promise.reject(new Error("DSH connector is not running"));
1427
+ }
1428
+ if (this.#pollPromise !== void 0)
1429
+ return this.#pollPromise;
1430
+ this.#pollPromise = this.#poll().then((result) => {
1431
+ this.#notifyLifecycle(this.#visibleState === "running" ? "running" : "idle", true);
1432
+ return result;
1433
+ }).catch((error) => {
1434
+ if (!this.#stopped)
1435
+ this.#notifyLifecycle("offline");
1436
+ throw error;
1437
+ }).finally(() => {
1438
+ this.#pollPromise = void 0;
1439
+ });
1440
+ return this.#pollPromise;
1441
+ }
1442
+ stop() {
1443
+ this.#stopPromise ??= (async () => {
1444
+ this.#stopped = true;
1445
+ this.#notifyLifecycle("stopped");
1446
+ this.#lifecycleAbort.abort(new Error("DSH connector stopped"));
1447
+ if (this.#timer !== void 0) {
1448
+ clearInterval(this.#timer);
1449
+ this.#timer = void 0;
1450
+ }
1451
+ if (this.#heartbeatTimer !== void 0) {
1452
+ clearInterval(this.#heartbeatTimer);
1453
+ this.#heartbeatTimer = void 0;
1454
+ }
1455
+ for (const dispose of this.#listenerDisposers.splice(0))
1456
+ dispose();
1457
+ await this.#host.dispose();
1458
+ try {
1459
+ await this.#pollPromise;
1460
+ } catch {
1461
+ }
1462
+ try {
1463
+ await this.#heartbeatPromise;
1464
+ } catch {
1465
+ }
1466
+ })();
1467
+ return this.#stopPromise;
1468
+ }
1469
+ async #poll() {
1470
+ const state = this.#requireState();
1471
+ await this.#flushOutbox();
1472
+ await this.#finalizeDeliveredRequest();
1473
+ if (state.activeRequest !== void 0) {
1474
+ await this.#withExecutionPermit(() => this.#recoverActiveRequest());
1475
+ return { scanned: 0, claimed: 0, completed: 1 };
1476
+ }
1477
+ await this.#captureLocalTurns();
1478
+ await this.#flushOutbox();
1479
+ let scanCursor = state.projectionCursor;
1480
+ let scanned = 0;
1481
+ while (!this.#stopped) {
1482
+ const page = await this.#api.readEvents(this.#config.sessionId, scanCursor, this.#config.pollLimit);
1483
+ this.#validatePage(page.events, scanCursor);
1484
+ const passive = [];
1485
+ for (const event of page.events) {
1486
+ scanCursor = Math.max(scanCursor, event.sequence);
1487
+ scanned += 1;
1488
+ const profile = requestedDshProfile(event);
1489
+ if (profile === void 0 || event.actorId !== this.#requireRuntime().userId || profile.runtimeId !== void 0 && profile.runtimeId !== this.#requireRuntime().id) {
1490
+ passive.push(event);
1491
+ continue;
1492
+ }
1493
+ await this.#projectCanonicalBatch(passive, passive.at(-1)?.sequence ?? state.projectionCursor);
1494
+ if (profile.provider !== void 0 && profile.provider !== this.#config.provider) {
1495
+ throw new Error("DeepSeek Harness Agent request provider does not match the configured Host binding");
1496
+ }
1497
+ if (profile.model !== this.#config.model) {
1498
+ throw new Error("DeepSeek Harness Agent request model does not match the configured Host binding");
1499
+ }
1500
+ const outcome = await this.#withExecutionPermit(() => this.#executeRequest(event));
1501
+ return {
1502
+ scanned,
1503
+ claimed: outcome.claimed ? 1 : 0,
1504
+ completed: outcome.completed ? 1 : 0
1505
+ };
1506
+ }
1507
+ await this.#projectCanonicalBatch(passive, Math.max(scanCursor, page.hasMore ? scanCursor : page.nextSequence));
1508
+ if (!page.hasMore) {
1509
+ return { scanned, claimed: 0, completed: 0 };
1510
+ }
1511
+ if (page.nextSequence <= scanCursor && page.events.length === 0) {
1512
+ throw new Error("GatherThread replay did not advance its cursor");
1513
+ }
1514
+ scanCursor = Math.max(scanCursor, page.nextSequence);
1515
+ }
1516
+ throw new Error("DSH connector stopped during event replay");
1517
+ }
1518
+ async #executeRequest(request) {
1519
+ if (this.#stopped)
1520
+ throw new Error("DSH connector stopped before claiming an Agent request");
1521
+ const state = this.#requireState();
1522
+ const runtime = this.#requireRuntime();
1523
+ let claim;
1524
+ try {
1525
+ claim = await this.#api.claimAgentRequest(this.#config.sessionId, request.id, runtime.id);
1526
+ } catch (error) {
1527
+ if (!isTerminalClaimConflict(error))
1528
+ throw error;
1529
+ await this.#projectCanonicalBatch([request], request.sequence);
1530
+ return { claimed: false, completed: false };
1531
+ }
1532
+ if (!claim.claimed || claim.status === "completed") {
1533
+ await this.#projectCanonicalBatch([request], request.sequence);
1534
+ return { claimed: false, completed: false };
1535
+ }
1536
+ const prompt = buildDshRequestPrompt(request);
1537
+ const baseline = this.#host.currentSequence();
1538
+ state.activeRequest = {
1539
+ requestId: request.id,
1540
+ requestSequence: request.sequence,
1541
+ dshFromSequence: baseline,
1542
+ promptDigest: digest(prompt)
1543
+ };
1544
+ this.#activeStatuses = [];
1545
+ this.#liveEventSequences.clear();
1546
+ await this.#stateStore.save(state);
1547
+ const result = await this.#host.prompt(prompt);
1548
+ if (result.fromSequence !== baseline || result.toSequence < result.fromSequence) {
1549
+ throw new Error("DSH Host prompt returned an inconsistent durable event range");
1550
+ }
1551
+ this.#assertLiveEventsDurable(result);
1552
+ await this.#settleActiveRequest(result.events, result.toSequence);
1553
+ return { claimed: true, completed: true };
1554
+ }
1555
+ async #recoverActiveRequest() {
1556
+ if (this.#stopped)
1557
+ throw new Error("DSH connector stopped before recovering an Agent request");
1558
+ const state = this.#requireState();
1559
+ const active = state.activeRequest;
1560
+ if (active === void 0)
1561
+ return;
1562
+ if (active.dshToSequence !== void 0) {
1563
+ await this.#flushOutbox();
1564
+ await this.#finalizeDeliveredRequest();
1565
+ return;
1566
+ }
1567
+ const history = await this.#readThroughRequest(active.requestId, active.requestSequence);
1568
+ const request = history.find((event) => event.id === active.requestId);
1569
+ if (request === void 0 || request.type !== "agent_request") {
1570
+ throw new Error("Active GatherThread request is unavailable during DSH resume");
1571
+ }
1572
+ const prompt = buildDshRequestPrompt(request);
1573
+ const legacyPrompt = buildDshCanonicalPrompt(history, request, this.#requireRuntime().id);
1574
+ if (digest(prompt) !== active.promptDigest && digest(legacyPrompt) !== active.promptDigest) {
1575
+ throw new Error("Active GatherThread request changed during DSH resume");
1576
+ }
1577
+ const recovered = this.#host.snapshotFrom(active.dshFromSequence);
1578
+ const settlement = turnSettlement(recovered);
1579
+ if (settlement === "completed") {
1580
+ await this.#settleActiveRequest(recovered, this.#host.currentSequence());
1581
+ return;
1582
+ }
1583
+ if (settlement !== void 0 && settlement !== "interrupted") {
1584
+ throw new Error(`DSH active request ended with unsupported settlement: ${settlement}`);
1585
+ }
1586
+ if (settlement === void 0 && recovered.length > 0) {
1587
+ throw new Error("DSH active request has an unclosed durable turn after Host resume");
1588
+ }
1589
+ const continuation = settlement === "interrupted" ? "Continue the interrupted GatherThread request using the DSH Session context already restored by the Host. Return only the public final answer." : prompt;
1590
+ const baseline = this.#host.currentSequence();
1591
+ state.activeRequest = {
1592
+ ...active,
1593
+ dshFromSequence: baseline
1594
+ };
1595
+ this.#activeStatuses = [];
1596
+ this.#liveEventSequences.clear();
1597
+ await this.#stateStore.save(state);
1598
+ const result = await this.#host.prompt(continuation);
1599
+ if (result.fromSequence !== baseline || result.toSequence < result.fromSequence) {
1600
+ throw new Error("DSH Host recovery prompt returned an inconsistent durable event range");
1601
+ }
1602
+ this.#assertLiveEventsDurable(result);
1603
+ await this.#settleActiveRequest(result.events, result.toSequence);
1604
+ }
1605
+ async #settleActiveRequest(durableEvents, toSequence) {
1606
+ const state = this.#requireState();
1607
+ const active = state.activeRequest;
1608
+ if (active === void 0)
1609
+ throw new Error("No active GatherThread request to settle");
1610
+ if (turnSettlement(durableEvents) !== "completed") {
1611
+ throw new Error("DSH Agent did not durably complete the active turn");
1612
+ }
1613
+ const mapped = mapDshSessionEvents(durableEvents);
1614
+ const final = finalVisibleAssistant(mapped);
1615
+ if (final === void 0) {
1616
+ throw new Error("DSH Agent completed without a public assistant answer");
1617
+ }
1618
+ if (state.outbox.length > 0)
1619
+ throw new Error("DSH connector outbox was not empty before settlement");
1620
+ state.activeRequest = { ...active, dshToSequence: toSequence };
1621
+ state.outbox = this.#outboxFor(mapped, this.#activeStatuses, active.requestId);
1622
+ await this.#stateStore.save(state);
1623
+ await this.#flushOutbox();
1624
+ await this.#finalizeDeliveredRequest();
1625
+ }
1626
+ async #projectCanonicalBatch(events, throughSequence) {
1627
+ const state = this.#requireState();
1628
+ if (throughSequence <= state.projectionCursor)
1629
+ return;
1630
+ const runtime = this.#requireRuntime();
1631
+ const projections = [];
1632
+ for (const event of events) {
1633
+ if (event.sequence > throughSequence || event.runtime?.runtimeId === runtime.id)
1634
+ continue;
1635
+ const content = boundedPublicText(extractPublicText(event.payload));
1636
+ if (!content)
1637
+ continue;
1638
+ if (event.type === "human_chat" || event.type === "agent_request") {
1639
+ projections.push({
1640
+ eventId: event.id,
1641
+ canonicalSequence: event.sequence,
1642
+ role: "user",
1643
+ content,
1644
+ occurredAt: event.timestamp,
1645
+ ...event.actorDisplayName === void 0 ? {} : {
1646
+ actorDisplayName: event.actorDisplayName
1647
+ }
1648
+ });
1649
+ } else if (event.type === "agent_response") {
1650
+ projections.push({
1651
+ eventId: event.id,
1652
+ canonicalSequence: event.sequence,
1653
+ role: "assistant",
1654
+ content,
1655
+ occurredAt: event.timestamp,
1656
+ ...event.actorDisplayName === void 0 ? {} : {
1657
+ actorDisplayName: event.actorDisplayName
1658
+ },
1659
+ ...event.runtime?.provider === void 0 ? {} : { provider: event.runtime.provider },
1660
+ ...event.runtime?.model === void 0 ? {} : { model: event.runtime.model }
1661
+ });
1662
+ }
1663
+ }
1664
+ if (projections.length > 0) {
1665
+ await this.#host.projectCanonicalEvents(projections);
1666
+ }
1667
+ state.projectionCursor = Math.max(state.projectionCursor, throughSequence);
1668
+ state.serverCursor = Math.max(state.serverCursor, throughSequence);
1669
+ await this.#stateStore.save(state);
1670
+ }
1671
+ async #captureLocalTurns() {
1672
+ const state = this.#requireState();
1673
+ if (state.activeRequest !== void 0 || state.outbox.length > 0)
1674
+ return;
1675
+ const current = this.#host.currentSequence();
1676
+ if (current <= state.publishedDshSequence)
1677
+ return;
1678
+ await this.#host.flush();
1679
+ const snapshot = this.#host.snapshotFrom(state.publishedDshSequence);
1680
+ const capture = captureCompletedLocalTurns(snapshot, state.publishedDshSequence, this.#config.dshSessionId);
1681
+ const runtime = this.#requireRuntime();
1682
+ state.outbox = capture.turns.map((turn) => ({
1683
+ id: turn.localTurnId,
1684
+ kind: "local_turn",
1685
+ dshToSequence: turn.dshToSequence,
1686
+ input: {
1687
+ localTurnId: turn.localTurnId,
1688
+ runtimeId: runtime.id,
1689
+ basedOnSequence: state.serverCursor,
1690
+ occurredAt: turn.occurredAt,
1691
+ observedModel: this.#config.model,
1692
+ requestPayload: {
1693
+ content: turn.request,
1694
+ capture_fidelity: "harness_transcript",
1695
+ source_harness: "deepseek-harness"
1696
+ },
1697
+ responsePayload: {
1698
+ text: turn.response,
1699
+ capture_fidelity: "harness_transcript",
1700
+ source_harness: "deepseek-harness"
1701
+ }
1702
+ }
1703
+ }));
1704
+ if (state.outbox.length === 0) {
1705
+ state.publishedDshSequence = Math.max(state.publishedDshSequence, capture.safeThroughSequence);
1706
+ }
1707
+ await this.#stateStore.save(state);
1708
+ }
1709
+ #outboxFor(events, statuses, requestId) {
1710
+ const runtime = this.#requireRuntime();
1711
+ const prefix = `${this.#config.deviceId}:${digest(requestId).slice(0, 24)}`;
1712
+ const operations = [];
1713
+ for (const [index, status] of statuses.entries()) {
1714
+ const idempotencyKey = `${prefix}:dsh-status-${status}-${index}`;
1715
+ operations.push({
1716
+ id: idempotencyKey,
1717
+ kind: "progress",
1718
+ requestId,
1719
+ input: {
1720
+ runtimeId: runtime.id,
1721
+ idempotencyKey,
1722
+ payload: {
1723
+ content: status === "running" ? "DeepSeek Harness started processing the request." : "DeepSeek Harness returned to idle.",
1724
+ phase: "lifecycle",
1725
+ status,
1726
+ capture_fidelity: "harness_transcript",
1727
+ source_harness: "deepseek-harness"
1728
+ },
1729
+ observedModel: this.#config.model
1730
+ }
1731
+ });
1732
+ }
1733
+ if (this.#config.shareToolEvents) {
1734
+ for (const event of events.filter((item) => item.kind !== "assistant").slice(0, 32)) {
1735
+ const idempotencyKey = `${prefix}:${event.localEventId}`;
1736
+ const payload = event.kind === "tool_call" ? {
1737
+ tool_name: event.toolName,
1738
+ tool_call_id: event.toolCallId,
1739
+ arguments: event.arguments,
1740
+ capture_fidelity: "harness_transcript",
1741
+ source_harness: "deepseek-harness",
1742
+ source_timestamp: event.timestamp,
1743
+ dsh_event_sequence: event.sequence
1744
+ } : {
1745
+ tool_call_id: event.toolCallId,
1746
+ result: event.result,
1747
+ is_error: event.isError,
1748
+ ...event.errorCode === void 0 ? {} : { error_code: event.errorCode },
1749
+ capture_fidelity: "harness_transcript",
1750
+ source_harness: "deepseek-harness",
1751
+ source_timestamp: event.timestamp,
1752
+ dsh_event_sequence: event.sequence
1753
+ };
1754
+ operations.push({
1755
+ id: idempotencyKey,
1756
+ kind: "append",
1757
+ input: {
1758
+ type: event.kind,
1759
+ idempotencyKey,
1760
+ payload: redactValue(payload),
1761
+ replyTo: requestId,
1762
+ runtimeId: runtime.id,
1763
+ observedModel: this.#config.model
1764
+ }
1765
+ });
1766
+ }
1767
+ }
1768
+ const final = finalVisibleAssistant(events);
1769
+ if (final === void 0)
1770
+ throw new Error("No public DSH final answer is available");
1771
+ const completionKey = `${prefix}:dsh-complete`;
1772
+ operations.push({
1773
+ id: completionKey,
1774
+ kind: "complete",
1775
+ requestId,
1776
+ input: {
1777
+ runtimeId: runtime.id,
1778
+ idempotencyKey: completionKey,
1779
+ payload: {
1780
+ text: final.content,
1781
+ capture_fidelity: "harness_transcript",
1782
+ source_harness: "deepseek-harness",
1783
+ source_timestamp: final.timestamp,
1784
+ dsh_event_sequence: final.sequence
1785
+ },
1786
+ observedModel: this.#config.model
1787
+ }
1788
+ });
1789
+ return operations;
1790
+ }
1791
+ async #flushOutbox() {
1792
+ const state = this.#requireState();
1793
+ const runtime = this.#requireRuntime();
1794
+ while (state.outbox.length > 0) {
1795
+ const operation = state.outbox[0];
1796
+ if (operation === void 0)
1797
+ break;
1798
+ if (operation.input.runtimeId !== runtime.id) {
1799
+ throw new Error("DSH connector outbox belongs to a different runtime");
1800
+ }
1801
+ if (operation.kind === "progress") {
1802
+ await this.#api.appendAgentProgress(this.#config.sessionId, operation.requestId, operation.input);
1803
+ } else if (operation.kind === "append") {
1804
+ await this.#api.appendEvent(this.#config.sessionId, operation.input);
1805
+ } else if (operation.kind === "complete") {
1806
+ await this.#api.completeAgentRequest(this.#config.sessionId, operation.requestId, operation.input);
1807
+ } else {
1808
+ const result = await this.#api.commitLocalTurn(this.#config.sessionId, operation.input);
1809
+ if (result.localTurnId !== operation.input.localTurnId || result.runtimeId !== runtime.id) {
1810
+ throw new Error("GatherThread returned an incompatible local-turn acknowledgement");
1811
+ }
1812
+ state.publishedDshSequence = Math.max(state.publishedDshSequence, operation.dshToSequence);
1813
+ }
1814
+ state.outbox.shift();
1815
+ await this.#stateStore.save(state);
1816
+ }
1817
+ }
1818
+ async #finalizeDeliveredRequest() {
1819
+ const state = this.#requireState();
1820
+ const active = state.activeRequest;
1821
+ if (active?.dshToSequence === void 0 || state.outbox.length > 0)
1822
+ return;
1823
+ state.serverCursor = Math.max(state.serverCursor, active.requestSequence);
1824
+ state.projectionCursor = Math.max(state.projectionCursor, active.requestSequence);
1825
+ state.publishedDshSequence = Math.max(state.publishedDshSequence, active.dshToSequence);
1826
+ delete state.activeRequest;
1827
+ this.#activeStatuses = [];
1828
+ this.#liveEventSequences.clear();
1829
+ await this.#stateStore.save(state);
1830
+ }
1831
+ async #readThroughRequest(requestId, requestSequence) {
1832
+ const state = this.#requireState();
1833
+ let cursor = state.projectionCursor;
1834
+ const events = [];
1835
+ while (true) {
1836
+ const page = await this.#api.readEvents(this.#config.sessionId, cursor, this.#config.pollLimit);
1837
+ this.#validatePage(page.events, cursor);
1838
+ for (const event of page.events) {
1839
+ events.push(event);
1840
+ cursor = Math.max(cursor, event.sequence);
1841
+ if (event.id === requestId) {
1842
+ if (event.sequence !== requestSequence) {
1843
+ throw new Error("Active GatherThread request sequence changed during DSH resume");
1844
+ }
1845
+ return events;
1846
+ }
1847
+ }
1848
+ if (!page.hasMore)
1849
+ break;
1850
+ if (page.nextSequence <= cursor && page.events.length === 0) {
1851
+ throw new Error("GatherThread replay did not advance while recovering an active request");
1852
+ }
1853
+ cursor = Math.max(cursor, page.nextSequence);
1854
+ }
1855
+ throw new Error("Active GatherThread request is absent from canonical replay");
1856
+ }
1857
+ async #verifySessionBinding() {
1858
+ const sessions = await this.#api.listProjectSessions(this.#config.projectId);
1859
+ const session = sessions.find((candidate) => candidate.id === this.#config.sessionId);
1860
+ if (session === void 0 || session.projectId !== this.#config.projectId) {
1861
+ throw new Error("Configured GatherThread Session is not in the configured Project");
1862
+ }
1863
+ if (session.state !== "active")
1864
+ throw new Error("Configured GatherThread Session is archived");
1865
+ if (!isSessionWritableBy(session, this.#actorUserId)) {
1866
+ throw new Error("Configured GatherThread Session is not writable by the current actor");
1867
+ }
1868
+ return session;
1869
+ }
1870
+ #withExecutionPermit(operation) {
1871
+ return this.#executionGate?.run(operation, this.#lifecycleAbort.signal) ?? operation();
1872
+ }
1873
+ #reconcileState(stored, mode) {
1874
+ if (stored === void 0) {
1875
+ if (mode === "resumed" && !this.#adoptExistingLocalSession) {
1876
+ throw new Error("Persisted DSH Session exists without connector state; refusing ambiguous resume");
1877
+ }
1878
+ return {
1879
+ version: 2,
1880
+ binding: {
1881
+ projectId: this.#config.projectId,
1882
+ sessionId: this.#config.sessionId,
1883
+ dshSessionId: this.#config.dshSessionId
1884
+ },
1885
+ serverCursor: 0,
1886
+ projectionCursor: 0,
1887
+ publishedDshSequence: 0,
1888
+ outbox: []
1889
+ };
1890
+ }
1891
+ if (mode === "created") {
1892
+ throw new Error("Connector state exists without its persisted DSH Session; refusing identity reuse");
1893
+ }
1894
+ const expected = JSON.stringify([
1895
+ this.#config.projectId,
1896
+ this.#config.sessionId,
1897
+ this.#config.dshSessionId
1898
+ ]);
1899
+ const actual = JSON.stringify([
1900
+ stored.binding.projectId,
1901
+ stored.binding.sessionId,
1902
+ stored.binding.dshSessionId
1903
+ ]);
1904
+ if (actual !== expected)
1905
+ throw new Error("DSH connector state belongs to a different binding");
1906
+ if (stored.publishedDshSequence > this.#host.currentSequence()) {
1907
+ throw new Error("Persisted DSH publication cursor exceeds the native Session head");
1908
+ }
1909
+ return stored;
1910
+ }
1911
+ #assertRuntime(runtime) {
1912
+ if (runtime.sessionId !== this.#config.sessionId || runtime.deviceId !== this.#config.deviceId || runtime.harness !== "deepseek-harness" || runtime.provider !== this.#config.provider || runtime.model !== this.#config.model || runtime.localSessionId !== this.#config.dshSessionId || runtime.captureFidelity !== "harness_transcript" || runtime.purpose !== "execution") {
1913
+ throw new Error("GatherThread returned an incompatible DSH runtime registration");
1914
+ }
1915
+ }
1916
+ #validatePage(events, afterSequence) {
1917
+ let previous = afterSequence;
1918
+ for (const event of events) {
1919
+ if (event.sessionId !== this.#config.sessionId || !Number.isSafeInteger(event.sequence) || event.sequence <= previous) {
1920
+ throw new Error("GatherThread returned an invalid or unordered canonical replay page");
1921
+ }
1922
+ previous = event.sequence;
1923
+ }
1924
+ }
1925
+ #assertLiveEventsDurable(result) {
1926
+ const durable = new Set(result.events.map((event) => event.seq));
1927
+ for (const sequence of this.#liveEventSequences) {
1928
+ if (sequence >= result.fromSequence && sequence < result.toSequence && !durable.has(sequence)) {
1929
+ throw new Error("A live DSH SessionEvent was absent from the flushed durable snapshot");
1930
+ }
1931
+ }
1932
+ }
1933
+ #schedulePoll() {
1934
+ if (this.#stopped)
1935
+ return;
1936
+ this.#timer = setInterval(() => {
1937
+ void this.pollOnce().catch((error) => {
1938
+ this.#onBackgroundError?.(publicError(error));
1939
+ });
1940
+ }, this.#config.pollIntervalMs);
1941
+ this.#timer.unref?.();
1942
+ }
1943
+ #scheduleHeartbeat() {
1944
+ if (this.#stopped)
1945
+ return;
1946
+ this.#heartbeatTimer = setInterval(() => {
1947
+ void this.#heartbeat().catch((error) => {
1948
+ const publicFailure = publicError(error);
1949
+ this.#notifyLifecycle(error instanceof RuntimeIdentityError ? "error" : "offline");
1950
+ this.#onBackgroundError?.(publicFailure);
1951
+ if (error instanceof RuntimeIdentityError) {
1952
+ this.#backgroundFatalError = publicFailure;
1953
+ void this.stop();
1954
+ }
1955
+ });
1956
+ }, this.#heartbeatIntervalMs);
1957
+ this.#heartbeatTimer.unref?.();
1958
+ }
1959
+ #heartbeat() {
1960
+ if (this.#stopped)
1961
+ return Promise.reject(new Error("DSH connector is not running"));
1962
+ if (this.#heartbeatPromise !== void 0)
1963
+ return this.#heartbeatPromise;
1964
+ const current = this.#requireRuntime();
1965
+ this.#heartbeatPromise = (async () => {
1966
+ const refreshed = await this.#api.heartbeatRuntime(current.id);
1967
+ if (refreshed.id !== current.id) {
1968
+ throw new RuntimeIdentityError("GatherThread changed the DSH runtime identity during heartbeat");
1969
+ }
1970
+ try {
1971
+ this.#assertRuntime(refreshed);
1972
+ } catch {
1973
+ throw new RuntimeIdentityError("GatherThread returned incompatible DSH runtime heartbeat data");
1974
+ }
1975
+ })().finally(() => {
1976
+ this.#heartbeatPromise = void 0;
1977
+ });
1978
+ return this.#heartbeatPromise;
1979
+ }
1980
+ #requireState() {
1981
+ if (this.#state === void 0)
1982
+ throw new Error("DSH connector state is unavailable");
1983
+ return this.#state;
1984
+ }
1985
+ #requireRuntime() {
1986
+ if (this.#runtime === void 0)
1987
+ throw new Error("DSH connector runtime is unavailable");
1988
+ return this.#runtime;
1989
+ }
1990
+ #notifyLifecycle(state, synced = false) {
1991
+ this.#visibleState = state;
1992
+ try {
1993
+ this.#onLifecycle?.({
1994
+ state,
1995
+ ...this.#session === void 0 ? {} : { session: this.#session },
1996
+ ...synced ? { synced: true } : {}
1997
+ });
1998
+ } catch {
1999
+ }
2000
+ }
2001
+ };
2002
+ var RuntimeIdentityError = class extends Error {
2003
+ };
2004
+ function turnSettlement(events) {
2005
+ const event = events.findLast((candidate) => candidate.type === "turn/end");
2006
+ if (event === void 0)
2007
+ return void 0;
2008
+ const data = asObject3(event.data);
2009
+ const reason = asObject3(data?.reason);
2010
+ return typeof reason?.kind === "string" ? reason.kind : void 0;
2011
+ }
2012
+ function digest(value) {
2013
+ return createHash3("sha256").update(value).digest("hex");
2014
+ }
2015
+ function publicError(error) {
2016
+ if (error instanceof Error)
2017
+ return error;
2018
+ return new Error("DeepSeek Harness connector background operation failed");
2019
+ }
2020
+ function captureCompletedLocalTurns(events, fromSequence, dshSessionId) {
2021
+ const turns = [];
2022
+ let safeThroughSequence = fromSequence;
2023
+ let openTurn;
2024
+ let pendingUser;
2025
+ for (const event of events) {
2026
+ if (openTurn === void 0) {
2027
+ if (event.type === "user/message" && isDirectLocalUserMessage(event)) {
2028
+ pendingUser = event;
2029
+ continue;
2030
+ }
2031
+ if (event.type === "turn/start") {
2032
+ openTurn = pendingUser === void 0 ? [event] : [pendingUser, event];
2033
+ continue;
2034
+ }
2035
+ safeThroughSequence = Math.max(safeThroughSequence, event.seq + 1);
2036
+ continue;
2037
+ }
2038
+ openTurn.push(event);
2039
+ if (event.type !== "turn/end")
2040
+ continue;
2041
+ if (turnSettlement(openTurn) === "completed") {
2042
+ const userEvents = openTurn.filter(isDirectLocalUserMessage);
2043
+ const request = boundedPublicText(userEvents.map(publicMessageText).filter(Boolean).join("\n\n"));
2044
+ const responseEvent = openTurn.findLast(isNativeAssistantMessage);
2045
+ const response = responseEvent === void 0 ? "" : boundedPublicText(publicAssistantText(responseEvent));
2046
+ if (request && response && userEvents[0] !== void 0) {
2047
+ const identity = `${dshSessionId}:${userEvents[0].seq}:${event.seq}`;
2048
+ turns.push({
2049
+ localTurnId: `dsh:${digest(identity)}`,
2050
+ dshToSequence: event.seq + 1,
2051
+ occurredAt: sessionTimestamp(event.time),
2052
+ request,
2053
+ response
2054
+ });
2055
+ }
2056
+ }
2057
+ safeThroughSequence = Math.max(safeThroughSequence, event.seq + 1);
2058
+ openTurn = void 0;
2059
+ pendingUser = void 0;
2060
+ }
2061
+ return { turns, safeThroughSequence };
2062
+ }
2063
+ function isDirectLocalUserMessage(event) {
2064
+ if (event.type !== "user/message")
2065
+ return false;
2066
+ const message = messageObject(event.data);
2067
+ const source = asObject3(message?.source);
2068
+ return source?.kind === "user" && typeof message?.id === "string" && !message.id.startsWith("gatherthread:");
2069
+ }
2070
+ function isNativeAssistantMessage(event) {
2071
+ if (event.type !== "assistant/message")
2072
+ return false;
2073
+ const data = asObject3(event.data);
2074
+ const message = asObject3(data?.message);
2075
+ return typeof message?.id !== "string" || !message.id.startsWith("gatherthread:");
2076
+ }
2077
+ function publicMessageText(value) {
2078
+ return publicContentText(messageObject(value.data)?.content);
2079
+ }
2080
+ function publicAssistantText(value) {
2081
+ const data = asObject3(value.data);
2082
+ return publicContentText(asObject3(data?.message)?.content);
2083
+ }
2084
+ function messageObject(value) {
2085
+ const data = asObject3(value);
2086
+ return asObject3(data?.message) ?? data;
2087
+ }
2088
+ function publicContentText(value) {
2089
+ if (!Array.isArray(value))
2090
+ return "";
2091
+ return value.flatMap((block) => {
2092
+ const item = asObject3(block);
2093
+ return item?.type === "text" && typeof item.text === "string" ? [item.text] : [];
2094
+ }).join("");
2095
+ }
2096
+ function boundedPublicText(value) {
2097
+ const redacted = redactText(value).trim();
2098
+ const maximumBytes = 64 * 1024;
2099
+ if (Buffer.byteLength(redacted, "utf8") <= maximumBytes)
2100
+ return redacted;
2101
+ const suffix = "\n[TRUNCATED]";
2102
+ const target = maximumBytes - Buffer.byteLength(suffix, "utf8");
2103
+ let low = 0;
2104
+ let high = redacted.length;
2105
+ while (low < high) {
2106
+ const middle = Math.ceil((low + high) / 2);
2107
+ if (Buffer.byteLength(redacted.slice(0, middle), "utf8") <= target)
2108
+ low = middle;
2109
+ else
2110
+ high = middle - 1;
2111
+ }
2112
+ return `${redacted.slice(0, low)}${suffix}`;
2113
+ }
2114
+ function sessionTimestamp(value) {
2115
+ const date = new Date(value);
2116
+ if (!Number.isFinite(date.getTime()))
2117
+ throw new Error("DSH local turn has an invalid timestamp");
2118
+ return date.toISOString();
2119
+ }
2120
+ function asObject3(value) {
2121
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2122
+ }
2123
+
2124
+ // dist/src/dsh-compat.js
2125
+ var DSH_COMPATIBILITY = Object.freeze({
2126
+ tag: "dsh-v0.1.3-alpha.1",
2127
+ version: "0.1.3-alpha.1",
2128
+ commit: "d347e703908d0406b7a7ef80e3a0e594d86b2215",
2129
+ profile: "headless"
2130
+ });
2131
+ var DSH_NPM_COMPATIBILITY = Object.freeze({
2132
+ package: "@deepseek-ai/dsh",
2133
+ version: "0.1.2-rc.1",
2134
+ profile: "web",
2135
+ persistenceProbe: "list"
2136
+ });
2137
+ var DSH_COMPATIBILITY_MATRIX = Object.freeze([
2138
+ Object.freeze({
2139
+ distribution: "npm",
2140
+ version: DSH_NPM_COMPATIBILITY.version,
2141
+ profile: DSH_NPM_COMPATIBILITY.profile,
2142
+ persistenceProbe: DSH_NPM_COMPATIBILITY.persistenceProbe
2143
+ }),
2144
+ Object.freeze({
2145
+ distribution: "source",
2146
+ version: DSH_COMPATIBILITY.version,
2147
+ profile: DSH_COMPATIBILITY.profile,
2148
+ persistenceProbe: "stat"
2149
+ })
2150
+ ]);
2151
+ var USER_MESSAGE_MODULE = "@deepseek-ai/dsh-llm";
2152
+ function createDshHostFacade(options) {
2153
+ const context = requireContext(options.context);
2154
+ const agents = requireService(context, "agents", ["create", "resume"]);
2155
+ const persistence = requirePersistenceService(context);
2156
+ const sessions = requireService(context, "sessions", ["flush"]);
2157
+ if (options.sessionTitle === void 0 !== (options.workspaceTitle === void 0)) {
2158
+ throw new Error("DSH native workspace integration requires both Session and workspace titles");
2159
+ }
2160
+ const sessionTitle = options.sessionTitle === void 0 ? void 0 : requireService(context, "sessionTitle", ["rename"]);
2161
+ const workspaceRegistry = options.workspaceTitle === void 0 ? void 0 : requireService(context, "workspaceRegistry", ["create"]);
2162
+ const eventSubscribers = /* @__PURE__ */ new Set();
2163
+ const statusSubscribers = /* @__PURE__ */ new Set();
2164
+ const listenerDisposers = [];
2165
+ const lifecycleAbort = new AbortController();
2166
+ let handle;
2167
+ let openPromise;
2168
+ let promptActive = false;
2169
+ let projectionActive = false;
2170
+ let knownProjectionMessageIds;
2171
+ let disposePromise;
2172
+ let disposed = false;
2173
+ listenerDisposers.push(context.on("session/event", (...args) => {
2174
+ const session = asSession(args[0]);
2175
+ const event = asSessionEvent(args[1]);
2176
+ if (session?.id !== options.sessionId || event === void 0)
2177
+ return;
2178
+ for (const subscriber of eventSubscribers)
2179
+ subscriber(event);
2180
+ }, { global: true }));
2181
+ listenerDisposers.push(context.on("agent/status", (...args) => {
2182
+ const payload = asObject4(args[0]);
2183
+ const agent = payload ? asAgent(payload.agent) : void 0;
2184
+ const status = payload?.status;
2185
+ if (agent?.session.id !== options.sessionId || status !== "running" && status !== "idle")
2186
+ return;
2187
+ for (const subscriber of statusSubscribers)
2188
+ subscriber(status);
2189
+ }, { global: true }));
2190
+ const open2 = () => {
2191
+ if (disposed)
2192
+ return Promise.reject(new Error("DSH host facade is disposed"));
2193
+ if (openPromise !== void 0)
2194
+ return openPromise;
2195
+ openPromise = (async () => {
2196
+ const liveCandidate = agents.get?.(options.sessionId);
2197
+ const liveAgent = liveCandidate === void 0 ? void 0 : asAgent(liveCandidate);
2198
+ if (liveCandidate !== void 0 && liveAgent === void 0) {
2199
+ throw new Error("Pinned DSH Agent registry returned an incompatible live Agent");
2200
+ }
2201
+ const stored = liveAgent === void 0 ? await hasPersistedSession(persistence, options.sessionId, lifecycleAbort.signal) : true;
2202
+ throwIfDisposed(disposed, lifecycleAbort.signal, "open");
2203
+ const mode = stored ? "resumed" : "created";
2204
+ let openedHandle;
2205
+ try {
2206
+ openedHandle = liveAgent !== void 0 ? { agent: liveAgent, dispose: async () => void 0 } : stored ? await agents.resume({
2207
+ resumeSessionId: options.sessionId,
2208
+ agentOptions: { provider: options.provider, model: options.model },
2209
+ signal: lifecycleAbort.signal
2210
+ }) : await agents.create({
2211
+ sessionId: options.sessionId,
2212
+ meta: {
2213
+ cwd: options.workspacePath,
2214
+ ...options.agentPreset === void 0 ? {} : { agentPreset: options.agentPreset }
2215
+ },
2216
+ agentOptions: { provider: options.provider, model: options.model },
2217
+ signal: lifecycleAbort.signal
2218
+ });
2219
+ } catch (error) {
2220
+ throwIfDisposed(disposed, lifecycleAbort.signal, "open");
2221
+ throw error;
2222
+ }
2223
+ if (disposed || lifecycleAbort.signal.aborted) {
2224
+ await openedHandle.dispose();
2225
+ throw new Error("DSH host facade was disposed during open");
2226
+ }
2227
+ if (openedHandle.agent.session.id !== options.sessionId) {
2228
+ await openedHandle.dispose();
2229
+ throw new Error("DSH Host returned an agent for an unexpected Session identity");
2230
+ }
2231
+ if (sessionTitle !== void 0 && workspaceRegistry !== void 0 && options.sessionTitle !== void 0 && options.workspaceTitle !== void 0) {
2232
+ try {
2233
+ await sessionTitle.rename(openedHandle.agent.session, options.sessionTitle);
2234
+ ensureNativeSessionListVisibility(openedHandle.agent.session);
2235
+ await sessions.flush(openedHandle.agent.session);
2236
+ const workspace = await workspaceRegistry.create(options.workspacePath, options.workspaceTitle);
2237
+ if (workspace === null || typeof workspace !== "object" || typeof workspace.attachSession !== "function") {
2238
+ throw new Error("Pinned DSH workspace registry returned an incompatible Workspace");
2239
+ }
2240
+ await workspace.attachSession(options.sessionId);
2241
+ if (options.supersededSessionId !== void 0 && options.supersededSessionId !== options.sessionId && workspace.sessionIds.includes(options.supersededSessionId)) {
2242
+ if (typeof workspace.detachSession !== "function") {
2243
+ throw new Error("Pinned DSH Workspace detachSession API is unavailable or incompatible");
2244
+ }
2245
+ await workspace.detachSession(options.supersededSessionId);
2246
+ }
2247
+ } catch (error) {
2248
+ await openedHandle.dispose();
2249
+ throwIfDisposed(disposed, lifecycleAbort.signal, "open");
2250
+ throw error;
2251
+ }
2252
+ }
2253
+ handle = openedHandle;
2254
+ return mode;
2255
+ })();
2256
+ return openPromise;
2257
+ };
2258
+ const snapshotFrom = (sequence) => {
2259
+ if (!Number.isSafeInteger(sequence) || sequence < 0) {
2260
+ throw new Error("DSH snapshot sequence must be a non-negative integer");
2261
+ }
2262
+ const agent = requireAgent(handle);
2263
+ return agent.session.snapshotEvents(sequence).map((event) => {
2264
+ const parsed = asSessionEvent(event);
2265
+ if (parsed === void 0)
2266
+ throw new Error("DSH Host returned an invalid durable SessionEvent");
2267
+ return parsed;
2268
+ });
2269
+ };
2270
+ const prompt = async (text) => {
2271
+ if (disposed)
2272
+ throw new Error("DSH host facade is disposed");
2273
+ if (!text.trim())
2274
+ throw new Error("DSH prompt must not be empty");
2275
+ if (Buffer.byteLength(text, "utf8") > 256 * 1024) {
2276
+ throw new Error("DSH prompt exceeds the 256 KiB connector limit");
2277
+ }
2278
+ if (promptActive)
2279
+ throw new Error("DSH host facade permits only one active prompt");
2280
+ if (projectionActive)
2281
+ throw new Error("DSH host facade permits only one active write");
2282
+ promptActive = true;
2283
+ try {
2284
+ await open2();
2285
+ throwIfDisposed(disposed, lifecycleAbort.signal, "prompt");
2286
+ const agent = requireAgent(handle);
2287
+ if (agent.status !== "idle")
2288
+ throw new Error("DSH Agent must be idle before a GatherThread prompt");
2289
+ const fromSequence = agent.session.seq;
2290
+ const message = await createUserMessage(text, options);
2291
+ throwIfDisposed(disposed, lifecycleAbort.signal, "prompt");
2292
+ agent.followup(message);
2293
+ await agent.whenIdle();
2294
+ throwIfDisposed(disposed, lifecycleAbort.signal, "prompt");
2295
+ await sessions.flush(agent.session);
2296
+ throwIfDisposed(disposed, lifecycleAbort.signal, "prompt");
2297
+ const toSequence = agent.session.seq;
2298
+ return {
2299
+ fromSequence,
2300
+ toSequence,
2301
+ events: snapshotFrom(fromSequence)
2302
+ };
2303
+ } finally {
2304
+ promptActive = false;
2305
+ }
2306
+ };
2307
+ const flush = async () => {
2308
+ if (disposed)
2309
+ throw new Error("DSH host facade is disposed");
2310
+ await open2();
2311
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
2312
+ await sessions.flush(requireAgent(handle).session);
2313
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
2314
+ };
2315
+ const projectCanonicalEvents = async (projections) => {
2316
+ if (disposed)
2317
+ throw new Error("DSH host facade is disposed");
2318
+ if (promptActive || projectionActive)
2319
+ throw new Error("DSH host facade permits only one active write");
2320
+ if (projections.length === 0)
2321
+ return;
2322
+ projectionActive = true;
2323
+ try {
2324
+ await open2();
2325
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
2326
+ const agent = requireAgent(handle);
2327
+ if (agent.status !== "idle") {
2328
+ throw new Error("DSH Agent must be idle before canonical history projection");
2329
+ }
2330
+ const knownIds = knownProjectionMessageIds ??= nativeMessageIds(agent.session.snapshotEvents(0));
2331
+ for (const projection of projections) {
2332
+ const messageId = canonicalMessageId(projection.eventId);
2333
+ if (knownIds.has(messageId))
2334
+ continue;
2335
+ const message = await createProjectionMessage(projection, options);
2336
+ if (projection.role === "user") {
2337
+ agent.session.append("user/message", message, { surfaceOp: "append" });
2338
+ } else {
2339
+ const turn = nextDshTurn(agent.session.snapshotEvents(0));
2340
+ agent.session.append("turn/start", { turn });
2341
+ agent.session.append("step/start", { turn, step: 1 });
2342
+ agent.session.append("assistant/message", {
2343
+ turn,
2344
+ step: 1,
2345
+ message,
2346
+ stream: []
2347
+ }, { surfaceOp: "append" });
2348
+ agent.session.append("step/end", { turn, step: 1 });
2349
+ agent.session.append("turn/end", { turn, reason: { kind: "completed" } });
2350
+ }
2351
+ knownIds.add(messageId);
2352
+ }
2353
+ await sessions.flush(agent.session);
2354
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
2355
+ } finally {
2356
+ projectionActive = false;
2357
+ }
2358
+ };
2359
+ const dispose = () => {
2360
+ disposePromise ??= (async () => {
2361
+ disposed = true;
2362
+ lifecycleAbort.abort(new Error("DSH host facade disposed"));
2363
+ eventSubscribers.clear();
2364
+ statusSubscribers.clear();
2365
+ for (const stop of listenerDisposers.splice(0))
2366
+ stop();
2367
+ try {
2368
+ await handle?.dispose();
2369
+ } finally {
2370
+ handle = void 0;
2371
+ }
2372
+ })();
2373
+ return disposePromise;
2374
+ };
2375
+ return {
2376
+ sessionId: options.sessionId,
2377
+ open: open2,
2378
+ currentSequence() {
2379
+ return requireAgent(handle).session.seq;
2380
+ },
2381
+ snapshotFrom,
2382
+ projectCanonicalEvents,
2383
+ flush,
2384
+ prompt,
2385
+ onSessionEvent(listener) {
2386
+ if (disposed)
2387
+ throw new Error("DSH host facade is disposed");
2388
+ eventSubscribers.add(listener);
2389
+ return () => eventSubscribers.delete(listener);
2390
+ },
2391
+ onStatus(listener) {
2392
+ if (disposed)
2393
+ throw new Error("DSH host facade is disposed");
2394
+ statusSubscribers.add(listener);
2395
+ return () => statusSubscribers.delete(listener);
2396
+ },
2397
+ dispose
2398
+ };
2399
+ }
2400
+ function ensureNativeSessionListVisibility(session) {
2401
+ const events = session.snapshotEvents(0);
2402
+ if (events.some((event) => asSessionEvent(event)?.type === "turn/start"))
2403
+ return;
2404
+ session.append("turn/start", { turn: 1 });
2405
+ session.append("turn/end", { turn: 1, reason: { kind: "completed" } });
2406
+ }
2407
+ function throwIfDisposed(disposed, signal, operation) {
2408
+ if (disposed || signal.aborted) {
2409
+ throw new Error(`DSH host facade was disposed during ${operation}`);
2410
+ }
2411
+ }
2412
+ async function createProjectionMessage(projection, options) {
2413
+ const importer = options.moduleImporter ?? ((specifier) => import(specifier));
2414
+ const imported = asObject4(await importer(USER_MESSAGE_MODULE));
2415
+ if (imported === void 0 || typeof imported.freezeMessage !== "function") {
2416
+ throw new Error("Pinned DSH freezeMessage API is unavailable");
2417
+ }
2418
+ const factory = imported.freezeMessage;
2419
+ return factory({
2420
+ id: canonicalMessageId(projection.eventId),
2421
+ role: projection.role,
2422
+ content: [{ type: "text", text: formatProjectionText(projection) }],
2423
+ source: projection.role === "user" ? { kind: "user" } : {
2424
+ kind: "model",
2425
+ provider: projection.provider ?? "gatherthread",
2426
+ model: projection.model ?? "canonical-relay"
2427
+ }
2428
+ });
2429
+ }
2430
+ function formatProjectionText(projection) {
2431
+ if (projection.role === "user")
2432
+ return projection.content;
2433
+ const route = [projection.provider, projection.model].filter(Boolean).join(" / ");
2434
+ const attribution = [projection.actorDisplayName, route].filter(Boolean).join(" \xB7 ");
2435
+ return `[GatherThread Agent reply${attribution ? ` \xB7 ${attribution}` : ""}]
2436
+
2437
+ ${projection.content}`;
2438
+ }
2439
+ function canonicalMessageId(eventId) {
2440
+ return `gatherthread:${eventId}`;
2441
+ }
2442
+ function nextDshTurn(events) {
2443
+ let next = 1;
2444
+ for (const eventValue of events) {
2445
+ const event = asSessionEvent(eventValue);
2446
+ if (event?.type !== "turn/start")
2447
+ continue;
2448
+ const data = asObject4(event.data);
2449
+ const turn = data?.turn;
2450
+ if (typeof turn === "number" && Number.isSafeInteger(turn) && turn >= next) {
2451
+ next = turn + 1;
2452
+ }
2453
+ }
2454
+ return next;
2455
+ }
2456
+ function nativeMessageIds(events) {
2457
+ const ids = /* @__PURE__ */ new Set();
2458
+ for (const eventValue of events) {
2459
+ const event = asObject4(eventValue);
2460
+ const data = asObject4(event?.data);
2461
+ const message = event?.type === "assistant/message" ? asObject4(data?.message) : data;
2462
+ if (typeof message?.id === "string")
2463
+ ids.add(message.id);
2464
+ }
2465
+ return ids;
2466
+ }
2467
+ function registerDshPluginDisposer(contextValue, dispose) {
2468
+ const context = requireContext(contextValue);
2469
+ context.effect(() => dispose, "gatherthread-dsh-host.connector");
2470
+ }
2471
+ async function createUserMessage(text, options) {
2472
+ if (options.messageFactory !== void 0)
2473
+ return options.messageFactory(text);
2474
+ const importer = options.moduleImporter ?? ((specifier) => import(specifier));
2475
+ const imported = asObject4(await importer(USER_MESSAGE_MODULE));
2476
+ if (imported === void 0 || typeof imported.createUserMessage !== "function") {
2477
+ throw new Error("Pinned DSH createUserMessage API is unavailable");
2478
+ }
2479
+ const factory = imported.createUserMessage;
2480
+ return factory({
2481
+ content: [{ type: "text", text }],
2482
+ source: { kind: "user" }
2483
+ });
2484
+ }
2485
+ function requireContext(value) {
2486
+ const context = asObject4(value);
2487
+ if (context === void 0 || typeof context.get !== "function" || typeof context.on !== "function" || typeof context.effect !== "function") {
2488
+ throw new Error("Unsupported DeepSeek Harness Host context");
2489
+ }
2490
+ return context;
2491
+ }
2492
+ function requireService(context, name2, methods) {
2493
+ const service = asObject4(context.get(name2));
2494
+ if (service === void 0 || methods.some((method) => typeof service[method] !== "function")) {
2495
+ throw new Error(`Pinned DSH Host service ${name2} is unavailable or incompatible`);
2496
+ }
2497
+ return service;
2498
+ }
2499
+ function requirePersistenceService(context) {
2500
+ const service = asObject4(context.get("sessionPersistence"));
2501
+ if (service === void 0 || typeof service.stat !== "function" && typeof service.list !== "function") {
2502
+ throw new Error("Pinned DSH Host service sessionPersistence is unavailable or incompatible");
2503
+ }
2504
+ return service;
2505
+ }
2506
+ async function hasPersistedSession(persistence, sessionId, signal) {
2507
+ if (persistence.stat !== void 0) {
2508
+ return await persistence.stat(sessionId, { signal }) !== void 0;
2509
+ }
2510
+ if (persistence.list === void 0) {
2511
+ throw new Error("Pinned DSH persistence probe is unavailable");
2512
+ }
2513
+ const listed = await persistence.list();
2514
+ if (!Array.isArray(listed)) {
2515
+ throw new Error("Pinned DSH persistence list returned an invalid Session collection");
2516
+ }
2517
+ for (const value of listed) {
2518
+ const header = persistenceHeader(value);
2519
+ if (header === void 0 || typeof header.id !== "string") {
2520
+ throw new Error("Pinned DSH persistence list returned an invalid Session snapshot");
2521
+ }
2522
+ if (header.id === sessionId)
2523
+ return true;
2524
+ }
2525
+ return false;
2526
+ }
2527
+ function persistenceHeader(value) {
2528
+ const item = asObject4(value);
2529
+ return asObject4(item?.header) ?? item;
2530
+ }
2531
+ function requireAgent(handle) {
2532
+ if (handle === void 0)
2533
+ throw new Error("DSH Agent is not open");
2534
+ return handle.agent;
2535
+ }
2536
+ function asAgent(value) {
2537
+ const candidate = asObject4(value);
2538
+ const session = candidate ? asSession(candidate.session) : void 0;
2539
+ if (candidate === void 0 || session === void 0 || typeof candidate.id !== "string")
2540
+ return void 0;
2541
+ return candidate;
2542
+ }
2543
+ function asSession(value) {
2544
+ const candidate = asObject4(value);
2545
+ if (candidate === void 0 || typeof candidate.id !== "string" || !Number.isSafeInteger(candidate.seq) || typeof candidate.snapshotEvents !== "function" || typeof candidate.append !== "function")
2546
+ return void 0;
2547
+ return candidate;
2548
+ }
2549
+ function asSessionEvent(value) {
2550
+ const candidate = asObject4(value);
2551
+ if (candidate === void 0 || typeof candidate.type !== "string" || !Number.isSafeInteger(candidate.seq) || Number(candidate.seq) < 0 || !Number.isSafeInteger(candidate.time) || !("data" in candidate))
2552
+ return void 0;
2553
+ return {
2554
+ type: candidate.type,
2555
+ seq: Number(candidate.seq),
2556
+ time: Number(candidate.time),
2557
+ data: candidate.data
2558
+ };
2559
+ }
2560
+ function asObject4(value) {
2561
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2562
+ }
2563
+
2564
+ // dist/src/state-store.js
2565
+ import { randomUUID } from "node:crypto";
2566
+ import { constants } from "node:fs";
2567
+ import { lstat as lstat3, mkdir, open, rename, unlink } from "node:fs/promises";
2568
+ import path3 from "node:path";
2569
+ var FileConnectorStateStore = class {
2570
+ #path;
2571
+ constructor(statePath) {
2572
+ if (!path3.isAbsolute(statePath))
2573
+ throw new Error("DSH connector state path must be absolute");
2574
+ this.#path = path3.resolve(statePath);
2575
+ }
2576
+ async load() {
2577
+ const directory = path3.dirname(this.#path);
2578
+ const ancestorsBefore = await secureDirectoryChain(directory);
2579
+ let metadata;
2580
+ try {
2581
+ metadata = await lstat3(this.#path);
2582
+ } catch (error) {
2583
+ if (isNodeError3(error) && error.code === "ENOENT")
2584
+ return void 0;
2585
+ throw error;
2586
+ }
2587
+ assertPrivateRegularFile(metadata, "DSH connector state");
2588
+ const noFollow = "O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0;
2589
+ const handle = await open(this.#path, constants.O_RDONLY | noFollow);
2590
+ let raw;
2591
+ try {
2592
+ const opened = await handle.stat();
2593
+ if (opened.dev !== metadata.dev || opened.ino !== metadata.ino) {
2594
+ throw new Error("DSH connector state path changed while opening");
2595
+ }
2596
+ raw = await handle.readFile({ encoding: "utf8" });
2597
+ assertSameDirectoryChain(ancestorsBefore, await secureDirectoryChain(directory));
2598
+ } finally {
2599
+ await handle.close();
2600
+ }
2601
+ let parsed;
2602
+ try {
2603
+ parsed = JSON.parse(raw);
2604
+ } catch {
2605
+ throw new Error("DSH connector state is not valid JSON");
2606
+ }
2607
+ return validateConnectorState(parsed);
2608
+ }
2609
+ async save(state) {
2610
+ const validated = validateConnectorState(structuredClone(state));
2611
+ const directory = path3.dirname(this.#path);
2612
+ await secureDirectoryChain(directory);
2613
+ await mkdir(directory, { recursive: true, mode: 448 });
2614
+ const ancestorsBefore = await secureDirectoryChain(directory);
2615
+ const directoryBefore = await lstat3(directory);
2616
+ assertSecureDirectory(directoryBefore);
2617
+ const targetBefore = await optionalLstat(this.#path);
2618
+ if (targetBefore !== void 0)
2619
+ assertPrivateRegularFile(targetBefore, "DSH connector state");
2620
+ const temporaryPath = `${this.#path}.tmp-${process.pid}-${randomUUID()}`;
2621
+ const handle = await open(temporaryPath, "wx", 384);
2622
+ try {
2623
+ await handle.writeFile(`${JSON.stringify(validated, null, 2)}
2624
+ `, { encoding: "utf8" });
2625
+ await handle.sync();
2626
+ } finally {
2627
+ await handle.close();
2628
+ }
2629
+ try {
2630
+ const directoryAfter = await lstat3(directory);
2631
+ if (directoryAfter.isSymbolicLink() || directoryAfter.dev !== directoryBefore.dev || directoryAfter.ino !== directoryBefore.ino) {
2632
+ throw new Error("DSH connector state directory changed during atomic save");
2633
+ }
2634
+ assertSameDirectoryChain(ancestorsBefore, await secureDirectoryChain(directory));
2635
+ const targetAfter = await optionalLstat(this.#path);
2636
+ if (!sameArtifact(targetBefore, targetAfter)) {
2637
+ throw new Error("DSH connector state path changed during atomic save");
2638
+ }
2639
+ await rename(temporaryPath, this.#path);
2640
+ assertSameDirectoryChain(ancestorsBefore, await secureDirectoryChain(directory));
2641
+ assertPrivateRegularFile(await lstat3(this.#path), "DSH connector state");
2642
+ } catch (error) {
2643
+ await unlink(temporaryPath).catch(() => void 0);
2644
+ throw error;
2645
+ }
2646
+ }
2647
+ };
2648
+ function validateConnectorState(value) {
2649
+ const state = requiredObject3(value, "state");
2650
+ const legacy = state.version === 1;
2651
+ exactKeys(state, /* @__PURE__ */ new Set([
2652
+ "version",
2653
+ "binding",
2654
+ "serverCursor",
2655
+ ...legacy ? [] : ["projectionCursor"],
2656
+ "publishedDshSequence",
2657
+ "activeRequest",
2658
+ "outbox"
2659
+ ]), "state");
2660
+ if (!legacy && state.version !== 2)
2661
+ throw new Error("Unsupported DSH connector state version");
2662
+ const binding = requiredObject3(state.binding, "state.binding");
2663
+ exactKeys(binding, /* @__PURE__ */ new Set(["projectId", "sessionId", "dshSessionId"]), "state.binding");
2664
+ const serverCursor = nonNegativeInteger(state.serverCursor, "state.serverCursor");
2665
+ const publishedDshSequence = nonNegativeInteger(state.publishedDshSequence, "state.publishedDshSequence");
2666
+ const projectionCursor = legacy ? 0 : nonNegativeInteger(state.projectionCursor, "state.projectionCursor");
2667
+ const activeRequest = state.activeRequest === void 0 ? void 0 : parseActiveRequest(state.activeRequest);
2668
+ if (!Array.isArray(state.outbox))
2669
+ throw new Error("state.outbox must be an array");
2670
+ const outbox = state.outbox.map(parseOutboxOperation);
2671
+ const validated = {
2672
+ version: 2,
2673
+ binding: {
2674
+ projectId: safeString(binding.projectId, "state.binding.projectId", 128),
2675
+ sessionId: safeString(binding.sessionId, "state.binding.sessionId", 128),
2676
+ dshSessionId: safeString(binding.dshSessionId, "state.binding.dshSessionId", 128)
2677
+ },
2678
+ serverCursor,
2679
+ projectionCursor,
2680
+ publishedDshSequence,
2681
+ ...activeRequest === void 0 ? {} : { activeRequest },
2682
+ outbox
2683
+ };
2684
+ assertAlreadyRedacted(validated);
2685
+ return validated;
2686
+ }
2687
+ function parseActiveRequest(value) {
2688
+ const active = requiredObject3(value, "state.activeRequest");
2689
+ exactKeys(active, /* @__PURE__ */ new Set(["requestId", "requestSequence", "dshFromSequence", "dshToSequence", "promptDigest"]), "state.activeRequest");
2690
+ const parsed = {
2691
+ requestId: safeString(active.requestId, "state.activeRequest.requestId", 128),
2692
+ requestSequence: positiveInteger(active.requestSequence, "state.activeRequest.requestSequence"),
2693
+ dshFromSequence: nonNegativeInteger(active.dshFromSequence, "state.activeRequest.dshFromSequence"),
2694
+ ...active.dshToSequence === void 0 ? {} : {
2695
+ dshToSequence: nonNegativeInteger(active.dshToSequence, "state.activeRequest.dshToSequence")
2696
+ },
2697
+ promptDigest: hexDigest(active.promptDigest, "state.activeRequest.promptDigest")
2698
+ };
2699
+ if (parsed.dshToSequence !== void 0 && parsed.dshToSequence < parsed.dshFromSequence) {
2700
+ throw new Error("state.activeRequest.dshToSequence cannot precede dshFromSequence");
2701
+ }
2702
+ return parsed;
2703
+ }
2704
+ function parseOutboxOperation(value, index) {
2705
+ const label = `state.outbox[${index}]`;
2706
+ const operation = requiredObject3(value, label);
2707
+ const kind = operation.kind;
2708
+ if (kind === "append") {
2709
+ exactKeys(operation, /* @__PURE__ */ new Set(["id", "kind", "input"]), label);
2710
+ return {
2711
+ id: safeString(operation.id, `${label}.id`, 200),
2712
+ kind,
2713
+ input: parseAppendInput(operation.input, `${label}.input`)
2714
+ };
2715
+ }
2716
+ if (kind === "progress" || kind === "complete") {
2717
+ exactKeys(operation, /* @__PURE__ */ new Set(["id", "kind", "requestId", "input"]), label);
2718
+ return {
2719
+ id: safeString(operation.id, `${label}.id`, 200),
2720
+ kind,
2721
+ requestId: safeString(operation.requestId, `${label}.requestId`, 128),
2722
+ input: parseCompletionInput(operation.input, `${label}.input`)
2723
+ };
2724
+ }
2725
+ if (kind === "local_turn") {
2726
+ exactKeys(operation, /* @__PURE__ */ new Set(["id", "kind", "dshToSequence", "input"]), label);
2727
+ return {
2728
+ id: safeString(operation.id, `${label}.id`, 200),
2729
+ kind,
2730
+ dshToSequence: nonNegativeInteger(operation.dshToSequence, `${label}.dshToSequence`),
2731
+ input: parseLocalTurnInput(operation.input, `${label}.input`)
2732
+ };
2733
+ }
2734
+ throw new Error(`${label}.kind is unsupported`);
2735
+ }
2736
+ function parseLocalTurnInput(value, label) {
2737
+ const input = requiredObject3(value, label);
2738
+ exactKeys(input, /* @__PURE__ */ new Set([
2739
+ "localTurnId",
2740
+ "runtimeId",
2741
+ "basedOnSequence",
2742
+ "occurredAt",
2743
+ "observedModel",
2744
+ "observedReasoningEffort",
2745
+ "requestPayload",
2746
+ "responsePayload",
2747
+ "toolEvents"
2748
+ ]), label);
2749
+ const occurredAt = safeString(input.occurredAt, `${label}.occurredAt`, 64);
2750
+ if (!Number.isFinite(Date.parse(occurredAt)))
2751
+ throw new Error(`${label}.occurredAt must be an ISO timestamp`);
2752
+ if (input.toolEvents !== void 0 && !Array.isArray(input.toolEvents)) {
2753
+ throw new Error(`${label}.toolEvents must be an array`);
2754
+ }
2755
+ return {
2756
+ localTurnId: safeString(input.localTurnId, `${label}.localTurnId`, 200),
2757
+ runtimeId: safeString(input.runtimeId, `${label}.runtimeId`, 128),
2758
+ basedOnSequence: nonNegativeInteger(input.basedOnSequence, `${label}.basedOnSequence`),
2759
+ occurredAt,
2760
+ ...input.observedModel === void 0 ? {} : {
2761
+ observedModel: safeString(input.observedModel, `${label}.observedModel`, 160)
2762
+ },
2763
+ ...input.observedReasoningEffort === void 0 ? {} : {
2764
+ observedReasoningEffort: safeString(input.observedReasoningEffort, `${label}.observedReasoningEffort`, 80)
2765
+ },
2766
+ requestPayload: jsonValue(input.requestPayload, `${label}.requestPayload`),
2767
+ responsePayload: jsonValue(input.responsePayload, `${label}.responsePayload`),
2768
+ ...input.toolEvents === void 0 ? {} : {
2769
+ toolEvents: input.toolEvents.map((event, eventIndex) => jsonValue(event, `${label}.toolEvents[${eventIndex}]`))
2770
+ }
2771
+ };
2772
+ }
2773
+ function parseAppendInput(value, label) {
2774
+ const input = requiredObject3(value, label);
2775
+ exactKeys(input, /* @__PURE__ */ new Set([
2776
+ "type",
2777
+ "idempotencyKey",
2778
+ "payload",
2779
+ "replyTo",
2780
+ "visibility",
2781
+ "runtimeId",
2782
+ "observedModel",
2783
+ "observedReasoningEffort"
2784
+ ]), label);
2785
+ if (input.type !== "tool_call" && input.type !== "tool_result") {
2786
+ throw new Error(`${label}.type must be tool_call or tool_result`);
2787
+ }
2788
+ return {
2789
+ type: input.type,
2790
+ idempotencyKey: safeString(input.idempotencyKey, `${label}.idempotencyKey`, 200),
2791
+ payload: jsonValue(input.payload, `${label}.payload`),
2792
+ ...input.replyTo === void 0 ? {} : { replyTo: safeString(input.replyTo, `${label}.replyTo`, 128) },
2793
+ ...input.visibility === void 0 ? {} : { visibility: safeString(input.visibility, `${label}.visibility`, 32) },
2794
+ ...input.runtimeId === void 0 ? {} : { runtimeId: safeString(input.runtimeId, `${label}.runtimeId`, 128) },
2795
+ ...input.observedModel === void 0 ? {} : { observedModel: safeString(input.observedModel, `${label}.observedModel`, 160) },
2796
+ ...input.observedReasoningEffort === void 0 ? {} : {
2797
+ observedReasoningEffort: safeString(input.observedReasoningEffort, `${label}.observedReasoningEffort`, 80)
2798
+ }
2799
+ };
2800
+ }
2801
+ function parseCompletionInput(value, label) {
2802
+ const input = requiredObject3(value, label);
2803
+ exactKeys(input, /* @__PURE__ */ new Set(["runtimeId", "idempotencyKey", "payload", "observedModel", "observedReasoningEffort"]), label);
2804
+ return {
2805
+ runtimeId: safeString(input.runtimeId, `${label}.runtimeId`, 128),
2806
+ idempotencyKey: safeString(input.idempotencyKey, `${label}.idempotencyKey`, 200),
2807
+ payload: jsonValue(input.payload, `${label}.payload`),
2808
+ ...input.observedModel === void 0 ? {} : { observedModel: safeString(input.observedModel, `${label}.observedModel`, 160) },
2809
+ ...input.observedReasoningEffort === void 0 ? {} : {
2810
+ observedReasoningEffort: safeString(input.observedReasoningEffort, `${label}.observedReasoningEffort`, 80)
2811
+ }
2812
+ };
2813
+ }
2814
+ function assertAlreadyRedacted(state) {
2815
+ const encoded = JSON.stringify(state);
2816
+ const redacted = JSON.stringify(redactValue(state));
2817
+ if (encoded !== redacted) {
2818
+ throw new Error("DSH connector state contains a secret-bearing field or value");
2819
+ }
2820
+ }
2821
+ function requiredObject3(value, label) {
2822
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2823
+ throw new Error(`${label} must be an object`);
2824
+ }
2825
+ return value;
2826
+ }
2827
+ function exactKeys(value, allowed, label) {
2828
+ const extra = Object.keys(value).filter((key) => !allowed.has(key));
2829
+ if (extra.length > 0)
2830
+ throw new Error(`${label} contains unsupported keys: ${extra.join(", ")}`);
2831
+ }
2832
+ function safeString(value, label, maximum) {
2833
+ if (typeof value !== "string" || !value || value.length > maximum || /[\u0000-\u001f\u007f-\u009f]/u.test(value)) {
2834
+ throw new Error(`${label} must be a safe non-empty string`);
2835
+ }
2836
+ return value;
2837
+ }
2838
+ function jsonValue(value, label) {
2839
+ try {
2840
+ const encoded = JSON.stringify(value);
2841
+ if (encoded === void 0)
2842
+ throw new Error();
2843
+ return JSON.parse(encoded);
2844
+ } catch {
2845
+ throw new Error(`${label} must be a JSON value`);
2846
+ }
2847
+ }
2848
+ function hexDigest(value, label) {
2849
+ const digest2 = safeString(value, label, 64);
2850
+ if (!/^[a-f0-9]{64}$/.test(digest2))
2851
+ throw new Error(`${label} must be a SHA-256 digest`);
2852
+ return digest2;
2853
+ }
2854
+ function nonNegativeInteger(value, label) {
2855
+ if (!Number.isSafeInteger(value) || Number(value) < 0) {
2856
+ throw new Error(`${label} must be a non-negative integer`);
2857
+ }
2858
+ return Number(value);
2859
+ }
2860
+ function positiveInteger(value, label) {
2861
+ const parsed = nonNegativeInteger(value, label);
2862
+ if (parsed === 0)
2863
+ throw new Error(`${label} must be positive`);
2864
+ return parsed;
2865
+ }
2866
+ function isNodeError3(error) {
2867
+ return error instanceof Error && "code" in error;
2868
+ }
2869
+ async function optionalLstat(target) {
2870
+ try {
2871
+ return await lstat3(target);
2872
+ } catch (error) {
2873
+ if (isNodeError3(error) && error.code === "ENOENT")
2874
+ return void 0;
2875
+ throw error;
2876
+ }
2877
+ }
2878
+ function assertPrivateRegularFile(metadata, label) {
2879
+ if (metadata.isSymbolicLink() || !metadata.isFile()) {
2880
+ throw new Error(`${label} must be a regular file, never a symbolic link`);
2881
+ }
2882
+ if (process.platform !== "win32" && (Number(metadata.mode) & 511) !== 384) {
2883
+ throw new Error(`${label} permissions must be 0600`);
2884
+ }
2885
+ }
2886
+ function assertSecureDirectory(metadata) {
2887
+ if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
2888
+ throw new Error("DSH connector state directory must be a real directory");
2889
+ }
2890
+ if (process.platform !== "win32" && (Number(metadata.mode) & 511) !== 448) {
2891
+ throw new Error("DSH connector state directory permissions must be 0700");
2892
+ }
2893
+ }
2894
+ function sameArtifact(before, after) {
2895
+ if (before === void 0 || after === void 0)
2896
+ return before === after;
2897
+ return !after.isSymbolicLink() && after.isFile() && before.dev === after.dev && before.ino === after.ino;
2898
+ }
2899
+
2900
+ // dist/src/project-manager.js
2901
+ import path4 from "node:path";
2902
+ import { createHash as createHash4 } from "node:crypto";
2903
+
2904
+ // dist/src/execution-gate.js
2905
+ var BoundedDshExecutionGate = class {
2906
+ #limit;
2907
+ #pending = [];
2908
+ #active = 0;
2909
+ #closed;
2910
+ constructor(limit) {
2911
+ if (!Number.isSafeInteger(limit) || limit < 1) {
2912
+ throw new Error("DSH execution concurrency must be a positive integer");
2913
+ }
2914
+ this.#limit = limit;
2915
+ }
2916
+ get activeCount() {
2917
+ return this.#active;
2918
+ }
2919
+ get pendingCount() {
2920
+ return this.#pending.length;
2921
+ }
2922
+ run(operation, signal) {
2923
+ if (this.#closed !== void 0)
2924
+ return Promise.reject(this.#closed);
2925
+ if (signal?.aborted)
2926
+ return Promise.reject(abortReason(signal));
2927
+ return new Promise((resolve, reject) => {
2928
+ let pending;
2929
+ const onAbort = () => {
2930
+ const index = this.#pending.indexOf(pending);
2931
+ if (index < 0)
2932
+ return;
2933
+ this.#pending.splice(index, 1);
2934
+ pending.detachAbort();
2935
+ reject(abortReason(signal));
2936
+ };
2937
+ pending = {
2938
+ operation,
2939
+ resolve,
2940
+ reject,
2941
+ detachAbort: () => signal?.removeEventListener("abort", onAbort)
2942
+ };
2943
+ signal?.addEventListener("abort", onAbort, { once: true });
2944
+ this.#pending.push(pending);
2945
+ this.#drain();
2946
+ });
2947
+ }
2948
+ close(reason = new Error("DSH project execution gate is closed")) {
2949
+ if (this.#closed !== void 0)
2950
+ return;
2951
+ this.#closed = reason;
2952
+ for (const pending of this.#pending.splice(0)) {
2953
+ pending.detachAbort();
2954
+ pending.reject(reason);
2955
+ }
2956
+ }
2957
+ #drain() {
2958
+ while (this.#closed === void 0 && this.#active < this.#limit) {
2959
+ const pending = this.#pending.shift();
2960
+ if (pending === void 0)
2961
+ return;
2962
+ pending.detachAbort();
2963
+ this.#active += 1;
2964
+ void Promise.resolve().then(pending.operation).then((value) => {
2965
+ this.#active -= 1;
2966
+ this.#drain();
2967
+ pending.resolve(value);
2968
+ }, (error) => {
2969
+ this.#active -= 1;
2970
+ this.#drain();
2971
+ pending.reject(error);
2972
+ });
2973
+ }
2974
+ }
2975
+ };
2976
+ function abortReason(signal) {
2977
+ return signal?.reason instanceof Error ? signal.reason : new Error("DSH execution permit was canceled");
2978
+ }
2979
+
2980
+ // dist/src/project-manager.js
2981
+ var DshProjectManager = class {
2982
+ #config;
2983
+ #api;
2984
+ #createConnector;
2985
+ #onBackgroundError;
2986
+ #now;
2987
+ #executionGate;
2988
+ #onStatus;
2989
+ #discoverLocalSessions;
2990
+ #canCreateLocalSessions;
2991
+ #subscribeToLocalSessionChanges;
2992
+ #mapCloudSessionId;
2993
+ #managed = /* @__PURE__ */ new Map();
2994
+ #nativeLocalSessionIds = /* @__PURE__ */ new Set();
2995
+ #retries = /* @__PURE__ */ new Map();
2996
+ #actor;
2997
+ #started = false;
2998
+ #stopped = false;
2999
+ #timer;
3000
+ #refreshPromise;
3001
+ #stopPromise;
3002
+ #discoveryFailures = 0;
3003
+ #stopLocalSessionSubscription;
3004
+ constructor(options) {
3005
+ this.#config = options.config;
3006
+ this.#api = options.api;
3007
+ this.#createConnector = options.createConnector;
3008
+ this.#onBackgroundError = options.onBackgroundError;
3009
+ this.#now = options.now ?? Date.now;
3010
+ this.#executionGate = new BoundedDshExecutionGate(options.config.maxConcurrentSessions);
3011
+ this.#onStatus = options.onStatus;
3012
+ this.#discoverLocalSessions = options.discoverLocalSessions;
3013
+ this.#canCreateLocalSessions = options.canCreateLocalSessions === true;
3014
+ this.#subscribeToLocalSessionChanges = options.subscribeToLocalSessionChanges;
3015
+ this.#mapCloudSessionId = options.mapCloudSessionId;
3016
+ }
3017
+ get stopped() {
3018
+ return this.#stopped;
3019
+ }
3020
+ activeSessionIds() {
3021
+ return [...this.#managed].filter(([, binding]) => binding.phase === "active" && !binding.connector.stopped).map(([sessionId]) => sessionId).sort();
3022
+ }
3023
+ async start() {
3024
+ if (this.#started)
3025
+ throw new Error("DSH project manager is already started");
3026
+ if (this.#stopped)
3027
+ throw new Error("DSH project manager cannot restart after disposal");
3028
+ this.#started = true;
3029
+ try {
3030
+ const actor = await this.#api.getCurrentActor();
3031
+ if (actor.deviceId !== this.#config.deviceId) {
3032
+ throw new Error("Configured DSH device identity does not match the authenticated actor");
3033
+ }
3034
+ this.#actor = actor;
3035
+ const initial = await this.#refresh();
3036
+ if (initial.status === "project_inaccessible")
3037
+ throw initial.error;
3038
+ if (initial.status === "transient_failure") {
3039
+ throw publicError2(initial.error, "Initial GatherThread Project discovery failed");
3040
+ }
3041
+ this.#stopLocalSessionSubscription = this.#subscribeToLocalSessionChanges?.(() => {
3042
+ if (this.#stopped)
3043
+ return;
3044
+ void this.refreshOnce().catch((error) => this.#reportError(publicError2(error, "DSH local Session discovery failed")));
3045
+ });
3046
+ this.#scheduleNextRefresh();
3047
+ } catch (error) {
3048
+ await this.stop();
3049
+ throw error;
3050
+ }
3051
+ }
3052
+ refreshOnce() {
3053
+ if (!this.#started || this.#stopped) {
3054
+ return Promise.reject(new Error("DSH project manager is not running"));
3055
+ }
3056
+ return this.#refresh();
3057
+ }
3058
+ stop() {
3059
+ this.#stopPromise ??= (async () => {
3060
+ this.#stopped = true;
3061
+ if (this.#timer !== void 0) {
3062
+ clearTimeout(this.#timer);
3063
+ this.#timer = void 0;
3064
+ }
3065
+ this.#stopLocalSessionSubscription?.();
3066
+ this.#stopLocalSessionSubscription = void 0;
3067
+ this.#executionGate.close(new Error("DSH project manager stopped"));
3068
+ await this.#deactivateAll();
3069
+ const refresh = this.#refreshPromise;
3070
+ if (refresh !== void 0) {
3071
+ try {
3072
+ await refresh;
3073
+ } catch {
3074
+ }
3075
+ }
3076
+ await this.#deactivateAll();
3077
+ this.#retries.clear();
3078
+ this.#notifyStatus({ state: "stopped" });
3079
+ })();
3080
+ return this.#stopPromise;
3081
+ }
3082
+ #refresh() {
3083
+ if (this.#refreshPromise !== void 0)
3084
+ return this.#refreshPromise;
3085
+ this.#refreshPromise = this.#performRefresh().finally(() => {
3086
+ this.#refreshPromise = void 0;
3087
+ });
3088
+ return this.#refreshPromise;
3089
+ }
3090
+ async #performRefresh() {
3091
+ const actor = this.#requireActor();
3092
+ if (this.#stopped)
3093
+ throw new Error("DSH project manager stopped during discovery");
3094
+ for (const [sessionId, binding] of [...this.#managed]) {
3095
+ if (!binding.connector.stopped)
3096
+ continue;
3097
+ this.#managed.delete(sessionId);
3098
+ try {
3099
+ await binding.connector.stop();
3100
+ } catch (error) {
3101
+ this.#reportError(publicError2(error, "Stopped DSH Session connector cleanup failed"));
3102
+ }
3103
+ this.#recordFailure(sessionId);
3104
+ }
3105
+ const permissions = await refreshProjectSessionPermissions({
3106
+ loadSessions: () => this.#api.listProjectSessions(this.#config.projectId),
3107
+ actorUserId: actor.id,
3108
+ managed: this.#managed,
3109
+ harness: {}
3110
+ });
3111
+ if (permissions.status === "project_inaccessible") {
3112
+ this.#stopped = true;
3113
+ if (this.#timer !== void 0) {
3114
+ clearTimeout(this.#timer);
3115
+ this.#timer = void 0;
3116
+ }
3117
+ this.#executionGate.close(new Error("GatherThread Project became inaccessible"));
3118
+ await this.#deactivateAll();
3119
+ this.#reportError(permissions.error);
3120
+ this.#notifyStatus({ state: "error" });
3121
+ return { status: "project_inaccessible", error: permissions.error };
3122
+ }
3123
+ if (permissions.status === "transient_failure") {
3124
+ this.#discoveryFailures += 1;
3125
+ this.#reportError(publicError2(permissions.error, "GatherThread Project discovery failed transiently"));
3126
+ this.#notifyStatus({ state: "offline" });
3127
+ return permissions;
3128
+ }
3129
+ this.#discoveryFailures = 0;
3130
+ for (const error of permissions.errors)
3131
+ this.#reportError(error);
3132
+ const eligibleSessions = [...permissions.eligibleSessions];
3133
+ if (this.#discoverLocalSessions !== void 0) {
3134
+ try {
3135
+ const candidates2 = await this.#discoverLocalSessions();
3136
+ this.#nativeLocalSessionIds.clear();
3137
+ for (const candidate of candidates2)
3138
+ this.#nativeLocalSessionIds.add(candidate.localSessionId);
3139
+ if (this.#canCreateLocalSessions) {
3140
+ const representedIds = new Set(eligibleSessions.map((session) => session.id));
3141
+ for (const candidate of candidates2) {
3142
+ if (representedIds.has(candidate.localSessionId))
3143
+ continue;
3144
+ const created = await this.#api.createSession(this.#config.projectId, {
3145
+ sessionId: candidate.localSessionId,
3146
+ title: candidate.title,
3147
+ mode: "solo",
3148
+ idempotencyKey: nativeSessionCreationKey(this.#config.projectId, candidate.localSessionId)
3149
+ });
3150
+ eligibleSessions.push(created);
3151
+ representedIds.add(created.id);
3152
+ }
3153
+ }
3154
+ } catch (error) {
3155
+ this.#reportError(publicError2(error, "DSH local Session discovery failed"));
3156
+ }
3157
+ }
3158
+ const eligibleIds = new Set(eligibleSessions.map((session) => session.id));
3159
+ for (const sessionId of [...this.#retries.keys()]) {
3160
+ if (!eligibleIds.has(sessionId))
3161
+ this.#retries.delete(sessionId);
3162
+ }
3163
+ const now = this.#now();
3164
+ const candidates = eligibleSessions.filter((session) => {
3165
+ if (this.#managed.has(session.id))
3166
+ return false;
3167
+ return (this.#retries.get(session.id)?.nextAttemptAt ?? 0) <= now;
3168
+ });
3169
+ const attachErrors = await runBounded(candidates, this.#config.maxConcurrentSessions, (session) => this.#attach(session, actor.id));
3170
+ for (const error of attachErrors)
3171
+ this.#reportError(error);
3172
+ const result = {
3173
+ status: "updated",
3174
+ eligibleSessionIds: [...eligibleIds].sort(),
3175
+ activeSessionIds: this.activeSessionIds(),
3176
+ errors: [...permissions.errors, ...attachErrors]
3177
+ };
3178
+ this.#notifyStatus({
3179
+ state: "connected",
3180
+ eligibleSessions,
3181
+ activeSessionIds: result.activeSessionIds
3182
+ });
3183
+ return result;
3184
+ }
3185
+ async #attach(session, actorUserId) {
3186
+ if (this.#stopped || this.#managed.has(session.id))
3187
+ return;
3188
+ let binding;
3189
+ try {
3190
+ const connector = this.#createConnector({
3191
+ config: projectSessionConfig(this.#config, session.id, this.#nativeLocalSessionIds.has(session.id) ? session.id : this.#mapCloudSessionId?.(session.id)),
3192
+ session,
3193
+ actorUserId,
3194
+ executionGate: this.#executionGate,
3195
+ onBackgroundError: (error) => this.#reportError(error)
3196
+ });
3197
+ binding = {
3198
+ connector,
3199
+ phase: "starting",
3200
+ deactivateLocalPublishing: async () => connector.stop()
3201
+ };
3202
+ this.#managed.set(session.id, binding);
3203
+ await connector.start();
3204
+ if (this.#stopped || connector.stopped || this.#managed.get(session.id) !== binding) {
3205
+ await connector.stop();
3206
+ this.#managed.delete(session.id);
3207
+ return;
3208
+ }
3209
+ binding.phase = "active";
3210
+ this.#retries.delete(session.id);
3211
+ } catch (error) {
3212
+ if (binding !== void 0 && this.#managed.get(session.id) === binding) {
3213
+ this.#managed.delete(session.id);
3214
+ }
3215
+ try {
3216
+ await binding?.connector.stop();
3217
+ } catch (stopError) {
3218
+ this.#reportError(publicError2(stopError, "DSH Session connector cleanup failed"));
3219
+ }
3220
+ if (this.#stopped)
3221
+ return;
3222
+ this.#recordFailure(session.id);
3223
+ throw publicError2(error, `DSH Session ${session.id} failed to attach`);
3224
+ }
3225
+ }
3226
+ #recordFailure(sessionId) {
3227
+ const attempts = (this.#retries.get(sessionId)?.attempts ?? 0) + 1;
3228
+ this.#retries.set(sessionId, {
3229
+ attempts,
3230
+ nextAttemptAt: this.#now() + retryDelay(this.#config, attempts)
3231
+ });
3232
+ }
3233
+ #scheduleNextRefresh() {
3234
+ if (this.#stopped || this.#timer !== void 0)
3235
+ return;
3236
+ const now = this.#now();
3237
+ let delay = this.#discoveryFailures > 0 ? retryDelay(this.#config, this.#discoveryFailures) : this.#config.refreshIntervalMs;
3238
+ for (const retry of this.#retries.values()) {
3239
+ delay = Math.min(delay, Math.max(1, retry.nextAttemptAt - now));
3240
+ }
3241
+ this.#timer = setTimeout(() => {
3242
+ this.#timer = void 0;
3243
+ void this.refreshOnce().catch((error) => this.#reportError(publicError2(error, "DSH Project refresh failed"))).finally(() => this.#scheduleNextRefresh());
3244
+ }, delay);
3245
+ this.#timer.unref?.();
3246
+ }
3247
+ async #deactivateAll() {
3248
+ const bindings = [...this.#managed.values()];
3249
+ this.#managed.clear();
3250
+ const results = await Promise.allSettled(bindings.map((binding) => binding.connector.stop()));
3251
+ for (const result of results) {
3252
+ if (result.status === "rejected") {
3253
+ this.#reportError(publicError2(result.reason, "DSH Session connector cleanup failed"));
3254
+ }
3255
+ }
3256
+ }
3257
+ #requireActor() {
3258
+ if (this.#actor === void 0)
3259
+ throw new Error("Authenticated GatherThread actor is unavailable");
3260
+ return this.#actor;
3261
+ }
3262
+ #reportError(error) {
3263
+ this.#onBackgroundError?.(error);
3264
+ }
3265
+ #notifyStatus(update) {
3266
+ try {
3267
+ this.#onStatus?.(update);
3268
+ } catch {
3269
+ }
3270
+ }
3271
+ };
3272
+ function projectSessionConfig(config, sessionId, nativeSessionId) {
3273
+ const dshSessionId = nativeSessionId ?? deriveDshSessionId(config.projectId, sessionId, config.workspacePath);
3274
+ return {
3275
+ enabled: true,
3276
+ bindingMode: "single",
3277
+ apiUrl: config.apiUrl,
3278
+ credentialReference: config.credentialReference,
3279
+ projectId: config.projectId,
3280
+ ...config.projectName === void 0 ? {} : { projectName: config.projectName },
3281
+ sessionId,
3282
+ deviceId: config.deviceId,
3283
+ workspacePath: config.workspacePath,
3284
+ statePath: path4.join(config.stateRoot, `${dshSessionId}.json`),
3285
+ provider: config.provider,
3286
+ model: config.model,
3287
+ pollIntervalMs: config.pollIntervalMs,
3288
+ pollLimit: config.pollLimit,
3289
+ shareToolEvents: config.shareToolEvents,
3290
+ dshSessionId
3291
+ };
3292
+ }
3293
+ function nativeSessionCreationKey(projectId, localSessionId) {
3294
+ const identity = createHash4("sha256").update(`${projectId}\0${localSessionId}`).digest("hex");
3295
+ return `dsh-native:${identity}`;
3296
+ }
3297
+ function retryDelay(config, attempts) {
3298
+ return Math.min(config.retryMaxMs, config.retryBaseMs * 2 ** Math.min(attempts - 1, 30));
3299
+ }
3300
+ async function runBounded(values, limit, operation) {
3301
+ const errors = [];
3302
+ let index = 0;
3303
+ const workers = Array.from({ length: Math.min(limit, values.length) }, async () => {
3304
+ while (index < values.length) {
3305
+ const current = values[index];
3306
+ index += 1;
3307
+ if (current === void 0)
3308
+ continue;
3309
+ try {
3310
+ await operation(current);
3311
+ } catch (error) {
3312
+ errors.push(publicError2(error, "DSH Session attach failed"));
3313
+ }
3314
+ }
3315
+ });
3316
+ await Promise.all(workers);
3317
+ return errors;
3318
+ }
3319
+ function publicError2(error, fallback) {
3320
+ return error instanceof Error ? error : new Error(fallback);
3321
+ }
3322
+
3323
+ // dist/src/status.js
3324
+ var DSH_STATUS_PATH = "/api/gatherthread.status";
3325
+ var DSH_STATUS_MAX_SESSIONS = 100;
3326
+ var DSH_STATUS_MAX_ID_LENGTH = 128;
3327
+ var DSH_STATUS_MAX_LABEL_LENGTH = 160;
3328
+ var DshStatusController = class {
3329
+ #bindingMode;
3330
+ #projectName;
3331
+ #now;
3332
+ #sessions = /* @__PURE__ */ new Map();
3333
+ #connection = "connecting";
3334
+ #updatedAt;
3335
+ constructor(options) {
3336
+ this.#bindingMode = options.bindingMode;
3337
+ this.#projectName = publicText(options.projectName, "projectName", DSH_STATUS_MAX_LABEL_LENGTH);
3338
+ this.#now = options.now ?? (() => /* @__PURE__ */ new Date());
3339
+ this.#updatedAt = timestamp(this.#now());
3340
+ }
3341
+ setConnection(state) {
3342
+ this.#connection = state;
3343
+ this.#touch();
3344
+ }
3345
+ setProjectName(value) {
3346
+ this.#projectName = publicText(value, "projectName", DSH_STATUS_MAX_LABEL_LENGTH);
3347
+ this.#touch();
3348
+ }
3349
+ upsertSession(input) {
3350
+ const sessionId = publicText(input.sessionId, "sessionId", DSH_STATUS_MAX_ID_LENGTH);
3351
+ const existing = this.#sessions.get(sessionId);
3352
+ if (existing === void 0 && this.#sessions.size >= DSH_STATUS_MAX_SESSIONS) {
3353
+ throw new Error(`DSH public status supports at most ${String(DSH_STATUS_MAX_SESSIONS)} Sessions`);
3354
+ }
3355
+ const next = {
3356
+ sessionId,
3357
+ title: publicText(input.title, "session title", DSH_STATUS_MAX_LABEL_LENGTH),
3358
+ state: input.state,
3359
+ ...input.synced === true ? { lastSyncedAt: timestamp(this.#now()) } : existing?.lastSyncedAt === void 0 ? {} : { lastSyncedAt: existing.lastSyncedAt }
3360
+ };
3361
+ this.#sessions.set(sessionId, next);
3362
+ this.#touch();
3363
+ }
3364
+ removeSession(sessionIdValue) {
3365
+ const sessionId = publicText(sessionIdValue, "sessionId", DSH_STATUS_MAX_ID_LENGTH);
3366
+ if (this.#sessions.delete(sessionId))
3367
+ this.#touch();
3368
+ }
3369
+ reconcileProjectSessions(sessions, activeSessionIds) {
3370
+ const visibleSessions = sessions.slice(0, DSH_STATUS_MAX_SESSIONS);
3371
+ const visibleIds = new Set(visibleSessions.map((session) => session.id));
3372
+ const activeIds = new Set(activeSessionIds);
3373
+ for (const sessionId of this.#sessions.keys()) {
3374
+ if (!visibleIds.has(sessionId))
3375
+ this.#sessions.delete(sessionId);
3376
+ }
3377
+ for (const session of visibleSessions) {
3378
+ const existing = this.#sessions.get(session.id);
3379
+ const state = activeIds.has(session.id) ? existing?.state === "running" ? "running" : "idle" : existing?.state === "error" || existing?.state === "offline" ? existing.state : "connecting";
3380
+ this.upsertSession({
3381
+ sessionId: session.id,
3382
+ title: session.name ?? session.id,
3383
+ state
3384
+ });
3385
+ }
3386
+ this.#touch();
3387
+ }
3388
+ markSessionsOffline() {
3389
+ for (const [sessionId, session] of this.#sessions) {
3390
+ this.#sessions.set(sessionId, { ...session, state: "offline" });
3391
+ }
3392
+ this.#touch();
3393
+ }
3394
+ snapshot() {
3395
+ const sessions = [...this.#sessions.values()].sort((left, right) => left.sessionId.localeCompare(right.sessionId)).slice(0, DSH_STATUS_MAX_SESSIONS).map((session) => ({ ...session }));
3396
+ return {
3397
+ schemaVersion: 1,
3398
+ integration: "gatherthread",
3399
+ connection: this.#connection,
3400
+ bindingMode: this.#bindingMode,
3401
+ projectName: this.#projectName,
3402
+ activeSessionCount: sessions.filter((session) => session.state === "idle" || session.state === "running").length,
3403
+ sessions,
3404
+ updatedAt: this.#updatedAt
3405
+ };
3406
+ }
3407
+ #touch() {
3408
+ this.#updatedAt = timestamp(this.#now());
3409
+ }
3410
+ };
3411
+ function registerDshStatusRoute(contextValue, controller) {
3412
+ const context = asObject5(contextValue);
3413
+ if (context === void 0)
3414
+ throw new Error("Unsupported DeepSeek Harness Host context");
3415
+ const connection = asConnection(Reflect.get(context, "connection"));
3416
+ if (connection === void 0)
3417
+ return void 0;
3418
+ let active = true;
3419
+ const unregister = connection.fetch.register({
3420
+ path: DSH_STATUS_PATH,
3421
+ methods: ["GET", "HEAD"],
3422
+ requestBody: "buffered",
3423
+ fetch: async (request) => {
3424
+ if (!active)
3425
+ return new Response("not found", { status: 404 });
3426
+ if (request.method !== "GET" && request.method !== "HEAD") {
3427
+ return new Response(null, {
3428
+ status: 405,
3429
+ headers: { allow: "GET, HEAD", "cache-control": "no-store" }
3430
+ });
3431
+ }
3432
+ const url = new URL(request.url);
3433
+ if (url.pathname !== DSH_STATUS_PATH || url.search !== "") {
3434
+ return minimalResponse("invalid request", 400);
3435
+ }
3436
+ const body = JSON.stringify(controller.snapshot());
3437
+ const headers = statusHeaders();
3438
+ headers.set("content-length", String(Buffer.byteLength(body, "utf8")));
3439
+ return new Response(request.method === "HEAD" ? null : body, {
3440
+ status: 200,
3441
+ headers
3442
+ });
3443
+ }
3444
+ });
3445
+ let disposePromise;
3446
+ return () => {
3447
+ disposePromise ??= (async () => {
3448
+ active = false;
3449
+ await unregister();
3450
+ })();
3451
+ return disposePromise;
3452
+ };
3453
+ }
3454
+ function bindOptionalDshStatusRoute(contextValue, controller) {
3455
+ const context = asObject5(contextValue);
3456
+ if (context === void 0 || typeof context.inject !== "function") {
3457
+ throw new Error("Unsupported DeepSeek Harness optional service context");
3458
+ }
3459
+ const fiber = context.inject(["connection"], (connectionContextValue) => {
3460
+ const connectionContext = asObject5(connectionContextValue);
3461
+ if (connectionContext === void 0 || typeof connectionContext.effect !== "function") {
3462
+ throw new Error("Unsupported DeepSeek Harness Connection context");
3463
+ }
3464
+ const dispose = registerDshStatusRoute(connectionContextValue, controller);
3465
+ if (dispose === void 0) {
3466
+ throw new Error("Injected DeepSeek Harness Connection service is unavailable");
3467
+ }
3468
+ connectionContext.effect(() => dispose, "gatherthread-dsh-host.status-route");
3469
+ });
3470
+ let disposePromise;
3471
+ return () => {
3472
+ disposePromise ??= Promise.resolve(fiber.dispose()).then(() => void 0);
3473
+ return disposePromise;
3474
+ };
3475
+ }
3476
+ function statusHeaders() {
3477
+ return new Headers({
3478
+ "cache-control": "no-store",
3479
+ "content-security-policy": "default-src 'none'",
3480
+ "content-type": "application/json; charset=utf-8",
3481
+ "referrer-policy": "no-referrer",
3482
+ "x-content-type-options": "nosniff"
3483
+ });
3484
+ }
3485
+ function minimalResponse(body, status) {
3486
+ return new Response(body, {
3487
+ status,
3488
+ headers: {
3489
+ "cache-control": "no-store",
3490
+ "content-type": "text/plain; charset=utf-8",
3491
+ "x-content-type-options": "nosniff"
3492
+ }
3493
+ });
3494
+ }
3495
+ function asConnection(value) {
3496
+ const connection = asObject5(value);
3497
+ const fetch = asObject5(connection?.fetch);
3498
+ return typeof fetch?.register === "function" ? value : void 0;
3499
+ }
3500
+ function asObject5(value) {
3501
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3502
+ }
3503
+ function publicText(value, label, maximum) {
3504
+ const trimmed = value.trim();
3505
+ if (!trimmed || trimmed.length > maximum || hasControlCharacter(trimmed) || looksLikeAbsolutePath(trimmed)) {
3506
+ throw new Error(`${label} is not safe bounded display text`);
3507
+ }
3508
+ return trimmed;
3509
+ }
3510
+ function looksLikeAbsolutePath(value) {
3511
+ return value.startsWith("/") || value.startsWith("\\\\") || /^[A-Za-z]:[\\/]/u.test(value);
3512
+ }
3513
+ function hasControlCharacter(value) {
3514
+ for (const character of value) {
3515
+ const code = character.codePointAt(0) ?? 0;
3516
+ if (code < 32 || code >= 127 && code <= 159)
3517
+ return true;
3518
+ }
3519
+ return false;
3520
+ }
3521
+ function timestamp(value) {
3522
+ if (!Number.isFinite(value.getTime()))
3523
+ throw new Error("DSH status clock returned an invalid date");
3524
+ return value.toISOString();
3525
+ }
3526
+
3527
+ // dist/src/plugin.js
3528
+ var name = "gatherthread-dsh-host";
3529
+ var inject = ["agents", "sessions", "sessionPersistence", "llm"];
3530
+ async function apply(context, rawConfig) {
3531
+ const config = parseDshHostConfig(rawConfig);
3532
+ if (!config.enabled)
3533
+ return;
3534
+ assertSafeDshEnvironment();
3535
+ if (config.bindingMode === "single")
3536
+ await assertSafeDshPaths(config);
3537
+ else
3538
+ await assertSafeDshStateRoot(config);
3539
+ const status = new DshStatusController({
3540
+ bindingMode: config.bindingMode,
3541
+ projectName: config.projectName ?? config.projectId
3542
+ });
3543
+ let statusDispose;
3544
+ const credential = resolveCredentialReference(config.credentialReference);
3545
+ const abort = new AbortController();
3546
+ let owner;
3547
+ registerDshPluginDisposer(context, async () => {
3548
+ abort.abort(new Error("GatherThread DSH Host plugin unloaded"));
3549
+ await owner?.stop();
3550
+ status.setConnection("stopped");
3551
+ await statusDispose?.();
3552
+ });
3553
+ try {
3554
+ statusDispose = bindOptionalDshStatusRoute(context, status);
3555
+ const api = createHttpDshCollaborationApi({
3556
+ baseUrl: config.apiUrl,
3557
+ credential,
3558
+ signal: abort.signal
3559
+ });
3560
+ if (config.bindingMode === "single") {
3561
+ let sessionTitle = config.sessionId;
3562
+ status.upsertSession({
3563
+ sessionId: config.sessionId,
3564
+ title: sessionTitle,
3565
+ state: "connecting"
3566
+ });
3567
+ const actor = await api.getCurrentActor();
3568
+ assertActorDevice(actor.deviceId, config.deviceId);
3569
+ const host = createDshHostFacade({
3570
+ context,
3571
+ sessionId: config.dshSessionId,
3572
+ workspacePath: config.workspacePath,
3573
+ provider: config.provider,
3574
+ model: config.model
3575
+ });
3576
+ const connector = new DshHostConnector({
3577
+ config,
3578
+ api,
3579
+ host,
3580
+ stateStore: new FileConnectorStateStore(config.statePath),
3581
+ actorUserId: actor.id,
3582
+ onLifecycle: (update) => {
3583
+ sessionTitle = update.session?.name ?? sessionTitle;
3584
+ updateSingleStatus(status, config.sessionId, sessionTitle, update);
3585
+ }
3586
+ });
3587
+ owner = connector;
3588
+ await connector.start();
3589
+ return;
3590
+ }
3591
+ const manager = new DshProjectManager({
3592
+ config,
3593
+ api,
3594
+ createConnector: (input) => createManagedConnector({
3595
+ input,
3596
+ context,
3597
+ credential,
3598
+ rootSignal: abort.signal,
3599
+ status
3600
+ }),
3601
+ onStatus: (update) => {
3602
+ if (update.state === "connected") {
3603
+ status.reconcileProjectSessions(update.eligibleSessions, update.activeSessionIds);
3604
+ status.setConnection("connected");
3605
+ } else if (update.state === "offline") {
3606
+ status.markSessionsOffline();
3607
+ status.setConnection("offline");
3608
+ } else {
3609
+ status.setConnection(update.state);
3610
+ }
3611
+ }
3612
+ });
3613
+ owner = manager;
3614
+ await manager.start();
3615
+ } catch (error) {
3616
+ abort.abort(new Error("GatherThread DSH Host plugin initialization failed"));
3617
+ await Promise.allSettled([
3618
+ owner?.stop(),
3619
+ statusDispose?.()
3620
+ ]);
3621
+ throw error;
3622
+ }
3623
+ }
3624
+ function createManagedConnector(options) {
3625
+ const sessionAbort = new AbortController();
3626
+ const abortFromRoot = () => sessionAbort.abort(options.rootSignal.reason);
3627
+ if (options.rootSignal.aborted)
3628
+ abortFromRoot();
3629
+ else
3630
+ options.rootSignal.addEventListener("abort", abortFromRoot, { once: true });
3631
+ const api = createHttpDshCollaborationApi({
3632
+ baseUrl: options.input.config.apiUrl,
3633
+ credential: options.credential,
3634
+ signal: sessionAbort.signal
3635
+ });
3636
+ const host = createDshHostFacade({
3637
+ context: options.context,
3638
+ sessionId: options.input.config.dshSessionId,
3639
+ workspacePath: options.input.config.workspacePath,
3640
+ provider: options.input.config.provider,
3641
+ model: options.input.config.model
3642
+ });
3643
+ const connector = new DshHostConnector({
3644
+ config: options.input.config,
3645
+ api,
3646
+ host,
3647
+ stateStore: new FileConnectorStateStore(options.input.config.statePath),
3648
+ actorUserId: options.input.actorUserId,
3649
+ executionGate: options.input.executionGate,
3650
+ onBackgroundError: options.input.onBackgroundError,
3651
+ onLifecycle: (update) => {
3652
+ if (update.state === "stopped") {
3653
+ options.status.removeSession(options.input.session.id);
3654
+ return;
3655
+ }
3656
+ options.status.upsertSession({
3657
+ sessionId: options.input.session.id,
3658
+ title: options.input.session.name ?? options.input.session.id,
3659
+ state: update.state,
3660
+ ...update.synced === true ? { synced: true } : {}
3661
+ });
3662
+ }
3663
+ });
3664
+ let stopPromise;
3665
+ return {
3666
+ get stopped() {
3667
+ return connector.stopped;
3668
+ },
3669
+ async start() {
3670
+ try {
3671
+ await connector.start();
3672
+ } catch (error) {
3673
+ options.status.upsertSession({
3674
+ sessionId: options.input.session.id,
3675
+ title: options.input.session.name ?? options.input.session.id,
3676
+ state: "error"
3677
+ });
3678
+ throw error;
3679
+ }
3680
+ },
3681
+ stop() {
3682
+ stopPromise ??= (async () => {
3683
+ options.rootSignal.removeEventListener("abort", abortFromRoot);
3684
+ sessionAbort.abort(new Error("GatherThread DSH Session binding stopped"));
3685
+ await connector.stop();
3686
+ })();
3687
+ return stopPromise;
3688
+ }
3689
+ };
3690
+ }
3691
+ function updateSingleStatus(status, sessionId, sessionTitle, update) {
3692
+ if (update.state === "stopped") {
3693
+ status.setConnection("stopped");
3694
+ return;
3695
+ }
3696
+ status.upsertSession({
3697
+ sessionId,
3698
+ title: sessionTitle,
3699
+ state: update.state,
3700
+ ...update.synced === true ? { synced: true } : {}
3701
+ });
3702
+ status.setConnection(update.state === "idle" || update.state === "running" ? "connected" : update.state);
3703
+ }
3704
+ function assertActorDevice(actual, expected) {
3705
+ if (actual !== expected) {
3706
+ throw new Error("Configured DSH device identity does not match the authenticated actor");
3707
+ }
3708
+ }
3709
+ export {
3710
+ apply,
3711
+ inject,
3712
+ name
3713
+ };