dsh-remote-local-plugin 0.8.6 → 0.8.8
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
CHANGED
|
@@ -7,7 +7,7 @@ Remote development that appears as ordinary local sessions.
|
|
|
7
7
|
This plugin targets the official **DSH Desktop** application. Tested/supported versions:
|
|
8
8
|
|
|
9
9
|
- [DSH Desktop](https://github.com/deepseek-ai/deepseek-harness) [`0.2.1-rc.2`](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-rc.2)
|
|
10
|
-
- Plugin [`0.8.
|
|
10
|
+
- Plugin [`0.8.8`](https://www.npmjs.com/package/dsh-remote-local-plugin)
|
|
11
11
|
- Remote DSH [`0.2.1-rc.2`](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-rc.2)
|
|
12
12
|
- Remote [Node.js `22.19+`](https://nodejs.org/en/download) on Linux/macOS
|
|
13
13
|
|
|
Binary file
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* dsh-remote-local-plugin@0.8.
|
|
1
|
+
/* dsh-remote-local-plugin@0.8.8 — bundled host entry */
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
@@ -664,14 +664,14 @@ var RemoteMirrorService = class {
|
|
|
664
664
|
const workspaces = await this.#hosts.workspaces(hostId);
|
|
665
665
|
for (const workspace of workspaces) {
|
|
666
666
|
if (isTestHidden(workspace.path, workspace.workspaceId, this.#testVisibility)) continue;
|
|
667
|
-
const sessions = await this.#hosts.sessions(hostId, workspace.workspaceId);
|
|
667
|
+
const sessions = await this.#hosts.sessions(hostId, workspace.workspaceId, true);
|
|
668
668
|
const summary2 = sessions.sessions.find((item) => item.sessionId === remoteSessionId);
|
|
669
669
|
if (summary2 === void 0) continue;
|
|
670
670
|
const localWorkspace2 = await this.#ensureWorkspace(host, workspace);
|
|
671
671
|
const record = await this.#ensureMirror(host, summary2, localWorkspace2, workspace.path);
|
|
672
672
|
return record?.localSessionId;
|
|
673
673
|
}
|
|
674
|
-
const all = await this.#hosts.sessions(hostId);
|
|
674
|
+
const all = await this.#hosts.sessions(hostId, void 0, true);
|
|
675
675
|
const summary = all.sessions.find((item) => item.sessionId === remoteSessionId);
|
|
676
676
|
if (summary === void 0) return void 0;
|
|
677
677
|
if (summary.cwd !== null && isTestHidden(summary.cwd, "", this.#testVisibility)) return void 0;
|
|
@@ -742,24 +742,31 @@ var RemoteMirrorService = class {
|
|
|
742
742
|
await this.#reconcileTestHidden(host);
|
|
743
743
|
const workspaces = await this.#hosts.workspaces(host.id);
|
|
744
744
|
const covered = /* @__PURE__ */ new Set();
|
|
745
|
+
const subagents = /* @__PURE__ */ new Set();
|
|
745
746
|
const byPath = /* @__PURE__ */ new Map();
|
|
746
747
|
for (const workspace of workspaces) {
|
|
747
748
|
if (isTestHidden(workspace.path, workspace.workspaceId, this.#testVisibility)) {
|
|
748
|
-
const sessions2 = await this.#hosts.sessions(host.id, workspace.workspaceId);
|
|
749
|
+
const sessions2 = await this.#hosts.sessions(host.id, workspace.workspaceId, true);
|
|
749
750
|
for (const summary of sessions2.sessions) covered.add(summary.sessionId);
|
|
750
751
|
continue;
|
|
751
752
|
}
|
|
752
753
|
const local = await this.#ensureWorkspace(host, workspace);
|
|
753
754
|
byPath.set(workspace.path, { workspace: local, anchor: this.#anchorFor(local) });
|
|
754
|
-
const sessions = await this.#hosts.sessions(host.id, workspace.workspaceId);
|
|
755
|
+
const sessions = await this.#hosts.sessions(host.id, workspace.workspaceId, true);
|
|
755
756
|
for (const summary of sessions.sessions) {
|
|
756
757
|
covered.add(summary.sessionId);
|
|
758
|
+
if (summary.origin === "subagent") {
|
|
759
|
+
subagents.add(summary.sessionId);
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
757
762
|
await this.#ensureMirror(host, summary, local, workspace.path);
|
|
758
763
|
}
|
|
759
764
|
}
|
|
760
|
-
const all = await this.#hosts.sessions(host.id);
|
|
765
|
+
const all = await this.#hosts.sessions(host.id, void 0, true);
|
|
761
766
|
for (const summary of all.sessions) {
|
|
767
|
+
if (summary.origin === "subagent") subagents.add(summary.sessionId);
|
|
762
768
|
if (covered.has(summary.sessionId)) continue;
|
|
769
|
+
if (summary.origin === "subagent") continue;
|
|
763
770
|
if (summary.cwd !== null && isTestHidden(summary.cwd, "", this.#testVisibility)) continue;
|
|
764
771
|
const workspace = summary.cwd === null ? void 0 : byPath.get(summary.cwd);
|
|
765
772
|
if (workspace !== void 0) {
|
|
@@ -769,8 +776,47 @@ var RemoteMirrorService = class {
|
|
|
769
776
|
const ungrouped = await this.#ensureUngroupedWorkspace(host, summary.cwd ?? "ungrouped");
|
|
770
777
|
await this.#ensureMirror(host, summary, ungrouped.workspace, ungrouped.anchor);
|
|
771
778
|
}
|
|
779
|
+
await this.#pruneMirroredSubagents(host.id, subagents);
|
|
780
|
+
}
|
|
781
|
+
/** Remove local mirrors for remote subagent Sessions, including stale rows. */
|
|
782
|
+
async #pruneMirroredSubagents(hostId, subagents) {
|
|
783
|
+
let changed = false;
|
|
784
|
+
for (const [ref, record] of Object.entries(this.#state.sessions)) {
|
|
785
|
+
if (record.hostId !== hostId || !subagents.has(record.remoteSessionId)) continue;
|
|
786
|
+
const runtime = this.#runtimes.get(record.localSessionId);
|
|
787
|
+
if (runtime !== void 0) {
|
|
788
|
+
runtime.stopped = true;
|
|
789
|
+
if (runtime.timer !== void 0) clearTimeout(runtime.timer);
|
|
790
|
+
runtime.stream?.abort();
|
|
791
|
+
runtime.assistant?.abort();
|
|
792
|
+
this.#runtimes.delete(record.localSessionId);
|
|
793
|
+
}
|
|
794
|
+
const registration = this.#registrations.get(record.localSessionId);
|
|
795
|
+
if (registration !== void 0) {
|
|
796
|
+
registration();
|
|
797
|
+
this.#registrations.delete(record.localSessionId);
|
|
798
|
+
}
|
|
799
|
+
await this.#archive(record.localSessionId);
|
|
800
|
+
delete this.#state.sessions[ref];
|
|
801
|
+
changed = true;
|
|
802
|
+
}
|
|
803
|
+
if (!changed) return;
|
|
804
|
+
for (const [ref, workspace] of Object.entries(this.#state.workspaces)) {
|
|
805
|
+
if (workspace.hostId !== hostId || workspace.remoteWorkspaceId !== null) continue;
|
|
806
|
+
const used = Object.values(this.#state.sessions).some(
|
|
807
|
+
(session) => session.hostId === hostId && session.anchor === workspace.anchor
|
|
808
|
+
);
|
|
809
|
+
if (used) continue;
|
|
810
|
+
try {
|
|
811
|
+
await this.#workspaceRegistry.delete(workspace.localWorkspaceId);
|
|
812
|
+
} catch {
|
|
813
|
+
}
|
|
814
|
+
delete this.#state.workspaces[ref];
|
|
815
|
+
}
|
|
816
|
+
await this.#save();
|
|
772
817
|
}
|
|
773
818
|
/**
|
|
819
|
+
* Apply the test-only manifest to already-mirrored workspaces. Hidden entries /**
|
|
774
820
|
* Apply the test-only manifest to already-mirrored workspaces. Hidden entries
|
|
775
821
|
* archive local Sessions and remove the local workspace row; removing an
|
|
776
822
|
* entry unarchives the Sessions and lets the normal sync rebuild the row.
|
|
@@ -1061,6 +1107,13 @@ var RemoteMirrorService = class {
|
|
|
1061
1107
|
runtime.agent = agent;
|
|
1062
1108
|
this.#runtimes.set(record.localSessionId, runtime);
|
|
1063
1109
|
this.#registrations.set(record.localSessionId, this.#agents.enter(agent, void 0));
|
|
1110
|
+
const localMode = this.#localSandboxMode(session);
|
|
1111
|
+
const remoteMode = latestRemoteSandboxMode(snapshot.events);
|
|
1112
|
+
if (localMode !== void 0 && remoteMode !== void 0 && localMode !== remoteMode) {
|
|
1113
|
+
void this.#hosts.setSandboxMode(record.hostId, record.remoteSessionId, localMode).catch((error) => {
|
|
1114
|
+
this.#ctx.logger.warn("dsh-remote-local: initial sandbox mode failed: %s", String(error));
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1064
1117
|
await this.#save();
|
|
1065
1118
|
this.#schedule(runtime);
|
|
1066
1119
|
if (live) {
|
|
@@ -1196,9 +1249,18 @@ var RemoteMirrorService = class {
|
|
|
1196
1249
|
}
|
|
1197
1250
|
#onLocalSessionEvent(session, event) {
|
|
1198
1251
|
const candidate = event;
|
|
1199
|
-
if (candidate.type !== "model/selection") return;
|
|
1252
|
+
if (candidate.type !== "model/selection" && candidate.type !== "sandbox/mode") return;
|
|
1200
1253
|
const runtime = [...this.#runtimes.values()].find((item) => item.session.id === session.id);
|
|
1201
1254
|
if (runtime === void 0 || runtime.stopped || runtime.applyingRemote) return;
|
|
1255
|
+
if (candidate.type === "sandbox/mode") {
|
|
1256
|
+
const mode = candidate.data.mode;
|
|
1257
|
+
if (mode !== "read-only" && mode !== "workspace-write" && mode !== "danger-full-access") return;
|
|
1258
|
+
void this.#hosts.setSandboxMode(runtime.record.hostId, runtime.record.remoteSessionId, mode).catch((error) => {
|
|
1259
|
+
runtime.lastError = error instanceof Error ? error.message : String(error);
|
|
1260
|
+
this.#ctx.logger.warn("dsh-remote-local: remote sandbox mode failed: %s", runtime.lastError);
|
|
1261
|
+
});
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1202
1264
|
const record = candidate.data;
|
|
1203
1265
|
if (typeof record.provider !== "string" || typeof record.model !== "string") return;
|
|
1204
1266
|
const selection = {
|
|
@@ -1212,6 +1274,11 @@ var RemoteMirrorService = class {
|
|
|
1212
1274
|
this.#ctx.logger.warn("dsh-remote-local: remote model selection failed: %s", runtime.lastError);
|
|
1213
1275
|
});
|
|
1214
1276
|
}
|
|
1277
|
+
#localSandboxMode(session) {
|
|
1278
|
+
const projections = this.#ctx.get("sessionProjections");
|
|
1279
|
+
const mode = projections?.stateOf?.(session, "sandboxMode");
|
|
1280
|
+
return mode === "read-only" || mode === "workspace-write" || mode === "danger-full-access" ? mode : void 0;
|
|
1281
|
+
}
|
|
1215
1282
|
#defaultSelection() {
|
|
1216
1283
|
const defaults = this.#ctx.get("agentDefaultModel");
|
|
1217
1284
|
const candidate = defaults?.currentSelection?.();
|
|
@@ -1468,6 +1535,15 @@ function latestSelection(session) {
|
|
|
1468
1535
|
}
|
|
1469
1536
|
return void 0;
|
|
1470
1537
|
}
|
|
1538
|
+
function latestRemoteSandboxMode(events) {
|
|
1539
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
1540
|
+
const event = events[index];
|
|
1541
|
+
if (event === void 0 || event.type !== "sandbox/mode") continue;
|
|
1542
|
+
const mode = event.data.mode;
|
|
1543
|
+
if (mode === "read-only" || mode === "workspace-write" || mode === "danger-full-access") return mode;
|
|
1544
|
+
}
|
|
1545
|
+
return void 0;
|
|
1546
|
+
}
|
|
1471
1547
|
function contentText(message) {
|
|
1472
1548
|
if (typeof message === "string") return message;
|
|
1473
1549
|
if (typeof message !== "object" || message === null) return "";
|
|
@@ -2225,6 +2301,9 @@ function sessionPath(sessionId) {
|
|
|
2225
2301
|
function sessionModelPath(sessionId) {
|
|
2226
2302
|
return `${sessionPath(sessionId)}/model`;
|
|
2227
2303
|
}
|
|
2304
|
+
function sessionSandboxModePath(sessionId) {
|
|
2305
|
+
return `${sessionPath(sessionId)}/sandbox`;
|
|
2306
|
+
}
|
|
2228
2307
|
function sessionPromptPath(sessionId) {
|
|
2229
2308
|
return `${sessionPath(sessionId)}/prompt`;
|
|
2230
2309
|
}
|
|
@@ -2363,7 +2442,7 @@ function isRemoteWorkspace(value) {
|
|
|
2363
2442
|
function isRemoteSession(value) {
|
|
2364
2443
|
if (!isRecord(value))
|
|
2365
2444
|
return false;
|
|
2366
|
-
return isString(value.sessionId) && (value.workspaceId === null || isString(value.workspaceId)) && (value.cwd === null || isString(value.cwd)) && isIsoString(value.createdAt) && typeof value.live === "boolean" && (value.eventCount === null || typeof value.eventCount === "number");
|
|
2445
|
+
return isString(value.sessionId) && (value.workspaceId === null || isString(value.workspaceId)) && (value.cwd === null || isString(value.cwd)) && isIsoString(value.createdAt) && typeof value.live === "boolean" && (value.eventCount === null || typeof value.eventCount === "number") && (value.origin === void 0 || value.origin === "subagent") && (value.parentSession === void 0 || isString(value.parentSession));
|
|
2367
2446
|
}
|
|
2368
2447
|
function isRemoteEvent(value) {
|
|
2369
2448
|
return isRecord(value) && typeof value.seq === "number" && isString(value.type) && typeof value.time === "number" && "data" in value;
|
|
@@ -2487,6 +2566,13 @@ var RemoteAgentClient = class {
|
|
|
2487
2566
|
body: request
|
|
2488
2567
|
});
|
|
2489
2568
|
}
|
|
2569
|
+
/** Set the file-effect sandbox mode for one remote session. */
|
|
2570
|
+
async setSandboxMode(sessionId, request) {
|
|
2571
|
+
await this.#request(sessionSandboxModePath(sessionId), isAcceptedResult, {
|
|
2572
|
+
method: "POST",
|
|
2573
|
+
body: request
|
|
2574
|
+
});
|
|
2575
|
+
}
|
|
2490
2576
|
/** Send a text prompt to a remote session. */
|
|
2491
2577
|
async promptSession(sessionId, request) {
|
|
2492
2578
|
await this.#request(sessionPromptPath(sessionId), isAcceptedResult, {
|
|
@@ -4436,11 +4522,22 @@ var RemoteHostsService = class {
|
|
|
4436
4522
|
}
|
|
4437
4523
|
}
|
|
4438
4524
|
/** List remote sessions, optionally within one workspace. */
|
|
4439
|
-
async sessions(id, workspaceId) {
|
|
4525
|
+
async sessions(id, workspaceId, includeSubagents = false) {
|
|
4440
4526
|
const client = this.#requireClient(id);
|
|
4441
4527
|
try {
|
|
4442
4528
|
const sessions = await client.listSessions(workspaceId);
|
|
4443
|
-
return {
|
|
4529
|
+
return {
|
|
4530
|
+
sessions: includeSubagents ? sessions : sessions.filter((session) => session.origin !== "subagent")
|
|
4531
|
+
};
|
|
4532
|
+
} catch (error) {
|
|
4533
|
+
throw this.#remoteFailure(id, error);
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
/** Set the file-effect sandbox mode for one remote session. */
|
|
4537
|
+
async setSandboxMode(id, sessionId, mode) {
|
|
4538
|
+
const client = this.#requireClient(id);
|
|
4539
|
+
try {
|
|
4540
|
+
await client.setSandboxMode(sessionId, { mode });
|
|
4444
4541
|
} catch (error) {
|
|
4445
4542
|
throw this.#remoteFailure(id, error);
|
|
4446
4543
|
}
|
package/lib/types/service.d.ts
CHANGED
|
@@ -60,7 +60,9 @@ export declare class RemoteHostsService {
|
|
|
60
60
|
/** List remote workspaces. */
|
|
61
61
|
workspaces(id: string): Promise<readonly RemoteWorkspace[]>;
|
|
62
62
|
/** List remote sessions, optionally within one workspace. */
|
|
63
|
-
sessions(id: string, workspaceId?: string): Promise<ListSessionsResult>;
|
|
63
|
+
sessions(id: string, workspaceId?: string, includeSubagents?: boolean): Promise<ListSessionsResult>;
|
|
64
|
+
/** Set the file-effect sandbox mode for one remote session. */
|
|
65
|
+
setSandboxMode(id: string, sessionId: string, mode: 'read-only' | 'workspace-write' | 'danger-full-access'): Promise<void>;
|
|
64
66
|
/** Follow a live remote session from a cursor as an SSE-backed stream. */
|
|
65
67
|
followSessionEvents(id: string, sessionId: string, cursor: number, signal: AbortSignal): AsyncGenerator<SessionStreamFrame>;
|
|
66
68
|
/** Read one session snapshot. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-remote-local-plugin",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.8",
|
|
4
4
|
"description": "DSH remote-development local plugin: host registry, SSH tunnels, remote workspace/session discovery, an authenticated Connection RPC surface, agent tools, and a Settings page.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
Binary file
|