@wingsky-1/dsh-worktree-sidebar 0.2.6 → 0.2.7
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
CHANGED
|
@@ -108,24 +108,33 @@ function bindTypert(lookups) {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// ../../shared/loopback.js
|
|
111
|
-
function
|
|
111
|
+
function isLoopbackPeerAddress(request) {
|
|
112
112
|
const address = request.socket?.remoteAddress;
|
|
113
|
-
|
|
113
|
+
return address === "127.0.0.1" || address === "::1" || address === "::ffff:127.0.0.1";
|
|
114
|
+
}
|
|
115
|
+
function loopbackHostUrlOf(request) {
|
|
114
116
|
const host = request.headers.host;
|
|
115
|
-
if (typeof host !== "string") return
|
|
117
|
+
if (typeof host !== "string") return void 0;
|
|
116
118
|
let hostUrl;
|
|
117
119
|
try {
|
|
118
120
|
hostUrl = new URL(`http://${host}`);
|
|
119
121
|
} catch {
|
|
120
|
-
return
|
|
122
|
+
return void 0;
|
|
121
123
|
}
|
|
122
|
-
if (hostUrl.hostname !== "127.0.0.1" && hostUrl.hostname !== "localhost" && hostUrl.hostname !== "[::1]")
|
|
123
|
-
return
|
|
124
|
-
if (request.headers["sec-fetch-site"] === "cross-site") {
|
|
125
|
-
if (!(options.allowCrossSiteNoCors === true && request.headers["sec-fetch-mode"] === "no-cors")) {
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
124
|
+
if (hostUrl.hostname !== "127.0.0.1" && hostUrl.hostname !== "localhost" && hostUrl.hostname !== "[::1]") {
|
|
125
|
+
return void 0;
|
|
128
126
|
}
|
|
127
|
+
return hostUrl;
|
|
128
|
+
}
|
|
129
|
+
function crossSiteRequestAllowed(request, options) {
|
|
130
|
+
if (request.headers["sec-fetch-site"] !== "cross-site") return true;
|
|
131
|
+
return options.allowCrossSiteNoCors === true && request.headers["sec-fetch-mode"] === "no-cors";
|
|
132
|
+
}
|
|
133
|
+
function isLoopbackRequest(request, options = {}) {
|
|
134
|
+
if (!isLoopbackPeerAddress(request)) return false;
|
|
135
|
+
const hostUrl = loopbackHostUrlOf(request);
|
|
136
|
+
if (hostUrl === void 0) return false;
|
|
137
|
+
if (!crossSiteRequestAllowed(request, options)) return false;
|
|
129
138
|
const origin = request.headers.origin;
|
|
130
139
|
if (origin === void 0) return true;
|
|
131
140
|
try {
|
|
@@ -306,9 +315,9 @@ function validateRecord(value) {
|
|
|
306
315
|
const branch = raw["branch"];
|
|
307
316
|
const createdAt = raw["createdAt"];
|
|
308
317
|
const sessionCreatedAt = raw["sessionCreatedAt"];
|
|
309
|
-
if (
|
|
318
|
+
if (!isNonEmptyString(repoRoot))
|
|
310
319
|
return void 0;
|
|
311
|
-
if (
|
|
320
|
+
if (!isNonEmptyString(worktreeRoot))
|
|
312
321
|
return void 0;
|
|
313
322
|
if (typeof branch !== "string")
|
|
314
323
|
return void 0;
|
|
@@ -318,33 +327,49 @@ function validateRecord(value) {
|
|
|
318
327
|
return void 0;
|
|
319
328
|
return { repoRoot, worktreeRoot, branch, createdAt, sessionCreatedAt };
|
|
320
329
|
}
|
|
330
|
+
function isNonEmptyString(value) {
|
|
331
|
+
return typeof value === "string" && value.length > 0;
|
|
332
|
+
}
|
|
321
333
|
function parseTable(text) {
|
|
322
|
-
|
|
334
|
+
const parsed = parseJsonOrUndefined(text);
|
|
335
|
+
if (parsed === void 0)
|
|
336
|
+
return emptyTable();
|
|
337
|
+
const header = tableHeaderOf(parsed);
|
|
338
|
+
if (header === void 0)
|
|
339
|
+
return emptyTable();
|
|
340
|
+
return { version: BINDINGS_VERSION, revision: header.revision, bindings: recordsOf(header) };
|
|
341
|
+
}
|
|
342
|
+
function parseJsonOrUndefined(text) {
|
|
323
343
|
try {
|
|
324
|
-
|
|
344
|
+
return JSON.parse(text);
|
|
325
345
|
} catch {
|
|
326
|
-
return
|
|
346
|
+
return void 0;
|
|
327
347
|
}
|
|
348
|
+
}
|
|
349
|
+
function tableHeaderOf(parsed) {
|
|
328
350
|
if (typeof parsed !== "object" || parsed === null)
|
|
329
|
-
return
|
|
351
|
+
return void 0;
|
|
330
352
|
const raw = parsed;
|
|
331
353
|
if (raw["version"] !== BINDINGS_VERSION)
|
|
332
|
-
return
|
|
354
|
+
return void 0;
|
|
333
355
|
const revision2 = raw["revision"];
|
|
334
356
|
if (typeof revision2 !== "number" || !Number.isFinite(revision2) || revision2 < 0)
|
|
335
|
-
return
|
|
357
|
+
return void 0;
|
|
336
358
|
const entries = raw["bindings"];
|
|
337
359
|
if (typeof entries !== "object" || entries === null)
|
|
338
|
-
return
|
|
360
|
+
return void 0;
|
|
361
|
+
return { revision: revision2, entries };
|
|
362
|
+
}
|
|
363
|
+
function recordsOf(header) {
|
|
339
364
|
const bindings = {};
|
|
340
|
-
for (const [sessionId, value] of Object.entries(entries)) {
|
|
365
|
+
for (const [sessionId, value] of Object.entries(header.entries)) {
|
|
341
366
|
if (sessionId.length === 0)
|
|
342
367
|
continue;
|
|
343
368
|
const record = validateRecord(value);
|
|
344
369
|
if (record !== void 0)
|
|
345
370
|
bindings[sessionId] = record;
|
|
346
371
|
}
|
|
347
|
-
return
|
|
372
|
+
return bindings;
|
|
348
373
|
}
|
|
349
374
|
function serializeTable(table) {
|
|
350
375
|
return JSON.stringify(table, null, 2) + "\n";
|
|
@@ -1223,21 +1248,17 @@ function argBool(args, key) {
|
|
|
1223
1248
|
|
|
1224
1249
|
// lib/server/tools/impl/session/index.js
|
|
1225
1250
|
function sessionOf(exec) {
|
|
1226
|
-
if (typeof exec !== "object" || exec === null)
|
|
1227
|
-
return void 0;
|
|
1228
1251
|
const session = readObject(readObject(exec, "agent"), "session");
|
|
1229
1252
|
if (session === void 0)
|
|
1230
1253
|
return void 0;
|
|
1231
|
-
const id = session
|
|
1254
|
+
const id = readField(session, "id");
|
|
1232
1255
|
if (typeof id !== "string" || id.length === 0)
|
|
1233
1256
|
return void 0;
|
|
1234
1257
|
const header = readObject(session, "header");
|
|
1235
|
-
const cwd = header === void 0 ? void 0 : header.cwd;
|
|
1236
|
-
const createdAt = header === void 0 ? void 0 : header.createdAt;
|
|
1237
1258
|
return {
|
|
1238
1259
|
id,
|
|
1239
|
-
cwd:
|
|
1240
|
-
createdAt:
|
|
1260
|
+
cwd: nonEmptyTextOrUndefined(readField(header, "cwd")),
|
|
1261
|
+
createdAt: finiteNumberOrUndefined(readField(header, "createdAt"))
|
|
1241
1262
|
};
|
|
1242
1263
|
}
|
|
1243
1264
|
function readObject(source, key) {
|
|
@@ -1246,8 +1267,44 @@ function readObject(source, key) {
|
|
|
1246
1267
|
const value = source[key];
|
|
1247
1268
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
1248
1269
|
}
|
|
1270
|
+
function readField(source, key) {
|
|
1271
|
+
if (typeof source !== "object" || source === null)
|
|
1272
|
+
return void 0;
|
|
1273
|
+
return source[key];
|
|
1274
|
+
}
|
|
1275
|
+
function nonEmptyTextOrUndefined(value) {
|
|
1276
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1277
|
+
}
|
|
1278
|
+
function finiteNumberOrUndefined(value) {
|
|
1279
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1280
|
+
}
|
|
1249
1281
|
|
|
1250
1282
|
// lib/server/tools/impl/create/index.js
|
|
1283
|
+
async function checkBranch(deps, origin, branch) {
|
|
1284
|
+
if (branch === void 0)
|
|
1285
|
+
return { ok: true };
|
|
1286
|
+
if (await deps.git.checkRefFormat(branch))
|
|
1287
|
+
return { ok: true };
|
|
1288
|
+
return {
|
|
1289
|
+
ok: false,
|
|
1290
|
+
result: resultOf(origin, false, "Not a valid git branch name: " + branch + " (git check-ref-format --branch rejected it).")
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
async function resolveStartPoint(deps, repo, origin, base) {
|
|
1294
|
+
if (base === void 0)
|
|
1295
|
+
return { ok: true };
|
|
1296
|
+
if (base.startsWith("-")) {
|
|
1297
|
+
return {
|
|
1298
|
+
ok: false,
|
|
1299
|
+
result: resultOf(origin, false, "Not a valid start point: " + base + ' (a start point must not begin with "-").')
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
const startPoint = await deps.git.resolveCommit(repo, base);
|
|
1303
|
+
if (startPoint === void 0) {
|
|
1304
|
+
return { ok: false, result: resultOf(origin, false, "Not a valid start point: " + base + ".") };
|
|
1305
|
+
}
|
|
1306
|
+
return { ok: true, startPoint };
|
|
1307
|
+
}
|
|
1251
1308
|
function buildCreateTool(deps) {
|
|
1252
1309
|
return {
|
|
1253
1310
|
name: "ws_worktree_create",
|
|
@@ -1297,20 +1354,13 @@ function buildCreateTool(deps) {
|
|
|
1297
1354
|
const origin = read.origin;
|
|
1298
1355
|
const target = resolveTarget(repo, raw);
|
|
1299
1356
|
const branch = argString(args, "branch");
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
const
|
|
1304
|
-
if (
|
|
1305
|
-
return
|
|
1306
|
-
|
|
1307
|
-
let startPoint;
|
|
1308
|
-
if (base !== void 0) {
|
|
1309
|
-
startPoint = await deps.git.resolveCommit(repo, base);
|
|
1310
|
-
if (startPoint === void 0) {
|
|
1311
|
-
return resultOf(origin, false, "Not a valid start point: " + base + ".");
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1357
|
+
const branchCheck = await checkBranch(deps, origin, branch);
|
|
1358
|
+
if (!branchCheck.ok)
|
|
1359
|
+
return branchCheck.result;
|
|
1360
|
+
const start = await resolveStartPoint(deps, repo, origin, argString(args, "base"));
|
|
1361
|
+
if (!start.ok)
|
|
1362
|
+
return start.result;
|
|
1363
|
+
const startPoint = start.startPoint;
|
|
1314
1364
|
const created = await deps.git.addWorktree(repo, target, branch, startPoint);
|
|
1315
1365
|
if (!created.ok) {
|
|
1316
1366
|
return resultOf(origin, false, "git worktree add failed: " + created.reason);
|
|
@@ -1331,6 +1381,32 @@ function buildCreateTool(deps) {
|
|
|
1331
1381
|
}
|
|
1332
1382
|
|
|
1333
1383
|
// lib/server/tools/impl/remove/index.js
|
|
1384
|
+
async function unbindAndRemove(deps, sessionId, origin, force) {
|
|
1385
|
+
const removed = await deps.git.removeWorktree(origin.record.repoRoot, origin.record.worktreeRoot, force);
|
|
1386
|
+
if (!removed.ok) {
|
|
1387
|
+
return resultOf(origin, false, "git worktree remove failed: " + removed.reason + " The binding is unchanged.");
|
|
1388
|
+
}
|
|
1389
|
+
const dropped = await deps.binding.drop(sessionId);
|
|
1390
|
+
if (!dropped.ok) {
|
|
1391
|
+
return resultOf(origin, false, "Removed the worktree directory, but the binding could not be dropped: " + dropped.reason + " It is now stale and will be treated as unbound; call this tool again to retry the unbind.");
|
|
1392
|
+
}
|
|
1393
|
+
const after = await readOrigin(deps, sessionId);
|
|
1394
|
+
if (after.problem !== void 0) {
|
|
1395
|
+
return resultOf(after.origin, true, "Unbound this session and removed the worktree directory with git worktree remove. The Files tab root could not be read back: " + after.problem);
|
|
1396
|
+
}
|
|
1397
|
+
return resultOf(after.origin, true, after.origin.kind === "inherited" ? "Unbound this session and removed the worktree directory with git worktree remove. The Files tab now follows the root inherited from session " + after.origin.ownerSessionId + "." : "Unbound this session and removed the worktree directory with git worktree remove.");
|
|
1398
|
+
}
|
|
1399
|
+
async function unbindOnly(deps, sessionId, origin) {
|
|
1400
|
+
const dropped = await deps.binding.drop(sessionId);
|
|
1401
|
+
if (!dropped.ok) {
|
|
1402
|
+
return resultOf(origin, false, "Could not persist the unbind: " + dropped.reason);
|
|
1403
|
+
}
|
|
1404
|
+
const after = await readOrigin(deps, sessionId);
|
|
1405
|
+
if (after.problem !== void 0) {
|
|
1406
|
+
return resultOf(after.origin, true, "Unbound this session's own binding. The Files tab root could not be read back: " + after.problem);
|
|
1407
|
+
}
|
|
1408
|
+
return resultOf(after.origin, true, after.origin.kind === "inherited" ? "Unbound this session's own binding. The Files tab now follows the root inherited from session " + after.origin.ownerSessionId + "." : "Unbound the worktree from this session. The directory was left in place; open or refresh the Files tab to return to the session cwd.");
|
|
1409
|
+
}
|
|
1334
1410
|
function inheritedHelp(ownerSessionId, cwd) {
|
|
1335
1411
|
const main = "Nothing to unbind here: the Files tab root belongs to session " + ownerSessionId + ", not to this session. To stop following it: (1) unbind it in that session with ws_worktree_remove, or (2) bind this session to another worktree with ws_worktree_create / ws_worktree_register.";
|
|
1336
1412
|
if (cwd === void 0)
|
|
@@ -1379,30 +1455,9 @@ function buildRemoveTool(deps) {
|
|
|
1379
1455
|
if (force && !removeDirectory) {
|
|
1380
1456
|
return resultOf(origin, false, "force only applies together with removeDirectory: true; the binding is unchanged.");
|
|
1381
1457
|
}
|
|
1382
|
-
if (!removeDirectory)
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
return resultOf(origin, false, "Could not persist the unbind: " + dropped2.reason);
|
|
1386
|
-
}
|
|
1387
|
-
const after2 = await readOrigin(deps, session.id);
|
|
1388
|
-
if (after2.problem !== void 0) {
|
|
1389
|
-
return resultOf(after2.origin, true, "Unbound this session's own binding. The Files tab root could not be read back: " + after2.problem);
|
|
1390
|
-
}
|
|
1391
|
-
return resultOf(after2.origin, true, after2.origin.kind === "inherited" ? "Unbound this session's own binding. The Files tab now follows the root inherited from session " + after2.origin.ownerSessionId + "." : "Unbound the worktree from this session. The directory was left in place; open or refresh the Files tab to return to the session cwd.");
|
|
1392
|
-
}
|
|
1393
|
-
const removed = await deps.git.removeWorktree(origin.record.repoRoot, origin.record.worktreeRoot, force);
|
|
1394
|
-
if (!removed.ok) {
|
|
1395
|
-
return resultOf(origin, false, "git worktree remove failed: " + removed.reason + " The binding is unchanged.");
|
|
1396
|
-
}
|
|
1397
|
-
const dropped = await deps.binding.drop(session.id);
|
|
1398
|
-
if (!dropped.ok) {
|
|
1399
|
-
return resultOf(origin, false, "Removed the worktree directory, but the binding could not be dropped: " + dropped.reason + " It is now stale and will be treated as unbound; call this tool again to retry the unbind.");
|
|
1400
|
-
}
|
|
1401
|
-
const after = await readOrigin(deps, session.id);
|
|
1402
|
-
if (after.problem !== void 0) {
|
|
1403
|
-
return resultOf(after.origin, true, "Unbound this session and removed the worktree directory with git worktree remove. The Files tab root could not be read back: " + after.problem);
|
|
1404
|
-
}
|
|
1405
|
-
return resultOf(after.origin, true, after.origin.kind === "inherited" ? "Unbound this session and removed the worktree directory with git worktree remove. The Files tab now follows the root inherited from session " + after.origin.ownerSessionId + "." : "Unbound this session and removed the worktree directory with git worktree remove.");
|
|
1458
|
+
if (!removeDirectory)
|
|
1459
|
+
return unbindOnly(deps, session.id, origin);
|
|
1460
|
+
return unbindAndRemove(deps, session.id, origin, force);
|
|
1406
1461
|
}
|
|
1407
1462
|
};
|
|
1408
1463
|
}
|
|
@@ -2,6 +2,11 @@ import type { WorktreeOrigin } from "../../../scope/interface.js";
|
|
|
2
2
|
import type { AgentFace, ToolsDeps } from "../../deps.js";
|
|
3
3
|
import type { SessionFace } from "../session/index.js";
|
|
4
4
|
import type { ToolResultValue } from "../protocol/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* 绑定来源的形态。本域内各工具一律从**这里**取,不各自直引 scope/interface:
|
|
7
|
+
* tools → scope 是域间引用,纪律要求它只出现在一个面上(bind 域已经替本域读过一次)。
|
|
8
|
+
*/
|
|
9
|
+
export type { WorktreeOrigin };
|
|
5
10
|
/** 当前的绑定状态(三个工具的返回信封都要它)。 */
|
|
6
11
|
export interface BindingState {
|
|
7
12
|
readonly bound: boolean;
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `ws_worktree_remove`:摘掉登记;只有显式 `removeDirectory` 才连带 `git worktree remove`。
|
|
3
|
-
*
|
|
4
|
-
* 默认不删目录是刻意的:删除可能丢掉未提交改动,而「我只是想换个根」与「我要销毁这个工作区」
|
|
5
|
-
* 是两件事,不该由同一个默认值承担。
|
|
6
|
-
*
|
|
7
|
-
* **继承态不摘任何东西**:本会话没有自己的登记时,右栏的根属于父会话——摘它是改别人的状态,
|
|
8
|
-
* 删目录更是销毁别人的工作区。这一态只如实说明并给出路。
|
|
9
|
-
*/
|
|
10
1
|
import type { ToolDefinition } from "@deepseek-ai/dsh-tools";
|
|
11
2
|
import type { ToolsDeps } from "../../deps.js";
|
|
12
3
|
export declare function buildRemoveTool(deps: ToolsDeps): ToolDefinition;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wingsky-1/dsh-worktree-sidebar",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Point the current session's right-sidebar file tree at a git worktree without changing the session cwd. 把某个 git worktree 登记给当前会话,使该会话的官方右侧栏文件树根指向该 worktree;会话 cwd 不变。官方提供原生 worktree 会话能力即退役的过渡适配层。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"@deepseek-ai/dsh-api-workspace-files": "0.1.7-rc.2",
|
|
60
60
|
"@deepseek-ai/dsh-host-webserver": "0.1.7-rc.2",
|
|
61
61
|
"@deepseek-ai/dsh-tools": "0.1.7-rc.2",
|
|
62
|
-
"@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.2"
|
|
62
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.2",
|
|
63
|
+
"@deepseek-ai/dsh-session": "0.1.7-rc.2"
|
|
63
64
|
},
|
|
64
65
|
"peerDependenciesMeta": {
|
|
65
66
|
"@deepseek-ai/cordis": {
|
|
@@ -79,6 +80,9 @@
|
|
|
79
80
|
},
|
|
80
81
|
"@deepseek-ai/dsh-typert-protocol": {
|
|
81
82
|
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"@deepseek-ai/dsh-session": {
|
|
85
|
+
"optional": true
|
|
82
86
|
}
|
|
83
87
|
},
|
|
84
88
|
"engines": {
|