dsh-remote-local-plugin 0.8.2 → 0.8.4
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/dsh-remote-agent-0.2.2.tgz +0 -0
- package/cordis.patch.yml +1 -1
- package/lib/client.js +13 -12
- package/lib/client.js.map +2 -2
- package/lib/index.js +530 -46
- package/lib/types/mirror/service.d.ts +10 -2
- package/lib/types/service.d.ts +11 -1
- package/package.json +1 -1
- package/assets/dsh-remote-agent-0.2.1.tgz +0 -0
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* dsh-remote-local-plugin@0.8.
|
|
1
|
+
/* dsh-remote-local-plugin@0.8.4 — bundled host entry */
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
@@ -252,7 +252,7 @@ async function handleHostApi(ctx, service, mirrors, request) {
|
|
|
252
252
|
});
|
|
253
253
|
}
|
|
254
254
|
try {
|
|
255
|
-
const value = await dispatch(ctx, service, mirrors, body.endpoint, body.payload);
|
|
255
|
+
const value = await dispatch(ctx, service, mirrors, body.endpoint, body.payload, request.signal);
|
|
256
256
|
return jsonResult({ ok: true, value });
|
|
257
257
|
} catch (error) {
|
|
258
258
|
const failure = asLocalError(error);
|
|
@@ -266,7 +266,7 @@ async function handleHostApi(ctx, service, mirrors, request) {
|
|
|
266
266
|
});
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
|
-
async function dispatch(ctx, service, mirrors, endpoint, payload) {
|
|
269
|
+
async function dispatch(ctx, service, mirrors, endpoint, payload, signal) {
|
|
270
270
|
switch (endpoint) {
|
|
271
271
|
case "state":
|
|
272
272
|
return service.state();
|
|
@@ -396,7 +396,6 @@ async function dispatch(ctx, service, mirrors, endpoint, payload) {
|
|
|
396
396
|
const files = ctx.get("workspaceFiles");
|
|
397
397
|
if (files === void 0) return { mirrored: false };
|
|
398
398
|
const scope = { sessionId, workspaceRoot: root };
|
|
399
|
-
const signal = new AbortController().signal;
|
|
400
399
|
if (endpoint === "host/files/list") return { mirrored: true, value: await files.list(scope, path, signal) };
|
|
401
400
|
if (endpoint === "host/files/stat") return { mirrored: true, value: await files.stat(scope, path, signal) };
|
|
402
401
|
if (endpoint === "host/files/bytes") return { mirrored: true, value: await files.readBytes(scope, path, {
|
|
@@ -574,6 +573,7 @@ var RemoteMirrorService = class {
|
|
|
574
573
|
#syncing = false;
|
|
575
574
|
#saveQueue = Promise.resolve();
|
|
576
575
|
#timer;
|
|
576
|
+
#selectionOff;
|
|
577
577
|
#disposed = false;
|
|
578
578
|
constructor(ctx, hosts, config) {
|
|
579
579
|
this.#ctx = ctx;
|
|
@@ -583,6 +583,9 @@ var RemoteMirrorService = class {
|
|
|
583
583
|
/** Load mappings and begin periodic discovery. */
|
|
584
584
|
async start() {
|
|
585
585
|
await this.#load();
|
|
586
|
+
this.#selectionOff = this.#ctx.on("session/event", ((session, event) => {
|
|
587
|
+
this.#onLocalSessionEvent(session, event);
|
|
588
|
+
}), { global: true });
|
|
586
589
|
if (!this.#config.mirrorSessions) return;
|
|
587
590
|
this.#timer = setInterval(() => {
|
|
588
591
|
void this.sync();
|
|
@@ -593,9 +596,13 @@ var RemoteMirrorService = class {
|
|
|
593
596
|
async dispose() {
|
|
594
597
|
this.#disposed = true;
|
|
595
598
|
if (this.#timer !== void 0) clearInterval(this.#timer);
|
|
599
|
+
this.#selectionOff?.();
|
|
600
|
+
this.#selectionOff = void 0;
|
|
596
601
|
for (const runtime of this.#runtimes.values()) {
|
|
597
602
|
runtime.stopped = true;
|
|
598
603
|
if (runtime.timer !== void 0) clearTimeout(runtime.timer);
|
|
604
|
+
runtime.stream?.abort();
|
|
605
|
+
runtime.assistant?.abort();
|
|
599
606
|
}
|
|
600
607
|
for (const dispose of this.#registrations.values()) dispose();
|
|
601
608
|
this.#registrations.clear();
|
|
@@ -674,18 +681,26 @@ var RemoteMirrorService = class {
|
|
|
674
681
|
const text = contentText(message);
|
|
675
682
|
if (text === "") return;
|
|
676
683
|
runtime.pendingUsers.push(message);
|
|
684
|
+
if (!runtime.running) {
|
|
685
|
+
runtime.awaitingTurn = runtime.latestTurn + 1;
|
|
686
|
+
runtime.awaitingStarted = false;
|
|
687
|
+
} else {
|
|
688
|
+
runtime.awaitingStarted = true;
|
|
689
|
+
}
|
|
677
690
|
this.#setRunning(runtime, true);
|
|
678
691
|
if (runtime.stopped || runtime.timer === void 0) {
|
|
679
692
|
runtime.stopped = false;
|
|
680
693
|
runtime.lastError = null;
|
|
681
694
|
this.#schedule(runtime);
|
|
682
695
|
}
|
|
696
|
+
const selection = this.#selectionFor(runtime);
|
|
697
|
+
if (selection !== void 0) Object.assign(runtime.agent.options, selection);
|
|
683
698
|
void this.#hosts.promptSession(
|
|
684
699
|
runtime.record.hostId,
|
|
685
700
|
runtime.record.remoteSessionId,
|
|
686
701
|
text,
|
|
687
702
|
mode,
|
|
688
|
-
|
|
703
|
+
selection
|
|
689
704
|
).catch((error) => {
|
|
690
705
|
runtime.lastError = error instanceof Error ? error.message : String(error);
|
|
691
706
|
runtime.pendingUsers.shift();
|
|
@@ -693,12 +708,32 @@ var RemoteMirrorService = class {
|
|
|
693
708
|
this.#setRunning(runtime, false);
|
|
694
709
|
});
|
|
695
710
|
}
|
|
696
|
-
/** Route cancellation to the remote session. */
|
|
711
|
+
/** Route cancellation to the remote session only after the remote accepts it. */
|
|
697
712
|
cancel(runtime) {
|
|
698
|
-
void this.#hosts.cancelSession(runtime.record.hostId, runtime.record.remoteSessionId).
|
|
699
|
-
|
|
713
|
+
void this.#hosts.cancelSession(runtime.record.hostId, runtime.record.remoteSessionId).then(() => {
|
|
714
|
+
runtime.awaitingTurn = null;
|
|
715
|
+
runtime.awaitingStarted = false;
|
|
716
|
+
this.#setRunning(runtime, false);
|
|
717
|
+
}).catch((error) => {
|
|
718
|
+
runtime.lastError = error instanceof Error ? error.message : String(error);
|
|
719
|
+
this.#ctx.logger.warn("dsh-remote-local: remote cancel failed: %s", runtime.lastError);
|
|
720
|
+
this.#setRunning(runtime, true);
|
|
700
721
|
});
|
|
701
|
-
|
|
722
|
+
}
|
|
723
|
+
#selectionFor(runtime) {
|
|
724
|
+
const projections = this.#ctx.get("sessionProjections");
|
|
725
|
+
const state = projections?.stateOf?.(runtime.session, "modelSelection");
|
|
726
|
+
const read = (candidate) => {
|
|
727
|
+
if (typeof candidate !== "object" || candidate === null) return void 0;
|
|
728
|
+
const record = candidate;
|
|
729
|
+
if (typeof record.provider !== "string" || typeof record.model !== "string") return void 0;
|
|
730
|
+
return {
|
|
731
|
+
provider: record.provider,
|
|
732
|
+
model: record.model,
|
|
733
|
+
...typeof record.reasoningEffort === "string" ? { reasoningEffort: record.reasoningEffort } : {}
|
|
734
|
+
};
|
|
735
|
+
};
|
|
736
|
+
return read(state?.pending) ?? latestSelection(runtime.session) ?? read(runtime.agent.options) ?? read(state?.lastUsed);
|
|
702
737
|
}
|
|
703
738
|
async #syncHost(host) {
|
|
704
739
|
this.#ensureApprovalStream(host);
|
|
@@ -890,18 +925,32 @@ var RemoteMirrorService = class {
|
|
|
890
925
|
async #ensureMirror(host, remote, workspace, _remotePath) {
|
|
891
926
|
const ref = key(host.id, remote.sessionId);
|
|
892
927
|
const existing = this.#state.sessions[ref];
|
|
928
|
+
let reuse;
|
|
893
929
|
if (existing !== void 0) {
|
|
894
930
|
const live = this.#sessions.get(existing.localSessionId);
|
|
895
|
-
const
|
|
896
|
-
if (live !== void 0 &&
|
|
931
|
+
const runtime = this.#runtimes.get(existing.localSessionId);
|
|
932
|
+
if (live !== void 0 && runtime !== void 0 && (!runtime.stopped || !remote.live)) {
|
|
897
933
|
await this.#attach(workspace, live.id);
|
|
934
|
+
if (remote.live) {
|
|
935
|
+
this.#startStream(runtime);
|
|
936
|
+
this.#startAssistantStream(runtime);
|
|
937
|
+
}
|
|
898
938
|
return existing;
|
|
899
939
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
940
|
+
const stalled = this.#runtimes.get(existing.localSessionId);
|
|
941
|
+
if (stalled !== void 0) {
|
|
942
|
+
stalled.stopped = true;
|
|
943
|
+
if (stalled.timer !== void 0) clearTimeout(stalled.timer);
|
|
944
|
+
stalled.stream?.abort();
|
|
945
|
+
stalled.assistant?.abort();
|
|
946
|
+
this.#runtimes.delete(existing.localSessionId);
|
|
947
|
+
}
|
|
948
|
+
const registration = this.#registrations.get(existing.localSessionId);
|
|
949
|
+
if (registration !== void 0) {
|
|
950
|
+
registration();
|
|
951
|
+
this.#registrations.delete(existing.localSessionId);
|
|
904
952
|
}
|
|
953
|
+
if (live !== void 0) reuse = live;
|
|
905
954
|
}
|
|
906
955
|
const snapshot = await this.#hosts.snapshot(host.id, remote.sessionId, this.#config.mirrorMaxEvents);
|
|
907
956
|
if (snapshot.truncated && (snapshot.events[0]?.seq ?? 0) > 0) {
|
|
@@ -914,11 +963,38 @@ var RemoteMirrorService = class {
|
|
|
914
963
|
}
|
|
915
964
|
const anchor = this.#anchorFor(workspace);
|
|
916
965
|
await mkdir(anchor, { recursive: true });
|
|
917
|
-
const
|
|
966
|
+
const filtered = snapshot.events.filter((event) => !isMirrorNoise(event.type));
|
|
967
|
+
const seqMap = new Map(filtered.map((event, index) => [event.seq, index]));
|
|
968
|
+
let latestTurn = 0;
|
|
969
|
+
for (const event of filtered) {
|
|
970
|
+
if (event.type !== "turn/start") continue;
|
|
971
|
+
const turn = event.data.turn;
|
|
972
|
+
if (typeof turn === "number") latestTurn = Math.max(latestTurn, turn);
|
|
973
|
+
}
|
|
974
|
+
const remoteToLocalSeq = /* @__PURE__ */ new Map();
|
|
975
|
+
for (const [index, event] of filtered.entries()) remoteToLocalSeq.set(event.seq, index);
|
|
976
|
+
if (reuse !== void 0) {
|
|
977
|
+
const localEvents = reuse.snapshotEvents();
|
|
978
|
+
for (let index = 0; index < Math.min(localEvents.length, filtered.length); index += 1) {
|
|
979
|
+
const localEvent = localEvents[index];
|
|
980
|
+
const remoteEvent = filtered[index];
|
|
981
|
+
if (localEvent !== void 0 && remoteEvent !== void 0) {
|
|
982
|
+
remoteToLocalSeq.set(remoteEvent.seq, localEvent.seq);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
const record2 = existing;
|
|
986
|
+
await this.#attach(workspace, reuse.id);
|
|
987
|
+
await this.#mountRuntime(record2, reuse, snapshot, remoteToLocalSeq, latestTurn, remote.live);
|
|
988
|
+
this.#ctx.logger.info(
|
|
989
|
+
"dsh-remote-local: reattached remote session %s to local %s",
|
|
990
|
+
remote.sessionId,
|
|
991
|
+
reuse.id
|
|
992
|
+
);
|
|
993
|
+
return record2;
|
|
994
|
+
}
|
|
995
|
+
const localId = existing?.localSessionId ?? `session-${randomUUID()}`;
|
|
918
996
|
let session;
|
|
919
997
|
try {
|
|
920
|
-
const filtered = snapshot.events.filter((event) => !isMirrorNoise(event.type));
|
|
921
|
-
const seqMap = new Map(filtered.map((event, index) => [event.seq, index]));
|
|
922
998
|
const seed = filtered.map((event, index) => toSeedEvent(event, index, seqMap));
|
|
923
999
|
session = this.#sessions.create(localId, {
|
|
924
1000
|
...seed.length === 0 ? {} : { seed },
|
|
@@ -934,7 +1010,7 @@ var RemoteMirrorService = class {
|
|
|
934
1010
|
} catch (error) {
|
|
935
1011
|
this.#ctx.logger.debug("dsh-remote-local: mirror flush failed: %s", String(error));
|
|
936
1012
|
}
|
|
937
|
-
const record = {
|
|
1013
|
+
const record = existing === void 0 ? {
|
|
938
1014
|
hostId: host.id,
|
|
939
1015
|
remoteSessionId: remote.sessionId,
|
|
940
1016
|
localSessionId: localId,
|
|
@@ -942,32 +1018,53 @@ var RemoteMirrorService = class {
|
|
|
942
1018
|
anchor,
|
|
943
1019
|
lastSeq: snapshot.cursor,
|
|
944
1020
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1021
|
+
} : {
|
|
1022
|
+
...existing,
|
|
1023
|
+
remoteWorkspaceId: remote.workspaceId,
|
|
1024
|
+
anchor,
|
|
1025
|
+
lastSeq: snapshot.cursor,
|
|
1026
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
945
1027
|
};
|
|
946
1028
|
this.#state.sessions[ref] = record;
|
|
1029
|
+
await this.#mountRuntime(record, session, snapshot, remoteToLocalSeq, latestTurn, remote.live);
|
|
1030
|
+
this.#ctx.logger.info(
|
|
1031
|
+
"dsh-remote-local: mirrored remote session %s as local %s",
|
|
1032
|
+
remote.sessionId,
|
|
1033
|
+
localId
|
|
1034
|
+
);
|
|
1035
|
+
return record;
|
|
1036
|
+
}
|
|
1037
|
+
async #mountRuntime(record, session, snapshot, remoteToLocalSeq, latestTurn, live) {
|
|
947
1038
|
const runtime = {
|
|
948
1039
|
record,
|
|
949
1040
|
agent: null,
|
|
950
1041
|
session,
|
|
951
1042
|
timer: void 0,
|
|
1043
|
+
stream: void 0,
|
|
1044
|
+
assistant: void 0,
|
|
1045
|
+
remoteToLocalSeq,
|
|
1046
|
+
pendingAssistantEnds: /* @__PURE__ */ new Map(),
|
|
1047
|
+
latestTurn,
|
|
1048
|
+
awaitingTurn: null,
|
|
1049
|
+
awaitingStarted: false,
|
|
1050
|
+
applyingRemote: false,
|
|
952
1051
|
running: false,
|
|
953
1052
|
stopped: false,
|
|
954
1053
|
lastError: null,
|
|
955
1054
|
pendingUsers: [],
|
|
956
1055
|
waiters: []
|
|
957
1056
|
};
|
|
958
|
-
const options = modelOptions(snapshot.events);
|
|
1057
|
+
const options = this.#defaultSelection() ?? modelOptions(snapshot.events);
|
|
959
1058
|
const agent = new RemoteMirrorAgent(this, runtime, this.#ctx, options);
|
|
960
1059
|
runtime.agent = agent;
|
|
961
|
-
this.#runtimes.set(
|
|
962
|
-
this.#registrations.set(
|
|
1060
|
+
this.#runtimes.set(record.localSessionId, runtime);
|
|
1061
|
+
this.#registrations.set(record.localSessionId, this.#agents.enter(agent, void 0));
|
|
963
1062
|
await this.#save();
|
|
964
1063
|
this.#schedule(runtime);
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
);
|
|
970
|
-
return record;
|
|
1064
|
+
if (live) {
|
|
1065
|
+
this.#startStream(runtime);
|
|
1066
|
+
this.#startAssistantStream(runtime);
|
|
1067
|
+
}
|
|
971
1068
|
}
|
|
972
1069
|
#schedule(runtime) {
|
|
973
1070
|
if (runtime.stopped || this.#disposed) return;
|
|
@@ -981,6 +1078,8 @@ var RemoteMirrorService = class {
|
|
|
981
1078
|
const local = this.#sessions.get(runtime.session.id);
|
|
982
1079
|
if (local === void 0) {
|
|
983
1080
|
runtime.stopped = true;
|
|
1081
|
+
runtime.stream?.abort();
|
|
1082
|
+
runtime.assistant?.abort();
|
|
984
1083
|
return;
|
|
985
1084
|
}
|
|
986
1085
|
const snapshot = await this.#hosts.snapshot(
|
|
@@ -991,24 +1090,29 @@ var RemoteMirrorService = class {
|
|
|
991
1090
|
if (snapshot.truncated && (snapshot.events[0]?.seq ?? 0) > runtime.record.lastSeq) {
|
|
992
1091
|
runtime.lastError = "remote log grew past the mirror window";
|
|
993
1092
|
runtime.stopped = true;
|
|
1093
|
+
runtime.stream?.abort();
|
|
1094
|
+
runtime.assistant?.abort();
|
|
994
1095
|
return;
|
|
995
1096
|
}
|
|
996
|
-
let sawEnd = false;
|
|
997
1097
|
for (const event of snapshot.events) {
|
|
998
1098
|
if (event.seq < runtime.record.lastSeq) continue;
|
|
999
1099
|
if (event.seq !== runtime.record.lastSeq) {
|
|
1000
1100
|
throw new Error(`remote event gap at ${event.seq}, mirror ${runtime.record.lastSeq}`);
|
|
1001
1101
|
}
|
|
1002
|
-
|
|
1102
|
+
this.#appendEvent(runtime, event);
|
|
1003
1103
|
runtime.record.lastSeq = event.seq + 1;
|
|
1004
|
-
if (event.type === "turn/end") sawEnd = true;
|
|
1005
1104
|
}
|
|
1006
1105
|
runtime.record.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1007
|
-
if (sawEnd) this.#setRunning(runtime, false);
|
|
1008
1106
|
if (!snapshot.live && runtime.record.lastSeq >= snapshot.cursor) {
|
|
1009
1107
|
runtime.stopped = true;
|
|
1108
|
+
runtime.stream?.abort();
|
|
1109
|
+
runtime.assistant?.abort();
|
|
1010
1110
|
}
|
|
1011
1111
|
await this.#save();
|
|
1112
|
+
if (snapshot.live && !runtime.stopped) {
|
|
1113
|
+
this.#startStream(runtime);
|
|
1114
|
+
this.#startAssistantStream(runtime);
|
|
1115
|
+
}
|
|
1012
1116
|
} catch (error) {
|
|
1013
1117
|
runtime.lastError = error instanceof Error ? error.message : String(error);
|
|
1014
1118
|
this.#ctx.logger.warn(
|
|
@@ -1019,12 +1123,204 @@ var RemoteMirrorService = class {
|
|
|
1019
1123
|
}
|
|
1020
1124
|
this.#schedule(runtime);
|
|
1021
1125
|
}
|
|
1126
|
+
#startStream(runtime) {
|
|
1127
|
+
if (runtime.stopped || this.#disposed || runtime.stream !== void 0) return;
|
|
1128
|
+
const controller = new AbortController();
|
|
1129
|
+
runtime.stream = controller;
|
|
1130
|
+
void this.#followStream(runtime, controller.signal).finally(() => {
|
|
1131
|
+
if (runtime.stream === controller) runtime.stream = void 0;
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
async #followStream(runtime, signal) {
|
|
1135
|
+
try {
|
|
1136
|
+
for await (const frame of this.#hosts.followSessionEvents(
|
|
1137
|
+
runtime.record.hostId,
|
|
1138
|
+
runtime.record.remoteSessionId,
|
|
1139
|
+
runtime.record.lastSeq,
|
|
1140
|
+
signal
|
|
1141
|
+
)) {
|
|
1142
|
+
if (runtime.stopped || this.#disposed || signal.aborted) return;
|
|
1143
|
+
if (frame.type === "end") return;
|
|
1144
|
+
if (frame.type !== "event") continue;
|
|
1145
|
+
const event = frame.event;
|
|
1146
|
+
if (event.seq < runtime.record.lastSeq) continue;
|
|
1147
|
+
if (event.seq !== runtime.record.lastSeq) {
|
|
1148
|
+
runtime.lastError = `remote event gap at ${event.seq}, mirror ${runtime.record.lastSeq}`;
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
this.#appendEvent(runtime, event);
|
|
1152
|
+
runtime.record.lastSeq = event.seq + 1;
|
|
1153
|
+
runtime.record.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1154
|
+
await this.#save();
|
|
1155
|
+
}
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
if (signal.aborted || runtime.stopped || this.#disposed) return;
|
|
1158
|
+
runtime.lastError = error instanceof Error ? error.message : String(error);
|
|
1159
|
+
this.#ctx.logger.warn(
|
|
1160
|
+
"dsh-remote-local: mirror stream %s failed: %s",
|
|
1161
|
+
runtime.record.remoteSessionId,
|
|
1162
|
+
runtime.lastError
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
#appendEvent(runtime, event) {
|
|
1167
|
+
const before = runtime.session.seq;
|
|
1168
|
+
runtime.applyingRemote = true;
|
|
1169
|
+
try {
|
|
1170
|
+
if (!isMirrorNoise(event.type)) {
|
|
1171
|
+
appendRemoteEvent(runtime.session, remapRemoteEvent(event, runtime.remoteToLocalSeq), runtime);
|
|
1172
|
+
}
|
|
1173
|
+
} finally {
|
|
1174
|
+
runtime.applyingRemote = false;
|
|
1175
|
+
}
|
|
1176
|
+
const after = runtime.session.seq;
|
|
1177
|
+
if (after > before) {
|
|
1178
|
+
runtime.remoteToLocalSeq.set(event.seq, after - 1);
|
|
1179
|
+
this.#flushAssistantEnd(runtime, event.seq);
|
|
1180
|
+
}
|
|
1181
|
+
const turn = typeof event.data === "object" && event.data !== null ? event.data.turn : void 0;
|
|
1182
|
+
if (typeof turn !== "number") return;
|
|
1183
|
+
if (event.type === "turn/start") {
|
|
1184
|
+
runtime.latestTurn = Math.max(runtime.latestTurn, turn);
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
if (event.type !== "turn/end") return;
|
|
1188
|
+
runtime.latestTurn = Math.max(runtime.latestTurn, turn);
|
|
1189
|
+
if (runtime.awaitingTurn === null || turn >= runtime.awaitingTurn) {
|
|
1190
|
+
runtime.awaitingTurn = null;
|
|
1191
|
+
runtime.awaitingStarted = false;
|
|
1192
|
+
this.#setRunning(runtime, false);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
#onLocalSessionEvent(session, event) {
|
|
1196
|
+
const candidate = event;
|
|
1197
|
+
if (candidate.type !== "model/selection") return;
|
|
1198
|
+
const runtime = [...this.#runtimes.values()].find((item) => item.session.id === session.id);
|
|
1199
|
+
if (runtime === void 0 || runtime.stopped || runtime.applyingRemote) return;
|
|
1200
|
+
const record = candidate.data;
|
|
1201
|
+
if (typeof record.provider !== "string" || typeof record.model !== "string") return;
|
|
1202
|
+
const selection = {
|
|
1203
|
+
provider: record.provider,
|
|
1204
|
+
model: record.model,
|
|
1205
|
+
...typeof record.reasoningEffort === "string" ? { reasoningEffort: record.reasoningEffort } : {}
|
|
1206
|
+
};
|
|
1207
|
+
Object.assign(runtime.agent.options, selection);
|
|
1208
|
+
void this.#hosts.selectModel(runtime.record.hostId, runtime.record.remoteSessionId, selection).catch((error) => {
|
|
1209
|
+
runtime.lastError = error instanceof Error ? error.message : String(error);
|
|
1210
|
+
this.#ctx.logger.warn("dsh-remote-local: remote model selection failed: %s", runtime.lastError);
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
#defaultSelection() {
|
|
1214
|
+
const defaults = this.#ctx.get("agentDefaultModel");
|
|
1215
|
+
const candidate = defaults?.currentSelection?.();
|
|
1216
|
+
if (typeof candidate !== "object" || candidate === null) return void 0;
|
|
1217
|
+
const record = candidate;
|
|
1218
|
+
if (typeof record.provider !== "string" || typeof record.model !== "string") return void 0;
|
|
1219
|
+
return {
|
|
1220
|
+
provider: record.provider,
|
|
1221
|
+
model: record.model,
|
|
1222
|
+
...typeof record.reasoningEffort === "string" ? { reasoningEffort: record.reasoningEffort } : {}
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
#flushAssistantEnd(runtime, remoteSeq) {
|
|
1226
|
+
const pending = runtime.pendingAssistantEnds.get(remoteSeq);
|
|
1227
|
+
if (pending === void 0) return;
|
|
1228
|
+
const localSeq = runtime.remoteToLocalSeq.get(remoteSeq);
|
|
1229
|
+
if (localSeq === void 0) return;
|
|
1230
|
+
runtime.pendingAssistantEnds.delete(remoteSeq);
|
|
1231
|
+
if (pending.outcome?.kind !== "committed") {
|
|
1232
|
+
this.#emitAssistant(runtime, pending);
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
this.#emitAssistant(runtime, {
|
|
1236
|
+
...pending,
|
|
1237
|
+
outcome: { ...pending.outcome, seq: localSeq }
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
#emitAssistant(runtime, frame) {
|
|
1241
|
+
try {
|
|
1242
|
+
const emit = this.#ctx.emit;
|
|
1243
|
+
emit.call(this.#ctx, "agent/assistant-stream", { agent: runtime.agent, frame });
|
|
1244
|
+
} catch (error) {
|
|
1245
|
+
this.#ctx.logger.warn(
|
|
1246
|
+
"dsh-remote-local: local assistant frame %s failed: %s",
|
|
1247
|
+
runtime.record.remoteSessionId,
|
|
1248
|
+
String(error)
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
#startAssistantStream(runtime) {
|
|
1253
|
+
if (runtime.stopped || this.#disposed || runtime.assistant !== void 0) return;
|
|
1254
|
+
const controller = new AbortController();
|
|
1255
|
+
runtime.assistant = controller;
|
|
1256
|
+
void this.#followAssistant(runtime, controller.signal).finally(() => {
|
|
1257
|
+
if (runtime.assistant === controller) runtime.assistant = void 0;
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
async #followAssistant(runtime, signal) {
|
|
1261
|
+
try {
|
|
1262
|
+
for await (const frame of this.#hosts.followAssistantFrames(
|
|
1263
|
+
runtime.record.hostId,
|
|
1264
|
+
runtime.record.remoteSessionId,
|
|
1265
|
+
signal
|
|
1266
|
+
)) {
|
|
1267
|
+
if (runtime.stopped || this.#disposed || signal.aborted) return;
|
|
1268
|
+
if (frame.type === "start") {
|
|
1269
|
+
runtime.awaitingStarted = true;
|
|
1270
|
+
this.#setRunning(runtime, true);
|
|
1271
|
+
const { startedAfterSeq: _startedAfterSeq, ...rest } = frame;
|
|
1272
|
+
this.#emitAssistant(runtime, rest);
|
|
1273
|
+
continue;
|
|
1274
|
+
}
|
|
1275
|
+
if (frame.type === "chunk") {
|
|
1276
|
+
const chunkType = frame.chunk?.type;
|
|
1277
|
+
if (chunkType !== "tool-call-delta") this.#emitAssistant(runtime, frame);
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
const outcome = frame.outcome;
|
|
1281
|
+
if (outcome === void 0) return;
|
|
1282
|
+
if (outcome.kind !== "committed") {
|
|
1283
|
+
this.#emitAssistant(runtime, frame);
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
const localSeq = runtime.remoteToLocalSeq.get(outcome.seq);
|
|
1287
|
+
if (localSeq === void 0) {
|
|
1288
|
+
runtime.pendingAssistantEnds.set(outcome.seq, frame);
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
this.#emitAssistant(runtime, {
|
|
1292
|
+
...frame,
|
|
1293
|
+
outcome: { ...outcome, seq: localSeq }
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
if (signal.aborted || runtime.stopped || this.#disposed) return;
|
|
1298
|
+
this.#ctx.logger.warn(
|
|
1299
|
+
"dsh-remote-local: assistant stream %s failed: %s",
|
|
1300
|
+
runtime.record.remoteSessionId,
|
|
1301
|
+
error instanceof Error ? error.message : String(error)
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1022
1305
|
#setRunning(runtime, running) {
|
|
1023
1306
|
if (runtime.running === running) return;
|
|
1024
1307
|
runtime.running = running;
|
|
1025
1308
|
if (!running) {
|
|
1026
1309
|
for (const waiter of runtime.waiters.splice(0)) waiter();
|
|
1027
1310
|
}
|
|
1311
|
+
try {
|
|
1312
|
+
const emit = this.#ctx.emit;
|
|
1313
|
+
emit.call(this.#ctx, "agent/status", {
|
|
1314
|
+
agent: runtime.agent,
|
|
1315
|
+
status: running ? "running" : "idle"
|
|
1316
|
+
});
|
|
1317
|
+
} catch (error) {
|
|
1318
|
+
this.#ctx.logger.warn(
|
|
1319
|
+
"dsh-remote-local: local agent status %s failed: %s",
|
|
1320
|
+
runtime.record.remoteSessionId,
|
|
1321
|
+
String(error)
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1028
1324
|
}
|
|
1029
1325
|
async #attach(workspace, sessionId) {
|
|
1030
1326
|
try {
|
|
@@ -1074,6 +1370,19 @@ var RemoteMirrorService = class {
|
|
|
1074
1370
|
return task;
|
|
1075
1371
|
}
|
|
1076
1372
|
};
|
|
1373
|
+
function remapRemoteEvent(event, remoteToLocal) {
|
|
1374
|
+
const sourceEventSeqs = event.sourceEventSeqs?.map((seq) => remoteToLocal.get(seq)).filter((seq) => seq !== void 0);
|
|
1375
|
+
const surfaceOp = typeof event.surfaceOp === "object" ? {
|
|
1376
|
+
op: "replace",
|
|
1377
|
+
startSeq: remoteToLocal.get(event.surfaceOp.startSeq) ?? 0,
|
|
1378
|
+
endSeq: remoteToLocal.get(event.surfaceOp.endSeq) ?? 0
|
|
1379
|
+
} : event.surfaceOp;
|
|
1380
|
+
return {
|
|
1381
|
+
...event,
|
|
1382
|
+
...sourceEventSeqs === void 0 || sourceEventSeqs.length === 0 ? {} : { sourceEventSeqs },
|
|
1383
|
+
...surfaceOp === void 0 ? {} : { surfaceOp }
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1077
1386
|
function isMirrorNoise(type) {
|
|
1078
1387
|
return type === "agent/inbox/spliced";
|
|
1079
1388
|
}
|
|
@@ -1189,6 +1498,7 @@ function safe(value) {
|
|
|
1189
1498
|
// src/remote/files.ts
|
|
1190
1499
|
import { spawn as spawn2 } from "node:child_process";
|
|
1191
1500
|
import { isAbsolute as isAbsolute2, join as join4, relative, resolve as resolve3, sep } from "node:path";
|
|
1501
|
+
var HELPER_TIMEOUT_MS = 15e3;
|
|
1192
1502
|
var HELPER = String.raw`
|
|
1193
1503
|
import sys, json, os, base64
|
|
1194
1504
|
req = json.load(sys.stdin)
|
|
@@ -1346,21 +1656,41 @@ function installRemoteWorkspaceFiles(ctx, mirrors, hosts) {
|
|
|
1346
1656
|
function targetOf(mirrors, workspaceRoot, path) {
|
|
1347
1657
|
const context = mirrors.remoteFileContext(workspaceRoot);
|
|
1348
1658
|
if (context === void 0) return void 0;
|
|
1349
|
-
const
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1352
|
-
|
|
1659
|
+
const anchor = resolve3(workspaceRoot);
|
|
1660
|
+
const remoteRoot = resolve3(context.remoteRoot);
|
|
1661
|
+
const candidate = isAbsolute2(path) ? resolve3(path) : resolve3(anchor, path);
|
|
1662
|
+
const contains = (root, value) => {
|
|
1663
|
+
const rel = relative(root, value);
|
|
1664
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
|
|
1665
|
+
};
|
|
1666
|
+
if (isAbsolute2(path) && contains(remoteRoot, candidate)) {
|
|
1667
|
+
const rel = relative(remoteRoot, candidate);
|
|
1668
|
+
return {
|
|
1669
|
+
hostId: context.hostId,
|
|
1670
|
+
localPath: rel === "" ? anchor : resolve3(anchor, rel),
|
|
1671
|
+
remotePath: candidate,
|
|
1672
|
+
relativePath: rel.split(sep).join("/"),
|
|
1673
|
+
workspaceRoot: anchor
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
if (contains(anchor, candidate)) {
|
|
1677
|
+
const rel = relative(anchor, candidate);
|
|
1678
|
+
const remotePath = rel === "" ? remoteRoot : join4(remoteRoot, rel);
|
|
1353
1679
|
return {
|
|
1354
1680
|
hostId: context.hostId,
|
|
1355
|
-
localPath:
|
|
1681
|
+
localPath: candidate,
|
|
1356
1682
|
remotePath,
|
|
1357
1683
|
relativePath: rel.split(sep).join("/"),
|
|
1358
|
-
workspaceRoot
|
|
1684
|
+
workspaceRoot: anchor
|
|
1359
1685
|
};
|
|
1360
1686
|
}
|
|
1361
|
-
|
|
1687
|
+
throw Object.assign(
|
|
1688
|
+
new Error(`remote file path is outside the mirrored workspace: ${path}`),
|
|
1689
|
+
{ code: "workspace-file/not-found" }
|
|
1690
|
+
);
|
|
1362
1691
|
}
|
|
1363
1692
|
async function helper(hosts, hostId, request, signal) {
|
|
1693
|
+
if (signal.aborted) throw new Error("remote file helper aborted");
|
|
1364
1694
|
const ssh = hosts.sshFor(hostId);
|
|
1365
1695
|
const args = [
|
|
1366
1696
|
"-o",
|
|
@@ -1380,7 +1710,7 @@ async function helper(hosts, hostId, request, signal) {
|
|
|
1380
1710
|
const timer = setTimeout(() => {
|
|
1381
1711
|
child.kill("SIGKILL");
|
|
1382
1712
|
reject(new Error("remote file helper timed out"));
|
|
1383
|
-
},
|
|
1713
|
+
}, HELPER_TIMEOUT_MS);
|
|
1384
1714
|
const abort = () => {
|
|
1385
1715
|
child.kill("SIGKILL");
|
|
1386
1716
|
reject(new Error("remote file helper aborted"));
|
|
@@ -1394,6 +1724,11 @@ async function helper(hosts, hostId, request, signal) {
|
|
|
1394
1724
|
child.stderr.on("data", (chunk) => {
|
|
1395
1725
|
stderr += chunk;
|
|
1396
1726
|
});
|
|
1727
|
+
child.stdin.on("error", (error) => {
|
|
1728
|
+
clearTimeout(timer);
|
|
1729
|
+
signal.removeEventListener("abort", abort);
|
|
1730
|
+
reject(error);
|
|
1731
|
+
});
|
|
1397
1732
|
child.on("error", (error) => {
|
|
1398
1733
|
clearTimeout(timer);
|
|
1399
1734
|
signal.removeEventListener("abort", abort);
|
|
@@ -1840,6 +2175,35 @@ var RemoteProtocolError = class extends Error {
|
|
|
1840
2175
|
}
|
|
1841
2176
|
};
|
|
1842
2177
|
|
|
2178
|
+
// ../protocol/lib/types.js
|
|
2179
|
+
function isRemoteAssistantFrame(value) {
|
|
2180
|
+
if (typeof value !== "object" || value === null)
|
|
2181
|
+
return false;
|
|
2182
|
+
const frame = value;
|
|
2183
|
+
if (frame.type !== "start" && frame.type !== "chunk" && frame.type !== "end")
|
|
2184
|
+
return false;
|
|
2185
|
+
if (typeof frame.attemptId !== "string" || typeof frame.revision !== "number")
|
|
2186
|
+
return false;
|
|
2187
|
+
if (frame.type === "start") {
|
|
2188
|
+
return typeof frame.turn === "number" && typeof frame.step === "number";
|
|
2189
|
+
}
|
|
2190
|
+
if (frame.type === "chunk") {
|
|
2191
|
+
return typeof frame.index === "number" && typeof frame.time === "number" && "chunk" in frame;
|
|
2192
|
+
}
|
|
2193
|
+
if (typeof frame.index !== "number")
|
|
2194
|
+
return false;
|
|
2195
|
+
const outcome = frame.outcome;
|
|
2196
|
+
if (typeof outcome !== "object" || outcome === null)
|
|
2197
|
+
return false;
|
|
2198
|
+
const kind = outcome.kind;
|
|
2199
|
+
if (kind === "abandoned")
|
|
2200
|
+
return true;
|
|
2201
|
+
if (kind !== "committed")
|
|
2202
|
+
return false;
|
|
2203
|
+
const record = outcome;
|
|
2204
|
+
return typeof record.eventType === "string" && typeof record.seq === "number";
|
|
2205
|
+
}
|
|
2206
|
+
|
|
1843
2207
|
// ../protocol/lib/routes.js
|
|
1844
2208
|
var AGENT_ROUTES = {
|
|
1845
2209
|
health: `${AGENT_API_PREFIX}/health`,
|
|
@@ -1856,6 +2220,9 @@ function workspaceSessionsPath(workspaceId) {
|
|
|
1856
2220
|
function sessionPath(sessionId) {
|
|
1857
2221
|
return `${AGENT_API_PREFIX}/sessions/${encodeURIComponent(sessionId)}`;
|
|
1858
2222
|
}
|
|
2223
|
+
function sessionModelPath(sessionId) {
|
|
2224
|
+
return `${sessionPath(sessionId)}/model`;
|
|
2225
|
+
}
|
|
1859
2226
|
function sessionPromptPath(sessionId) {
|
|
1860
2227
|
return `${sessionPath(sessionId)}/prompt`;
|
|
1861
2228
|
}
|
|
@@ -1866,6 +2233,9 @@ function sessionEventsPath(sessionId, cursor) {
|
|
|
1866
2233
|
const base = `${sessionPath(sessionId)}/events`;
|
|
1867
2234
|
return cursor === void 0 ? base : `${base}?cursor=${encodeURIComponent(String(cursor))}`;
|
|
1868
2235
|
}
|
|
2236
|
+
function sessionAssistantEventsPath(sessionId) {
|
|
2237
|
+
return `${sessionPath(sessionId)}/assistant-events`;
|
|
2238
|
+
}
|
|
1869
2239
|
var SESSION_PREFIX = `${AGENT_API_PREFIX}/sessions/`;
|
|
1870
2240
|
var WORKSPACE_PREFIX = `${AGENT_API_PREFIX}/workspaces/`;
|
|
1871
2241
|
|
|
@@ -2108,6 +2478,13 @@ var RemoteAgentClient = class {
|
|
|
2108
2478
|
body: request
|
|
2109
2479
|
});
|
|
2110
2480
|
}
|
|
2481
|
+
/** Select the model and reasoning effort for one remote session. */
|
|
2482
|
+
async selectModel(sessionId, request) {
|
|
2483
|
+
await this.#request(sessionModelPath(sessionId), isAcceptedResult, {
|
|
2484
|
+
method: "POST",
|
|
2485
|
+
body: request
|
|
2486
|
+
});
|
|
2487
|
+
}
|
|
2111
2488
|
/** Send a text prompt to a remote session. */
|
|
2112
2489
|
async promptSession(sessionId, request) {
|
|
2113
2490
|
await this.#request(sessionPromptPath(sessionId), isAcceptedResult, {
|
|
@@ -2192,6 +2569,38 @@ var RemoteAgentClient = class {
|
|
|
2192
2569
|
yield frame;
|
|
2193
2570
|
}
|
|
2194
2571
|
}
|
|
2572
|
+
/** Follow one session's dense process-local Assistant frames. */
|
|
2573
|
+
async *followAssistantFrames(sessionId, signal) {
|
|
2574
|
+
const response = await this.#openStream(sessionAssistantEventsPath(sessionId), signal);
|
|
2575
|
+
const body = response.body;
|
|
2576
|
+
if (body === null) {
|
|
2577
|
+
throw new RemoteProtocolError({
|
|
2578
|
+
code: "request/invalid",
|
|
2579
|
+
message: "remote agent returned an empty assistant stream"
|
|
2580
|
+
});
|
|
2581
|
+
}
|
|
2582
|
+
for await (const message of parseSseStream(body)) {
|
|
2583
|
+
if (message.data === "")
|
|
2584
|
+
continue;
|
|
2585
|
+
let frame;
|
|
2586
|
+
try {
|
|
2587
|
+
frame = JSON.parse(message.data);
|
|
2588
|
+
} catch (error) {
|
|
2589
|
+
throw new RemoteProtocolError({
|
|
2590
|
+
code: "request/invalid",
|
|
2591
|
+
message: "remote agent sent malformed assistant JSON",
|
|
2592
|
+
cause: error
|
|
2593
|
+
});
|
|
2594
|
+
}
|
|
2595
|
+
if (!isRemoteAssistantFrame(frame)) {
|
|
2596
|
+
throw new RemoteProtocolError({
|
|
2597
|
+
code: "request/invalid",
|
|
2598
|
+
message: "remote agent sent an unknown assistant frame"
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
2601
|
+
yield frame;
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2195
2604
|
/** Validate an event sequence against a cursor. */
|
|
2196
2605
|
static guardEvent(cursor, event) {
|
|
2197
2606
|
return cursor.accept(event);
|
|
@@ -2656,6 +3065,7 @@ function renderSystemdUnit(options) {
|
|
|
2656
3065
|
// $PATH is expanded by the bootstrap shell after nodePathPrelude(), so the
|
|
2657
3066
|
// systemd unit sees the discovered nvm/Volta/fnm Node bin directory too.
|
|
2658
3067
|
'Environment="PATH=$INSTALL_DIR/node/bin:$INSTALL_DIR/node_modules/.bin:/usr/local/bin:/usr/bin:/bin:$PATH"',
|
|
3068
|
+
"${PROXY_ENV_LINES}",
|
|
2659
3069
|
...options.modelBaseUrl === void 0 ? [] : ['Environment="DEEPSEEK_BASE_URL=$MODEL_BASE_URL"'],
|
|
2660
3070
|
...options.modelApiKey === void 0 ? [] : ['Environment="DEEPSEEK_API_KEY=$MODEL_API_KEY"'],
|
|
2661
3071
|
'ExecStart="$DSH_BIN" web --host 127.0.0.1 --port $PORT --no-open',
|
|
@@ -2806,12 +3216,47 @@ export DSH_HOME
|
|
|
2806
3216
|
mkdir -p "$INSTALL_DIR" "$DSH_HOME/remote-agent"
|
|
2807
3217
|
echo "dsh-remote: preparing remote install"
|
|
2808
3218
|
if [ -z "$PROXY_URL" ]; then
|
|
2809
|
-
# Reuse a proxy already configured
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
if
|
|
2813
|
-
|
|
2814
|
-
|
|
3219
|
+
# Reuse a proxy already configured by the account's actual login shell.
|
|
3220
|
+
LOGIN_SHELL="\${SHELL:-}"
|
|
3221
|
+
if [ -z "$LOGIN_SHELL" ] || [ ! -x "$LOGIN_SHELL" ]; then
|
|
3222
|
+
if command -v getent >/dev/null 2>&1; then
|
|
3223
|
+
LOGIN_SHELL="$(getent passwd "$(id -un)" 2>/dev/null | cut -d: -f7)"
|
|
3224
|
+
else
|
|
3225
|
+
LOGIN_SHELL="$(awk -F: -v u="$(id -un)" '$1 == u { print $7; exit }' /etc/passwd)"
|
|
3226
|
+
fi
|
|
3227
|
+
fi
|
|
3228
|
+
if [ -z "$LOGIN_SHELL" ] || [ ! -x "$LOGIN_SHELL" ]; then
|
|
3229
|
+
LOGIN_SHELL=/bin/sh
|
|
3230
|
+
fi
|
|
3231
|
+
PROXY_TIMEOUT=""
|
|
3232
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
3233
|
+
PROXY_TIMEOUT="timeout 5"
|
|
3234
|
+
fi
|
|
3235
|
+
read_proxy_env() {
|
|
3236
|
+
if [ -n "$PROXY_TIMEOUT" ]; then
|
|
3237
|
+
$PROXY_TIMEOUT "$LOGIN_SHELL" "$@" -c 'env' 2>/dev/null || true
|
|
3238
|
+
else
|
|
3239
|
+
"$LOGIN_SHELL" "$@" -c 'env' 2>/dev/null || true
|
|
3240
|
+
fi
|
|
3241
|
+
}
|
|
3242
|
+
proxy_from_env() {
|
|
3243
|
+
local proxy_value
|
|
3244
|
+
proxy_value="$(printf '%s\\n' "$1" | tr -d '\\r' | sed -n -e 's/^HTTPS_PROXY=//p' -e 's/^https_proxy=//p' | head -n 1)"
|
|
3245
|
+
if [ -z "$proxy_value" ]; then
|
|
3246
|
+
proxy_value="$(printf '%s\\n' "$1" | tr -d '\\r' | sed -n -e 's/^HTTP_PROXY=//p' -e 's/^http_proxy=//p' | head -n 1)"
|
|
3247
|
+
fi
|
|
3248
|
+
printf '%s' "$proxy_value"
|
|
3249
|
+
}
|
|
3250
|
+
PROXY_ENV="$(read_proxy_env -l -i)"
|
|
3251
|
+
PROXY_URL="$(proxy_from_env "$PROXY_ENV")"
|
|
3252
|
+
if [ -z "$PROXY_URL" ]; then
|
|
3253
|
+
PROXY_ENV="$(read_proxy_env -l)"
|
|
3254
|
+
PROXY_URL="$(proxy_from_env "$PROXY_ENV")"
|
|
3255
|
+
fi
|
|
3256
|
+
if [ -z "$PROXY_URL" ]; then
|
|
3257
|
+
PROXY_ENV="$(read_proxy_env -i)"
|
|
3258
|
+
PROXY_URL="$(proxy_from_env "$PROXY_ENV")"
|
|
3259
|
+
fi
|
|
2815
3260
|
fi
|
|
2816
3261
|
if [ -n "$PROXY_URL" ]; then
|
|
2817
3262
|
export HTTP_PROXY="$PROXY_URL" http_proxy="$PROXY_URL"
|
|
@@ -2823,6 +3268,16 @@ if [ -n "$PROXY_URL" ]; then
|
|
|
2823
3268
|
fi
|
|
2824
3269
|
echo "dsh-remote: install proxy configured"
|
|
2825
3270
|
fi
|
|
3271
|
+
PROXY_ENV_LINES=""
|
|
3272
|
+
if [ -n "$PROXY_URL" ]; then
|
|
3273
|
+
PROXY_ENV_LINES="Environment=\\"HTTP_PROXY=$PROXY_URL\\"
|
|
3274
|
+
Environment=\\"HTTPS_PROXY=$PROXY_URL\\"
|
|
3275
|
+
Environment=\\"ALL_PROXY=$PROXY_URL\\""
|
|
3276
|
+
if [ -n "$PROXY_NO_PROXY" ]; then
|
|
3277
|
+
PROXY_ENV_LINES="$PROXY_ENV_LINES
|
|
3278
|
+
Environment=\\"NO_PROXY=$PROXY_NO_PROXY\\""
|
|
3279
|
+
fi
|
|
3280
|
+
fi
|
|
2826
3281
|
|
|
2827
3282
|
${nodePathPrelude()}
|
|
2828
3283
|
|
|
@@ -3844,6 +4299,16 @@ var RemoteHostsService = class {
|
|
|
3844
4299
|
throw this.#remoteFailure(id, error);
|
|
3845
4300
|
}
|
|
3846
4301
|
}
|
|
4302
|
+
/** Follow a live remote session from a cursor as an SSE-backed stream. */
|
|
4303
|
+
async *followSessionEvents(id, sessionId, cursor, signal) {
|
|
4304
|
+
const client = this.#requireClient(id);
|
|
4305
|
+
try {
|
|
4306
|
+
yield* client.followSession(sessionId, cursor, signal);
|
|
4307
|
+
} catch (error) {
|
|
4308
|
+
if (signal.aborted) return;
|
|
4309
|
+
throw this.#remoteFailure(id, error);
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
3847
4312
|
/** Read one session snapshot. */
|
|
3848
4313
|
async snapshot(id, sessionId, limit) {
|
|
3849
4314
|
const client = this.#requireClient(id);
|
|
@@ -3853,6 +4318,16 @@ var RemoteHostsService = class {
|
|
|
3853
4318
|
throw this.#remoteFailure(id, error);
|
|
3854
4319
|
}
|
|
3855
4320
|
}
|
|
4321
|
+
/** Follow dense remote Assistant frames for one live Session. */
|
|
4322
|
+
async *followAssistantFrames(id, sessionId, signal) {
|
|
4323
|
+
const client = this.#requireClient(id);
|
|
4324
|
+
try {
|
|
4325
|
+
yield* client.followAssistantFrames(sessionId, signal);
|
|
4326
|
+
} catch (error) {
|
|
4327
|
+
if (signal.aborted) return;
|
|
4328
|
+
throw this.#remoteFailure(id, error);
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
3856
4331
|
/** Register a remote workspace. */
|
|
3857
4332
|
async createWorkspace(id, request) {
|
|
3858
4333
|
const client = this.#requireClient(id);
|
|
@@ -3873,6 +4348,15 @@ var RemoteHostsService = class {
|
|
|
3873
4348
|
throw this.#remoteFailure(id, error);
|
|
3874
4349
|
}
|
|
3875
4350
|
}
|
|
4351
|
+
/** Install a model/reasoning-effort selection on a remote session. */
|
|
4352
|
+
async selectModel(id, sessionId, selection) {
|
|
4353
|
+
const client = this.#requireClient(id);
|
|
4354
|
+
try {
|
|
4355
|
+
await client.selectModel(sessionId, selection);
|
|
4356
|
+
} catch (error) {
|
|
4357
|
+
throw this.#remoteFailure(id, error);
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
3876
4360
|
/** Send a prompt to a remote session. */
|
|
3877
4361
|
async promptSession(id, sessionId, text, mode, selection) {
|
|
3878
4362
|
const client = this.#requireClient(id);
|