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/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /* dsh-remote-local-plugin@0.8.7 — bundled host entry */
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 = { "next-turn": [], "next-step": [], nextTurn: [], nextStep: [] };
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 text = contentText(message);
684
- if (text === "") return;
685
- runtime.pendingUsers.push(message);
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
- ).catch((error) => {
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.shift();
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: { cwd: anchor }
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 (after > before) {
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 message = runtime.pendingUsers.shift();
1442
- if (message !== void 0) {
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 contentText(message) {
1518
- if (typeof message === "string") return message;
1519
- if (typeof message !== "object" || message === null) return "";
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 texts = [];
1693
+ if (!Array.isArray(content)) return [];
1694
+ const parts = [];
1523
1695
  for (const part of content) {
1524
- if (typeof part === "string") texts.push(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") texts.push(text);
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 texts.join("\n").trim();
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: unknown[];
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: Record<string, unknown[]>;
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. */
@@ -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.7",
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",