@vibedeckx/linux-x64 0.2.1 → 0.2.3

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 +932 -86
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -9400,7 +9400,7 @@ var require_InstrumentDescriptor = __commonJS({
9400
9400
  var api_1 = (init_esm(), __toCommonJS(esm_exports));
9401
9401
  var utils_1 = require_utils5();
9402
9402
  function createInstrumentDescriptor(name25, type, options) {
9403
- if (!isValidName(name25)) {
9403
+ if (!isValidName2(name25)) {
9404
9404
  api_1.diag.warn(`Invalid metric name: "${name25}". The metric name should be a ASCII string with a length no greater than 255 characters.`);
9405
9405
  }
9406
9406
  return {
@@ -9429,10 +9429,10 @@ var require_InstrumentDescriptor = __commonJS({
9429
9429
  }
9430
9430
  exports.isDescriptorCompatibleWith = isDescriptorCompatibleWith;
9431
9431
  var NAME_REGEXP = /^[a-z][a-z0-9_.\-/]{0,254}$/i;
9432
- function isValidName(name25) {
9432
+ function isValidName2(name25) {
9433
9433
  return NAME_REGEXP.test(name25);
9434
9434
  }
9435
- exports.isValidName = isValidName;
9435
+ exports.isValidName = isValidName2;
9436
9436
  }
9437
9437
  });
9438
9438
 
@@ -185912,6 +185912,217 @@ var createCrossRemoteAuditRepo = (kdb) => ({
185912
185912
  }
185913
185913
  });
185914
185914
 
185915
+ // src/storage/repositories/merge-targets.ts
185916
+ var createMergeTargetsRepo = (kdb) => ({
185917
+ mergeTargets: {
185918
+ getForBranches: async (projectId, branches) => {
185919
+ if (branches.length === 0) return /* @__PURE__ */ new Map();
185920
+ const rows = await kdb.selectFrom("branch_merge_targets").select(["branch", "target"]).where("project_id", "=", projectId).where("branch", "in", branches).execute();
185921
+ return new Map(rows.map((row) => [row.branch, row.target]));
185922
+ },
185923
+ upsert: async (projectId, branch, target) => {
185924
+ const result = await kdb.insertInto("branch_merge_targets").values({ project_id: projectId, branch, target }).onConflict(
185925
+ (conflict) => conflict.columns(["project_id", "branch"]).doUpdateSet({
185926
+ target,
185927
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
185928
+ }).where("target", "!=", target)
185929
+ ).executeTakeFirst();
185930
+ return (result.numInsertedOrUpdatedRows ?? 0n) > 0n;
185931
+ },
185932
+ insertIfAbsent: async (projectId, branch, target) => {
185933
+ const result = await kdb.insertInto("branch_merge_targets").values({ project_id: projectId, branch, target }).onConflict((conflict) => conflict.columns(["project_id", "branch"]).doNothing()).executeTakeFirst();
185934
+ return (result.numInsertedOrUpdatedRows ?? 0n) > 0n;
185935
+ },
185936
+ delete: async (projectId, branch) => {
185937
+ const result = await kdb.deleteFrom("branch_merge_targets").where("project_id", "=", projectId).where("branch", "=", branch).executeTakeFirst();
185938
+ return result.numDeletedRows > 0n;
185939
+ }
185940
+ }
185941
+ });
185942
+
185943
+ // src/storage/repositories/search-cache.ts
185944
+ var toDbBranch = (branch) => branch ?? "";
185945
+ var fromDbBranch = (branch) => branch === "" ? null : branch;
185946
+ var escapeLike = (s3) => s3.replace(/[\\%_]/g, (c) => `\\${c}`);
185947
+ var matchTier = (text2, q) => {
185948
+ if (!q) return 2;
185949
+ if (!text2) return 3;
185950
+ const t = text2.toLowerCase();
185951
+ if (t === q) return 0;
185952
+ if (t.startsWith(q)) return 1;
185953
+ if (t.includes(q)) return 2;
185954
+ return 3;
185955
+ };
185956
+ var parseDbTimestamp = (ts) => {
185957
+ if (!ts) return null;
185958
+ const ms = Date.parse(ts.replace(" ", "T") + "Z");
185959
+ return Number.isNaN(ms) ? null : ms;
185960
+ };
185961
+ var rankAndCap = (items, limit) => items.filter((x2) => x2.tier < 3).sort((a, b2) => a.tier - b2.tier || Number(b2.favorited) - Number(a.favorited) || b2.recency - a.recency).slice(0, limit).map((x2) => x2.item);
185962
+ var createSearchCacheRepos = (kdb, _h) => ({
185963
+ searchCache: {
185964
+ // Generation-based reconciliation: only a FULLY successful snapshot may
185965
+ // mark rows deleted. Runs in one transaction so a crash mid-apply can't
185966
+ // leave a half-deleted cache.
185967
+ applyCatalogSnapshot: async (projectId, targetId, snapshot) => {
185968
+ const now2 = Date.now();
185969
+ await kdb.transaction().execute(async (trx) => {
185970
+ const state = await trx.selectFrom("search_catalog_sync_state").select("snapshot_generation").where("project_id", "=", projectId).where("target_id", "=", targetId).executeTakeFirst();
185971
+ const generation = (state?.snapshot_generation ?? 0) + 1;
185972
+ for (const w2 of snapshot.workspaces) {
185973
+ await trx.insertInto("workspace_search_cache").values({ project_id: projectId, target_id: targetId, branch: toDbBranch(w2.branch), generation, deleted_at: null }).onConflict((oc) => oc.columns(["project_id", "target_id", "branch"]).doUpdateSet({ generation, deleted_at: null })).execute();
185974
+ }
185975
+ for (const s3 of snapshot.sessions) {
185976
+ await trx.insertInto("session_search_cache").values({
185977
+ local_session_id: s3.id,
185978
+ project_id: projectId,
185979
+ target_id: targetId,
185980
+ branch: toDbBranch(s3.branch),
185981
+ title: s3.title,
185982
+ last_active_at: s3.lastActiveAt,
185983
+ favorited_at: s3.favoritedAt,
185984
+ entry_count: s3.entryCount,
185985
+ generation,
185986
+ deleted_at: null
185987
+ }).onConflict((oc) => oc.column("local_session_id").doUpdateSet({
185988
+ project_id: projectId,
185989
+ target_id: targetId,
185990
+ branch: toDbBranch(s3.branch),
185991
+ title: s3.title,
185992
+ last_active_at: s3.lastActiveAt,
185993
+ favorited_at: s3.favoritedAt,
185994
+ entry_count: s3.entryCount,
185995
+ generation,
185996
+ deleted_at: null
185997
+ })).execute();
185998
+ }
185999
+ await trx.updateTable("workspace_search_cache").set({ deleted_at: now2 }).where("project_id", "=", projectId).where("target_id", "=", targetId).where("generation", "<", generation).where("deleted_at", "is", null).execute();
186000
+ await trx.updateTable("session_search_cache").set({ deleted_at: now2 }).where("project_id", "=", projectId).where("target_id", "=", targetId).where("generation", "<", generation).where("deleted_at", "is", null).execute();
186001
+ await trx.insertInto("search_catalog_sync_state").values({
186002
+ project_id: projectId,
186003
+ target_id: targetId,
186004
+ last_success_at: now2,
186005
+ last_attempt_at: now2,
186006
+ snapshot_generation: generation,
186007
+ last_error: null
186008
+ }).onConflict((oc) => oc.columns(["project_id", "target_id"]).doUpdateSet({
186009
+ last_success_at: now2,
186010
+ last_attempt_at: now2,
186011
+ snapshot_generation: generation,
186012
+ last_error: null
186013
+ })).execute();
186014
+ });
186015
+ },
186016
+ recordSyncFailure: async (projectId, targetId, error48) => {
186017
+ const now2 = Date.now();
186018
+ await kdb.insertInto("search_catalog_sync_state").values({
186019
+ project_id: projectId,
186020
+ target_id: targetId,
186021
+ last_success_at: null,
186022
+ last_attempt_at: now2,
186023
+ snapshot_generation: 0,
186024
+ last_error: error48
186025
+ }).onConflict((oc) => oc.columns(["project_id", "target_id"]).doUpdateSet({ last_attempt_at: now2, last_error: error48 })).execute();
186026
+ },
186027
+ getSyncStates: async (projectIds) => {
186028
+ if (projectIds.length === 0) return [];
186029
+ return kdb.selectFrom("search_catalog_sync_state").select(["project_id", "target_id", "last_success_at", "last_attempt_at", "last_error"]).where("project_id", "in", projectIds).execute();
186030
+ },
186031
+ // Opportunistic freshness: called where a title transits the server
186032
+ // anyway (remote title PATCH proxy). UPDATE-only — inserting here would
186033
+ // fabricate a row outside snapshot generations.
186034
+ updateCachedSessionTitle: async (localSessionId, title) => {
186035
+ await kdb.updateTable("session_search_cache").set({ title }).where("local_session_id", "=", localSessionId).execute();
186036
+ },
186037
+ search: async ({ userId, query, limitPerGroup }) => {
186038
+ const q = query.trim().slice(0, 256).toLowerCase();
186039
+ let projQuery = kdb.selectFrom("projects").select(["id", "name", "path"]).where("id", "not like", "path:%");
186040
+ if (userId) projQuery = projQuery.where("user_id", "=", userId);
186041
+ const allProjects = await projQuery.execute();
186042
+ const projectIds = allProjects.map((p2) => p2.id);
186043
+ const nameById = new Map(allProjects.map((p2) => [p2.id, p2.name]));
186044
+ if (projectIds.length === 0) return { projects: [], workspaces: [], sessions: [] };
186045
+ const pattern = `%${escapeLike(q)}%`;
186046
+ let localBase = kdb.selectFrom("agent_sessions as s").select(["s.id", "s.project_id", "s.branch", "s.title", "s.last_user_message_at", "s.updated_at", "s.favorited_at"]).where("s.project_id", "in", projectIds).where((eb) => eb.or([
186047
+ eb("s.title", "is not", null),
186048
+ eb.exists(
186049
+ eb.selectFrom("agent_session_entries").select("agent_session_entries.session_id").whereRef("agent_session_entries.session_id", "=", "s.id")
186050
+ )
186051
+ ]));
186052
+ if (q) localBase = localBase.where(sql`lower(coalesce(s.title, '')) like ${pattern} escape '\\'`);
186053
+ const localRows = await localBase.orderBy("s.updated_at", "desc").limit(200).execute();
186054
+ const localFavRows = await localBase.where("s.favorited_at", "is not", null).execute();
186055
+ let cacheBase = kdb.selectFrom("session_search_cache as c").leftJoin("project_remotes as pr", (join2) => join2.onRef("pr.project_id", "=", "c.project_id").onRef("pr.remote_server_id", "=", "c.target_id")).select(["c.local_session_id", "c.project_id", "c.target_id", "c.branch", "c.title", "c.last_active_at", "c.favorited_at"]).where("c.project_id", "in", projectIds).where("c.deleted_at", "is", null).where((eb) => eb.or([
186056
+ eb("c.target_id", "=", "local"),
186057
+ eb("pr.id", "is not", null)
186058
+ ]));
186059
+ if (q) cacheBase = cacheBase.where(sql`lower(coalesce(c.title, '')) like ${pattern} escape '\\'`);
186060
+ const cacheRows = await cacheBase.orderBy("c.last_active_at", "desc").limit(200).execute();
186061
+ const cacheFavRows = await cacheBase.where("c.favorited_at", "is not", null).execute();
186062
+ const candidateById = /* @__PURE__ */ new Map();
186063
+ for (const r of [...localRows, ...localFavRows]) {
186064
+ if (candidateById.has(r.id)) continue;
186065
+ candidateById.set(r.id, {
186066
+ sessionId: r.id,
186067
+ projectId: r.project_id,
186068
+ projectName: nameById.get(r.project_id) ?? "",
186069
+ targetId: "local",
186070
+ branch: fromDbBranch(r.branch),
186071
+ title: r.title ?? null,
186072
+ lastActiveAt: r.last_user_message_at ?? parseDbTimestamp(r.updated_at),
186073
+ favoritedAt: r.favorited_at ?? null
186074
+ });
186075
+ }
186076
+ for (const r of [...cacheRows, ...cacheFavRows]) {
186077
+ if (candidateById.has(r.local_session_id)) continue;
186078
+ candidateById.set(r.local_session_id, {
186079
+ sessionId: r.local_session_id,
186080
+ projectId: r.project_id,
186081
+ projectName: nameById.get(r.project_id) ?? "",
186082
+ targetId: r.target_id,
186083
+ branch: fromDbBranch(r.branch),
186084
+ title: r.title ?? null,
186085
+ lastActiveAt: r.last_active_at ?? null,
186086
+ favoritedAt: r.favorited_at ?? null
186087
+ });
186088
+ }
186089
+ const sessionCandidates = [...candidateById.values()];
186090
+ if (!q) {
186091
+ const sessions2 = [...sessionCandidates].sort((a, b2) => Number(!!b2.favoritedAt) - Number(!!a.favoritedAt) || (b2.lastActiveAt ?? 0) - (a.lastActiveAt ?? 0)).slice(0, limitPerGroup);
186092
+ return { projects: [], workspaces: [], sessions: sessions2 };
186093
+ }
186094
+ const projects = rankAndCap(allProjects.map((p2) => ({
186095
+ item: { id: p2.id, name: p2.name, path: p2.path ?? null },
186096
+ tier: Math.min(matchTier(p2.name, q), matchTier(p2.path, q)),
186097
+ favorited: false,
186098
+ recency: 0
186099
+ })), limitPerGroup);
186100
+ const wsRows = await kdb.selectFrom("workspace_search_cache as w").leftJoin("project_remotes as pr", (join2) => join2.onRef("pr.project_id", "=", "w.project_id").onRef("pr.remote_server_id", "=", "w.target_id")).select(["w.project_id", "w.target_id", "w.branch"]).where("w.project_id", "in", projectIds).where("w.deleted_at", "is", null).where((eb) => eb.or([
186101
+ eb("w.target_id", "=", "local"),
186102
+ eb("pr.id", "is not", null)
186103
+ ])).execute();
186104
+ const workspaces = rankAndCap(wsRows.map((w2) => ({
186105
+ item: {
186106
+ projectId: w2.project_id,
186107
+ projectName: nameById.get(w2.project_id) ?? "",
186108
+ targetId: w2.target_id,
186109
+ branch: fromDbBranch(w2.branch)
186110
+ },
186111
+ tier: matchTier(fromDbBranch(w2.branch) ?? "main", q),
186112
+ favorited: false,
186113
+ recency: 0
186114
+ })), limitPerGroup);
186115
+ const sessions = rankAndCap(sessionCandidates.map((s3) => ({
186116
+ item: s3,
186117
+ tier: matchTier(s3.title, q),
186118
+ favorited: !!s3.favoritedAt,
186119
+ recency: s3.lastActiveAt ?? 0
186120
+ })), limitPerGroup);
186121
+ return { projects, workspaces, sessions };
186122
+ }
186123
+ }
186124
+ });
186125
+
185915
186126
  // src/storage/sqlite.ts
185916
186127
  var createDatabase = (dbPath) => {
185917
186128
  const db = new Database(dbPath);
@@ -186534,6 +186745,54 @@ var createDatabase = (dbPath) => {
186534
186745
  if (!scheduledRunCols.some((c) => c.name === "report")) {
186535
186746
  db.exec("ALTER TABLE scheduled_task_runs ADD COLUMN report TEXT DEFAULT NULL");
186536
186747
  }
186748
+ db.exec(`
186749
+ CREATE TABLE IF NOT EXISTS branch_merge_targets (
186750
+ project_id TEXT NOT NULL,
186751
+ branch TEXT NOT NULL,
186752
+ target TEXT NOT NULL,
186753
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
186754
+ PRIMARY KEY (project_id, branch),
186755
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
186756
+ );
186757
+
186758
+ -- Search caches: server-side searchable copies of worker catalogs.
186759
+ -- remote_session_mappings stays routing-only; these tables are reconciled
186760
+ -- from full catalog snapshots (generation-based) and rows are soft-deleted,
186761
+ -- so wiping them never breaks existing remote session URLs.
186762
+ CREATE TABLE IF NOT EXISTS session_search_cache (
186763
+ local_session_id TEXT PRIMARY KEY,
186764
+ project_id TEXT NOT NULL,
186765
+ target_id TEXT NOT NULL,
186766
+ branch TEXT NOT NULL DEFAULT '',
186767
+ title TEXT,
186768
+ last_active_at INTEGER,
186769
+ favorited_at INTEGER,
186770
+ entry_count INTEGER NOT NULL DEFAULT 0,
186771
+ generation INTEGER NOT NULL,
186772
+ deleted_at INTEGER
186773
+ );
186774
+ CREATE INDEX IF NOT EXISTS idx_session_search_cache_project
186775
+ ON session_search_cache(project_id, target_id);
186776
+
186777
+ CREATE TABLE IF NOT EXISTS workspace_search_cache (
186778
+ project_id TEXT NOT NULL,
186779
+ target_id TEXT NOT NULL,
186780
+ branch TEXT NOT NULL,
186781
+ generation INTEGER NOT NULL,
186782
+ deleted_at INTEGER,
186783
+ PRIMARY KEY (project_id, target_id, branch)
186784
+ );
186785
+
186786
+ CREATE TABLE IF NOT EXISTS search_catalog_sync_state (
186787
+ project_id TEXT NOT NULL,
186788
+ target_id TEXT NOT NULL,
186789
+ last_success_at INTEGER,
186790
+ last_attempt_at INTEGER,
186791
+ snapshot_generation INTEGER NOT NULL DEFAULT 0,
186792
+ last_error TEXT,
186793
+ PRIMARY KEY (project_id, target_id)
186794
+ );
186795
+ `);
186537
186796
  db.pragma("foreign_keys = ON");
186538
186797
  return db;
186539
186798
  };
@@ -186550,6 +186809,8 @@ var createSqliteStorage = async (dbPath) => {
186550
186809
  ...createAgentSessionRepos(kdb, h),
186551
186810
  ...createWorkspaceRepos(kdb, h),
186552
186811
  ...createCrossRemoteAuditRepo(kdb),
186812
+ ...createMergeTargetsRepo(kdb),
186813
+ ...createSearchCacheRepos(kdb, h),
186553
186814
  close: async () => {
186554
186815
  await kdb.destroy();
186555
186816
  }
@@ -223716,6 +223977,17 @@ var AgentSessionManager = class {
223716
223977
  setEventBus(eventBus) {
223717
223978
  this.eventBus = eventBus;
223718
223979
  }
223980
+ /**
223981
+ * Broadcast a freshly-generated title on the global event bus so the sidebar
223982
+ * (`useResidentSessions`) updates regardless of which workspace is currently
223983
+ * focused. The per-session WS `titleUpdated` broadcast only reaches the one
223984
+ * mounted AgentConversation, which is lost when the user navigates to another
223985
+ * workspace before the ~1-2s title generation completes. Used by both the
223986
+ * local title path and the remote proxy path.
223987
+ */
223988
+ emitSessionTitle(projectId, branch, sessionId, title) {
223989
+ this.eventBus?.emit({ type: "session:title", projectId, branch, sessionId, title });
223990
+ }
223719
223991
  /**
223720
223992
  * Single emit path for `branch:activity` events. Derives the current
223721
223993
  * activity from local DB state (the source of truth — see
@@ -223953,7 +224225,8 @@ var AgentSessionManager = class {
223953
224225
  eventChain: Promise.resolve(),
223954
224226
  bgSpawnHintsThisTurn: 0,
223955
224227
  taskStartedThisTurn: 0,
223956
- lastActiveAt: Date.now()
224228
+ lastActiveAt: Date.now(),
224229
+ turnOpenSince: null
223957
224230
  };
223958
224231
  this.sessions.set(sessionId, session);
223959
224232
  const provider = getProvider(agentType);
@@ -224093,21 +224366,26 @@ var AgentSessionManager = class {
224093
224366
  if (action.kind === "commit") {
224094
224367
  await this.commitCompletion(session, action.payload);
224095
224368
  }
224369
+ if (code !== 0 && !spawnFailed && !session.producedOutput) {
224370
+ try {
224371
+ await this.pushEntry(session.id, {
224372
+ type: "error",
224373
+ message: this.buildStartupFailureMessage(session.agentType, stderrTail),
224374
+ timestamp: Date.now()
224375
+ }, true);
224376
+ } catch (err) {
224377
+ console.error(`[AgentSession] Failed to push startup-failure entry for ${session.id}:`, err);
224378
+ }
224379
+ }
224380
+ await this.finalizeStreamingEntry(session);
224381
+ session.store.currentAssistantIndex = null;
224382
+ await this.endActiveTurn(session, "process_exit");
224096
224383
  session.status = code === 0 ? "stopped" : "error";
224097
224384
  if (!session.skipDb) {
224098
224385
  this.storage.agentSessions.updateStatus(session.id, session.status).catch((err) => {
224099
224386
  console.error(`[AgentSession] Failed to update status for ${session.id}:`, err);
224100
224387
  });
224101
224388
  }
224102
- if (code !== 0 && !spawnFailed && !session.producedOutput) {
224103
- this.pushEntry(session.id, {
224104
- type: "error",
224105
- message: this.buildStartupFailureMessage(session.agentType, stderrTail),
224106
- timestamp: Date.now()
224107
- }, true).catch((err) => {
224108
- console.error(`[AgentSession] Failed to push startup-failure entry for ${session.id}:`, err);
224109
- });
224110
- }
224111
224389
  this.broadcastPatch(session.id, ConversationPatch.updateStatus(session.status));
224112
224390
  this.eventBus?.emit({ type: "session:status", projectId: session.projectId, branch: session.branch, sessionId: session.id, status: session.status });
224113
224391
  this.broadcastRaw(session.id, { finished: true });
@@ -224216,6 +224494,7 @@ var AgentSessionManager = class {
224216
224494
  summaryText
224217
224495
  });
224218
224496
  await this.emitDerivedBranchActivity(session.projectId, session.branch);
224497
+ await this.endActiveTurn(session, "completed");
224219
224498
  if (session.status !== "stopped") {
224220
224499
  session.status = "stopped";
224221
224500
  if (!session.skipDb) await this.storage.agentSessions.updateStatus(sessionId, "stopped");
@@ -224410,6 +224689,7 @@ var AgentSessionManager = class {
224410
224689
  }
224411
224690
  if (event.subtype === "error") {
224412
224691
  this.applyCompletionTimerAction(session, session.completion.errorResult());
224692
+ await this.endActiveTurn(session, "failed");
224413
224693
  if (session.status !== "stopped") {
224414
224694
  session.status = "stopped";
224415
224695
  if (!session.skipDb) await this.storage.agentSessions.updateStatus(sessionId, "stopped");
@@ -224570,6 +224850,20 @@ ${details}`;
224570
224850
  await this.persistEntry(session, index, entry);
224571
224851
  }
224572
224852
  }
224853
+ /**
224854
+ * Close the open turn with a persisted turn_end stop-point entry.
224855
+ * turn_end entries are constructed ONLY here and in repairInterruptedTurn
224856
+ * (restore path). Wall clock only — see design doc for why the CLI's
224857
+ * payload.duration_ms is not used. Rides the normal best-effort entry
224858
+ * persistence on purpose (no strict path — design decision).
224859
+ */
224860
+ async endActiveTurn(session, outcome) {
224861
+ if (session.turnOpenSince === null) return;
224862
+ const endedAt = Date.now();
224863
+ const durationMs = endedAt - session.turnOpenSince;
224864
+ await this.pushEntry(session.id, { type: "turn_end", timestamp: endedAt, durationMs, outcome }, true);
224865
+ session.turnOpenSince = null;
224866
+ }
224573
224867
  /**
224574
224868
  * Send a user message to the agent
224575
224869
  */
@@ -224615,6 +224909,7 @@ ${details}`;
224615
224909
  `[AgentSession] sendUserMessage: wrote ${formatted.length}B to ${session.agentType} stdin (session=${sessionId})`
224616
224910
  );
224617
224911
  session.process.stdin.write(formatted);
224912
+ if (session.turnOpenSince === null) session.turnOpenSince = Date.now();
224618
224913
  return true;
224619
224914
  } catch (error48) {
224620
224915
  console.error(`[AgentSession] Failed to send message:`, error48);
@@ -224733,6 +225028,7 @@ ${details}`;
224733
225028
  content: "Session stopped by user.",
224734
225029
  timestamp: Date.now()
224735
225030
  });
225031
+ await this.endActiveTurn(session, "stopped");
224736
225032
  session.dormant = true;
224737
225033
  this.resetCompletion(session);
224738
225034
  session.status = "stopped";
@@ -224842,6 +225138,7 @@ ${details}`;
224842
225138
  session.store.currentAssistantIndex = null;
224843
225139
  session.buffer = "";
224844
225140
  session.dormant = false;
225141
+ session.turnOpenSince = null;
224845
225142
  this.touchSession(session);
224846
225143
  const clearPatch = ConversationPatch.clearAll();
224847
225144
  this.broadcastPatch(sessionId, clearPatch);
@@ -225002,6 +225299,8 @@ ${details}`;
225002
225299
  break;
225003
225300
  case "system":
225004
225301
  break;
225302
+ case "turn_end":
225303
+ break;
225005
225304
  }
225006
225305
  }
225007
225306
  if (lines.length === 0) return null;
@@ -225044,6 +225343,7 @@ ${details}`;
225044
225343
  content: userMessage,
225045
225344
  timestamp: Date.now()
225046
225345
  }, true, userId);
225346
+ session.turnOpenSince = Date.now();
225047
225347
  setTimeout(() => {
225048
225348
  const context2 = this.buildFullConversationContext(session.store.entries);
225049
225349
  if (context2) {
@@ -225094,6 +225394,34 @@ ${details}`;
225094
225394
  indexProvider.setIndex(maxIndex + 1);
225095
225395
  return store;
225096
225396
  }
225397
+ /**
225398
+ * Crash repair (restore path): if the previous process died mid-turn, the
225399
+ * history has no closing turn_end — append one with outcome
225400
+ * "server_restart" and no duration (the crash time is unknown; the UI
225401
+ * shows "interrupted" instead of a fabricated number). Runs BEFORE
225402
+ * rebuildStoreFromRows so the store is built from the repaired rows.
225403
+ * The other constructor of turn_end entries is endActiveTurn (live paths).
225404
+ */
225405
+ async repairInterruptedTurn(sessionId, rows) {
225406
+ let landingType = null;
225407
+ for (let i = rows.length - 1; i >= 0; i--) {
225408
+ try {
225409
+ const msg = JSON.parse(rows[i].data);
225410
+ if (msg.type === "system") continue;
225411
+ landingType = msg.type;
225412
+ } catch {
225413
+ landingType = "unparsable";
225414
+ }
225415
+ break;
225416
+ }
225417
+ if (landingType === null || landingType === "turn_end") return rows;
225418
+ const maxIndex = rows.reduce((m2, r) => Math.max(m2, r.entry_index), -1);
225419
+ const repair = { type: "turn_end", timestamp: Date.now(), outcome: "server_restart" };
225420
+ const data = JSON.stringify(repair);
225421
+ await this.storage.agentSessions.upsertEntry(sessionId, maxIndex + 1, data);
225422
+ console.log(`[AgentSession] Repaired interrupted turn for ${sessionId} (server_restart turn_end at ${maxIndex + 1})`);
225423
+ return [...rows, { entry_index: maxIndex + 1, data }];
225424
+ }
225097
225425
  /**
225098
225426
  * Restore sessions from database on startup.
225099
225427
  * Creates dormant RunningSession objects with process=null for sessions that have entries.
@@ -225103,8 +225431,11 @@ ${details}`;
225103
225431
  let restoredCount = 0;
225104
225432
  for (const dbSession of allSessions) {
225105
225433
  if (this.sessions.has(dbSession.id)) continue;
225106
- const entries = await this.storage.agentSessions.getEntries(dbSession.id);
225434
+ let entries = await this.storage.agentSessions.getEntries(dbSession.id);
225107
225435
  if (entries.length === 0) continue;
225436
+ if (dbSession.status === "running") {
225437
+ entries = await this.repairInterruptedTurn(dbSession.id, entries);
225438
+ }
225108
225439
  const store = this.rebuildStoreFromRows(entries, dbSession.id);
225109
225440
  const permissionMode = dbSession.permission_mode === "plan" ? "plan" : "edit";
225110
225441
  const runningSession = {
@@ -225125,7 +225456,8 @@ ${details}`;
225125
225456
  eventChain: Promise.resolve(),
225126
225457
  bgSpawnHintsThisTurn: 0,
225127
225458
  taskStartedThisTurn: 0,
225128
- lastActiveAt: Date.now()
225459
+ lastActiveAt: Date.now(),
225460
+ turnOpenSince: null
225129
225461
  };
225130
225462
  this.sessions.set(dbSession.id, runningSession);
225131
225463
  await this.storage.agentSessions.updateStatusPreservingTimestamp(dbSession.id, "stopped");
@@ -225143,22 +225475,43 @@ ${details}`;
225143
225475
  * message goes through wakeDormantSession, which replays the full copied
225144
225476
  * context to a fresh process, so a branch also works with a different
225145
225477
  * agent type than the source.
225146
- * Returns the new session id, or null when the source is unknown or has
225147
- * no persisted history to copy.
225478
+ *
225479
+ * `opts.upToEntryIndex`, when given, is an inclusive cutoff that must land
225480
+ * on a `turn_end` row — every branch then ends with its own tail divider.
225481
+ * With a cutoff the source's live process is never touched (no
225482
+ * finalizeStreamingEntry), so a historical branch is safe even while the
225483
+ * source is running. Without a cutoff (legacy full-copy callers) a running
225484
+ * source is refused outright — copying mid-turn would leave the branch
225485
+ * with a half-finished turn and no closing turn_end.
225148
225486
  */
225149
- async branchSession(sourceSessionId, agentTypeOverride) {
225487
+ async branchSession(sourceSessionId, agentTypeOverride, opts = {}) {
225150
225488
  const source = this.sessions.get(sourceSessionId);
225151
225489
  const sourceRow = await this.storage.agentSessions.getById(sourceSessionId);
225152
- if (!source && !sourceRow) return null;
225153
- if (source?.skipDb) return null;
225154
- if (source) await this.finalizeStreamingEntry(source);
225155
- const entryRows = await this.storage.agentSessions.getEntries(sourceSessionId);
225156
- if (entryRows.length === 0) return null;
225490
+ if (!source && !sourceRow) return { ok: false, reason: "not-found" };
225491
+ if (source?.skipDb) return { ok: false, reason: "empty-history" };
225492
+ if (opts.upToEntryIndex === void 0) {
225493
+ if (source?.status === "running") return { ok: false, reason: "running-needs-cutoff" };
225494
+ if (source) await this.finalizeStreamingEntry(source);
225495
+ }
225496
+ let entryRows = await this.storage.agentSessions.getEntries(sourceSessionId);
225497
+ if (opts.upToEntryIndex !== void 0) {
225498
+ const cut = entryRows.find((r) => r.entry_index === opts.upToEntryIndex);
225499
+ let cutType = null;
225500
+ if (cut) {
225501
+ try {
225502
+ cutType = JSON.parse(cut.data).type;
225503
+ } catch {
225504
+ }
225505
+ }
225506
+ if (cutType !== "turn_end") return { ok: false, reason: "invalid-cutoff" };
225507
+ entryRows = entryRows.filter((r) => r.entry_index <= opts.upToEntryIndex);
225508
+ }
225509
+ if (entryRows.length === 0) return { ok: false, reason: "empty-history" };
225157
225510
  const projectId = source?.projectId ?? sourceRow.project_id;
225158
225511
  const branch = source?.branch ?? (sourceRow.branch || null);
225159
225512
  const permissionMode = source?.permissionMode ?? (sourceRow?.permission_mode === "plan" ? "plan" : "edit");
225160
225513
  const agentType = agentTypeOverride ?? source?.agentType ?? (sourceRow?.agent_type || "claude-code");
225161
- const newId = randomUUID();
225514
+ const newId = opts.sessionId ?? randomUUID();
225162
225515
  await this.storage.agentSessions.create({
225163
225516
  id: newId,
225164
225517
  project_id: projectId,
@@ -225204,12 +225557,14 @@ ${details}`;
225204
225557
  eventChain: Promise.resolve(),
225205
225558
  bgSpawnHintsThisTurn: 0,
225206
225559
  taskStartedThisTurn: 0,
225207
- lastActiveAt: Date.now()
225560
+ lastActiveAt: Date.now(),
225561
+ turnOpenSince: null,
225562
+ crossRemoteMcp: opts.crossRemoteMcp
225208
225563
  };
225209
225564
  this.sessions.set(newId, branched);
225210
225565
  await this.emitDerivedBranchActivity(projectId, branch);
225211
225566
  console.log(`[AgentSession] branchSession: ${sourceSessionId} \u2192 ${newId} (entries=${entryRows.length}, agentType=${agentType})`);
225212
- return newId;
225567
+ return { ok: true, sessionId: newId };
225213
225568
  }
225214
225569
  /**
225215
225570
  * Kill all active session processes and clear state for graceful shutdown
@@ -225249,6 +225604,7 @@ ${details}`;
225249
225604
  if (!dbRow || dbRow.title !== null && dbRow.title !== void 0) return;
225250
225605
  await this.storage.agentSessions.updateTitle(session.id, finalTitle);
225251
225606
  this.broadcastRaw(session.id, { titleUpdated: { title: finalTitle } });
225607
+ this.emitSessionTitle(session.projectId, session.branch, session.id, finalTitle);
225252
225608
  } catch (error48) {
225253
225609
  console.error(`[AgentSession] Failed to persist generated title for ${session.id}:`, error48);
225254
225610
  }
@@ -225868,6 +226224,12 @@ async function generateAndPushRemoteSessionTitle(deps, localSessionId, userText,
225868
226224
  localSessionId,
225869
226225
  JSON.stringify({ titleUpdated: { title: finalTitle } })
225870
226226
  );
226227
+ deps.agentSessionManager.emitSessionTitle(
226228
+ projectIdFromRemoteSessionId(localSessionId, remoteInfo),
226229
+ remoteInfo.branch ?? null,
226230
+ localSessionId,
226231
+ finalTitle
226232
+ );
225871
226233
  }
225872
226234
 
225873
226235
  // src/chat-session-manager.ts
@@ -225888,12 +226250,12 @@ function extractLogText(logs, tailLines) {
225888
226250
  const lines = joined.split("\n");
225889
226251
  return stripAnsi(lines.slice(-tailLines).join("\n"));
225890
226252
  }
225891
- function parseDbTimestamp(s3) {
226253
+ function parseDbTimestamp2(s3) {
225892
226254
  if (s3.includes("T") || s3.endsWith("Z")) return new Date(s3).getTime();
225893
226255
  return (/* @__PURE__ */ new Date(s3.replace(" ", "T") + "Z")).getTime();
225894
226256
  }
225895
226257
  function formatRelativeAge(timestamp, nowMs) {
225896
- const t = parseDbTimestamp(timestamp);
226258
+ const t = parseDbTimestamp2(timestamp);
225897
226259
  if (Number.isNaN(t)) return "unknown";
225898
226260
  const sec = Math.max(0, Math.round((nowMs - t) / 1e3));
225899
226261
  if (sec < 60) return `${sec}s ago`;
@@ -227104,8 +227466,8 @@ var ChatSessionManager = class {
227104
227466
  };
227105
227467
  }
227106
227468
  const TURN_GRACE_MS = 1e3;
227107
- const startedThisTurn = turnStartedAt != null && parseDbTimestamp(lastRow.started_at) >= turnStartedAt - TURN_GRACE_MS;
227108
- const finishedThisTurn = turnStartedAt != null && lastRow.finished_at != null && parseDbTimestamp(lastRow.finished_at) >= turnStartedAt - TURN_GRACE_MS;
227469
+ const startedThisTurn = turnStartedAt != null && parseDbTimestamp2(lastRow.started_at) >= turnStartedAt - TURN_GRACE_MS;
227470
+ const finishedThisTurn = turnStartedAt != null && lastRow.finished_at != null && parseDbTimestamp2(lastRow.finished_at) >= turnStartedAt - TURN_GRACE_MS;
227109
227471
  return {
227110
227472
  name: executor.name,
227111
227473
  command: executor.command,
@@ -231843,6 +232205,15 @@ function isAncestor(repoPath, ancestor, descendant) {
231843
232205
  return false;
231844
232206
  }
231845
232207
  }
232208
+ function branchContentContainedInTarget(repoPath, branch, target) {
232209
+ try {
232210
+ const mergedTree = git(repoPath, ["merge-tree", "--write-tree", target, branch]).split("\n", 1)[0].trim();
232211
+ const targetTree = revParse(repoPath, `${target}^{tree}`);
232212
+ return targetTree !== null && mergedTree === targetTree;
232213
+ } catch {
232214
+ return false;
232215
+ }
232216
+ }
231846
232217
  function computeBranchMergeStatus(repoPath, branch, target) {
231847
232218
  const branchTip = revParse(repoPath, `refs/heads/${branch}`);
231848
232219
  const targetTip = revParse(repoPath, `refs/heads/${target}`);
@@ -231868,6 +232239,10 @@ function computeBranchMergeStatus(repoPath, branch, target) {
231868
232239
  else if (unmergedCount === 0) status = "merged";
231869
232240
  else if (unmergedCount < lines.length) status = "partial";
231870
232241
  else status = "unmerged";
232242
+ if (unmergedCount > 0 && branchContentContainedInTarget(repoPath, branch, target)) {
232243
+ status = "merged";
232244
+ unmergedCount = 0;
232245
+ }
231871
232246
  }
231872
232247
  statusCache.set(cacheKey, { branchTip, targetTip, status, unmergedCount });
231873
232248
  return { status, unmergedCount };
@@ -233112,6 +233487,48 @@ var routes11 = async (fastify2) => {
233112
233487
  if (!project) return null;
233113
233488
  return remoteInfo;
233114
233489
  }
233490
+ async function performLocalBranch(sourceSessionId, userId, opts) {
233491
+ const sourceRow = await fastify2.storage.agentSessions.getById(sourceSessionId);
233492
+ if (!sourceRow || !await fastify2.storage.projects.getById(sourceRow.project_id, userId)) {
233493
+ return { ok: false, code: 404, error: "Session not found" };
233494
+ }
233495
+ const result = await fastify2.agentSessionManager.branchSession(
233496
+ sourceSessionId,
233497
+ opts.agentType,
233498
+ { sessionId: opts.sessionId, crossRemoteMcp: opts.crossRemoteMcp, upToEntryIndex: opts.upToEntryIndex }
233499
+ );
233500
+ if (!result.ok) {
233501
+ if (result.reason === "invalid-cutoff") {
233502
+ return { ok: false, code: 400, error: "upToEntryIndex must reference a turn_end stop point" };
233503
+ }
233504
+ if (result.reason === "running-needs-cutoff") {
233505
+ return { ok: false, code: 409, error: "Session is running; branching requires a stop-point cutoff" };
233506
+ }
233507
+ return { ok: false, code: 404, error: "Session not found or has no history to branch" };
233508
+ }
233509
+ const newSessionId = result.sessionId;
233510
+ const session = fastify2.agentSessionManager.getSession(newSessionId);
233511
+ const messages = fastify2.agentSessionManager.getMessages(newSessionId);
233512
+ const dbRow = await fastify2.storage.agentSessions.getById(newSessionId);
233513
+ return {
233514
+ ok: true,
233515
+ payload: {
233516
+ session: {
233517
+ id: newSessionId,
233518
+ projectId: session?.projectId,
233519
+ branch: session?.branch ?? null,
233520
+ status: session?.status || "stopped",
233521
+ permissionMode: session?.permissionMode || "edit",
233522
+ agentType: session?.agentType || "claude-code",
233523
+ title: dbRow?.title ?? null
233524
+ },
233525
+ messages
233526
+ }
233527
+ };
233528
+ }
233529
+ function broadcastRenamedTitle(sessionId, projectId, branch, title) {
233530
+ fastify2.agentSessionManager.emitSessionTitle(projectId, branch, sessionId, title);
233531
+ }
233115
233532
  fastify2.get("/api/agent-providers", async (_req, reply) => {
233116
233533
  const providers2 = getAllProviders().map((provider) => ({
233117
233534
  type: provider.getAgentType(),
@@ -233839,82 +234256,115 @@ var routes11 = async (fastify2) => {
233839
234256
  fastify2.post(
233840
234257
  "/api/agent-sessions/:sessionId/branch",
233841
234258
  async (req, reply) => {
233842
- const { agentType } = req.body || {};
234259
+ const { agentType, upToEntryIndex } = req.body || {};
234260
+ if (upToEntryIndex !== void 0 && (!Number.isInteger(upToEntryIndex) || upToEntryIndex < 0)) {
234261
+ return reply.code(400).send({ error: "upToEntryIndex must be a non-negative integer" });
234262
+ }
234263
+ const userId = requireAuth(req, reply);
234264
+ if (userId === null) return;
233843
234265
  if (req.params.sessionId.startsWith("remote-")) {
233844
- const userId = requireAuth(req, reply);
233845
- if (userId === null) return;
233846
234266
  const remoteInfo = await getAuthorizedRemoteSessionInfo(req.params.sessionId, userId);
233847
234267
  if (!remoteInfo) {
233848
234268
  return reply.code(404).send({ error: "Remote session not found" });
233849
234269
  }
234270
+ const projectId = projectIdFromRemoteSessionId(req.params.sessionId, remoteInfo);
234271
+ const newRemoteSessionId = randomUUID14();
234272
+ const localSessionId = `remote-${remoteInfo.remoteServerId}-${projectId}-${newRemoteSessionId}`;
234273
+ const crossRemoteMcp2 = await mintCrossRemoteMcpConfig(
234274
+ { storage: fastify2.storage },
234275
+ { userId, sessionId: localSessionId, sourceRemoteServerId: remoteInfo.remoteServerId }
234276
+ );
233850
234277
  const result = await proxyAuto(
233851
234278
  remoteInfo.remoteServerId,
233852
234279
  remoteInfo.remoteUrl,
233853
234280
  remoteInfo.remoteApiKey,
233854
234281
  "POST",
233855
- `/api/agent-sessions/${remoteInfo.remoteSessionId}/branch`,
233856
- { agentType }
234282
+ `/api/path/agent-sessions/${remoteInfo.remoteSessionId}/branch`,
234283
+ { agentType, sessionId: newRemoteSessionId, crossRemoteMcp: crossRemoteMcp2, upToEntryIndex }
233857
234284
  );
233858
234285
  if (!result.ok) {
233859
234286
  return reply.code(proxyStatus(result)).send(result.data);
233860
234287
  }
233861
234288
  const remoteData = result.data;
233862
- const projectId = projectIdFromRemoteSessionId(req.params.sessionId, remoteInfo);
233863
- const localSessionId = `remote-${remoteInfo.remoteServerId}-${projectId}-${remoteData.session.id}`;
234289
+ if (remoteData.session.id !== newRemoteSessionId) {
234290
+ return reply.code(409).send({ error: "Remote returned an unexpected session id; upgrade the remote" });
234291
+ }
234292
+ if (upToEntryIndex !== void 0 && remoteData.messages.length > upToEntryIndex + 1) {
234293
+ console.error(
234294
+ `[Branch] Remote ${remoteInfo.remoteServerId} ignored branch cutoff (${remoteData.messages.length} messages > cutoff ${upToEntryIndex}) \u2014 version drift, upgrade the remote`
234295
+ );
234296
+ return reply.code(409).send({ error: "Remote ignored branch cutoff; upgrade the remote" });
234297
+ }
233864
234298
  fastify2.remoteSessionMap.set(localSessionId, {
233865
234299
  remoteServerId: remoteInfo.remoteServerId,
233866
234300
  remoteUrl: remoteInfo.remoteUrl,
233867
234301
  remoteApiKey: remoteInfo.remoteApiKey,
233868
- remoteSessionId: remoteData.session.id,
234302
+ remoteSessionId: newRemoteSessionId,
233869
234303
  branch: remoteInfo.branch ?? null
233870
234304
  });
233871
- await fastify2.storage.remoteSessionMappings.upsert(
233872
- localSessionId,
233873
- projectId,
233874
- remoteInfo.remoteServerId,
233875
- remoteData.session.id,
233876
- remoteInfo.branch ?? null
233877
- );
233878
- await fastify2.storage.remoteSessionMappings.markTitleResolved(localSessionId);
233879
- fastify2.agentSessionManager.markTitleResolved(localSessionId);
233880
- if (remoteData.messages && remoteData.messages.length > 0) {
233881
- const cacheEntry = fastify2.remotePatchCache.getOrCreate(localSessionId);
233882
- if (cacheEntry.messages.length === 0) {
233883
- for (let i = 0; i < remoteData.messages.length; i++) {
233884
- const patch = ConversationPatch.addEntry(i, remoteData.messages[i]);
233885
- fastify2.remotePatchCache.appendMessage(localSessionId, JSON.stringify({ JsonPatch: patch }), true);
234305
+ try {
234306
+ await fastify2.storage.remoteSessionMappings.upsert(
234307
+ localSessionId,
234308
+ projectId,
234309
+ remoteInfo.remoteServerId,
234310
+ newRemoteSessionId,
234311
+ remoteInfo.branch ?? null
234312
+ );
234313
+ await fastify2.storage.remoteSessionMappings.markTitleResolved(localSessionId);
234314
+ fastify2.agentSessionManager.markTitleResolved(localSessionId);
234315
+ if (remoteData.messages && remoteData.messages.length > 0) {
234316
+ const cacheEntry = fastify2.remotePatchCache.getOrCreate(localSessionId);
234317
+ if (cacheEntry.messages.length === 0) {
234318
+ for (let i = 0; i < remoteData.messages.length; i++) {
234319
+ const patch = ConversationPatch.addEntry(i, remoteData.messages[i]);
234320
+ fastify2.remotePatchCache.appendMessage(localSessionId, JSON.stringify({ JsonPatch: patch }), true);
234321
+ }
233886
234322
  }
233887
234323
  }
234324
+ } catch (err) {
234325
+ fastify2.remoteSessionMap.delete(localSessionId);
234326
+ throw err;
233888
234327
  }
233889
234328
  return reply.code(200).send({
233890
234329
  session: { ...remoteData.session, id: localSessionId, projectId },
233891
234330
  messages: remoteData.messages
233892
234331
  });
233893
234332
  }
233894
- const newSessionId = await fastify2.agentSessionManager.branchSession(
233895
- req.params.sessionId,
233896
- agentType
234333
+ const preSessionId = randomUUID14();
234334
+ const crossRemoteMcp = await mintCrossRemoteMcpConfig(
234335
+ { storage: fastify2.storage },
234336
+ { userId, sessionId: preSessionId, sourceRemoteServerId: null }
233897
234337
  );
233898
- if (!newSessionId) {
233899
- return reply.code(404).send({ error: "Session not found or has no history to branch" });
233900
- }
233901
- const session = fastify2.agentSessionManager.getSession(newSessionId);
233902
- const messages = fastify2.agentSessionManager.getMessages(newSessionId);
233903
- const dbRow = await fastify2.storage.agentSessions.getById(newSessionId);
233904
- return reply.code(200).send({
233905
- session: {
233906
- id: newSessionId,
233907
- projectId: session?.projectId,
233908
- branch: session?.branch ?? null,
233909
- status: session?.status || "stopped",
233910
- permissionMode: session?.permissionMode || "edit",
233911
- agentType: session?.agentType || "claude-code",
233912
- title: dbRow?.title ?? null
233913
- },
233914
- messages
234338
+ const branched = await performLocalBranch(req.params.sessionId, userId, {
234339
+ agentType,
234340
+ sessionId: preSessionId,
234341
+ crossRemoteMcp,
234342
+ upToEntryIndex
233915
234343
  });
234344
+ if (!branched.ok) {
234345
+ return reply.code(branched.code).send({ error: branched.error });
234346
+ }
234347
+ return reply.code(200).send(branched.payload);
233916
234348
  }
233917
234349
  );
234350
+ fastify2.post("/api/path/agent-sessions/:sessionId/branch", async (req, reply) => {
234351
+ const { agentType, sessionId, crossRemoteMcp, upToEntryIndex } = req.body || {};
234352
+ if (upToEntryIndex !== void 0 && (!Number.isInteger(upToEntryIndex) || upToEntryIndex < 0)) {
234353
+ return reply.code(400).send({ error: "upToEntryIndex must be a non-negative integer" });
234354
+ }
234355
+ const userId = requireAuth(req, reply);
234356
+ if (userId === null) return;
234357
+ const branched = await performLocalBranch(req.params.sessionId, userId, {
234358
+ agentType,
234359
+ sessionId,
234360
+ crossRemoteMcp,
234361
+ upToEntryIndex
234362
+ });
234363
+ if (!branched.ok) {
234364
+ return reply.code(branched.code).send({ error: branched.error });
234365
+ }
234366
+ return reply.code(200).send(branched.payload);
234367
+ });
233918
234368
  fastify2.post(
233919
234369
  "/api/agent-sessions/:sessionId/switch-mode",
233920
234370
  async (req, reply) => {
@@ -234073,6 +234523,7 @@ var routes11 = async (fastify2) => {
234073
234523
  if (title !== null && (typeof title !== "string" || title.length > 200)) {
234074
234524
  return reply.code(400).send({ error: "title must be null or a string up to 200 chars" });
234075
234525
  }
234526
+ const normalizedTitle = title && title.trim().length > 0 ? title.trim() : null;
234076
234527
  if (req.params.sessionId.startsWith("remote-")) {
234077
234528
  const userId = requireAuth(req, reply);
234078
234529
  if (userId === null) return;
@@ -234084,14 +234535,35 @@ var routes11 = async (fastify2) => {
234084
234535
  remoteInfo.remoteApiKey,
234085
234536
  "PATCH",
234086
234537
  `/api/agent-sessions/${remoteInfo.remoteSessionId}/title`,
234087
- { title }
234538
+ { title: normalizedTitle }
234088
234539
  );
234540
+ if (result.ok) {
234541
+ broadcastRenamedTitle(
234542
+ req.params.sessionId,
234543
+ projectIdFromRemoteSessionId(req.params.sessionId, remoteInfo),
234544
+ remoteInfo.branch ?? null,
234545
+ normalizedTitle
234546
+ );
234547
+ if (normalizedTitle) {
234548
+ try {
234549
+ await fastify2.storage.searchCache.updateCachedSessionTitle(req.params.sessionId, normalizedTitle);
234550
+ } catch (err) {
234551
+ console.error("[API] searchCache.updateCachedSessionTitle failed:", err);
234552
+ }
234553
+ }
234554
+ }
234089
234555
  return reply.code(proxyStatus(result)).send(result.data);
234090
234556
  }
234091
234557
  const session = await fastify2.storage.agentSessions.getById(req.params.sessionId);
234092
234558
  if (!session) return reply.code(404).send({ error: "Session not found" });
234093
- await fastify2.storage.agentSessions.updateTitle(req.params.sessionId, title);
234094
- return reply.code(200).send({ success: true, title });
234559
+ await fastify2.storage.agentSessions.updateTitle(req.params.sessionId, normalizedTitle);
234560
+ broadcastRenamedTitle(
234561
+ req.params.sessionId,
234562
+ session.project_id,
234563
+ session.branch,
234564
+ normalizedTitle
234565
+ );
234566
+ return reply.code(200).send({ success: true, title: normalizedTitle });
234095
234567
  });
234096
234568
  fastify2.patch("/api/agent-sessions/:sessionId/favorite", async (req, reply) => {
234097
234569
  const { favorited } = req.body;
@@ -234208,6 +234680,77 @@ var branch_activity_routes_default = (0, import_fastify_plugin13.default)(routes
234208
234680
 
234209
234681
  // src/routes/merge-status-routes.ts
234210
234682
  var import_fastify_plugin14 = __toESM(require_plugin2(), 1);
234683
+ function resolveTargets(comparisons, storedTargets) {
234684
+ const sources = [];
234685
+ const effective = comparisons.map((comparison) => {
234686
+ if (comparison.target !== void 0) {
234687
+ sources.push("request");
234688
+ return comparison;
234689
+ }
234690
+ const storedTarget = storedTargets.get(comparison.branch);
234691
+ if (storedTarget !== void 0) {
234692
+ sources.push("stored");
234693
+ return { branch: comparison.branch, target: storedTarget };
234694
+ }
234695
+ sources.push("default");
234696
+ return comparison;
234697
+ });
234698
+ return { comparisons: effective, sources };
234699
+ }
234700
+ function annotateEntries(entries, comparisons, sources) {
234701
+ return entries.map((entry, index) => ({
234702
+ ...entry,
234703
+ targetSource: sources[index],
234704
+ requestedTarget: comparisons[index]?.target ?? entry.target
234705
+ }));
234706
+ }
234707
+ var MERGE_PAIR_ERRORS = /* @__PURE__ */ new Set([
234708
+ "target-not-found",
234709
+ "branch-not-found",
234710
+ "no-default-branch"
234711
+ ]);
234712
+ var MERGE_STATUS_VALUES = /* @__PURE__ */ new Set([
234713
+ "merged",
234714
+ "partial",
234715
+ "unmerged",
234716
+ "no-unique-commits"
234717
+ ]);
234718
+ function isRecord(value) {
234719
+ return value !== null && typeof value === "object" && !Array.isArray(value);
234720
+ }
234721
+ function isRemoteMergeStatusEntry(value, comparison) {
234722
+ if (!isRecord(value) || value.branch !== comparison.branch) return false;
234723
+ if (value.target !== null && typeof value.target !== "string") return false;
234724
+ if (value.error !== void 0) {
234725
+ if (!MERGE_PAIR_ERRORS.has(value.error)) return false;
234726
+ if (value.status !== void 0 || value.unmergedCount !== void 0 || value.dirty !== void 0) {
234727
+ return false;
234728
+ }
234729
+ if (value.error === "target-not-found") {
234730
+ return comparison.target !== void 0 && value.target === null;
234731
+ }
234732
+ if (value.error === "no-default-branch") {
234733
+ return comparison.target === void 0 && value.target === null;
234734
+ }
234735
+ return typeof value.target === "string" && (comparison.target === void 0 || value.target === comparison.target);
234736
+ }
234737
+ return typeof value.target === "string" && (comparison.target === void 0 || value.target === comparison.target) && MERGE_STATUS_VALUES.has(value.status) && Number.isInteger(value.unmergedCount) && value.unmergedCount >= 0 && typeof value.dirty === "boolean";
234738
+ }
234739
+ function parseRemoteEntries(data, comparisons) {
234740
+ if (!isRecord(data) || !Array.isArray(data.entries)) return null;
234741
+ if (data.entries.length !== comparisons.length) return null;
234742
+ if (!data.entries.every((entry, index) => isRemoteMergeStatusEntry(entry, comparisons[index]))) {
234743
+ return null;
234744
+ }
234745
+ const entries = data.entries;
234746
+ const implicitEntries = entries.filter((_entry, index) => comparisons[index].target === void 0);
234747
+ const hasNoDefault = implicitEntries.some((entry) => entry.error === "no-default-branch");
234748
+ if (hasNoDefault) {
234749
+ return implicitEntries.every((entry) => entry.error === "no-default-branch") ? entries : null;
234750
+ }
234751
+ const resolvedDefault = implicitEntries[0]?.target;
234752
+ return implicitEntries.every((entry) => entry.target === resolvedDefault) ? entries : null;
234753
+ }
234211
234754
  function legacyRemoteLabel(remoteUrl) {
234212
234755
  try {
234213
234756
  return new URL(remoteUrl).hostname;
@@ -234239,6 +234782,10 @@ async function getRemoteConfig4(fastify2, project) {
234239
234782
  return null;
234240
234783
  }
234241
234784
  var MAX_COMPARISONS = 50;
234785
+ var MAX_NAME_LENGTH = 256;
234786
+ function isValidName(value) {
234787
+ return typeof value === "string" && value.length > 0 && value.length <= MAX_NAME_LENGTH;
234788
+ }
234242
234789
  function parseComparisons(body) {
234243
234790
  if (!body || typeof body !== "object") return null;
234244
234791
  const comparisons = body.comparisons;
@@ -234294,6 +234841,11 @@ var routes13 = async (fastify2) => {
234294
234841
  if (!comparisons) {
234295
234842
  return reply.code(400).send({ error: "Invalid comparisons" });
234296
234843
  }
234844
+ const storedTargets = await fastify2.storage.mergeTargets.getForBranches(
234845
+ project.id,
234846
+ comparisons.map(({ branch }) => branch)
234847
+ );
234848
+ const resolved = resolveTargets(comparisons, storedTargets);
234297
234849
  if (!project.path) {
234298
234850
  const remoteConfig = await getRemoteConfig4(fastify2, project);
234299
234851
  if (!remoteConfig) {
@@ -234305,28 +234857,83 @@ var routes13 = async (fastify2) => {
234305
234857
  remoteConfig.apiKey,
234306
234858
  "POST",
234307
234859
  "/api/path/branches/merge-status",
234308
- { path: remoteConfig.remotePath, comparisons },
234860
+ { path: remoteConfig.remotePath, comparisons: resolved.comparisons },
234309
234861
  { reverseConnectManager: fastify2.reverseConnectManager }
234310
234862
  );
234311
234863
  if (!result.ok) {
234312
234864
  return reply.code(proxyStatus(result)).send(result.data);
234313
234865
  }
234314
- const data = result.data;
234866
+ const entries = parseRemoteEntries(result.data, resolved.comparisons);
234867
+ if (!entries) {
234868
+ return reply.code(502).send({ error: "Remote merge-status response invalid" });
234869
+ }
234315
234870
  return reply.code(200).send({
234316
234871
  repository: {
234317
234872
  kind: "remote",
234318
234873
  remoteServerId: remoteConfig.serverId,
234319
234874
  label: remoteConfig.serverName
234320
234875
  },
234321
- entries: data.entries ?? []
234876
+ entries: annotateEntries(
234877
+ entries,
234878
+ resolved.comparisons,
234879
+ resolved.sources
234880
+ )
234322
234881
  });
234323
234882
  }
234324
- return sendComputed(
234325
- reply,
234326
- project.path,
234327
- comparisons,
234328
- { kind: "local", label: "Local" }
234329
- );
234883
+ try {
234884
+ const entries = computeMergeStatusPairs(project.path, resolved.comparisons);
234885
+ return reply.code(200).send({
234886
+ repository: { kind: "local", label: "Local" },
234887
+ entries: annotateEntries(entries, resolved.comparisons, resolved.sources)
234888
+ });
234889
+ } catch (error48) {
234890
+ const statusCode = error48.statusCode ?? 500;
234891
+ const message = error48 instanceof Error ? error48.message : "Failed to compute merge status";
234892
+ return reply.code(statusCode).send({ error: message });
234893
+ }
234894
+ }
234895
+ );
234896
+ fastify2.put(
234897
+ "/api/projects/:id/branches/merge-target",
234898
+ async (req, reply) => {
234899
+ const userId = requireAuth(req, reply);
234900
+ if (userId === null) return;
234901
+ const project = await fastify2.storage.projects.getById(req.params.id, userId);
234902
+ if (!project) {
234903
+ return reply.code(404).send({ error: "Project not found" });
234904
+ }
234905
+ if (!req.body || typeof req.body !== "object") {
234906
+ return reply.code(400).send({ error: "Invalid merge target" });
234907
+ }
234908
+ const body = req.body;
234909
+ const { branch, target, ifAbsent } = body;
234910
+ if (!isValidName(branch) || target !== null && !isValidName(target) || ifAbsent !== void 0 && typeof ifAbsent !== "boolean" || ifAbsent === true && target === null) {
234911
+ return reply.code(400).send({ error: "Invalid merge target" });
234912
+ }
234913
+ let changed;
234914
+ let storedTarget;
234915
+ if (target === null) {
234916
+ changed = await fastify2.storage.mergeTargets.delete(project.id, branch);
234917
+ storedTarget = null;
234918
+ } else if (ifAbsent === true) {
234919
+ changed = await fastify2.storage.mergeTargets.insertIfAbsent(project.id, branch, target);
234920
+ if (changed) {
234921
+ storedTarget = target;
234922
+ } else {
234923
+ storedTarget = (await fastify2.storage.mergeTargets.getForBranches(project.id, [branch])).get(branch) ?? null;
234924
+ }
234925
+ } else {
234926
+ changed = await fastify2.storage.mergeTargets.upsert(project.id, branch, target);
234927
+ storedTarget = target;
234928
+ }
234929
+ if (changed) {
234930
+ fastify2.eventBus.emit({
234931
+ type: "merge-target:updated",
234932
+ projectId: project.id,
234933
+ branch
234934
+ });
234935
+ }
234936
+ return reply.code(200).send({ branch, target: storedTarget });
234330
234937
  }
234331
234938
  );
234332
234939
  };
@@ -234772,7 +235379,7 @@ var SCROLLBACK_MAX = 1e5;
234772
235379
  var FONT_SIZE_MIN = 8;
234773
235380
  var FONT_SIZE_MAX = 32;
234774
235381
  var DEFAULT_CONVERSATION_SETTINGS = {
234775
- agentFontSize: 15,
235382
+ agentFontSize: 16,
234776
235383
  chatFontSize: 15,
234777
235384
  filesTreeFontSize: 14,
234778
235385
  filesContentFontSize: 14
@@ -237538,6 +238145,244 @@ var routes28 = async (fastify2) => {
237538
238145
  };
237539
238146
  var schedule_routes_default = (0, import_fastify_plugin29.default)(routes28, { name: "schedule-routes" });
237540
238147
 
238148
+ // src/search/catalog.ts
238149
+ var parseDbTimestamp3 = (ts) => {
238150
+ if (!ts) return null;
238151
+ const ms = Date.parse(ts.replace(" ", "T") + "Z");
238152
+ return Number.isNaN(ms) ? null : ms;
238153
+ };
238154
+ async function buildSearchCatalog(deps, projectId, projectPath) {
238155
+ pruneWorktrees(projectPath);
238156
+ const workspaces = getWorktreeBranches(projectPath);
238157
+ const sessions = await deps.storage.agentSessions.getByProjectId(projectId);
238158
+ const counts = new Map(
238159
+ (await deps.storage.agentSessions.countEntries()).map((r) => [r.session_id, r.cnt])
238160
+ );
238161
+ return {
238162
+ snapshotAt: Date.now(),
238163
+ workspaces,
238164
+ sessions: sessions.map((s3) => ({ s: s3, entryCount: counts.get(s3.id) ?? 0 })).filter(({ s: s3, entryCount }) => shouldShowBranchSessionInList({
238165
+ entryCount,
238166
+ processAlive: deps.getProcessAlive?.(s3.id) ?? false
238167
+ })).map(({ s: s3, entryCount }) => ({
238168
+ id: s3.id,
238169
+ branch: s3.branch === "" ? null : s3.branch,
238170
+ title: s3.title ?? null,
238171
+ lastActiveAt: s3.last_user_message_at ?? parseDbTimestamp3(s3.updated_at),
238172
+ favoritedAt: s3.favorited_at ?? null,
238173
+ entryCount
238174
+ }))
238175
+ };
238176
+ }
238177
+
238178
+ // src/search/refresh.ts
238179
+ var DEFAULT_TTL_MS = 3e4;
238180
+ var DEFAULT_DEADLINE_MS = 5e3;
238181
+ var PER_WORKER_CONCURRENCY = 3;
238182
+ var LOCAL_CONCURRENCY = 4;
238183
+ async function listSearchTargets(storage, userId) {
238184
+ const projects = await storage.projects.getAll(userId);
238185
+ const targets = [];
238186
+ for (const p2 of projects) {
238187
+ if (p2.path) targets.push({ projectId: p2.id, targetId: "local", projectPath: p2.path });
238188
+ const remotes = await storage.projectRemotes.getByProject(p2.id);
238189
+ for (const r of remotes) {
238190
+ targets.push({
238191
+ projectId: p2.id,
238192
+ targetId: r.remote_server_id,
238193
+ remote: {
238194
+ serverId: r.remote_server_id,
238195
+ url: r.server_url ?? "",
238196
+ apiKey: r.server_api_key ?? "",
238197
+ remotePath: r.remote_path
238198
+ }
238199
+ });
238200
+ }
238201
+ }
238202
+ return targets;
238203
+ }
238204
+ function computeCacheState(states, targets, now2, ttlMs = DEFAULT_TTL_MS) {
238205
+ if (targets.length === 0) return "fresh";
238206
+ const stateByKey = new Map(states.map((s3) => [`${s3.project_id}:${s3.target_id}`, s3]));
238207
+ const matched = targets.map((t) => stateByKey.get(`${t.projectId}:${t.targetId}`));
238208
+ if (matched.some((s3) => s3?.last_success_at == null)) return "cold";
238209
+ return matched.every((s3) => now2 - (s3.last_success_at ?? 0) <= ttlMs) ? "fresh" : "stale";
238210
+ }
238211
+ async function runWithConcurrency(tasks, limit) {
238212
+ const queue = [...tasks];
238213
+ const workers = Array.from({ length: Math.max(1, Math.min(limit, queue.length)) }, async () => {
238214
+ for (let task = queue.shift(); task; task = queue.shift()) {
238215
+ await task();
238216
+ }
238217
+ });
238218
+ await Promise.all(workers);
238219
+ }
238220
+ function createSearchRefresher(deps) {
238221
+ const ttlMs = deps.ttlMs ?? DEFAULT_TTL_MS;
238222
+ const deadlineMs = deps.deadlineMs ?? DEFAULT_DEADLINE_MS;
238223
+ const now2 = deps.now ?? Date.now;
238224
+ const inflight = /* @__PURE__ */ new Map();
238225
+ function refreshTarget(t) {
238226
+ const key = `${t.projectId}:${t.targetId}`;
238227
+ const existing = inflight.get(key);
238228
+ if (existing) return existing;
238229
+ const run2 = (async () => {
238230
+ try {
238231
+ const snapshot = t.targetId === "local" ? await deps.buildLocalCatalog(t.projectId, t.projectPath ?? "") : await deps.fetchRemoteCatalog(t);
238232
+ await deps.storage.searchCache.applyCatalogSnapshot(t.projectId, t.targetId, snapshot);
238233
+ } catch (err) {
238234
+ await deps.storage.searchCache.recordSyncFailure(
238235
+ t.projectId,
238236
+ t.targetId,
238237
+ err instanceof Error ? err.message : String(err)
238238
+ ).catch(() => {
238239
+ });
238240
+ }
238241
+ })();
238242
+ inflight.set(key, run2);
238243
+ void run2.finally(() => inflight.delete(key));
238244
+ return run2;
238245
+ }
238246
+ async function refreshAll(userId) {
238247
+ const targets = await listSearchTargets(deps.storage, userId);
238248
+ const states = await deps.storage.searchCache.getSyncStates([...new Set(targets.map((t) => t.projectId))]);
238249
+ const stateByKey = new Map(states.map((s3) => [`${s3.project_id}:${s3.target_id}`, s3]));
238250
+ const due = targets.filter((t) => {
238251
+ const s3 = stateByKey.get(`${t.projectId}:${t.targetId}`);
238252
+ return !s3?.last_success_at || now2() - s3.last_success_at > ttlMs;
238253
+ });
238254
+ const byWorker = /* @__PURE__ */ new Map();
238255
+ for (const t of due) {
238256
+ const k2 = t.targetId;
238257
+ byWorker.set(k2, [...byWorker.get(k2) ?? [], t]);
238258
+ }
238259
+ const lanes = [...byWorker.entries()].map(
238260
+ ([workerId, ts]) => runWithConcurrency(
238261
+ ts.map((t) => () => refreshTarget(t)),
238262
+ workerId === "local" ? LOCAL_CONCURRENCY : PER_WORKER_CONCURRENCY
238263
+ )
238264
+ );
238265
+ const all = Promise.all(lanes).then(() => void 0);
238266
+ await Promise.race([
238267
+ all,
238268
+ new Promise((resolve3) => {
238269
+ const timer = setTimeout(resolve3, deadlineMs);
238270
+ timer.unref?.();
238271
+ })
238272
+ ]);
238273
+ }
238274
+ return { refreshAll };
238275
+ }
238276
+
238277
+ // src/routes/search-routes.ts
238278
+ var searchRoutes = async (fastify2) => {
238279
+ fastify2.get(
238280
+ "/api/path/search-catalog",
238281
+ async (req, reply) => {
238282
+ const projectPath = req.query.path;
238283
+ if (!projectPath) {
238284
+ return reply.code(400).send({ error: "path is required" });
238285
+ }
238286
+ const project = await fastify2.storage.projects.getByPath(projectPath);
238287
+ if (!project) {
238288
+ return reply.code(200).send({ snapshotAt: Date.now(), workspaces: [], sessions: [] });
238289
+ }
238290
+ try {
238291
+ const catalog = await buildSearchCatalog(
238292
+ {
238293
+ storage: fastify2.storage,
238294
+ getProcessAlive: (id) => fastify2.agentSessionManager.getSessionProcessAlive(id)
238295
+ },
238296
+ project.id,
238297
+ projectPath
238298
+ );
238299
+ return reply.code(200).send(catalog);
238300
+ } catch (error48) {
238301
+ return reply.code(500).send({ error: String(error48) });
238302
+ }
238303
+ }
238304
+ );
238305
+ const refresher = createSearchRefresher({
238306
+ storage: fastify2.storage,
238307
+ buildLocalCatalog: (projectId, projectPath) => buildSearchCatalog(
238308
+ {
238309
+ storage: fastify2.storage,
238310
+ getProcessAlive: (id) => fastify2.agentSessionManager.getSessionProcessAlive(id)
238311
+ },
238312
+ projectId,
238313
+ projectPath
238314
+ ),
238315
+ fetchRemoteCatalog: async (target) => {
238316
+ const r = target.remote;
238317
+ if (!r) throw new Error("remote target without remote config");
238318
+ const params = new URLSearchParams({ path: r.remotePath });
238319
+ const result = await proxyToRemoteAuto(
238320
+ r.serverId,
238321
+ r.url,
238322
+ r.apiKey,
238323
+ "GET",
238324
+ `/api/path/search-catalog?${params.toString()}`,
238325
+ void 0,
238326
+ { reverseConnectManager: fastify2.reverseConnectManager, timeoutMs: 2e3 }
238327
+ );
238328
+ if (!result.ok) {
238329
+ throw new Error(`catalog fetch failed: ${result.status} ${result.errorCode ?? ""}`);
238330
+ }
238331
+ const data = result.data;
238332
+ const sessions = await Promise.all(data.sessions.map(async (s3) => {
238333
+ const localSessionId = `remote-${target.targetId}-${target.projectId}-${s3.id}`;
238334
+ const mapBranch = s3.branch ?? "";
238335
+ if (!fastify2.remoteSessionMap.has(localSessionId)) {
238336
+ fastify2.remoteSessionMap.set(localSessionId, {
238337
+ remoteServerId: target.targetId,
238338
+ remoteUrl: r.url,
238339
+ remoteApiKey: r.apiKey,
238340
+ remoteSessionId: s3.id,
238341
+ branch: mapBranch
238342
+ });
238343
+ }
238344
+ await fastify2.storage.remoteSessionMappings.upsert(
238345
+ localSessionId,
238346
+ target.projectId,
238347
+ target.targetId,
238348
+ s3.id,
238349
+ mapBranch
238350
+ );
238351
+ return { ...s3, id: localSessionId };
238352
+ }));
238353
+ return { workspaces: data.workspaces, sessions };
238354
+ }
238355
+ });
238356
+ async function currentCacheState(userId) {
238357
+ const targets = await listSearchTargets(fastify2.storage, userId);
238358
+ const states = await fastify2.storage.searchCache.getSyncStates(
238359
+ [...new Set(targets.map((t) => t.projectId))]
238360
+ );
238361
+ return computeCacheState(states, targets, Date.now());
238362
+ }
238363
+ fastify2.get(
238364
+ "/api/search",
238365
+ async (req, reply) => {
238366
+ const userId = requireAuth(req, reply);
238367
+ if (userId === null) return;
238368
+ const query = (req.query.q ?? "").slice(0, 256);
238369
+ const parsed = parseInt(req.query.limitPerGroup ?? "10", 10);
238370
+ const limitPerGroup = Math.min(Math.max(Number.isNaN(parsed) ? 10 : parsed, 1), 50);
238371
+ const results = await fastify2.storage.searchCache.search({ userId, query, limitPerGroup });
238372
+ const cacheState = await currentCacheState(userId);
238373
+ return reply.code(200).send({ ...results, cacheState });
238374
+ }
238375
+ );
238376
+ fastify2.post("/api/search/refresh", async (req, reply) => {
238377
+ const userId = requireAuth(req, reply);
238378
+ if (userId === null) return;
238379
+ await refresher.refreshAll(userId);
238380
+ const cacheState = await currentCacheState(userId);
238381
+ return reply.code(200).send({ ok: true, cacheState });
238382
+ });
238383
+ };
238384
+ var search_routes_default = searchRoutes;
238385
+
237541
238386
  // ../../node_modules/.pnpm/@clerk+fastify@3.1.2_fastify@5.7.2_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@clerk/fastify/dist/chunk-RY4T2JMQ.mjs
237542
238387
  import { Readable as Readable3 } from "stream";
237543
238388
  var fastifyRequestToRequest = (req) => {
@@ -238795,6 +239640,7 @@ var createServer = async (opts) => {
238795
239640
  server.register(remote_routes_default);
238796
239641
  server.register(remote_server_routes_default);
238797
239642
  server.register(project_remote_routes_default);
239643
+ server.register(search_routes_default);
238798
239644
  server.register(executor_group_routes_default);
238799
239645
  server.register(executor_routes_default);
238800
239646
  server.register(schedule_routes_default);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibedeckx/linux-x64",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Vibedeckx platform binaries for Linux x64",
5
5
  "os": [
6
6
  "linux"