chatroom-cli 1.65.0 → 1.65.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -84704,765 +84704,6 @@ var init_daemon_services = __esm(() => {
84704
84704
  };
84705
84705
  });
84706
84706
 
84707
- // src/commands/machine/daemon-start/utils.ts
84708
- function formatTimestamp() {
84709
- return new Date().toISOString().replace("T", " ").substring(0, 19);
84710
- }
84711
-
84712
- // src/infrastructure/services/workspace/dir-listing-content-hash.ts
84713
- import { createHash as createHash2 } from "node:crypto";
84714
- function computeDirListingContentHash(listing) {
84715
- const payload = {
84716
- entries: listing.entries,
84717
- truncated: listing.truncated,
84718
- totalCount: listing.totalCount
84719
- };
84720
- return createHash2("md5").update(JSON.stringify(payload)).digest("hex");
84721
- }
84722
- var init_dir_listing_content_hash = () => {};
84723
-
84724
- // src/infrastructure/services/workspace/workspace-path-security.ts
84725
- import { realpath as realpath2 } from "node:fs/promises";
84726
- import { basename, dirname as dirname8, isAbsolute, relative, resolve as resolve4, sep } from "node:path";
84727
- import { gunzipSync } from "node:zlib";
84728
- function validateRelativePathSegments(filePath) {
84729
- if (filePath.includes("\x00"))
84730
- return { ok: false, error: "Invalid file path" };
84731
- if (filePath.includes(".."))
84732
- return { ok: false, error: "Invalid file path" };
84733
- if (filePath.startsWith("/"))
84734
- return { ok: false, error: "Invalid file path" };
84735
- return { ok: true };
84736
- }
84737
- function isPathInsideRoot(workspaceRoot, targetPath) {
84738
- const rel = relative(workspaceRoot, targetPath);
84739
- if (rel === "")
84740
- return true;
84741
- if (rel.startsWith("..") || rel.includes(`..${sep}`))
84742
- return false;
84743
- if (isAbsolute(rel))
84744
- return false;
84745
- return true;
84746
- }
84747
- async function resolvePathWithinWorkspace(workingDir, filePath) {
84748
- const basic = validateRelativePathSegments(filePath);
84749
- if (!basic.ok)
84750
- return basic;
84751
- let workspaceRoot;
84752
- try {
84753
- workspaceRoot = await realpath2(resolve4(workingDir));
84754
- } catch {
84755
- return { ok: false, error: "Working directory not found" };
84756
- }
84757
- const candidate = resolve4(workspaceRoot, filePath);
84758
- if (!isPathInsideRoot(workspaceRoot, candidate)) {
84759
- return { ok: false, error: "Path escapes workspace" };
84760
- }
84761
- try {
84762
- const resolved = await realpath2(candidate);
84763
- if (!isPathInsideRoot(workspaceRoot, resolved)) {
84764
- return { ok: false, error: "Path escapes workspace" };
84765
- }
84766
- return { ok: true, absolutePath: resolved };
84767
- } catch (err) {
84768
- const code2 = err?.code;
84769
- if (code2 !== "ENOENT") {
84770
- return { ok: false, error: "Invalid file path" };
84771
- }
84772
- const parent = dirname8(candidate);
84773
- if (parent === workspaceRoot || isPathInsideRoot(workspaceRoot, parent)) {
84774
- try {
84775
- const parentReal = await realpath2(parent);
84776
- if (!isPathInsideRoot(workspaceRoot, parentReal)) {
84777
- return { ok: false, error: "Path escapes workspace" };
84778
- }
84779
- const resolved = resolve4(parentReal, basename(candidate));
84780
- if (!isPathInsideRoot(workspaceRoot, resolved)) {
84781
- return { ok: false, error: "Path escapes workspace" };
84782
- }
84783
- return { ok: true, absolutePath: resolved };
84784
- } catch {
84785
- return { ok: true, absolutePath: candidate };
84786
- }
84787
- }
84788
- return { ok: false, error: "Path escapes workspace" };
84789
- }
84790
- }
84791
- function gunzipBase64Payload(base64, maxBytes) {
84792
- let compressed;
84793
- try {
84794
- compressed = Buffer.from(base64, "base64");
84795
- } catch {
84796
- return { ok: false, errorMessage: "Missing file data" };
84797
- }
84798
- if (compressed.length > maxBytes * 10) {
84799
- return { ok: false, errorMessage: "File content too large" };
84800
- }
84801
- try {
84802
- const content = gunzipSync(compressed, { maxOutputLength: maxBytes });
84803
- if (content.length > maxBytes) {
84804
- return { ok: false, errorMessage: "File content too large" };
84805
- }
84806
- return { ok: true, content };
84807
- } catch {
84808
- return { ok: false, errorMessage: "File content too large" };
84809
- }
84810
- }
84811
- var init_workspace_path_security = () => {};
84812
-
84813
- // src/infrastructure/services/workspace/workspace-visibility-policy.ts
84814
- import { exec as exec2, spawn as spawn4 } from "node:child_process";
84815
- import { promisify as promisify2 } from "node:util";
84816
- function isAlwaysExcludedDirName(name) {
84817
- return ALWAYS_EXCLUDE_DIR_NAMES.has(name);
84818
- }
84819
- function isSecretPath(relativePath) {
84820
- const normalized = relativePath.replace(/\\/g, "/");
84821
- return SECRET_PATH_PATTERNS.some((pattern2) => pattern2.test(normalized));
84822
- }
84823
- function hasExcludedDirSegment(relativePath) {
84824
- const segments = relativePath.split("/");
84825
- return segments.some((segment) => isAlwaysExcludedDirName(segment));
84826
- }
84827
- function isPathVisible(relativePath) {
84828
- if (!relativePath)
84829
- return true;
84830
- return !isSecretPath(relativePath) && !hasExcludedDirSegment(relativePath);
84831
- }
84832
- function isPathContentReadable(relativePath) {
84833
- return isPathVisible(relativePath);
84834
- }
84835
- async function isGitRepo2(rootDir) {
84836
- try {
84837
- const { stdout } = await execAsync2("git rev-parse --is-inside-work-tree", {
84838
- cwd: rootDir,
84839
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_PAGER: "cat", NO_COLOR: "1" },
84840
- maxBuffer: 1024 * 1024
84841
- });
84842
- return stdout.trim() === "true";
84843
- } catch {
84844
- return false;
84845
- }
84846
- }
84847
- async function filterGitIgnored(rootDir, relativePaths) {
84848
- if (relativePaths.length === 0)
84849
- return new Set;
84850
- const inRepo = await isGitRepo2(rootDir);
84851
- if (!inRepo)
84852
- return new Set;
84853
- try {
84854
- const stdout = await new Promise((resolve5, reject) => {
84855
- const child = spawn4("git", ["check-ignore", "--stdin", "-z"], {
84856
- cwd: rootDir,
84857
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_PAGER: "cat", NO_COLOR: "1" }
84858
- });
84859
- let output = "";
84860
- child.stdout.on("data", (chunk2) => {
84861
- output += chunk2.toString();
84862
- });
84863
- child.on("error", reject);
84864
- child.on("close", () => resolve5(output));
84865
- child.stdin.write(relativePaths.join(`
84866
- `));
84867
- child.stdin.end();
84868
- });
84869
- const ignored = new Set;
84870
- if (!stdout)
84871
- return ignored;
84872
- for (const entry of stdout.split("\x00")) {
84873
- const trimmed = entry.trim();
84874
- if (trimmed)
84875
- ignored.add(trimmed);
84876
- }
84877
- return ignored;
84878
- } catch {
84879
- return new Set;
84880
- }
84881
- }
84882
- var execAsync2, ALWAYS_EXCLUDE_DIR_NAMES, SECRET_PATH_PATTERNS;
84883
- var init_workspace_visibility_policy = __esm(() => {
84884
- execAsync2 = promisify2(exec2);
84885
- ALWAYS_EXCLUDE_DIR_NAMES = new Set([
84886
- "node_modules",
84887
- ".git",
84888
- "dist",
84889
- "build",
84890
- ".next",
84891
- "coverage",
84892
- "__pycache__",
84893
- ".turbo",
84894
- ".cache",
84895
- ".tmp",
84896
- "tmp",
84897
- "_generated",
84898
- ".vercel"
84899
- ]);
84900
- SECRET_PATH_PATTERNS = [
84901
- /^\.env$/,
84902
- /^\.env\./,
84903
- /\.pem$/,
84904
- /\.key$/,
84905
- /id_rsa$/,
84906
- /credentials\.json$/,
84907
- /^secrets(\/|$)/,
84908
- /^\.aws(\/|$)/
84909
- ];
84910
- });
84911
-
84912
- // src/infrastructure/services/workspace/dir-listing-scanner.ts
84913
- import { promises as fsPromises } from "node:fs";
84914
- import path3 from "node:path";
84915
- async function listDirectory(rootDir, dirPath, options) {
84916
- const maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
84917
- const scannedAt = Date.now();
84918
- const absDir = dirPath ? path3.join(rootDir, dirPath) : rootDir;
84919
- const resolvedRoot = path3.resolve(rootDir);
84920
- const resolvedDir = path3.resolve(absDir);
84921
- if (!isPathInsideRoot(resolvedRoot, resolvedDir)) {
84922
- return { dirPath, entries: [], scannedAt, truncated: false, totalCount: 0 };
84923
- }
84924
- let dirents;
84925
- try {
84926
- dirents = await fsPromises.readdir(absDir, { withFileTypes: true });
84927
- } catch {
84928
- return { dirPath, entries: [], scannedAt, truncated: false, totalCount: 0 };
84929
- }
84930
- const candidates = [];
84931
- for (const ent of dirents) {
84932
- if (isAlwaysExcludedDirName(ent.name))
84933
- continue;
84934
- const relativePath = dirPath ? `${dirPath}/${ent.name}` : ent.name;
84935
- if (!isPathVisible(relativePath))
84936
- continue;
84937
- if (ent.isDirectory()) {
84938
- candidates.push({ name: ent.name, path: relativePath, type: "directory" });
84939
- } else if (ent.isFile()) {
84940
- let size11;
84941
- try {
84942
- const st = await fsPromises.stat(path3.join(absDir, ent.name));
84943
- size11 = st.size;
84944
- } catch {}
84945
- candidates.push({ name: ent.name, path: relativePath, type: "file", size: size11 });
84946
- }
84947
- }
84948
- const ignored = await filterGitIgnored(rootDir, candidates.map((c) => c.path));
84949
- const visible = candidates.filter((c) => !ignored.has(c.path));
84950
- visible.sort((a, b) => {
84951
- if (a.type === "directory" && b.type === "file")
84952
- return -1;
84953
- if (a.type === "file" && b.type === "directory")
84954
- return 1;
84955
- return a.name.localeCompare(b.name, undefined, { sensitivity: "base" });
84956
- });
84957
- const totalCount = visible.length;
84958
- const truncated = totalCount > maxEntries;
84959
- const entries2 = visible.slice(0, maxEntries);
84960
- return { dirPath, entries: entries2, scannedAt, truncated, totalCount };
84961
- }
84962
- var DEFAULT_MAX_ENTRIES = 500;
84963
- var init_dir_listing_scanner = __esm(() => {
84964
- init_workspace_path_security();
84965
- init_workspace_visibility_policy();
84966
- });
84967
-
84968
- // src/infrastructure/services/workspace/dir-listing-sync.ts
84969
- import { gzipSync } from "node:zlib";
84970
- function listingCacheKey(workingDir, dirPath) {
84971
- return `${workingDir}\x00${dirPath}`;
84972
- }
84973
- async function scanDirListingForSync(workingDir, dirPath) {
84974
- const listing = await listDirectory(workingDir, dirPath);
84975
- const dataHash = computeDirListingContentHash(listing);
84976
- const cacheKey = listingCacheKey(workingDir, dirPath);
84977
- if (lastSyncedContentHash.get(cacheKey) === dataHash)
84978
- return null;
84979
- const json = JSON.stringify(listing);
84980
- return {
84981
- dirPath,
84982
- data: { compression: "gzip", content: gzipSync(Buffer.from(json)).toString("base64") },
84983
- dataHash,
84984
- scannedAt: listing.scannedAt,
84985
- truncated: listing.truncated,
84986
- totalCount: listing.totalCount
84987
- };
84988
- }
84989
- async function scanDirListingsWithConcurrency(workingDir, dirPaths) {
84990
- const items = [];
84991
- let index = 0;
84992
- async function worker() {
84993
- while (index < dirPaths.length) {
84994
- const currentIndex = index;
84995
- index += 1;
84996
- const dirPath = dirPaths[currentIndex];
84997
- if (dirPath === undefined)
84998
- continue;
84999
- const item = await scanDirListingForSync(workingDir, dirPath);
85000
- if (item)
85001
- items.push(item);
85002
- }
85003
- }
85004
- const workerCount = Math.min(SCAN_CONCURRENCY, dirPaths.length);
85005
- await Promise.all(Array.from({ length: workerCount }, () => worker()));
85006
- return items;
85007
- }
85008
- function markItemsSynced(workingDir, items) {
85009
- for (const item of items) {
85010
- lastSyncedContentHash.set(listingCacheKey(workingDir, item.dirPath), item.dataHash);
85011
- }
85012
- }
85013
- async function syncDirListingToBackend(session2, workingDir, dirPath) {
85014
- const item = await scanDirListingForSync(workingDir, dirPath);
85015
- if (!item)
85016
- return;
85017
- await session2.backend.mutation(api.workspaceFiles.syncDirListingV2, {
85018
- sessionId: session2.sessionId,
85019
- machineId: session2.machineId,
85020
- workingDir,
85021
- dirPath: item.dirPath,
85022
- data: item.data,
85023
- dataHash: item.dataHash,
85024
- scannedAt: item.scannedAt,
85025
- truncated: item.truncated,
85026
- totalCount: item.totalCount
85027
- });
85028
- markItemsSynced(workingDir, [item]);
85029
- }
85030
- async function syncDirListingsToBackend(session2, workingDir, dirPaths) {
85031
- const unique = [...new Set(dirPaths)];
85032
- if (unique.length === 0)
85033
- return;
85034
- if (unique.length === 1) {
85035
- const dirPath = unique[0];
85036
- if (dirPath !== undefined) {
85037
- await syncDirListingToBackend(session2, workingDir, dirPath);
85038
- }
85039
- return;
85040
- }
85041
- const items = await scanDirListingsWithConcurrency(workingDir, unique);
85042
- if (items.length === 0)
85043
- return;
85044
- for (let i2 = 0;i2 < items.length; i2 += MAX_BATCH_SIZE) {
85045
- const chunk2 = items.slice(i2, i2 + MAX_BATCH_SIZE);
85046
- await session2.backend.mutation(api.workspaceFiles.syncDirListingV2Batch, {
85047
- sessionId: session2.sessionId,
85048
- machineId: session2.machineId,
85049
- workingDir,
85050
- items: chunk2
85051
- });
85052
- markItemsSynced(workingDir, chunk2);
85053
- }
85054
- }
85055
- var MAX_BATCH_SIZE = 25, SCAN_CONCURRENCY = 4, lastSyncedContentHash;
85056
- var init_dir_listing_sync = __esm(() => {
85057
- init_dir_listing_content_hash();
85058
- init_dir_listing_scanner();
85059
- init_api3();
85060
- lastSyncedContentHash = new Map;
85061
- });
85062
-
85063
- // src/infrastructure/services/workspace/workspace-file-search.ts
85064
- import { promises as fsPromises2 } from "node:fs";
85065
- import path4 from "node:path";
85066
- async function searchWorkspaceFiles(rootDir, query, options) {
85067
- const maxResults = options?.maxResults ?? DEFAULT_MAX_RESULTS;
85068
- const scannedAt = Date.now();
85069
- const normalizedQuery = query.trim().toLowerCase();
85070
- const matches = [];
85071
- let visitedDirs = 0;
85072
- let truncated = false;
85073
- async function visitDir(relDir) {
85074
- if (visitedDirs >= MAX_VISIT_DIRS || matches.length >= maxResults) {
85075
- truncated = true;
85076
- return;
85077
- }
85078
- visitedDirs++;
85079
- const absDir = relDir ? path4.join(rootDir, relDir) : rootDir;
85080
- let dirents;
85081
- try {
85082
- dirents = await fsPromises2.readdir(absDir, { withFileTypes: true });
85083
- } catch {
85084
- return;
85085
- }
85086
- const fileCandidates = [];
85087
- const subdirs = [];
85088
- for (const ent of dirents) {
85089
- if (isAlwaysExcludedDirName(ent.name))
85090
- continue;
85091
- const relativePath = relDir ? `${relDir}/${ent.name}` : ent.name;
85092
- if (!isPathVisible(relativePath))
85093
- continue;
85094
- if (ent.isDirectory()) {
85095
- subdirs.push(relativePath);
85096
- } else if (ent.isFile()) {
85097
- fileCandidates.push(relativePath);
85098
- }
85099
- }
85100
- const ignored = await filterGitIgnored(rootDir, fileCandidates);
85101
- for (const filePath of fileCandidates) {
85102
- if (ignored.has(filePath))
85103
- continue;
85104
- const fileName = path4.basename(filePath).toLowerCase();
85105
- if (normalizedQuery === "" || fileName.includes(normalizedQuery)) {
85106
- matches.push({ path: filePath, type: "file" });
85107
- if (matches.length >= maxResults) {
85108
- truncated = true;
85109
- return;
85110
- }
85111
- }
85112
- }
85113
- for (const sub of subdirs) {
85114
- if (matches.length >= maxResults || visitedDirs >= MAX_VISIT_DIRS) {
85115
- truncated = true;
85116
- return;
85117
- }
85118
- await visitDir(sub);
85119
- }
85120
- }
85121
- await visitDir("");
85122
- return {
85123
- query,
85124
- entries: matches,
85125
- scannedAt,
85126
- truncated,
85127
- totalCount: matches.length
85128
- };
85129
- }
85130
- var DEFAULT_MAX_RESULTS = 300, MAX_VISIT_DIRS = 2000;
85131
- var init_workspace_file_search = __esm(() => {
85132
- init_workspace_visibility_policy();
85133
- });
85134
-
85135
- // src/commands/machine/daemon-start/dir-listing-subscription.ts
85136
- import { createHash as createHash3 } from "node:crypto";
85137
- import { gzipSync as gzipSync2 } from "node:zlib";
85138
- function logSubscriptionWarn(label, err) {
85139
- console.warn(`[${formatTimestamp()}] ⚠️ ${label}: ${getErrorMessage2(err)}`);
85140
- }
85141
- async function uploadDirListing(session2, workingDir, dirPath) {
85142
- await syncDirListingToBackend(session2, workingDir, dirPath);
85143
- await session2.backend.mutation(api.workspaceFiles.fulfillDirListingRequest, {
85144
- sessionId: session2.sessionId,
85145
- machineId: session2.machineId,
85146
- workingDir,
85147
- dirPath
85148
- });
85149
- }
85150
- async function uploadFileSearch(session2, workingDir, query) {
85151
- const result = await searchWorkspaceFiles(workingDir, query);
85152
- const json = JSON.stringify(result);
85153
- const dataHash = createHash3("md5").update(json).digest("hex");
85154
- const compressed = gzipSync2(Buffer.from(json)).toString("base64");
85155
- await session2.backend.mutation(api.workspaceFiles.syncFileSearchV2, {
85156
- sessionId: session2.sessionId,
85157
- machineId: session2.machineId,
85158
- workingDir,
85159
- query,
85160
- data: { compression: "gzip", content: compressed },
85161
- dataHash,
85162
- scannedAt: result.scannedAt,
85163
- truncated: result.truncated,
85164
- totalCount: result.totalCount
85165
- });
85166
- await session2.backend.mutation(api.workspaceFiles.fulfillFileSearchRequest, {
85167
- sessionId: session2.sessionId,
85168
- machineId: session2.machineId,
85169
- workingDir,
85170
- query
85171
- });
85172
- }
85173
- var fulfillDirListingRequestsEffect = (session2, requests) => exports_Effect.gen(function* () {
85174
- for (const request2 of requests) {
85175
- yield* exports_Effect.catchAll(exports_Effect.gen(function* () {
85176
- const start3 = Date.now();
85177
- yield* exports_Effect.tryPromise(() => uploadDirListing(session2, request2.workingDir, request2.dirPath));
85178
- console.log(`[${formatTimestamp()}] \uD83D\uDCC2 Dir listing fulfilled: ${request2.workingDir}/${request2.dirPath || "(root)"} (${Date.now() - start3}ms)`);
85179
- }), (err) => {
85180
- logSubscriptionWarn(`Dir listing failed for ${request2.workingDir}/${request2.dirPath}`, err);
85181
- return exports_Effect.void;
85182
- });
85183
- }
85184
- }), fulfillFileSearchRequestsEffect = (session2, requests) => exports_Effect.gen(function* () {
85185
- for (const request2 of requests) {
85186
- yield* exports_Effect.catchAll(exports_Effect.gen(function* () {
85187
- const start3 = Date.now();
85188
- yield* exports_Effect.tryPromise(() => uploadFileSearch(session2, request2.workingDir, request2.query));
85189
- console.log(`[${formatTimestamp()}] \uD83D\uDD0D File search fulfilled: ${request2.workingDir} query="${request2.query}" (${Date.now() - start3}ms)`);
85190
- }), (err) => {
85191
- logSubscriptionWarn(`File search failed for ${request2.workingDir}`, err);
85192
- return exports_Effect.void;
85193
- });
85194
- }
85195
- }), startDirListingSubscriptionEffect = (wsClient2) => exports_Effect.gen(function* () {
85196
- const session2 = yield* DaemonSessionService;
85197
- let processingDir = false;
85198
- let processingSearch = false;
85199
- const unsubDir = wsClient2.onUpdate(api.workspaceFiles.getPendingDirListingRequests, { sessionId: session2.sessionId, machineId: session2.machineId }, (requests) => {
85200
- if (!requests?.length || processingDir)
85201
- return;
85202
- processingDir = true;
85203
- exports_Effect.runPromise(fulfillDirListingRequestsEffect(session2, requests)).catch((err) => {
85204
- logSubscriptionWarn("Dir listing subscription processing failed", err);
85205
- }).finally(() => {
85206
- processingDir = false;
85207
- });
85208
- }, (err) => {
85209
- logSubscriptionWarn("Dir listing subscription error", err);
85210
- });
85211
- const unsubSearch = wsClient2.onUpdate(api.workspaceFiles.getPendingFileSearchRequests, { sessionId: session2.sessionId, machineId: session2.machineId }, (requests) => {
85212
- if (!requests?.length || processingSearch)
85213
- return;
85214
- processingSearch = true;
85215
- exports_Effect.runPromise(fulfillFileSearchRequestsEffect(session2, requests)).catch((err) => {
85216
- logSubscriptionWarn("File search subscription processing failed", err);
85217
- }).finally(() => {
85218
- processingSearch = false;
85219
- });
85220
- }, (err) => {
85221
- logSubscriptionWarn("File search subscription error", err);
85222
- });
85223
- console.log(`[${formatTimestamp()}] \uD83D\uDCC2 Dir listing subscription started (reactive)`);
85224
- return {
85225
- stop: () => {
85226
- unsubDir();
85227
- unsubSearch();
85228
- console.log(`[${formatTimestamp()}] \uD83D\uDCC2 Dir listing subscription stopped`);
85229
- }
85230
- };
85231
- });
85232
- var init_dir_listing_subscription = __esm(() => {
85233
- init_esm();
85234
- init_daemon_services();
85235
- init_api3();
85236
- init_dir_listing_sync();
85237
- init_workspace_file_search();
85238
- init_convex_error();
85239
- });
85240
-
85241
- // src/infrastructure/services/workspace/workspace-fs-watch-paths.ts
85242
- function parentDirPath(relativePath) {
85243
- const normalized = relativePath.replace(/\\/g, "/");
85244
- const idx = normalized.lastIndexOf("/");
85245
- return idx === -1 ? "" : normalized.slice(0, idx);
85246
- }
85247
- function shouldIgnoreWatchRelativePath(relativePath) {
85248
- const normalized = relativePath.replace(/\\/g, "/").replace(/\/+$/, "");
85249
- if (!normalized || normalized === ".")
85250
- return false;
85251
- return hasExcludedDirSegment(normalized);
85252
- }
85253
- function dirsToRefreshForEvent(relativePath, isDirectory = false) {
85254
- const normalized = relativePath.replace(/\\/g, "/").replace(/\/+$/, "");
85255
- if (!normalized || normalized === ".")
85256
- return [""];
85257
- const dirs = new Set;
85258
- dirs.add(parentDirPath(normalized));
85259
- if (isDirectory)
85260
- dirs.add(normalized);
85261
- return [...dirs];
85262
- }
85263
- function filterDirsByActiveSet(dirs, activeDirPaths) {
85264
- return dirs.filter((d) => activeDirPaths.has(d));
85265
- }
85266
- var init_workspace_fs_watch_paths = __esm(() => {
85267
- init_workspace_visibility_policy();
85268
- });
85269
-
85270
- // src/infrastructure/services/workspace/workspace-fs-watcher.ts
85271
- import { watch } from "node:fs";
85272
- import path5 from "node:path";
85273
- function isRecursiveWatchPlatform() {
85274
- return process.platform === "darwin" || process.platform === "win32";
85275
- }
85276
- function normalizeFilename(filename) {
85277
- if (filename == null)
85278
- return null;
85279
- const value = typeof filename === "string" ? filename : filename.toString();
85280
- if (!value || value === "." || value === "..")
85281
- return null;
85282
- return value.replace(/\\/g, "/");
85283
- }
85284
- function createWorkspaceFsWatcher(options) {
85285
- const absWorkingDir = path5.resolve(options.workingDir);
85286
- const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
85287
- let activeDirPaths = new Set(options.activeDirPaths);
85288
- let stopped = false;
85289
- let debounceTimer = null;
85290
- const pendingDirs = new Set;
85291
- let rootWatcher = null;
85292
- const dirWatchers = new Map;
85293
- const scheduleFlush = () => {
85294
- if (debounceTimer)
85295
- clearTimeout(debounceTimer);
85296
- debounceTimer = setTimeout(() => {
85297
- debounceTimer = null;
85298
- if (pendingDirs.size === 0)
85299
- return;
85300
- const dirs = [...pendingDirs];
85301
- pendingDirs.clear();
85302
- Promise.resolve(options.onRefreshDirs(dirs));
85303
- }, debounceMs);
85304
- debounceTimer.unref?.();
85305
- };
85306
- const enqueueDirs = (dirs) => {
85307
- for (const dir of dirs)
85308
- pendingDirs.add(dir);
85309
- scheduleFlush();
85310
- };
85311
- const handleRelativePath = (relativePath, isDirectory = false) => {
85312
- if (stopped)
85313
- return;
85314
- if (shouldIgnoreWatchRelativePath(relativePath))
85315
- return;
85316
- const toRefresh = filterDirsByActiveSet(dirsToRefreshForEvent(relativePath, isDirectory), activeDirPaths);
85317
- if (toRefresh.length > 0)
85318
- enqueueDirs(toRefresh);
85319
- };
85320
- const onWatchEvent = (watchedDirPath, filename) => {
85321
- const normalizedFilename = normalizeFilename(filename);
85322
- if (!normalizedFilename)
85323
- return;
85324
- let relativePath;
85325
- if (isRecursiveWatchPlatform()) {
85326
- relativePath = normalizedFilename;
85327
- } else if (watchedDirPath === "") {
85328
- relativePath = normalizedFilename;
85329
- } else {
85330
- relativePath = `${watchedDirPath}/${normalizedFilename}`;
85331
- }
85332
- const isDirectory = relativePath.endsWith("/");
85333
- handleRelativePath(relativePath.replace(/\/+$/, ""), isDirectory);
85334
- };
85335
- const closePerDirWatchers = () => {
85336
- for (const watcher of dirWatchers.values())
85337
- watcher.close();
85338
- dirWatchers.clear();
85339
- };
85340
- const startPerDirWatch = (dirPath) => {
85341
- if (dirPath === "" || dirWatchers.has(dirPath))
85342
- return;
85343
- const absDir = path5.join(absWorkingDir, dirPath);
85344
- const watcher = watch(absDir, (_eventType, filename) => {
85345
- onWatchEvent(dirPath, filename);
85346
- });
85347
- dirWatchers.set(dirPath, watcher);
85348
- };
85349
- const rewirePerDirWatches = () => {
85350
- const wanted = new Set;
85351
- for (const dirPath of activeDirPaths) {
85352
- if (dirPath !== "")
85353
- wanted.add(dirPath);
85354
- }
85355
- for (const [dirPath, watcher] of dirWatchers) {
85356
- if (!wanted.has(dirPath)) {
85357
- watcher.close();
85358
- dirWatchers.delete(dirPath);
85359
- }
85360
- }
85361
- for (const dirPath of wanted) {
85362
- if (!dirWatchers.has(dirPath))
85363
- startPerDirWatch(dirPath);
85364
- }
85365
- };
85366
- const startWatching = () => {
85367
- if (isRecursiveWatchPlatform()) {
85368
- rootWatcher = watch(absWorkingDir, { recursive: true }, (_eventType, filename) => {
85369
- onWatchEvent("", filename);
85370
- });
85371
- return;
85372
- }
85373
- rootWatcher = watch(absWorkingDir, (_eventType, filename) => {
85374
- onWatchEvent("", filename);
85375
- });
85376
- rewirePerDirWatches();
85377
- };
85378
- startWatching();
85379
- return {
85380
- updateActiveDirPaths: (paths) => {
85381
- activeDirPaths = new Set(paths);
85382
- if (!isRecursiveWatchPlatform() && !stopped) {
85383
- rewirePerDirWatches();
85384
- }
85385
- },
85386
- stop: () => {
85387
- stopped = true;
85388
- if (debounceTimer) {
85389
- clearTimeout(debounceTimer);
85390
- debounceTimer = null;
85391
- }
85392
- rootWatcher?.close();
85393
- rootWatcher = null;
85394
- closePerDirWatchers();
85395
- pendingDirs.clear();
85396
- }
85397
- };
85398
- }
85399
- var DEFAULT_DEBOUNCE_MS = 400;
85400
- var init_workspace_fs_watcher = __esm(() => {
85401
- init_workspace_fs_watch_paths();
85402
- });
85403
-
85404
- // src/commands/machine/daemon-start/dir-listing-watch-subscription.ts
85405
- var startDirListingWatchSubscriptionEffect = (wsClient2) => exports_Effect.gen(function* () {
85406
- const session2 = yield* DaemonSessionService;
85407
- const watchers = new Map;
85408
- const applyTargets = (targets) => {
85409
- const nextWorkingDirs = new Set(targets.map((t) => t.workingDir));
85410
- for (const [workingDir, handle] of watchers) {
85411
- if (!nextWorkingDirs.has(workingDir)) {
85412
- handle.stop();
85413
- watchers.delete(workingDir);
85414
- }
85415
- }
85416
- for (const target of targets) {
85417
- const activeSet = new Set(target.activeDirPaths);
85418
- const existing = watchers.get(target.workingDir);
85419
- if (existing) {
85420
- existing.updateActiveDirPaths(activeSet);
85421
- continue;
85422
- }
85423
- const handle = createWorkspaceFsWatcher({
85424
- workingDir: target.workingDir,
85425
- activeDirPaths: activeSet,
85426
- onRefreshDirs: async (dirPaths) => {
85427
- try {
85428
- await syncDirListingsToBackend(session2, target.workingDir, dirPaths);
85429
- } catch (err) {
85430
- console.warn(`[${formatTimestamp()}] ⚠️ FS watch sync failed for ${target.workingDir}: ${getErrorMessage2(err)}`);
85431
- }
85432
- }
85433
- });
85434
- watchers.set(target.workingDir, handle);
85435
- }
85436
- };
85437
- let stopped = false;
85438
- const unsub = wsClient2.onUpdate(api.workspaceFiles.listDirListingWatchTargets, { sessionId: session2.sessionId, machineId: session2.machineId }, (targets) => {
85439
- if (stopped)
85440
- return;
85441
- applyTargets(targets ?? []);
85442
- }, (err) => {
85443
- console.warn(`[${formatTimestamp()}] ⚠️ Dir listing watch subscription error: ${getErrorMessage2(err)}`);
85444
- });
85445
- console.log(`[${formatTimestamp()}] \uD83D\uDC41️ Dir listing FS watch subscription started`);
85446
- return {
85447
- stop: () => {
85448
- stopped = true;
85449
- unsub();
85450
- for (const handle of watchers.values())
85451
- handle.stop();
85452
- watchers.clear();
85453
- console.log(`[${formatTimestamp()}] \uD83D\uDC41️ Dir listing FS watch subscription stopped`);
85454
- }
85455
- };
85456
- });
85457
- var init_dir_listing_watch_subscription = __esm(() => {
85458
- init_esm();
85459
- init_daemon_services();
85460
- init_api3();
85461
- init_dir_listing_sync();
85462
- init_workspace_fs_watcher();
85463
- init_convex_error();
85464
- });
85465
-
85466
84707
  // src/events/daemon/agent/on-request-start-agent.ts
