dsh-remote-local-plugin 0.8.5 → 0.8.7
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 +1 -1
- package/assets/goldenpigeon-dsh-remote-agent-0.2.4.tgz +0 -0
- package/lib/index.js +99 -9
- package/lib/types/service.d.ts +1 -1
- package/package.json +1 -1
- package/assets/goldenpigeon-dsh-remote-agent-0.2.3.tgz +0 -0
- package/lib/types/rpc/contract.d.ts +0 -114
- package/lib/types/rpc/handler.d.ts +0 -12
- package/node_modules/dsh-remote-terminal-controller/lib/client.js.map +0 -7
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.7`](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.7 — 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.
|
|
@@ -2363,7 +2409,7 @@ function isRemoteWorkspace(value) {
|
|
|
2363
2409
|
function isRemoteSession(value) {
|
|
2364
2410
|
if (!isRecord(value))
|
|
2365
2411
|
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");
|
|
2412
|
+
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
2413
|
}
|
|
2368
2414
|
function isRemoteEvent(value) {
|
|
2369
2415
|
return isRecord(value) && typeof value.seq === "number" && isString(value.type) && typeof value.time === "number" && "data" in value;
|
|
@@ -3322,6 +3368,48 @@ if [ ! -x "$DSH_BIN" ]; then
|
|
|
3322
3368
|
exit 24
|
|
3323
3369
|
fi
|
|
3324
3370
|
|
|
3371
|
+
PROFILE_DIR="$DSH_HOME/profiles/$PROFILE"
|
|
3372
|
+
LEGACY_CHANGED=0
|
|
3373
|
+
if [ -f "$PROFILE_DIR/package.json" ]; then
|
|
3374
|
+
if node - "$PROFILE_DIR/package.json" <<'DSH_REMOTE_MIGRATE'
|
|
3375
|
+
const fs = require('node:fs')
|
|
3376
|
+
const file = process.argv[2]
|
|
3377
|
+
const raw = fs.readFileSync(file, 'utf8')
|
|
3378
|
+
const data = JSON.parse(raw)
|
|
3379
|
+
let changed = false
|
|
3380
|
+
if (data.dependencies !== undefined && Object.hasOwn(data.dependencies, 'dsh-remote-agent')) {
|
|
3381
|
+
delete data.dependencies['dsh-remote-agent']
|
|
3382
|
+
changed = true
|
|
3383
|
+
}
|
|
3384
|
+
const bundles = data.dsh?.profile?.bundles
|
|
3385
|
+
if (Array.isArray(bundles)) {
|
|
3386
|
+
const next = bundles.filter(name => name !== 'dsh-remote-agent')
|
|
3387
|
+
if (next.length !== bundles.length) {
|
|
3388
|
+
data.dsh.profile.bundles = next
|
|
3389
|
+
changed = true
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
if (!changed) process.exit(1)
|
|
3393
|
+
fs.writeFileSync(file + '.legacy-backup', raw)
|
|
3394
|
+
fs.writeFileSync(file, JSON.stringify(data, null, 2) + '\\n')
|
|
3395
|
+
DSH_REMOTE_MIGRATE
|
|
3396
|
+
then
|
|
3397
|
+
LEGACY_CHANGED=1
|
|
3398
|
+
else
|
|
3399
|
+
MIGRATE_STATUS=$?
|
|
3400
|
+
if [ "$MIGRATE_STATUS" -ne 1 ]; then
|
|
3401
|
+
echo "dsh-remote: could not migrate the legacy remote-agent profile entry" >&2
|
|
3402
|
+
exit 26
|
|
3403
|
+
fi
|
|
3404
|
+
fi
|
|
3405
|
+
fi
|
|
3406
|
+
if [ "$LEGACY_CHANGED" -eq 1 ]; then
|
|
3407
|
+
if [ -f "$PROFILE_DIR/pnpm-lock.yaml" ]; then
|
|
3408
|
+
mv "$PROFILE_DIR/pnpm-lock.yaml" "$PROFILE_DIR/pnpm-lock.yaml.legacy-backup"
|
|
3409
|
+
fi
|
|
3410
|
+
echo "dsh-remote: removed the legacy dsh-remote-agent profile entry"
|
|
3411
|
+
fi
|
|
3412
|
+
|
|
3325
3413
|
echo "dsh-remote: adding plugin to profile $PROFILE"
|
|
3326
3414
|
mkdir -p "$AGENT_ARTIFACT_DIR"
|
|
3327
3415
|
cp -f "$AGENT_TARBALL" "$AGENT_ARTIFACT"
|
|
@@ -4394,11 +4482,13 @@ var RemoteHostsService = class {
|
|
|
4394
4482
|
}
|
|
4395
4483
|
}
|
|
4396
4484
|
/** List remote sessions, optionally within one workspace. */
|
|
4397
|
-
async sessions(id, workspaceId) {
|
|
4485
|
+
async sessions(id, workspaceId, includeSubagents = false) {
|
|
4398
4486
|
const client = this.#requireClient(id);
|
|
4399
4487
|
try {
|
|
4400
4488
|
const sessions = await client.listSessions(workspaceId);
|
|
4401
|
-
return {
|
|
4489
|
+
return {
|
|
4490
|
+
sessions: includeSubagents ? sessions : sessions.filter((session) => session.origin !== "subagent")
|
|
4491
|
+
};
|
|
4402
4492
|
} catch (error) {
|
|
4403
4493
|
throw this.#remoteFailure(id, error);
|
|
4404
4494
|
}
|
package/lib/types/service.d.ts
CHANGED
|
@@ -60,7 +60,7 @@ 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
64
|
/** Follow a live remote session from a cursor as an SSE-backed stream. */
|
|
65
65
|
followSessionEvents(id: string, sessionId: string, cursor: number, signal: AbortSignal): AsyncGenerator<SessionStreamFrame>;
|
|
66
66
|
/** 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.7",
|
|
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
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RPC contract shared by the host handler and the browser Settings page.
|
|
3
|
-
*
|
|
4
|
-
* The channel rides DSH's authenticated Connection transport, so it works
|
|
5
|
-
* through the web HTTP carrier and the desktop IPC carrier without a second
|
|
6
|
-
* authentication scheme.
|
|
7
|
-
*
|
|
8
|
-
* @module dsh-remote-local/rpc/contract
|
|
9
|
-
*/
|
|
10
|
-
import type { HostView } from '../hosts/types.ts';
|
|
11
|
-
import type { HostInfo, RemoteSessionSummary, RemoteWorkspace, SessionSnapshot } from '@dsh-remote/protocol';
|
|
12
|
-
/** Absolute Connection RPC channel. */
|
|
13
|
-
export declare const RPC_CHANNEL = "/dsh-remote";
|
|
14
|
-
/** Endpoint names. */
|
|
15
|
-
export type RpcEndpoint = 'state' | 'target/validate' | 'host/add' | 'host/remove' | 'host/connect' | 'host/disconnect' | 'host/refresh' | 'host/workspaces' | 'host/sessions' | 'host/session' | 'host/web-url';
|
|
16
|
-
/** Browser-safe state snapshot. */
|
|
17
|
-
export interface RemoteHostsState {
|
|
18
|
-
readonly protocolVersion: number;
|
|
19
|
-
readonly hosts: readonly HostView[];
|
|
20
|
-
}
|
|
21
|
-
/** Result of parsing a destination. */
|
|
22
|
-
export interface ValidatedTarget {
|
|
23
|
-
readonly canonical: string;
|
|
24
|
-
readonly host: string;
|
|
25
|
-
readonly user: string | null;
|
|
26
|
-
readonly port: number | null;
|
|
27
|
-
}
|
|
28
|
-
/** Result of listing workspaces. */
|
|
29
|
-
export interface WorkspacesResult {
|
|
30
|
-
readonly workspaces: readonly RemoteWorkspace[];
|
|
31
|
-
}
|
|
32
|
-
/** Result of listing sessions. */
|
|
33
|
-
export interface SessionsResult {
|
|
34
|
-
readonly sessions: readonly RemoteSessionSummary[];
|
|
35
|
-
}
|
|
36
|
-
/** Result of resolving the remote Web UI. */
|
|
37
|
-
export interface WebUrlResult {
|
|
38
|
-
readonly url: string;
|
|
39
|
-
}
|
|
40
|
-
/** Success/failure result used by the Connection RPC handler. */
|
|
41
|
-
export type RpcResult<T> = {
|
|
42
|
-
readonly ok: true;
|
|
43
|
-
readonly value: T;
|
|
44
|
-
} | {
|
|
45
|
-
readonly ok: false;
|
|
46
|
-
readonly error: {
|
|
47
|
-
readonly code: string;
|
|
48
|
-
readonly message: string;
|
|
49
|
-
readonly details: Record<string, unknown>;
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
/** Payloads accepted by each endpoint. */
|
|
53
|
-
export interface RpcPayloads {
|
|
54
|
-
state: undefined;
|
|
55
|
-
'target/validate': {
|
|
56
|
-
sshTarget: string;
|
|
57
|
-
};
|
|
58
|
-
'host/add': {
|
|
59
|
-
sshTarget: string;
|
|
60
|
-
label?: string;
|
|
61
|
-
token?: string;
|
|
62
|
-
remoteAgentPort?: number;
|
|
63
|
-
autoConnect?: boolean;
|
|
64
|
-
};
|
|
65
|
-
'host/remove': {
|
|
66
|
-
hostId: string;
|
|
67
|
-
};
|
|
68
|
-
'host/connect': {
|
|
69
|
-
hostId: string;
|
|
70
|
-
};
|
|
71
|
-
'host/disconnect': {
|
|
72
|
-
hostId: string;
|
|
73
|
-
};
|
|
74
|
-
'host/refresh': {
|
|
75
|
-
hostId: string;
|
|
76
|
-
};
|
|
77
|
-
'host/workspaces': {
|
|
78
|
-
hostId: string;
|
|
79
|
-
};
|
|
80
|
-
'host/sessions': {
|
|
81
|
-
hostId: string;
|
|
82
|
-
workspaceId?: string;
|
|
83
|
-
};
|
|
84
|
-
'host/session': {
|
|
85
|
-
hostId: string;
|
|
86
|
-
sessionId: string;
|
|
87
|
-
limit?: number;
|
|
88
|
-
};
|
|
89
|
-
'host/web-url': {
|
|
90
|
-
hostId: string;
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
/** Values returned by each endpoint. */
|
|
94
|
-
export interface RpcValues {
|
|
95
|
-
state: RemoteHostsState;
|
|
96
|
-
'target/validate': ValidatedTarget;
|
|
97
|
-
'host/add': HostView;
|
|
98
|
-
'host/remove': {
|
|
99
|
-
removed: true;
|
|
100
|
-
};
|
|
101
|
-
'host/connect': HostView;
|
|
102
|
-
'host/disconnect': HostView;
|
|
103
|
-
'host/refresh': HostView;
|
|
104
|
-
'host/workspaces': WorkspacesResult;
|
|
105
|
-
'host/sessions': SessionsResult;
|
|
106
|
-
'host/session': SessionSnapshot;
|
|
107
|
-
'host/web-url': WebUrlResult;
|
|
108
|
-
}
|
|
109
|
-
/** Host capabilities advertised in the UI. */
|
|
110
|
-
export declare const HOST_CAPABILITIES: readonly ["workspace.list", "session.list", "session.snapshot"];
|
|
111
|
-
/** Informational type for the Settings page. */
|
|
112
|
-
export interface HostSummary extends Pick<HostView, 'id' | 'label' | 'sshTarget' | 'phase'> {
|
|
113
|
-
readonly hostInfo: HostInfo | null;
|
|
114
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Host-side RPC dispatch for the Settings page.
|
|
3
|
-
*
|
|
4
|
-
* @module dsh-remote-local/rpc/handler
|
|
5
|
-
*/
|
|
6
|
-
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
-
import type { RemoteHostsService } from '../service.ts';
|
|
8
|
-
import { type RpcEndpoint, type RpcResult, type RpcValues } from './contract.ts';
|
|
9
|
-
/** Register the channel; returns its asynchronous disposer. */
|
|
10
|
-
export declare function registerHostRpc(ctx: Context, service: RemoteHostsService): () => Promise<void>;
|
|
11
|
-
/** Narrow a result for callers that want a compiler-checked endpoint name. */
|
|
12
|
-
export type HostRpcResult<K extends RpcEndpoint> = RpcResult<RpcValues[K]>;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/client/index.tsx"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { Context } from '@deepseek-ai/cordis'\nimport { ClientTerminals } from '@deepseek-ai/dsh-api-terminal-controller/client'\n\nexport const inject = ['remote', 'remote.terminal', 'connection']\nconst ROUTE = '/api/dsh-remote-terminal'\n\ninterface Handle extends AsyncIterable<unknown> {\n send(item: unknown): void\n end(): void\n dispose(): void\n}\n\nexport function apply(ctx: Context): void {\n const original = (ctx.remote as any).terminal\n const routed = route(original)\n new ClientTerminals(ctx as any, routed)\n}\n\nfunction route(original: any): any {\n const mirrored = new Map<string, Promise<boolean>>()\n const isMirror = (sessionId: string): Promise<boolean> => {\n let pending = mirrored.get(sessionId)\n if (pending === undefined) {\n pending = post({ action: 'context', sessionId })\n .then(value => (value as { mirrored?: boolean } | undefined)?.mirrored === true)\n .catch(() => false)\n mirrored.set(sessionId, pending)\n }\n return pending\n }\n const unary = (name: string) => async (...args: any[]) => {\n const sessionId = String(args[0])\n if (!await isMirror(sessionId)) return original[name](...args)\n const value = await post({ action: name, sessionId, ...payload(name, args) })\n return value === undefined ? original[name](...args) : { ok: true, value }\n }\n return {\n ...original,\n environment: unary('environment'),\n shells: unary('shells'),\n list: unary('list'),\n create: unary('create'),\n rename: unary('rename'),\n close: unary('close'),\n retain: (sessionId: string, id: string, signal: AbortSignal) => {\n const local = original.retain(sessionId, id, signal)\n return (async function* () {\n if (await isMirror(sessionId)) {\n yield { type: 'retained' }\n return\n }\n yield* local\n })()\n },\n attach: (sessionId: string, id: string, signal: AbortSignal) => new RoutedHandle(original, sessionId, id, signal, isMirror),\n }\n}\n\nfunction payload(name: string, args: any[]): Record<string, unknown> {\n if (name === 'create') {\n const request = args[1] ?? {}\n return { id: request.id, cols: request.cols, rows: request.rows, shellPath: request.shellPath }\n }\n if (name === 'rename') return { id: args[1], title: args[2] }\n if (name === 'close') return { id: args[1] }\n return {}\n}\n\nasync function post(payload: Record<string, unknown>): Promise<any> {\n const response = await fetch(ROUTE, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!response.ok) return undefined\n const body = await response.json() as { ok?: boolean; value?: unknown; mirrored?: boolean }\n if (body.mirrored !== undefined) return body\n return body.ok === true ? body.value : undefined\n}\n\nclass RoutedHandle implements Handle {\n #original: any\n #sessionId: string\n #id: string\n #signal: AbortSignal\n #isMirror: (sessionId: string) => Promise<boolean>\n #handle: Handle | undefined\n #pending: unknown[] = []\n #controller = new AbortController()\n #opening: Promise<Handle> | undefined\n\n constructor(original: any, sessionId: string, id: string, signal: AbortSignal, isMirror: (sessionId: string) => Promise<boolean>) {\n this.#original = original\n this.#sessionId = sessionId\n this.#id = id\n this.#signal = signal\n this.#isMirror = isMirror\n signal.addEventListener('abort', () => this.#controller.abort(), { once: true })\n }\n\n async *[Symbol.asyncIterator](): AsyncGenerator<unknown> {\n const handle = await this.#open()\n yield* handle\n }\n\n send(item: unknown): void {\n if (this.#handle !== undefined) this.#handle.send(item)\n else this.#pending.push(item)\n }\n\n end(): void { this.#handle?.end() }\n\n dispose(): void {\n this.#controller.abort()\n this.#handle?.dispose()\n }\n\n async #open(): Promise<Handle> {\n if (this.#handle !== undefined) return this.#handle\n this.#opening ??= (async () => {\n const remote = await this.#isMirror(this.#sessionId)\n const handle: Handle = remote\n ? remoteHandle(this.#sessionId, this.#id, this.#controller.signal)\n : this.#original.attach(this.#sessionId, this.#id, this.#signal)\n this.#handle = handle\n for (const item of this.#pending.splice(0)) handle.send(item)\n return handle\n })()\n return this.#opening\n }\n}\n\nfunction remoteHandle(sessionId: string, id: string, signal: AbortSignal): Handle {\n let controller = new AbortController()\n const abort = (): void => controller.abort()\n signal.addEventListener('abort', abort, { once: true })\n return {\n async *[Symbol.asyncIterator]() {\n const response = await fetch(ROUTE, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'content-type': 'application/json', accept: 'text/event-stream' },\n body: JSON.stringify({ action: 'attach', sessionId, id }),\n signal: controller.signal,\n })\n if (!response.ok || response.body === null) throw new Error('remote terminal attach failed')\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n try {\n for (;;) {\n const { done, value } = await reader.read()\n buffer += decoder.decode(value, { stream: !done })\n let newline = buffer.indexOf('\\n')\n while (newline >= 0) {\n const line = buffer.slice(0, newline).replace(/\\r$/u, '')\n buffer = buffer.slice(newline + 1)\n if (line.startsWith('data: ')) {\n const parsed = JSON.parse(line.slice(6).trim())\n if (parsed.message !== undefined && parsed.type === undefined) throw new Error(parsed.message)\n yield parsed\n }\n newline = buffer.indexOf('\\n')\n }\n if (done) return\n }\n } finally {\n reader.releaseLock()\n }\n },\n send(item: unknown) {\n const input = item as { type?: string; data?: string; cols?: number; rows?: number; sequence?: number }\n void post(input.type === 'write'\n ? { action: 'input', sessionId, id, data: input.data ?? '', sequence: input.sequence ?? 0 }\n : { action: 'resize', sessionId, id, cols: input.cols ?? 80, rows: input.rows ?? 24, sequence: input.sequence ?? 0 })\n },\n end() {},\n dispose() { controller.abort() },\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["payload"]
|
|
7
|
-
}
|