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.
- package/dist/acp/adapters/opencode-native-api.d.ts +4 -2
- package/dist/acp/adapters/opencode-native-api.d.ts.map +1 -1
- package/dist/acp/adapters/opencode-native-api.js +29 -3
- package/dist/acp/adapters/opencode-native-api.js.map +1 -1
- package/dist/acp/adapters/opencode-process.d.ts +1 -0
- package/dist/acp/adapters/opencode-process.d.ts.map +1 -1
- package/dist/acp/adapters/opencode-process.js +10 -5
- package/dist/acp/adapters/opencode-process.js.map +1 -1
- package/dist/acp/gateway.d.ts +2 -0
- package/dist/acp/gateway.d.ts.map +1 -1
- package/dist/acp/gateway.js +12 -0
- package/dist/acp/gateway.js.map +1 -1
- package/dist/acp/virtual-contract.d.ts +1 -0
- package/dist/acp/virtual-contract.d.ts.map +1 -1
- package/dist/acp/virtual-contract.js.map +1 -1
- package/dist/acp/virtual-harness.d.ts +1 -2
- package/dist/acp/virtual-harness.d.ts.map +1 -1
- package/dist/acp/virtual-harness.js +15 -23
- package/dist/acp/virtual-harness.js.map +1 -1
- package/dist/acp/virtual-live.d.ts +1 -0
- package/dist/acp/virtual-live.d.ts.map +1 -1
- package/dist/acp/virtual-live.js +6 -0
- package/dist/acp/virtual-live.js.map +1 -1
- package/dist/acp/virtual-titles.d.ts +17 -0
- package/dist/acp/virtual-titles.d.ts.map +1 -0
- package/dist/acp/virtual-titles.js +99 -0
- package/dist/acp/virtual-titles.js.map +1 -0
- package/dist/index.js +457 -90
- package/dist/index.js.map +4 -4
- package/dist/rpc-router.d.ts +4 -2
- package/dist/rpc-router.d.ts.map +1 -1
- package/dist/rpc-router.js +19 -2
- package/dist/rpc-router.js.map +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +4 -2
- package/dist/service.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workspace-upload-authority.d.ts +4 -0
- package/dist/workspace-upload-authority.d.ts.map +1 -0
- package/dist/workspace-upload-authority.js +25 -0
- package/dist/workspace-upload-authority.js.map +1 -0
- package/dist/workspace-upload.d.ts +27 -0
- package/dist/workspace-upload.d.ts.map +1 -0
- package/dist/workspace-upload.js +226 -0
- package/dist/workspace-upload.js.map +1 -0
- package/dsh-plugin.json +1 -1
- 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.
|
|
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
|
-
...
|
|
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
|
-
|
|
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(
|
|
14107
|
-
return
|
|
14197
|
+
function isLocalNetworkScope(scope2) {
|
|
14198
|
+
return scope2 === "private" || scope2 === "link-local" || scope2 === "loopback";
|
|
14108
14199
|
}
|
|
14109
|
-
function isExplicitlyNonLanScope(
|
|
14110
|
-
return
|
|
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
|
|
27050
|
+
const failure4 = data2.failure;
|
|
26960
27051
|
queue.fail(remoteFailure({
|
|
26961
|
-
code: typeof
|
|
26962
|
-
message: typeof
|
|
26963
|
-
details: isRecord9(
|
|
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(
|
|
26981
|
-
return Object.assign(new Error(
|
|
27071
|
+
function remoteFailure(failure4) {
|
|
27072
|
+
return Object.assign(new Error(failure4.message), {
|
|
26982
27073
|
isDSHRemoteError: true,
|
|
26983
|
-
code:
|
|
26984
|
-
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.
|
|
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
|
|
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:
|
|
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
|
|
28119
|
-
const rc1 = usesRc1Arity(
|
|
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(
|
|
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(
|
|
28208
|
-
return
|
|
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
|
|
28225
|
-
if (
|
|
28226
|
-
return usesRc1Arity(
|
|
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
|
|
28370
|
-
if (!
|
|
28460
|
+
const stat9 = statSync(candidate);
|
|
28461
|
+
if (!stat9.isFile()) return false;
|
|
28371
28462
|
if (process.platform === "win32") return true;
|
|
28372
|
-
return (
|
|
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
|
|
30190
|
-
return join4(root, "servers",
|
|
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(),
|
|
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 =
|
|
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
|
|
34566
|
+
let stat9;
|
|
34476
34567
|
try {
|
|
34477
|
-
|
|
34568
|
+
stat9 = await fs2.stat(this.transcriptPath);
|
|
34478
34569
|
} catch {
|
|
34479
34570
|
return;
|
|
34480
34571
|
}
|
|
34481
|
-
if (
|
|
34572
|
+
if (stat9.size <= this.offset) return;
|
|
34482
34573
|
const handle = await fs2.open(this.transcriptPath, "r");
|
|
34483
34574
|
try {
|
|
34484
|
-
const bytesToRead =
|
|
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
|
|
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(
|
|
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:
|
|
35918
|
-
const { promisify:
|
|
35919
|
-
const
|
|
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
|
|
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
|
|
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() :
|
|
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
|
|
37151
|
-
throw
|
|
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/
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
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
|
|
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)
|
|
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
|
-
...
|
|
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:
|
|
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
|
|
41838
|
+
import { stat as stat8 } from "node:fs/promises";
|
|
41472
41839
|
import { hostname as hostname3 } from "node:os";
|
|
41473
|
-
import { join as
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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,
|
|
42828
|
+
reportSettingsMigration(ctx, await migrateLegacySettings(settings, scope2));
|
|
42462
42829
|
return {
|
|
42463
|
-
get: () =>
|
|
42830
|
+
get: () => scope2.get(),
|
|
42464
42831
|
replace: async (section) => {
|
|
42465
|
-
await
|
|
42832
|
+
await scope2.replace(section);
|
|
42466
42833
|
}
|
|
42467
42834
|
};
|
|
42468
42835
|
}
|