85467
84708
  function notifyAgentStartFailed(backend2, opts) {
85468
84709
  backend2.mutation(api.machines.emitAgentStartFailed, opts).catch((err) => {
@@ -85562,8 +84803,13 @@ var init_on_request_stop_agent = __esm(() => {
85562
84803
  init_stop_agent();
85563
84804
  });
85564
84805
 
84806
+ // src/commands/machine/daemon-start/utils.ts
84807
+ function formatTimestamp() {
84808
+ return new Date().toISOString().replace("T", " ").substring(0, 19);
84809
+ }
84810
+
85565
84811
  // src/commands/machine/daemon-start/handlers/orphan-tracker.ts
85566
- import { createHash as createHash4 } from "node:crypto";
84812
+ import { createHash as createHash2 } from "node:crypto";
85567
84813
  import {
85568
84814
  appendFileSync,
85569
84815
  existsSync as existsSync5,
@@ -85577,7 +84823,7 @@ import { homedir as homedir5 } from "node:os";
85577
84823
  import { join as join15 } from "node:path";
85578
84824
  function getUrlHash() {
85579
84825
  const url2 = getConvexUrl();
85580
- return createHash4("sha256").update(url2).digest("hex").substring(0, 8);
84826
+ return createHash2("sha256").update(url2).digest("hex").substring(0, 8);
85581
84827
  }
85582
84828
  function getChildPidsFilePath() {
85583
84829
  const dir = join15(homedir5(), ".chatroom");
@@ -85771,19 +85017,19 @@ class ProcessManager {
85771
85017
  this.pendingStops.clear();
85772
85018
  }
85773
85019
  waitForExit(runId, ms) {
85774
- return new Promise((resolve5) => {
85020
+ return new Promise((resolve4) => {
85775
85021
  const interval = 100;
85776
85022
  let elapsed3 = 0;
85777
85023
  const timer = setInterval(() => {
85778
85024
  if (!this.runningProcesses.has(runId)) {
85779
85025
  clearInterval(timer);
85780
- resolve5(true);
85026
+ resolve4(true);
85781
85027
  return;
85782
85028
  }
85783
85029
  elapsed3 += interval;
85784
85030
  if (elapsed3 >= ms) {
85785
85031
  clearInterval(timer);
85786
- resolve5(false);
85032
+ resolve4(false);
85787
85033
  }
85788
85034
  }, interval);
85789
85035
  });
@@ -85818,9 +85064,9 @@ var init_killer = __esm(() => {
85818
85064
  });
85819
85065
 
85820
85066
  // ../../services/backend/src/output-encoding.ts
85821
- import { gunzipSync as gunzipSync2, gzipSync as gzipSync3 } from "node:zlib";
85067
+ import { gunzipSync, gzipSync } from "node:zlib";
85822
85068
  function encodeOutput(plain) {
85823
- const compressed = gzipSync3(Buffer.from(plain, "utf-8"));
85069
+ const compressed = gzipSync(Buffer.from(plain, "utf-8"));
85824
85070
  return {
85825
85071
  compression: "gzip",
85826
85072
  content: compressed.toString("base64")
@@ -85998,7 +85244,7 @@ var init_output_store = __esm(() => {
85998
85244
  });
85999
85245
 
86000
85246
  // src/commands/machine/daemon-start/handlers/process/spawner.ts
86001
- import { spawn as spawn5 } from "node:child_process";
85247
+ import { spawn as spawn4 } from "node:child_process";
86002
85248
  async function flushTailV2(deps, tracked, force = false) {
86003
85249
  if (!force && !isRunLogObserved(tracked.runId))
86004
85250
  return;
@@ -86081,7 +85327,7 @@ async function spawnCommandProcess(deps, event, commandKey) {
86081
85327
  tempDirReady = true;
86082
85328
  }
86083
85329
  const store = createOutputStore(runIdStr);
86084
- const child = spawn5("sh", ["-c", script], {
85330
+ const child = spawn4("sh", ["-c", script], {
86085
85331
  cwd: workingDir,
86086
85332
  env: buildChatroomSpawnEnv(deps.convexUrl),
86087
85333
  stdio: ["ignore", "pipe", "pipe"],
@@ -86343,8 +85589,8 @@ var init_command_runner = __esm(() => {
86343
85589
  }).catch((err) => {
86344
85590
  console.warn(`[${formatTimestamp()}] ⚠️ Failed to mark run as killed on shutdown: ${getErrorMessage2(err)}`);
86345
85591
  })));
86346
- yield* exports_Effect.promise(() => new Promise((resolve5) => {
86347
- const t = setTimeout(resolve5, 3000);
85592
+ yield* exports_Effect.promise(() => new Promise((resolve4) => {
85593
+ const t = setTimeout(resolve4, 3000);
86348
85594
  t.unref?.();
86349
85595
  }));
86350
85596
  for (const [, tracked] of trackedEntries) {
@@ -86356,8 +85602,8 @@ var init_command_runner = __esm(() => {
86356
85602
  clearTrackedPids();
86357
85603
  yield* exports_Effect.promise(() => Promise.race([
86358
85604
  statusUpdates,
86359
- new Promise((resolve5) => {
86360
- const t = setTimeout(resolve5, 2000);
85605
+ new Promise((resolve4) => {
85606
+ const t = setTimeout(resolve4, 2000);
86361
85607
  t.unref?.();
86362
85608
  })
86363
85609
  ]));
@@ -86485,7 +85731,7 @@ var init_git = __esm(() => {
86485
85731
  });
86486
85732
 
86487
85733
  // src/infrastructure/local-actions/execute-local-action.ts
86488
- import { exec as exec3 } from "node:child_process";
85734
+ import { exec as exec2 } from "node:child_process";
86489
85735
  import { access as access3 } from "node:fs/promises";
86490
85736
  function escapeShellArg(arg) {
86491
85737
  return `"${arg.replace(/"/g, "\\\"")}"`;
@@ -86494,14 +85740,14 @@ function resolveWhichCommand(name) {
86494
85740
  return process.platform === "win32" ? `where ${name}` : `which ${name}`;
86495
85741
  }
86496
85742
  function isCliAvailable(cliName) {
86497
- return new Promise((resolve5) => {
86498
- exec3(resolveWhichCommand(cliName), (err) => {
86499
- resolve5(!err);
85743
+ return new Promise((resolve4) => {
85744
+ exec2(resolveWhichCommand(cliName), (err) => {
85745
+ resolve4(!err);
86500
85746
  });
86501
85747
  });
86502
85748
  }
86503
85749
  function execFireAndForget(command, logTag) {
86504
- exec3(command, (err) => {
85750
+ exec2(command, (err) => {
86505
85751
  if (err) {
86506
85752
  console.warn(`[${logTag}] exec failed: ${err.message}`);
86507
85753
  }
@@ -86620,10 +85866,10 @@ var init_local_actions = __esm(() => {
86620
85866
  import { execFileSync } from "node:child_process";
86621
85867
  function runPicker(command, args2) {
86622
85868
  try {
86623
- const path6 = execFileSync(command, args2, { encoding: "utf8", timeout: 300000 }).trim();
86624
- if (!path6)
85869
+ const path3 = execFileSync(command, args2, { encoding: "utf8", timeout: 300000 }).trim();
85870
+ if (!path3)
86625
85871
  return { success: false, error: "No folder selected" };
86626
- return { success: true, path: path6 };
85872
+ return { success: true, path: path3 };
86627
85873
  } catch (error) {
86628
85874
  const exitCode = typeof error === "object" && error !== null && "status" in error ? error.status : null;
86629
85875
  if (exitCode === 1)
@@ -86665,13 +85911,13 @@ function pickFolderDialog() {
86665
85911
  var init_pick_folder = () => {};
86666
85912
 
86667
85913
  // src/commands/machine/pid.ts
86668
- import { createHash as createHash5 } from "node:crypto";
85914
+ import { createHash as createHash3 } from "node:crypto";
86669
85915
  import { existsSync as existsSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, mkdirSync as mkdirSync5 } from "node:fs";
86670
85916
  import { homedir as homedir6 } from "node:os";
86671
85917
  import { join as join17 } from "node:path";
86672
85918
  function getUrlHash2() {
86673
85919
  const url2 = getConvexUrl();
86674
- return createHash5("sha256").update(url2).digest("hex").substring(0, 8);
85920
+ return createHash3("sha256").update(url2).digest("hex").substring(0, 8);
86675
85921
  }
86676
85922
  function getPidFileName() {
86677
85923
  return `daemon-${getUrlHash2()}.pid`;
@@ -86764,7 +86010,7 @@ async function waitForLockOrTimeout(deadline, intervalMs, sleep5) {
86764
86010
  async function acquireLockWithRetry(options) {
86765
86011
  const intervalMs = options?.intervalMs ?? LOCK_RETRY_INTERVAL_MS;
86766
86012
  const maxWaitMs = options?.maxWaitMs ?? LOCK_RETRY_MAX_WAIT_MS;
86767
- const sleep5 = options?.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
86013
+ const sleep5 = options?.sleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
86768
86014
  const deadline = Date.now() + maxWaitMs;
86769
86015
  if (await waitForLockOrTimeout(deadline, intervalMs, sleep5)) {
86770
86016
  return true;
@@ -87130,14 +86376,14 @@ var init_sse_event_buffer = __esm(() => {
87130
86376
  }
87131
86377
  _wake() {
87132
86378
  if (this._waiter) {
87133
- const resolve5 = this._waiter;
86379
+ const resolve4 = this._waiter;
87134
86380
  this._waiter = null;
87135
- resolve5();
86381
+ resolve4();
87136
86382
  }
87137
86383
  }
87138
86384
  _waitForData() {
87139
- return new Promise((resolve5) => {
87140
- this._waiter = resolve5;
86385
+ return new Promise((resolve4) => {
86386
+ this._waiter = resolve4;
87141
86387
  });
87142
86388
  }
87143
86389
  [Symbol.asyncIterator]() {
@@ -87192,8 +86438,8 @@ class OpencodeSdkSession {
87192
86438
  async prompt(input) {
87193
86439
  if (this.closed)
87194
86440
  throw new Error("Session is closed");
87195
- const idlePromise = new Promise((resolve5) => {
87196
- this._idleResolve = resolve5;
86441
+ const idlePromise = new Promise((resolve4) => {
86442
+ this._idleResolve = resolve4;
87197
86443
  });
87198
86444
  const IDLE_TIMEOUT_MS = 300000;
87199
86445
  const timeoutPromise = new Promise((_, reject) => {
@@ -87277,7 +86523,7 @@ var init_opencode_session = __esm(() => {
87277
86523
  });
87278
86524
 
87279
86525
  // src/infrastructure/harnesses/opencode-sdk/opencode-harness.ts
87280
- import { spawn as spawn6 } from "node:child_process";
86526
+ import { spawn as spawn5 } from "node:child_process";
87281
86527
  function harnessEventSessionId(event) {
87282
86528
  const p = event.properties;
87283
86529
  if (!p)
@@ -87524,14 +86770,14 @@ class OpencodeSdkHarness {
87524
86770
  }
87525
86771
  this.sessionListeners.clear();
87526
86772
  this.childProcess.kill("SIGTERM");
87527
- await new Promise((resolve5) => {
86773
+ await new Promise((resolve4) => {
87528
86774
  const timeout3 = setTimeout(() => {
87529
86775
  this.childProcess.kill("SIGKILL");
87530
- resolve5();
86776
+ resolve4();
87531
86777
  }, 5000);
87532
86778
  this.childProcess.once("exit", () => {
87533
86779
  clearTimeout(timeout3);
87534
- resolve5();
86780
+ resolve4();
87535
86781
  });
87536
86782
  });
87537
86783
  }
@@ -87551,7 +86797,7 @@ class OpencodeSdkHarness {
87551
86797
  }
87552
86798
  }
87553
86799
  var OPENCODE_COMMAND3 = "opencode", SERVE_STARTUP_TIMEOUT_MS2 = 1e4, startOpencodeSdkHarness = async (config3) => {
87554
- const childProcess = spawn6(OPENCODE_COMMAND3, ["serve", "--print-logs"], {
86800
+ const childProcess = spawn5(OPENCODE_COMMAND3, ["serve", "--print-logs"], {
87555
86801
  cwd: config3.workingDir,
87556
86802
  stdio: ["pipe", "pipe", "pipe"],
87557
86803
  shell: false,
@@ -88913,10 +88159,10 @@ class BufferedJournalFactory {
88913
88159
  const waitForInProgress = () => {
88914
88160
  if (!flushInProgress)
88915
88161
  return Promise.resolve();
88916
- return new Promise((resolve5) => {
88162
+ return new Promise((resolve4) => {
88917
88163
  const check3 = () => {
88918
88164
  if (!flushInProgress)
88919
- resolve5();
88165
+ resolve4();
88920
88166
  else
88921
88167
  setTimeout(check3, 10);
88922
88168
  };
@@ -89078,15 +88324,555 @@ async function assertRegisteredWorkingDir(session2, workingDir) {
89078
88324
  if (!workspaces.some((w) => normalizeWorkingDirForLookup(w.workingDir) === normalizedWorkingDir)) {
89079
88325
  return { ok: false, error: "Workspace not registered for this machine" };
89080
88326
  }
89081
- return { ok: true };
88327
+ return { ok: true };
88328
+ }
88329
+ var init_assert_registered_working_dir = __esm(() => {
88330
+ init_workspace_cache();
88331
+ });
88332
+
88333
+ // src/infrastructure/services/workspace/workspace-path-security.ts
88334
+ import { realpath as realpath2 } from "node:fs/promises";
88335
+ import { basename, dirname as dirname8, isAbsolute, relative, resolve as resolve4, sep } from "node:path";
88336
+ import { gunzipSync as gunzipSync2 } from "node:zlib";
88337
+ function validateRelativePathSegments(filePath) {
88338
+ if (filePath.includes("\x00"))
88339
+ return { ok: false, error: "Invalid file path" };
88340
+ if (filePath.includes(".."))
88341
+ return { ok: false, error: "Invalid file path" };
88342
+ if (filePath.startsWith("/"))
88343
+ return { ok: false, error: "Invalid file path" };
88344
+ return { ok: true };
88345
+ }
88346
+ function isPathInsideRoot(workspaceRoot, targetPath) {
88347
+ const rel = relative(workspaceRoot, targetPath);
88348
+ if (rel === "")
88349
+ return true;
88350
+ if (rel.startsWith("..") || rel.includes(`..${sep}`))
88351
+ return false;
88352
+ if (isAbsolute(rel))
88353
+ return false;
88354
+ return true;
88355
+ }
88356
+ async function resolvePathWithinWorkspace(workingDir, filePath) {
88357
+ const basic = validateRelativePathSegments(filePath);
88358
+ if (!basic.ok)
88359
+ return basic;
88360
+ let workspaceRoot;
88361
+ try {
88362
+ workspaceRoot = await realpath2(resolve4(workingDir));
88363
+ } catch {
88364
+ return { ok: false, error: "Working directory not found" };
88365
+ }
88366
+ const candidate = resolve4(workspaceRoot, filePath);
88367
+ if (!isPathInsideRoot(workspaceRoot, candidate)) {
88368
+ return { ok: false, error: "Path escapes workspace" };
88369
+ }
88370
+ try {
88371
+ const resolved = await realpath2(candidate);
88372
+ if (!isPathInsideRoot(workspaceRoot, resolved)) {
88373
+ return { ok: false, error: "Path escapes workspace" };
88374
+ }
88375
+ return { ok: true, absolutePath: resolved };
88376
+ } catch (err) {
88377
+ const code2 = err?.code;
88378
+ if (code2 !== "ENOENT") {
88379
+ return { ok: false, error: "Invalid file path" };
88380
+ }
88381
+ const parent = dirname8(candidate);
88382
+ if (parent === workspaceRoot || isPathInsideRoot(workspaceRoot, parent)) {
88383
+ try {
88384
+ const parentReal = await realpath2(parent);
88385
+ if (!isPathInsideRoot(workspaceRoot, parentReal)) {
88386
+ return { ok: false, error: "Path escapes workspace" };
88387
+ }
88388
+ const resolved = resolve4(parentReal, basename(candidate));
88389
+ if (!isPathInsideRoot(workspaceRoot, resolved)) {
88390
+ return { ok: false, error: "Path escapes workspace" };
88391
+ }
88392
+ return { ok: true, absolutePath: resolved };
88393
+ } catch {
88394
+ return { ok: true, absolutePath: candidate };
88395
+ }
88396
+ }
88397
+ return { ok: false, error: "Path escapes workspace" };
88398
+ }
88399
+ }
88400
+ function gunzipBase64Payload(base64, maxBytes) {
88401
+ let compressed;
88402
+ try {
88403
+ compressed = Buffer.from(base64, "base64");
88404
+ } catch {
88405
+ return { ok: false, errorMessage: "Missing file data" };
88406
+ }
88407
+ if (compressed.length > maxBytes * 10) {
88408
+ return { ok: false, errorMessage: "File content too large" };
88409
+ }
88410
+ try {
88411
+ const content = gunzipSync2(compressed, { maxOutputLength: maxBytes });
88412
+ if (content.length > maxBytes) {
88413
+ return { ok: false, errorMessage: "File content too large" };
88414
+ }
88415
+ return { ok: true, content };
88416
+ } catch {
88417
+ return { ok: false, errorMessage: "File content too large" };
88418
+ }
88419
+ }
88420
+ var init_workspace_path_security = () => {};
88421
+
88422
+ // ../../node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
88423
+ var require_ignore = __commonJS((exports, module) => {
88424
+ function makeArray(subject) {
88425
+ return Array.isArray(subject) ? subject : [subject];
88426
+ }
88427
+ var UNDEFINED = undefined;
88428
+ var EMPTY = "";
88429
+ var SPACE = " ";
88430
+ var ESCAPE = "\\";
88431
+ var REGEX_TEST_BLANK_LINE = /^\s+$/;
88432
+ var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
88433
+ var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
88434
+ var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
88435
+ var REGEX_SPLITALL_CRLF = /\r?\n/g;
88436
+ var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
88437
+ var REGEX_TEST_TRAILING_SLASH = /\/$/;
88438
+ var SLASH = "/";
88439
+ var TMP_KEY_IGNORE = "node-ignore";
88440
+ if (typeof Symbol !== "undefined") {
88441
+ TMP_KEY_IGNORE = Symbol.for("node-ignore");
88442
+ }
88443
+ var KEY_IGNORE = TMP_KEY_IGNORE;
88444
+ var define = (object, key, value) => {
88445
+ Object.defineProperty(object, key, { value });
88446
+ return value;
88447
+ };
88448
+ var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
88449
+ var RETURN_FALSE = () => false;
88450
+ var sanitizeRange = (range) => range.replace(REGEX_REGEXP_RANGE, (match17, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match17 : EMPTY);
88451
+ var cleanRangeBackSlash = (slashes) => {
88452
+ const { length: length2 } = slashes;
88453
+ return slashes.slice(0, length2 - length2 % 2);
88454
+ };
88455
+ var REPLACERS = [
88456
+ [
88457
+ /^\uFEFF/,
88458
+ () => EMPTY
88459
+ ],
88460
+ [
88461
+ /((?:\\\\)*?)(\\?\s+)$/,
88462
+ (_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
88463
+ ],
88464
+ [
88465
+ /(\\+?)\s/g,
88466
+ (_, m1) => {
88467
+ const { length: length2 } = m1;
88468
+ return m1.slice(0, length2 - length2 % 2) + SPACE;
88469
+ }
88470
+ ],
88471
+ [
88472
+ /[\\$.|*+(){^]/g,
88473
+ (match17) => `\\${match17}`
88474
+ ],
88475
+ [
88476
+ /(?!\\)\?/g,
88477
+ () => "[^/]"
88478
+ ],
88479
+ [
88480
+ /^\//,
88481
+ () => "^"
88482
+ ],
88483
+ [
88484
+ /\//g,
88485
+ () => "\\/"
88486
+ ],
88487
+ [
88488
+ /^\^*\\\*\\\*\\\//,
88489
+ () => "^(?:.*\\/)?"
88490
+ ],
88491
+ [
88492
+ /^(?=[^^])/,
88493
+ function startingReplacer() {
88494
+ return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^";
88495
+ }
88496
+ ],
88497
+ [
88498
+ /\\\/\\\*\\\*(?=\\\/|$)/g,
88499
+ (_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"
88500
+ ],
88501
+ [
88502
+ /(^|[^\\]+)(\\\*)+(?=.+)/g,
88503
+ (_, p1, p2) => {
88504
+ const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
88505
+ return p1 + unescaped;
88506
+ }
88507
+ ],
88508
+ [
88509
+ /\\\\\\(?=[$.|*+(){^])/g,
88510
+ () => ESCAPE
88511
+ ],
88512
+ [
88513
+ /\\\\/g,
88514
+ () => ESCAPE
88515
+ ],
88516
+ [
88517
+ /(\\)?\[([^\]/]*?)(\\*)($|\])/g,
88518
+ (match17, leadEscape, range, endEscape, close2) => leadEscape === ESCAPE ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close2}` : close2 === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range)}${endEscape}]` : "[]" : "[]"
88519
+ ],
88520
+ [
88521
+ /(?:[^*])$/,
88522
+ (match17) => /\/$/.test(match17) ? `${match17}$` : `${match17}(?=$|\\/$)`
88523
+ ]
88524
+ ];
88525
+ var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/;
88526
+ var MODE_IGNORE = "regex";
88527
+ var MODE_CHECK_IGNORE = "checkRegex";
88528
+ var UNDERSCORE = "_";
88529
+ var TRAILING_WILD_CARD_REPLACERS = {
88530
+ [MODE_IGNORE](_, p1) {
88531
+ const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
88532
+ return `${prefix}(?=$|\\/$)`;
88533
+ },
88534
+ [MODE_CHECK_IGNORE](_, p1) {
88535
+ const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
88536
+ return `${prefix}(?=$|\\/$)`;
88537
+ }
88538
+ };
88539
+ var makeRegexPrefix = (pattern2) => REPLACERS.reduce((prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern2)), pattern2);
88540
+ var isString3 = (subject) => typeof subject === "string";
88541
+ var checkPattern = (pattern2) => pattern2 && isString3(pattern2) && !REGEX_TEST_BLANK_LINE.test(pattern2) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern2) && pattern2.indexOf("#") !== 0;
88542
+ var splitPattern = (pattern2) => pattern2.split(REGEX_SPLITALL_CRLF).filter(Boolean);
88543
+
88544
+ class IgnoreRule {
88545
+ constructor(pattern2, mark2, body, ignoreCase, negative, prefix) {
88546
+ this.pattern = pattern2;
88547
+ this.mark = mark2;
88548
+ this.negative = negative;
88549
+ define(this, "body", body);
88550
+ define(this, "ignoreCase", ignoreCase);
88551
+ define(this, "regexPrefix", prefix);
88552
+ }
88553
+ get regex() {
88554
+ const key = UNDERSCORE + MODE_IGNORE;
88555
+ if (this[key]) {
88556
+ return this[key];
88557
+ }
88558
+ return this._make(MODE_IGNORE, key);
88559
+ }
88560
+ get checkRegex() {
88561
+ const key = UNDERSCORE + MODE_CHECK_IGNORE;
88562
+ if (this[key]) {
88563
+ return this[key];
88564
+ }
88565
+ return this._make(MODE_CHECK_IGNORE, key);
88566
+ }
88567
+ _make(mode, key) {
88568
+ const str = this.regexPrefix.replace(REGEX_REPLACE_TRAILING_WILDCARD, TRAILING_WILD_CARD_REPLACERS[mode]);
88569
+ const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
88570
+ return define(this, key, regex);
88571
+ }
88572
+ }
88573
+ var createRule = ({
88574
+ pattern: pattern2,
88575
+ mark: mark2
88576
+ }, ignoreCase) => {
88577
+ let negative = false;
88578
+ let body = pattern2;
88579
+ if (body.indexOf("!") === 0) {
88580
+ negative = true;
88581
+ body = body.substr(1);
88582
+ }
88583
+ body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
88584
+ const regexPrefix = makeRegexPrefix(body);
88585
+ return new IgnoreRule(pattern2, mark2, body, ignoreCase, negative, regexPrefix);
88586
+ };
88587
+
88588
+ class RuleManager {
88589
+ constructor(ignoreCase) {
88590
+ this._ignoreCase = ignoreCase;
88591
+ this._rules = [];
88592
+ }
88593
+ _add(pattern2) {
88594
+ if (pattern2 && pattern2[KEY_IGNORE]) {
88595
+ this._rules = this._rules.concat(pattern2._rules._rules);
88596
+ this._added = true;
88597
+ return;
88598
+ }
88599
+ if (isString3(pattern2)) {
88600
+ pattern2 = {
88601
+ pattern: pattern2
88602
+ };
88603
+ }
88604
+ if (checkPattern(pattern2.pattern)) {
88605
+ const rule = createRule(pattern2, this._ignoreCase);
88606
+ this._added = true;
88607
+ this._rules.push(rule);
88608
+ }
88609
+ }
88610
+ add(pattern2) {
88611
+ this._added = false;
88612
+ makeArray(isString3(pattern2) ? splitPattern(pattern2) : pattern2).forEach(this._add, this);
88613
+ return this._added;
88614
+ }
88615
+ test(path3, checkUnignored, mode) {
88616
+ let ignored = false;
88617
+ let unignored = false;
88618
+ let matchedRule;
88619
+ this._rules.forEach((rule) => {
88620
+ const { negative } = rule;
88621
+ if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
88622
+ return;
88623
+ }
88624
+ const matched = rule[mode].test(path3);
88625
+ if (!matched) {
88626
+ return;
88627
+ }
88628
+ ignored = !negative;
88629
+ unignored = negative;
88630
+ matchedRule = negative ? UNDEFINED : rule;
88631
+ });
88632
+ const ret = {
88633
+ ignored,
88634
+ unignored
88635
+ };
88636
+ if (matchedRule) {
88637
+ ret.rule = matchedRule;
88638
+ }
88639
+ return ret;
88640
+ }
88641
+ }
88642
+ var throwError = (message, Ctor) => {
88643
+ throw new Ctor(message);
88644
+ };
88645
+ var checkPath = (path3, originalPath, doThrow) => {
88646
+ if (!isString3(path3)) {
88647
+ return doThrow(`path must be a string, but got \`${originalPath}\``, TypeError);
88648
+ }
88649
+ if (!path3) {
88650
+ return doThrow(`path must not be empty`, TypeError);
88651
+ }
88652
+ if (checkPath.isNotRelative(path3)) {
88653
+ const r = "`path.relative()`d";
88654
+ return doThrow(`path should be a ${r} string, but got "${originalPath}"`, RangeError);
88655
+ }
88656
+ return true;
88657
+ };
88658
+ var isNotRelative = (path3) => REGEX_TEST_INVALID_PATH.test(path3);
88659
+ checkPath.isNotRelative = isNotRelative;
88660
+ checkPath.convert = (p) => p;
88661
+
88662
+ class Ignore {
88663
+ constructor({
88664
+ ignorecase = true,
88665
+ ignoreCase = ignorecase,
88666
+ allowRelativePaths = false
88667
+ } = {}) {
88668
+ define(this, KEY_IGNORE, true);
88669
+ this._rules = new RuleManager(ignoreCase);
88670
+ this._strictPathCheck = !allowRelativePaths;
88671
+ this._initCache();
88672
+ }
88673
+ _initCache() {
88674
+ this._ignoreCache = Object.create(null);
88675
+ this._testCache = Object.create(null);
88676
+ }
88677
+ add(pattern2) {
88678
+ if (this._rules.add(pattern2)) {
88679
+ this._initCache();
88680
+ }
88681
+ return this;
88682
+ }
88683
+ addPattern(pattern2) {
88684
+ return this.add(pattern2);
88685
+ }
88686
+ _test(originalPath, cache, checkUnignored, slices) {
88687
+ const path3 = originalPath && checkPath.convert(originalPath);
88688
+ checkPath(path3, originalPath, this._strictPathCheck ? throwError : RETURN_FALSE);
88689
+ return this._t(path3, cache, checkUnignored, slices);
88690
+ }
88691
+ checkIgnore(path3) {
88692
+ if (!REGEX_TEST_TRAILING_SLASH.test(path3)) {
88693
+ return this.test(path3);
88694
+ }
88695
+ const slices = path3.split(SLASH).filter(Boolean);
88696
+ slices.pop();
88697
+ if (slices.length) {
88698
+ const parent = this._t(slices.join(SLASH) + SLASH, this._testCache, true, slices);
88699
+ if (parent.ignored) {
88700
+ return parent;
88701
+ }
88702
+ }
88703
+ return this._rules.test(path3, false, MODE_CHECK_IGNORE);
88704
+ }
88705
+ _t(path3, cache, checkUnignored, slices) {
88706
+ if (path3 in cache) {
88707
+ return cache[path3];
88708
+ }
88709
+ if (!slices) {
88710
+ slices = path3.split(SLASH).filter(Boolean);
88711
+ }
88712
+ slices.pop();
88713
+ if (!slices.length) {
88714
+ return cache[path3] = this._rules.test(path3, checkUnignored, MODE_IGNORE);
88715
+ }
88716
+ const parent = this._t(slices.join(SLASH) + SLASH, cache, checkUnignored, slices);
88717
+ return cache[path3] = parent.ignored ? parent : this._rules.test(path3, checkUnignored, MODE_IGNORE);
88718
+ }
88719
+ ignores(path3) {
88720
+ return this._test(path3, this._ignoreCache, false).ignored;
88721
+ }
88722
+ createFilter() {
88723
+ return (path3) => !this.ignores(path3);
88724
+ }
88725
+ filter(paths) {
88726
+ return makeArray(paths).filter(this.createFilter());
88727
+ }
88728
+ test(path3) {
88729
+ return this._test(path3, this._testCache, true);
88730
+ }
88731
+ }
88732
+ var factory2 = (options) => new Ignore(options);
88733
+ var isPathValid = (path3) => checkPath(path3 && checkPath.convert(path3), path3, RETURN_FALSE);
88734
+ var setupWindows = () => {
88735
+ const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
88736
+ checkPath.convert = makePosix;
88737
+ const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
88738
+ checkPath.isNotRelative = (path3) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path3) || isNotRelative(path3);
88739
+ };
88740
+ if (typeof process !== "undefined" && process.platform === "win32") {
88741
+ setupWindows();
88742
+ }
88743
+ module.exports = factory2;
88744
+ factory2.default = factory2;
88745
+ module.exports.isPathValid = isPathValid;
88746
+ define(module.exports, Symbol.for("setupWindows"), setupWindows);
88747
+ });
88748
+
88749
+ // src/infrastructure/services/workspace/workspace-ignore.ts
88750
+ import { readFile as readFile7 } from "node:fs/promises";
88751
+ import path3 from "node:path";
88752
+ async function loadWorkspaceIgnore(rootDir) {
88753
+ const ig = import_ignore.default();
88754
+ for (const name of IGNORE_FILES) {
88755
+ try {
88756
+ const content = await readFile7(path3.join(rootDir, name), "utf-8");
88757
+ ig.add(content);
88758
+ } catch {}
88759
+ }
88760
+ return ig;
88761
+ }
88762
+ function isPathIgnoredByRules(ig, relativePath) {
88763
+ const normalized = relativePath.replace(/\\/g, "/");
88764
+ return ig.ignores(normalized);
88765
+ }
88766
+ var import_ignore, IGNORE_FILES;
88767
+ var init_workspace_ignore = __esm(() => {
88768
+ import_ignore = __toESM(require_ignore(), 1);
88769
+ IGNORE_FILES = [".gitignore", ".cursorignore"];
88770
+ });
88771
+
88772
+ // src/infrastructure/services/workspace/workspace-visibility-policy.ts
88773
+ import { spawn as spawn6 } from "node:child_process";
88774
+ function isAlwaysExcludedDirName(name) {
88775
+ return ALWAYS_EXCLUDE_DIR_NAMES.has(name);
88776
+ }
88777
+ function isSecretPath(relativePath) {
88778
+ const normalized = relativePath.replace(/\\/g, "/");
88779
+ return SECRET_PATH_PATTERNS.some((pattern2) => pattern2.test(normalized));
88780
+ }
88781
+ function hasExcludedDirSegment(relativePath) {
88782
+ const segments = relativePath.split("/");
88783
+ return segments.some((segment) => isAlwaysExcludedDirName(segment));
88784
+ }
88785
+ function isPathVisible(relativePath) {
88786
+ if (!relativePath)
88787
+ return true;
88788
+ return !isSecretPath(relativePath) && !hasExcludedDirSegment(relativePath);
88789
+ }
88790
+ function isPathContentReadable(relativePath) {
88791
+ return isPathVisible(relativePath);
88792
+ }
88793
+ async function filterIgnoredPaths(rootDir, relativePaths) {
88794
+ if (relativePaths.length === 0)
88795
+ return new Set;
88796
+ const inRepo = await isGitRepo(rootDir);
88797
+ if (inRepo)
88798
+ return filterGitIgnored(rootDir, relativePaths);
88799
+ const ig = await loadWorkspaceIgnore(rootDir);
88800
+ const ignored = new Set;
88801
+ for (const p of relativePaths) {
88802
+ if (isPathIgnoredByRules(ig, p))
88803
+ ignored.add(p);
88804
+ }
88805
+ return ignored;
88806
+ }
88807
+ async function filterGitIgnored(rootDir, relativePaths) {
88808
+ if (relativePaths.length === 0)
88809
+ return new Set;
88810
+ const inRepo = await isGitRepo(rootDir);
88811
+ if (!inRepo)
88812
+ return new Set;
88813
+ try {
88814
+ const stdout = await new Promise((resolve5, reject) => {
88815
+ const child = spawn6("git", ["check-ignore", "--stdin", "-z"], {
88816
+ cwd: rootDir,
88817
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_PAGER: "cat", NO_COLOR: "1" }
88818
+ });
88819
+ let output = "";
88820
+ child.stdout.on("data", (chunk2) => {
88821
+ output += chunk2.toString();
88822
+ });
88823
+ child.on("error", reject);
88824
+ child.on("close", () => resolve5(output));
88825
+ child.stdin.write(relativePaths.join(`
88826
+ `));
88827
+ child.stdin.end();
88828
+ });
88829
+ const ignored = new Set;
88830
+ if (!stdout)
88831
+ return ignored;
88832
+ for (const entry of stdout.split("\x00")) {
88833
+ const trimmed = entry.trim();
88834
+ if (trimmed)
88835
+ ignored.add(trimmed);
88836
+ }
88837
+ return ignored;
88838
+ } catch {
88839
+ return new Set;
88840
+ }
89082
88841
  }
89083
- var init_assert_registered_working_dir = __esm(() => {
89084
- init_workspace_cache();
88842
+ var ALWAYS_EXCLUDE_DIR_NAMES, SECRET_PATH_PATTERNS;
88843
+ var init_workspace_visibility_policy = __esm(() => {
88844
+ init_workspace_ignore();
88845
+ init_git_reader();
88846
+ ALWAYS_EXCLUDE_DIR_NAMES = new Set([
88847
+ "node_modules",
88848
+ ".git",
88849
+ "dist",
88850
+ "build",
88851
+ ".next",
88852
+ "coverage",
88853
+ "__pycache__",
88854
+ ".turbo",
88855
+ ".cache",
88856
+ ".tmp",
88857
+ "tmp",
88858
+ "_generated",
88859
+ ".vercel"
88860
+ ]);
88861
+ SECRET_PATH_PATTERNS = [
88862
+ /^\.env$/,
88863
+ /^\.env\./,
88864
+ /\.pem$/,
88865
+ /\.key$/,
88866
+ /id_rsa$/,
88867
+ /credentials\.json$/,
88868
+ /^secrets(\/|$)/,
88869
+ /^\.aws(\/|$)/
88870
+ ];
89085
88871
  });
89086
88872
 
89087
88873
  // src/commands/machine/daemon-start/file-content-fulfillment.ts
89088
- import { readFile as readFile7 } from "node:fs/promises";
89089
- import { gzipSync as gzipSync4 } from "node:zlib";
88874
+ import { readFile as readFile8 } from "node:fs/promises";
88875
+ import { gzipSync as gzipSync2 } from "node:zlib";
89090
88876
  function getErrorCause(error) {
89091
88877
  if (typeof error === "object" && error !== null && "cause" in error && error.cause !== undefined) {
89092
88878
  return error.cause;
@@ -89097,14 +88883,14 @@ function isENOENT(error) {
89097
88883
  const cause3 = getErrorCause(error);
89098
88884
  return typeof cause3 === "object" && cause3 !== null && "code" in cause3 && cause3.code === "ENOENT";
89099
88885
  }
89100
- function isBinaryFile(path6) {
89101
- const lastDot = path6.lastIndexOf(".");
88886
+ function isBinaryFile(path4) {
88887
+ const lastDot = path4.lastIndexOf(".");
89102
88888
  if (lastDot === -1)
89103
88889
  return false;
89104
- return BINARY_EXTENSIONS.has(path6.slice(lastDot).toLowerCase());
88890
+ return BINARY_EXTENSIONS.has(path4.slice(lastDot).toLowerCase());
89105
88891
  }
89106
88892
  function gzipPlainText(text) {
89107
- return gzipSync4(Buffer.from(text)).toString("base64");
88893
+ return gzipSync2(Buffer.from(text)).toString("base64");
89108
88894
  }
89109
88895
  function fulfillGzippedContentEffect(session2, workingDir, filePath, plainText, truncated) {
89110
88896
  return exports_Effect.catchAll(exports_Effect.tryPromise(() => session2.backend.mutation(api.workspaceFiles.fulfillFileContentV2, {
@@ -89203,7 +88989,7 @@ var init_file_content_fulfillment = __esm(() => {
89203
88989
  console.log(`[${formatTimestamp()}] \uD83D\uDCC4 File content blocked: ${filePath} [secret] (${elapsed4}ms)`);
89204
88990
  continue;
89205
88991
  }
89206
- const readOutcome = yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => readFile7(absolutePath)).pipe(exports_Effect.map((buffer) => {
88992
+ const readOutcome = yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => readFile8(absolutePath)).pipe(exports_Effect.map((buffer) => {
89207
88993
  if (buffer.length > MAX_CONTENT_BYTES) {
89208
88994
  return {
89209
88995
  kind: "ok",
@@ -89226,7 +89012,7 @@ var init_file_content_fulfillment = __esm(() => {
89226
89012
  continue;
89227
89013
  }
89228
89014
  const { content, truncated } = readOutcome.kind === "ok" ? readOutcome : { content: readOutcome.content, truncated: readOutcome.truncated };
89229
- const compressed = gzipSync4(Buffer.from(content));
89015
+ const compressed = gzipSync2(Buffer.from(content));
89230
89016
  const contentCompressed = compressed.toString("base64");
89231
89017
  yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => session2.backend.mutation(api.workspaceFiles.fulfillFileContentV2, {
89232
89018
  sessionId: session2.sessionId,
@@ -89280,15 +89066,476 @@ var init_file_content_subscription = __esm(() => {
89280
89066
  init_convex_error();
89281
89067
  });
89282
89068
 
89069
+ // src/infrastructure/services/workspace/file-tree-data-hash.ts
89070
+ import { createHash as createHash4 } from "node:crypto";
89071
+ function computeFileTreeDataHash(tree) {
89072
+ return createHash4("md5").update(JSON.stringify(tree)).digest("hex");
89073
+ }
89074
+ var init_file_tree_data_hash = () => {};
89075
+
89076
+ // src/infrastructure/services/workspace/file-tree-partition.ts
89077
+ import { createHash as createHash5 } from "node:crypto";
89078
+ import { gzipSync as gzipSync3 } from "node:zlib";
89079
+ function computeShardDataHash(payload) {
89080
+ return createHash5("md5").update(JSON.stringify(payload)).digest("hex");
89081
+ }
89082
+ function shardIdForPath(path4) {
89083
+ const slash = path4.indexOf("/");
89084
+ return slash === -1 ? "__root__" : path4.slice(0, slash);
89085
+ }
89086
+ function shouldUseV3Upload(tree) {
89087
+ return Buffer.byteLength(JSON.stringify(tree), "utf8") > MAX_TREE_JSON_BYTES;
89088
+ }
89089
+ function childShardId(path4, parentShardId) {
89090
+ if (parentShardId === "__root__") {
89091
+ return shardIdForPath(path4);
89092
+ }
89093
+ if (path4 === parentShardId) {
89094
+ return parentShardId;
89095
+ }
89096
+ const prefix = `${parentShardId}/`;
89097
+ if (!path4.startsWith(prefix)) {
89098
+ return parentShardId;
89099
+ }
89100
+ const remainder = path4.slice(prefix.length);
89101
+ const slash = remainder.indexOf("/");
89102
+ return slash === -1 ? parentShardId : `${parentShardId}/${remainder.slice(0, slash)}`;
89103
+ }
89104
+ function groupEntriesByShardId(entries2, parentShardId) {
89105
+ const groups = new Map;
89106
+ for (const entry of entries2) {
89107
+ const shardId = parentShardId === "__root__" ? shardIdForPath(entry.path) : childShardId(entry.path, parentShardId);
89108
+ const group = groups.get(shardId) ?? [];
89109
+ group.push(entry);
89110
+ groups.set(shardId, group);
89111
+ }
89112
+ return groups;
89113
+ }
89114
+ function buildPreparedShard(shardId, payload) {
89115
+ const compressed = gzipSync3(Buffer.from(JSON.stringify(payload))).toString("base64");
89116
+ return {
89117
+ shardId,
89118
+ payload,
89119
+ dataHash: computeShardDataHash(payload),
89120
+ entryCount: payload.entries.length,
89121
+ data: { compression: "gzip", content: compressed }
89122
+ };
89123
+ }
89124
+ function prepareShardGroup(entries2, shardId, tree) {
89125
+ const payload = {
89126
+ entries: entries2,
89127
+ scannedAt: tree.scannedAt,
89128
+ rootDir: tree.rootDir
89129
+ };
89130
+ const compressed = gzipSync3(Buffer.from(JSON.stringify(payload))).toString("base64");
89131
+ if (Buffer.byteLength(compressed, "utf8") <= MAX_SHARD_JSON_BYTES) {
89132
+ return [buildPreparedShard(shardId, payload)];
89133
+ }
89134
+ const subgroups = groupEntriesByShardId(entries2, shardId);
89135
+ if (subgroups.size <= 1) {
89136
+ return [buildPreparedShard(shardId, payload)];
89137
+ }
89138
+ const shards = [];
89139
+ for (const [subId, subEntries] of subgroups) {
89140
+ shards.push(...prepareShardGroup(subEntries, subId, tree));
89141
+ }
89142
+ return shards;
89143
+ }
89144
+ function partitionFileTree(tree) {
89145
+ const topGroups = groupEntriesByShardId(tree.entries, "__root__");
89146
+ const shards = [];
89147
+ for (const [shardId, entries2] of topGroups) {
89148
+ shards.push(...prepareShardGroup(entries2, shardId, tree));
89149
+ }
89150
+ return shards;
89151
+ }
89152
+ var MAX_TREE_JSON_BYTES, MAX_SHARD_JSON_BYTES, MAX_SHARD_BATCH_SIZE = 8;
89153
+ var init_file_tree_partition = __esm(() => {
89154
+ MAX_TREE_JSON_BYTES = 900 * 1024;
89155
+ MAX_SHARD_JSON_BYTES = 800 * 1024;
89156
+ });
89157
+
89158
+ // src/infrastructure/services/workspace/workspace-file-walk.ts
89159
+ import { promises as fsPromises } from "node:fs";
89160
+ import path4 from "node:path";
89161
+ async function walkWorkspaceFiles(rootDir, options) {
89162
+ const maxFilePaths = options?.maxFilePaths ?? 1e4;
89163
+ const filePaths = [];
89164
+ let truncated = false;
89165
+ const inRepo = await isGitRepo(rootDir);
89166
+ const parsedIgnore = inRepo ? null : await loadWorkspaceIgnore(rootDir);
89167
+ async function isIgnored(relativePath) {
89168
+ if (inRepo) {
89169
+ const ignored = await filterIgnoredPaths(rootDir, [relativePath]);
89170
+ return ignored.has(relativePath);
89171
+ }
89172
+ return parsedIgnore !== null && isPathIgnoredByRules(parsedIgnore, relativePath);
89173
+ }
89174
+ async function visitDir(relDir) {
89175
+ if (truncated || filePaths.length >= maxFilePaths) {
89176
+ truncated = true;
89177
+ return;
89178
+ }
89179
+ const absDir = relDir ? path4.join(rootDir, relDir) : rootDir;
89180
+ let dirents;
89181
+ try {
89182
+ dirents = await fsPromises.readdir(absDir, { withFileTypes: true });
89183
+ } catch {
89184
+ return;
89185
+ }
89186
+ for (const ent of dirents) {
89187
+ if (truncated || filePaths.length >= maxFilePaths) {
89188
+ truncated = true;
89189
+ return;
89190
+ }
89191
+ if (isAlwaysExcludedDirName(ent.name))
89192
+ continue;
89193
+ const relativePath = relDir ? `${relDir}/${ent.name}` : ent.name;
89194
+ if (!isPathVisible(relativePath))
89195
+ continue;
89196
+ if (ent.isDirectory()) {
89197
+ if (await isIgnored(relativePath))
89198
+ continue;
89199
+ await visitDir(relativePath);
89200
+ } else if (ent.isFile()) {
89201
+ if (await isIgnored(relativePath))
89202
+ continue;
89203
+ filePaths.push(relativePath);
89204
+ if (filePaths.length >= maxFilePaths)
89205
+ truncated = true;
89206
+ }
89207
+ }
89208
+ }
89209
+ await visitDir("");
89210
+ return { filePaths, truncated };
89211
+ }
89212
+ var init_workspace_file_walk = __esm(() => {
89213
+ init_workspace_ignore();
89214
+ init_workspace_visibility_policy();
89215
+ init_git_reader();
89216
+ });
89217
+
89218
+ // src/infrastructure/services/workspace/file-tree-scanner.ts
89219
+ async function scanFileTree(rootDir, options) {
89220
+ const maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
89221
+ const scannedAt = Date.now();
89222
+ const walk = await walkWorkspaceFiles(rootDir, { maxFilePaths: maxEntries });
89223
+ const filteredPaths = walk.filePaths.filter((p) => !isExcluded(p));
89224
+ const entries2 = buildEntries(filteredPaths, maxEntries);
89225
+ return {
89226
+ entries: entries2,
89227
+ scannedAt,
89228
+ rootDir
89229
+ };
89230
+ }
89231
+ function isExcluded(filePath) {
89232
+ return hasExcludedDirSegment(filePath);
89233
+ }
89234
+ function buildEntries(filePaths, maxEntries) {
89235
+ const directories = new Set;
89236
+ for (const filePath of filePaths) {
89237
+ const parts2 = filePath.split("/");
89238
+ for (let i2 = 1;i2 < parts2.length; i2++) {
89239
+ directories.add(parts2.slice(0, i2).join("/"));
89240
+ }
89241
+ }
89242
+ const entries2 = [];
89243
+ const sortedDirs = Array.from(directories).sort();
89244
+ for (const dir of sortedDirs) {
89245
+ if (entries2.length >= maxEntries)
89246
+ break;
89247
+ entries2.push({ path: dir, type: "directory" });
89248
+ }
89249
+ const sortedFiles = filePaths.slice().sort();
89250
+ for (const file of sortedFiles) {
89251
+ if (entries2.length >= maxEntries)
89252
+ break;
89253
+ entries2.push({ path: file, type: "file" });
89254
+ }
89255
+ return entries2;
89256
+ }
89257
+ var DEFAULT_MAX_ENTRIES = 1e4;
89258
+ var init_file_tree_scanner = __esm(() => {
89259
+ init_workspace_file_walk();
89260
+ init_workspace_visibility_policy();
89261
+ });
89262
+
89263
+ // src/infrastructure/services/workspace/file-tree-v3-upload.ts
89264
+ async function uploadFileTreeV3(session2, workingDir, tree, syncGeneration) {
89265
+ const shards = partitionFileTree(tree);
89266
+ const shardIds = [];
89267
+ for (let i2 = 0;i2 < shards.length; i2 += MAX_SHARD_BATCH_SIZE) {
89268
+ const batch = shards.slice(i2, i2 + MAX_SHARD_BATCH_SIZE);
89269
+ await session2.backend.mutation(api.workspaceFiles.syncFileTreeShardV3Batch, {
89270
+ sessionId: session2.sessionId,
89271
+ machineId: session2.machineId,
89272
+ workingDir,
89273
+ syncGeneration,
89274
+ items: batch.map((s) => ({
89275
+ shardId: s.shardId,
89276
+ data: s.data,
89277
+ dataHash: s.dataHash,
89278
+ scannedAt: tree.scannedAt,
89279
+ entryCount: s.entryCount
89280
+ }))
89281
+ });
89282
+ for (const s of batch)
89283
+ shardIds.push(s.shardId);
89284
+ }
89285
+ await session2.backend.mutation(api.workspaceFiles.syncFileTreeManifestV3, {
89286
+ sessionId: session2.sessionId,
89287
+ machineId: session2.machineId,
89288
+ workingDir,
89289
+ syncGeneration,
89290
+ shardIds,
89291
+ totalEntryCount: tree.entries.length,
89292
+ complete: true,
89293
+ scannedAt: tree.scannedAt
89294
+ });
89295
+ return { shardIds, totalEntryCount: tree.entries.length };
89296
+ }
89297
+ var init_file_tree_v3_upload = __esm(() => {
89298
+ init_file_tree_partition();
89299
+ init_api3();
89300
+ });
89301
+
89302
+ // src/infrastructure/services/workspace/workspace-sync-diff.ts
89303
+ function diffPathIndexes(previous, next4) {
89304
+ const added = [];
89305
+ const removed = [];
89306
+ const typeChanged = [];
89307
+ const prev = previous ?? {};
89308
+ for (const [path5, type] of Object.entries(next4)) {
89309
+ if (!(path5 in prev)) {
89310
+ added.push(path5);
89311
+ } else if (prev[path5] !== type) {
89312
+ typeChanged.push(path5);
89313
+ }
89314
+ }
89315
+ for (const path5 of Object.keys(prev)) {
89316
+ if (!(path5 in next4)) {
89317
+ removed.push(path5);
89318
+ }
89319
+ }
89320
+ added.sort();
89321
+ removed.sort();
89322
+ typeChanged.sort();
89323
+ return { added, removed, typeChanged };
89324
+ }
89325
+ function formatPathDiffSummary(diff8) {
89326
+ return `+${diff8.added.length} added, -${diff8.removed.length} removed${diff8.typeChanged.length > 0 ? `, ~${diff8.typeChanged.length} type-changed` : ""}`;
89327
+ }
89328
+
89329
+ // src/infrastructure/services/workspace/workspace-sync-queue.ts
89330
+ function queueKey(machineId, workingDir) {
89331
+ return `${machineId}\x00${workingDir}`;
89332
+ }
89333
+ function getOrCreateState(key) {
89334
+ let state = queues.get(key);
89335
+ if (!state) {
89336
+ state = { running: false, trailing: false, drainPromise: null };
89337
+ queues.set(key, state);
89338
+ }
89339
+ return state;
89340
+ }
89341
+ async function enqueueFileTreeSync(machineId, workingDir, task) {
89342
+ const key = queueKey(machineId, workingDir);
89343
+ const state = getOrCreateState(key);
89344
+ if (state.running) {
89345
+ state.trailing = true;
89346
+ return state.drainPromise ?? Promise.resolve();
89347
+ }
89348
+ const run3 = async () => {
89349
+ state.running = true;
89350
+ try {
89351
+ do {
89352
+ state.trailing = false;
89353
+ await task();
89354
+ } while (state.trailing);
89355
+ } finally {
89356
+ state.running = false;
89357
+ state.trailing = false;
89358
+ if (!state.trailing) {
89359
+ queues.delete(key);
89360
+ }
89361
+ }
89362
+ };
89363
+ state.drainPromise = run3();
89364
+ return state.drainPromise;
89365
+ }
89366
+ var queues;
89367
+ var init_workspace_sync_queue = __esm(() => {
89368
+ queues = new Map;
89369
+ });
89370
+
89371
+ // src/infrastructure/services/workspace/workspace-sync-state.ts
89372
+ import { createHash as createHash6, randomUUID as randomUUID6 } from "node:crypto";
89373
+ import * as fs11 from "node:fs/promises";
89374
+ import { homedir as homedir7 } from "node:os";
89375
+ import { join as join18 } from "node:path";
89376
+ function workspaceKeyFor(workingDir) {
89377
+ const normalized = normalizeWorkingDirForLookup(workingDir);
89378
+ return createHash6("sha256").update(normalized).digest("hex").slice(0, 16);
89379
+ }
89380
+ function buildPathIndex(entries2) {
89381
+ const paths = {};
89382
+ for (const entry of entries2) {
89383
+ paths[entry.path] = entry.type;
89384
+ }
89385
+ return paths;
89386
+ }
89387
+ function manifestPath(machineId, workingDir) {
89388
+ const key = workspaceKeyFor(workingDir);
89389
+ return join18(SYNC_STATE_DIR, machineId, key, "manifest.json");
89390
+ }
89391
+ async function ensureDir(filePath) {
89392
+ await fs11.mkdir(join18(filePath, ".."), { recursive: true, mode: 448 });
89393
+ }
89394
+ async function loadWorkspaceSyncManifest(machineId, workingDir) {
89395
+ try {
89396
+ const content = await fs11.readFile(manifestPath(machineId, workingDir), "utf-8");
89397
+ return JSON.parse(content);
89398
+ } catch (error) {
89399
+ if (error.code === "ENOENT")
89400
+ return null;
89401
+ return null;
89402
+ }
89403
+ }
89404
+ async function saveWorkspaceSyncManifest(manifest) {
89405
+ const filePath = manifestPath(manifest.machineId, manifest.workingDir);
89406
+ const tempPath = `${filePath}.tmp`;
89407
+ await ensureDir(filePath);
89408
+ const content = JSON.stringify(manifest, null, 2);
89409
+ await fs11.writeFile(tempPath, content, { mode: 384 });
89410
+ await fs11.rename(tempPath, filePath);
89411
+ }
89412
+ function createManifestFromTree(args2) {
89413
+ return {
89414
+ version: SYNC_STATE_VERSION,
89415
+ machineId: args2.machineId,
89416
+ workingDir: normalizeWorkingDirForLookup(args2.workingDir),
89417
+ syncGeneration: randomUUID6(),
89418
+ completedAt: Date.now(),
89419
+ scanner: args2.scanner,
89420
+ dataHash: args2.dataHash,
89421
+ totalEntryCount: args2.tree.entries.length,
89422
+ paths: buildPathIndex(args2.tree.entries)
89423
+ };
89424
+ }
89425
+ var SYNC_STATE_VERSION = "1", SYNC_STATE_DIR;
89426
+ var init_workspace_sync_state = __esm(() => {
89427
+ SYNC_STATE_DIR = join18(homedir7(), ".chatroom", "sync-state");
89428
+ });
89429
+
89430
+ // src/commands/machine/daemon-start/file-tree-subscription.ts
89431
+ import { gzipSync as gzipSync4 } from "node:zlib";
89432
+ function logSubscriptionWarn(label, err) {
89433
+ console.warn(`[${formatTimestamp()}] ⚠️ ${label}: ${getErrorMessage2(err)}`);
89434
+ }
89435
+ async function syncScannedFileTree(session2, normalizedWorkingDir, tree, dataHash, syncGeneration) {
89436
+ if (shouldUseV3Upload(tree)) {
89437
+ await uploadFileTreeV3(session2, normalizedWorkingDir, tree, syncGeneration);
89438
+ return;
89439
+ }
89440
+ const treeJson = JSON.stringify(tree);
89441
+ const compressed = gzipSync4(Buffer.from(treeJson)).toString("base64");
89442
+ await session2.backend.mutation(api.workspaceFiles.syncFileTreeV2, {
89443
+ sessionId: session2.sessionId,
89444
+ machineId: session2.machineId,
89445
+ workingDir: normalizedWorkingDir,
89446
+ data: { compression: "gzip", content: compressed },
89447
+ dataHash,
89448
+ scannedAt: tree.scannedAt
89449
+ });
89450
+ }
89451
+ async function uploadFileTree(session2, workingDir) {
89452
+ const normalizedWorkingDir = normalizeWorkingDirForLookup(workingDir);
89453
+ const previousManifest = await loadWorkspaceSyncManifest(session2.machineId, normalizedWorkingDir);
89454
+ const tree = await scanFileTree(normalizedWorkingDir);
89455
+ const dataHash = computeFileTreeDataHash(tree);
89456
+ if (previousManifest?.dataHash === dataHash) {
89457
+ await session2.backend.mutation(api.workspaceFiles.fulfillFileTreeRequest, {
89458
+ sessionId: session2.sessionId,
89459
+ machineId: session2.machineId,
89460
+ workingDir: normalizedWorkingDir
89461
+ });
89462
+ console.log(`[${formatTimestamp()}] \uD83C\uDF33 File tree unchanged, skipped upload: ${normalizedWorkingDir}`);
89463
+ return;
89464
+ }
89465
+ const pathDiff = diffPathIndexes(previousManifest?.paths, buildPathIndex(tree.entries));
89466
+ if (previousManifest) {
89467
+ console.log(`[${formatTimestamp()}] \uD83C\uDF33 File tree diff: ${formatPathDiffSummary(pathDiff)} (${normalizedWorkingDir})`);
89468
+ }
89469
+ const scanner = await isGitRepo(normalizedWorkingDir) ? "git" : "filesystem";
89470
+ const manifest = createManifestFromTree({
89471
+ machineId: session2.machineId,
89472
+ workingDir: normalizedWorkingDir,
89473
+ scanner,
89474
+ dataHash,
89475
+ tree
89476
+ });
89477
+ await syncScannedFileTree(session2, normalizedWorkingDir, tree, dataHash, manifest.syncGeneration);
89478
+ await session2.backend.mutation(api.workspaceFiles.fulfillFileTreeRequest, {
89479
+ sessionId: session2.sessionId,
89480
+ machineId: session2.machineId,
89481
+ workingDir: normalizedWorkingDir
89482
+ });
89483
+ await saveWorkspaceSyncManifest(manifest);
89484
+ }
89485
+ var startFileTreeSubscriptionEffect = (wsClient2) => exports_Effect.gen(function* () {
89486
+ const session2 = yield* DaemonSessionService;
89487
+ const unsubscribe = wsClient2.onUpdate(api.workspaceFiles.getPendingFileTreeRequests, { sessionId: session2.sessionId, machineId: session2.machineId }, (requests) => {
89488
+ if (!requests?.length)
89489
+ return;
89490
+ const uniqueDirs = [
89491
+ ...new Set(requests.map((r) => normalizeWorkingDirForLookup(r.workingDir)))
89492
+ ];
89493
+ for (const workingDir of uniqueDirs) {
89494
+ const normalized = normalizeWorkingDirForLookup(workingDir);
89495
+ enqueueFileTreeSync(session2.machineId, normalized, () => {
89496
+ const start3 = Date.now();
89497
+ return uploadFileTree(session2, normalized).then(() => {
89498
+ console.log(`[${formatTimestamp()}] \uD83C\uDF33 File tree fulfilled: ${normalized} (${Date.now() - start3}ms)`);
89499
+ }).catch((err) => {
89500
+ logSubscriptionWarn(`File tree failed for ${normalized}`, err);
89501
+ });
89502
+ }).catch((err) => {
89503
+ logSubscriptionWarn("File tree queue drain failed", err);
89504
+ });
89505
+ }
89506
+ }, (err) => {
89507
+ logSubscriptionWarn("File tree subscription error", err);
89508
+ });
89509
+ console.log(`[${formatTimestamp()}] \uD83C\uDF33 File tree subscription started (reactive)`);
89510
+ return {
89511
+ stop: () => {
89512
+ unsubscribe();
89513
+ console.log(`[${formatTimestamp()}] \uD83C\uDF33 File tree subscription stopped`);
89514
+ }
89515
+ };
89516
+ });
89517
+ var init_file_tree_subscription = __esm(() => {
89518
+ init_esm();
89519
+ init_daemon_services();
89520
+ init_api3();
89521
+ init_git_reader();
89522
+ init_file_tree_data_hash();
89523
+ init_file_tree_partition();
89524
+ init_file_tree_scanner();
89525
+ init_file_tree_v3_upload();
89526
+ init_workspace_sync_queue();
89527
+ init_workspace_sync_state();
89528
+ init_convex_error();
89529
+ });
89530
+
89283
89531
  // src/commands/machine/daemon-start/file-write-errors.ts
89284
89532
  function unsupportedFileWriteOperationMessage(operation) {
89285
89533
  return `Unsupported file write operation "${operation}". ` + "Please upgrade chatroom-cli to the latest version and restart the machine daemon " + "(e.g. npm install -g chatroom-cli@latest).";
89286
89534
  }
89287
89535
 
89288
89536
  // src/commands/machine/daemon-start/file-write-fulfillment.ts
89289
- import { access as access4, mkdir as mkdir8, rename as rename4, rm as rm2, writeFile as writeFile7 } from "node:fs/promises";
89537
+ import { access as access4, mkdir as mkdir9, rename as rename5, rm as rm3, writeFile as writeFile8 } from "node:fs/promises";
89290
89538
  import { dirname as dirname9 } from "node:path";
89291
- import { gzipSync as gzipSync5 } from "node:zlib";
89292
89539
  function isTerminalFileWriteError(errorMessage) {
89293
89540
  const terminalMessages = new Set([
89294
89541
  "Invalid file path",
@@ -89308,28 +89555,6 @@ function isTerminalFileWriteError(errorMessage) {
89308
89555
  return true;
89309
89556
  return terminalMessages.has(errorMessage);
89310
89557
  }
89311
- function parentDirPath2(filePath) {
89312
- const idx = filePath.lastIndexOf("/");
89313
- return idx === -1 ? "" : filePath.slice(0, idx);
89314
- }
89315
- async function syncParentDirListingAfterWrite(session2, workingDir, filePath) {
89316
- const dirPath = parentDirPath2(filePath);
89317
- const listing = await listDirectory(workingDir, dirPath);
89318
- const json = JSON.stringify(listing);
89319
- const dataHash = computeDirListingContentHash(listing);
89320
- const compressed = gzipSync5(Buffer.from(json)).toString("base64");
89321
- await session2.backend.mutation(api.workspaceFiles.syncDirListingV2, {
89322
- sessionId: session2.sessionId,
89323
- machineId: session2.machineId,
89324
- workingDir,
89325
- dirPath,
89326
- data: { compression: "gzip", content: compressed },
89327
- dataHash,
89328
- scannedAt: listing.scannedAt,
89329
- truncated: listing.truncated,
89330
- totalCount: listing.totalCount
89331
- });
89332
- }
89333
89558
  async function completeWriteRequest(session2, requestId, result) {
89334
89559
  await session2.backend.mutation(api.workspaceFiles.completeFileWriteRequest, {
89335
89560
  sessionId: session2.sessionId,
@@ -89362,9 +89587,9 @@ async function validateWriteOperation(operation, absolutePath) {
89362
89587
  }
89363
89588
  async function writePayloadToDisk(absolutePath, operation, content) {
89364
89589
  if (operation === "create") {
89365
- await mkdir8(dirname9(absolutePath), { recursive: true });
89590
+ await mkdir9(dirname9(absolutePath), { recursive: true });
89366
89591
  }
89367
- await writeFile7(absolutePath, content);
89592
+ await writeFile8(absolutePath, content);
89368
89593
  }
89369
89594
  async function fulfillOneFileWriteRequest(session2, request2) {
89370
89595
  const startTime = Date.now();
@@ -89425,12 +89650,8 @@ async function fulfillOneFileWriteRequest(session2, request2) {
89425
89650
  });
89426
89651
  return;
89427
89652
  }
89428
- await mkdir8(dirname9(targetResolved.absolutePath), { recursive: true });
89429
- await rename4(resolved.absolutePath, targetResolved.absolutePath);
89430
- await syncParentDirListingAfterWrite(session2, workingDir, filePath);
89431
- if (parentDirPath2(filePath) !== parentDirPath2(request2.targetFilePath)) {
89432
- await syncParentDirListingAfterWrite(session2, workingDir, request2.targetFilePath);
89433
- }
89653
+ await mkdir9(dirname9(targetResolved.absolutePath), { recursive: true });
89654
+ await rename5(resolved.absolutePath, targetResolved.absolutePath);
89434
89655
  await completeWriteRequest(session2, request2._id, { status: "done" });
89435
89656
  const elapsed4 = Date.now() - startTime;
89436
89657
  console.log(`[${formatTimestamp()}] ✏️ File rename fulfilled: ${filePath} → ${request2.targetFilePath} (${elapsed4}ms)`);
@@ -89445,8 +89666,7 @@ async function fulfillOneFileWriteRequest(session2, request2) {
89445
89666
  });
89446
89667
  return;
89447
89668
  }
89448
- await mkdir8(resolved.absolutePath, { recursive: true });
89449
- await syncParentDirListingAfterWrite(session2, workingDir, filePath);
89669
+ await mkdir9(resolved.absolutePath, { recursive: true });
89450
89670
  await completeWriteRequest(session2, request2._id, { status: "done" });
89451
89671
  const elapsed4 = Date.now() - startTime;
89452
89672
  console.log(`[${formatTimestamp()}] ✏️ Directory mkdir fulfilled: ${filePath} (${elapsed4}ms)`);
@@ -89468,8 +89688,7 @@ async function fulfillOneFileWriteRequest(session2, request2) {
89468
89688
  });
89469
89689
  return;
89470
89690
  }
89471
- await rm2(resolved.absolutePath, { recursive: true, force: false });
89472
- await syncParentDirListingAfterWrite(session2, workingDir, filePath);
89691
+ await rm3(resolved.absolutePath, { recursive: true, force: false });
89473
89692
  await completeWriteRequest(session2, request2._id, { status: "done" });
89474
89693
  const elapsed4 = Date.now() - startTime;
89475
89694
  console.log(`[${formatTimestamp()}] ✏️ File delete fulfilled: ${filePath} (${elapsed4}ms)`);
@@ -89499,7 +89718,6 @@ async function fulfillOneFileWriteRequest(session2, request2) {
89499
89718
  return;
89500
89719
  }
89501
89720
  await writePayloadToDisk(resolved.absolutePath, operation, payload.content);
89502
- await syncParentDirListingAfterWrite(session2, workingDir, filePath);
89503
89721
  await completeWriteRequest(session2, request2._id, { status: "done" });
89504
89722
  const elapsed3 = Date.now() - startTime;
89505
89723
  console.log(`[${formatTimestamp()}] ✏️ File write fulfilled: ${filePath} (${operation}, ${(payload.content.length / 1024).toFixed(1)}KB, ${elapsed3}ms)`);
@@ -89522,8 +89740,6 @@ var init_file_write_fulfillment = __esm(() => {
89522
89740
  init_daemon_services();
89523
89741
  init_api3();
89524
89742
  init_assert_registered_working_dir();
89525
- init_dir_listing_content_hash();
89526
- init_dir_listing_scanner();
89527
89743
  init_workspace_path_security();
89528
89744
  MAX_CONTENT_BYTES2 = 512 * 1024;
89529
89745
  fulfillFileWriteRequestsEffect = exports_Effect.gen(function* () {
@@ -89579,7 +89795,7 @@ var init_file_write_subscription = __esm(() => {
89579
89795
  });
89580
89796
 
89581
89797
  // src/infrastructure/git/git-state-pipeline.ts
89582
- import { createHash as createHash6 } from "node:crypto";
89798
+ import { createHash as createHash7 } from "node:crypto";
89583
89799
 
89584
89800
  class GitStatePipeline {
89585
89801
  fields;
@@ -89611,7 +89827,7 @@ class GitStatePipeline {
89611
89827
  const raw = values3.get(field.key) ?? field.defaultValue;
89612
89828
  hashInput[field.key] = field.toHashable(raw);
89613
89829
  }
89614
- return createHash6("md5").update(JSON.stringify(hashInput)).digest("hex");
89830
+ return createHash7("md5").update(JSON.stringify(hashInput)).digest("hex");
89615
89831
  }
89616
89832
  toMutationArgs(values3, slim) {
89617
89833
  const args2 = {};
@@ -89941,7 +90157,7 @@ var init_git_heartbeat = __esm(() => {
89941
90157
  });
89942
90158
 
89943
90159
  // src/commands/machine/daemon-start/git-subscription.ts
89944
- import { gzipSync as gzipSync6 } from "node:zlib";
90160
+ import { gzipSync as gzipSync5 } from "node:zlib";
89945
90161
  function extractDiffStatFromShowOutput(content) {
89946
90162
  for (const line of content.split(`
89947
90163
  `)) {
@@ -89956,7 +90172,7 @@ async function processFullDiff(deps, req) {
89956
90172
  if (result.status === "available" || result.status === "truncated") {
89957
90173
  const diffStatResult = await getDiffStat(req.workingDir);
89958
90174
  const diffStat = diffStatResult.status === "available" ? diffStatResult.diffStat : { filesChanged: 0, insertions: 0, deletions: 0 };
89959
- const compressed = gzipSync6(Buffer.from(result.content));
90175
+ const compressed = gzipSync5(Buffer.from(result.content));
89960
90176
  const diffContentCompressed = compressed.toString("base64");
89961
90177
  await deps.backend.mutation(api.workspaces.upsertFullDiffV2, {
89962
90178
  sessionId: deps.sessionId,
@@ -89968,7 +90184,7 @@ async function processFullDiff(deps, req) {
89968
90184
  });
89969
90185
  console.log(`[${formatTimestamp()}] \uD83D\uDCC4 Full diff pushed: ${req.workingDir} (${diffStat.filesChanged} files, ${(Buffer.byteLength(result.content) / 1024).toFixed(1)}KB → ${(compressed.length / 1024).toFixed(1)}KB gzip, ${result.truncated ? "truncated" : "complete"})`);
89970
90186
  } else {
89971
- const emptyCompressed = gzipSync6(Buffer.from("")).toString("base64");
90187
+ const emptyCompressed = gzipSync5(Buffer.from("")).toString("base64");
89972
90188
  await deps.backend.mutation(api.workspaces.upsertFullDiffV2, {
89973
90189
  sessionId: deps.sessionId,
89974
90190
  machineId: deps.machineId,
@@ -90069,7 +90285,7 @@ async function processCommitDetail(deps, req) {
90069
90285
  ]);
90070
90286
  await upsertCommitDetailResult(deps, req, result, metadata);
90071
90287
  if (result.status === "available" || result.status === "truncated") {
90072
- const compressed = gzipSync6(Buffer.from(result.content));
90288
+ const compressed = gzipSync5(Buffer.from(result.content));
90073
90289
  console.log(`[${formatTimestamp()}] \uD83D\uDD0D Commit detail pushed: ${req.sha.slice(0, 7)} in ${req.workingDir} (${(Buffer.byteLength(result.content) / 1024).toFixed(1)}KB → ${(compressed.length / 1024).toFixed(1)}KB gzip)`);
90074
90290
  }
90075
90291
  }
@@ -90100,7 +90316,7 @@ async function upsertCommitDetailResult(deps, req, result, metadata) {
90100
90316
  return;
90101
90317
  }
90102
90318
  const diffStat = extractDiffStatFromShowOutput(result.content);
90103
- const compressed = gzipSync6(Buffer.from(result.content));
90319
+ const compressed = gzipSync5(Buffer.from(result.content));
90104
90320
  const diffContentCompressed = compressed.toString("base64");
90105
90321
  await deps.backend.mutation(api.workspaces.upsertCommitDetailV2, {
90106
90322
  ...baseArgs,
@@ -91224,6 +91440,7 @@ var init_classify_resume_storm_reason = __esm(() => {
91224
91440
  /unauthorized/i,
91225
91441
  /unauthenticated/i,
91226
91442
  /authentication failed/i,
91443
+ /authentication error/i,
91227
91444
  /invalid.{0,24}api.{0,12}key/i,
91228
91445
  /api key.{0,20}(invalid|missing|expired)/i
91229
91446
  ]
@@ -91244,10 +91461,7 @@ var init_classify_resume_storm_reason = __esm(() => {
91244
91461
  ]
91245
91462
  }
91246
91463
  ];
91247
- PERMANENT_FAILURE_REASONS = new Set([
91248
- "auth_error",
91249
- "config_error"
91250
- ]);
91464
+ PERMANENT_FAILURE_REASONS = new Set(["config_error"]);
91251
91465
  });
91252
91466
 
91253
91467
  // src/domain/agent-lifecycle/policies/abort-resume-storm.ts
@@ -94441,17 +94655,17 @@ var init_jsonc = __esm(() => {
94441
94655
  });
94442
94656
 
94443
94657
  // src/infrastructure/services/workspace/workspace-resolver.ts
94444
- import { readFile as readFile8, readdir, stat as stat3 } from "node:fs/promises";
94445
- import { join as join18, basename as basename2, resolve as resolve5 } from "node:path";
94658
+ import { readFile as readFile10, readdir, stat as stat3 } from "node:fs/promises";
94659
+ import { join as join19, basename as basename2, resolve as resolve5 } from "node:path";
94446
94660
  async function resolveGlobPatternStar(rootDir, cleaned) {
94447
- const parentDir = join18(rootDir, cleaned.slice(0, -2));
94661
+ const parentDir = join19(rootDir, cleaned.slice(0, -2));
94448
94662
  try {
94449
94663
  const entries2 = await readdir(parentDir, { withFileTypes: true });
94450
94664
  const dirs = [];
94451
94665
  for (const entry of entries2) {
94452
94666
  if (!entry.isDirectory())
94453
94667
  continue;
94454
- const dirPath = join18(parentDir, entry.name);
94668
+ const dirPath = join19(parentDir, entry.name);
94455
94669
  if (resolve5(dirPath).startsWith(resolve5(rootDir))) {
94456
94670
  dirs.push(dirPath);
94457
94671
  }
@@ -94462,7 +94676,7 @@ async function resolveGlobPatternStar(rootDir, cleaned) {
94462
94676
  }
94463
94677
  }
94464
94678
  async function resolveLiteralPath(rootDir, cleaned) {
94465
- const dir = join18(rootDir, cleaned);
94679
+ const dir = join19(rootDir, cleaned);
94466
94680
  try {
94467
94681
  if (!resolve5(dir).startsWith(resolve5(rootDir)))
94468
94682
  return [];
@@ -94482,7 +94696,7 @@ async function resolveGlobPattern(rootDir, pattern2) {
94482
94696
  }
94483
94697
  async function readPackageJson(dir) {
94484
94698
  try {
94485
- const content = await readFile8(join18(dir, "package.json"), "utf-8");
94699
+ const content = await readFile10(join19(dir, "package.json"), "utf-8");
94486
94700
  const pkg = JSON.parse(content);
94487
94701
  return {
94488
94702
  name: pkg.name || basename2(dir),
@@ -94494,7 +94708,7 @@ async function readPackageJson(dir) {
94494
94708
  }
94495
94709
  async function readPnpmWorkspacePatterns(rootDir) {
94496
94710
  try {
94497
- const content = await readFile8(join18(rootDir, "pnpm-workspace.yaml"), "utf-8");
94711
+ const content = await readFile10(join19(rootDir, "pnpm-workspace.yaml"), "utf-8");
94498
94712
  const patterns = [];
94499
94713
  let inPackages = false;
94500
94714
  for (const line of content.split(`
@@ -94521,7 +94735,7 @@ async function readPnpmWorkspacePatterns(rootDir) {
94521
94735
  }
94522
94736
  async function readPackageJsonWorkspacePatterns(rootDir) {
94523
94737
  try {
94524
- const content = await readFile8(join18(rootDir, "package.json"), "utf-8");
94738
+ const content = await readFile10(join19(rootDir, "package.json"), "utf-8");
94525
94739
  const pkg = JSON.parse(content);
94526
94740
  if (!pkg.workspaces)
94527
94741
  return [];
@@ -94569,12 +94783,12 @@ async function resolveSubWorkspaces(rootDir, pm) {
94569
94783
  var init_workspace_resolver = () => {};
94570
94784
 
94571
94785
  // src/infrastructure/services/workspace/command-discovery.ts
94572
- import { access as access5, readFile as readFile9 } from "node:fs/promises";
94573
- import { join as join19, relative as relative2, basename as basename3 } from "node:path";
94786
+ import { access as access5, readFile as readFile11 } from "node:fs/promises";
94787
+ import { join as join20, relative as relative2, basename as basename3 } from "node:path";
94574
94788
  async function detectPackageManager(workingDir) {
94575
94789
  for (const { file, manager } of LOCKFILE_MAP) {
94576
94790
  try {
94577
- await access5(join19(workingDir, file));
94791
+ await access5(join20(workingDir, file));
94578
94792
  return manager;
94579
94793
  } catch {}
94580
94794
  }
@@ -94618,7 +94832,7 @@ function isValidScriptEntry(name, script) {
94618
94832
  }
94619
94833
  async function readJsonFile(filePath, label) {
94620
94834
  try {
94621
- const content = await readFile9(filePath, "utf-8");
94835
+ const content = await readFile11(filePath, "utf-8");
94622
94836
  return parseJsonc2(content);
94623
94837
  } catch (error) {
94624
94838
  if (error.code !== "ENOENT") {
@@ -94643,7 +94857,7 @@ function collectRootScriptCommands(scripts, pm, scriptPrefix, rootSw) {
94643
94857
  }
94644
94858
  async function readRootPackageJson(workingDir, pm, scriptPrefix) {
94645
94859
  let rootPackageName = basename3(workingDir);
94646
- const pkg = await readJsonFile(join19(workingDir, "package.json"), "root package.json");
94860
+ const pkg = await readJsonFile(join20(workingDir, "package.json"), "root package.json");
94647
94861
  if (!pkg)
94648
94862
  return { commands: [], rootPackageName };
94649
94863
  if (pkg.name)
@@ -94659,7 +94873,7 @@ async function readRootPackageJson(workingDir, pm, scriptPrefix) {
94659
94873
  }
94660
94874
  async function readTurboJson(workingDir, _turboPrefix, _rootSubWorkspace) {
94661
94875
  const turboTaskNames = [];
94662
- const turbo = await readJsonFile(join19(workingDir, "turbo.json"), "turbo.json");
94876
+ const turbo = await readJsonFile(join20(workingDir, "turbo.json"), "turbo.json");
94663
94877
  if (!turbo?.tasks || typeof turbo.tasks !== "object")
94664
94878
  return turboTaskNames;
94665
94879
  for (const taskName of Object.keys(turbo.tasks)) {
@@ -94728,14 +94942,14 @@ var init_command_discovery = __esm(() => {
94728
94942
  });
94729
94943
 
94730
94944
  // src/commands/machine/daemon-start/command-sync-heartbeat.ts
94731
- import { createHash as createHash7 } from "node:crypto";
94945
+ import { createHash as createHash8 } from "node:crypto";
94732
94946
  var pushCommandsEffect, pushSingleWorkspaceCommandsEffect = (workingDir) => exports_Effect.gen(function* () {
94733
94947
  const session2 = yield* DaemonSessionService;
94734
94948
  const mutable2 = yield* DaemonMutableStateService;
94735
94949
  const lastPushedGitState = yield* exports_Ref.get(mutable2.lastPushedGitState);
94736
94950
  const commands = yield* exports_Effect.promise(() => discoverCommands(workingDir));
94737
94951
  const stateKey = `commands:${session2.machineId}::${workingDir}`;
94738
- const commandsHash = createHash7("md5").update(JSON.stringify(commands)).digest("hex");
94952
+ const commandsHash = createHash8("md5").update(JSON.stringify(commands)).digest("hex");
94739
94953
  if (lastPushedGitState.get(stateKey) === commandsHash) {
94740
94954
  return;
94741
94955
  }
@@ -95166,10 +95380,10 @@ function assignProp(target, prop, value) {
95166
95380
  configurable: true
95167
95381
  });
95168
95382
  }
95169
- function getElementAtPath(obj, path6) {
95170
- if (!path6)
95383
+ function getElementAtPath(obj, path5) {
95384
+ if (!path5)
95171
95385
  return obj;
95172
- return path6.reduce((acc, key) => acc?.[key], obj);
95386
+ return path5.reduce((acc, key) => acc?.[key], obj);
95173
95387
  }
95174
95388
  function promiseAllObject(promisesObj) {
95175
95389
  const keys5 = Object.keys(promisesObj);
@@ -95415,11 +95629,11 @@ function aborted(x, startIndex = 0) {
95415
95629
  }
95416
95630
  return false;
95417
95631
  }
95418
- function prefixIssues(path6, issues) {
95632
+ function prefixIssues(path5, issues) {
95419
95633
  return issues.map((iss) => {
95420
95634
  var _a2;
95421
95635
  (_a2 = iss).path ?? (_a2.path = []);
95422
- iss.path.unshift(path6);
95636
+ iss.path.unshift(path5);
95423
95637
  return iss;
95424
95638
  });
95425
95639
  }
@@ -95604,7 +95818,7 @@ function treeifyError(error, _mapper) {
95604
95818
  return issue2.message;
95605
95819
  };
95606
95820
  const result = { errors: [] };
95607
- const processError = (error2, path6 = []) => {
95821
+ const processError = (error2, path5 = []) => {
95608
95822
  var _a2, _b2;
95609
95823
  for (const issue2 of error2.issues) {
95610
95824
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -95614,7 +95828,7 @@ function treeifyError(error, _mapper) {
95614
95828
  } else if (issue2.code === "invalid_element") {
95615
95829
  processError({ issues: issue2.issues }, issue2.path);
95616
95830
  } else {
95617
- const fullpath = [...path6, ...issue2.path];
95831
+ const fullpath = [...path5, ...issue2.path];
95618
95832
  if (fullpath.length === 0) {
95619
95833
  result.errors.push(mapper(issue2));
95620
95834
  continue;
@@ -95644,9 +95858,9 @@ function treeifyError(error, _mapper) {
95644
95858
  processError(error);
95645
95859
  return result;
95646
95860
  }
95647
- function toDotPath(path6) {
95861
+ function toDotPath(path5) {
95648
95862
  const segs = [];
95649
- for (const seg of path6) {
95863
+ for (const seg of path5) {
95650
95864
  if (typeof seg === "number")
95651
95865
  segs.push(`[${seg}]`);
95652
95866
  else if (typeof seg === "symbol")
@@ -107547,8 +107761,6 @@ var init_command_loop = __esm(() => {
107547
107761
  init_featureFlags();
107548
107762
  init_reliability();
107549
107763
  init_esm();
107550
- init_dir_listing_subscription();
107551
- init_dir_listing_watch_subscription();
107552
107764
  init_api3();
107553
107765
  init_on_request_start_agent();
107554
107766
  init_on_request_stop_agent();
@@ -107561,6 +107773,7 @@ var init_command_loop = __esm(() => {
107561
107773
  init_daemon_services();
107562
107774
  init_start_subscriptions();
107563
107775
  init_file_content_subscription();
107776
+ init_file_tree_subscription();
107564
107777
  init_file_write_subscription();
107565
107778
  init_git_heartbeat();
107566
107779
  init_git_subscription();
@@ -107604,8 +107817,7 @@ var init_command_loop = __esm(() => {
107604
107817
  let gitSubscriptionHandle = null;
107605
107818
  let fileContentSubscriptionHandle = null;
107606
107819
  let fileWriteSubscriptionHandle = null;
107607
- let dirListingSubscriptionHandle = null;
107608
- let dirListingWatchSubscriptionHandle = null;
107820
+ let fileTreeSubscriptionHandle = null;
107609
107821
  let workspaceListSubscriptionHandle = null;
107610
107822
  let observedSyncSubscriptionHandle = null;
107611
107823
  let logObserverSubscriptionHandle = null;
@@ -107670,8 +107882,7 @@ var init_command_loop = __esm(() => {
107670
107882
  gitSubscriptionHandle?.stop();
107671
107883
  fileContentSubscriptionHandle?.stop();
107672
107884
  fileWriteSubscriptionHandle?.stop();
107673
- dirListingSubscriptionHandle?.stop();
107674
- dirListingWatchSubscriptionHandle?.stop();
107885
+ fileTreeSubscriptionHandle?.stop();
107675
107886
  workspaceListSubscriptionHandle?.stop();
107676
107887
  observedSyncSubscriptionHandle?.stop();
107677
107888
  taskMonitorHandle?.stop();
@@ -107716,8 +107927,7 @@ var init_command_loop = __esm(() => {
107716
107927
  gitSubscriptionHandle = yield* startGitRequestSubscriptionEffect(wsClient2);
107717
107928
  fileContentSubscriptionHandle = yield* startFileContentSubscriptionEffect(wsClient2);
107718
107929
  fileWriteSubscriptionHandle = yield* startFileWriteSubscriptionEffect(wsClient2);
107719
- dirListingSubscriptionHandle = yield* startDirListingSubscriptionEffect(wsClient2);
107720
- dirListingWatchSubscriptionHandle = yield* startDirListingWatchSubscriptionEffect(wsClient2);
107930
+ fileTreeSubscriptionHandle = yield* startFileTreeSubscriptionEffect(wsClient2);
107721
107931
  workspaceListSubscriptionHandle = yield* startWorkspaceListSubscriptionEffect(wsClient2);
107722
107932
  observedSyncSubscriptionHandle = yield* startObservedSyncSubscriptionEffect(wsClient2);
107723
107933
  const taskMonitorHandle = yield* startTaskMonitorEffect(wsClient2);
@@ -107918,7 +108128,7 @@ __export(exports_opencode_install, {
107918
108128
  installTool: () => installTool
107919
108129
  });
107920
108130
  import * as os2 from "os";
107921
- import * as path6 from "path";
108131
+ import * as path5 from "path";
107922
108132
  async function isChatroomInstalledDefault() {
107923
108133
  try {
107924
108134
  const { execSync: execSync3 } = await import("child_process");
@@ -107930,7 +108140,7 @@ async function isChatroomInstalledDefault() {
107930
108140
  }
107931
108141
  async function createDefaultDeps18() {
107932
108142
  const client4 = await getConvexClient();
107933
- const fs11 = await import("fs/promises");
108143
+ const fs12 = await import("fs/promises");
107934
108144
  return {
107935
108145
  backend: {
107936
108146
  mutation: (endpoint, args2) => client4.mutation(endpoint, args2),
@@ -107943,13 +108153,13 @@ async function createDefaultDeps18() {
107943
108153
  },
107944
108154
  fs: {
107945
108155
  access: async (p) => {
107946
- await fs11.access(p);
108156
+ await fs12.access(p);
107947
108157
  },
107948
108158
  mkdir: async (p, options) => {
107949
- await fs11.mkdir(p, options);
108159
+ await fs12.mkdir(p, options);
107950
108160
  },
107951
108161
  writeFile: async (p, content, encoding) => {
107952
- await fs11.writeFile(p, content, encoding);
108162
+ await fs12.writeFile(p, content, encoding);
107953
108163
  }
107954
108164
  },
107955
108165
  isChatroomInstalled: isChatroomInstalledDefault
@@ -108288,9 +108498,9 @@ After logging in, try this command again.\`;
108288
108498
  const fsService = yield* OpenCodeInstallFsService;
108289
108499
  const { checkExisting = true } = options;
108290
108500
  const homeDir = os2.homedir();
108291
- const toolDir = path6.join(homeDir, ".config", "opencode", "tool");
108292
- const toolPath = path6.join(toolDir, "chatroom.ts");
108293
- const handoffToolPath = path6.join(toolDir, "chatroom-handoff.ts");
108501
+ const toolDir = path5.join(homeDir, ".config", "opencode", "tool");
108502
+ const toolPath = path5.join(toolDir, "chatroom.ts");
108503
+ const handoffToolPath = path5.join(toolDir, "chatroom-handoff.ts");
108294
108504
  if (checkExisting) {
108295
108505
  const existingFiles = [];
108296
108506
  const toolExists = yield* fsService.access(toolPath);
@@ -108845,4 +109055,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
108845
109055
  });
108846
109056
  program2.parse();
108847
109057
 
108848
- //# debugId=44E20CE9388FFEA564756E2164756E21
109058
+ //# debugId=0F49F7F33D425EFB64756E2164756E21