@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 isLoopbackRequest(request, options = {}) {
111
+ function isLoopbackPeerAddress(request) {
112
112
  const address = request.socket?.remoteAddress;
113
- if (address !== "127.0.0.1" && address !== "::1" && address !== "::ffff:127.0.0.1") return false;
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 false;
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 false;
122
+ return void 0;
121
123
  }
122
- if (hostUrl.hostname !== "127.0.0.1" && hostUrl.hostname !== "localhost" && hostUrl.hostname !== "[::1]")
123
- return false;
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 (typeof repoRoot !== "string" || repoRoot.length === 0)
318
+ if (!isNonEmptyString(repoRoot))
310
319
  return void 0;
311
- if (typeof worktreeRoot !== "string" || worktreeRoot.length === 0)
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
- let parsed;
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
- parsed = JSON.parse(text);
344
+ return JSON.parse(text);
325
345
  } catch {
326
- return emptyTable();
346
+ return void 0;
327
347
  }
348
+ }
349
+ function tableHeaderOf(parsed) {
328
350
  if (typeof parsed !== "object" || parsed === null)
329
- return emptyTable();
351
+ return void 0;
330
352
  const raw = parsed;
331
353
  if (raw["version"] !== BINDINGS_VERSION)
332
- return emptyTable();
354
+ return void 0;
333
355
  const revision2 = raw["revision"];
334
356
  if (typeof revision2 !== "number" || !Number.isFinite(revision2) || revision2 < 0)
335
- return emptyTable();
357
+ return void 0;
336
358
  const entries = raw["bindings"];
337
359
  if (typeof entries !== "object" || entries === null)
338
- return emptyTable();
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 { version: BINDINGS_VERSION, revision: revision2, bindings };
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.id;
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: typeof cwd === "string" && cwd.length > 0 ? cwd : void 0,
1240
- createdAt: typeof createdAt === "number" && Number.isFinite(createdAt) ? createdAt : void 0
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
- if (branch !== void 0 && !await deps.git.checkRefFormat(branch)) {
1301
- return resultOf(origin, false, "Not a valid git branch name: " + branch + " (git check-ref-format --branch rejected it).");
1302
- }
1303
- const base = argString(args, "base");
1304
- if (base !== void 0 && base.startsWith("-")) {
1305
- return resultOf(origin, false, "Not a valid start point: " + base + ' (a start point must not begin with "-").');
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
- const dropped2 = await deps.binding.drop(session.id);
1384
- if (!dropped2.ok) {
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.6",
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": {