ds-harness-remote 0.5.12 → 0.5.13

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.
Files changed (48) hide show
  1. package/dist/acp/adapters/opencode-native-api.d.ts +4 -2
  2. package/dist/acp/adapters/opencode-native-api.d.ts.map +1 -1
  3. package/dist/acp/adapters/opencode-native-api.js +29 -3
  4. package/dist/acp/adapters/opencode-native-api.js.map +1 -1
  5. package/dist/acp/adapters/opencode-process.d.ts +1 -0
  6. package/dist/acp/adapters/opencode-process.d.ts.map +1 -1
  7. package/dist/acp/adapters/opencode-process.js +10 -5
  8. package/dist/acp/adapters/opencode-process.js.map +1 -1
  9. package/dist/acp/gateway.d.ts +2 -0
  10. package/dist/acp/gateway.d.ts.map +1 -1
  11. package/dist/acp/gateway.js +12 -0
  12. package/dist/acp/gateway.js.map +1 -1
  13. package/dist/acp/virtual-contract.d.ts +1 -0
  14. package/dist/acp/virtual-contract.d.ts.map +1 -1
  15. package/dist/acp/virtual-contract.js.map +1 -1
  16. package/dist/acp/virtual-harness.d.ts +1 -2
  17. package/dist/acp/virtual-harness.d.ts.map +1 -1
  18. package/dist/acp/virtual-harness.js +15 -23
  19. package/dist/acp/virtual-harness.js.map +1 -1
  20. package/dist/acp/virtual-live.d.ts +1 -0
  21. package/dist/acp/virtual-live.d.ts.map +1 -1
  22. package/dist/acp/virtual-live.js +6 -0
  23. package/dist/acp/virtual-live.js.map +1 -1
  24. package/dist/acp/virtual-titles.d.ts +17 -0
  25. package/dist/acp/virtual-titles.d.ts.map +1 -0
  26. package/dist/acp/virtual-titles.js +99 -0
  27. package/dist/acp/virtual-titles.js.map +1 -0
  28. package/dist/index.js +457 -90
  29. package/dist/index.js.map +4 -4
  30. package/dist/rpc-router.d.ts +4 -2
  31. package/dist/rpc-router.d.ts.map +1 -1
  32. package/dist/rpc-router.js +19 -2
  33. package/dist/rpc-router.js.map +1 -1
  34. package/dist/service.d.ts.map +1 -1
  35. package/dist/service.js +4 -2
  36. package/dist/service.js.map +1 -1
  37. package/dist/version.d.ts +1 -1
  38. package/dist/version.js +1 -1
  39. package/dist/workspace-upload-authority.d.ts +4 -0
  40. package/dist/workspace-upload-authority.d.ts.map +1 -0
  41. package/dist/workspace-upload-authority.js +25 -0
  42. package/dist/workspace-upload-authority.js.map +1 -0
  43. package/dist/workspace-upload.d.ts +27 -0
  44. package/dist/workspace-upload.d.ts.map +1 -0
  45. package/dist/workspace-upload.js +226 -0
  46. package/dist/workspace-upload.js.map +1 -0
  47. package/dsh-plugin.json +1 -1
  48. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9574,6 +9574,10 @@ var controlFrameTypes = [
9574
9574
  "error"
9575
9575
  ];
