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