freestyle-sync 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{freestyle-sync.config.ts → dist/freestyle-sync.config.js} +2 -3
- package/dist/main.js +1319 -0
- package/{plugins/agent-claude/src/index.ts → dist/plugins/agent-claude/src/index.js} +32 -29
- package/{plugins/agent-codex/src/index.ts → dist/plugins/agent-codex/src/index.js} +13 -14
- package/{plugins/agent-copilot/src/index.ts → dist/plugins/agent-copilot/src/index.js} +136 -159
- package/{plugins/auth-aws/src/index.ts → dist/plugins/auth-aws/src/index.js} +14 -8
- package/{plugins/auth-azure/src/index.ts → dist/plugins/auth-azure/src/index.js} +14 -8
- package/dist/plugins/auth-context.js +213 -0
- package/{plugins/auth-docker/src/index.ts → dist/plugins/auth-docker/src/index.js} +14 -8
- package/{plugins/auth-env/src/index.ts → dist/plugins/auth-env/src/index.js} +11 -11
- package/{plugins/auth-gcloud/src/index.ts → dist/plugins/auth-gcloud/src/index.js} +14 -8
- package/{plugins/auth-git/src/index.ts → dist/plugins/auth-git/src/index.js} +24 -17
- package/{plugins/auth-github-cli/src/index.ts → dist/plugins/auth-github-cli/src/index.js} +20 -14
- package/{plugins/auth-npm/src/index.ts → dist/plugins/auth-npm/src/index.js} +19 -13
- package/{plugins/auth-ssh/src/index.ts → dist/plugins/auth-ssh/src/index.js} +19 -13
- package/dist/plugins/auth-yarn/src/index.js +24 -0
- package/{plugins/node-npm/src/index.ts → dist/plugins/node-npm/src/index.js} +6 -8
- package/dist/plugins/npm-native-deps.js +307 -0
- package/{plugins/shell-history/src/index.ts → dist/plugins/shell-history/src/index.js} +13 -12
- package/{plugins/vscode/src/index.ts → dist/plugins/vscode/src/index.js} +38 -40
- package/{src/main.ts → dist/src/main.js} +285 -464
- package/dist/src/plugin-api.js +6 -0
- package/dist/src/pushvm.config.js +36 -0
- package/package.json +8 -4
- package/PUBLISHING.md +0 -3
- package/plugins/agent-claude/package.json +0 -8
- package/plugins/agent-codex/package.json +0 -8
- package/plugins/agent-copilot/package.json +0 -8
- package/plugins/auth-aws/package.json +0 -8
- package/plugins/auth-azure/package.json +0 -8
- package/plugins/auth-docker/package.json +0 -8
- package/plugins/auth-env/package.json +0 -8
- package/plugins/auth-gcloud/package.json +0 -8
- package/plugins/auth-git/package.json +0 -8
- package/plugins/auth-github-cli/package.json +0 -8
- package/plugins/auth-npm/package.json +0 -8
- package/plugins/auth-ssh/package.json +0 -8
- package/plugins/auth-yarn/package.json +0 -8
- package/plugins/auth-yarn/src/index.ts +0 -19
- package/plugins/node-npm/package.json +0 -8
- package/plugins/shell-history/package.json +0 -8
- package/plugins/vscode/package.json +0 -8
- package/src/plugin-api.ts +0 -110
- package/tsconfig.json +0 -18
|
@@ -4,18 +4,15 @@ import path from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { execFile } from "node:child_process";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
|
-
import { definePlugin
|
|
8
|
-
|
|
7
|
+
import { definePlugin } from "../../../src/plugin-api.js";
|
|
9
8
|
const execFileAsync = promisify(execFile);
|
|
10
|
-
|
|
11
|
-
type CopyResult = { status: "copied"; count: number } | { status: "pending" | "skipped"; count: 0; reason?: string };
|
|
12
|
-
|
|
13
9
|
export function copilotAgentPlugin() {
|
|
14
10
|
return definePlugin({
|
|
15
11
|
name: "@freestyle-sync/agent-copilot",
|
|
16
12
|
async discoverContextCandidates({ options }) {
|
|
17
|
-
if (!options.includeAgentContext)
|
|
18
|
-
|
|
13
|
+
if (!options.includeAgentContext)
|
|
14
|
+
return [];
|
|
15
|
+
const candidates = [];
|
|
19
16
|
for (const codeRoot of codeUserRoots(homedir())) {
|
|
20
17
|
await addCopilotCandidates(candidates, codeRoot, options.projectRoot, options.includeAllCopilotWorkspaces);
|
|
21
18
|
}
|
|
@@ -29,31 +26,31 @@ export function copilotAgentPlugin() {
|
|
|
29
26
|
if (result.status === "copied") {
|
|
30
27
|
return [`Copilot sidebar state: copied to ${result.count} VS Code Remote workspace${result.count === 1 ? "" : "s"}`];
|
|
31
28
|
}
|
|
32
|
-
if (result.status === "pending")
|
|
29
|
+
if (result.status === "pending")
|
|
30
|
+
return [`Copilot sidebar state: ${result.reason}`];
|
|
33
31
|
return [];
|
|
34
32
|
},
|
|
35
33
|
async beforeOpenRemoteEditor({ vm, vmId, scheme, remoteWorkspaceUri, contextCandidates, utils }) {
|
|
36
|
-
if (!isSupportedEditorScheme(scheme))
|
|
34
|
+
if (!isSupportedEditorScheme(scheme))
|
|
35
|
+
return [];
|
|
37
36
|
const result = await preseedLocalCopilotForRemoteUri(vm, vmId, contextCandidates, scheme, remoteWorkspaceUri, utils);
|
|
38
|
-
if (result.status === "copied")
|
|
37
|
+
if (result.status === "copied")
|
|
38
|
+
return [`Prepared Copilot sidebar state for ${scheme === "cursor" ? "Cursor" : "VS Code"}.`];
|
|
39
39
|
return [];
|
|
40
40
|
},
|
|
41
41
|
async afterOpenRemoteEditor({ vm, vmId, scheme, remoteWorkspaceUri, contextCandidates, utils }) {
|
|
42
|
-
if (!isSupportedEditorScheme(scheme))
|
|
42
|
+
if (!isSupportedEditorScheme(scheme))
|
|
43
|
+
return [];
|
|
43
44
|
const result = await syncLocalCopilotForRemoteUri(vm, vmId, contextCandidates, remoteWorkspaceUri, utils);
|
|
44
|
-
if (result.status === "copied")
|
|
45
|
-
|
|
45
|
+
if (result.status === "copied")
|
|
46
|
+
return [`Copied Copilot sidebar state to opened ${scheme === "cursor" ? "Cursor" : "VS Code"} workspace.`];
|
|
47
|
+
if (result.status === "pending")
|
|
48
|
+
return ["Copilot sidebar state: opened the editor, but the workspace storage bucket was not created yet. Reload the window after it finishes connecting, then rerun vmpush if needed."];
|
|
46
49
|
return [];
|
|
47
50
|
},
|
|
48
51
|
});
|
|
49
52
|
}
|
|
50
|
-
|
|
51
|
-
async function installAgentCli(
|
|
52
|
-
vm: RemoteVm,
|
|
53
|
-
checkedExec: PushvmPluginUtils["checkedExec"],
|
|
54
|
-
binary: string,
|
|
55
|
-
packageName: string,
|
|
56
|
-
) {
|
|
53
|
+
async function installAgentCli(vm, checkedExec, binary, packageName) {
|
|
57
54
|
await checkedExec(vm, `
|
|
58
55
|
set -e
|
|
59
56
|
export HOME="\${HOME:-/root}"
|
|
@@ -94,16 +91,14 @@ fi
|
|
|
94
91
|
test -x /usr/local/bin/${binary}
|
|
95
92
|
`, 600000);
|
|
96
93
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
94
|
+
async function addCopilotCandidates(candidates, codeUserRoot, projectRoot, includeAllWorkspaces) {
|
|
95
|
+
if (!(await exists(codeUserRoot)))
|
|
96
|
+
return;
|
|
101
97
|
const remoteUserRoot = remoteCodeRoot(codeUserRoot);
|
|
102
98
|
const globalStorage = path.join(codeUserRoot, "globalStorage", "github.copilot-chat");
|
|
103
99
|
if (await exists(globalStorage)) {
|
|
104
100
|
addCandidate(candidates, globalStorage, `${remoteUserRoot}/globalStorage/github.copilot-chat`, "Copilot global chat state", "context:copilot-global");
|
|
105
101
|
}
|
|
106
|
-
|
|
107
102
|
const workspaceStorage = path.join(codeUserRoot, "workspaceStorage");
|
|
108
103
|
const workspaceDirs = await listDirectories(workspaceStorage);
|
|
109
104
|
for (const workspaceDir of workspaceDirs) {
|
|
@@ -117,13 +112,11 @@ async function addCopilotCandidates(candidates: ContextCandidate[], codeUserRoot
|
|
|
117
112
|
}
|
|
118
113
|
}
|
|
119
114
|
}
|
|
120
|
-
|
|
121
|
-
async function syncLocalCopilotForRemoteWorkspaces(vm: RemoteVm, vmId: string, remoteProjectDir: string, contextCandidates: ContextCandidate[], utils: PushvmPluginUtils): Promise<CopyResult> {
|
|
115
|
+
async function syncLocalCopilotForRemoteWorkspaces(vm, vmId, remoteProjectDir, contextCandidates, utils) {
|
|
122
116
|
const sourceCopilotDir = selectedCurrentCopilotSource(contextCandidates);
|
|
123
117
|
if (!sourceCopilotDir || !(await exists(sourceCopilotDir))) {
|
|
124
118
|
return { status: "skipped", count: 0, reason: "Copilot current workspace state is not selected." };
|
|
125
119
|
}
|
|
126
|
-
|
|
127
120
|
const destinations = await findRemoteWorkspaceCopilotDestinations(vmId, remoteProjectDir);
|
|
128
121
|
if (destinations.length === 0) {
|
|
129
122
|
return {
|
|
@@ -132,19 +125,17 @@ async function syncLocalCopilotForRemoteWorkspaces(vm: RemoteVm, vmId: string, r
|
|
|
132
125
|
reason: "open this VM folder in VS Code once, then rerun vmpush to copy sidebar history.",
|
|
133
126
|
};
|
|
134
127
|
}
|
|
135
|
-
|
|
136
128
|
await copyCopilotState(sourceCopilotDir, destinations);
|
|
137
129
|
await copyCopilotWorkspaceStateKeys(sourceCopilotDir, destinations);
|
|
138
130
|
await copyCopilotStateToRemoteServer(vm, vmId, sourceCopilotDir, destinations, utils);
|
|
139
131
|
return { status: "copied", count: destinations.length };
|
|
140
132
|
}
|
|
141
|
-
|
|
142
|
-
async function syncLocalCopilotForRemoteUri(vm: RemoteVm, vmId: string, contextCandidates: ContextCandidate[], remoteWorkspaceUri: string, utils: PushvmPluginUtils): Promise<CopyResult> {
|
|
133
|
+
async function syncLocalCopilotForRemoteUri(vm, vmId, contextCandidates, remoteWorkspaceUri, utils) {
|
|
143
134
|
const sourceCopilotDir = selectedCurrentCopilotSource(contextCandidates);
|
|
144
|
-
if (!sourceCopilotDir || !(await exists(sourceCopilotDir)))
|
|
145
|
-
|
|
135
|
+
if (!sourceCopilotDir || !(await exists(sourceCopilotDir)))
|
|
136
|
+
return { status: "skipped", count: 0 };
|
|
146
137
|
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
147
|
-
const destinations
|
|
138
|
+
const destinations = [];
|
|
148
139
|
for (const codeRoot of codeUserRoots(homedir())) {
|
|
149
140
|
const workspaceStorage = path.join(codeRoot, "workspaceStorage");
|
|
150
141
|
const workspaceDirs = await listDirectories(workspaceStorage);
|
|
@@ -154,26 +145,22 @@ async function syncLocalCopilotForRemoteUri(vm: RemoteVm, vmId: string, contextC
|
|
|
154
145
|
}
|
|
155
146
|
}
|
|
156
147
|
}
|
|
157
|
-
|
|
158
148
|
if (destinations.length > 0) {
|
|
159
149
|
await copyCopilotState(sourceCopilotDir, destinations);
|
|
160
150
|
await copyCopilotWorkspaceStateKeys(sourceCopilotDir, destinations);
|
|
161
151
|
await copyCopilotStateToRemoteServer(vm, vmId, sourceCopilotDir, destinations, utils);
|
|
162
152
|
return { status: "copied", count: destinations.length };
|
|
163
153
|
}
|
|
164
|
-
|
|
165
154
|
await utils.delay(500);
|
|
166
155
|
}
|
|
167
|
-
|
|
168
156
|
return { status: "pending", count: 0 };
|
|
169
157
|
}
|
|
170
|
-
|
|
171
|
-
async function preseedLocalCopilotForRemoteUri(vm: RemoteVm, vmId: string, contextCandidates: ContextCandidate[], scheme: string, remoteWorkspaceUri: string, utils: PushvmPluginUtils): Promise<CopyResult> {
|
|
158
|
+
async function preseedLocalCopilotForRemoteUri(vm, vmId, contextCandidates, scheme, remoteWorkspaceUri, utils) {
|
|
172
159
|
const sourceCopilotDir = selectedCurrentCopilotSource(contextCandidates);
|
|
173
|
-
if (!sourceCopilotDir || !(await exists(sourceCopilotDir)))
|
|
174
|
-
|
|
160
|
+
if (!sourceCopilotDir || !(await exists(sourceCopilotDir)))
|
|
161
|
+
return { status: "skipped", count: 0 };
|
|
175
162
|
const workspaceId = utils.md5(remoteWorkspaceUri);
|
|
176
|
-
const destinations
|
|
163
|
+
const destinations = [];
|
|
177
164
|
for (const codeRoot of codeUserRootsForScheme(homedir(), scheme)) {
|
|
178
165
|
const workspaceDir = path.join(codeRoot, "workspaceStorage", workspaceId);
|
|
179
166
|
await mkdir(workspaceDir, { recursive: true });
|
|
@@ -181,19 +168,16 @@ async function preseedLocalCopilotForRemoteUri(vm: RemoteVm, vmId: string, conte
|
|
|
181
168
|
await ensureWorkspaceStateDb(path.join(workspaceDir, "state.vscdb"));
|
|
182
169
|
destinations.push(path.join(workspaceDir, "GitHub.copilot-chat"));
|
|
183
170
|
}
|
|
184
|
-
|
|
185
171
|
await copyCopilotState(sourceCopilotDir, destinations);
|
|
186
172
|
await copyCopilotWorkspaceStateKeys(sourceCopilotDir, destinations);
|
|
187
173
|
await copyCopilotStateToRemoteServer(vm, vmId, sourceCopilotDir, destinations, utils);
|
|
188
174
|
return { status: "copied", count: destinations.length };
|
|
189
175
|
}
|
|
190
|
-
|
|
191
|
-
function selectedCurrentCopilotSource(contextCandidates: ContextCandidate[]) {
|
|
176
|
+
function selectedCurrentCopilotSource(contextCandidates) {
|
|
192
177
|
return contextCandidates.find((candidate) => candidate.preferenceKey === "context:copilot-current-workspace")?.source;
|
|
193
178
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const destinations: string[] = [];
|
|
179
|
+
async function findRemoteWorkspaceCopilotDestinations(vmId, remoteProjectDir) {
|
|
180
|
+
const destinations = [];
|
|
197
181
|
for (const codeRoot of codeUserRoots(homedir())) {
|
|
198
182
|
const workspaceStorage = path.join(codeRoot, "workspaceStorage");
|
|
199
183
|
const workspaceDirs = await listDirectories(workspaceStorage);
|
|
@@ -205,8 +189,7 @@ async function findRemoteWorkspaceCopilotDestinations(vmId: string, remoteProjec
|
|
|
205
189
|
}
|
|
206
190
|
return destinations;
|
|
207
191
|
}
|
|
208
|
-
|
|
209
|
-
async function copyCopilotState(sourceCopilotDir: string, destinations: string[]) {
|
|
192
|
+
async function copyCopilotState(sourceCopilotDir, destinations) {
|
|
210
193
|
for (const destination of destinations) {
|
|
211
194
|
await rm(destination, { recursive: true, force: true });
|
|
212
195
|
await mkdir(path.dirname(destination), { recursive: true });
|
|
@@ -215,8 +198,7 @@ async function copyCopilotState(sourceCopilotDir: string, destinations: string[]
|
|
|
215
198
|
await copyCoreChatState(sourceCopilotDir, destination);
|
|
216
199
|
}
|
|
217
200
|
}
|
|
218
|
-
|
|
219
|
-
async function copyCoreChatState(sourceCopilotDir: string, destinationCopilotDir: string) {
|
|
201
|
+
async function copyCoreChatState(sourceCopilotDir, destinationCopilotDir) {
|
|
220
202
|
const sourceWorkspaceDir = path.dirname(sourceCopilotDir);
|
|
221
203
|
const destinationWorkspaceDir = path.dirname(destinationCopilotDir);
|
|
222
204
|
const sessionIds = await copiedCopilotSessionIds(destinationCopilotDir);
|
|
@@ -228,7 +210,6 @@ async function copyCoreChatState(sourceCopilotDir: string, destinationCopilotDir
|
|
|
228
210
|
await cp(sourceChatSession, destinationChatSession, { force: true });
|
|
229
211
|
await trimTranscriptToCompletedTurn(destinationChatSession);
|
|
230
212
|
}
|
|
231
|
-
|
|
232
213
|
const sourceEditingSession = path.join(sourceWorkspaceDir, "chatEditingSessions", sessionId);
|
|
233
214
|
if (await exists(sourceEditingSession)) {
|
|
234
215
|
const destinationEditingSession = path.join(destinationWorkspaceDir, "chatEditingSessions", sessionId);
|
|
@@ -238,29 +219,29 @@ async function copyCoreChatState(sourceCopilotDir: string, destinationCopilotDir
|
|
|
238
219
|
}
|
|
239
220
|
}
|
|
240
221
|
}
|
|
241
|
-
|
|
242
|
-
async function copiedCopilotSessionIds(copilotDir: string) {
|
|
222
|
+
async function copiedCopilotSessionIds(copilotDir) {
|
|
243
223
|
const transcripts = await listFiles(path.join(copilotDir, "transcripts"));
|
|
244
224
|
return transcripts.filter((transcript) => transcript.endsWith(".jsonl")).map((transcript) => path.basename(transcript, ".jsonl"));
|
|
245
225
|
}
|
|
246
|
-
|
|
247
|
-
async function sanitizeCopilotTranscripts(copilotDir: string) {
|
|
226
|
+
async function sanitizeCopilotTranscripts(copilotDir) {
|
|
248
227
|
const entries = await listFiles(path.join(copilotDir, "transcripts"));
|
|
249
228
|
for (const transcript of entries.filter((entry) => entry.endsWith(".jsonl"))) {
|
|
250
229
|
await trimTranscriptToCompletedTurn(transcript);
|
|
251
230
|
}
|
|
252
231
|
}
|
|
253
|
-
|
|
254
|
-
async function trimTranscriptToCompletedTurn(transcriptPath: string) {
|
|
232
|
+
async function trimTranscriptToCompletedTurn(transcriptPath) {
|
|
255
233
|
const content = await readFile(transcriptPath, "utf8").catch(() => undefined);
|
|
256
|
-
if (!content)
|
|
234
|
+
if (!content)
|
|
235
|
+
return;
|
|
257
236
|
const lines = content.trimEnd().split("\n");
|
|
258
237
|
let lastCompletedLine = -1;
|
|
259
238
|
for (let index = 0; index < lines.length; index += 1) {
|
|
260
239
|
try {
|
|
261
|
-
const event = JSON.parse(lines[index])
|
|
262
|
-
if (event.type === "assistant.turn_end")
|
|
263
|
-
|
|
240
|
+
const event = JSON.parse(lines[index]);
|
|
241
|
+
if (event.type === "assistant.turn_end")
|
|
242
|
+
lastCompletedLine = index;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
264
245
|
break;
|
|
265
246
|
}
|
|
266
247
|
}
|
|
@@ -268,14 +249,14 @@ async function trimTranscriptToCompletedTurn(transcriptPath: string) {
|
|
|
268
249
|
await writeFile(transcriptPath, `${lines.slice(0, lastCompletedLine + 1).join("\n")}\n`, "utf8");
|
|
269
250
|
}
|
|
270
251
|
}
|
|
271
|
-
|
|
272
|
-
async function copyCopilotWorkspaceStateKeys(sourceCopilotDir: string, destinations: string[]) {
|
|
252
|
+
async function copyCopilotWorkspaceStateKeys(sourceCopilotDir, destinations) {
|
|
273
253
|
const sourceStateDb = path.join(path.dirname(sourceCopilotDir), "state.vscdb");
|
|
274
|
-
if (!(await exists(sourceStateDb)))
|
|
275
|
-
|
|
254
|
+
if (!(await exists(sourceStateDb)))
|
|
255
|
+
return;
|
|
276
256
|
for (const destination of destinations) {
|
|
277
257
|
const destinationStateDb = path.join(path.dirname(destination), "state.vscdb");
|
|
278
|
-
if (!(await exists(destinationStateDb)))
|
|
258
|
+
if (!(await exists(destinationStateDb)))
|
|
259
|
+
continue;
|
|
279
260
|
await mergeWorkspaceStateKeys(sourceStateDb, destinationStateDb).catch((error) => {
|
|
280
261
|
console.warn(`Copilot sidebar state database merge skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
281
262
|
});
|
|
@@ -284,8 +265,7 @@ async function copyCopilotWorkspaceStateKeys(sourceCopilotDir: string, destinati
|
|
|
284
265
|
});
|
|
285
266
|
}
|
|
286
267
|
}
|
|
287
|
-
|
|
288
|
-
async function mergeWorkspaceStateKeys(sourceStateDb: string, destinationStateDb: string) {
|
|
268
|
+
async function mergeWorkspaceStateKeys(sourceStateDb, destinationStateDb) {
|
|
289
269
|
await execFileAsync("sqlite3", [
|
|
290
270
|
destinationStateDb,
|
|
291
271
|
`PRAGMA busy_timeout=5000;
|
|
@@ -302,17 +282,16 @@ WHERE key IN (
|
|
|
302
282
|
DETACH DATABASE source_state;`,
|
|
303
283
|
]);
|
|
304
284
|
}
|
|
305
|
-
|
|
306
|
-
async function normalizeCopiedChatIndex(copilotDir: string) {
|
|
285
|
+
async function normalizeCopiedChatIndex(copilotDir) {
|
|
307
286
|
const stateDb = path.join(path.dirname(copilotDir), "state.vscdb");
|
|
308
|
-
if (!(await exists(stateDb)))
|
|
309
|
-
|
|
287
|
+
if (!(await exists(stateDb)))
|
|
288
|
+
return;
|
|
310
289
|
const { stdout } = await execFileAsync("sqlite3", [stateDb, "SELECT value FROM ItemTable WHERE key='chat.ChatSessionStore.index';"]);
|
|
311
|
-
if (!stdout.trim())
|
|
312
|
-
|
|
313
|
-
const index = JSON.parse(stdout)
|
|
314
|
-
if (!index.entries)
|
|
315
|
-
|
|
290
|
+
if (!stdout.trim())
|
|
291
|
+
return;
|
|
292
|
+
const index = JSON.parse(stdout);
|
|
293
|
+
if (!index.entries)
|
|
294
|
+
return;
|
|
316
295
|
for (const sessionId of Object.keys(index.entries)) {
|
|
317
296
|
const transcriptPath = path.join(copilotDir, "transcripts", `${sessionId}.jsonl`);
|
|
318
297
|
const lastCompletedTimestamp = await lastCompletedTurnTimestamp(transcriptPath);
|
|
@@ -324,36 +303,36 @@ async function normalizeCopiedChatIndex(copilotDir: string) {
|
|
|
324
303
|
entry.lastResponseState = 1;
|
|
325
304
|
entry.hasPendingEdits = false;
|
|
326
305
|
entry.lastMessageDate = lastCompletedTimestamp;
|
|
327
|
-
const timing = typeof entry.timing === "object" && entry.timing !== null ? entry.timing
|
|
306
|
+
const timing = typeof entry.timing === "object" && entry.timing !== null ? entry.timing : {};
|
|
328
307
|
timing.lastRequestEnded = lastCompletedTimestamp;
|
|
329
308
|
entry.timing = timing;
|
|
330
309
|
}
|
|
331
|
-
|
|
332
310
|
await execFileAsync("sqlite3", [
|
|
333
311
|
stateDb,
|
|
334
312
|
`INSERT OR REPLACE INTO ItemTable(key, value) VALUES ('chat.ChatSessionStore.index', ${sqliteLiteral(JSON.stringify(index))});`,
|
|
335
313
|
]);
|
|
336
314
|
}
|
|
337
|
-
|
|
338
|
-
async function lastCompletedTurnTimestamp(transcriptPath: string) {
|
|
315
|
+
async function lastCompletedTurnTimestamp(transcriptPath) {
|
|
339
316
|
const content = await readFile(transcriptPath, "utf8").catch(() => undefined);
|
|
340
|
-
if (!content)
|
|
317
|
+
if (!content)
|
|
318
|
+
return undefined;
|
|
341
319
|
const lines = content.trimEnd().split("\n");
|
|
342
320
|
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
343
321
|
try {
|
|
344
|
-
const event = JSON.parse(lines[index])
|
|
345
|
-
if (event.type === "assistant.turn_end" && event.timestamp)
|
|
346
|
-
|
|
322
|
+
const event = JSON.parse(lines[index]);
|
|
323
|
+
if (event.type === "assistant.turn_end" && event.timestamp)
|
|
324
|
+
return Date.parse(event.timestamp);
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
347
327
|
return undefined;
|
|
348
328
|
}
|
|
349
329
|
}
|
|
350
330
|
return undefined;
|
|
351
331
|
}
|
|
352
|
-
|
|
353
|
-
async function copyCopilotStateToRemoteServer(vm: RemoteVm, vmId: string, sourceCopilotDir: string, localDestinations: string[], utils: PushvmPluginUtils) {
|
|
332
|
+
async function copyCopilotStateToRemoteServer(vm, vmId, sourceCopilotDir, localDestinations, utils) {
|
|
354
333
|
const workspaceIds = localDestinations.map((destination) => path.basename(path.dirname(destination)));
|
|
355
|
-
if (workspaceIds.length === 0)
|
|
356
|
-
|
|
334
|
+
if (workspaceIds.length === 0)
|
|
335
|
+
return;
|
|
357
336
|
const archive = await createRemoteServerWorkspaceStateArchive(sourceCopilotDir, localDestinations, utils);
|
|
358
337
|
try {
|
|
359
338
|
await utils.uploadArchiveInChunks(vm, vmId, archive, "/tmp/vmpush-copilot-chat.tgz", "copilot-chat");
|
|
@@ -365,17 +344,16 @@ async function copyCopilotStateToRemoteServer(vm: RemoteVm, vmId: string, source
|
|
|
365
344
|
commands.push(`tar --no-same-owner --no-same-permissions -xzf /tmp/vmpush-copilot-chat.tgz -C ${utils.shellQuote(baseDir)}`);
|
|
366
345
|
commands.push("rm -f /tmp/vmpush-copilot-chat.tgz");
|
|
367
346
|
await utils.checkedExec(vm, commands.join("\n"));
|
|
368
|
-
}
|
|
347
|
+
}
|
|
348
|
+
finally {
|
|
369
349
|
await rm(path.dirname(archive), { recursive: true, force: true });
|
|
370
350
|
}
|
|
371
351
|
}
|
|
372
|
-
|
|
373
|
-
async function createRemoteServerWorkspaceStateArchive(sourceCopilotDir: string, localDestinations: string[], utils: PushvmPluginUtils) {
|
|
352
|
+
async function createRemoteServerWorkspaceStateArchive(sourceCopilotDir, localDestinations, utils) {
|
|
374
353
|
const tempDir = await mkdtemp(path.join(tmpdir(), "vmpush-copilot-"));
|
|
375
354
|
const stagingDir = path.join(tempDir, "staging");
|
|
376
355
|
const archivePath = path.join(tempDir, "copilot-chat.tgz");
|
|
377
356
|
await mkdir(stagingDir, { recursive: true });
|
|
378
|
-
|
|
379
357
|
for (const destination of localDestinations) {
|
|
380
358
|
const workspaceId = path.basename(path.dirname(destination));
|
|
381
359
|
const workspaceDir = path.join(stagingDir, workspaceId);
|
|
@@ -384,80 +362,80 @@ async function createRemoteServerWorkspaceStateArchive(sourceCopilotDir: string,
|
|
|
384
362
|
await sanitizeCopilotTranscripts(path.join(workspaceDir, "GitHub.copilot-chat"));
|
|
385
363
|
await copyCoreChatState(sourceCopilotDir, path.join(workspaceDir, "GitHub.copilot-chat"));
|
|
386
364
|
const stateDb = path.join(path.dirname(destination), "state.vscdb");
|
|
387
|
-
if (await exists(stateDb))
|
|
365
|
+
if (await exists(stateDb))
|
|
366
|
+
await cp(stateDb, path.join(workspaceDir, "state.vscdb"), { force: true });
|
|
388
367
|
}
|
|
389
|
-
|
|
390
368
|
await utils.createTar(["--no-xattrs", "-czf", archivePath, "-C", stagingDir, "."]);
|
|
391
369
|
return archivePath;
|
|
392
370
|
}
|
|
393
|
-
|
|
394
|
-
async function isRemoteWorkspaceStorageForVmProject(workspaceDir: string, vmId: string, remoteProjectDir: string) {
|
|
371
|
+
async function isRemoteWorkspaceStorageForVmProject(workspaceDir, vmId, remoteProjectDir) {
|
|
395
372
|
try {
|
|
396
373
|
const raw = await readFile(path.join(workspaceDir, "workspace.json"), "utf8");
|
|
397
|
-
const parsed = JSON.parse(raw)
|
|
374
|
+
const parsed = JSON.parse(raw);
|
|
398
375
|
const workspaceUri = parsed.folder ?? parsed.workspace;
|
|
399
|
-
if (!workspaceUri?.startsWith("vscode-remote://"))
|
|
376
|
+
if (!workspaceUri?.startsWith("vscode-remote://"))
|
|
377
|
+
return false;
|
|
400
378
|
const decodedUri = decodeURIComponent(workspaceUri);
|
|
401
379
|
return decodedUri.includes(vmId) && remoteWorkspacePath(decodedUri) === remoteProjectDir;
|
|
402
|
-
}
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
403
382
|
return false;
|
|
404
383
|
}
|
|
405
384
|
}
|
|
406
|
-
|
|
407
|
-
async function isWorkspaceStorageForRemoteUri(workspaceDir: string, remoteWorkspaceUri: string) {
|
|
385
|
+
async function isWorkspaceStorageForRemoteUri(workspaceDir, remoteWorkspaceUri) {
|
|
408
386
|
try {
|
|
409
387
|
const raw = await readFile(path.join(workspaceDir, "workspace.json"), "utf8");
|
|
410
|
-
const parsed = JSON.parse(raw)
|
|
388
|
+
const parsed = JSON.parse(raw);
|
|
411
389
|
const workspaceUri = parsed.folder ?? parsed.workspace;
|
|
412
390
|
return workspaceUri === remoteWorkspaceUri;
|
|
413
|
-
}
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
414
393
|
return false;
|
|
415
394
|
}
|
|
416
395
|
}
|
|
417
|
-
|
|
418
|
-
async function isWorkspaceStorageForProject(workspaceDir: string, projectRoot: string) {
|
|
396
|
+
async function isWorkspaceStorageForProject(workspaceDir, projectRoot) {
|
|
419
397
|
try {
|
|
420
398
|
const raw = await readFile(path.join(workspaceDir, "workspace.json"), "utf8");
|
|
421
|
-
const parsed = JSON.parse(raw)
|
|
399
|
+
const parsed = JSON.parse(raw);
|
|
422
400
|
const workspacePath = workspaceJsonPath(parsed.folder ?? parsed.workspace);
|
|
423
401
|
return workspacePath !== undefined && isSameOrParentPath(workspacePath, projectRoot);
|
|
424
|
-
}
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
425
404
|
return false;
|
|
426
405
|
}
|
|
427
406
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
407
|
+
function workspaceJsonPath(uri) {
|
|
408
|
+
if (!uri)
|
|
409
|
+
return undefined;
|
|
431
410
|
try {
|
|
432
|
-
if (uri.startsWith("file://"))
|
|
411
|
+
if (uri.startsWith("file://"))
|
|
412
|
+
return path.resolve(fileURLToPath(uri));
|
|
433
413
|
return path.resolve(uri);
|
|
434
|
-
}
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
435
416
|
return undefined;
|
|
436
417
|
}
|
|
437
418
|
}
|
|
438
|
-
|
|
439
|
-
function isSameOrParentPath(workspacePath: string, projectRoot: string) {
|
|
419
|
+
function isSameOrParentPath(workspacePath, projectRoot) {
|
|
440
420
|
const relative = path.relative(workspacePath, projectRoot);
|
|
441
421
|
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
442
422
|
}
|
|
443
|
-
|
|
444
|
-
function remoteWorkspacePath(decodedRemoteUri: string) {
|
|
423
|
+
function remoteWorkspacePath(decodedRemoteUri) {
|
|
445
424
|
const hostEnd = decodedRemoteUri.indexOf("/", "vscode-remote://".length);
|
|
446
|
-
if (hostEnd === -1)
|
|
425
|
+
if (hostEnd === -1)
|
|
426
|
+
return "/";
|
|
447
427
|
return normalizeRemotePath(decodedRemoteUri.slice(hostEnd));
|
|
448
428
|
}
|
|
449
|
-
|
|
450
|
-
function normalizeRemotePath(value: string) {
|
|
429
|
+
function normalizeRemotePath(value) {
|
|
451
430
|
const normalized = path.posix.normalize(value.replace(/\\/g, "/"));
|
|
452
|
-
if (!normalized.startsWith("/"))
|
|
431
|
+
if (!normalized.startsWith("/"))
|
|
432
|
+
return "/";
|
|
453
433
|
return normalized;
|
|
454
434
|
}
|
|
455
|
-
|
|
456
|
-
async function ensureWorkspaceStateDb(stateDb: string) {
|
|
435
|
+
async function ensureWorkspaceStateDb(stateDb) {
|
|
457
436
|
await execFileAsync("sqlite3", [stateDb, "CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB);"]);
|
|
458
437
|
}
|
|
459
|
-
|
|
460
|
-
function codeUserRootsForScheme(home: string, scheme: string) {
|
|
438
|
+
function codeUserRootsForScheme(home, scheme) {
|
|
461
439
|
switch (scheme) {
|
|
462
440
|
case "cursor":
|
|
463
441
|
return [
|
|
@@ -473,12 +451,10 @@ function codeUserRootsForScheme(home: string, scheme: string) {
|
|
|
473
451
|
return [];
|
|
474
452
|
}
|
|
475
453
|
}
|
|
476
|
-
|
|
477
|
-
function isSupportedEditorScheme(scheme: string): scheme is "vscode" | "cursor" {
|
|
454
|
+
function isSupportedEditorScheme(scheme) {
|
|
478
455
|
return scheme === "vscode" || scheme === "cursor";
|
|
479
456
|
}
|
|
480
|
-
|
|
481
|
-
function codeUserRoots(home: string) {
|
|
457
|
+
function codeUserRoots(home) {
|
|
482
458
|
return [
|
|
483
459
|
path.join(home, "Library", "Application Support", "Code", "User"),
|
|
484
460
|
path.join(home, "Library", "Application Support", "Code - Insiders", "User"),
|
|
@@ -488,65 +464,66 @@ function codeUserRoots(home: string) {
|
|
|
488
464
|
path.join(home, ".config", "Cursor", "User"),
|
|
489
465
|
];
|
|
490
466
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (localCodeUserRoot.includes("Code - Insiders"))
|
|
467
|
+
function remoteCodeRoot(localCodeUserRoot) {
|
|
468
|
+
if (localCodeUserRoot.includes("Cursor"))
|
|
469
|
+
return "~/.config/Cursor/User";
|
|
470
|
+
if (localCodeUserRoot.includes("Code - Insiders"))
|
|
471
|
+
return "~/.config/Code - Insiders/User";
|
|
495
472
|
return "~/.config/Code/User";
|
|
496
473
|
}
|
|
497
|
-
|
|
498
|
-
async function listDirectories(directory: string) {
|
|
474
|
+
async function listDirectories(directory) {
|
|
499
475
|
try {
|
|
500
476
|
const dirents = await import("node:fs/promises").then((fs) => fs.readdir(directory, { withFileTypes: true }));
|
|
501
477
|
return dirents.filter((dirent) => dirent.isDirectory()).map((dirent) => path.join(directory, dirent.name));
|
|
502
|
-
}
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
503
480
|
return [];
|
|
504
481
|
}
|
|
505
482
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
async function visit(current: string) {
|
|
483
|
+
async function listFiles(directory) {
|
|
484
|
+
const result = [];
|
|
485
|
+
async function visit(current) {
|
|
510
486
|
const dirents = await import("node:fs/promises").then((fs) => fs.readdir(current, { withFileTypes: true })).catch(() => []);
|
|
511
487
|
for (const dirent of dirents) {
|
|
512
488
|
const child = path.join(current, dirent.name);
|
|
513
|
-
if (dirent.isDirectory())
|
|
514
|
-
|
|
489
|
+
if (dirent.isDirectory())
|
|
490
|
+
await visit(child);
|
|
491
|
+
else if (dirent.isFile())
|
|
492
|
+
result.push(child);
|
|
515
493
|
}
|
|
516
494
|
}
|
|
517
495
|
await visit(directory);
|
|
518
496
|
return result;
|
|
519
497
|
}
|
|
520
|
-
|
|
521
|
-
function addCandidate(candidates: ContextCandidate[], source: string, remoteRoot: string, label: string, preferenceKey: string) {
|
|
498
|
+
function addCandidate(candidates, source, remoteRoot, label, preferenceKey) {
|
|
522
499
|
candidates.push({ source, remoteRoot: expandRemoteHome(remoteRoot), label, sensitive: true, preferenceKey, promptLabel: label });
|
|
523
500
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const seen = new Set<string>();
|
|
501
|
+
function dedupeCandidates(candidates) {
|
|
502
|
+
const seen = new Set();
|
|
527
503
|
return candidates.filter((candidate) => {
|
|
528
504
|
const key = `${candidate.source}\0${candidate.remoteRoot}`;
|
|
529
|
-
if (seen.has(key))
|
|
505
|
+
if (seen.has(key))
|
|
506
|
+
return false;
|
|
530
507
|
seen.add(key);
|
|
531
508
|
return true;
|
|
532
509
|
});
|
|
533
510
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
if (value.startsWith("~/"))
|
|
511
|
+
function expandRemoteHome(value) {
|
|
512
|
+
if (value === "~")
|
|
513
|
+
return "/root";
|
|
514
|
+
if (value.startsWith("~/"))
|
|
515
|
+
return `/root/${value.slice(2)}`;
|
|
538
516
|
return value;
|
|
539
517
|
}
|
|
540
|
-
|
|
541
|
-
function sqliteLiteral(value: string) {
|
|
518
|
+
function sqliteLiteral(value) {
|
|
542
519
|
return `'${value.replace(/'/g, "''")}'`;
|
|
543
520
|
}
|
|
544
|
-
|
|
545
|
-
async function exists(filePath: string) {
|
|
521
|
+
async function exists(filePath) {
|
|
546
522
|
try {
|
|
547
523
|
await stat(filePath);
|
|
548
524
|
return true;
|
|
549
|
-
}
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
550
527
|
return false;
|
|
551
528
|
}
|
|
552
529
|
}
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
import { stat } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { definePlugin
|
|
5
|
-
|
|
4
|
+
import { definePlugin } from "../../../src/plugin-api.js";
|
|
6
5
|
export function awsAuthPlugin() {
|
|
7
6
|
return definePlugin({
|
|
8
7
|
name: "@freestyle-sync/auth-aws",
|
|
9
8
|
async discoverContextCandidates({ options }) {
|
|
10
|
-
if (!options.includeAuth)
|
|
11
|
-
|
|
9
|
+
if (!options.includeAuth)
|
|
10
|
+
return [];
|
|
11
|
+
const item = { source: path.join(homedir(), ".aws"), remoteRoot: "/root/.aws", label: "AWS credentials", sensitive: true, preferenceKey: "context:aws", promptLabel: "AWS credentials" };
|
|
12
12
|
return await exists(item.source) ? [item] : [];
|
|
13
13
|
},
|
|
14
14
|
async afterContextSync({ vm, changedRemotePaths, utils }) {
|
|
15
|
-
if (!changedRemotePaths.some((remotePath) => remotePath === "/root/.aws" || remotePath.startsWith("/root/.aws/")))
|
|
15
|
+
if (!changedRemotePaths.some((remotePath) => remotePath === "/root/.aws" || remotePath.startsWith("/root/.aws/")))
|
|
16
|
+
return;
|
|
16
17
|
await utils.checkedExec(vm, "chown -R root:root /root/.aws 2>/dev/null || true; chmod 700 /root/.aws 2>/dev/null || true");
|
|
17
18
|
},
|
|
18
19
|
});
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
async function exists(filePath) {
|
|
22
|
+
try {
|
|
23
|
+
await stat(filePath);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
23
29
|
}
|