@civilization/dsh-git-workbench 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +199 -102
- package/package.json +1 -1
- package/src/index.ts +233 -149
package/lib/index.js
CHANGED
|
@@ -1157,118 +1157,215 @@ function registerGitTool(ctx, resolveDir) {
|
|
|
1157
1157
|
/** Cordis plugin name */
|
|
1158
1158
|
const name = "@civilization/dsh-git-workbench";
|
|
1159
1159
|
/** Required host services */
|
|
1160
|
-
const inject = [
|
|
1160
|
+
const inject = [
|
|
1161
|
+
"webServer",
|
|
1162
|
+
"sessions",
|
|
1163
|
+
"sandboxPolicy"
|
|
1164
|
+
];
|
|
1161
1165
|
/**
|
|
1162
|
-
*
|
|
1163
|
-
* Sets up authenticated RPC handlers under `/git/*` and registers the model tool.
|
|
1164
|
-
* @param ctx - Context carrying the connection and optional session services.
|
|
1166
|
+
* Execute Git operations based on requested endpoint and payload.
|
|
1165
1167
|
*/
|
|
1166
|
-
function
|
|
1167
|
-
|
|
1168
|
-
const resolveTargetDir = (sessionId) => {
|
|
1169
|
-
if (sessionId) {
|
|
1170
|
-
const liveCwd = host.sessions?.get(sessionId)?.header?.cwd;
|
|
1171
|
-
if (liveCwd) return liveCwd;
|
|
1172
|
-
}
|
|
1173
|
-
return host.sandboxPolicy?.workspaceRoot ?? process.cwd();
|
|
1174
|
-
};
|
|
1175
|
-
host.connection.rpc.handle("/git", async (endpoint, payload, _signal) => {
|
|
1168
|
+
async function executeGitEndpoint(endpoint, payload, resolveTargetDir) {
|
|
1169
|
+
try {
|
|
1176
1170
|
const body = typeof payload === "object" && payload !== null ? payload : {};
|
|
1177
1171
|
const sessionId = typeof body.sessionId === "string" ? body.sessionId : void 0;
|
|
1178
1172
|
const cwd = typeof body.path === "string" ? body.path : resolveTargetDir(sessionId);
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1173
|
+
if (endpoint === "status") return {
|
|
1174
|
+
ok: true,
|
|
1175
|
+
value: await getGitStatus(cwd)
|
|
1176
|
+
};
|
|
1177
|
+
if (endpoint === "log") return {
|
|
1178
|
+
ok: true,
|
|
1179
|
+
value: await getGitLog(cwd, typeof body.maxCount === "number" ? body.maxCount : 50)
|
|
1180
|
+
};
|
|
1181
|
+
if (endpoint === "commitDetails") return {
|
|
1182
|
+
ok: true,
|
|
1183
|
+
value: await getGitCommitDetails(cwd, typeof body.commit === "string" ? body.commit : "")
|
|
1184
|
+
};
|
|
1185
|
+
if (endpoint === "diff") {
|
|
1186
|
+
const path = typeof body.filePath === "string" ? body.filePath : void 0;
|
|
1187
|
+
const staged = typeof body.staged === "boolean" ? body.staged : false;
|
|
1188
|
+
const commit = typeof body.commit === "string" ? body.commit : void 0;
|
|
1189
|
+
return {
|
|
1189
1190
|
ok: true,
|
|
1190
|
-
value: await
|
|
1191
|
+
value: await getGitDiff(cwd, {
|
|
1192
|
+
...path !== void 0 ? { path } : {},
|
|
1193
|
+
staged,
|
|
1194
|
+
...commit !== void 0 ? { commit } : {}
|
|
1195
|
+
})
|
|
1191
1196
|
};
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1197
|
+
}
|
|
1198
|
+
if (endpoint === "branches") return {
|
|
1199
|
+
ok: true,
|
|
1200
|
+
value: await getGitAllBranches(cwd)
|
|
1201
|
+
};
|
|
1202
|
+
if (endpoint === "checkout") return {
|
|
1203
|
+
ok: true,
|
|
1204
|
+
value: await gitCheckout(cwd, typeof body.branch === "string" ? body.branch : "", typeof body.create === "boolean" ? body.create : false)
|
|
1205
|
+
};
|
|
1206
|
+
if (endpoint === "stage") return {
|
|
1207
|
+
ok: true,
|
|
1208
|
+
value: await gitStage(cwd, Array.isArray(body.paths) ? body.paths : body.paths === "all" ? "all" : typeof body.path === "string" ? [body.path] : "all")
|
|
1209
|
+
};
|
|
1210
|
+
if (endpoint === "unstage") return {
|
|
1211
|
+
ok: true,
|
|
1212
|
+
value: await gitUnstage(cwd, Array.isArray(body.paths) ? body.paths : body.paths === "all" ? "all" : typeof body.path === "string" ? [body.path] : "all")
|
|
1213
|
+
};
|
|
1214
|
+
if (endpoint === "discard") return {
|
|
1215
|
+
ok: true,
|
|
1216
|
+
value: await gitDiscard(cwd, Array.isArray(body.paths) ? body.paths : typeof body.path === "string" ? [body.path] : [])
|
|
1217
|
+
};
|
|
1218
|
+
if (endpoint === "commit") return {
|
|
1219
|
+
ok: true,
|
|
1220
|
+
value: await gitCommit(cwd, typeof body.message === "string" ? body.message : "")
|
|
1221
|
+
};
|
|
1222
|
+
if (endpoint === "fetch") return {
|
|
1223
|
+
ok: true,
|
|
1224
|
+
value: await gitFetch(cwd)
|
|
1225
|
+
};
|
|
1226
|
+
if (endpoint === "pull") return {
|
|
1227
|
+
ok: true,
|
|
1228
|
+
value: await gitPull(cwd)
|
|
1229
|
+
};
|
|
1230
|
+
if (endpoint === "push") return {
|
|
1231
|
+
ok: true,
|
|
1232
|
+
value: await gitPush(cwd)
|
|
1233
|
+
};
|
|
1234
|
+
if (endpoint === "tags") return {
|
|
1235
|
+
ok: true,
|
|
1236
|
+
value: await getGitTags(cwd)
|
|
1237
|
+
};
|
|
1238
|
+
if (endpoint === "createTag") return {
|
|
1239
|
+
ok: true,
|
|
1240
|
+
value: await gitCreateTag(cwd, typeof body.name === "string" ? body.name : "", typeof body.commit === "string" ? body.commit : void 0, typeof body.message === "string" ? body.message : void 0)
|
|
1241
|
+
};
|
|
1242
|
+
if (endpoint === "deleteTag") return {
|
|
1243
|
+
ok: true,
|
|
1244
|
+
value: await gitDeleteTag(cwd, typeof body.name === "string" ? body.name : "")
|
|
1245
|
+
};
|
|
1246
|
+
return {
|
|
1247
|
+
ok: false,
|
|
1248
|
+
error: {
|
|
1249
|
+
code: "git/not_found",
|
|
1250
|
+
message: `Unknown git RPC endpoint: ${endpoint}`,
|
|
1251
|
+
details: {}
|
|
1204
1252
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
if (
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
ok: true,
|
|
1251
|
-
value: await gitDeleteTag(cwd, typeof body.name === "string" ? body.name : "")
|
|
1252
|
-
};
|
|
1253
|
-
return {
|
|
1254
|
-
ok: false,
|
|
1255
|
-
error: {
|
|
1256
|
-
code: "git/not_found",
|
|
1257
|
-
message: `Unknown git RPC endpoint: ${endpoint}`,
|
|
1258
|
-
details: {}
|
|
1253
|
+
};
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
return {
|
|
1256
|
+
ok: false,
|
|
1257
|
+
error: {
|
|
1258
|
+
code: "git/error",
|
|
1259
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1260
|
+
details: {}
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Apply the Git host plugin.
|
|
1267
|
+
* Sets up direct WebServer HTTP route handlers under `/git/*` and registers the model tool.
|
|
1268
|
+
* @param ctx - Context carrying the webServer, sessions, and sandboxPolicy services.
|
|
1269
|
+
*/
|
|
1270
|
+
function apply(ctx) {
|
|
1271
|
+
const host = ctx;
|
|
1272
|
+
const resolveTargetDir = (sessionId) => {
|
|
1273
|
+
try {
|
|
1274
|
+
if (sessionId) {
|
|
1275
|
+
const liveCwd = host.sessions?.get?.(sessionId)?.header?.cwd;
|
|
1276
|
+
if (liveCwd) return liveCwd;
|
|
1277
|
+
}
|
|
1278
|
+
} catch {}
|
|
1279
|
+
try {
|
|
1280
|
+
const workspaceRoot = host.sandboxPolicy?.workspaceRoot;
|
|
1281
|
+
if (workspaceRoot) return workspaceRoot;
|
|
1282
|
+
} catch {}
|
|
1283
|
+
return process.cwd();
|
|
1284
|
+
};
|
|
1285
|
+
const webServer = ctx.webServer;
|
|
1286
|
+
if (webServer?.register) ctx.effect(() => webServer.register({
|
|
1287
|
+
kind: "prefix",
|
|
1288
|
+
path: "/git",
|
|
1289
|
+
handler: async (req, res) => {
|
|
1290
|
+
try {
|
|
1291
|
+
if (req.method === "OPTIONS") {
|
|
1292
|
+
res.writeHead(204, {
|
|
1293
|
+
"Access-Control-Allow-Origin": "*",
|
|
1294
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
1295
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
1296
|
+
});
|
|
1297
|
+
return res.end();
|
|
1259
1298
|
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1267
|
-
details: {}
|
|
1299
|
+
if (req.method !== "POST") {
|
|
1300
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
1301
|
+
return res.end(JSON.stringify({
|
|
1302
|
+
ok: false,
|
|
1303
|
+
error: { message: "Method Not Allowed" }
|
|
1304
|
+
}));
|
|
1268
1305
|
}
|
|
1269
|
-
|
|
1306
|
+
const match = new URL(req.url ?? "/", "http://localhost").pathname.match(/^\/git\/([a-zA-Z0-9_-]+)/);
|
|
1307
|
+
const endpoint = match ? match[1] : void 0;
|
|
1308
|
+
if (!endpoint) {
|
|
1309
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1310
|
+
return res.end(JSON.stringify({
|
|
1311
|
+
ok: false,
|
|
1312
|
+
error: { message: "Not Found" }
|
|
1313
|
+
}));
|
|
1314
|
+
}
|
|
1315
|
+
let rawBody = "";
|
|
1316
|
+
req.on("data", (chunk) => {
|
|
1317
|
+
rawBody += chunk.toString("utf8");
|
|
1318
|
+
});
|
|
1319
|
+
req.on("end", async () => {
|
|
1320
|
+
try {
|
|
1321
|
+
let body = {};
|
|
1322
|
+
try {
|
|
1323
|
+
if (rawBody.trim()) body = JSON.parse(rawBody);
|
|
1324
|
+
} catch {
|
|
1325
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1326
|
+
return res.end(JSON.stringify({
|
|
1327
|
+
ok: false,
|
|
1328
|
+
error: { message: "Invalid JSON body" }
|
|
1329
|
+
}));
|
|
1330
|
+
}
|
|
1331
|
+
const rpcId = body?.type === "client-request" ? body.rpcId : void 0;
|
|
1332
|
+
const payload = body?.type === "client-request" ? body.payload ?? {} : body;
|
|
1333
|
+
const result = await executeGitEndpoint(endpoint, payload, resolveTargetDir);
|
|
1334
|
+
res.writeHead(200, {
|
|
1335
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
1336
|
+
"Access-Control-Allow-Origin": "*"
|
|
1337
|
+
});
|
|
1338
|
+
if (rpcId !== void 0) res.end(JSON.stringify({
|
|
1339
|
+
type: "server-response",
|
|
1340
|
+
rpcId,
|
|
1341
|
+
result
|
|
1342
|
+
}));
|
|
1343
|
+
else res.end(JSON.stringify(result));
|
|
1344
|
+
} catch (err) {
|
|
1345
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1346
|
+
res.end(JSON.stringify({
|
|
1347
|
+
ok: false,
|
|
1348
|
+
error: {
|
|
1349
|
+
code: "git/internal_error",
|
|
1350
|
+
message: err?.message ?? String(err)
|
|
1351
|
+
}
|
|
1352
|
+
}));
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
} catch (err) {
|
|
1356
|
+
try {
|
|
1357
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1358
|
+
res.end(JSON.stringify({
|
|
1359
|
+
ok: false,
|
|
1360
|
+
error: {
|
|
1361
|
+
code: "git/internal_error",
|
|
1362
|
+
message: err?.message ?? String(err)
|
|
1363
|
+
}
|
|
1364
|
+
}));
|
|
1365
|
+
} catch {}
|
|
1366
|
+
}
|
|
1270
1367
|
}
|
|
1271
|
-
});
|
|
1368
|
+
}), "git-workbench: /git routes");
|
|
1272
1369
|
registerGitTool(ctx, resolveTargetDir);
|
|
1273
1370
|
}
|
|
1274
|
-
export { apply, inject, name };
|
|
1371
|
+
export { apply, executeGitEndpoint, inject, name };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@civilization/dsh-git-workbench",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "IntelliJ IDEA-grade Git Workbench, DAG topology graph, branch switcher, tag manager and agent tools for DeepSeek Harness.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
package/src/index.ts
CHANGED
|
@@ -30,17 +30,9 @@ export type * from './types.ts'
|
|
|
30
30
|
export const name = '@civilization/dsh-git-workbench'
|
|
31
31
|
|
|
32
32
|
/** Required host services */
|
|
33
|
-
export const inject = ['
|
|
33
|
+
export const inject = ['webServer', 'sessions', 'sandboxPolicy']
|
|
34
34
|
|
|
35
35
|
interface HostServices {
|
|
36
|
-
connection: {
|
|
37
|
-
rpc: {
|
|
38
|
-
handle: (
|
|
39
|
-
channel: string,
|
|
40
|
-
handler: (endpoint: string, payload: unknown, signal: AbortSignal) => Promise<unknown>
|
|
41
|
-
) => () => void
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
36
|
webServer?: {
|
|
45
37
|
register: (route: unknown) => () => void
|
|
46
38
|
}
|
|
@@ -53,168 +45,260 @@ interface HostServices {
|
|
|
53
45
|
}
|
|
54
46
|
|
|
55
47
|
/**
|
|
56
|
-
*
|
|
57
|
-
* Sets up authenticated RPC handlers under `/git/*` and registers the model tool.
|
|
58
|
-
* @param ctx - Context carrying the connection and optional session services.
|
|
48
|
+
* Execute Git operations based on requested endpoint and payload.
|
|
59
49
|
*/
|
|
60
|
-
export function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
50
|
+
export async function executeGitEndpoint(
|
|
51
|
+
endpoint: string,
|
|
52
|
+
payload: unknown,
|
|
53
|
+
resolveTargetDir: (sessionId?: string) => string,
|
|
54
|
+
): Promise<{ ok: true; value: unknown } | { ok: false; error: { code: string; message: string; details: Record<string, unknown> } }> {
|
|
55
|
+
try {
|
|
56
|
+
const body = (typeof payload === 'object' && payload !== null ? payload : {}) as Record<string, unknown>
|
|
57
|
+
const sessionId = typeof body.sessionId === 'string' ? body.sessionId : undefined
|
|
58
|
+
const cwd = typeof body.path === 'string' ? body.path : resolveTargetDir(sessionId)
|
|
59
|
+
|
|
60
|
+
if (endpoint === 'status') {
|
|
61
|
+
const status = await getGitStatus(cwd)
|
|
62
|
+
return { ok: true, value: status }
|
|
66
63
|
}
|
|
67
|
-
const workspaceRoot = host.sandboxPolicy?.workspaceRoot
|
|
68
|
-
return workspaceRoot ?? process.cwd()
|
|
69
|
-
}
|
|
70
64
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const sessionId = typeof body.sessionId === 'string' ? body.sessionId : undefined
|
|
77
|
-
const cwd = typeof body.path === 'string' ? body.path : resolveTargetDir(sessionId)
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
if (endpoint === 'status') {
|
|
81
|
-
const status = await getGitStatus(cwd)
|
|
82
|
-
return { ok: true, value: status }
|
|
83
|
-
}
|
|
65
|
+
if (endpoint === 'log') {
|
|
66
|
+
const maxCount = typeof body.maxCount === 'number' ? body.maxCount : 50
|
|
67
|
+
const commits = await getGitLog(cwd, maxCount)
|
|
68
|
+
return { ok: true, value: commits }
|
|
69
|
+
}
|
|
84
70
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
71
|
+
if (endpoint === 'commitDetails') {
|
|
72
|
+
const commit = typeof body.commit === 'string' ? body.commit : ''
|
|
73
|
+
const details = await getGitCommitDetails(cwd, commit)
|
|
74
|
+
return { ok: true, value: details }
|
|
75
|
+
}
|
|
90
76
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
77
|
+
if (endpoint === 'diff') {
|
|
78
|
+
const path = typeof body.filePath === 'string' ? body.filePath : undefined
|
|
79
|
+
const staged = typeof body.staged === 'boolean' ? body.staged : false
|
|
80
|
+
const commit = typeof body.commit === 'string' ? body.commit : undefined
|
|
81
|
+
const diff = await getGitDiff(cwd, {
|
|
82
|
+
...path !== undefined ? { path } : {},
|
|
83
|
+
staged,
|
|
84
|
+
...commit !== undefined ? { commit } : {},
|
|
85
|
+
})
|
|
86
|
+
return { ok: true, value: diff }
|
|
87
|
+
}
|
|
96
88
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const diff = await getGitDiff(cwd, {
|
|
102
|
-
...path !== undefined ? { path } : {},
|
|
103
|
-
staged,
|
|
104
|
-
...commit !== undefined ? { commit } : {},
|
|
105
|
-
})
|
|
106
|
-
return { ok: true, value: diff }
|
|
107
|
-
}
|
|
89
|
+
if (endpoint === 'branches') {
|
|
90
|
+
const branches = await getGitAllBranches(cwd)
|
|
91
|
+
return { ok: true, value: branches }
|
|
92
|
+
}
|
|
108
93
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
94
|
+
if (endpoint === 'checkout') {
|
|
95
|
+
const branch = typeof body.branch === 'string' ? body.branch : ''
|
|
96
|
+
const createNew = typeof body.create === 'boolean' ? body.create : false
|
|
97
|
+
const result = await gitCheckout(cwd, branch, createNew)
|
|
98
|
+
return { ok: true, value: result }
|
|
99
|
+
}
|
|
113
100
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
101
|
+
if (endpoint === 'stage') {
|
|
102
|
+
const paths = Array.isArray(body.paths)
|
|
103
|
+
? (body.paths as string[])
|
|
104
|
+
: body.paths === 'all'
|
|
105
|
+
? 'all'
|
|
106
|
+
: typeof body.path === 'string'
|
|
107
|
+
? [body.path]
|
|
108
|
+
: 'all'
|
|
109
|
+
const result = await gitStage(cwd, paths)
|
|
110
|
+
return { ok: true, value: result }
|
|
111
|
+
}
|
|
120
112
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
113
|
+
if (endpoint === 'unstage') {
|
|
114
|
+
const paths = Array.isArray(body.paths)
|
|
115
|
+
? (body.paths as string[])
|
|
116
|
+
: body.paths === 'all'
|
|
117
|
+
? 'all'
|
|
118
|
+
: typeof body.path === 'string'
|
|
119
|
+
? [body.path]
|
|
120
|
+
: 'all'
|
|
121
|
+
const result = await gitUnstage(cwd, paths)
|
|
122
|
+
return { ok: true, value: result }
|
|
123
|
+
}
|
|
132
124
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return { ok: true, value: result }
|
|
143
|
-
}
|
|
125
|
+
if (endpoint === 'discard') {
|
|
126
|
+
const paths = Array.isArray(body.paths)
|
|
127
|
+
? (body.paths as string[])
|
|
128
|
+
: typeof body.path === 'string'
|
|
129
|
+
? [body.path]
|
|
130
|
+
: []
|
|
131
|
+
const result = await gitDiscard(cwd, paths)
|
|
132
|
+
return { ok: true, value: result }
|
|
133
|
+
}
|
|
144
134
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
: []
|
|
151
|
-
const result = await gitDiscard(cwd, paths)
|
|
152
|
-
return { ok: true, value: result }
|
|
153
|
-
}
|
|
135
|
+
if (endpoint === 'commit') {
|
|
136
|
+
const message = typeof body.message === 'string' ? body.message : ''
|
|
137
|
+
const result = await gitCommit(cwd, message)
|
|
138
|
+
return { ok: true, value: result }
|
|
139
|
+
}
|
|
154
140
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
141
|
+
if (endpoint === 'fetch') {
|
|
142
|
+
const result = await gitFetch(cwd)
|
|
143
|
+
return { ok: true, value: result }
|
|
144
|
+
}
|
|
160
145
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
146
|
+
if (endpoint === 'pull') {
|
|
147
|
+
const result = await gitPull(cwd)
|
|
148
|
+
return { ok: true, value: result }
|
|
149
|
+
}
|
|
165
150
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
151
|
+
if (endpoint === 'push') {
|
|
152
|
+
const result = await gitPush(cwd)
|
|
153
|
+
return { ok: true, value: result }
|
|
154
|
+
}
|
|
170
155
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
156
|
+
if (endpoint === 'tags') {
|
|
157
|
+
const tags = await getGitTags(cwd)
|
|
158
|
+
return { ok: true, value: tags }
|
|
159
|
+
}
|
|
175
160
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
161
|
+
if (endpoint === 'createTag') {
|
|
162
|
+
const name = typeof body.name === 'string' ? body.name : ''
|
|
163
|
+
const commit = typeof body.commit === 'string' ? body.commit : undefined
|
|
164
|
+
const message = typeof body.message === 'string' ? body.message : undefined
|
|
165
|
+
const result = await gitCreateTag(cwd, name, commit, message)
|
|
166
|
+
return { ok: true, value: result }
|
|
167
|
+
}
|
|
180
168
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return { ok: true, value: result }
|
|
187
|
-
}
|
|
169
|
+
if (endpoint === 'deleteTag') {
|
|
170
|
+
const name = typeof body.name === 'string' ? body.name : ''
|
|
171
|
+
const result = await gitDeleteTag(cwd, name)
|
|
172
|
+
return { ok: true, value: result }
|
|
173
|
+
}
|
|
188
174
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
error: {
|
|
178
|
+
code: 'git/not_found',
|
|
179
|
+
message: `Unknown git RPC endpoint: ${endpoint}`,
|
|
180
|
+
details: {},
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
} catch (error) {
|
|
184
|
+
return {
|
|
185
|
+
ok: false,
|
|
186
|
+
error: {
|
|
187
|
+
code: 'git/error',
|
|
188
|
+
message: error instanceof Error ? error.message : String(error),
|
|
189
|
+
details: {},
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Apply the Git host plugin.
|
|
197
|
+
* Sets up direct WebServer HTTP route handlers under `/git/*` and registers the model tool.
|
|
198
|
+
* @param ctx - Context carrying the webServer, sessions, and sandboxPolicy services.
|
|
199
|
+
*/
|
|
200
|
+
export function apply(ctx: Context): void {
|
|
201
|
+
const host = ctx as any as HostServices
|
|
195
202
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
details: {},
|
|
202
|
-
},
|
|
203
|
-
}
|
|
204
|
-
} catch (error) {
|
|
205
|
-
return {
|
|
206
|
-
ok: false,
|
|
207
|
-
error: {
|
|
208
|
-
code: 'git/error',
|
|
209
|
-
message: error instanceof Error ? error.message : String(error),
|
|
210
|
-
details: {},
|
|
211
|
-
},
|
|
212
|
-
}
|
|
203
|
+
const resolveTargetDir = (sessionId?: string): string => {
|
|
204
|
+
try {
|
|
205
|
+
if (sessionId) {
|
|
206
|
+
const liveCwd = host.sessions?.get?.(sessionId)?.header?.cwd
|
|
207
|
+
if (liveCwd) return liveCwd
|
|
213
208
|
}
|
|
214
|
-
}
|
|
215
|
-
|
|
209
|
+
} catch {}
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
const workspaceRoot = host.sandboxPolicy?.workspaceRoot
|
|
213
|
+
if (workspaceRoot) return workspaceRoot
|
|
214
|
+
} catch {}
|
|
215
|
+
|
|
216
|
+
return process.cwd()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 1. Direct WebServer route registration (Standard, robust DSH plugin route pattern)
|
|
220
|
+
const webServer = (ctx as any).webServer
|
|
221
|
+
if (webServer?.register) {
|
|
222
|
+
ctx.effect(() => webServer.register({
|
|
223
|
+
kind: 'prefix',
|
|
224
|
+
path: '/git',
|
|
225
|
+
handler: async (req: any, res: any) => {
|
|
226
|
+
try {
|
|
227
|
+
if (req.method === 'OPTIONS') {
|
|
228
|
+
res.writeHead(204, {
|
|
229
|
+
'Access-Control-Allow-Origin': '*',
|
|
230
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
231
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
232
|
+
})
|
|
233
|
+
return res.end()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (req.method !== 'POST') {
|
|
237
|
+
res.writeHead(405, { 'Content-Type': 'application/json' })
|
|
238
|
+
return res.end(JSON.stringify({ ok: false, error: { message: 'Method Not Allowed' } }))
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const pathname = new URL(req.url ?? '/', 'http://localhost').pathname
|
|
242
|
+
const match = pathname.match(/^\/git\/([a-zA-Z0-9_-]+)/)
|
|
243
|
+
const endpoint = match ? match[1] : undefined
|
|
216
244
|
|
|
245
|
+
if (!endpoint) {
|
|
246
|
+
res.writeHead(404, { 'Content-Type': 'application/json' })
|
|
247
|
+
return res.end(JSON.stringify({ ok: false, error: { message: 'Not Found' } }))
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let rawBody = ''
|
|
251
|
+
req.on('data', (chunk: Buffer) => { rawBody += chunk.toString('utf8') })
|
|
252
|
+
req.on('end', async () => {
|
|
253
|
+
try {
|
|
254
|
+
let body: any = {}
|
|
255
|
+
try {
|
|
256
|
+
if (rawBody.trim()) body = JSON.parse(rawBody)
|
|
257
|
+
} catch {
|
|
258
|
+
res.writeHead(400, { 'Content-Type': 'application/json' })
|
|
259
|
+
return res.end(JSON.stringify({ ok: false, error: { message: 'Invalid JSON body' } }))
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const rpcId = body?.type === 'client-request' ? body.rpcId : undefined
|
|
263
|
+
const payload = body?.type === 'client-request' ? (body.payload ?? {}) : body
|
|
264
|
+
|
|
265
|
+
const result = await executeGitEndpoint(endpoint, payload, resolveTargetDir)
|
|
266
|
+
|
|
267
|
+
res.writeHead(200, {
|
|
268
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
269
|
+
'Access-Control-Allow-Origin': '*',
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
if (rpcId !== undefined) {
|
|
273
|
+
res.end(JSON.stringify({
|
|
274
|
+
type: 'server-response',
|
|
275
|
+
rpcId,
|
|
276
|
+
result,
|
|
277
|
+
}))
|
|
278
|
+
} else {
|
|
279
|
+
res.end(JSON.stringify(result))
|
|
280
|
+
}
|
|
281
|
+
} catch (err: any) {
|
|
282
|
+
res.writeHead(500, { 'Content-Type': 'application/json' })
|
|
283
|
+
res.end(JSON.stringify({
|
|
284
|
+
ok: false,
|
|
285
|
+
error: { code: 'git/internal_error', message: err?.message ?? String(err) },
|
|
286
|
+
}))
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
} catch (err: any) {
|
|
290
|
+
try {
|
|
291
|
+
res.writeHead(500, { 'Content-Type': 'application/json' })
|
|
292
|
+
res.end(JSON.stringify({
|
|
293
|
+
ok: false,
|
|
294
|
+
error: { code: 'git/internal_error', message: err?.message ?? String(err) },
|
|
295
|
+
}))
|
|
296
|
+
} catch {}
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
}), 'git-workbench: /git routes')
|
|
300
|
+
}
|
|
217
301
|
|
|
218
|
-
// Register the agent tool
|
|
302
|
+
// 2. Register the agent tool
|
|
219
303
|
registerGitTool(ctx, resolveTargetDir)
|
|
220
304
|
}
|