@gatherthread/dsh-host 0.1.0-alpha.2 → 0.1.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/bundle/manifest.json +1 -1
- package/client/client.js +55 -5
- package/dist/bundle/native-plugin.js +175 -12
- package/dist/bundle/plugin.js +118 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ The package does not install a DSH runtime dependency.
|
|
|
19
19
|
After `@gatherthread/dsh-host` is published, the ordinary three-step path is:
|
|
20
20
|
|
|
21
21
|
```text
|
|
22
|
-
npx @deepseek-ai/dsh@0.1.2-rc.1 plugin --profile web add @gatherthread/dsh-host@0.1.0-alpha.
|
|
22
|
+
npx @deepseek-ai/dsh@0.1.2-rc.1 plugin --profile web add @gatherthread/dsh-host@0.1.0-alpha.3
|
|
23
23
|
npx @deepseek-ai/dsh@0.1.2-rc.1 web
|
|
24
24
|
# In DSH: Settings -> GatherThread / 共序 -> Sign in and pair
|
|
25
25
|
```
|
|
@@ -159,3 +159,10 @@ same runtime advance the projection cursor but are not appended back to DSH,
|
|
|
159
159
|
while events written through another harness are imported normally. V1
|
|
160
160
|
connector state deliberately restarts only the native projection cursor at zero
|
|
161
161
|
once so history that the earlier poller observed but discarded is backfilled.
|
|
162
|
+
|
|
163
|
+
Automatic upload is stored independently for every bound DSH conversation and
|
|
164
|
+
defaults on. In **Settings -> GatherThread / 共序**, the user can switch it off
|
|
165
|
+
without stopping cloud-to-local projection, or choose **Manual upload** to scan
|
|
166
|
+
and send completed eligible local turns. The manual action is also the recovery
|
|
167
|
+
path after a missed native event or transport interruption, uses the existing
|
|
168
|
+
durable idempotent outbox, and never silently re-enables automatic upload.
|
package/bundle/manifest.json
CHANGED
package/client/client.js
CHANGED
|
@@ -105,7 +105,9 @@ function GatherThreadStatusPanel({ connection, sessions }) {
|
|
|
105
105
|
}
|
|
106
106
|
onSuccess?.(next);
|
|
107
107
|
} catch {
|
|
108
|
-
setNotice("
|
|
108
|
+
setNotice(endpoint.startsWith("sync/")
|
|
109
|
+
? "上传操作未完成。请检查连接和会话权限后重试。"
|
|
110
|
+
: "操作未完成。请检查服务器地址、当前授权和模型配置。");
|
|
109
111
|
} finally {
|
|
110
112
|
setBusy(false);
|
|
111
113
|
}
|
|
@@ -129,7 +131,9 @@ function GatherThreadStatusPanel({ connection, sessions }) {
|
|
|
129
131
|
React.createElement("p", { style: styles.summary },
|
|
130
132
|
`活跃会话 ${String(snapshot?.activeSessionCount ?? 0)} / ${String(sessionRows.length)}`),
|
|
131
133
|
React.createElement("div", { style: styles.list },
|
|
132
|
-
...sessionRows.map((session) =>
|
|
134
|
+
...sessionRows.map((session) => {
|
|
135
|
+
const sync = nativeState?.localSync?.find((item) => item.sessionId === session.sessionId);
|
|
136
|
+
return React.createElement("div", {
|
|
133
137
|
key: session.sessionId,
|
|
134
138
|
style: styles.row,
|
|
135
139
|
},
|
|
@@ -137,7 +141,30 @@ function GatherThreadStatusPanel({ connection, sessions }) {
|
|
|
137
141
|
React.createElement("strong", { style: styles.sessionTitle }, session.title),
|
|
138
142
|
React.createElement("span", { style: styles.sessionMeta },
|
|
139
143
|
session.lastSyncedAt === undefined ? "尚未同步" : `最近同步 ${formatTime(session.lastSyncedAt)}`)),
|
|
140
|
-
React.createElement("span", { style: badgeStyle(session.state) }, statusLabel(session.state))
|
|
144
|
+
sync === undefined ? React.createElement("span", { style: badgeStyle(session.state) }, statusLabel(session.state))
|
|
145
|
+
: React.createElement("div", { style: styles.syncActions },
|
|
146
|
+
React.createElement("label", { style: styles.syncToggle },
|
|
147
|
+
React.createElement("input", {
|
|
148
|
+
type: "checkbox",
|
|
149
|
+
checked: sync.automaticUpload,
|
|
150
|
+
disabled: busy,
|
|
151
|
+
onChange: (event) => void runAction("sync/set-auto-upload", {
|
|
152
|
+
projectId: sync.projectId,
|
|
153
|
+
sessionId: sync.sessionId,
|
|
154
|
+
enabled: event.target.checked,
|
|
155
|
+
}),
|
|
156
|
+
}),
|
|
157
|
+
"自动上传"),
|
|
158
|
+
React.createElement("button", {
|
|
159
|
+
type: "button",
|
|
160
|
+
disabled: busy || sync.uploadableLocalTurns < 1,
|
|
161
|
+
style: styles.compactButton,
|
|
162
|
+
onClick: () => void runAction("sync/upload", {
|
|
163
|
+
projectId: sync.projectId,
|
|
164
|
+
sessionId: sync.sessionId,
|
|
165
|
+
}),
|
|
166
|
+
}, sync.uploadableLocalTurns > 0 ? `手动上传 ${String(sync.uploadableLocalTurns)}` : "已上传")));
|
|
167
|
+
})),
|
|
141
168
|
nativeState === undefined || unavailable
|
|
142
169
|
? null
|
|
143
170
|
: renderNativeControls({
|
|
@@ -386,7 +413,7 @@ function detail(label, value) {
|
|
|
386
413
|
function parseNativeState(value) {
|
|
387
414
|
exactObject(value, [
|
|
388
415
|
"schemaVersion", "integration", "authorization", "compatibility", "runtime",
|
|
389
|
-
"officialServerUrl", "serverUrl", "deviceName", "route", "projectCount", "bindings", "pairing", "recoverableError",
|
|
416
|
+
"officialServerUrl", "serverUrl", "deviceName", "route", "projectCount", "bindings", "localSync", "pairing", "recoverableError",
|
|
390
417
|
]);
|
|
391
418
|
if (value.schemaVersion !== 2 || value.integration !== "gatherthread") throw new Error("invalid native status identity");
|
|
392
419
|
if (!AUTHORIZATION_STATES.has(value.authorization)) throw new Error("invalid native authorization state");
|
|
@@ -409,6 +436,7 @@ function parseNativeState(value) {
|
|
|
409
436
|
if (value.deviceName !== undefined) boundedText(value.deviceName, 120);
|
|
410
437
|
let route;
|
|
411
438
|
let bindings;
|
|
439
|
+
let localSync;
|
|
412
440
|
if (value.route !== undefined || value.bindings !== undefined || value.projectCount !== undefined) {
|
|
413
441
|
if (value.route === undefined || !Number.isSafeInteger(value.projectCount) || value.projectCount < 0
|
|
414
442
|
|| !Array.isArray(value.bindings) || value.bindings.length > 100
|
|
@@ -430,6 +458,25 @@ function parseNativeState(value) {
|
|
|
430
458
|
}
|
|
431
459
|
return { ...binding };
|
|
432
460
|
});
|
|
461
|
+
const localSyncValue = value.localSync ?? [];
|
|
462
|
+
if (!Array.isArray(localSyncValue) || localSyncValue.length > 100) throw new Error("invalid local sync status");
|
|
463
|
+
localSync = localSyncValue.map((sync) => {
|
|
464
|
+
exactObject(sync, [
|
|
465
|
+
"projectId", "projectName", "sessionId", "localSessionId", "title", "automaticUpload",
|
|
466
|
+
"pendingLocalTurns", "uploadableLocalTurns",
|
|
467
|
+
]);
|
|
468
|
+
boundedText(sync.projectId, 128);
|
|
469
|
+
boundedText(sync.projectName, 160);
|
|
470
|
+
boundedText(sync.sessionId, 128);
|
|
471
|
+
boundedText(sync.localSessionId, 200);
|
|
472
|
+
boundedText(sync.title, 160);
|
|
473
|
+
if (typeof sync.automaticUpload !== "boolean"
|
|
474
|
+
|| !Number.isSafeInteger(sync.pendingLocalTurns) || sync.pendingLocalTurns < 0
|
|
475
|
+
|| !Number.isSafeInteger(sync.uploadableLocalTurns) || sync.uploadableLocalTurns < 0) {
|
|
476
|
+
throw new Error("invalid local sync status");
|
|
477
|
+
}
|
|
478
|
+
return { ...sync };
|
|
479
|
+
});
|
|
433
480
|
}
|
|
434
481
|
let pairing;
|
|
435
482
|
if (value.pairing !== undefined) {
|
|
@@ -465,7 +512,7 @@ function parseNativeState(value) {
|
|
|
465
512
|
...(officialServerUrl === undefined ? {} : { officialServerUrl }),
|
|
466
513
|
...(serverUrl === undefined ? {} : { serverUrl }),
|
|
467
514
|
...(value.deviceName === undefined ? {} : { deviceName: value.deviceName }),
|
|
468
|
-
...(route === undefined ? {} : { route, projectCount: value.projectCount, bindings }),
|
|
515
|
+
...(route === undefined ? {} : { route, projectCount: value.projectCount, bindings, localSync }),
|
|
469
516
|
...(pairing === undefined ? {} : { pairing }),
|
|
470
517
|
...(value.recoverableError === undefined ? {} : { recoverableError: value.recoverableError }),
|
|
471
518
|
};
|
|
@@ -592,6 +639,9 @@ const styles = {
|
|
|
592
639
|
sessionText: { minWidth: 0, display: "grid", gap: 3 },
|
|
593
640
|
sessionTitle: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontSize: 14 },
|
|
594
641
|
sessionMeta: { opacity: 0.62, fontSize: 12 },
|
|
642
|
+
syncActions: { flex: "none", display: "grid", justifyItems: "end", gap: 6 },
|
|
643
|
+
syncToggle: { display: "inline-flex", alignItems: "center", gap: 5, fontSize: 12, cursor: "pointer" },
|
|
644
|
+
compactButton: { minHeight: 28, padding: "4px 8px", borderRadius: 8, border: "1px solid rgba(127,127,127,.3)", background: "transparent", color: "inherit", font: "inherit", fontSize: 12, cursor: "pointer" },
|
|
595
645
|
badge: { flex: "none", padding: "4px 8px", borderRadius: 999, fontSize: 12, fontWeight: 600, background: "rgba(127,127,127,.12)", color: "inherit", border: "1px solid rgba(127,127,127,.2)" },
|
|
596
646
|
connectionCard: { display: "grid", gap: 12, padding: "14px", border: "1px solid rgba(127,127,127,.2)", borderRadius: 14, background: "rgba(127,127,127,.06)" },
|
|
597
647
|
subheading: { display: "grid", gap: 4 },
|
|
@@ -1398,6 +1398,7 @@ var DshHostConnector = class {
|
|
|
1398
1398
|
#timer;
|
|
1399
1399
|
#heartbeatTimer;
|
|
1400
1400
|
#pollPromise;
|
|
1401
|
+
#localSyncControlPromise;
|
|
1401
1402
|
#heartbeatPromise;
|
|
1402
1403
|
#stopPromise;
|
|
1403
1404
|
#backgroundFatalError;
|
|
@@ -1429,6 +1430,47 @@ var DshHostConnector = class {
|
|
|
1429
1430
|
get stopped() {
|
|
1430
1431
|
return this.#stopped;
|
|
1431
1432
|
}
|
|
1433
|
+
localSyncStatus() {
|
|
1434
|
+
const state = this.#requireState();
|
|
1435
|
+
const pendingOperations = state.outbox.filter((operation) => operation.kind === "local_turn");
|
|
1436
|
+
const pending = pendingOperations.length;
|
|
1437
|
+
const discoverFrom = pendingOperations.reduce((sequence, operation) => Math.max(sequence, operation.dshToSequence), state.publishedDshSequence);
|
|
1438
|
+
const discoverable = state.activeRequest === void 0 ? captureCompletedLocalTurns(this.#host.snapshotFrom(discoverFrom), discoverFrom, this.#config.dshSessionId).turns.length : 0;
|
|
1439
|
+
return {
|
|
1440
|
+
sessionId: this.#config.sessionId,
|
|
1441
|
+
localSessionId: this.#config.dshSessionId,
|
|
1442
|
+
automaticUpload: state.automaticUpload,
|
|
1443
|
+
pendingLocalTurns: pending,
|
|
1444
|
+
uploadableLocalTurns: pending + discoverable
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
async setLocalAutoUpload(enabled) {
|
|
1448
|
+
const status = await this.#withLocalSyncControl(async () => {
|
|
1449
|
+
const state = this.#requireState();
|
|
1450
|
+
state.automaticUpload = enabled;
|
|
1451
|
+
await this.#stateStore.save(state);
|
|
1452
|
+
return this.localSyncStatus();
|
|
1453
|
+
});
|
|
1454
|
+
if (enabled)
|
|
1455
|
+
await this.pollOnce();
|
|
1456
|
+
return enabled ? this.localSyncStatus() : status;
|
|
1457
|
+
}
|
|
1458
|
+
uploadLocalTurns() {
|
|
1459
|
+
return this.#withLocalSyncControl(async () => {
|
|
1460
|
+
const before = this.localSyncStatus();
|
|
1461
|
+
await this.#flushOutbox(true);
|
|
1462
|
+
await this.#captureLocalTurns(0);
|
|
1463
|
+
const discoveredLocalTurns = this.localSyncStatus().pendingLocalTurns;
|
|
1464
|
+
const pendingBefore = this.localSyncStatus().pendingLocalTurns;
|
|
1465
|
+
await this.#flushOutbox(true);
|
|
1466
|
+
const after = this.localSyncStatus();
|
|
1467
|
+
return {
|
|
1468
|
+
...after,
|
|
1469
|
+
discoveredLocalTurns,
|
|
1470
|
+
uploadedLocalTurns: Math.max(0, before.pendingLocalTurns + pendingBefore - after.pendingLocalTurns)
|
|
1471
|
+
};
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1432
1474
|
async start(options = {}) {
|
|
1433
1475
|
if (this.#started)
|
|
1434
1476
|
throw new Error("DSH connector is already started");
|
|
@@ -1493,7 +1535,7 @@ var DshHostConnector = class {
|
|
|
1493
1535
|
this.#notifyLifecycle("idle");
|
|
1494
1536
|
if (options.schedule !== false)
|
|
1495
1537
|
this.#scheduleHeartbeat();
|
|
1496
|
-
await this.#flushOutbox();
|
|
1538
|
+
await this.#flushOutbox(this.#requireState().automaticUpload);
|
|
1497
1539
|
await this.#finalizeDeliveredRequest();
|
|
1498
1540
|
if (this.#state.activeRequest !== void 0) {
|
|
1499
1541
|
await this.#withExecutionPermit(() => this.#recoverActiveRequest());
|
|
@@ -1514,6 +1556,10 @@ var DshHostConnector = class {
|
|
|
1514
1556
|
if (!this.#started || this.#stopped) {
|
|
1515
1557
|
return Promise.reject(new Error("DSH connector is not running"));
|
|
1516
1558
|
}
|
|
1559
|
+
const localSyncControl = this.#localSyncControlPromise;
|
|
1560
|
+
if (localSyncControl !== void 0) {
|
|
1561
|
+
return localSyncControl.then(() => this.pollOnce());
|
|
1562
|
+
}
|
|
1517
1563
|
if (this.#pollPromise !== void 0)
|
|
1518
1564
|
return this.#pollPromise;
|
|
1519
1565
|
this.#pollPromise = this.#poll().then((result) => {
|
|
@@ -1528,6 +1574,28 @@ var DshHostConnector = class {
|
|
|
1528
1574
|
});
|
|
1529
1575
|
return this.#pollPromise;
|
|
1530
1576
|
}
|
|
1577
|
+
async #withLocalSyncControl(operation) {
|
|
1578
|
+
const previous = this.#localSyncControlPromise;
|
|
1579
|
+
let release;
|
|
1580
|
+
const barrier = new Promise((resolve) => {
|
|
1581
|
+
release = resolve;
|
|
1582
|
+
});
|
|
1583
|
+
const current = (previous ?? Promise.resolve()).then(() => barrier);
|
|
1584
|
+
this.#localSyncControlPromise = current;
|
|
1585
|
+
try {
|
|
1586
|
+
await previous;
|
|
1587
|
+
const poll = this.#pollPromise;
|
|
1588
|
+
if (poll !== void 0)
|
|
1589
|
+
await poll;
|
|
1590
|
+
if (!this.#started || this.#stopped)
|
|
1591
|
+
throw new Error("DSH connector is not running");
|
|
1592
|
+
return await operation();
|
|
1593
|
+
} finally {
|
|
1594
|
+
release();
|
|
1595
|
+
if (this.#localSyncControlPromise === current)
|
|
1596
|
+
this.#localSyncControlPromise = void 0;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1531
1599
|
stop() {
|
|
1532
1600
|
this.#stopPromise ??= (async () => {
|
|
1533
1601
|
this.#stopped = true;
|
|
@@ -1557,14 +1625,17 @@ var DshHostConnector = class {
|
|
|
1557
1625
|
}
|
|
1558
1626
|
async #poll() {
|
|
1559
1627
|
const state = this.#requireState();
|
|
1560
|
-
await this.#flushOutbox();
|
|
1628
|
+
await this.#flushOutbox(state.automaticUpload);
|
|
1561
1629
|
await this.#finalizeDeliveredRequest();
|
|
1562
1630
|
if (state.activeRequest !== void 0) {
|
|
1563
1631
|
await this.#withExecutionPermit(() => this.#recoverActiveRequest());
|
|
1564
1632
|
return { scanned: 0, claimed: 0, completed: 1 };
|
|
1565
1633
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1634
|
+
if (state.automaticUpload) {
|
|
1635
|
+
await this.#captureLocalTurns();
|
|
1636
|
+
await this.#flushOutbox(true);
|
|
1637
|
+
}
|
|
1638
|
+
const manualUploadRequired = !state.automaticUpload && this.localSyncStatus().uploadableLocalTurns > 0;
|
|
1568
1639
|
let scanCursor = state.projectionCursor;
|
|
1569
1640
|
let scanned = 0;
|
|
1570
1641
|
while (!this.#stopped) {
|
|
@@ -1580,6 +1651,9 @@ var DshHostConnector = class {
|
|
|
1580
1651
|
continue;
|
|
1581
1652
|
}
|
|
1582
1653
|
await this.#projectCanonicalBatch(passive, passive.at(-1)?.sequence ?? state.projectionCursor);
|
|
1654
|
+
if (manualUploadRequired) {
|
|
1655
|
+
return { scanned, claimed: 0, completed: 0 };
|
|
1656
|
+
}
|
|
1583
1657
|
if (profile.provider !== void 0 && profile.provider !== this.#config.provider) {
|
|
1584
1658
|
throw new Error("DeepSeek Harness Agent request provider does not match the configured Host binding");
|
|
1585
1659
|
}
|
|
@@ -1757,7 +1831,7 @@ var DshHostConnector = class {
|
|
|
1757
1831
|
state.serverCursor = Math.max(state.serverCursor, throughSequence);
|
|
1758
1832
|
await this.#stateStore.save(state);
|
|
1759
1833
|
}
|
|
1760
|
-
async #captureLocalTurns() {
|
|
1834
|
+
async #captureLocalTurns(basedOnSequence) {
|
|
1761
1835
|
const state = this.#requireState();
|
|
1762
1836
|
if (state.activeRequest !== void 0 || state.outbox.length > 0)
|
|
1763
1837
|
return;
|
|
@@ -1775,7 +1849,7 @@ var DshHostConnector = class {
|
|
|
1775
1849
|
input: {
|
|
1776
1850
|
localTurnId: turn.localTurnId,
|
|
1777
1851
|
runtimeId: runtime.id,
|
|
1778
|
-
basedOnSequence: state.serverCursor,
|
|
1852
|
+
basedOnSequence: basedOnSequence ?? state.serverCursor,
|
|
1779
1853
|
occurredAt: turn.occurredAt,
|
|
1780
1854
|
observedModel: this.#config.model,
|
|
1781
1855
|
requestPayload: {
|
|
@@ -1877,13 +1951,15 @@ var DshHostConnector = class {
|
|
|
1877
1951
|
});
|
|
1878
1952
|
return operations;
|
|
1879
1953
|
}
|
|
1880
|
-
async #flushOutbox() {
|
|
1954
|
+
async #flushOutbox(includeLocalTurns = true) {
|
|
1881
1955
|
const state = this.#requireState();
|
|
1882
1956
|
const runtime = this.#requireRuntime();
|
|
1883
1957
|
while (state.outbox.length > 0) {
|
|
1884
1958
|
const operation = state.outbox[0];
|
|
1885
1959
|
if (operation === void 0)
|
|
1886
1960
|
break;
|
|
1961
|
+
if (operation.kind === "local_turn" && !includeLocalTurns)
|
|
1962
|
+
break;
|
|
1887
1963
|
if (operation.input.runtimeId !== runtime.id) {
|
|
1888
1964
|
throw new Error("DSH connector outbox belongs to a different runtime");
|
|
1889
1965
|
}
|
|
@@ -1965,7 +2041,7 @@ var DshHostConnector = class {
|
|
|
1965
2041
|
throw new Error("Persisted DSH Session exists without connector state; refusing ambiguous resume");
|
|
1966
2042
|
}
|
|
1967
2043
|
return {
|
|
1968
|
-
version:
|
|
2044
|
+
version: 3,
|
|
1969
2045
|
binding: {
|
|
1970
2046
|
projectId: this.#config.projectId,
|
|
1971
2047
|
sessionId: this.#config.sessionId,
|
|
@@ -1974,6 +2050,7 @@ var DshHostConnector = class {
|
|
|
1974
2050
|
serverCursor: 0,
|
|
1975
2051
|
projectionCursor: 0,
|
|
1976
2052
|
publishedDshSequence: 0,
|
|
2053
|
+
automaticUpload: true,
|
|
1977
2054
|
outbox: []
|
|
1978
2055
|
};
|
|
1979
2056
|
}
|
|
@@ -3273,6 +3350,21 @@ var DshProjectManager = class {
|
|
|
3273
3350
|
activeSessionIds() {
|
|
3274
3351
|
return [...this.#managed].filter(([, binding]) => binding.phase === "active" && !binding.connector.stopped).map(([sessionId]) => sessionId).sort();
|
|
3275
3352
|
}
|
|
3353
|
+
localSyncStatuses() {
|
|
3354
|
+
return [...this.#managed.entries()].filter(([, binding]) => binding.phase === "active" && !binding.connector.stopped).flatMap(([, binding]) => binding.connector.localSyncStatus?.() ?? []).sort((left, right) => left.sessionId.localeCompare(right.sessionId));
|
|
3355
|
+
}
|
|
3356
|
+
async setLocalAutoUpload(sessionId, enabled) {
|
|
3357
|
+
const connector = this.#requireManagedConnector(sessionId);
|
|
3358
|
+
if (!connector.setLocalAutoUpload)
|
|
3359
|
+
throw new Error("This DSH conversation does not support upload controls");
|
|
3360
|
+
return connector.setLocalAutoUpload(enabled);
|
|
3361
|
+
}
|
|
3362
|
+
async uploadLocalTurns(sessionId) {
|
|
3363
|
+
const connector = this.#requireManagedConnector(sessionId);
|
|
3364
|
+
if (!connector.uploadLocalTurns)
|
|
3365
|
+
throw new Error("This DSH conversation does not support manual upload");
|
|
3366
|
+
return connector.uploadLocalTurns();
|
|
3367
|
+
}
|
|
3276
3368
|
async start() {
|
|
3277
3369
|
if (this.#started)
|
|
3278
3370
|
throw new Error("DSH project manager is already started");
|
|
@@ -3512,6 +3604,13 @@ var DshProjectManager = class {
|
|
|
3512
3604
|
throw new Error("Authenticated GatherThread actor is unavailable");
|
|
3513
3605
|
return this.#actor;
|
|
3514
3606
|
}
|
|
3607
|
+
#requireManagedConnector(sessionId) {
|
|
3608
|
+
const binding = this.#managed.get(sessionId);
|
|
3609
|
+
if (!binding || binding.phase !== "active" || binding.connector.stopped) {
|
|
3610
|
+
throw new Error("The selected DSH conversation is not active");
|
|
3611
|
+
}
|
|
3612
|
+
return binding.connector;
|
|
3613
|
+
}
|
|
3515
3614
|
#reportError(error) {
|
|
3516
3615
|
this.#onBackgroundError?.(error);
|
|
3517
3616
|
}
|
|
@@ -3660,17 +3759,20 @@ var FileConnectorStateStore = class {
|
|
|
3660
3759
|
function validateConnectorState(value) {
|
|
3661
3760
|
const state = requiredObject4(value, "state");
|
|
3662
3761
|
const legacy = state.version === 1;
|
|
3762
|
+
const legacyUploadPolicy = state.version === 1 || state.version === 2;
|
|
3663
3763
|
exactKeys(state, /* @__PURE__ */ new Set([
|
|
3664
3764
|
"version",
|
|
3665
3765
|
"binding",
|
|
3666
3766
|
"serverCursor",
|
|
3667
3767
|
...legacy ? [] : ["projectionCursor"],
|
|
3668
3768
|
"publishedDshSequence",
|
|
3769
|
+
...legacyUploadPolicy ? [] : ["automaticUpload"],
|
|
3669
3770
|
"activeRequest",
|
|
3670
3771
|
"outbox"
|
|
3671
3772
|
]), "state");
|
|
3672
|
-
if (
|
|
3773
|
+
if (state.version !== 1 && state.version !== 2 && state.version !== 3) {
|
|
3673
3774
|
throw new Error("Unsupported DSH connector state version");
|
|
3775
|
+
}
|
|
3674
3776
|
const binding = requiredObject4(state.binding, "state.binding");
|
|
3675
3777
|
exactKeys(binding, /* @__PURE__ */ new Set(["projectId", "sessionId", "dshSessionId"]), "state.binding");
|
|
3676
3778
|
const serverCursor = nonNegativeInteger(state.serverCursor, "state.serverCursor");
|
|
@@ -3681,7 +3783,7 @@ function validateConnectorState(value) {
|
|
|
3681
3783
|
throw new Error("state.outbox must be an array");
|
|
3682
3784
|
const outbox = state.outbox.map(parseOutboxOperation);
|
|
3683
3785
|
const validated = {
|
|
3684
|
-
version:
|
|
3786
|
+
version: 3,
|
|
3685
3787
|
binding: {
|
|
3686
3788
|
projectId: safeString(binding.projectId, "state.binding.projectId", 128),
|
|
3687
3789
|
sessionId: safeString(binding.sessionId, "state.binding.sessionId", 128),
|
|
@@ -3690,6 +3792,7 @@ function validateConnectorState(value) {
|
|
|
3690
3792
|
serverCursor,
|
|
3691
3793
|
projectionCursor,
|
|
3692
3794
|
publishedDshSequence,
|
|
3795
|
+
automaticUpload: legacyUploadPolicy ? true : requiredBoolean(state.automaticUpload, "state.automaticUpload"),
|
|
3693
3796
|
...activeRequest === void 0 ? {} : { activeRequest },
|
|
3694
3797
|
outbox
|
|
3695
3798
|
};
|
|
@@ -3869,6 +3972,11 @@ function nonNegativeInteger(value, label) {
|
|
|
3869
3972
|
}
|
|
3870
3973
|
return Number(value);
|
|
3871
3974
|
}
|
|
3975
|
+
function requiredBoolean(value, label) {
|
|
3976
|
+
if (typeof value !== "boolean")
|
|
3977
|
+
throw new Error(`${label} must be a boolean`);
|
|
3978
|
+
return value;
|
|
3979
|
+
}
|
|
3872
3980
|
function positiveInteger(value, label) {
|
|
3873
3981
|
const parsed = nonNegativeInteger(value, label);
|
|
3874
3982
|
if (parsed === 0)
|
|
@@ -4133,7 +4241,9 @@ var RPC_ENDPOINTS = /* @__PURE__ */ new Set([
|
|
|
4133
4241
|
"pairing/cancel",
|
|
4134
4242
|
"catalog/get",
|
|
4135
4243
|
"connection/configure",
|
|
4136
|
-
"connection/disconnect"
|
|
4244
|
+
"connection/disconnect",
|
|
4245
|
+
"sync/set-auto-upload",
|
|
4246
|
+
"sync/upload"
|
|
4137
4247
|
]);
|
|
4138
4248
|
var MAX_CATALOG_PROVIDERS = 64;
|
|
4139
4249
|
var MAX_CATALOG_MODELS = 256;
|
|
@@ -4202,7 +4312,8 @@ var DshNativeHostController = class {
|
|
|
4202
4312
|
...this.#grant.route === void 0 ? {} : {
|
|
4203
4313
|
route: { ...this.#grant.route },
|
|
4204
4314
|
projectCount: this.#projects.size,
|
|
4205
|
-
bindings: this.#currentBindings(this.#grant.route).slice(0, 100)
|
|
4315
|
+
bindings: this.#currentBindings(this.#grant.route).slice(0, 100),
|
|
4316
|
+
localSync: this.#currentLocalSync().slice(0, 100)
|
|
4206
4317
|
}
|
|
4207
4318
|
},
|
|
4208
4319
|
...this.#pairing === void 0 ? {} : {
|
|
@@ -4371,6 +4482,34 @@ var DshNativeHostController = class {
|
|
|
4371
4482
|
this.#options.status.setConnection("stopped");
|
|
4372
4483
|
return this.publicState();
|
|
4373
4484
|
}
|
|
4485
|
+
async setLocalAutoUpload(inputValue) {
|
|
4486
|
+
this.#assertAvailable();
|
|
4487
|
+
const input = exactInput(inputValue, /* @__PURE__ */ new Set(["projectId", "sessionId", "enabled"]));
|
|
4488
|
+
const projectId = safeInputIdentifier(input.projectId, "projectId");
|
|
4489
|
+
const sessionId = safeInputIdentifier(input.sessionId, "sessionId");
|
|
4490
|
+
if (typeof input.enabled !== "boolean")
|
|
4491
|
+
throw new Error("enabled must be true or false");
|
|
4492
|
+
const owner = this.#owners.get(projectId);
|
|
4493
|
+
if (!owner)
|
|
4494
|
+
throw new Error("The selected GatherThread Project is not active");
|
|
4495
|
+
if (!owner.setLocalAutoUpload)
|
|
4496
|
+
throw new Error("This DSH Project does not expose upload controls");
|
|
4497
|
+
await owner.setLocalAutoUpload(sessionId, input.enabled);
|
|
4498
|
+
return this.publicState();
|
|
4499
|
+
}
|
|
4500
|
+
async uploadLocalTurns(inputValue) {
|
|
4501
|
+
this.#assertAvailable();
|
|
4502
|
+
const input = exactInput(inputValue, /* @__PURE__ */ new Set(["projectId", "sessionId"]));
|
|
4503
|
+
const projectId = safeInputIdentifier(input.projectId, "projectId");
|
|
4504
|
+
const sessionId = safeInputIdentifier(input.sessionId, "sessionId");
|
|
4505
|
+
const owner = this.#owners.get(projectId);
|
|
4506
|
+
if (!owner)
|
|
4507
|
+
throw new Error("The selected GatherThread Project is not active");
|
|
4508
|
+
if (!owner.uploadLocalTurns)
|
|
4509
|
+
throw new Error("This DSH Project does not expose manual upload");
|
|
4510
|
+
await owner.uploadLocalTurns(sessionId);
|
|
4511
|
+
return this.publicState();
|
|
4512
|
+
}
|
|
4374
4513
|
dispose() {
|
|
4375
4514
|
this.#disposePromise ??= (async () => {
|
|
4376
4515
|
this.#disposed = true;
|
|
@@ -4507,6 +4646,20 @@ var DshNativeHostController = class {
|
|
|
4507
4646
|
model: route.model
|
|
4508
4647
|
})).sort((left, right) => left.projectName.localeCompare(right.projectName) || left.projectId.localeCompare(right.projectId));
|
|
4509
4648
|
}
|
|
4649
|
+
#currentLocalSync() {
|
|
4650
|
+
const titles = new Map(this.#options.status.snapshot().sessions.map((session) => [session.sessionId, session.title]));
|
|
4651
|
+
return [...this.#owners.entries()].flatMap(([projectId, owner]) => {
|
|
4652
|
+
const project = this.#projects.get(projectId);
|
|
4653
|
+
if (!project)
|
|
4654
|
+
return [];
|
|
4655
|
+
return (owner.localSyncStatuses?.() ?? []).map((sync) => ({
|
|
4656
|
+
...sync,
|
|
4657
|
+
projectId,
|
|
4658
|
+
projectName: boundedPublicText2(project.name, "project name", 160),
|
|
4659
|
+
title: titles.get(sync.sessionId) ?? sync.sessionId
|
|
4660
|
+
}));
|
|
4661
|
+
}).sort((left, right) => left.projectName.localeCompare(right.projectName) || left.title.localeCompare(right.title) || left.sessionId.localeCompare(right.sessionId));
|
|
4662
|
+
}
|
|
4510
4663
|
#updateProjectStatus(projectId, update) {
|
|
4511
4664
|
if (!this.#projects.has(projectId) || this.#disposed)
|
|
4512
4665
|
return;
|
|
@@ -4628,6 +4781,10 @@ function registerNativeDshRpc(contextValue, controller) {
|
|
|
4628
4781
|
case "connection/disconnect":
|
|
4629
4782
|
exactInput(payload, /* @__PURE__ */ new Set());
|
|
4630
4783
|
return rpcSuccess(await controller.disconnect());
|
|
4784
|
+
case "sync/set-auto-upload":
|
|
4785
|
+
return rpcSuccess(await controller.setLocalAutoUpload(payload));
|
|
4786
|
+
case "sync/upload":
|
|
4787
|
+
return rpcSuccess(await controller.uploadLocalTurns(payload));
|
|
4631
4788
|
default:
|
|
4632
4789
|
return rpcFailure("gatherthread/not-found", "GatherThread DSH action is unavailable");
|
|
4633
4790
|
}
|
|
@@ -4709,6 +4866,9 @@ async function createProductionOwner(options) {
|
|
|
4709
4866
|
}
|
|
4710
4867
|
let stopPromise;
|
|
4711
4868
|
return {
|
|
4869
|
+
localSyncStatuses: () => manager.localSyncStatuses(),
|
|
4870
|
+
setLocalAutoUpload: (sessionId, enabled) => manager.setLocalAutoUpload(sessionId, enabled),
|
|
4871
|
+
uploadLocalTurns: (sessionId) => manager.uploadLocalTurns(sessionId),
|
|
4712
4872
|
stop() {
|
|
4713
4873
|
stopPromise ??= (async () => {
|
|
4714
4874
|
options.signal.removeEventListener("abort", abortFromRoot);
|
|
@@ -4773,6 +4933,9 @@ function createNativeManagedConnector(options) {
|
|
|
4773
4933
|
async start() {
|
|
4774
4934
|
await connector.start();
|
|
4775
4935
|
},
|
|
4936
|
+
localSyncStatus: () => connector.localSyncStatus(),
|
|
4937
|
+
setLocalAutoUpload: (enabled) => connector.setLocalAutoUpload(enabled),
|
|
4938
|
+
uploadLocalTurns: () => connector.uploadLocalTurns(),
|
|
4776
4939
|
stop() {
|
|
4777
4940
|
stopPromise ??= (async () => {
|
|
4778
4941
|
options.rootSignal.removeEventListener("abort", abortFromRoot);
|
package/dist/bundle/plugin.js
CHANGED
|
@@ -1309,6 +1309,7 @@ var DshHostConnector = class {
|
|
|
1309
1309
|
#timer;
|
|
1310
1310
|
#heartbeatTimer;
|
|
1311
1311
|
#pollPromise;
|
|
1312
|
+
#localSyncControlPromise;
|
|
1312
1313
|
#heartbeatPromise;
|
|
1313
1314
|
#stopPromise;
|
|
1314
1315
|
#backgroundFatalError;
|
|
@@ -1340,6 +1341,47 @@ var DshHostConnector = class {
|
|
|
1340
1341
|
get stopped() {
|
|
1341
1342
|
return this.#stopped;
|
|
1342
1343
|
}
|
|
1344
|
+
localSyncStatus() {
|
|
1345
|
+
const state = this.#requireState();
|
|
1346
|
+
const pendingOperations = state.outbox.filter((operation) => operation.kind === "local_turn");
|
|
1347
|
+
const pending = pendingOperations.length;
|
|
1348
|
+
const discoverFrom = pendingOperations.reduce((sequence, operation) => Math.max(sequence, operation.dshToSequence), state.publishedDshSequence);
|
|
1349
|
+
const discoverable = state.activeRequest === void 0 ? captureCompletedLocalTurns(this.#host.snapshotFrom(discoverFrom), discoverFrom, this.#config.dshSessionId).turns.length : 0;
|
|
1350
|
+
return {
|
|
1351
|
+
sessionId: this.#config.sessionId,
|
|
1352
|
+
localSessionId: this.#config.dshSessionId,
|
|
1353
|
+
automaticUpload: state.automaticUpload,
|
|
1354
|
+
pendingLocalTurns: pending,
|
|
1355
|
+
uploadableLocalTurns: pending + discoverable
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
async setLocalAutoUpload(enabled) {
|
|
1359
|
+
const status = await this.#withLocalSyncControl(async () => {
|
|
1360
|
+
const state = this.#requireState();
|
|
1361
|
+
state.automaticUpload = enabled;
|
|
1362
|
+
await this.#stateStore.save(state);
|
|
1363
|
+
return this.localSyncStatus();
|
|
1364
|
+
});
|
|
1365
|
+
if (enabled)
|
|
1366
|
+
await this.pollOnce();
|
|
1367
|
+
return enabled ? this.localSyncStatus() : status;
|
|
1368
|
+
}
|
|
1369
|
+
uploadLocalTurns() {
|
|
1370
|
+
return this.#withLocalSyncControl(async () => {
|
|
1371
|
+
const before = this.localSyncStatus();
|
|
1372
|
+
await this.#flushOutbox(true);
|
|
1373
|
+
await this.#captureLocalTurns(0);
|
|
1374
|
+
const discoveredLocalTurns = this.localSyncStatus().pendingLocalTurns;
|
|
1375
|
+
const pendingBefore = this.localSyncStatus().pendingLocalTurns;
|
|
1376
|
+
await this.#flushOutbox(true);
|
|
1377
|
+
const after = this.localSyncStatus();
|
|
1378
|
+
return {
|
|
1379
|
+
...after,
|
|
1380
|
+
discoveredLocalTurns,
|
|
1381
|
+
uploadedLocalTurns: Math.max(0, before.pendingLocalTurns + pendingBefore - after.pendingLocalTurns)
|
|
1382
|
+
};
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1343
1385
|
async start(options = {}) {
|
|
1344
1386
|
if (this.#started)
|
|
1345
1387
|
throw new Error("DSH connector is already started");
|
|
@@ -1404,7 +1446,7 @@ var DshHostConnector = class {
|
|
|
1404
1446
|
this.#notifyLifecycle("idle");
|
|
1405
1447
|
if (options.schedule !== false)
|
|
1406
1448
|
this.#scheduleHeartbeat();
|
|
1407
|
-
await this.#flushOutbox();
|
|
1449
|
+
await this.#flushOutbox(this.#requireState().automaticUpload);
|
|
1408
1450
|
await this.#finalizeDeliveredRequest();
|
|
1409
1451
|
if (this.#state.activeRequest !== void 0) {
|
|
1410
1452
|
await this.#withExecutionPermit(() => this.#recoverActiveRequest());
|
|
@@ -1425,6 +1467,10 @@ var DshHostConnector = class {
|
|
|
1425
1467
|
if (!this.#started || this.#stopped) {
|
|
1426
1468
|
return Promise.reject(new Error("DSH connector is not running"));
|
|
1427
1469
|
}
|
|
1470
|
+
const localSyncControl = this.#localSyncControlPromise;
|
|
1471
|
+
if (localSyncControl !== void 0) {
|
|
1472
|
+
return localSyncControl.then(() => this.pollOnce());
|
|
1473
|
+
}
|
|
1428
1474
|
if (this.#pollPromise !== void 0)
|
|
1429
1475
|
return this.#pollPromise;
|
|
1430
1476
|
this.#pollPromise = this.#poll().then((result) => {
|
|
@@ -1439,6 +1485,28 @@ var DshHostConnector = class {
|
|
|
1439
1485
|
});
|
|
1440
1486
|
return this.#pollPromise;
|
|
1441
1487
|
}
|
|
1488
|
+
async #withLocalSyncControl(operation) {
|
|
1489
|
+
const previous = this.#localSyncControlPromise;
|
|
1490
|
+
let release;
|
|
1491
|
+
const barrier = new Promise((resolve) => {
|
|
1492
|
+
release = resolve;
|
|
1493
|
+
});
|
|
1494
|
+
const current = (previous ?? Promise.resolve()).then(() => barrier);
|
|
1495
|
+
this.#localSyncControlPromise = current;
|
|
1496
|
+
try {
|
|
1497
|
+
await previous;
|
|
1498
|
+
const poll = this.#pollPromise;
|
|
1499
|
+
if (poll !== void 0)
|
|
1500
|
+
await poll;
|
|
1501
|
+
if (!this.#started || this.#stopped)
|
|
1502
|
+
throw new Error("DSH connector is not running");
|
|
1503
|
+
return await operation();
|
|
1504
|
+
} finally {
|
|
1505
|
+
release();
|
|
1506
|
+
if (this.#localSyncControlPromise === current)
|
|
1507
|
+
this.#localSyncControlPromise = void 0;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1442
1510
|
stop() {
|
|
1443
1511
|
this.#stopPromise ??= (async () => {
|
|
1444
1512
|
this.#stopped = true;
|
|
@@ -1468,14 +1536,17 @@ var DshHostConnector = class {
|
|
|
1468
1536
|
}
|
|
1469
1537
|
async #poll() {
|
|
1470
1538
|
const state = this.#requireState();
|
|
1471
|
-
await this.#flushOutbox();
|
|
1539
|
+
await this.#flushOutbox(state.automaticUpload);
|
|
1472
1540
|
await this.#finalizeDeliveredRequest();
|
|
1473
1541
|
if (state.activeRequest !== void 0) {
|
|
1474
1542
|
await this.#withExecutionPermit(() => this.#recoverActiveRequest());
|
|
1475
1543
|
return { scanned: 0, claimed: 0, completed: 1 };
|
|
1476
1544
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1545
|
+
if (state.automaticUpload) {
|
|
1546
|
+
await this.#captureLocalTurns();
|
|
1547
|
+
await this.#flushOutbox(true);
|
|
1548
|
+
}
|
|
1549
|
+
const manualUploadRequired = !state.automaticUpload && this.localSyncStatus().uploadableLocalTurns > 0;
|
|
1479
1550
|
let scanCursor = state.projectionCursor;
|
|
1480
1551
|
let scanned = 0;
|
|
1481
1552
|
while (!this.#stopped) {
|
|
@@ -1491,6 +1562,9 @@ var DshHostConnector = class {
|
|
|
1491
1562
|
continue;
|
|
1492
1563
|
}
|
|
1493
1564
|
await this.#projectCanonicalBatch(passive, passive.at(-1)?.sequence ?? state.projectionCursor);
|
|
1565
|
+
if (manualUploadRequired) {
|
|
1566
|
+
return { scanned, claimed: 0, completed: 0 };
|
|
1567
|
+
}
|
|
1494
1568
|
if (profile.provider !== void 0 && profile.provider !== this.#config.provider) {
|
|
1495
1569
|
throw new Error("DeepSeek Harness Agent request provider does not match the configured Host binding");
|
|
1496
1570
|
}
|
|
@@ -1668,7 +1742,7 @@ var DshHostConnector = class {
|
|
|
1668
1742
|
state.serverCursor = Math.max(state.serverCursor, throughSequence);
|
|
1669
1743
|
await this.#stateStore.save(state);
|
|
1670
1744
|
}
|
|
1671
|
-
async #captureLocalTurns() {
|
|
1745
|
+
async #captureLocalTurns(basedOnSequence) {
|
|
1672
1746
|
const state = this.#requireState();
|
|
1673
1747
|
if (state.activeRequest !== void 0 || state.outbox.length > 0)
|
|
1674
1748
|
return;
|
|
@@ -1686,7 +1760,7 @@ var DshHostConnector = class {
|
|
|
1686
1760
|
input: {
|
|
1687
1761
|
localTurnId: turn.localTurnId,
|
|
1688
1762
|
runtimeId: runtime.id,
|
|
1689
|
-
basedOnSequence: state.serverCursor,
|
|
1763
|
+
basedOnSequence: basedOnSequence ?? state.serverCursor,
|
|
1690
1764
|
occurredAt: turn.occurredAt,
|
|
1691
1765
|
observedModel: this.#config.model,
|
|
1692
1766
|
requestPayload: {
|
|
@@ -1788,13 +1862,15 @@ var DshHostConnector = class {
|
|
|
1788
1862
|
});
|
|
1789
1863
|
return operations;
|
|
1790
1864
|
}
|
|
1791
|
-
async #flushOutbox() {
|
|
1865
|
+
async #flushOutbox(includeLocalTurns = true) {
|
|
1792
1866
|
const state = this.#requireState();
|
|
1793
1867
|
const runtime = this.#requireRuntime();
|
|
1794
1868
|
while (state.outbox.length > 0) {
|
|
1795
1869
|
const operation = state.outbox[0];
|
|
1796
1870
|
if (operation === void 0)
|
|
1797
1871
|
break;
|
|
1872
|
+
if (operation.kind === "local_turn" && !includeLocalTurns)
|
|
1873
|
+
break;
|
|
1798
1874
|
if (operation.input.runtimeId !== runtime.id) {
|
|
1799
1875
|
throw new Error("DSH connector outbox belongs to a different runtime");
|
|
1800
1876
|
}
|
|
@@ -1876,7 +1952,7 @@ var DshHostConnector = class {
|
|
|
1876
1952
|
throw new Error("Persisted DSH Session exists without connector state; refusing ambiguous resume");
|
|
1877
1953
|
}
|
|
1878
1954
|
return {
|
|
1879
|
-
version:
|
|
1955
|
+
version: 3,
|
|
1880
1956
|
binding: {
|
|
1881
1957
|
projectId: this.#config.projectId,
|
|
1882
1958
|
sessionId: this.#config.sessionId,
|
|
@@ -1885,6 +1961,7 @@ var DshHostConnector = class {
|
|
|
1885
1961
|
serverCursor: 0,
|
|
1886
1962
|
projectionCursor: 0,
|
|
1887
1963
|
publishedDshSequence: 0,
|
|
1964
|
+
automaticUpload: true,
|
|
1888
1965
|
outbox: []
|
|
1889
1966
|
};
|
|
1890
1967
|
}
|
|
@@ -2648,17 +2725,20 @@ var FileConnectorStateStore = class {
|
|
|
2648
2725
|
function validateConnectorState(value) {
|
|
2649
2726
|
const state = requiredObject3(value, "state");
|
|
2650
2727
|
const legacy = state.version === 1;
|
|
2728
|
+
const legacyUploadPolicy = state.version === 1 || state.version === 2;
|
|
2651
2729
|
exactKeys(state, /* @__PURE__ */ new Set([
|
|
2652
2730
|
"version",
|
|
2653
2731
|
"binding",
|
|
2654
2732
|
"serverCursor",
|
|
2655
2733
|
...legacy ? [] : ["projectionCursor"],
|
|
2656
2734
|
"publishedDshSequence",
|
|
2735
|
+
...legacyUploadPolicy ? [] : ["automaticUpload"],
|
|
2657
2736
|
"activeRequest",
|
|
2658
2737
|
"outbox"
|
|
2659
2738
|
]), "state");
|
|
2660
|
-
if (
|
|
2739
|
+
if (state.version !== 1 && state.version !== 2 && state.version !== 3) {
|
|
2661
2740
|
throw new Error("Unsupported DSH connector state version");
|
|
2741
|
+
}
|
|
2662
2742
|
const binding = requiredObject3(state.binding, "state.binding");
|
|
2663
2743
|
exactKeys(binding, /* @__PURE__ */ new Set(["projectId", "sessionId", "dshSessionId"]), "state.binding");
|
|
2664
2744
|
const serverCursor = nonNegativeInteger(state.serverCursor, "state.serverCursor");
|
|
@@ -2669,7 +2749,7 @@ function validateConnectorState(value) {
|
|
|
2669
2749
|
throw new Error("state.outbox must be an array");
|
|
2670
2750
|
const outbox = state.outbox.map(parseOutboxOperation);
|
|
2671
2751
|
const validated = {
|
|
2672
|
-
version:
|
|
2752
|
+
version: 3,
|
|
2673
2753
|
binding: {
|
|
2674
2754
|
projectId: safeString(binding.projectId, "state.binding.projectId", 128),
|
|
2675
2755
|
sessionId: safeString(binding.sessionId, "state.binding.sessionId", 128),
|
|
@@ -2678,6 +2758,7 @@ function validateConnectorState(value) {
|
|
|
2678
2758
|
serverCursor,
|
|
2679
2759
|
projectionCursor,
|
|
2680
2760
|
publishedDshSequence,
|
|
2761
|
+
automaticUpload: legacyUploadPolicy ? true : requiredBoolean(state.automaticUpload, "state.automaticUpload"),
|
|
2681
2762
|
...activeRequest === void 0 ? {} : { activeRequest },
|
|
2682
2763
|
outbox
|
|
2683
2764
|
};
|
|
@@ -2857,6 +2938,11 @@ function nonNegativeInteger(value, label) {
|
|
|
2857
2938
|
}
|
|
2858
2939
|
return Number(value);
|
|
2859
2940
|
}
|
|
2941
|
+
function requiredBoolean(value, label) {
|
|
2942
|
+
if (typeof value !== "boolean")
|
|
2943
|
+
throw new Error(`${label} must be a boolean`);
|
|
2944
|
+
return value;
|
|
2945
|
+
}
|
|
2860
2946
|
function positiveInteger(value, label) {
|
|
2861
2947
|
const parsed = nonNegativeInteger(value, label);
|
|
2862
2948
|
if (parsed === 0)
|
|
@@ -3020,6 +3106,21 @@ var DshProjectManager = class {
|
|
|
3020
3106
|
activeSessionIds() {
|
|
3021
3107
|
return [...this.#managed].filter(([, binding]) => binding.phase === "active" && !binding.connector.stopped).map(([sessionId]) => sessionId).sort();
|
|
3022
3108
|
}
|
|
3109
|
+
localSyncStatuses() {
|
|
3110
|
+
return [...this.#managed.entries()].filter(([, binding]) => binding.phase === "active" && !binding.connector.stopped).flatMap(([, binding]) => binding.connector.localSyncStatus?.() ?? []).sort((left, right) => left.sessionId.localeCompare(right.sessionId));
|
|
3111
|
+
}
|
|
3112
|
+
async setLocalAutoUpload(sessionId, enabled) {
|
|
3113
|
+
const connector = this.#requireManagedConnector(sessionId);
|
|
3114
|
+
if (!connector.setLocalAutoUpload)
|
|
3115
|
+
throw new Error("This DSH conversation does not support upload controls");
|
|
3116
|
+
return connector.setLocalAutoUpload(enabled);
|
|
3117
|
+
}
|
|
3118
|
+
async uploadLocalTurns(sessionId) {
|
|
3119
|
+
const connector = this.#requireManagedConnector(sessionId);
|
|
3120
|
+
if (!connector.uploadLocalTurns)
|
|
3121
|
+
throw new Error("This DSH conversation does not support manual upload");
|
|
3122
|
+
return connector.uploadLocalTurns();
|
|
3123
|
+
}
|
|
3023
3124
|
async start() {
|
|
3024
3125
|
if (this.#started)
|
|
3025
3126
|
throw new Error("DSH project manager is already started");
|
|
@@ -3259,6 +3360,13 @@ var DshProjectManager = class {
|
|
|
3259
3360
|
throw new Error("Authenticated GatherThread actor is unavailable");
|
|
3260
3361
|
return this.#actor;
|
|
3261
3362
|
}
|
|
3363
|
+
#requireManagedConnector(sessionId) {
|
|
3364
|
+
const binding = this.#managed.get(sessionId);
|
|
3365
|
+
if (!binding || binding.phase !== "active" || binding.connector.stopped) {
|
|
3366
|
+
throw new Error("The selected DSH conversation is not active");
|
|
3367
|
+
}
|
|
3368
|
+
return binding.connector;
|
|
3369
|
+
}
|
|
3262
3370
|
#reportError(error) {
|
|
3263
3371
|
this.#onBackgroundError?.(error);
|
|
3264
3372
|
}
|