dsh-remote-local-plugin 0.8.7 → 0.8.9
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.9.tgz +0 -0
- package/lib/client.js +34 -11
- package/lib/client.js.map +2 -2
- package/lib/index.js +237 -30
- package/lib/types/host-api/contract.d.ts +2 -0
- package/lib/types/mirror/service.d.ts +19 -2
- package/lib/types/service.d.ts +3 -1
- package/package.json +1 -1
- package/assets/goldenpigeon-dsh-remote-agent-0.2.4.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.9 — bundled host entry */
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
@@ -269,7 +269,7 @@ async function handleHostApi(ctx, service, mirrors, request) {
|
|
|
269
269
|
async function dispatch(ctx, service, mirrors, endpoint, payload, signal) {
|
|
270
270
|
switch (endpoint) {
|
|
271
271
|
case "state":
|
|
272
|
-
return service.state();
|
|
272
|
+
return { ...service.state(), mirroredWorkspaceIds: mirrors?.localWorkspaceIds() ?? [] };
|
|
273
273
|
case "target/validate":
|
|
274
274
|
return service.validateTarget(requireStringField(payload, "sshTarget"));
|
|
275
275
|
case "host/add": {
|
|
@@ -504,12 +504,83 @@ var SURFACE_TYPES = /* @__PURE__ */ new Set([
|
|
|
504
504
|
"assistant/message",
|
|
505
505
|
"tool/result"
|
|
506
506
|
]);
|
|
507
|
+
function messageIdentity(message) {
|
|
508
|
+
return typeof message === "object" && message !== null ? message.id : void 0;
|
|
509
|
+
}
|
|
510
|
+
function createMirrorInbox(session) {
|
|
511
|
+
const nextTurn = [];
|
|
512
|
+
const nextStep = [];
|
|
513
|
+
const locate = (id) => {
|
|
514
|
+
for (const target of ["next-turn", "next-step"]) {
|
|
515
|
+
const list = target === "next-turn" ? nextTurn : nextStep;
|
|
516
|
+
const index = list.findIndex((message) => messageIdentity(message) === id);
|
|
517
|
+
if (index >= 0) return { target, index };
|
|
518
|
+
}
|
|
519
|
+
return void 0;
|
|
520
|
+
};
|
|
521
|
+
const append = (target, start, inserted, removedCount = 0) => {
|
|
522
|
+
try {
|
|
523
|
+
;
|
|
524
|
+
session.append("agent/inbox/spliced", {
|
|
525
|
+
target,
|
|
526
|
+
start,
|
|
527
|
+
...removedCount === 0 ? {} : { removedCount },
|
|
528
|
+
inserted
|
|
529
|
+
});
|
|
530
|
+
} catch {
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
const inbox = {
|
|
534
|
+
"next-turn": nextTurn,
|
|
535
|
+
"next-step": nextStep,
|
|
536
|
+
nextTurn,
|
|
537
|
+
nextStep,
|
|
538
|
+
get hasPending() {
|
|
539
|
+
return nextTurn.length > 0 || nextStep.length > 0;
|
|
540
|
+
},
|
|
541
|
+
remove(id) {
|
|
542
|
+
const found = locate(id);
|
|
543
|
+
if (found === void 0) return false;
|
|
544
|
+
const list = found.target === "next-turn" ? nextTurn : nextStep;
|
|
545
|
+
list.splice(found.index, 1);
|
|
546
|
+
append(found.target, found.index, [], 1);
|
|
547
|
+
return true;
|
|
548
|
+
},
|
|
549
|
+
replace(id, message) {
|
|
550
|
+
const found = locate(id);
|
|
551
|
+
if (found === void 0) return false;
|
|
552
|
+
const list = found.target === "next-turn" ? nextTurn : nextStep;
|
|
553
|
+
list.splice(found.index, 1, message);
|
|
554
|
+
append(found.target, found.index, [message], 1);
|
|
555
|
+
return true;
|
|
556
|
+
},
|
|
557
|
+
splice(target, start, deleteCount, inserted) {
|
|
558
|
+
const list = target === "next-turn" ? nextTurn : nextStep;
|
|
559
|
+
const removed = list.splice(start, deleteCount, ...inserted);
|
|
560
|
+
append(target === "next-turn" ? "next-turn" : "next-step", start, inserted, removed.length);
|
|
561
|
+
return removed;
|
|
562
|
+
},
|
|
563
|
+
clear() {
|
|
564
|
+
if (nextStep.length > 0) {
|
|
565
|
+
const count = nextStep.length;
|
|
566
|
+
nextStep.length = 0;
|
|
567
|
+
append("next-step", 0, [], count);
|
|
568
|
+
}
|
|
569
|
+
if (nextTurn.length > 0) {
|
|
570
|
+
const count = nextTurn.length;
|
|
571
|
+
nextTurn.length = 0;
|
|
572
|
+
append("next-turn", 0, [], count);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
return inbox;
|
|
577
|
+
}
|
|
507
578
|
var RemoteMirrorAgent = class {
|
|
508
579
|
id;
|
|
509
580
|
session;
|
|
510
581
|
options;
|
|
511
582
|
ctx;
|
|
512
|
-
inbox
|
|
583
|
+
inbox;
|
|
513
584
|
#runtime;
|
|
514
585
|
#mirror;
|
|
515
586
|
constructor(mirror, runtime, ctx, options) {
|
|
@@ -519,11 +590,13 @@ var RemoteMirrorAgent = class {
|
|
|
519
590
|
this.session = runtime.session;
|
|
520
591
|
this.ctx = ctx;
|
|
521
592
|
this.options = options;
|
|
593
|
+
this.inbox = createMirrorInbox(runtime.session);
|
|
522
594
|
}
|
|
523
595
|
get status() {
|
|
524
596
|
return this.#runtime.running ? "running" : "idle";
|
|
525
597
|
}
|
|
526
598
|
cancel() {
|
|
599
|
+
this.inbox.clear();
|
|
527
600
|
this.#mirror.cancel(this.#runtime);
|
|
528
601
|
}
|
|
529
602
|
whenIdle() {
|
|
@@ -536,10 +609,14 @@ var RemoteMirrorAgent = class {
|
|
|
536
609
|
return task(new AbortController().signal);
|
|
537
610
|
}
|
|
538
611
|
send(message, _target, wakeup = false) {
|
|
539
|
-
if (wakeup) this.followup(message);
|
|
612
|
+
if (wakeup || this.#runtime.running) this.followup(message);
|
|
540
613
|
else this.#mirror.prompt(this.#runtime, message, "queue");
|
|
541
614
|
}
|
|
542
615
|
followup(message) {
|
|
616
|
+
if (this.#runtime.running) {
|
|
617
|
+
this.inbox.splice("next-turn", this.inbox.nextTurn.length, 0, [message]);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
543
620
|
this.#mirror.prompt(this.#runtime, message, "queue");
|
|
544
621
|
}
|
|
545
622
|
steer(message) {
|
|
@@ -643,6 +720,10 @@ var RemoteMirrorService = class {
|
|
|
643
720
|
}
|
|
644
721
|
return void 0;
|
|
645
722
|
}
|
|
723
|
+
/** Local workspace ids that represent remote workspaces. */
|
|
724
|
+
localWorkspaceIds() {
|
|
725
|
+
return Object.values(this.#state.workspaces).map((workspace) => workspace.localWorkspaceId).filter((id) => typeof id === "string" && id !== "");
|
|
726
|
+
}
|
|
646
727
|
/** Whether the test-only manifest excludes this remote workspace from UI listings. */
|
|
647
728
|
isTestHidden(remoteWorkspaceId, remotePath) {
|
|
648
729
|
return isTestHidden(remotePath, remoteWorkspaceId, this.#testVisibility);
|
|
@@ -680,9 +761,10 @@ var RemoteMirrorService = class {
|
|
|
680
761
|
}
|
|
681
762
|
/** Route one prompt from the local Agent façade to the remote session. */
|
|
682
763
|
prompt(runtime, message, mode) {
|
|
683
|
-
const
|
|
684
|
-
if (
|
|
685
|
-
|
|
764
|
+
const parts = promptParts(message);
|
|
765
|
+
if (parts.length === 0) return;
|
|
766
|
+
const pending = { message, localSeq: -1 };
|
|
767
|
+
runtime.pendingUsers.push(pending);
|
|
686
768
|
if (!runtime.running) {
|
|
687
769
|
runtime.awaitingTurn = runtime.latestTurn + 1;
|
|
688
770
|
runtime.awaitingStarted = false;
|
|
@@ -697,19 +779,63 @@ var RemoteMirrorService = class {
|
|
|
697
779
|
}
|
|
698
780
|
const selection = this.#selectionFor(runtime);
|
|
699
781
|
if (selection !== void 0) Object.assign(runtime.agent.options, selection);
|
|
700
|
-
void this.#hosts.promptSession(
|
|
782
|
+
void this.#promptContent(message).then(({ text, content }) => this.#hosts.promptSession(
|
|
701
783
|
runtime.record.hostId,
|
|
702
784
|
runtime.record.remoteSessionId,
|
|
703
785
|
text,
|
|
704
786
|
mode,
|
|
705
|
-
selection
|
|
706
|
-
|
|
787
|
+
selection,
|
|
788
|
+
content
|
|
789
|
+
)).then(() => {
|
|
790
|
+
if (!runtime.pendingUsers.includes(pending)) return;
|
|
791
|
+
const before = runtime.session.seq;
|
|
792
|
+
try {
|
|
793
|
+
runtime.session.append("user/message", messageData(message), { surfaceOp: "append" });
|
|
794
|
+
pending.localSeq = runtime.session.seq > before ? runtime.session.seq - 1 : -1;
|
|
795
|
+
} catch (error) {
|
|
796
|
+
this.#ctx.logger.warn("dsh-remote-local: could not append accepted user message: %s", String(error));
|
|
797
|
+
}
|
|
798
|
+
}).catch((error) => {
|
|
707
799
|
runtime.lastError = error instanceof Error ? error.message : String(error);
|
|
708
|
-
runtime.pendingUsers.
|
|
800
|
+
const index = runtime.pendingUsers.indexOf(pending);
|
|
801
|
+
if (index >= 0) runtime.pendingUsers.splice(index, 1);
|
|
709
802
|
this.#ctx.logger.warn("dsh-remote-local: remote prompt failed: %s", runtime.lastError);
|
|
803
|
+
try {
|
|
804
|
+
const emit = this.#ctx.emit;
|
|
805
|
+
emit.call(this.#ctx, "agent/error", {
|
|
806
|
+
agent: runtime.agent,
|
|
807
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
808
|
+
});
|
|
809
|
+
} catch (emitError) {
|
|
810
|
+
this.#ctx.logger.warn("dsh-remote-local: prompt error notification failed: %s", String(emitError));
|
|
811
|
+
}
|
|
710
812
|
this.#setRunning(runtime, false);
|
|
711
813
|
});
|
|
712
814
|
}
|
|
815
|
+
async #promptContent(message) {
|
|
816
|
+
const content = [];
|
|
817
|
+
for (const part of promptParts(message)) {
|
|
818
|
+
if (part.type === "text") {
|
|
819
|
+
content.push({ type: "text", text: part.text });
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
const attachments = this.#ctx.get("attachments");
|
|
823
|
+
if (attachments === void 0) throw new Error("\u672C\u5730\u9644\u4EF6\u670D\u52A1\u4E0D\u53EF\u7528");
|
|
824
|
+
const stored = await attachments.readImage(part.attachment);
|
|
825
|
+
const mediaType = typeof stored.mediaType === "string" ? stored.mediaType : typeof stored.ref?.mediaType === "string" ? stored.ref.mediaType : void 0;
|
|
826
|
+
if (mediaType === void 0) throw new Error("\u56FE\u7247\u7F3A\u5C11 mediaType");
|
|
827
|
+
const name2 = part.name ?? (typeof stored.ref?.name === "string" ? stored.ref.name : void 0);
|
|
828
|
+
content.push({
|
|
829
|
+
type: "image",
|
|
830
|
+
data: Buffer.from(stored.data).toString("base64"),
|
|
831
|
+
mediaType,
|
|
832
|
+
...name2 === void 0 ? {} : { name: name2 }
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
const text = content.filter((part) => part.type === "text").map((part) => part.text).join("\n").trim();
|
|
836
|
+
if (content.length === 0) throw new Error("\u6D88\u606F\u5185\u5BB9\u4E3A\u7A7A");
|
|
837
|
+
return { text, content };
|
|
838
|
+
}
|
|
713
839
|
/** Route cancellation to the remote session only after the remote accepts it. */
|
|
714
840
|
cancel(runtime) {
|
|
715
841
|
void this.#hosts.cancelSession(runtime.record.hostId, runtime.record.remoteSessionId).then(() => {
|
|
@@ -1044,9 +1170,13 @@ var RemoteMirrorService = class {
|
|
|
1044
1170
|
let session;
|
|
1045
1171
|
try {
|
|
1046
1172
|
const seed = filtered.map((event, index) => toSeedEvent(event, index, seqMap));
|
|
1173
|
+
const remoteCreatedAt = Date.parse(remote.createdAt);
|
|
1047
1174
|
session = this.#sessions.create(localId, {
|
|
1048
1175
|
...seed.length === 0 ? {} : { seed },
|
|
1049
|
-
meta: {
|
|
1176
|
+
meta: {
|
|
1177
|
+
cwd: anchor,
|
|
1178
|
+
...Number.isFinite(remoteCreatedAt) ? { createdAt: remoteCreatedAt } : {}
|
|
1179
|
+
}
|
|
1050
1180
|
});
|
|
1051
1181
|
} catch (error) {
|
|
1052
1182
|
this.#ctx.logger.warn("dsh-remote-local: could not seed mirror for %s: %s", remote.sessionId, String(error));
|
|
@@ -1107,6 +1237,13 @@ var RemoteMirrorService = class {
|
|
|
1107
1237
|
runtime.agent = agent;
|
|
1108
1238
|
this.#runtimes.set(record.localSessionId, runtime);
|
|
1109
1239
|
this.#registrations.set(record.localSessionId, this.#agents.enter(agent, void 0));
|
|
1240
|
+
const localMode = this.#localSandboxMode(session);
|
|
1241
|
+
const remoteMode = latestRemoteSandboxMode(snapshot.events);
|
|
1242
|
+
if (localMode !== void 0 && remoteMode !== void 0 && localMode !== remoteMode) {
|
|
1243
|
+
void this.#hosts.setSandboxMode(record.hostId, record.remoteSessionId, localMode).catch((error) => {
|
|
1244
|
+
this.#ctx.logger.warn("dsh-remote-local: initial sandbox mode failed: %s", String(error));
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1110
1247
|
await this.#save();
|
|
1111
1248
|
this.#schedule(runtime);
|
|
1112
1249
|
if (live) {
|
|
@@ -1214,15 +1351,19 @@ var RemoteMirrorService = class {
|
|
|
1214
1351
|
#appendEvent(runtime, event) {
|
|
1215
1352
|
const before = runtime.session.seq;
|
|
1216
1353
|
runtime.applyingRemote = true;
|
|
1354
|
+
let mappedSeq;
|
|
1217
1355
|
try {
|
|
1218
1356
|
if (!isMirrorNoise(event.type)) {
|
|
1219
|
-
appendRemoteEvent(runtime.session, remapRemoteEvent(event, runtime.remoteToLocalSeq), runtime);
|
|
1357
|
+
mappedSeq = appendRemoteEvent(runtime.session, remapRemoteEvent(event, runtime.remoteToLocalSeq), runtime);
|
|
1220
1358
|
}
|
|
1221
1359
|
} finally {
|
|
1222
1360
|
runtime.applyingRemote = false;
|
|
1223
1361
|
}
|
|
1224
1362
|
const after = runtime.session.seq;
|
|
1225
|
-
if (
|
|
1363
|
+
if (mappedSeq !== void 0) {
|
|
1364
|
+
runtime.remoteToLocalSeq.set(event.seq, mappedSeq);
|
|
1365
|
+
this.#flushAssistantEnd(runtime, event.seq);
|
|
1366
|
+
} else if (after > before) {
|
|
1226
1367
|
runtime.remoteToLocalSeq.set(event.seq, after - 1);
|
|
1227
1368
|
this.#flushAssistantEnd(runtime, event.seq);
|
|
1228
1369
|
}
|
|
@@ -1230,6 +1371,7 @@ var RemoteMirrorService = class {
|
|
|
1230
1371
|
if (typeof turn !== "number") return;
|
|
1231
1372
|
if (event.type === "turn/start") {
|
|
1232
1373
|
runtime.latestTurn = Math.max(runtime.latestTurn, turn);
|
|
1374
|
+
this.#setRunning(runtime, true);
|
|
1233
1375
|
return;
|
|
1234
1376
|
}
|
|
1235
1377
|
if (event.type !== "turn/end") return;
|
|
@@ -1238,13 +1380,32 @@ var RemoteMirrorService = class {
|
|
|
1238
1380
|
runtime.awaitingTurn = null;
|
|
1239
1381
|
runtime.awaitingStarted = false;
|
|
1240
1382
|
this.#setRunning(runtime, false);
|
|
1383
|
+
const reason = typeof event.data === "object" && event.data !== null ? event.data.reason?.kind : void 0;
|
|
1384
|
+
if (reason === "aborted") {
|
|
1385
|
+
runtime.agent.inbox.clear();
|
|
1386
|
+
} else if (runtime.agent.inbox.hasPending) {
|
|
1387
|
+
const next = runtime.agent.inbox.nextTurn[0];
|
|
1388
|
+
if (next !== void 0) {
|
|
1389
|
+
runtime.agent.inbox.remove(messageIdentity(next));
|
|
1390
|
+
this.prompt(runtime, next, "queue");
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1241
1393
|
}
|
|
1242
1394
|
}
|
|
1243
1395
|
#onLocalSessionEvent(session, event) {
|
|
1244
1396
|
const candidate = event;
|
|
1245
|
-
if (candidate.type !== "model/selection") return;
|
|
1397
|
+
if (candidate.type !== "model/selection" && candidate.type !== "sandbox/mode") return;
|
|
1246
1398
|
const runtime = [...this.#runtimes.values()].find((item) => item.session.id === session.id);
|
|
1247
1399
|
if (runtime === void 0 || runtime.stopped || runtime.applyingRemote) return;
|
|
1400
|
+
if (candidate.type === "sandbox/mode") {
|
|
1401
|
+
const mode = candidate.data.mode;
|
|
1402
|
+
if (mode !== "read-only" && mode !== "workspace-write" && mode !== "danger-full-access") return;
|
|
1403
|
+
void this.#hosts.setSandboxMode(runtime.record.hostId, runtime.record.remoteSessionId, mode).catch((error) => {
|
|
1404
|
+
runtime.lastError = error instanceof Error ? error.message : String(error);
|
|
1405
|
+
this.#ctx.logger.warn("dsh-remote-local: remote sandbox mode failed: %s", runtime.lastError);
|
|
1406
|
+
});
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1248
1409
|
const record = candidate.data;
|
|
1249
1410
|
if (typeof record.provider !== "string" || typeof record.model !== "string") return;
|
|
1250
1411
|
const selection = {
|
|
@@ -1258,6 +1419,11 @@ var RemoteMirrorService = class {
|
|
|
1258
1419
|
this.#ctx.logger.warn("dsh-remote-local: remote model selection failed: %s", runtime.lastError);
|
|
1259
1420
|
});
|
|
1260
1421
|
}
|
|
1422
|
+
#localSandboxMode(session) {
|
|
1423
|
+
const projections = this.#ctx.get("sessionProjections");
|
|
1424
|
+
const mode = projections?.stateOf?.(session, "sandboxMode");
|
|
1425
|
+
return mode === "read-only" || mode === "workspace-write" || mode === "danger-full-access" ? mode : void 0;
|
|
1426
|
+
}
|
|
1261
1427
|
#defaultSelection() {
|
|
1262
1428
|
const defaults = this.#ctx.get("agentDefaultModel");
|
|
1263
1429
|
const candidate = defaults?.currentSelection?.();
|
|
@@ -1438,11 +1604,8 @@ function appendRemoteEvent(session, event, runtime) {
|
|
|
1438
1604
|
if (event.type === "user/message" && runtime.pendingUsers.length > 0) {
|
|
1439
1605
|
const source = event.data.source;
|
|
1440
1606
|
if (source?.kind === "user") {
|
|
1441
|
-
const
|
|
1442
|
-
if (
|
|
1443
|
-
session.append("user/message", messageData(message), { surfaceOp: "append" });
|
|
1444
|
-
return;
|
|
1445
|
-
}
|
|
1607
|
+
const pending = runtime.pendingUsers.shift();
|
|
1608
|
+
if (pending !== void 0 && pending.localSeq >= 0) return pending.localSeq;
|
|
1446
1609
|
}
|
|
1447
1610
|
}
|
|
1448
1611
|
const append = session.append.bind(session);
|
|
@@ -1455,6 +1618,7 @@ function appendRemoteEvent(session, event, runtime) {
|
|
|
1455
1618
|
} else {
|
|
1456
1619
|
append(event.type, event.data);
|
|
1457
1620
|
}
|
|
1621
|
+
return void 0;
|
|
1458
1622
|
}
|
|
1459
1623
|
function messageData(message) {
|
|
1460
1624
|
if (typeof message !== "object" || message === null) return message;
|
|
@@ -1514,20 +1678,34 @@ function latestSelection(session) {
|
|
|
1514
1678
|
}
|
|
1515
1679
|
return void 0;
|
|
1516
1680
|
}
|
|
1517
|
-
function
|
|
1518
|
-
|
|
1519
|
-
|
|
1681
|
+
function latestRemoteSandboxMode(events) {
|
|
1682
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
1683
|
+
const event = events[index];
|
|
1684
|
+
if (event === void 0 || event.type !== "sandbox/mode") continue;
|
|
1685
|
+
const mode = event.data.mode;
|
|
1686
|
+
if (mode === "read-only" || mode === "workspace-write" || mode === "danger-full-access") return mode;
|
|
1687
|
+
}
|
|
1688
|
+
return void 0;
|
|
1689
|
+
}
|
|
1690
|
+
function promptParts(message) {
|
|
1691
|
+
if (typeof message !== "object" || message === null) return [];
|
|
1520
1692
|
const content = message.content;
|
|
1521
|
-
if (!Array.isArray(content)) return
|
|
1522
|
-
const
|
|
1693
|
+
if (!Array.isArray(content)) return [];
|
|
1694
|
+
const parts = [];
|
|
1523
1695
|
for (const part of content) {
|
|
1524
|
-
if (typeof part === "
|
|
1525
|
-
else if (typeof part === "object" && part !== null && part.type === "text") {
|
|
1696
|
+
if (typeof part === "object" && part !== null && part.type === "text") {
|
|
1526
1697
|
const text = part.text;
|
|
1527
|
-
if (typeof text === "string")
|
|
1698
|
+
if (typeof text === "string" && text.trim() !== "") parts.push({ type: "text", text });
|
|
1699
|
+
continue;
|
|
1700
|
+
}
|
|
1701
|
+
if (typeof part === "object" && part !== null && part.type === "image") {
|
|
1702
|
+
const attachment = part.attachment;
|
|
1703
|
+
if (attachment === void 0) continue;
|
|
1704
|
+
const name2 = attachment.name;
|
|
1705
|
+
parts.push({ type: "image", attachment, ...typeof name2 === "string" ? { name: name2 } : {} });
|
|
1528
1706
|
}
|
|
1529
1707
|
}
|
|
1530
|
-
return
|
|
1708
|
+
return parts;
|
|
1531
1709
|
}
|
|
1532
1710
|
function canonicalPath(path) {
|
|
1533
1711
|
try {
|
|
@@ -1732,6 +1910,15 @@ function targetOf(mirrors, workspaceRoot, path) {
|
|
|
1732
1910
|
workspaceRoot: anchor
|
|
1733
1911
|
};
|
|
1734
1912
|
}
|
|
1913
|
+
if (isAbsolute2(path)) {
|
|
1914
|
+
return {
|
|
1915
|
+
hostId: context.hostId,
|
|
1916
|
+
localPath: path,
|
|
1917
|
+
remotePath: path,
|
|
1918
|
+
relativePath: path,
|
|
1919
|
+
workspaceRoot: anchor
|
|
1920
|
+
};
|
|
1921
|
+
}
|
|
1735
1922
|
throw Object.assign(
|
|
1736
1923
|
new Error(`remote file path is outside the mirrored workspace: ${path}`),
|
|
1737
1924
|
{ code: "workspace-file/not-found" }
|
|
@@ -2271,6 +2458,9 @@ function sessionPath(sessionId) {
|
|
|
2271
2458
|
function sessionModelPath(sessionId) {
|
|
2272
2459
|
return `${sessionPath(sessionId)}/model`;
|
|
2273
2460
|
}
|
|
2461
|
+
function sessionSandboxModePath(sessionId) {
|
|
2462
|
+
return `${sessionPath(sessionId)}/sandbox`;
|
|
2463
|
+
}
|
|
2274
2464
|
function sessionPromptPath(sessionId) {
|
|
2275
2465
|
return `${sessionPath(sessionId)}/prompt`;
|
|
2276
2466
|
}
|
|
@@ -2533,6 +2723,13 @@ var RemoteAgentClient = class {
|
|
|
2533
2723
|
body: request
|
|
2534
2724
|
});
|
|
2535
2725
|
}
|
|
2726
|
+
/** Set the file-effect sandbox mode for one remote session. */
|
|
2727
|
+
async setSandboxMode(sessionId, request) {
|
|
2728
|
+
await this.#request(sessionSandboxModePath(sessionId), isAcceptedResult, {
|
|
2729
|
+
method: "POST",
|
|
2730
|
+
body: request
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2536
2733
|
/** Send a text prompt to a remote session. */
|
|
2537
2734
|
async promptSession(sessionId, request) {
|
|
2538
2735
|
await this.#request(sessionPromptPath(sessionId), isAcceptedResult, {
|
|
@@ -4493,6 +4690,15 @@ var RemoteHostsService = class {
|
|
|
4493
4690
|
throw this.#remoteFailure(id, error);
|
|
4494
4691
|
}
|
|
4495
4692
|
}
|
|
4693
|
+
/** Set the file-effect sandbox mode for one remote session. */
|
|
4694
|
+
async setSandboxMode(id, sessionId, mode) {
|
|
4695
|
+
const client = this.#requireClient(id);
|
|
4696
|
+
try {
|
|
4697
|
+
await client.setSandboxMode(sessionId, { mode });
|
|
4698
|
+
} catch (error) {
|
|
4699
|
+
throw this.#remoteFailure(id, error);
|
|
4700
|
+
}
|
|
4701
|
+
}
|
|
4496
4702
|
/** Follow a live remote session from a cursor as an SSE-backed stream. */
|
|
4497
4703
|
async *followSessionEvents(id, sessionId, cursor, signal) {
|
|
4498
4704
|
const client = this.#requireClient(id);
|
|
@@ -4552,12 +4758,13 @@ var RemoteHostsService = class {
|
|
|
4552
4758
|
}
|
|
4553
4759
|
}
|
|
4554
4760
|
/** Send a prompt to a remote session. */
|
|
4555
|
-
async promptSession(id, sessionId, text, mode, selection) {
|
|
4761
|
+
async promptSession(id, sessionId, text, mode, selection, content) {
|
|
4556
4762
|
const client = this.#requireClient(id);
|
|
4557
4763
|
try {
|
|
4558
4764
|
await client.promptSession(sessionId, {
|
|
4559
4765
|
text,
|
|
4560
4766
|
...mode === void 0 ? {} : { mode },
|
|
4767
|
+
...content === void 0 ? {} : { content },
|
|
4561
4768
|
...selection === void 0 ? {} : selection
|
|
4562
4769
|
});
|
|
4563
4770
|
return { accepted: true };
|
|
@@ -17,6 +17,8 @@ export type HostApiEndpoint = 'state' | 'target/validate' | 'host/add' | 'host/r
|
|
|
17
17
|
export interface RemoteHostsState {
|
|
18
18
|
readonly protocolVersion: number;
|
|
19
19
|
readonly hosts: readonly HostView[];
|
|
20
|
+
/** Local workspace ids whose Session creation is owned by a remote host. */
|
|
21
|
+
readonly mirroredWorkspaceIds: readonly string[];
|
|
20
22
|
}
|
|
21
23
|
/** Result of parsing a destination. */
|
|
22
24
|
export interface ValidatedTarget {
|
|
@@ -25,6 +25,10 @@ interface MirrorRecord {
|
|
|
25
25
|
lastSeq: number;
|
|
26
26
|
updatedAt: string;
|
|
27
27
|
}
|
|
28
|
+
interface PendingUser {
|
|
29
|
+
readonly message: unknown;
|
|
30
|
+
localSeq: number;
|
|
31
|
+
}
|
|
28
32
|
interface MirrorRuntime {
|
|
29
33
|
readonly record: MirrorRecord;
|
|
30
34
|
readonly agent: MirrorAgent;
|
|
@@ -41,7 +45,7 @@ interface MirrorRuntime {
|
|
|
41
45
|
running: boolean;
|
|
42
46
|
stopped: boolean;
|
|
43
47
|
lastError: string | null;
|
|
44
|
-
pendingUsers:
|
|
48
|
+
pendingUsers: PendingUser[];
|
|
45
49
|
waiters: Array<() => void>;
|
|
46
50
|
}
|
|
47
51
|
interface MirrorAgent {
|
|
@@ -53,7 +57,7 @@ interface MirrorAgent {
|
|
|
53
57
|
reasoningEffort?: string;
|
|
54
58
|
};
|
|
55
59
|
readonly ctx: Context;
|
|
56
|
-
readonly inbox:
|
|
60
|
+
readonly inbox: MirrorInbox;
|
|
57
61
|
readonly status: 'idle' | 'running';
|
|
58
62
|
cancel(cause?: unknown): void;
|
|
59
63
|
whenIdle(): Promise<void>;
|
|
@@ -63,6 +67,17 @@ interface MirrorAgent {
|
|
|
63
67
|
steer(message: unknown): void;
|
|
64
68
|
inject(message: unknown): void;
|
|
65
69
|
}
|
|
70
|
+
interface MirrorInbox {
|
|
71
|
+
readonly 'next-turn': unknown[];
|
|
72
|
+
readonly 'next-step': unknown[];
|
|
73
|
+
readonly nextTurn: unknown[];
|
|
74
|
+
readonly nextStep: unknown[];
|
|
75
|
+
readonly hasPending: boolean;
|
|
76
|
+
remove(id: unknown): boolean;
|
|
77
|
+
replace(id: unknown, message: unknown): boolean;
|
|
78
|
+
splice(target: string, start: number, deleteCount: number, inserted: unknown[]): unknown[];
|
|
79
|
+
clear(): void;
|
|
80
|
+
}
|
|
66
81
|
/** Mirror remote sessions into local Sessions and custom Agents. */
|
|
67
82
|
export declare class RemoteMirrorService {
|
|
68
83
|
#private;
|
|
@@ -79,6 +94,8 @@ export declare class RemoteMirrorService {
|
|
|
79
94
|
remoteRoot: string;
|
|
80
95
|
anchor: string;
|
|
81
96
|
} | undefined;
|
|
97
|
+
/** Local workspace ids that represent remote workspaces. */
|
|
98
|
+
localWorkspaceIds(): string[];
|
|
82
99
|
/** Whether the test-only manifest excludes this remote workspace from UI listings. */
|
|
83
100
|
isTestHidden(remoteWorkspaceId: string, remotePath: string): boolean;
|
|
84
101
|
/** Find the local mirror workspace for one remote workspace. */
|
package/lib/types/service.d.ts
CHANGED
|
@@ -61,6 +61,8 @@ export declare class RemoteHostsService {
|
|
|
61
61
|
workspaces(id: string): Promise<readonly RemoteWorkspace[]>;
|
|
62
62
|
/** List remote sessions, optionally within one workspace. */
|
|
63
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. */
|
|
@@ -82,7 +84,7 @@ export declare class RemoteHostsService {
|
|
|
82
84
|
provider: string;
|
|
83
85
|
model: string;
|
|
84
86
|
reasoningEffort?: string;
|
|
85
|
-
}): Promise<AcceptedResult>;
|
|
87
|
+
}, content?: readonly import('@dsh-remote/protocol').PromptContentPart[]): Promise<AcceptedResult>;
|
|
86
88
|
/** Cancel a remote session's active turn. */
|
|
87
89
|
cancelSession(id: string, sessionId: string): Promise<AcceptedResult>;
|
|
88
90
|
/** SSH invocation facts for auxiliary remote file/terminal adapters. */
|
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.9",
|
|
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
|