9576
9576
  var rpcMethods = [
9577
+ "workspace.upload.open",
9578
+ "workspace.upload.chunk",
9579
+ "workspace.upload.commit",
9580
+ "workspace.upload.abort",
9577
9581
  "harness.transport.describe",
9578
9582
  "harness.api.call",
9579
9583
  "harness.api.transfer.open",
@@ -11747,6 +11751,12 @@ var AcpLiveProjection = class {
11747
11751
  const update = isRecord3(params.update) ? params.update : params;
11748
11752
  const kind = string(update.sessionUpdate) ?? string(update.type);
11749
11753
  const session = this.context.sessions.get(follow.sessionId);
11754
+ if (kind === "session_info_update") {
11755
+ if (session && params.sessionId === follow.acpSessionId && typeof update.title === "string") {
11756
+ this.context.applySessionTitle(session, update.title);
11757
+ }
11758
+ return;
11759
+ }
11750
11760
  if (kind === "model_selection" && isRecord3(update.selected) && update.selected.provider === this.context.backend && typeof update.selected.model === "string") {
11751
11761
  this.context.selectedModels.set(follow.sessionId, update.selected);
11752
11762
  this.context.publishProjection(follow.sessionId, "modelSelection", this.context.modelSelectionProjection(follow.sessionId), this.context.nextProjectionSeq());
@@ -12111,6 +12121,86 @@ var AcpLiveProjection = class {
12111
12121
  }
12112
12122
  };
12113
12123
 
12124
+ // src/acp/virtual-titles.ts
12125
+ var OPENCODE_DEFAULT_TITLE = /^(?:New|Child) session - \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
12126
+ var AcpSessionTitles = class {
12127
+ constructor(backend, client, sessions, publish) {
12128
+ this.backend = backend;
12129
+ this.client = client;
12130
+ this.sessions = sessions;
12131
+ this.publish = publish;
12132
+ }
12133
+ pending = /* @__PURE__ */ new Map();
12134
+ closed = false;
12135
+ nativeTitle(title) {
12136
+ const next = title?.trim();
12137
+ if (!next || next === "Untitled Session" || this.backend === "opencode" && OPENCODE_DEFAULT_TITLE.test(next)) return void 0;
12138
+ return next;
12139
+ }
12140
+ applyNative(session, title) {
12141
+ const next = this.nativeTitle(title);
12142
+ if (this.closed || session.titleOverridden || !next || this.sessions.get(session.sessionId) !== session) return;
12143
+ session.titleFromPrompt = false;
12144
+ this.pending.get(session.sessionId)?.abort();
12145
+ if (next === session.title) return;
12146
+ session.title = next;
12147
+ this.publish(session.sessionId, next);
12148
+ }
12149
+ applyPrompt(session, text) {
12150
+ if (this.backend !== "opencode" || this.closed || session.titleOverridden || session.titleFromPrompt || !session.blank && this.nativeTitle(session.title) !== void 0) return;
12151
+ const next = Array.from(text?.replace(/\s+/g, " ").trim() ?? "").slice(0, 60).join("");
12152
+ if (!next) return;
12153
+ session.titleFromPrompt = true;
12154
+ session.title = next;
12155
+ this.publish(session.sessionId, next);
12156
+ }
12157
+ async refresh(session) {
12158
+ if (this.backend === "cursor" || this.closed || session.titleOverridden || !this.client.listSessions) return;
12159
+ this.pending.get(session.sessionId)?.abort();
12160
+ const controller = new AbortController();
12161
+ const signal = controller.signal;
12162
+ this.pending.set(session.sessionId, controller);
12163
+ const deadline = this.backend === "opencode" ? setTimeout(() => controller.abort(), 2e4) : void 0;
12164
+ try {
12165
+ for (const delay of this.backend === "opencode" ? [0, 1e3, 3e3, 6e3, 9e3] : [0]) {
12166
+ if (delay) await waitForTitle(delay, signal);
12167
+ if (signal.aborted || this.closed || session.titleOverridden) return;
12168
+ const previous = session.title;
12169
+ try {
12170
+ const items = await this.client.listSessions(session.cwd, this.backend, 30, signal);
12171
+ if (signal.aborted || this.sessions.get(session.sessionId) !== session || session.title !== previous) return;
12172
+ const current = items.find((item) => item.conversationId === session.acpSessionId);
12173
+ if (current && this.nativeTitle(current.title) !== void 0) {
12174
+ this.applyNative(session, current.title);
12175
+ return;
12176
+ }
12177
+ } catch {
12178
+ }
12179
+ }
12180
+ } finally {
12181
+ if (deadline !== void 0) clearTimeout(deadline);
12182
+ if (this.pending.get(session.sessionId) === controller) this.pending.delete(session.sessionId);
12183
+ }
12184
+ }
12185
+ close() {
12186
+ this.closed = true;
12187
+ for (const controller of this.pending.values()) controller.abort();
12188
+ this.pending.clear();
12189
+ }
12190
+ };
12191
+ function waitForTitle(delay, signal) {
12192
+ return new Promise((resolve6) => {
12193
+ const finish = () => {
12194
+ clearTimeout(timer);
12195
+ signal.removeEventListener("abort", finish);
12196
+ resolve6();
12197
+ };
12198
+ const timer = setTimeout(finish, delay);
12199
+ signal.addEventListener("abort", finish, { once: true });
12200
+ if (signal.aborted) finish();
12201
+ });
12202
+ }
12203
+
12114
12204
  // src/acp/virtual-harness.ts
12115
12205
  var AcpVirtualHarness = class _AcpVirtualHarness {
12116
12206
  constructor(client, host, backend = "cursor", hostCarrier) {
@@ -12142,7 +12232,8 @@ var AcpVirtualHarness = class _AcpVirtualHarness {
12142
12232
  },
12143
12233
  publishProjection: (...args) => owner.events.publishProjection(...args),
12144
12234
  nextProjectionSeq: (...args) => owner.nextProjectionSeq(...args),
12145
- refreshSessionTitle: (...args) => owner.refreshSessionTitle(...args),
12235
+ refreshSessionTitle: (...args) => owner.titles.refresh(...args),
12236
+ applySessionTitle: (...args) => owner.titles.applyNative(...args),
12146
12237
  publishWorkspaceBaseline: () => owner.publishWorkspaceBaseline(),
12147
12238
  get pendingApprovals() {
12148
12239
  return owner.pendingApprovals;
@@ -12190,6 +12281,12 @@ var AcpVirtualHarness = class _AcpVirtualHarness {
12190
12281
  },
12191
12282
  nextProjectionSeq: (...args) => owner.nextProjectionSeq(...args)
12192
12283
  });
12284
+ this.titles = new AcpSessionTitles(
12285
+ backend,
12286
+ client,
12287
+ this.sessions,
12288
+ (sessionId, title) => this.events.publishProjection(sessionId, "title", title)
12289
+ );
12193
12290
  this.api = this.events.createApiProxy();
12194
12291
  this.unsubscribeArchived = client.onSessionArchived?.((data2) => {
12195
12292
  if (this.closed || data2.backend !== this.backend) return;
@@ -12198,6 +12295,7 @@ var AcpVirtualHarness = class _AcpVirtualHarness {
12198
12295
  }
12199
12296
  live;
12200
12297
  events;
12298
+ titles;
12201
12299
  api;
12202
12300
  workspaceById = /* @__PURE__ */ new Map();
12203
12301
  sessions = /* @__PURE__ */ new Map();
@@ -12408,6 +12506,7 @@ var AcpVirtualHarness = class _AcpVirtualHarness {
12408
12506
  async close() {
12409
12507
  if (this.closed) return;
12410
12508
  this.closed = true;
12509
+ this.titles.close();
12411
12510
  this.unsubscribeArchived?.();
12412
12511
  for (const stream of this.workspaceStreams) stream.close();
12413
12512
  for (const stream of this.controlStreams) stream.close();
@@ -12563,6 +12662,7 @@ var AcpVirtualHarness = class _AcpVirtualHarness {
12563
12662
  source: requestId ? { kind: "user", rpcId: requestId } : { kind: "user" }
12564
12663
  });
12565
12664
  }
12665
+ this.titles.applyPrompt(session, text);
12566
12666
  session.blank = false;
12567
12667
  session.running = true;
12568
12668
  session.updatedAt = Date.now();
@@ -12639,6 +12739,8 @@ var AcpVirtualHarness = class _AcpVirtualHarness {
12639
12739
  }
12640
12740
  if (events.length > 0) {
12641
12741
  session.events = events;
12742
+ const firstUser = session.events.find((entry) => entry.event.type === "user/message");
12743
+ this.titles.applyPrompt(session, extractPromptText(array(record(firstUser?.event.data).content)));
12642
12744
  session.blank = false;
12643
12745
  for (const event of session.events) this.cacheImageAttachments(session.sessionId, array(record(event.event.data).content ?? record(record(event.event.data).message).content));
12644
12746
  }
@@ -12729,11 +12831,12 @@ var AcpVirtualHarness = class _AcpVirtualHarness {
12729
12831
  return success(await this.client.listDirectory(path, signal));
12730
12832
  }
12731
12833
  registerSession(acpSessionId, cwd2, title, createdAt = Date.now(), updatedAt = Date.now(), blank = true) {
12834
+ const nativeTitle = this.titles.nativeTitle(title);
12732
12835
  const session = {
12733
12836
  sessionId: `${sessionPrefix(this.backend)}${acpSessionId}`,
12734
12837
  acpSessionId,
12735
12838
  cwd: cwd2,
12736
- ...title === void 0 ? {} : { title },
12839
+ ...nativeTitle === void 0 ? {} : { title: nativeTitle },
12737
12840
  blank,
12738
12841
  running: false,
12739
12842
  createdAt,
@@ -12743,23 +12846,9 @@ var AcpVirtualHarness = class _AcpVirtualHarness {
12743
12846
  this.sessions.set(session.sessionId, session);
12744
12847
  return session;
12745
12848
  }
12746
- applyDiscoveredTitle(session, title) {
12747
- const next = title.trim();
12748
- if (this.closed || session.titleOverridden || !next || next === "Untitled Session" || next === session.title) return;
12749
- session.title = next;
12750
- this.events.publishProjection(session.sessionId, "title", next);
12751
- }
12752
- async refreshSessionTitle(session) {
12753
- if (this.backend === "cursor" || this.closed || session.titleOverridden || this.client.listSessions === void 0) return;
12754
- try {
12755
- const items = await this.client.listSessions(session.cwd, this.backend, 30);
12756
- const current = items.find((item) => item.conversationId === session.acpSessionId);
12757
- if (current !== void 0 && this.sessions.get(session.sessionId) === session) this.applyDiscoveredTitle(session, current.title);
12758
- } catch {
12759
- }
12760
- }
12761
12849
  async discoverAndAttachSessions(workspace) {
12762
12850
  if (this.backend === "cursor") return;
12851
+ const previousTitles = new Map([...this.sessions].map(([id5, session]) => [id5, session.title]));
12763
12852
  try {
12764
12853
  let discovered = [];
12765
12854
  if (this.client.listSessions !== void 0) {
@@ -12777,7 +12866,9 @@ var AcpVirtualHarness = class _AcpVirtualHarness {
12777
12866
  const existing = this.sessions.get(sessionId);
12778
12867
  if (existing !== void 0) {
12779
12868
  if (Number.isFinite(item.updatedAt)) existing.updatedAt = Math.max(existing.updatedAt, item.updatedAt);
12780
- this.applyDiscoveredTitle(existing, item.title);
12869
+ if (!previousTitles.has(sessionId) || existing.title === previousTitles.get(sessionId)) {
12870
+ this.titles.applyNative(existing, item.title);
12871
+ }
12781
12872
  }
12782
12873
  if (!this.archivedSessionIds.has(sessionId) && !workspace.sessionIds.includes(sessionId)) {
12783
12874
  workspace.sessionIds.push(sessionId);
@@ -14103,11 +14194,11 @@ function isLanCandidatePair(localType, remoteType, localScope, remoteScope) {
14103
14194
  return true;
14104
14195
  return localType === "host" && isLocalNetworkScope(localScope) && remoteType === "prflx" && remoteScope === "unknown" || remoteType === "host" && isLocalNetworkScope(remoteScope) && localType === "prflx" && localScope === "unknown";
14105
14196
  }
14106
- function isLocalNetworkScope(scope) {
14107
- return scope === "private" || scope === "link-local" || scope === "loopback";
14197
+ function isLocalNetworkScope(scope2) {
14198
+ return scope2 === "private" || scope2 === "link-local" || scope2 === "loopback";
14108
14199
  }
14109
- function isExplicitlyNonLanScope(scope) {
14110
- return scope === "public" || scope === "cgnat" || scope === "reserved";
14200
+ function isExplicitlyNonLanScope(scope2) {
14201
+ return scope2 === "public" || scope2 === "cgnat" || scope2 === "reserved";
14111
14202
  }
14112
14203
  function increment(map, key) {
14113
14204
  map[key] = (map[key] ?? 0) + 1;
@@ -26956,11 +27047,11 @@ function routeStreamEvent2(event, streamId, queue) {
26956
27047
  const data2 = event.data;
26957
27048
  if (data2.streamId !== streamId) return;
26958
27049
  if (data2.reason === "failed") {
26959
- const failure3 = data2.failure;
27050
+ const failure4 = data2.failure;
26960
27051
  queue.fail(remoteFailure({
26961
- code: typeof failure3?.code === "string" ? failure3.code : "internal",
26962
- message: typeof failure3?.message === "string" ? failure3.message : "The remote Harness stream failed.",
26963
- details: isRecord9(failure3?.details) ? failure3.details : {}
27052
+ code: typeof failure4?.code === "string" ? failure4.code : "internal",
27053
+ message: typeof failure4?.message === "string" ? failure4.message : "The remote Harness stream failed.",
27054
+ details: isRecord9(failure4?.details) ? failure4.details : {}
26964
27055
  }));
26965
27056
  } else {
26966
27057
  queue.close();
@@ -26977,11 +27068,11 @@ function parseRpcResult(value) {
26977
27068
  error: { code: value.error.code, message: value.error.message, details: value.error.details }
26978
27069
  };
26979
27070
  }
26980
- function remoteFailure(failure3) {
26981
- return Object.assign(new Error(failure3.message), {
27071
+ function remoteFailure(failure4) {
27072
+ return Object.assign(new Error(failure4.message), {
26982
27073
  isDSHRemoteError: true,
26983
- code: failure3.code,
26984
- details: failure3.details
27074
+ code: failure4.code,
27075
+ details: failure4.details
26985
27076
  });
26986
27077
  }
26987
27078
  function bytesToBase643(bytes) {
@@ -27313,7 +27404,7 @@ function normalizeServerUrl(value) {
27313
27404
  }
27314
27405
 
27315
27406
  // src/version.ts
27316
- var PLUGIN_VERSION = "0.5.12";
27407
+ var PLUGIN_VERSION = "0.5.13";
27317
27408
 
27318
27409
  // src/server-api.ts
27319
27410
  var TERMINAL_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/u;
@@ -28079,7 +28170,7 @@ var TypertGatewaySwitch = class {
28079
28170
  return { ok: false, error: this.failure(error) };
28080
28171
  }
28081
28172
  }) : (endpoint, payload, signal) => this.localDispatch(endpoint, payload, signal, peer);
28082
- const open2 = this.localOpen === void 0 ? (async (endpoint, payload, signal) => {
28173
+ const open3 = this.localOpen === void 0 ? (async (endpoint, payload, signal) => {
28083
28174
  if (this.localStream === void 0) throw new Error("The local Harness Gateway does not support Remote streams.");
28084
28175
  return this.localStream({ ...requestFromCarrier(endpoint, payload, signal), ...peer === void 0 ? {} : { peer } });
28085
28176
  }) : (endpoint, payload, signal, uplink) => this.localOpen(endpoint, payload, signal, uplink, peer);
@@ -28088,7 +28179,7 @@ var TypertGatewaySwitch = class {
28088
28179
  peer,
28089
28180
  ...this.localStream === void 0 ? {} : { stream: (request) => this.localStream({ ...request, ...peer === void 0 ? {} : { peer } }) },
28090
28181
  dispatch,
28091
- open: open2,
28182
+ open: open3,
28092
28183
  failure: (error) => this.failure(error),
28093
28184
  supportsCarrier: this.localDispatch !== void 0 && this.localOpen !== void 0
28094
28185
  };
@@ -28115,12 +28206,12 @@ var TypertGatewaySwitch = class {
28115
28206
  };
28116
28207
  }
28117
28208
  if (this.originalOpen !== void 0) {
28118
- const open2 = this.originalOpen;
28119
- const rc1 = usesRc1Arity(open2);
28209
+ const open3 = this.originalOpen;
28210
+ const rc1 = usesRc1Arity(open3);
28120
28211
  this.runtime.openWireStream = (...callArgs) => {
28121
28212
  const endpoint = callArgs[0];
28122
28213
  if (this.remoteTarget === void 0 || isLocalOnlyEndpoint(endpoint)) {
28123
- return rc1 ? Reflect.apply(open2, this.runtime, callArgs) : open2.call(this.runtime, endpoint, callArgs[1], callArgs[2]);
28214
+ return rc1 ? Reflect.apply(open3, this.runtime, callArgs) : open3.call(this.runtime, endpoint, callArgs[1], callArgs[2]);
28124
28215
  }
28125
28216
  const signal = rc1 ? callArgs[4] : callArgs[2];
28126
28217
  return this.openRemote(endpoint, callArgs[1], signal ?? new AbortController().signal, rc1 ? callArgs[2] : void 0);
@@ -28204,8 +28295,8 @@ function workspaceConnectionResult(endpoint, result) {
28204
28295
  ]
28205
28296
  };
28206
28297
  }
28207
- function usesRc1Arity(open2) {
28208
- return open2.length >= 5;
28298
+ function usesRc1Arity(open3) {
28299
+ return open3.length >= 5;
28209
28300
  }
28210
28301
  function endedUplink() {
28211
28302
  return {
@@ -28221,9 +28312,9 @@ function linkControl(signal) {
28221
28312
  return control;
28222
28313
  }
28223
28314
  function createLocalOpen(gateway, runtime) {
28224
- const open2 = runtime.openWireStream;
28225
- if (open2 !== void 0) {
28226
- return usesRc1Arity(open2) ? (endpoint, payload, signal, uplink, peer) => open2.call(runtime, endpoint, payload, uplink ?? endedUplink(), peer, signal, linkControl(signal)) : (endpoint, payload, signal) => open2.call(runtime, endpoint, payload, signal);
28315
+ const open3 = runtime.openWireStream;
28316
+ if (open3 !== void 0) {
28317
+ return usesRc1Arity(open3) ? (endpoint, payload, signal, uplink, peer) => open3.call(runtime, endpoint, payload, uplink ?? endedUplink(), peer, signal, linkControl(signal)) : (endpoint, payload, signal) => open3.call(runtime, endpoint, payload, signal);
28227
28318
  }
28228
28319
  const wire = gateway.wireStream;
28229
28320
  if (wire === void 0) return void 0;
@@ -28366,10 +28457,10 @@ function isUsableExternalNode(candidate, requireFrom) {
28366
28457
  }
28367
28458
  function isExecutableFile(candidate) {
28368
28459
  try {
28369
- const stat8 = statSync(candidate);
28370
- if (!stat8.isFile()) return false;
28460
+ const stat9 = statSync(candidate);
28461
+ if (!stat9.isFile()) return false;
28371
28462
  if (process.platform === "win32") return true;
28372
- return (stat8.mode & 73) !== 0;
28463
+ return (stat9.mode & 73) !== 0;
28373
28464
  } catch {
28374
28465
  return false;
28375
28466
  }
@@ -30186,8 +30277,8 @@ var IdentityStore = class {
30186
30277
  };
30187
30278
  function serverStorageDirectory(root, serverUrl, role) {
30188
30279
  const origin = new URL(serverUrl).origin;
30189
- const scope = createHash("sha256").update(origin).digest("hex").slice(0, 24);
30190
- return join4(root, "servers", scope, role);
30280
+ const scope2 = createHash("sha256").update(origin).digest("hex").slice(0, 24);
30281
+ return join4(root, "servers", scope2, role);
30191
30282
  }
30192
30283
  function fingerprint(publicKey) {
30193
30284
  const compact = createHash("sha256").update(fromBase64Url2(publicKey)).digest("hex").slice(0, 12).toUpperCase();
@@ -33670,11 +33761,11 @@ var AsyncQueue = class {
33670
33761
  }
33671
33762
  };
33672
33763
  var WorkspaceBridge = class {
33673
- constructor(resolveCwd, terminalEnabled, state = new WorkspaceBridgeState(), spawnTerminal = pipeTerminalSpawner(), scope) {
33764
+ constructor(resolveCwd, terminalEnabled, state = new WorkspaceBridgeState(), spawnTerminal = pipeTerminalSpawner(), scope2) {
33674
33765
  this.resolveCwd = resolveCwd;
33675
33766
  this.terminalEnabled = terminalEnabled;
33676
33767
  this.state = state;
33677
- this.scope = scope;
33768
+ this.scope = scope2;
33678
33769
  this.terminal = new RemoteTerminalController(state, spawnTerminal, terminalEnabled, (code, message) => this.scopeError(code, message));
33679
33770
  }
33680
33771
  terminal;
@@ -34472,16 +34563,16 @@ var TranscriptWatcher = class extends EventEmitter {
34472
34563
  await this.readNewLines();
34473
34564
  }
34474
34565
  async readNewLines() {
34475
- let stat8;
34566
+ let stat9;
34476
34567
  try {
34477
- stat8 = await fs2.stat(this.transcriptPath);
34568
+ stat9 = await fs2.stat(this.transcriptPath);
34478
34569
  } catch {
34479
34570
  return;
34480
34571
  }
34481
- if (stat8.size <= this.offset) return;
34572
+ if (stat9.size <= this.offset) return;
34482
34573
  const handle = await fs2.open(this.transcriptPath, "r");
34483
34574
  try {
34484
- const bytesToRead = stat8.size - this.offset;
34575
+ const bytesToRead = stat9.size - this.offset;
34485
34576
  const buffer = Buffer.alloc(bytesToRead);
34486
34577
  const { bytesRead } = await handle.read(buffer, 0, bytesToRead, this.offset);
34487
34578
  this.offset += bytesRead;
@@ -35156,7 +35247,32 @@ import { realpath as realpath4 } from "node:fs/promises";
35156
35247
  import { randomBytes as randomBytes8 } from "node:crypto";
35157
35248
  import { createServer as createServer2 } from "node:net";
35158
35249
  import { realpath as realpath3 } from "node:fs/promises";
35250
+ import { execFile as execFile2 } from "node:child_process";
35251
+ import { promisify as promisify2, stripVTControlCharacters } from "node:util";
35159
35252
  var MAX_RESPONSE_BYTES = 64 * 1024;
35253
+ var execFileAsync = promisify2(execFile2);
35254
+ async function opencodeHasNativeApi(binary, args, cwd2, signal) {
35255
+ try {
35256
+ const { stdout, stderr } = await execFileAsync(binary, [...opencodePrivateArgs(args), "--help"], {
35257
+ cwd: cwd2,
35258
+ signal,
35259
+ timeout: 2e4,
35260
+ maxBuffer: MAX_RESPONSE_BYTES,
35261
+ windowsHide: true,
35262
+ killSignal: "SIGKILL"
35263
+ });
35264
+ const help = stripVTControlCharacters(`${stdout}
35265
+ ${stderr}`);
35266
+ const hasFlag = (name2) => new RegExp(`^\\s*(?:-[A-Za-z],?\\s+)?--${name2}(?=[\\s=,\\[]|$)`, "m").test(help);
35267
+ const network = ["hostname", "port", "mdns"].map(hasFlag);
35268
+ if (!/\bacp\b/i.test(help) || !hasFlag("help") || network.some(Boolean) && !network.every(Boolean)) throw new Error("Invalid ACP help");
35269
+ return network.every(Boolean);
35270
+ } catch (error) {
35271
+ if (signal.aborted) throw new RpcError("OPENCODE_CLOSED", "OpenCode ACP was closed.");
35272
+ if (error?.code === "ENOENT") throw new RpcError("OPENCODE_BINARY_UNAVAILABLE", "The Host OpenCode executable is unavailable.");
35273
+ throw new RpcError("OPENCODE_CLI_PROBE_FAILED", "Could not inspect OpenCode ACP options. Check opencode acp --help on the Host.");
35274
+ }
35275
+ }
35160
35276
  var OpencodeNativeApi = class {
35161
35277
  constructor(origin, request = fetch) {
35162
35278
  this.origin = origin;
@@ -35233,10 +35349,10 @@ function opencodePrivateArgs(args, port) {
35233
35349
  for (let i = 0; i < args.length; i++) {
35234
35350
  const arg = args[i];
35235
35351
  if (/^--(?:port|hostname|mdns|no-mdns)(?:=|$)/.test(arg)) {
35236
- if (arg === "--port" || arg === "--hostname" || arg === "--mdns" && /^(true|false)$/.test(args[i + 1] ?? "")) i++;
35352
+ if (arg === "--port" || arg === "--hostname" || (arg === "--mdns" || arg === "--no-mdns") && /^(true|false)$/.test(args[i + 1] ?? "")) i++;
35237
35353
  } else kept.push(arg);
35238
35354
  }
35239
- return [...kept, `--hostname=127.0.0.1`, `--port=${port}`, "--mdns=false"];
35355
+ return port === void 0 ? kept : [...kept, `--hostname=127.0.0.1`, `--port=${port}`, "--mdns=false"];
35240
35356
  }
35241
35357
  function unavailable() {
35242
35358
  return new RpcError("OPENCODE_ARCHIVE_UNAVAILABLE", "OpenCode did not confirm the archive operation. Check the Host OpenCode version and retry after refreshing its session list.");
@@ -35275,6 +35391,7 @@ var OpencodeAcpClient = class _OpencodeAcpClient {
35275
35391
  startPromise;
35276
35392
  historyBusy = false;
35277
35393
  historyStop;
35394
+ startupAbort = new AbortController();
35278
35395
  nativeApi;
35279
35396
  archivedSessions = /* @__PURE__ */ new Set();
35280
35397
  archivingSessions = /* @__PURE__ */ new Set();
@@ -35464,6 +35581,7 @@ var OpencodeAcpClient = class _OpencodeAcpClient {
35464
35581
  async close() {
35465
35582
  if (this.closed) return;
35466
35583
  this.closed = true;
35584
+ this.startupAbort.abort();
35467
35585
  this.ready = false;
35468
35586
  this.models.clear();
35469
35587
  this.nativeApi?.close();
@@ -35495,14 +35613,16 @@ var OpencodeAcpClient = class _OpencodeAcpClient {
35495
35613
  let child;
35496
35614
  if (this.spawnAcp) child = this.spawnAcp(this.binary);
35497
35615
  else {
35498
- const port = await opencodeLoopbackPort();
35616
+ const args = this.options.args ?? ["acp"];
35617
+ const nativeApiSupported = await opencodeHasNativeApi(this.binary, args, this.options.cwd, this.startupAbort.signal);
35618
+ const port = nativeApiSupported ? await opencodeLoopbackPort() : void 0;
35499
35619
  if (this.closed) throw new OpencodeAcpError("OPENCODE_CLOSED", "OpenCode ACP was closed.");
35500
- this.nativeApi = new OpencodeNativeApi(`http://127.0.0.1:${port}`);
35501
- child = spawn6(this.binary, opencodePrivateArgs(this.options.args ?? ["acp"], port), {
35620
+ if (port !== void 0) this.nativeApi = new OpencodeNativeApi(`http://127.0.0.1:${port}`);
35621
+ child = spawn6(this.binary, opencodePrivateArgs(args, port), {
35502
35622
  cwd: this.options.cwd,
35503
35623
  stdio: ["pipe", "pipe", "pipe"],
35504
35624
  windowsHide: true,
35505
- env: { ...process.env, OPENCODE_SERVER_USERNAME: "opencode", OPENCODE_SERVER_PASSWORD: this.nativeApi.password }
35625
+ env: { ...process.env, ...this.nativeApi ? { OPENCODE_SERVER_USERNAME: "opencode", OPENCODE_SERVER_PASSWORD: this.nativeApi.password } : {} }
35506
35626
  });
35507
35627
  }
35508
35628
  this.process = child;
@@ -35914,15 +36034,15 @@ async function querySqliteJson(dbPath, sql, params = []) {
35914
36034
  } catch {
35915
36035
  }
35916
36036
  try {
35917
- const { execFile: execFile2 } = await import("node:child_process");
35918
- const { promisify: promisify2 } = await import("node:util");
35919
- const execFileAsync = promisify2(execFile2);
36037
+ const { execFile: execFile3 } = await import("node:child_process");
36038
+ const { promisify: promisify3 } = await import("node:util");
36039
+ const execFileAsync2 = promisify3(execFile3);
35920
36040
  let formattedSql = sql;
35921
36041
  for (const p of params) {
35922
36042
  const val = typeof p === "number" ? String(p) : `'${String(p).replace(/'/g, "''")}'`;
35923
36043
  formattedSql = formattedSql.replace("?", val);
35924
36044
  }
35925
- const { stdout } = await execFileAsync("sqlite3", [dbPath, "-json", formattedSql]);
36045
+ const { stdout } = await execFileAsync2("sqlite3", [dbPath, "-json", formattedSql]);
35926
36046
  return JSON.parse(stdout || "[]");
35927
36047
  } catch {
35928
36048
  return [];
@@ -35972,7 +36092,7 @@ async function readTranscriptSummary(baseDir, conversationId) {
35972
36092
  try {
35973
36093
  const path = await transcriptPath(baseDir, conversationId);
35974
36094
  if (path === void 0) return void 0;
35975
- const stat8 = await fs3.stat(path);
36095
+ const stat9 = await fs3.stat(path);
35976
36096
  const handle = await fs3.open(path, "r");
35977
36097
  try {
35978
36098
  const buffer = Buffer.alloc(4096);
@@ -35983,7 +36103,7 @@ async function readTranscriptSummary(baseDir, conversationId) {
35983
36103
  if (record9.type !== "USER_INPUT") return void 0;
35984
36104
  const title = cleanUserPrompt(record9.content).split("\n")[0]?.trim().slice(0, 40) || (agyImageReferences(record9.content ?? "").length > 0 ? "Image" : void 0);
35985
36105
  if (!title) return void 0;
35986
- return { conversationId, title, createdAt: record9.created_at ? new Date(record9.created_at).getTime() : stat8.mtimeMs, updatedAt: stat8.mtimeMs };
36106
+ return { conversationId, title, createdAt: record9.created_at ? new Date(record9.created_at).getTime() : stat9.mtimeMs, updatedAt: stat9.mtimeMs };
35987
36107
  } finally {
35988
36108
  await handle.close();
35989
36109
  }
@@ -37003,6 +37123,18 @@ var AcpRemoteGateway = class {
37003
37123
  if (!tools) throw new RpcError("ACP_CONNECTION_CLOSED", "The ACP workspace connection is closed.");
37004
37124
  return tools;
37005
37125
  }
37126
+ /** Trusted, connection-scoped authority for the dedicated workspace uploader. */
37127
+ resolveUploadWorkspace(connectionId, publicId) {
37128
+ const match = /^(acp|cursor|pi|opencode|claude):(.+)$/.exec(publicId);
37129
+ const backend = match?.[1] === "acp" ? "antigravity" : match?.[1];
37130
+ const session = match ? this.sessionCwds.get(match[2]) : void 0;
37131
+ if (!this.peers.has(connectionId) || !session || session.backend !== backend || this.closed) {
37132
+ throw new RpcError("ACP_WORKSPACE_UNAVAILABLE", "The ACP session has no trusted workspace.");
37133
+ }
37134
+ this.requireAcp(backend);
37135
+ this.requireSessionOwner(connectionId, match[2]);
37136
+ return session.cwd;
37137
+ }
37006
37138
  async openToolStream(connectionId, sessionId, backend, endpoint, args, signal) {
37007
37139
  const session = this.sessionCwds.get(sessionId);
37008
37140
  if (!session || session.backend !== backend) throw new RpcError("ACP_WORKSPACE_UNAVAILABLE", "The ACP session has no trusted workspace.");
@@ -37147,8 +37279,8 @@ var AcpRemoteGateway = class {
37147
37279
  this.restartAttempt = 0;
37148
37280
  return;
37149
37281
  }
37150
- const failure3 = results.find((result) => result.status === "rejected");
37151
- throw failure3?.status === "rejected" ? failure3.reason : new CursorAcpError("CURSOR_BINARY_UNAVAILABLE", "No ACP backend is available.");
37282
+ const failure4 = results.find((result) => result.status === "rejected");
37283
+ throw failure4?.status === "rejected" ? failure4.reason : new CursorAcpError("CURSOR_BINARY_UNAVAILABLE", "No ACP backend is available.");
37152
37284
  }
37153
37285
  backendCurrent(backend) {
37154
37286
  return this.config.enabled && backend.enabled && !this.pendingReconfiguration.has(backend.id) && JSON.stringify(this.config.backends.find((item) => item.id === backend.id)) === JSON.stringify(backend);
@@ -38044,8 +38176,223 @@ function redact(value, key = "") {
38044
38176
  return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [childKey, redact(child, childKey)]));
38045
38177
  }
38046
38178
 
38047
- // src/service.ts
38179
+ // src/workspace-upload.ts
38048
38180
  import { randomUUID as randomUUID5 } from "node:crypto";
38181
+ import { constants as constants4 } from "node:fs";
38182
+ import { link, lstat as lstat2, open as open2, realpath as realpath6, stat as stat6, unlink } from "node:fs/promises";
38183
+ import { extname, isAbsolute as isAbsolute7, join as join12 } from "node:path";
38184
+ var WORKSPACE_UPLOAD_BYTES = 20 * 1024 * 1024;
38185
+ var WORKSPACE_UPLOAD_CHUNK = 64 * 1024;
38186
+ var scope = external_exports.object({ sessionId: external_exports.string().min(1).max(512), uploadId: external_exports.string().uuid() }).strict();
38187
+ var openParams = scope.extend({ name: external_exports.string().min(1), totalBytes: external_exports.number().int().min(0).max(WORKSPACE_UPLOAD_BYTES) }).strict();
38188
+ var chunkParams = scope.extend({ offset: external_exports.number().int().min(0), data: external_exports.string().min(1).max(Math.ceil(WORKSPACE_UPLOAD_CHUNK / 3) * 4) }).strict();
38189
+ var WorkspaceUploadHost = class {
38190
+ constructor(resolveRoot, ttlMs = 12e4) {
38191
+ this.resolveRoot = resolveRoot;
38192
+ this.ttlMs = ttlMs;
38193
+ this.timer = setInterval(() => {
38194
+ void this.serialize(() => this.expire()).catch(() => void 0);
38195
+ }, Math.min(ttlMs, 3e4));
38196
+ this.timer.unref();
38197
+ }
38198
+ uploads = /* @__PURE__ */ new Map();
38199
+ completed = /* @__PURE__ */ new Map();
38200
+ queue = Promise.resolve();
38201
+ lifetime = new AbortController();
38202
+ timer;
38203
+ closed = false;
38204
+ call(method, input3) {
38205
+ return this.serialize(async () => {
38206
+ if (this.closed) throw failure3("UPLOAD_CLOSED");
38207
+ await this.expire();
38208
+ try {
38209
+ switch (method) {
38210
+ case "workspace.upload.open":
38211
+ return await this.begin(input3);
38212
+ case "workspace.upload.chunk":
38213
+ return await this.append(input3);
38214
+ case "workspace.upload.commit":
38215
+ return await this.commit(input3);
38216
+ case "workspace.upload.abort": {
38217
+ const p = scope.parse(input3);
38218
+ const upload = this.uploads.get(p.uploadId);
38219
+ if (upload && upload.sessionId !== p.sessionId) throw failure3("UPLOAD_NOT_FOUND");
38220
+ if (upload) await this.discard(p.uploadId, upload);
38221
+ return { aborted: true };
38222
+ }
38223
+ default:
38224
+ throw failure3("METHOD_NOT_FOUND");
38225
+ }
38226
+ } catch (error) {
38227
+ const p = scope.safeParse(input3 && typeof input3 === "object" ? { sessionId: input3.sessionId, uploadId: input3.uploadId } : input3);
38228
+ const upload = p.success ? this.uploads.get(p.data.uploadId) : void 0;
38229
+ if (p.success && upload?.sessionId === p.data.sessionId) await this.discard(p.data.uploadId, upload);
38230
+ if (error instanceof external_exports.ZodError || error instanceof RpcError) throw error;
38231
+ throw failure3("UPLOAD_FAILED");
38232
+ }
38233
+ });
38234
+ }
38235
+ closeAll() {
38236
+ this.closed = true;
38237
+ this.lifetime.abort();
38238
+ clearInterval(this.timer);
38239
+ return this.serialize(async () => {
38240
+ await Promise.all([...this.uploads].map(([id5, upload]) => this.discard(id5, upload)));
38241
+ this.completed.clear();
38242
+ });
38243
+ }
38244
+ serialize(operation) {
38245
+ const next = this.queue.then(operation);
38246
+ this.queue = next.catch(() => void 0);
38247
+ return next;
38248
+ }
38249
+ async begin(input3) {
38250
+ const p = openParams.parse(input3);
38251
+ if (!validName(p.name)) throw failure3("UPLOAD_INVALID_NAME");
38252
+ if (this.uploads.has(p.uploadId) || this.completed.has(p.uploadId)) throw failure3("UPLOAD_ID_IN_USE");
38253
+ if (this.uploads.size >= 2) throw failure3("UPLOAD_LIMIT");
38254
+ const sourceRoot = await this.sessionRoot(p.sessionId);
38255
+ this.assertLive();
38256
+ if (!sourceRoot || !isAbsolute7(sourceRoot)) throw failure3("UPLOAD_WORKSPACE_UNAVAILABLE");
38257
+ const root = await realpath6(sourceRoot);
38258
+ const directory = await stat6(root);
38259
+ if (!directory.isDirectory()) throw failure3("UPLOAD_WORKSPACE_UNAVAILABLE");
38260
+ const temp = join12(root, `.dsh-upload-${randomUUID5()}.part`);
38261
+ const handle = await open2(temp, constants4.O_CREAT | constants4.O_EXCL | constants4.O_RDWR | (constants4.O_NOFOLLOW ?? 0), 384);
38262
+ const upload = { ...p, received: 0, touched: Date.now(), root, sourceRoot, dev: directory.dev, ino: directory.ino, temp, handle };
38263
+ this.uploads.set(p.uploadId, upload);
38264
+ await this.validateRoot(upload);
38265
+ return { uploadId: p.uploadId, chunkBytes: WORKSPACE_UPLOAD_CHUNK };
38266
+ }
38267
+ async append(input3) {
38268
+ const p = chunkParams.parse(input3);
38269
+ const upload = this.requireUpload(p);
38270
+ const data2 = Buffer.from(p.data, "base64");
38271
+ if (!data2.length || data2.length > WORKSPACE_UPLOAD_CHUNK || data2.toString("base64") !== p.data || p.offset !== upload.received || upload.received + data2.length > upload.totalBytes) throw failure3("UPLOAD_INVALID_CHUNK");
38272
+ await this.validateRoot(upload);
38273
+ let written = 0;
38274
+ while (written < data2.length) {
38275
+ const result = await upload.handle.write(data2, written, data2.length - written, upload.received + written);
38276
+ if (!result.bytesWritten) throw failure3("UPLOAD_FAILED");
38277
+ written += result.bytesWritten;
38278
+ }
38279
+ this.assertLive();
38280
+ upload.received += data2.length;
38281
+ upload.touched = Date.now();
38282
+ return { uploadId: p.uploadId, receivedBytes: upload.received };
38283
+ }
38284
+ async commit(input3) {
38285
+ const p = scope.parse(input3);
38286
+ const prior = this.completed.get(p.uploadId);
38287
+ if (prior) {
38288
+ if (prior.sessionId !== p.sessionId) throw failure3("UPLOAD_NOT_FOUND");
38289
+ return prior.result;
38290
+ }
38291
+ const upload = this.requireUpload(p);
38292
+ if (upload.received !== upload.totalBytes) throw failure3("UPLOAD_INCOMPLETE");
38293
+ await this.validateRoot(upload);
38294
+ await upload.handle.sync();
38295
+ const original = await upload.handle.stat();
38296
+ const temp = await lstat2(upload.temp);
38297
+ if (!temp.isFile() || temp.dev !== original.dev || temp.ino !== original.ino || temp.size !== upload.totalBytes) throw failure3("UPLOAD_WORKSPACE_CHANGED");
38298
+ const extension = extname(upload.name);
38299
+ const stem = upload.name.slice(0, upload.name.length - extension.length);
38300
+ for (let suffix = 0; suffix < 1e4; suffix++) {
38301
+ await this.validateRoot(upload);
38302
+ const name2 = suffix === 0 ? upload.name : `${stem} (${suffix})${extension}`;
38303
+ try {
38304
+ await link(upload.temp, join12(upload.root, name2));
38305
+ } catch (error) {
38306
+ if (error.code === "EEXIST") continue;
38307
+ throw error;
38308
+ }
38309
+ const result = { name: name2, path: `./${name2}`, bytes: upload.totalBytes };
38310
+ this.completed.set(p.uploadId, { sessionId: p.sessionId, result, touched: Date.now() });
38311
+ if (this.completed.size > 64) this.completed.delete(this.completed.keys().next().value);
38312
+ await this.discard(p.uploadId, upload);
38313
+ return result;
38314
+ }
38315
+ throw failure3("UPLOAD_NAME_CONFLICT");
38316
+ }
38317
+ requireUpload(p) {
38318
+ const upload = this.uploads.get(p.uploadId);
38319
+ if (!upload || upload.sessionId !== p.sessionId) throw failure3("UPLOAD_NOT_FOUND");
38320
+ return upload;
38321
+ }
38322
+ assertLive() {
38323
+ if (this.closed) throw failure3("UPLOAD_CLOSED");
38324
+ }
38325
+ async sessionRoot(sessionId) {
38326
+ const signal = AbortSignal.any([this.lifetime.signal, AbortSignal.timeout(15e3)]);
38327
+ let onAbort;
38328
+ try {
38329
+ signal.throwIfAborted();
38330
+ return await Promise.race([
38331
+ this.resolveRoot(sessionId, signal),
38332
+ new Promise((_, reject) => {
38333
+ onAbort = () => reject(failure3(this.closed ? "UPLOAD_CLOSED" : "UPLOAD_TIMEOUT"));
38334
+ signal.addEventListener("abort", onAbort, { once: true });
38335
+ if (signal.aborted) onAbort();
38336
+ })
38337
+ ]);
38338
+ } finally {
38339
+ if (onAbort) signal.removeEventListener("abort", onAbort);
38340
+ }
38341
+ }
38342
+ async validateRoot(upload) {
38343
+ const current = await this.sessionRoot(upload.sessionId);
38344
+ this.assertLive();
38345
+ if (current !== upload.sourceRoot || await realpath6(current) !== upload.root || await realpath6(upload.root) !== upload.root) throw failure3("UPLOAD_WORKSPACE_CHANGED");
38346
+ const directory = await stat6(upload.root);
38347
+ if (!directory.isDirectory() || directory.dev !== upload.dev || directory.ino !== upload.ino) throw failure3("UPLOAD_WORKSPACE_CHANGED");
38348
+ }
38349
+ async discard(id5, upload) {
38350
+ this.uploads.delete(id5);
38351
+ const original = await upload.handle.stat().catch(() => void 0);
38352
+ await upload.handle.close().catch(() => void 0);
38353
+ try {
38354
+ const root = await lstat2(upload.root);
38355
+ const temp = await lstat2(upload.temp);
38356
+ if (original && root.isDirectory() && root.dev === upload.dev && root.ino === upload.ino && temp.isFile() && temp.dev === original.dev && temp.ino === original.ino) await unlink(upload.temp);
38357
+ } catch {
38358
+ }
38359
+ }
38360
+ async expire() {
38361
+ const cutoff = Date.now() - this.ttlMs;
38362
+ await Promise.all([...this.uploads].filter(([, u]) => u.touched <= cutoff).map(([id5, u]) => this.discard(id5, u)));
38363
+ for (const [id5, entry] of this.completed) if (entry.touched <= cutoff) this.completed.delete(id5);
38364
+ }
38365
+ };
38366
+ function failure3(code) {
38367
+ return new RpcError(code, "The workspace upload could not be completed.");
38368
+ }
38369
+ function validName(name2) {
38370
+ return Buffer.byteLength(name2, "utf8") <= 220 && name2 !== "." && name2 !== ".." && !/[/\\\x00-\x1f\x7f<>:"|?*]/u.test(name2) && !/[. ]$/u.test(name2) && !/^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu.test(name2) && !name2.startsWith(".dsh-upload-");
38371
+ }
38372
+
38373
+ // src/workspace-upload-authority.ts
38374
+ function workspaceUploadRootResolver(gateway, supportsHarnessSessions, codex, acp) {
38375
+ return async (sessionId, signal) => {
38376
+ signal.throwIfAborted();
38377
+ if (sessionId.startsWith("codex:")) {
38378
+ const parsed = parseCodexSessionId(sessionId);
38379
+ if (!parsed) throw unavailable2();
38380
+ return codex(parsed.threadId);
38381
+ }
38382
+ if (/^(acp|cursor|pi|opencode|claude):/.test(sessionId)) return acp(sessionId);
38383
+ if (!gateway?.supportsCarrier || !supportsHarnessSessions()) throw unavailable2();
38384
+ const result = await gateway.dispatch("session/list", { args: { _request: {} } }, signal);
38385
+ signal.throwIfAborted();
38386
+ const value = result.ok ? result.value : void 0;
38387
+ return value?.items?.find((item) => item.sessionId === sessionId)?.cwd;
38388
+ };
38389
+ }
38390
+ function unavailable2() {
38391
+ return new RpcError("UPLOAD_WORKSPACE_UNAVAILABLE", "The session workspace is unavailable.");
38392
+ }
38393
+
38394
+ // src/service.ts
38395
+ import { randomUUID as randomUUID6 } from "node:crypto";
38049
38396
 
38050
38397
  // src/connection-controller.ts
38051
38398
  var ConnectionController = class {
@@ -38276,7 +38623,11 @@ var apiMethods = /* @__PURE__ */ new Set([
38276
38623
  "agent.acp.transfer.commit",
38277
38624
  "agent.acp.transfer.read",
38278
38625
  "agent.acp.transfer.close",
38279
- "plugin.route.call"
38626
+ "plugin.route.call",
38627
+ "workspace.upload.open",
38628
+ "workspace.upload.chunk",
38629
+ "workspace.upload.commit",
38630
+ "workspace.upload.abort"
38280
38631
  ]);
38281
38632
  var HOST_CAPABILITIES = [
38282
38633
  "harness.api.v1",
@@ -38284,13 +38635,14 @@ var HOST_CAPABILITIES = [
38284
38635
  "harness.remote.v1",
38285
38636
  "harness.remote.transfer.v1",
38286
38637
  "fileviewer.read.v1",
38638
+ "workspace.upload.v1",
38287
38639
  "codex.appserver.v1",
38288
38640
  "codex.appserver.transfer.v1",
38289
38641
  "agent.acp.v1",
38290
38642
  "agent.acp.transfer.v1"
38291
38643
  ];
38292
38644
  var RpcRouter = class {
38293
- constructor(harnessApi, maxPending = 128, logger, fileViewer, harnessRemote, capabilities = () => HOST_CAPABILITIES, codex, acp, loopback, workspaceTypes, pluginRoute) {
38645
+ constructor(harnessApi, maxPending = 128, logger, fileViewer, harnessRemote, capabilities = () => HOST_CAPABILITIES, codex, acp, loopback, workspaceTypes, pluginRoute, workspaceUpload) {
38294
38646
  this.harnessApi = harnessApi;
38295
38647
  this.maxPending = maxPending;
38296
38648
  this.logger = logger;
@@ -38302,6 +38654,7 @@ var RpcRouter = class {
38302
38654
  this.loopback = loopback;
38303
38655
  this.workspaceTypes = workspaceTypes;
38304
38656
  this.pluginRoute = pluginRoute;
38657
+ this.workspaceUpload = workspaceUpload;
38305
38658
  }
38306
38659
  active = 0;
38307
38660
  async closePeerStreams() {
@@ -38310,7 +38663,8 @@ var RpcRouter = class {
38310
38663
  this.harnessApi?.closeAll(),
38311
38664
  this.harnessRemote?.closeAll(),
38312
38665
  this.codex?.closeAll(),
38313
- this.acp?.closeAll()
38666
+ this.acp?.closeAll(),
38667
+ this.workspaceUpload?.closeAll()
38314
38668
  ]);
38315
38669
  }
38316
38670
  async handle(message) {
@@ -38339,7 +38693,7 @@ var RpcRouter = class {
38339
38693
  return createRpcResponse(request.id, result);
38340
38694
  } catch (error) {
38341
38695
  const response = errorResponse2(request.id, error);
38342
- const failedEndpoint = typeof request.payload.params?.endpoint === "string" ? String(request.payload.params.endpoint) : void 0;
38696
+ const failedEndpoint = !request.payload.method.startsWith("workspace.upload.") && typeof request.payload.params?.endpoint === "string" ? String(request.payload.params.endpoint) : void 0;
38343
38697
  this.logger?.warn("host rpc failed", {
38344
38698
  method: request.payload.method,
38345
38699
  ...failedEndpoint ? { endpoint: failedEndpoint } : {},
@@ -38450,6 +38804,13 @@ var RpcRouter = class {
38450
38804
  return this.requireAcp().readTransfer(params);
38451
38805
  case "agent.acp.transfer.close":
38452
38806
  return this.requireAcp().closeTransfer(params);
38807
+ case "workspace.upload.open":
38808
+ case "workspace.upload.chunk":
38809
+ case "workspace.upload.commit":
38810
+ case "workspace.upload.abort": {
38811
+ if (!this.workspaceUpload) throw new RpcError("FEATURE_NOT_SUPPORTED", "Workspace upload is unavailable on this Host.");
38812
+ return this.workspaceUpload.call(method, params);
38813
+ }
38453
38814
  case "plugin.route.call":
38454
38815
  return this.requirePluginRoute().call(params);
38455
38816
  default:
@@ -39535,22 +39896,22 @@ function closeCode(code) {
39535
39896
  }
39536
39897
 
39537
39898
  // src/remote-directory-browser.ts
39538
- import { readdir as readdir4, stat as stat6 } from "node:fs/promises";
39899
+ import { readdir as readdir4, stat as stat7 } from "node:fs/promises";
39539
39900
  import { homedir as homedir6, platform as platform2 } from "node:os";
39540
- import { basename as basename5, dirname as dirname5, isAbsolute as isAbsolute7, parse, resolve as resolve5 } from "node:path";
39901
+ import { basename as basename5, dirname as dirname5, isAbsolute as isAbsolute8, parse, resolve as resolve5 } from "node:path";
39541
39902
  var MAX_ENTRIES = 500;
39542
39903
  async function listRemoteDirectory(path, signal) {
39543
39904
  signal?.throwIfAborted();
39544
39905
  const home = resolve5(homedir6());
39545
39906
  const target2 = path === void 0 || path.trim() === "" ? home : resolve5(path);
39546
- if (!isAbsolute7(target2)) throw new Error("The remote directory path must be absolute.");
39907
+ if (!isAbsolute8(target2)) throw new Error("The remote directory path must be absolute.");
39547
39908
  const rows = await readdir4(target2, { withFileTypes: true });
39548
39909
  const directories = [];
39549
39910
  for (const row of rows) {
39550
39911
  signal?.throwIfAborted();
39551
39912
  const child = resolve5(target2, row.name);
39552
39913
  let directory = row.isDirectory();
39553
- if (!directory && row.isSymbolicLink()) directory = await stat6(child).then((value) => value.isDirectory()).catch(() => false);
39914
+ if (!directory && row.isSymbolicLink()) directory = await stat7(child).then((value) => value.isDirectory()).catch(() => false);
39554
39915
  if (!directory) continue;
39555
39916
  directories.push({ name: row.name, path: child, hidden: platform2() !== "win32" && row.name.startsWith(".") });
39556
39917
  }
@@ -40762,7 +41123,7 @@ var HarnessRemoteBridge = class {
40762
41123
  }
40763
41124
  async pump(streamId, source, signal) {
40764
41125
  let reason = "completed";
40765
- let failure3;
41126
+ let failure4;
40766
41127
  try {
40767
41128
  for await (const value of source) {
40768
41129
  if (signal.aborted) break;
@@ -40779,13 +41140,13 @@ var HarnessRemoteBridge = class {
40779
41140
  if (signal.aborted) reason = "cancelled";
40780
41141
  } catch (error) {
40781
41142
  reason = signal.aborted ? "cancelled" : "failed";
40782
- if (!signal.aborted) failure3 = this.gateway.failure(error);
41143
+ if (!signal.aborted) failure4 = this.gateway.failure(error);
40783
41144
  } finally {
40784
41145
  this.streams.delete(streamId);
40785
41146
  await this.publish("harness.remote.stream.closed", {
40786
41147
  streamId,
40787
41148
  reason,
40788
- ...failure3 === void 0 ? {} : { failure: failure3 }
41149
+ ...failure4 === void 0 ? {} : { failure: failure4 }
40789
41150
  }).catch(() => void 0);
40790
41151
  }
40791
41152
  }
@@ -40994,7 +41355,13 @@ var HostPluginRuntime = class {
40994
41355
  // Handles and their lifetime belong to this connection; only policy is shared.
40995
41356
  this.createLoopbackHost(),
40996
41357
  () => this.hostWorkspaceTypes(),
40997
- pluginRoute
41358
+ pluginRoute,
41359
+ new WorkspaceUploadHost(workspaceUploadRootResolver(
41360
+ peerGateway,
41361
+ () => harnessSessionGeneration(this.harnessVersion) === "v3",
41362
+ (threadId) => this.codex.resolveThreadWorkspace(context.connectionId, threadId),
41363
+ (sessionId) => this.acp.resolveUploadWorkspace(context.connectionId, sessionId)
41364
+ ))
40998
41365
  );
40999
41366
  }, this.logger);
41000
41367
  if (config.serverUrl !== void 0) {
@@ -41295,7 +41662,7 @@ var HostPluginRuntime = class {
41295
41662
  let reportedVersion;
41296
41663
  let errorCode6;
41297
41664
  try {
41298
- const response = await this.apiProxy?.host.describe({ rpcId: randomUUID5(), payload: {} });
41665
+ const response = await this.apiProxy?.host.describe({ rpcId: randomUUID6(), payload: {} });
41299
41666
  if (response === void 0) throw new Error("ApiProxy is unavailable");
41300
41667
  if (!response.result.ok) {
41301
41668
  errorCode6 = response.result.error.code;
@@ -41311,7 +41678,7 @@ var HostPluginRuntime = class {
41311
41678
  return void 0;
41312
41679
  }
41313
41680
  hostCapabilities() {
41314
- const capabilities = [];
41681
+ const capabilities = ["workspace.upload.v1"];
41315
41682
  if (this.loopbackPorts.length > 0) capabilities.push("loopback.http-ws.v1");
41316
41683
  if (this.localGateway?.supportsCarrier === true) {
41317
41684
  capabilities.push(
@@ -41468,9 +41835,9 @@ function withSignal(pending, signal) {
41468
41835
  }
41469
41836
 
41470
41837
  // src/cli.ts
41471
- import { stat as stat7 } from "node:fs/promises";
41838
+ import { stat as stat8 } from "node:fs/promises";
41472
41839
  import { hostname as hostname3 } from "node:os";
41473
- import { join as join12 } from "node:path";
41840
+ import { join as join13 } from "node:path";
41474
41841
  var QR_POLL_INTERVAL_MS = 2e3;
41475
41842
  var TERMINAL_QR_MARGIN = 4;
41476
41843
  async function runCli(args = process.argv.slice(2), dependencies = {}) {
@@ -41561,7 +41928,7 @@ async function status(args, runtime) {
41561
41928
  `Server: ${serverUrl}`,
41562
41929
  "Host control: enabled (dsh-TUI default)"
41563
41930
  ];
41564
- if (!await exists3(join12(directory, "device.json"))) {
41931
+ if (!await exists3(join13(directory, "device.json"))) {
41565
41932
  lines.push("Device: not initialized", "Authorization: logged out", "Credential: unavailable");
41566
41933
  write(runtime.stdout, `${lines.join("\n")}
41567
41934
 
@@ -41606,7 +41973,7 @@ async function logout(args, runtime) {
41606
41973
  const serverUrl = selectedServer();
41607
41974
  const root = new IdentityStore({ env: runtime.env }).directory;
41608
41975
  const directory = serverStorageDirectory(root, serverUrl, "host");
41609
- if (!await exists3(join12(directory, "device.json"))) {
41976
+ if (!await exists3(join13(directory, "device.json"))) {
41610
41977
  await new ServerCredentialStore(directory).clear();
41611
41978
  write(runtime.stdout, "This Host is already logged out.\n");
41612
41979
  return 0;
@@ -41732,7 +42099,7 @@ function terminalLink(url, target2) {
41732
42099
  }
41733
42100
  async function exists3(path) {
41734
42101
  try {
41735
- await stat7(path);
42102
+ await stat8(path);
41736
42103
  return true;
41737
42104
  } catch (error) {
41738
42105
  if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
@@ -42451,18 +42818,18 @@ async function createSettingsBinding(ctx, settings, readConfig, entryId) {
42451
42818
  }
42452
42819
  };
42453
42820
  }
42454
- const scope = settings.register(pluginSettingsNamespace, Config, {
42821
+ const scope2 = settings.register(pluginSettingsNamespace, Config, {
42455
42822
  base: readConfig(),
42456
42823
  applies: "restart",
42457
42824
  validate: (value) => {
42458
42825
  resolveConfig(value);
42459
42826
  }
42460
42827
  });
42461
- reportSettingsMigration(ctx, await migrateLegacySettings(settings, scope));
42828
+ reportSettingsMigration(ctx, await migrateLegacySettings(settings, scope2));
42462
42829
  return {
42463
- get: () => scope.get(),
42830
+ get: () => scope2.get(),
42464
42831
  replace: async (section) => {
42465
- await scope.replace(section);
42832
+ await scope2.replace(section);
42466
42833
  }
42467
42834
  };
42468
42835
  }