@vibedeckx/linux-x64 0.3.8 → 0.3.10

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.
Files changed (2) hide show
  1. package/dist/bin.js +130 -7
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -185980,10 +185980,29 @@ var createCoreRepos = (kdb, h) => ({
185980
185980
  const row = await readQuery.executeTakeFirst();
185981
185981
  return row ? mapProject(row) : void 0;
185982
185982
  },
185983
+ /**
185984
+ * Deleting a project cascades down two independent paths: to its sessions,
185985
+ * and to `workspaces` → `workspace_checkouts`. `remote_session_mappings`
185986
+ * sits on neither — it has no key to `projects` — so once a mapping's
185987
+ * checkout is a real foreign key, leaving those rows behind would strand
185988
+ * them against a checkout the same statement just deleted.
185989
+ *
185990
+ * The dependents are therefore removed explicitly, in order, inside one
185991
+ * transaction. The scope check happens first so an unauthorized caller
185992
+ * cannot delete another tenant's mappings on the way to a no-op.
185993
+ */
185983
185994
  delete: async (id, userId) => {
185984
- let query = kdb.deleteFrom("projects").where("id", "=", id);
185985
- if (userId) query = query.where("user_id", "=", userId);
185986
- await query.execute();
185995
+ await kdb.transaction().execute(async (trx) => {
185996
+ let scope = trx.selectFrom("projects").select("id").where("id", "=", id);
185997
+ if (userId) scope = scope.where("user_id", "=", userId);
185998
+ if (!await scope.executeTakeFirst()) return;
185999
+ const mappings = await trx.selectFrom("remote_session_mappings").select(["remote_server_id", "remote_session_id"]).where("project_id", "=", id).execute();
186000
+ for (const mapping of mappings) {
186001
+ await trx.deleteFrom("notification_sync_cursors").where("remote_server_id", "=", mapping.remote_server_id).where("remote_session_id", "=", mapping.remote_session_id).execute();
186002
+ }
186003
+ await trx.deleteFrom("remote_session_mappings").where("project_id", "=", id).execute();
186004
+ await trx.deleteFrom("projects").where("id", "=", id).execute();
186005
+ });
185987
186006
  },
185988
186007
  // Deliberately unscoped: the notification importer runs without a request
185989
186008
  // context and must DERIVE ownership from the mapped local project rather
@@ -203458,6 +203477,100 @@ var createWorkspaceRegistryRepo = (kdb, h) => ({
203458
203477
  });
203459
203478
 
203460
203479
  // src/storage/sqlite.ts
203480
+ var countDanglingCheckoutBindings = (db) => {
203481
+ const scalar = (sql2) => Number(db.prepare(sql2).get().count);
203482
+ return {
203483
+ sessions: scalar(`SELECT count(*) AS count FROM agent_sessions s
203484
+ LEFT JOIN workspace_checkouts c ON c.id = s.workspace_checkout_id
203485
+ WHERE s.workspace_checkout_id IS NOT NULL AND c.id IS NULL`),
203486
+ mappings: scalar(`SELECT count(*) AS count FROM remote_session_mappings m
203487
+ LEFT JOIN workspace_checkouts c ON c.id = m.workspace_checkout_id
203488
+ WHERE m.workspace_checkout_id IS NOT NULL AND c.id IS NULL`)
203489
+ };
203490
+ };
203491
+ var tightenWorkspaceCheckoutForeignKeys = (db) => {
203492
+ const ddl = (table) => db.prepare(
203493
+ "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?"
203494
+ ).get(table)?.sql ?? "";
203495
+ const bound = (table) => /REFERENCES\s+workspace_checkouts/i.test(ddl(table));
203496
+ if (bound("agent_sessions") && bound("remote_session_mappings")) return;
203497
+ const indexDdl = (table) => db.prepare(
203498
+ "SELECT sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL"
203499
+ ).all(table).map((row) => row.sql);
203500
+ const dangling = countDanglingCheckoutBindings(db);
203501
+ if (dangling.sessions > 0 || dangling.mappings > 0) {
203502
+ console.warn(
203503
+ `[Storage] Refusing to add the workspace_checkout foreign key: ${dangling.sessions} session(s) and ${dangling.mappings} remote mapping(s) reference a checkout that no longer exists. Resolve or clear those bindings, then restart to complete the migration.`
203504
+ );
203505
+ return;
203506
+ }
203507
+ const sessionIndexes = indexDdl("agent_sessions");
203508
+ const mappingIndexes = indexDdl("remote_session_mappings");
203509
+ db.transaction(() => {
203510
+ if (!bound("agent_sessions")) {
203511
+ db.exec(`
203512
+ CREATE TABLE agent_sessions_fk_new (
203513
+ id TEXT PRIMARY KEY,
203514
+ project_id TEXT NOT NULL,
203515
+ branch TEXT NOT NULL DEFAULT '',
203516
+ workspace_checkout_id TEXT DEFAULT NULL,
203517
+ status TEXT NOT NULL DEFAULT 'running',
203518
+ permission_mode TEXT DEFAULT 'edit',
203519
+ agent_type TEXT DEFAULT 'claude-code',
203520
+ title TEXT DEFAULT NULL,
203521
+ model TEXT DEFAULT NULL,
203522
+ created_at TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')),
203523
+ updated_at TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')),
203524
+ activity_at INTEGER DEFAULT (cast((julianday('now') - 2440587.5) * 86400000 as integer)),
203525
+ last_user_message_at INTEGER DEFAULT NULL,
203526
+ last_completed_at INTEGER DEFAULT NULL,
203527
+ favorited_at INTEGER DEFAULT NULL,
203528
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
203529
+ FOREIGN KEY (workspace_checkout_id) REFERENCES workspace_checkouts(id)
203530
+ DEFERRABLE INITIALLY DEFERRED
203531
+ );
203532
+ INSERT INTO agent_sessions_fk_new
203533
+ (id, project_id, branch, workspace_checkout_id, status, permission_mode, agent_type,
203534
+ title, model, created_at, updated_at, activity_at, last_user_message_at,
203535
+ last_completed_at, favorited_at)
203536
+ SELECT id, project_id, branch, workspace_checkout_id, status, permission_mode, agent_type,
203537
+ title, model, created_at, updated_at, activity_at, last_user_message_at,
203538
+ last_completed_at, favorited_at
203539
+ FROM agent_sessions;
203540
+ DROP TABLE agent_sessions;
203541
+ ALTER TABLE agent_sessions_fk_new RENAME TO agent_sessions;
203542
+ `);
203543
+ for (const sql2 of sessionIndexes) db.exec(sql2);
203544
+ }
203545
+ if (!bound("remote_session_mappings")) {
203546
+ db.exec(`
203547
+ CREATE TABLE remote_session_mappings_fk_new (
203548
+ local_session_id TEXT PRIMARY KEY,
203549
+ project_id TEXT NOT NULL,
203550
+ remote_server_id TEXT NOT NULL,
203551
+ remote_session_id TEXT NOT NULL,
203552
+ branch TEXT,
203553
+ workspace_checkout_id TEXT DEFAULT NULL,
203554
+ title_resolved INTEGER NOT NULL DEFAULT 0,
203555
+ notification_sync_start TEXT NOT NULL DEFAULT 'from_now',
203556
+ notification_watch_until INTEGER,
203557
+ FOREIGN KEY (workspace_checkout_id) REFERENCES workspace_checkouts(id)
203558
+ DEFERRABLE INITIALLY DEFERRED
203559
+ );
203560
+ INSERT INTO remote_session_mappings_fk_new
203561
+ (local_session_id, project_id, remote_server_id, remote_session_id, branch,
203562
+ workspace_checkout_id, title_resolved, notification_sync_start, notification_watch_until)
203563
+ SELECT local_session_id, project_id, remote_server_id, remote_session_id, branch,
203564
+ workspace_checkout_id, title_resolved, notification_sync_start, notification_watch_until
203565
+ FROM remote_session_mappings;
203566
+ DROP TABLE remote_session_mappings;
203567
+ ALTER TABLE remote_session_mappings_fk_new RENAME TO remote_session_mappings;
203568
+ `);
203569
+ for (const sql2 of mappingIndexes) db.exec(sql2);
203570
+ }
203571
+ })();
203572
+ console.log("[Storage] workspace_checkout foreign keys are now enforced.");
203573
+ };
203461
203574
  var createDatabase = (dbPath) => {
203462
203575
  const db = new Database(dbPath);
203463
203576
  db.pragma("journal_mode = WAL");
@@ -204954,6 +205067,7 @@ var createDatabase = (dbPath) => {
204954
205067
  }
204955
205068
  db.exec(`CREATE INDEX IF NOT EXISTS idx_remote_session_mappings_workspace_checkout
204956
205069
  ON remote_session_mappings(workspace_checkout_id)`);
205070
+ tightenWorkspaceCheckoutForeignKeys(db);
204957
205071
  const sessionSearchCacheInfo = db.prepare("PRAGMA table_info(session_search_cache)").all();
204958
205072
  if (!sessionSearchCacheInfo.some((col) => col.name === "written_at")) {
204959
205073
  db.exec("ALTER TABLE session_search_cache ADD COLUMN written_at INTEGER");
@@ -207242,6 +207356,13 @@ function parseGitWorktreeList(projectPath) {
207242
207356
  worktreeListCache.set(projectPath, { entries, expiresAt: now3 + WORKTREE_LIST_TTL_MS });
207243
207357
  return entries;
207244
207358
  }
207359
+ function readWorktreeListTolerant(projectPath) {
207360
+ try {
207361
+ return parseGitWorktreeList(projectPath);
207362
+ } catch {
207363
+ return [{ path: projectPath, branch: null }];
207364
+ }
207365
+ }
207245
207366
  function pruneWorktrees(projectPath) {
207246
207367
  try {
207247
207368
  execSync("git worktree prune", {
@@ -207249,6 +207370,7 @@ function pruneWorktrees(projectPath) {
207249
207370
  encoding: "utf-8",
207250
207371
  stdio: ["pipe", "pipe", "pipe"]
207251
207372
  });
207373
+ } catch {
207252
207374
  } finally {
207253
207375
  worktreeListCache.delete(projectPath);
207254
207376
  }
@@ -207288,7 +207410,8 @@ function reconcileWorktreeBranches(projectPath, entries, sessionBranches = [], r
207288
207410
  }
207289
207411
  const rootEntry = entries[0];
207290
207412
  const rootAnchor = rootEntry ? registryByPath.get(path6.resolve(rootEntry.path)) : void 0;
207291
- const worktrees = [rootAnchor && rootEntry?.branch !== rootAnchor.expectedBranch ? { branch: null, currentBranch: rootEntry?.branch ?? null } : { branch: null }];
207413
+ const rootDrifted = Boolean(rootAnchor) && rootEntry?.branch != null && rootEntry.branch !== rootAnchor.expectedBranch;
207414
+ const worktrees = [rootDrifted ? { branch: null, currentBranch: rootEntry?.branch ?? null } : { branch: null }];
207292
207415
  for (let i = 1; i < entries.length; i++) {
207293
207416
  const entry = entries[i];
207294
207417
  const anchor = registryByPath.get(path6.resolve(entry.path));
@@ -207303,7 +207426,7 @@ function reconcileWorktreeBranches(projectPath, entries, sessionBranches = [], r
207303
207426
  return worktrees;
207304
207427
  }
207305
207428
  async function getRegisteredWorktreeBranches(storage, projectId, projectPath) {
207306
- const entries = parseGitWorktreeList(projectPath);
207429
+ const entries = readWorktreeListTolerant(projectPath);
207307
207430
  const sessions = await storage.agentSessions.getProjectedByProjectId(projectId, "runtime");
207308
207431
  const sessionBranches = sessions.map((session) => session.branch);
207309
207432
  const sessionBranchByPath = /* @__PURE__ */ new Map();
@@ -207337,14 +207460,14 @@ async function getRegisteredWorktreeBranches(storage, projectId, projectPath) {
207337
207460
  }
207338
207461
  continue;
207339
207462
  }
207340
- if (registeredPaths.has(resolvedPath) || entry.branch === null) continue;
207463
+ if (registeredPaths.has(resolvedPath) || entry.branch === null && index > 0) continue;
207341
207464
  const stableBranch = index === 0 ? "" : sessionBranchByPath.get(resolvedPath) ?? entry.branch;
207342
207465
  await storage.workspaceRegistry.registerReadyCheckout({
207343
207466
  projectId,
207344
207467
  branch: stableBranch,
207345
207468
  targetId: "local",
207346
207469
  worktreePath: entry.path,
207347
- expectedBranch: index === 0 ? entry.branch : stableBranch
207470
+ expectedBranch: index === 0 ? entry.branch ?? "" : stableBranch
207348
207471
  });
207349
207472
  registeredPaths.add(resolvedPath);
207350
207473
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibedeckx/linux-x64",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "description": "Vibedeckx platform binaries for Linux x64",
5
5
  "os": [
6
6
  "linux"