chatroom-cli 1.59.0 → 1.60.0

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
@@ -79917,6 +79917,765 @@ var init_daemon_services = __esm(() => {
79917
79917
  };
79918
79918
  });
79919
79919
 
79920
+ // src/commands/machine/daemon-start/utils.ts
79921
+ function formatTimestamp() {
79922
+ return new Date().toISOString().replace("T", " ").substring(0, 19);
79923
+ }
79924
+
79925
+ // src/infrastructure/services/workspace/dir-listing-content-hash.ts
79926
+ import { createHash as createHash2 } from "node:crypto";
79927
+ function computeDirListingContentHash(listing) {
79928
+ const payload = {
79929
+ entries: listing.entries,
79930
+ truncated: listing.truncated,
79931
+ totalCount: listing.totalCount
79932
+ };
79933
+ return createHash2("md5").update(JSON.stringify(payload)).digest("hex");
79934
+ }
79935
+ var init_dir_listing_content_hash = () => {};
79936
+
79937
+ // src/infrastructure/services/workspace/workspace-path-security.ts
79938
+ import { realpath } from "node:fs/promises";
79939
+ import { basename, dirname as dirname8, isAbsolute, relative, resolve as resolve3, sep } from "node:path";
79940
+ import { gunzipSync } from "node:zlib";
79941
+ function validateRelativePathSegments(filePath) {
79942
+ if (filePath.includes("\x00"))
79943
+ return { ok: false, error: "Invalid file path" };
79944
+ if (filePath.includes(".."))
79945
+ return { ok: false, error: "Invalid file path" };
79946
+ if (filePath.startsWith("/"))
79947
+ return { ok: false, error: "Invalid file path" };
79948
+ return { ok: true };
79949
+ }
79950
+ function isPathInsideRoot(workspaceRoot, targetPath) {
79951
+ const rel = relative(workspaceRoot, targetPath);
79952
+ if (rel === "")
79953
+ return true;
79954
+ if (rel.startsWith("..") || rel.includes(`..${sep}`))
79955
+ return false;
79956
+ if (isAbsolute(rel))
79957
+ return false;
79958
+ return true;
79959
+ }
79960
+ async function resolvePathWithinWorkspace(workingDir, filePath) {
79961
+ const basic = validateRelativePathSegments(filePath);
79962
+ if (!basic.ok)
79963
+ return basic;
79964
+ let workspaceRoot;
79965
+ try {
79966
+ workspaceRoot = await realpath(resolve3(workingDir));
79967
+ } catch {
79968
+ return { ok: false, error: "Working directory not found" };
79969
+ }
79970
+ const candidate = resolve3(workspaceRoot, filePath);
79971
+ if (!isPathInsideRoot(workspaceRoot, candidate)) {
79972
+ return { ok: false, error: "Path escapes workspace" };
79973
+ }
79974
+ try {
79975
+ const resolved = await realpath(candidate);
79976
+ if (!isPathInsideRoot(workspaceRoot, resolved)) {
79977
+ return { ok: false, error: "Path escapes workspace" };
79978
+ }
79979
+ return { ok: true, absolutePath: resolved };
79980
+ } catch (err) {
79981
+ const code2 = err?.code;
79982
+ if (code2 !== "ENOENT") {
79983
+ return { ok: false, error: "Invalid file path" };
79984
+ }
79985
+ const parent = dirname8(candidate);
79986
+ if (parent === workspaceRoot || isPathInsideRoot(workspaceRoot, parent)) {
79987
+ try {
79988
+ const parentReal = await realpath(parent);
79989
+ if (!isPathInsideRoot(workspaceRoot, parentReal)) {
79990
+ return { ok: false, error: "Path escapes workspace" };
79991
+ }
79992
+ const resolved = resolve3(parentReal, basename(candidate));
79993
+ if (!isPathInsideRoot(workspaceRoot, resolved)) {
79994
+ return { ok: false, error: "Path escapes workspace" };
79995
+ }
79996
+ return { ok: true, absolutePath: resolved };
79997
+ } catch {
79998
+ return { ok: true, absolutePath: candidate };
79999
+ }
80000
+ }
80001
+ return { ok: false, error: "Path escapes workspace" };
80002
+ }
80003
+ }
80004
+ function gunzipBase64Payload(base64, maxBytes) {
80005
+ let compressed;
80006
+ try {
80007
+ compressed = Buffer.from(base64, "base64");
80008
+ } catch {
80009
+ return { ok: false, errorMessage: "Missing file data" };
80010
+ }
80011
+ if (compressed.length > maxBytes * 10) {
80012
+ return { ok: false, errorMessage: "File content too large" };
80013
+ }
80014
+ try {
80015
+ const content = gunzipSync(compressed, { maxOutputLength: maxBytes });
80016
+ if (content.length > maxBytes) {
80017
+ return { ok: false, errorMessage: "File content too large" };
80018
+ }
80019
+ return { ok: true, content };
80020
+ } catch {
80021
+ return { ok: false, errorMessage: "File content too large" };
80022
+ }
80023
+ }
80024
+ var init_workspace_path_security = () => {};
80025
+
80026
+ // src/infrastructure/services/workspace/workspace-visibility-policy.ts
80027
+ import { exec as exec2, spawn as spawn3 } from "node:child_process";
80028
+ import { promisify as promisify2 } from "node:util";
80029
+ function isAlwaysExcludedDirName(name) {
80030
+ return ALWAYS_EXCLUDE_DIR_NAMES.has(name);
80031
+ }
80032
+ function isSecretPath(relativePath) {
80033
+ const normalized = relativePath.replace(/\\/g, "/");
80034
+ return SECRET_PATH_PATTERNS.some((pattern) => pattern.test(normalized));
80035
+ }
80036
+ function hasExcludedDirSegment(relativePath) {
80037
+ const segments = relativePath.split("/");
80038
+ return segments.some((segment) => isAlwaysExcludedDirName(segment));
80039
+ }
80040
+ function isPathVisible(relativePath) {
80041
+ if (!relativePath)
80042
+ return true;
80043
+ return !isSecretPath(relativePath) && !hasExcludedDirSegment(relativePath);
80044
+ }
80045
+ function isPathContentReadable(relativePath) {
80046
+ return isPathVisible(relativePath);
80047
+ }
80048
+ async function isGitRepo(rootDir) {
80049
+ try {
80050
+ const { stdout } = await execAsync2("git rev-parse --is-inside-work-tree", {
80051
+ cwd: rootDir,
80052
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_PAGER: "cat", NO_COLOR: "1" },
80053
+ maxBuffer: 1024 * 1024
80054
+ });
80055
+ return stdout.trim() === "true";
80056
+ } catch {
80057
+ return false;
80058
+ }
80059
+ }
80060
+ async function filterGitIgnored(rootDir, relativePaths) {
80061
+ if (relativePaths.length === 0)
80062
+ return new Set;
80063
+ const inRepo = await isGitRepo(rootDir);
80064
+ if (!inRepo)
80065
+ return new Set;
80066
+ try {
80067
+ const stdout = await new Promise((resolve4, reject) => {
80068
+ const child = spawn3("git", ["check-ignore", "--stdin", "-z"], {
80069
+ cwd: rootDir,
80070
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_PAGER: "cat", NO_COLOR: "1" }
80071
+ });
80072
+ let output = "";
80073
+ child.stdout.on("data", (chunk2) => {
80074
+ output += chunk2.toString();
80075
+ });
80076
+ child.on("error", reject);
80077
+ child.on("close", () => resolve4(output));
80078
+ child.stdin.write(relativePaths.join(`
80079
+ `));
80080
+ child.stdin.end();
80081
+ });
80082
+ const ignored = new Set;
80083
+ if (!stdout)
80084
+ return ignored;
80085
+ for (const entry of stdout.split("\x00")) {
80086
+ const trimmed = entry.trim();
80087
+ if (trimmed)
80088
+ ignored.add(trimmed);
80089
+ }
80090
+ return ignored;
80091
+ } catch {
80092
+ return new Set;
80093
+ }
80094
+ }
80095
+ var execAsync2, ALWAYS_EXCLUDE_DIR_NAMES, SECRET_PATH_PATTERNS;
80096
+ var init_workspace_visibility_policy = __esm(() => {
80097
+ execAsync2 = promisify2(exec2);
80098
+ ALWAYS_EXCLUDE_DIR_NAMES = new Set([
80099
+ "node_modules",
80100
+ ".git",
80101
+ "dist",
80102
+ "build",
80103
+ ".next",
80104
+ "coverage",
80105
+ "__pycache__",
80106
+ ".turbo",
80107
+ ".cache",
80108
+ ".tmp",
80109
+ "tmp",
80110
+ "_generated",
80111
+ ".vercel"
80112
+ ]);
80113
+ SECRET_PATH_PATTERNS = [
80114
+ /^\.env$/,
80115
+ /^\.env\./,
80116
+ /\.pem$/,
80117
+ /\.key$/,
80118
+ /id_rsa$/,
80119
+ /credentials\.json$/,
80120
+ /^secrets(\/|$)/,
80121
+ /^\.aws(\/|$)/
80122
+ ];
80123
+ });
80124
+
80125
+ // src/infrastructure/services/workspace/dir-listing-scanner.ts
80126
+ import { promises as fsPromises } from "node:fs";
80127
+ import path3 from "node:path";
80128
+ async function listDirectory(rootDir, dirPath, options) {
80129
+ const maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
80130
+ const scannedAt = Date.now();
80131
+ const absDir = dirPath ? path3.join(rootDir, dirPath) : rootDir;
80132
+ const resolvedRoot = path3.resolve(rootDir);
80133
+ const resolvedDir = path3.resolve(absDir);
80134
+ if (!isPathInsideRoot(resolvedRoot, resolvedDir)) {
80135
+ return { dirPath, entries: [], scannedAt, truncated: false, totalCount: 0 };
80136
+ }
80137
+ let dirents;
80138
+ try {
80139
+ dirents = await fsPromises.readdir(absDir, { withFileTypes: true });
80140
+ } catch {
80141
+ return { dirPath, entries: [], scannedAt, truncated: false, totalCount: 0 };
80142
+ }
80143
+ const candidates = [];
80144
+ for (const ent of dirents) {
80145
+ if (isAlwaysExcludedDirName(ent.name))
80146
+ continue;
80147
+ const relativePath = dirPath ? `${dirPath}/${ent.name}` : ent.name;
80148
+ if (!isPathVisible(relativePath))
80149
+ continue;
80150
+ if (ent.isDirectory()) {
80151
+ candidates.push({ name: ent.name, path: relativePath, type: "directory" });
80152
+ } else if (ent.isFile()) {
80153
+ let size9;
80154
+ try {
80155
+ const st = await fsPromises.stat(path3.join(absDir, ent.name));
80156
+ size9 = st.size;
80157
+ } catch {}
80158
+ candidates.push({ name: ent.name, path: relativePath, type: "file", size: size9 });
80159
+ }
80160
+ }
80161
+ const ignored = await filterGitIgnored(rootDir, candidates.map((c) => c.path));
80162
+ const visible = candidates.filter((c) => !ignored.has(c.path));
80163
+ visible.sort((a, b) => {
80164
+ if (a.type === "directory" && b.type === "file")
80165
+ return -1;
80166
+ if (a.type === "file" && b.type === "directory")
80167
+ return 1;
80168
+ return a.name.localeCompare(b.name, undefined, { sensitivity: "base" });
80169
+ });
80170
+ const totalCount = visible.length;
80171
+ const truncated = totalCount > maxEntries;
80172
+ const entries2 = visible.slice(0, maxEntries);
80173
+ return { dirPath, entries: entries2, scannedAt, truncated, totalCount };
80174
+ }
80175
+ var DEFAULT_MAX_ENTRIES = 500;
80176
+ var init_dir_listing_scanner = __esm(() => {
80177
+ init_workspace_path_security();
80178
+ init_workspace_visibility_policy();
80179
+ });
80180
+
80181
+ // src/infrastructure/services/workspace/dir-listing-sync.ts
80182
+ import { gzipSync } from "node:zlib";
80183
+ function listingCacheKey(workingDir, dirPath) {
80184
+ return `${workingDir}\x00${dirPath}`;
80185
+ }
80186
+ async function scanDirListingForSync(workingDir, dirPath) {
80187
+ const listing = await listDirectory(workingDir, dirPath);
80188
+ const dataHash = computeDirListingContentHash(listing);
80189
+ const cacheKey = listingCacheKey(workingDir, dirPath);
80190
+ if (lastSyncedContentHash.get(cacheKey) === dataHash)
80191
+ return null;
80192
+ const json = JSON.stringify(listing);
80193
+ return {
80194
+ dirPath,
80195
+ data: { compression: "gzip", content: gzipSync(Buffer.from(json)).toString("base64") },
80196
+ dataHash,
80197
+ scannedAt: listing.scannedAt,
80198
+ truncated: listing.truncated,
80199
+ totalCount: listing.totalCount
80200
+ };
80201
+ }
80202
+ async function scanDirListingsWithConcurrency(workingDir, dirPaths) {
80203
+ const items = [];
80204
+ let index = 0;
80205
+ async function worker() {
80206
+ while (index < dirPaths.length) {
80207
+ const currentIndex = index;
80208
+ index += 1;
80209
+ const dirPath = dirPaths[currentIndex];
80210
+ if (dirPath === undefined)
80211
+ continue;
80212
+ const item = await scanDirListingForSync(workingDir, dirPath);
80213
+ if (item)
80214
+ items.push(item);
80215
+ }
80216
+ }
80217
+ const workerCount = Math.min(SCAN_CONCURRENCY, dirPaths.length);
80218
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
80219
+ return items;
80220
+ }
80221
+ function markItemsSynced(workingDir, items) {
80222
+ for (const item of items) {
80223
+ lastSyncedContentHash.set(listingCacheKey(workingDir, item.dirPath), item.dataHash);
80224
+ }
80225
+ }
80226
+ async function syncDirListingToBackend(session2, workingDir, dirPath) {
80227
+ const item = await scanDirListingForSync(workingDir, dirPath);
80228
+ if (!item)
80229
+ return;
80230
+ await session2.backend.mutation(api.workspaceFiles.syncDirListingV2, {
80231
+ sessionId: session2.sessionId,
80232
+ machineId: session2.machineId,
80233
+ workingDir,
80234
+ dirPath: item.dirPath,
80235
+ data: item.data,
80236
+ dataHash: item.dataHash,
80237
+ scannedAt: item.scannedAt,
80238
+ truncated: item.truncated,
80239
+ totalCount: item.totalCount
80240
+ });
80241
+ markItemsSynced(workingDir, [item]);
80242
+ }
80243
+ async function syncDirListingsToBackend(session2, workingDir, dirPaths) {
80244
+ const unique = [...new Set(dirPaths)];
80245
+ if (unique.length === 0)
80246
+ return;
80247
+ if (unique.length === 1) {
80248
+ const dirPath = unique[0];
80249
+ if (dirPath !== undefined) {
80250
+ await syncDirListingToBackend(session2, workingDir, dirPath);
80251
+ }
80252
+ return;
80253
+ }
80254
+ const items = await scanDirListingsWithConcurrency(workingDir, unique);
80255
+ if (items.length === 0)
80256
+ return;
80257
+ for (let i2 = 0;i2 < items.length; i2 += MAX_BATCH_SIZE) {
80258
+ const chunk2 = items.slice(i2, i2 + MAX_BATCH_SIZE);
80259
+ await session2.backend.mutation(api.workspaceFiles.syncDirListingV2Batch, {
80260
+ sessionId: session2.sessionId,
80261
+ machineId: session2.machineId,
80262
+ workingDir,
80263
+ items: chunk2
80264
+ });
80265
+ markItemsSynced(workingDir, chunk2);
80266
+ }
80267
+ }
80268
+ var MAX_BATCH_SIZE = 25, SCAN_CONCURRENCY = 4, lastSyncedContentHash;
80269
+ var init_dir_listing_sync = __esm(() => {
80270
+ init_dir_listing_content_hash();
80271
+ init_dir_listing_scanner();
80272
+ init_api3();
80273
+ lastSyncedContentHash = new Map;
80274
+ });
80275
+
80276
+ // src/infrastructure/services/workspace/workspace-file-search.ts
80277
+ import { promises as fsPromises2 } from "node:fs";
80278
+ import path4 from "node:path";
80279
+ async function searchWorkspaceFiles(rootDir, query, options) {
80280
+ const maxResults = options?.maxResults ?? DEFAULT_MAX_RESULTS;
80281
+ const scannedAt = Date.now();
80282
+ const normalizedQuery = query.trim().toLowerCase();
80283
+ const matches = [];
80284
+ let visitedDirs = 0;
80285
+ let truncated = false;
80286
+ async function visitDir(relDir) {
80287
+ if (visitedDirs >= MAX_VISIT_DIRS || matches.length >= maxResults) {
80288
+ truncated = true;
80289
+ return;
80290
+ }
80291
+ visitedDirs++;
80292
+ const absDir = relDir ? path4.join(rootDir, relDir) : rootDir;
80293
+ let dirents;
80294
+ try {
80295
+ dirents = await fsPromises2.readdir(absDir, { withFileTypes: true });
80296
+ } catch {
80297
+ return;
80298
+ }
80299
+ const fileCandidates = [];
80300
+ const subdirs = [];
80301
+ for (const ent of dirents) {
80302
+ if (isAlwaysExcludedDirName(ent.name))
80303
+ continue;
80304
+ const relativePath = relDir ? `${relDir}/${ent.name}` : ent.name;
80305
+ if (!isPathVisible(relativePath))
80306
+ continue;
80307
+ if (ent.isDirectory()) {
80308
+ subdirs.push(relativePath);
80309
+ } else if (ent.isFile()) {
80310
+ fileCandidates.push(relativePath);
80311
+ }
80312
+ }
80313
+ const ignored = await filterGitIgnored(rootDir, fileCandidates);
80314
+ for (const filePath of fileCandidates) {
80315
+ if (ignored.has(filePath))
80316
+ continue;
80317
+ const fileName = path4.basename(filePath).toLowerCase();
80318
+ if (normalizedQuery === "" || fileName.includes(normalizedQuery)) {
80319
+ matches.push({ path: filePath, type: "file" });
80320
+ if (matches.length >= maxResults) {
80321
+ truncated = true;
80322
+ return;
80323
+ }
80324
+ }
80325
+ }
80326
+ for (const sub of subdirs) {
80327
+ if (matches.length >= maxResults || visitedDirs >= MAX_VISIT_DIRS) {
80328
+ truncated = true;
80329
+ return;
80330
+ }
80331
+ await visitDir(sub);
80332
+ }
80333
+ }
80334
+ await visitDir("");
80335
+ return {
80336
+ query,
80337
+ entries: matches,
80338
+ scannedAt,
80339
+ truncated,
80340
+ totalCount: matches.length
80341
+ };
80342
+ }
80343
+ var DEFAULT_MAX_RESULTS = 300, MAX_VISIT_DIRS = 2000;
80344
+ var init_workspace_file_search = __esm(() => {
80345
+ init_workspace_visibility_policy();
80346
+ });
80347
+
80348
+ // src/commands/machine/daemon-start/dir-listing-subscription.ts
80349
+ import { createHash as createHash3 } from "node:crypto";
80350
+ import { gzipSync as gzipSync2 } from "node:zlib";
80351
+ function logSubscriptionWarn(label, err) {
80352
+ console.warn(`[${formatTimestamp()}] ⚠️ ${label}: ${getErrorMessage(err)}`);
80353
+ }
80354
+ async function uploadDirListing(session2, workingDir, dirPath) {
80355
+ await syncDirListingToBackend(session2, workingDir, dirPath);
80356
+ await session2.backend.mutation(api.workspaceFiles.fulfillDirListingRequest, {
80357
+ sessionId: session2.sessionId,
80358
+ machineId: session2.machineId,
80359
+ workingDir,
80360
+ dirPath
80361
+ });
80362
+ }
80363
+ async function uploadFileSearch(session2, workingDir, query) {
80364
+ const result = await searchWorkspaceFiles(workingDir, query);
80365
+ const json = JSON.stringify(result);
80366
+ const dataHash = createHash3("md5").update(json).digest("hex");
80367
+ const compressed = gzipSync2(Buffer.from(json)).toString("base64");
80368
+ await session2.backend.mutation(api.workspaceFiles.syncFileSearchV2, {
80369
+ sessionId: session2.sessionId,
80370
+ machineId: session2.machineId,
80371
+ workingDir,
80372
+ query,
80373
+ data: { compression: "gzip", content: compressed },
80374
+ dataHash,
80375
+ scannedAt: result.scannedAt,
80376
+ truncated: result.truncated,
80377
+ totalCount: result.totalCount
80378
+ });
80379
+ await session2.backend.mutation(api.workspaceFiles.fulfillFileSearchRequest, {
80380
+ sessionId: session2.sessionId,
80381
+ machineId: session2.machineId,
80382
+ workingDir,
80383
+ query
80384
+ });
80385
+ }
80386
+ var fulfillDirListingRequestsEffect = (session2, requests) => exports_Effect.gen(function* () {
80387
+ for (const request2 of requests) {
80388
+ yield* exports_Effect.catchAll(exports_Effect.gen(function* () {
80389
+ const start3 = Date.now();
80390
+ yield* exports_Effect.tryPromise(() => uploadDirListing(session2, request2.workingDir, request2.dirPath));
80391
+ console.log(`[${formatTimestamp()}] \uD83D\uDCC2 Dir listing fulfilled: ${request2.workingDir}/${request2.dirPath || "(root)"} (${Date.now() - start3}ms)`);
80392
+ }), (err) => {
80393
+ logSubscriptionWarn(`Dir listing failed for ${request2.workingDir}/${request2.dirPath}`, err);
80394
+ return exports_Effect.void;
80395
+ });
80396
+ }
80397
+ }), fulfillFileSearchRequestsEffect = (session2, requests) => exports_Effect.gen(function* () {
80398
+ for (const request2 of requests) {
80399
+ yield* exports_Effect.catchAll(exports_Effect.gen(function* () {
80400
+ const start3 = Date.now();
80401
+ yield* exports_Effect.tryPromise(() => uploadFileSearch(session2, request2.workingDir, request2.query));
80402
+ console.log(`[${formatTimestamp()}] \uD83D\uDD0D File search fulfilled: ${request2.workingDir} query="${request2.query}" (${Date.now() - start3}ms)`);
80403
+ }), (err) => {
80404
+ logSubscriptionWarn(`File search failed for ${request2.workingDir}`, err);
80405
+ return exports_Effect.void;
80406
+ });
80407
+ }
80408
+ }), startDirListingSubscriptionEffect = (wsClient2) => exports_Effect.gen(function* () {
80409
+ const session2 = yield* DaemonSessionService;
80410
+ let processingDir = false;
80411
+ let processingSearch = false;
80412
+ const unsubDir = wsClient2.onUpdate(api.workspaceFiles.getPendingDirListingRequests, { sessionId: session2.sessionId, machineId: session2.machineId }, (requests) => {
80413
+ if (!requests?.length || processingDir)
80414
+ return;
80415
+ processingDir = true;
80416
+ exports_Effect.runPromise(fulfillDirListingRequestsEffect(session2, requests)).catch((err) => {
80417
+ logSubscriptionWarn("Dir listing subscription processing failed", err);
80418
+ }).finally(() => {
80419
+ processingDir = false;
80420
+ });
80421
+ }, (err) => {
80422
+ logSubscriptionWarn("Dir listing subscription error", err);
80423
+ });
80424
+ const unsubSearch = wsClient2.onUpdate(api.workspaceFiles.getPendingFileSearchRequests, { sessionId: session2.sessionId, machineId: session2.machineId }, (requests) => {
80425
+ if (!requests?.length || processingSearch)
80426
+ return;
80427
+ processingSearch = true;
80428
+ exports_Effect.runPromise(fulfillFileSearchRequestsEffect(session2, requests)).catch((err) => {
80429
+ logSubscriptionWarn("File search subscription processing failed", err);
80430
+ }).finally(() => {
80431
+ processingSearch = false;
80432
+ });
80433
+ }, (err) => {
80434
+ logSubscriptionWarn("File search subscription error", err);
80435
+ });
80436
+ console.log(`[${formatTimestamp()}] \uD83D\uDCC2 Dir listing subscription started (reactive)`);
80437
+ return {
80438
+ stop: () => {
80439
+ unsubDir();
80440
+ unsubSearch();
80441
+ console.log(`[${formatTimestamp()}] \uD83D\uDCC2 Dir listing subscription stopped`);
80442
+ }
80443
+ };
80444
+ });
80445
+ var init_dir_listing_subscription = __esm(() => {
80446
+ init_esm();
80447
+ init_daemon_services();
80448
+ init_api3();
80449
+ init_dir_listing_sync();
80450
+ init_workspace_file_search();
80451
+ init_convex_error();
80452
+ });
80453
+
80454
+ // src/infrastructure/services/workspace/workspace-fs-watch-paths.ts
80455
+ function parentDirPath(relativePath) {
80456
+ const normalized = relativePath.replace(/\\/g, "/");
80457
+ const idx = normalized.lastIndexOf("/");
80458
+ return idx === -1 ? "" : normalized.slice(0, idx);
80459
+ }
80460
+ function shouldIgnoreWatchRelativePath(relativePath) {
80461
+ const normalized = relativePath.replace(/\\/g, "/").replace(/\/+$/, "");
80462
+ if (!normalized || normalized === ".")
80463
+ return false;
80464
+ return hasExcludedDirSegment(normalized);
80465
+ }
80466
+ function dirsToRefreshForEvent(relativePath, isDirectory = false) {
80467
+ const normalized = relativePath.replace(/\\/g, "/").replace(/\/+$/, "");
80468
+ if (!normalized || normalized === ".")
80469
+ return [""];
80470
+ const dirs = new Set;
80471
+ dirs.add(parentDirPath(normalized));
80472
+ if (isDirectory)
80473
+ dirs.add(normalized);
80474
+ return [...dirs];
80475
+ }
80476
+ function filterDirsByActiveSet(dirs, activeDirPaths) {
80477
+ return dirs.filter((d) => activeDirPaths.has(d));
80478
+ }
80479
+ var init_workspace_fs_watch_paths = __esm(() => {
80480
+ init_workspace_visibility_policy();
80481
+ });
80482
+
80483
+ // src/infrastructure/services/workspace/workspace-fs-watcher.ts
80484
+ import { watch } from "node:fs";
80485
+ import path5 from "node:path";
80486
+ function isRecursiveWatchPlatform() {
80487
+ return process.platform === "darwin" || process.platform === "win32";
80488
+ }
80489
+ function normalizeFilename(filename) {
80490
+ if (filename == null)
80491
+ return null;
80492
+ const value = typeof filename === "string" ? filename : filename.toString();
80493
+ if (!value || value === "." || value === "..")
80494
+ return null;
80495
+ return value.replace(/\\/g, "/");
80496
+ }
80497
+ function createWorkspaceFsWatcher(options) {
80498
+ const absWorkingDir = path5.resolve(options.workingDir);
80499
+ const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
80500
+ let activeDirPaths = new Set(options.activeDirPaths);
80501
+ let stopped = false;
80502
+ let debounceTimer = null;
80503
+ const pendingDirs = new Set;
80504
+ let rootWatcher = null;
80505
+ const dirWatchers = new Map;
80506
+ const scheduleFlush = () => {
80507
+ if (debounceTimer)
80508
+ clearTimeout(debounceTimer);
80509
+ debounceTimer = setTimeout(() => {
80510
+ debounceTimer = null;
80511
+ if (pendingDirs.size === 0)
80512
+ return;
80513
+ const dirs = [...pendingDirs];
80514
+ pendingDirs.clear();
80515
+ Promise.resolve(options.onRefreshDirs(dirs));
80516
+ }, debounceMs);
80517
+ debounceTimer.unref?.();
80518
+ };
80519
+ const enqueueDirs = (dirs) => {
80520
+ for (const dir of dirs)
80521
+ pendingDirs.add(dir);
80522
+ scheduleFlush();
80523
+ };
80524
+ const handleRelativePath = (relativePath, isDirectory = false) => {
80525
+ if (stopped)
80526
+ return;
80527
+ if (shouldIgnoreWatchRelativePath(relativePath))
80528
+ return;
80529
+ const toRefresh = filterDirsByActiveSet(dirsToRefreshForEvent(relativePath, isDirectory), activeDirPaths);
80530
+ if (toRefresh.length > 0)
80531
+ enqueueDirs(toRefresh);
80532
+ };
80533
+ const onWatchEvent = (watchedDirPath, filename) => {
80534
+ const normalizedFilename = normalizeFilename(filename);
80535
+ if (!normalizedFilename)
80536
+ return;
80537
+ let relativePath;
80538
+ if (isRecursiveWatchPlatform()) {
80539
+ relativePath = normalizedFilename;
80540
+ } else if (watchedDirPath === "") {
80541
+ relativePath = normalizedFilename;
80542
+ } else {
80543
+ relativePath = `${watchedDirPath}/${normalizedFilename}`;
80544
+ }
80545
+ const isDirectory = relativePath.endsWith("/");
80546
+ handleRelativePath(relativePath.replace(/\/+$/, ""), isDirectory);
80547
+ };
80548
+ const closePerDirWatchers = () => {
80549
+ for (const watcher of dirWatchers.values())
80550
+ watcher.close();
80551
+ dirWatchers.clear();
80552
+ };
80553
+ const startPerDirWatch = (dirPath) => {
80554
+ if (dirPath === "" || dirWatchers.has(dirPath))
80555
+ return;
80556
+ const absDir = path5.join(absWorkingDir, dirPath);
80557
+ const watcher = watch(absDir, (_eventType, filename) => {
80558
+ onWatchEvent(dirPath, filename);
80559
+ });
80560
+ dirWatchers.set(dirPath, watcher);
80561
+ };
80562
+ const rewirePerDirWatches = () => {
80563
+ const wanted = new Set;
80564
+ for (const dirPath of activeDirPaths) {
80565
+ if (dirPath !== "")
80566
+ wanted.add(dirPath);
80567
+ }
80568
+ for (const [dirPath, watcher] of dirWatchers) {
80569
+ if (!wanted.has(dirPath)) {
80570
+ watcher.close();
80571
+ dirWatchers.delete(dirPath);
80572
+ }
80573
+ }
80574
+ for (const dirPath of wanted) {
80575
+ if (!dirWatchers.has(dirPath))
80576
+ startPerDirWatch(dirPath);
80577
+ }
80578
+ };
80579
+ const startWatching = () => {
80580
+ if (isRecursiveWatchPlatform()) {
80581
+ rootWatcher = watch(absWorkingDir, { recursive: true }, (_eventType, filename) => {
80582
+ onWatchEvent("", filename);
80583
+ });
80584
+ return;
80585
+ }
80586
+ rootWatcher = watch(absWorkingDir, (_eventType, filename) => {
80587
+ onWatchEvent("", filename);
80588
+ });
80589
+ rewirePerDirWatches();
80590
+ };
80591
+ startWatching();
80592
+ return {
80593
+ updateActiveDirPaths: (paths) => {
80594
+ activeDirPaths = new Set(paths);
80595
+ if (!isRecursiveWatchPlatform() && !stopped) {
80596
+ rewirePerDirWatches();
80597
+ }
80598
+ },
80599
+ stop: () => {
80600
+ stopped = true;
80601
+ if (debounceTimer) {
80602
+ clearTimeout(debounceTimer);
80603
+ debounceTimer = null;
80604
+ }
80605
+ rootWatcher?.close();
80606
+ rootWatcher = null;
80607
+ closePerDirWatchers();
80608
+ pendingDirs.clear();
80609
+ }
80610
+ };
80611
+ }
80612
+ var DEFAULT_DEBOUNCE_MS = 400;
80613
+ var init_workspace_fs_watcher = __esm(() => {
80614
+ init_workspace_fs_watch_paths();
80615
+ });
80616
+
80617
+ // src/commands/machine/daemon-start/dir-listing-watch-subscription.ts
80618
+ var startDirListingWatchSubscriptionEffect = (wsClient2) => exports_Effect.gen(function* () {
80619
+ const session2 = yield* DaemonSessionService;
80620
+ const watchers = new Map;
80621
+ const applyTargets = (targets) => {
80622
+ const nextWorkingDirs = new Set(targets.map((t) => t.workingDir));
80623
+ for (const [workingDir, handle] of watchers) {
80624
+ if (!nextWorkingDirs.has(workingDir)) {
80625
+ handle.stop();
80626
+ watchers.delete(workingDir);
80627
+ }
80628
+ }
80629
+ for (const target of targets) {
80630
+ const activeSet = new Set(target.activeDirPaths);
80631
+ const existing = watchers.get(target.workingDir);
80632
+ if (existing) {
80633
+ existing.updateActiveDirPaths(activeSet);
80634
+ continue;
80635
+ }
80636
+ const handle = createWorkspaceFsWatcher({
80637
+ workingDir: target.workingDir,
80638
+ activeDirPaths: activeSet,
80639
+ onRefreshDirs: async (dirPaths) => {
80640
+ try {
80641
+ await syncDirListingsToBackend(session2, target.workingDir, dirPaths);
80642
+ } catch (err) {
80643
+ console.warn(`[${formatTimestamp()}] ⚠️ FS watch sync failed for ${target.workingDir}: ${getErrorMessage(err)}`);
80644
+ }
80645
+ }
80646
+ });
80647
+ watchers.set(target.workingDir, handle);
80648
+ }
80649
+ };
80650
+ let stopped = false;
80651
+ const unsub = wsClient2.onUpdate(api.workspaceFiles.listDirListingWatchTargets, { sessionId: session2.sessionId, machineId: session2.machineId }, (targets) => {
80652
+ if (stopped)
80653
+ return;
80654
+ applyTargets(targets ?? []);
80655
+ }, (err) => {
80656
+ console.warn(`[${formatTimestamp()}] ⚠️ Dir listing watch subscription error: ${getErrorMessage(err)}`);
80657
+ });
80658
+ console.log(`[${formatTimestamp()}] \uD83D\uDC41️ Dir listing FS watch subscription started`);
80659
+ return {
80660
+ stop: () => {
80661
+ stopped = true;
80662
+ unsub();
80663
+ for (const handle of watchers.values())
80664
+ handle.stop();
80665
+ watchers.clear();
80666
+ console.log(`[${formatTimestamp()}] \uD83D\uDC41️ Dir listing FS watch subscription stopped`);
80667
+ }
80668
+ };
80669
+ });
80670
+ var init_dir_listing_watch_subscription = __esm(() => {
80671
+ init_esm();
80672
+ init_daemon_services();
80673
+ init_api3();
80674
+ init_dir_listing_sync();
80675
+ init_workspace_fs_watcher();
80676
+ init_convex_error();
80677
+ });
80678
+
79920
80679
  // src/events/daemon/agent/on-request-start-agent.ts
79921
80680
  function notifyAgentStartFailed(backend2, opts) {
79922
80681
  backend2.mutation(api.machines.emitAgentStartFailed, opts).catch((err) => {
@@ -80016,13 +80775,8 @@ var init_on_request_stop_agent = __esm(() => {
80016
80775
  init_stop_agent();
80017
80776
  });
80018
80777
 
80019
- // src/commands/machine/daemon-start/utils.ts
80020
- function formatTimestamp() {
80021
- return new Date().toISOString().replace("T", " ").substring(0, 19);
80022
- }
80023
-
80024
80778
  // src/commands/machine/daemon-start/handlers/orphan-tracker.ts
80025
- import { createHash as createHash2 } from "node:crypto";
80779
+ import { createHash as createHash4 } from "node:crypto";
80026
80780
  import {
80027
80781
  appendFileSync,
80028
80782
  existsSync as existsSync5,
@@ -80036,7 +80790,7 @@ import { homedir as homedir5 } from "node:os";
80036
80790
  import { join as join15 } from "node:path";
80037
80791
  function getUrlHash() {
80038
80792
  const url2 = getConvexUrl();
80039
- return createHash2("sha256").update(url2).digest("hex").substring(0, 8);
80793
+ return createHash4("sha256").update(url2).digest("hex").substring(0, 8);
80040
80794
  }
80041
80795
  function getChildPidsFilePath() {
80042
80796
  const dir = join15(homedir5(), ".chatroom");
@@ -80230,19 +80984,19 @@ class ProcessManager {
80230
80984
  this.pendingStops.clear();
80231
80985
  }
80232
80986
  waitForExit(runId, ms) {
80233
- return new Promise((resolve3) => {
80987
+ return new Promise((resolve4) => {
80234
80988
  const interval = 100;
80235
80989
  let elapsed3 = 0;
80236
80990
  const timer = setInterval(() => {
80237
80991
  if (!this.runningProcesses.has(runId)) {
80238
80992
  clearInterval(timer);
80239
- resolve3(true);
80993
+ resolve4(true);
80240
80994
  return;
80241
80995
  }
80242
80996
  elapsed3 += interval;
80243
80997
  if (elapsed3 >= ms) {
80244
80998
  clearInterval(timer);
80245
- resolve3(false);
80999
+ resolve4(false);
80246
81000
  }
80247
81001
  }, interval);
80248
81002
  });
@@ -80277,9 +81031,9 @@ var init_killer = __esm(() => {
80277
81031
  });
80278
81032
 
80279
81033
  // ../../services/backend/src/output-encoding.ts
80280
- import { gunzipSync, gzipSync } from "node:zlib";
81034
+ import { gunzipSync as gunzipSync2, gzipSync as gzipSync3 } from "node:zlib";
80281
81035
  function encodeOutput(plain) {
80282
- const compressed = gzipSync(Buffer.from(plain, "utf-8"));
81036
+ const compressed = gzipSync3(Buffer.from(plain, "utf-8"));
80283
81037
  return {
80284
81038
  compression: "gzip",
80285
81039
  content: compressed.toString("base64")
@@ -80457,7 +81211,7 @@ var init_output_store = __esm(() => {
80457
81211
  });
80458
81212
 
80459
81213
  // src/commands/machine/daemon-start/handlers/process/spawner.ts
80460
- import { spawn as spawn3 } from "node:child_process";
81214
+ import { spawn as spawn4 } from "node:child_process";
80461
81215
  async function flushTailV2(deps, tracked, force = false) {
80462
81216
  if (!force && !isRunLogObserved(tracked.runId))
80463
81217
  return;
@@ -80540,7 +81294,7 @@ async function spawnCommandProcess(deps, event, commandKey) {
80540
81294
  tempDirReady = true;
80541
81295
  }
80542
81296
  const store = createOutputStore(runIdStr);
80543
- const child = spawn3("sh", ["-c", script], {
81297
+ const child = spawn4("sh", ["-c", script], {
80544
81298
  cwd: workingDir,
80545
81299
  env: buildChatroomSpawnEnv(deps.convexUrl),
80546
81300
  stdio: ["ignore", "pipe", "pipe"],
@@ -80802,8 +81556,8 @@ var init_command_runner = __esm(() => {
80802
81556
  }).catch((err) => {
80803
81557
  console.warn(`[${formatTimestamp()}] ⚠️ Failed to mark run as killed on shutdown: ${getErrorMessage(err)}`);
80804
81558
  })));
80805
- yield* exports_Effect.promise(() => new Promise((resolve3) => {
80806
- const t = setTimeout(resolve3, 3000);
81559
+ yield* exports_Effect.promise(() => new Promise((resolve4) => {
81560
+ const t = setTimeout(resolve4, 3000);
80807
81561
  t.unref?.();
80808
81562
  }));
80809
81563
  for (const [, tracked] of trackedEntries) {
@@ -80815,8 +81569,8 @@ var init_command_runner = __esm(() => {
80815
81569
  clearTrackedPids();
80816
81570
  yield* exports_Effect.promise(() => Promise.race([
80817
81571
  statusUpdates,
80818
- new Promise((resolve3) => {
80819
- const t = setTimeout(resolve3, 2000);
81572
+ new Promise((resolve4) => {
81573
+ const t = setTimeout(resolve4, 2000);
80820
81574
  t.unref?.();
80821
81575
  })
80822
81576
  ]));
@@ -80851,28 +81605,81 @@ var init_on_daemon_shutdown = __esm(() => {
80851
81605
  connected: false
80852
81606
  }).catch(() => {}));
80853
81607
  });
80854
- });
80855
-
80856
- // src/infrastructure/git/types.ts
80857
- function makeGitStateKey(machineId, workingDir) {
80858
- return `${machineId}::${workingDir}`;
81608
+ });
81609
+
81610
+ // src/infrastructure/git/types.ts
81611
+ function makeGitStateKey(machineId, workingDir) {
81612
+ return `${machineId}::${workingDir}`;
81613
+ }
81614
+ var FULL_DIFF_MAX_BYTES = 500000, COMMITS_PER_PAGE = 20;
81615
+
81616
+ // src/infrastructure/git/run-command.ts
81617
+ import { spawn as spawn5 } from "node:child_process";
81618
+ function runCommandSpawn(command, args2, cwd, options) {
81619
+ return new Promise((resolve4) => {
81620
+ const child = spawn5(command, args2, {
81621
+ cwd,
81622
+ env: options?.env ?? DEFAULT_GIT_ENV,
81623
+ stdio: ["ignore", "pipe", "pipe"]
81624
+ });
81625
+ let stdout = "";
81626
+ let stderr = "";
81627
+ const maxBuffer = options?.maxBuffer ?? 10 * 1024 * 1024;
81628
+ const onData = (chunk2, target) => {
81629
+ const str = chunk2.toString();
81630
+ if (target === "stdout")
81631
+ stdout += str;
81632
+ else
81633
+ stderr += str;
81634
+ if (stdout.length + stderr.length > maxBuffer) {
81635
+ child.kill("SIGTERM");
81636
+ }
81637
+ };
81638
+ child.stdout?.on("data", (c) => onData(c, "stdout"));
81639
+ child.stderr?.on("data", (c) => onData(c, "stderr"));
81640
+ const timer = options?.timeout ? setTimeout(() => child.kill("SIGTERM"), options.timeout) : undefined;
81641
+ child.on("close", (code2) => {
81642
+ if (timer)
81643
+ clearTimeout(timer);
81644
+ if (code2 === 0)
81645
+ resolve4({ stdout, stderr });
81646
+ else {
81647
+ resolve4({
81648
+ error: Object.assign(new Error(stderr || stdout || `exit ${code2}`), {
81649
+ code: code2 ?? undefined
81650
+ })
81651
+ });
81652
+ }
81653
+ });
81654
+ child.on("error", (err) => {
81655
+ if (timer)
81656
+ clearTimeout(timer);
81657
+ resolve4({ error: err });
81658
+ });
81659
+ });
80859
81660
  }
80860
- var FULL_DIFF_MAX_BYTES = 500000, COMMITS_PER_PAGE = 20;
81661
+ function runGit(args2, cwd, options) {
81662
+ return runCommandSpawn("git", args2, cwd, options);
81663
+ }
81664
+ function runGh(args2, cwd, options) {
81665
+ return runCommandSpawn("gh", args2, cwd, {
81666
+ timeout: options?.timeout,
81667
+ env: { ...process.env, NO_COLOR: "1" }
81668
+ });
81669
+ }
81670
+ var DEFAULT_GIT_ENV;
81671
+ var init_run_command = __esm(() => {
81672
+ DEFAULT_GIT_ENV = {
81673
+ ...process.env,
81674
+ GIT_TERMINAL_PROMPT: "0",
81675
+ GIT_PAGER: "cat",
81676
+ NO_COLOR: "1"
81677
+ };
81678
+ });
80861
81679
 
80862
81680
  // src/infrastructure/git/git-reader.ts
80863
- import { exec as exec2 } from "node:child_process";
80864
- import { promisify as promisify2 } from "node:util";
80865
- async function runGit(args2, cwd) {
80866
- try {
80867
- const result = await execAsync2(`git ${args2}`, {
80868
- cwd,
80869
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_PAGER: "cat", NO_COLOR: "1" },
80870
- maxBuffer: FULL_DIFF_MAX_BYTES + 64 * 1024
80871
- });
80872
- return result;
80873
- } catch (err) {
80874
- return { error: err };
80875
- }
81681
+ function repoArgs(repoSlug) {
81682
+ return repoSlug ? ["--repo", repoSlug] : [];
80876
81683
  }
80877
81684
  function isGitNotInstalled(message) {
80878
81685
  return message.includes("command not found") || message.includes("ENOENT") || message.includes("not found") || message.includes("'git' is not recognized");
@@ -80898,14 +81705,14 @@ function classifyError(errMessage) {
80898
81705
  }
80899
81706
  return { status: "error", message: errMessage.trim() };
80900
81707
  }
80901
- async function isGitRepo(workingDir) {
80902
- const result = await runGit("rev-parse --git-dir", workingDir);
81708
+ async function isGitRepo2(workingDir) {
81709
+ const result = await runGit(["rev-parse", "--git-dir"], workingDir);
80903
81710
  if ("error" in result)
80904
81711
  return false;
80905
81712
  return result.stdout.trim().length > 0;
80906
81713
  }
80907
81714
  async function getBranch(workingDir) {
80908
- const result = await runGit("rev-parse --abbrev-ref HEAD", workingDir);
81715
+ const result = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], workingDir);
80909
81716
  if ("error" in result) {
80910
81717
  const errMsg = result.error.message;
80911
81718
  if (errMsg.includes("unknown revision") || errMsg.includes("No such file or directory") || errMsg.includes("does not have any commits")) {
@@ -80920,7 +81727,7 @@ async function getBranch(workingDir) {
80920
81727
  return { status: "available", branch };
80921
81728
  }
80922
81729
  async function isDirty(workingDir) {
80923
- const result = await runGit("status --porcelain", workingDir);
81730
+ const result = await runGit(["status", "--porcelain"], workingDir);
80924
81731
  if ("error" in result)
80925
81732
  return false;
80926
81733
  return result.stdout.trim().length > 0;
@@ -80936,7 +81743,7 @@ function parseDiffStatLine(statLine) {
80936
81743
  };
80937
81744
  }
80938
81745
  async function getDiffStat(workingDir) {
80939
- const result = await runGit("diff HEAD --stat", workingDir);
81746
+ const result = await runGit(["diff", "HEAD", "--stat"], workingDir);
80940
81747
  if ("error" in result) {
80941
81748
  const errMsg = result.error.message;
80942
81749
  if (isEmptyRepo(result.error.message)) {
@@ -80965,7 +81772,9 @@ async function getDiffStat(workingDir) {
80965
81772
  return { status: "available", diffStat };
80966
81773
  }
80967
81774
  async function getFullDiff(workingDir) {
80968
- const result = await runGit("diff HEAD", workingDir);
81775
+ const result = await runGit(["diff", "HEAD"], workingDir, {
81776
+ maxBuffer: FULL_DIFF_MAX_BYTES + 64 * 1024
81777
+ });
80969
81778
  if ("error" in result) {
80970
81779
  const errMsg = result.error.message;
80971
81780
  if (isEmptyRepo(errMsg)) {
@@ -80990,8 +81799,7 @@ async function getFullDiff(workingDir) {
80990
81799
  }
80991
81800
  async function getPRDiffByNumber(cwd, prNumber) {
80992
81801
  const repoSlug = await getOriginRepoSlug(cwd);
80993
- const repoFlag = repoSlug ? ` --repo ${JSON.stringify(repoSlug)}` : "";
80994
- const result = await runCommand(`gh pr diff ${prNumber}${repoFlag}`, cwd);
81802
+ const result = await runGh(["pr", "diff", String(prNumber), ...repoArgs(repoSlug)], cwd);
80995
81803
  if ("error" in result) {
80996
81804
  return { status: "error", message: result.error.message };
80997
81805
  }
@@ -81008,8 +81816,11 @@ async function getPRDiffByNumber(cwd, prNumber) {
81008
81816
  }
81009
81817
  async function getRecentCommits(workingDir, count3 = 20, skip = 0) {
81010
81818
  const format4 = "%H%x1f%h%x1f%s%x1f%b%x1f%an%x1f%aI%x1e";
81011
- const skipArg = skip > 0 ? ` --skip=${skip}` : "";
81012
- const result = await runGit(`log -${count3}${skipArg} --format=${format4}`, workingDir);
81819
+ const logArgs = ["log", `-${count3}`];
81820
+ if (skip > 0)
81821
+ logArgs.push(`--skip=${skip}`);
81822
+ logArgs.push(`--format=${format4}`);
81823
+ const result = await runGit(logArgs, workingDir);
81013
81824
  if ("error" in result) {
81014
81825
  return [];
81015
81826
  }
@@ -81031,7 +81842,7 @@ async function getRecentCommits(workingDir, count3 = 20, skip = 0) {
81031
81842
  return commits;
81032
81843
  }
81033
81844
  async function getCommitDetail(workingDir, sha) {
81034
- const result = await runGit(`show ${sha} --format="" --stat -p`, workingDir);
81845
+ const result = await runGit(["show", sha, "--format=", "--stat", "-p"], workingDir);
81035
81846
  if ("error" in result) {
81036
81847
  const errMsg = result.error.message;
81037
81848
  const classified = classifyError(errMsg);
@@ -81052,7 +81863,7 @@ async function getCommitDetail(workingDir, sha) {
81052
81863
  }
81053
81864
  async function getCommitMetadata(workingDir, sha) {
81054
81865
  const format4 = "%s%x1f%b%x1f%an%x1f%aI";
81055
- const result = await runGit(`log -1 --format=${format4} ${sha}`, workingDir);
81866
+ const result = await runGit(["log", "-1", `--format=${format4}`, sha], workingDir);
81056
81867
  if ("error" in result)
81057
81868
  return null;
81058
81869
  const output = result.stdout.trim();
@@ -81065,18 +81876,6 @@ async function getCommitMetadata(workingDir, sha) {
81065
81876
  const body = rawBody.trim();
81066
81877
  return { message, ...body ? { body } : {}, author, date };
81067
81878
  }
81068
- async function runCommand(command, cwd) {
81069
- try {
81070
- const result = await execAsync2(command, {
81071
- cwd,
81072
- env: { ...process.env, NO_COLOR: "1" },
81073
- timeout: 15000
81074
- });
81075
- return result;
81076
- } catch (err) {
81077
- return { error: err };
81078
- }
81079
- }
81080
81879
  function parseRepoSlug(remoteUrl) {
81081
81880
  const trimmed = remoteUrl.trim();
81082
81881
  const httpsMatch = trimmed.match(/https?:\/\/[^/]+\/([^/]+)\/([^/]+?)(?:\.git)?$/);
@@ -81090,7 +81889,7 @@ function parseRepoSlug(remoteUrl) {
81090
81889
  return null;
81091
81890
  }
81092
81891
  async function getOriginRepoSlug(cwd) {
81093
- const result = await runGit("remote get-url origin", cwd);
81892
+ const result = await runGit(["remote", "get-url", "origin"], cwd);
81094
81893
  if ("error" in result)
81095
81894
  return null;
81096
81895
  const url2 = result.stdout.trim();
@@ -81100,9 +81899,22 @@ async function getOriginRepoSlug(cwd) {
81100
81899
  }
81101
81900
  async function getOpenPRsForBranch(cwd, branch) {
81102
81901
  const repoSlug = await getOriginRepoSlug(cwd);
81103
- const repoFlag = repoSlug ? ` --repo ${JSON.stringify(repoSlug)}` : "";
81104
81902
  const repoOwner = repoSlug?.split("/")[0] ?? null;
81105
- const result = await runCommand(`gh pr list --head ${JSON.stringify(branch)} --state open --author @me --json number,title,url,headRefName,state,headRepositoryOwner --limit 5${repoFlag}`, cwd);
81903
+ const result = await runGh([
81904
+ "pr",
81905
+ "list",
81906
+ "--head",
81907
+ branch,
81908
+ "--state",
81909
+ "open",
81910
+ "--author",
81911
+ "@me",
81912
+ "--json",
81913
+ "number,title,url,headRefName,state,headRepositoryOwner",
81914
+ "--limit",
81915
+ "5",
81916
+ ...repoArgs(repoSlug)
81917
+ ], cwd);
81106
81918
  if ("error" in result) {
81107
81919
  return [];
81108
81920
  }
@@ -81133,8 +81945,17 @@ function isGHPRItem(item) {
81133
81945
  }
81134
81946
  async function getAllPRs(cwd) {
81135
81947
  const repoSlug = await getOriginRepoSlug(cwd);
81136
- const repoFlag = repoSlug ? ` --repo ${JSON.stringify(repoSlug)}` : "";
81137
- const result = await runCommand(`gh pr list --limit 20 --state all --json number,title,state,headRefName,baseRefName,url,author,createdAt,updatedAt,mergedAt,closedAt,isDraft${repoFlag}`, cwd);
81948
+ const result = await runGh([
81949
+ "pr",
81950
+ "list",
81951
+ "--limit",
81952
+ "20",
81953
+ "--state",
81954
+ "all",
81955
+ "--json",
81956
+ "number,title,state,headRefName,baseRefName,url,author,createdAt,updatedAt,mergedAt,closedAt,isDraft",
81957
+ ...repoArgs(repoSlug)
81958
+ ], cwd);
81138
81959
  if ("error" in result) {
81139
81960
  return [];
81140
81961
  }
@@ -81168,8 +81989,7 @@ async function getAllPRs(cwd) {
81168
81989
  }
81169
81990
  async function getPRCommits(cwd, prNumber) {
81170
81991
  const repoSlug = await getOriginRepoSlug(cwd);
81171
- const repoFlag = repoSlug ? ` --repo ${JSON.stringify(repoSlug)}` : "";
81172
- const result = await runCommand(`gh pr view ${prNumber} --json commits${repoFlag}`, cwd);
81992
+ const result = await runGh(["pr", "view", String(prNumber), "--json", "commits", ...repoArgs(repoSlug)], cwd);
81173
81993
  if ("error" in result) {
81174
81994
  return [];
81175
81995
  }
@@ -81206,7 +82026,7 @@ async function getPRCommits(cwd, prNumber) {
81206
82026
  }
81207
82027
  }
81208
82028
  async function getRemotes(cwd) {
81209
- const result = await runGit("remote -v", cwd);
82029
+ const result = await runGit(["remote", "-v"], cwd);
81210
82030
  if ("error" in result)
81211
82031
  return [];
81212
82032
  if (!result.stdout.trim())
@@ -81230,14 +82050,14 @@ async function getRemotes(cwd) {
81230
82050
  return remotes;
81231
82051
  }
81232
82052
  async function getCommitsAhead(workingDir) {
81233
- const result = await runGit("rev-list --count @{upstream}..HEAD", workingDir);
82053
+ const result = await runGit(["rev-list", "--count", "@{upstream}..HEAD"], workingDir);
81234
82054
  if ("error" in result)
81235
82055
  return 0;
81236
82056
  const count3 = parseInt(result.stdout.trim(), 10);
81237
82057
  return Number.isNaN(count3) ? 0 : count3;
81238
82058
  }
81239
82059
  async function getCommitsBehind(workingDir) {
81240
- const result = await runGit("rev-list --count HEAD..@{upstream}", workingDir);
82060
+ const result = await runGit(["rev-list", "--count", "HEAD..@{upstream}"], workingDir);
81241
82061
  if ("error" in result)
81242
82062
  return 0;
81243
82063
  const count3 = parseInt(result.stdout.trim(), 10);
@@ -81249,8 +82069,18 @@ async function getCommitStatusChecks(cwd, ref) {
81249
82069
  return null;
81250
82070
  try {
81251
82071
  const [checkRunsResult, legacyStatusesResult] = await Promise.all([
81252
- runCommand(`gh api repos/${repoSlug}/commits/${encodeURIComponent(ref)}/check-runs --jq '{check_runs: [.check_runs[] | {name: .name, status: .status, conclusion: .conclusion}], total_count: .total_count}'`, cwd),
81253
- runCommand(`gh api repos/${repoSlug}/commits/${encodeURIComponent(ref)}/statuses --jq '[group_by(.context)[] | max_by(.created_at) | {context: .context, state: .state, target_url: .target_url}]'`, cwd)
82072
+ runGh([
82073
+ "api",
82074
+ `repos/${repoSlug}/commits/${encodeURIComponent(ref)}/check-runs`,
82075
+ "--jq",
82076
+ "{check_runs: [.check_runs[] | {name: .name, status: .status, conclusion: .conclusion}], total_count: .total_count}"
82077
+ ], cwd),
82078
+ runGh([
82079
+ "api",
82080
+ `repos/${repoSlug}/commits/${encodeURIComponent(ref)}/statuses`,
82081
+ "--jq",
82082
+ "[group_by(.context)[] | max_by(.created_at) | {context: .context, state: .state, target_url: .target_url}]"
82083
+ ], cwd)
81254
82084
  ]);
81255
82085
  if ("error" in checkRunsResult || "error" in legacyStatusesResult)
81256
82086
  return null;
@@ -81303,49 +82133,34 @@ async function getCommitStatusChecks(cwd, ref) {
81303
82133
  return null;
81304
82134
  }
81305
82135
  }
81306
- var execAsync2;
81307
82136
  var init_git_reader = __esm(() => {
81308
- execAsync2 = promisify2(exec2);
82137
+ init_run_command();
81309
82138
  });
81310
82139
 
81311
82140
  // src/infrastructure/git/git-writer.ts
81312
- import { exec as exec3 } from "node:child_process";
81313
- import { promisify as promisify3 } from "node:util";
81314
- async function runGit2(args2, cwd) {
81315
- try {
81316
- const result = await execAsync3(`git ${args2}`, {
81317
- cwd,
81318
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_PAGER: "cat", NO_COLOR: "1" },
81319
- timeout: 30000
81320
- });
81321
- return result;
81322
- } catch (err) {
81323
- return { error: err };
81324
- }
81325
- }
81326
82141
  function classifyError2(errMessage) {
81327
82142
  return { status: "error", message: errMessage.trim() };
81328
82143
  }
81329
82144
  async function discardFile(workingDir, filePath) {
81330
- const result = await runGit2(`checkout -- ${filePath}`, workingDir);
82145
+ const result = await runGit(["checkout", "--", filePath], workingDir);
81331
82146
  if ("error" in result) {
81332
82147
  return classifyError2(result.error.message);
81333
82148
  }
81334
82149
  return { status: "available" };
81335
82150
  }
81336
82151
  async function discardAllChanges(workingDir) {
81337
- const checkoutResult = await runGit2("checkout -- .", workingDir);
82152
+ const checkoutResult = await runGit(["checkout", "--", "."], workingDir);
81338
82153
  if ("error" in checkoutResult) {
81339
82154
  return classifyError2(checkoutResult.error.message);
81340
82155
  }
81341
- const cleanResult = await runGit2("clean -fd", workingDir);
82156
+ const cleanResult = await runGit(["clean", "-fd"], workingDir);
81342
82157
  if ("error" in cleanResult) {
81343
82158
  return classifyError2(cleanResult.error.message);
81344
82159
  }
81345
82160
  return { status: "available" };
81346
82161
  }
81347
82162
  async function gitPull(workingDir) {
81348
- const result = await runGit2("pull", workingDir);
82163
+ const result = await runGit(["pull"], workingDir);
81349
82164
  if ("error" in result) {
81350
82165
  const message = result.error.message;
81351
82166
  if (message.includes("no tracking branch")) {
@@ -81372,7 +82187,7 @@ async function gitPull(workingDir) {
81372
82187
  return { status: "available" };
81373
82188
  }
81374
82189
  async function gitPush(workingDir) {
81375
- const result = await runGit2("push", workingDir);
82190
+ const result = await runGit(["push"], workingDir);
81376
82191
  if ("error" in result) {
81377
82192
  const message = result.error.message;
81378
82193
  if (message.includes("no upstream branch")) {
@@ -81402,9 +82217,8 @@ async function gitSync(workingDir) {
81402
82217
  }
81403
82218
  return gitPush(workingDir);
81404
82219
  }
81405
- var execAsync3;
81406
82220
  var init_git_writer = __esm(() => {
81407
- execAsync3 = promisify3(exec3);
82221
+ init_run_command();
81408
82222
  });
81409
82223
 
81410
82224
  // src/infrastructure/git/index.ts
@@ -81414,7 +82228,7 @@ var init_git = __esm(() => {
81414
82228
  });
81415
82229
 
81416
82230
  // src/infrastructure/local-actions/execute-local-action.ts
81417
- import { exec as exec4 } from "node:child_process";
82231
+ import { exec as exec3 } from "node:child_process";
81418
82232
  import { access as access3 } from "node:fs/promises";
81419
82233
  function escapeShellArg(arg) {
81420
82234
  return `"${arg.replace(/"/g, "\\\"")}"`;
@@ -81423,14 +82237,14 @@ function resolveWhichCommand(name) {
81423
82237
  return process.platform === "win32" ? `where ${name}` : `which ${name}`;
81424
82238
  }
81425
82239
  function isCliAvailable(cliName) {
81426
- return new Promise((resolve3) => {
81427
- exec4(resolveWhichCommand(cliName), (err) => {
81428
- resolve3(!err);
82240
+ return new Promise((resolve4) => {
82241
+ exec3(resolveWhichCommand(cliName), (err) => {
82242
+ resolve4(!err);
81429
82243
  });
81430
82244
  });
81431
82245
  }
81432
82246
  function execFireAndForget(command, logTag) {
81433
- exec4(command, (err) => {
82247
+ exec3(command, (err) => {
81434
82248
  if (err) {
81435
82249
  console.warn(`[${logTag}] exec failed: ${err.message}`);
81436
82250
  }
@@ -81546,13 +82360,13 @@ var init_local_actions = __esm(() => {
81546
82360
  });
81547
82361
 
81548
82362
  // src/commands/machine/pid.ts
81549
- import { createHash as createHash3 } from "node:crypto";
82363
+ import { createHash as createHash5 } from "node:crypto";
81550
82364
  import { existsSync as existsSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, mkdirSync as mkdirSync5 } from "node:fs";
81551
82365
  import { homedir as homedir6 } from "node:os";
81552
82366
  import { join as join17 } from "node:path";
81553
82367
  function getUrlHash2() {
81554
82368
  const url2 = getConvexUrl();
81555
- return createHash3("sha256").update(url2).digest("hex").substring(0, 8);
82369
+ return createHash5("sha256").update(url2).digest("hex").substring(0, 8);
81556
82370
  }
81557
82371
  function getPidFileName() {
81558
82372
  return `daemon-${getUrlHash2()}.pid`;
@@ -81645,7 +82459,7 @@ async function waitForLockOrTimeout(deadline, intervalMs, sleep5) {
81645
82459
  async function acquireLockWithRetry(options) {
81646
82460
  const intervalMs = options?.intervalMs ?? LOCK_RETRY_INTERVAL_MS;
81647
82461
  const maxWaitMs = options?.maxWaitMs ?? LOCK_RETRY_MAX_WAIT_MS;
81648
- const sleep5 = options?.sleep ?? ((ms) => new Promise((resolve3) => setTimeout(resolve3, ms)));
82462
+ const sleep5 = options?.sleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
81649
82463
  const deadline = Date.now() + maxWaitMs;
81650
82464
  if (await waitForLockOrTimeout(deadline, intervalMs, sleep5)) {
81651
82465
  return true;
@@ -82573,7 +83387,7 @@ var init_jsonc = __esm(() => {
82573
83387
 
82574
83388
  // src/infrastructure/services/workspace/workspace-resolver.ts
82575
83389
  import { readFile as readFile7, readdir, stat as stat2 } from "node:fs/promises";
82576
- import { join as join18, basename, resolve as resolve3 } from "node:path";
83390
+ import { join as join18, basename as basename2, resolve as resolve4 } from "node:path";
82577
83391
  async function resolveGlobPatternStar(rootDir, cleaned) {
82578
83392
  const parentDir = join18(rootDir, cleaned.slice(0, -2));
82579
83393
  try {
@@ -82583,7 +83397,7 @@ async function resolveGlobPatternStar(rootDir, cleaned) {
82583
83397
  if (!entry.isDirectory())
82584
83398
  continue;
82585
83399
  const dirPath = join18(parentDir, entry.name);
82586
- if (resolve3(dirPath).startsWith(resolve3(rootDir))) {
83400
+ if (resolve4(dirPath).startsWith(resolve4(rootDir))) {
82587
83401
  dirs.push(dirPath);
82588
83402
  }
82589
83403
  }
@@ -82595,7 +83409,7 @@ async function resolveGlobPatternStar(rootDir, cleaned) {
82595
83409
  async function resolveLiteralPath(rootDir, cleaned) {
82596
83410
  const dir = join18(rootDir, cleaned);
82597
83411
  try {
82598
- if (!resolve3(dir).startsWith(resolve3(rootDir)))
83412
+ if (!resolve4(dir).startsWith(resolve4(rootDir)))
82599
83413
  return [];
82600
83414
  const s = await stat2(dir);
82601
83415
  if (s.isDirectory())
@@ -82616,7 +83430,7 @@ async function readPackageJson(dir) {
82616
83430
  const content = await readFile7(join18(dir, "package.json"), "utf-8");
82617
83431
  const pkg = JSON.parse(content);
82618
83432
  return {
82619
- name: pkg.name || basename(dir),
83433
+ name: pkg.name || basename2(dir),
82620
83434
  scripts: pkg.scripts && typeof pkg.scripts === "object" ? pkg.scripts : {}
82621
83435
  };
82622
83436
  } catch {
@@ -82701,7 +83515,7 @@ var init_workspace_resolver = () => {};
82701
83515
 
82702
83516
  // src/infrastructure/services/workspace/command-discovery.ts
82703
83517
  import { access as access4, readFile as readFile8 } from "node:fs/promises";
82704
- import { join as join19, relative, basename as basename2 } from "node:path";
83518
+ import { join as join19, relative as relative2, basename as basename3 } from "node:path";
82705
83519
  async function detectPackageManager(workingDir) {
82706
83520
  for (const { file, manager } of LOCKFILE_MAP) {
82707
83521
  try {
@@ -82773,7 +83587,7 @@ function collectRootScriptCommands(scripts, pm, scriptPrefix, rootSw) {
82773
83587
  return commands;
82774
83588
  }
82775
83589
  async function readRootPackageJson(workingDir, pm, scriptPrefix) {
82776
- let rootPackageName = basename2(workingDir);
83590
+ let rootPackageName = basename3(workingDir);
82777
83591
  const pkg = await readJsonFile(join19(workingDir, "package.json"), "root package.json");
82778
83592
  if (!pkg)
82779
83593
  return { commands: [], rootPackageName };
@@ -82819,7 +83633,7 @@ async function discoverCommands(workingDir) {
82819
83633
  }
82820
83634
  const subWorkspaces = await resolveSubWorkspaces(workingDir, pm);
82821
83635
  for (const pkg of subWorkspaces) {
82822
- const wsPath = relative(workingDir, pkg.dir) || ".";
83636
+ const wsPath = relative2(workingDir, pkg.dir) || ".";
82823
83637
  const pkgSubWorkspace = { type: "npm", path: wsPath, name: pkg.name };
82824
83638
  await addSubWorkspaceCommands(commands, pm, pkg, wsPath, pkgSubWorkspace, turboTaskNames);
82825
83639
  }
@@ -82859,14 +83673,14 @@ var init_command_discovery = __esm(() => {
82859
83673
  });
82860
83674
 
82861
83675
  // src/commands/machine/daemon-start/command-sync-heartbeat.ts
82862
- import { createHash as createHash4 } from "node:crypto";
83676
+ import { createHash as createHash6 } from "node:crypto";
82863
83677
  var pushCommandsEffect, pushSingleWorkspaceCommandsEffect = (workingDir) => exports_Effect.gen(function* () {
82864
83678
  const session2 = yield* DaemonSessionService;
82865
83679
  const mutable = yield* DaemonMutableStateService;
82866
83680
  const lastPushedGitState = yield* exports_Ref.get(mutable.lastPushedGitState);
82867
83681
  const commands = yield* exports_Effect.promise(() => discoverCommands(workingDir));
82868
83682
  const stateKey = `commands:${session2.machineId}::${workingDir}`;
82869
- const commandsHash = createHash4("md5").update(JSON.stringify(commands)).digest("hex");
83683
+ const commandsHash = createHash6("md5").update(JSON.stringify(commands)).digest("hex");
82870
83684
  if (lastPushedGitState.get(stateKey) === commandsHash) {
82871
83685
  return;
82872
83686
  }
@@ -82908,7 +83722,7 @@ var init_command_sync_heartbeat = __esm(() => {
82908
83722
  });
82909
83723
 
82910
83724
  // src/infrastructure/git/git-state-pipeline.ts
82911
- import { createHash as createHash5 } from "node:crypto";
83725
+ import { createHash as createHash7 } from "node:crypto";
82912
83726
 
82913
83727
  class GitStatePipeline {
82914
83728
  fields;
@@ -82940,7 +83754,7 @@ class GitStatePipeline {
82940
83754
  const raw = values3.get(field.key) ?? field.defaultValue;
82941
83755
  hashInput[field.key] = field.toHashable(raw);
82942
83756
  }
82943
- return createHash5("md5").update(JSON.stringify(hashInput)).digest("hex");
83757
+ return createHash7("md5").update(JSON.stringify(hashInput)).digest("hex");
82944
83758
  }
82945
83759
  toMutationArgs(values3, slim) {
82946
83760
  const args2 = {};
@@ -82988,7 +83802,7 @@ function buildGitStateDeps(session2, lastPushedGitState) {
82988
83802
  }
82989
83803
  async function pushSingleWorkspaceGitStateImpl(ctx, workingDir) {
82990
83804
  const stateKey = makeGitStateKey(ctx.machineId, workingDir);
82991
- const isRepo = await isGitRepo(workingDir);
83805
+ const isRepo = await isGitRepo2(workingDir);
82992
83806
  if (!isRepo) {
82993
83807
  await pushNotFoundGitState(ctx, workingDir, stateKey);
82994
83808
  return;
@@ -83133,7 +83947,7 @@ var lastFullPushMs, branchField, GIT_STATE_FIELDS, pushSingleWorkspaceGitSummary
83133
83947
  const mutable = yield* DaemonMutableStateService;
83134
83948
  const lastPushedGitState = yield* exports_Ref.get(mutable.lastPushedGitState);
83135
83949
  const stateKey = makeGitStateKey(session2.machineId, workingDir);
83136
- const isRepo = yield* exports_Effect.promise(() => isGitRepo(workingDir));
83950
+ const isRepo = yield* exports_Effect.promise(() => isGitRepo2(workingDir));
83137
83951
  if (!isRepo) {
83138
83952
  yield* pushObservedNotRepoEffect(session2, lastPushedGitState, stateKey, workingDir, reason);
83139
83953
  return;
@@ -83270,9 +84084,7 @@ var init_git_heartbeat = __esm(() => {
83270
84084
  });
83271
84085
 
83272
84086
  // src/commands/machine/daemon-start/git-subscription.ts
83273
- import { exec as exec5 } from "child_process";
83274
- import { gzipSync as gzipSync2 } from "node:zlib";
83275
- import { promisify as promisify4 } from "util";
84087
+ import { gzipSync as gzipSync4 } from "node:zlib";
83276
84088
  function extractDiffStatFromShowOutput(content) {
83277
84089
  for (const line of content.split(`
83278
84090
  `)) {
@@ -83287,7 +84099,7 @@ async function processFullDiff(deps, req) {
83287
84099
  if (result.status === "available" || result.status === "truncated") {
83288
84100
  const diffStatResult = await getDiffStat(req.workingDir);
83289
84101
  const diffStat = diffStatResult.status === "available" ? diffStatResult.diffStat : { filesChanged: 0, insertions: 0, deletions: 0 };
83290
- const compressed = gzipSync2(Buffer.from(result.content));
84102
+ const compressed = gzipSync4(Buffer.from(result.content));
83291
84103
  const diffContentCompressed = compressed.toString("base64");
83292
84104
  await deps.backend.mutation(api.workspaces.upsertFullDiffV2, {
83293
84105
  sessionId: deps.sessionId,
@@ -83299,7 +84111,7 @@ async function processFullDiff(deps, req) {
83299
84111
  });
83300
84112
  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"})`);
83301
84113
  } else {
83302
- const emptyCompressed = gzipSync2(Buffer.from("")).toString("base64");
84114
+ const emptyCompressed = gzipSync4(Buffer.from("")).toString("base64");
83303
84115
  await deps.backend.mutation(api.workspaces.upsertFullDiffV2, {
83304
84116
  sessionId: deps.sessionId,
83305
84117
  machineId: deps.machineId,
@@ -83349,25 +84161,24 @@ async function processPRAction(deps, req) {
83349
84161
  if (!prNumber || !action) {
83350
84162
  throw new Error("pr_action request missing prNumber or prAction");
83351
84163
  }
83352
- let cmd;
84164
+ let ghArgs;
83353
84165
  switch (action) {
83354
84166
  case "merge_squash":
83355
- cmd = `gh pr merge ${prNumber} --squash --delete-branch`;
84167
+ ghArgs = ["pr", "merge", String(prNumber), "--squash", "--delete-branch"];
83356
84168
  break;
83357
84169
  case "merge_no_squash":
83358
- cmd = `gh pr merge ${prNumber} --merge`;
84170
+ ghArgs = ["pr", "merge", String(prNumber), "--merge"];
83359
84171
  break;
83360
84172
  case "close":
83361
- cmd = `gh pr close ${prNumber}`;
84173
+ ghArgs = ["pr", "close", String(prNumber)];
83362
84174
  break;
83363
84175
  default:
83364
84176
  throw new Error(`Unknown PR action: ${action}`);
83365
84177
  }
83366
- const execAsync4 = promisify4(exec5);
83367
- const result = await execAsync4(cmd, {
83368
- cwd: req.workingDir,
83369
- timeout: EXEC_TIMEOUT_MS
83370
- });
84178
+ const result = await runGh(ghArgs, req.workingDir, { timeout: EXEC_TIMEOUT_MS });
84179
+ if ("error" in result) {
84180
+ throw result.error;
84181
+ }
83371
84182
  console.log(`[${formatTimestamp()}] ✅ PR action: ${action} on #${prNumber}${result.stdout ? ` — ${result.stdout.trim()}` : ""}`);
83372
84183
  exports_Runtime.runFork(deps.runtime)(pushGitStateEffect.pipe(exports_Effect.provide(exports_Layer.mergeAll(exports_Layer.succeed(DaemonSessionService, deps), DaemonMutableStateServiceLive({
83373
84184
  lastPushedGitState: deps.lastPushedGitState,
@@ -83401,7 +84212,7 @@ async function processCommitDetail(deps, req) {
83401
84212
  ]);
83402
84213
  await upsertCommitDetailResult(deps, req, result, metadata);
83403
84214
  if (result.status === "available" || result.status === "truncated") {
83404
- const compressed = gzipSync2(Buffer.from(result.content));
84215
+ const compressed = gzipSync4(Buffer.from(result.content));
83405
84216
  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)`);
83406
84217
  }
83407
84218
  }
@@ -83432,7 +84243,7 @@ async function upsertCommitDetailResult(deps, req, result, metadata) {
83432
84243
  return;
83433
84244
  }
83434
84245
  const diffStat = extractDiffStatFromShowOutput(result.content);
83435
- const compressed = gzipSync2(Buffer.from(result.content));
84246
+ const compressed = gzipSync4(Buffer.from(result.content));
83436
84247
  const diffContentCompressed = compressed.toString("base64");
83437
84248
  await deps.backend.mutation(api.workspaces.upsertCommitDetailV2, {
83438
84249
  ...baseArgs,
@@ -83581,11 +84392,12 @@ var init_git_subscription = __esm(() => {
83581
84392
  init_git_heartbeat();
83582
84393
  init_api3();
83583
84394
  init_git_reader();
84395
+ init_run_command();
83584
84396
  init_convex_error();
83585
84397
  });
83586
84398
 
83587
84399
  // src/commands/machine/daemon-start/commit-detail-sync.ts
83588
- import { gzipSync as gzipSync3 } from "node:zlib";
84400
+ import { gzipSync as gzipSync5 } from "node:zlib";
83589
84401
  function upsertCommitDetailArgs(session2, workingDir, sha, metadata, overrides) {
83590
84402
  return {
83591
84403
  sessionId: session2.sessionId,
@@ -83614,7 +84426,7 @@ var seenShas, prefetchSingleShaEffect = (session2, workingDir, sha, commits) =>
83614
84426
  return;
83615
84427
  }
83616
84428
  const diffStat = extractDiffStatFromShowOutput(result.content);
83617
- const compressed = gzipSync3(Buffer.from(result.content));
84429
+ const compressed = gzipSync5(Buffer.from(result.content));
83618
84430
  const diffContentCompressed = compressed.toString("base64");
83619
84431
  yield* exports_Effect.tryPromise(() => session2.backend.mutation(api.workspaces.upsertCommitDetailV2, upsertCommitDetailArgs(session2, workingDir, sha, metadata, {
83620
84432
  status: "available",
@@ -84035,14 +84847,14 @@ var init_sse_event_buffer = __esm(() => {
84035
84847
  }
84036
84848
  _wake() {
84037
84849
  if (this._waiter) {
84038
- const resolve4 = this._waiter;
84850
+ const resolve5 = this._waiter;
84039
84851
  this._waiter = null;
84040
- resolve4();
84852
+ resolve5();
84041
84853
  }
84042
84854
  }
84043
84855
  _waitForData() {
84044
- return new Promise((resolve4) => {
84045
- this._waiter = resolve4;
84856
+ return new Promise((resolve5) => {
84857
+ this._waiter = resolve5;
84046
84858
  });
84047
84859
  }
84048
84860
  [Symbol.asyncIterator]() {
@@ -84097,8 +84909,8 @@ class OpencodeSdkSession {
84097
84909
  async prompt(input) {
84098
84910
  if (this.closed)
84099
84911
  throw new Error("Session is closed");
84100
- const idlePromise = new Promise((resolve4) => {
84101
- this._idleResolve = resolve4;
84912
+ const idlePromise = new Promise((resolve5) => {
84913
+ this._idleResolve = resolve5;
84102
84914
  });
84103
84915
  const IDLE_TIMEOUT_MS = 300000;
84104
84916
  const timeoutPromise = new Promise((_, reject) => {
@@ -84182,7 +84994,7 @@ var init_opencode_session = __esm(() => {
84182
84994
  });
84183
84995
 
84184
84996
  // src/infrastructure/harnesses/opencode-sdk/opencode-harness.ts
84185
- import { spawn as spawn4 } from "node:child_process";
84997
+ import { spawn as spawn6 } from "node:child_process";
84186
84998
  function harnessEventSessionId(event) {
84187
84999
  const p = event.properties;
84188
85000
  if (!p)
@@ -84429,14 +85241,14 @@ class OpencodeSdkHarness {
84429
85241
  }
84430
85242
  this.sessionListeners.clear();
84431
85243
  this.childProcess.kill("SIGTERM");
84432
- await new Promise((resolve4) => {
85244
+ await new Promise((resolve5) => {
84433
85245
  const timeout3 = setTimeout(() => {
84434
85246
  this.childProcess.kill("SIGKILL");
84435
- resolve4();
85247
+ resolve5();
84436
85248
  }, 5000);
84437
85249
  this.childProcess.once("exit", () => {
84438
85250
  clearTimeout(timeout3);
84439
- resolve4();
85251
+ resolve5();
84440
85252
  });
84441
85253
  });
84442
85254
  }
@@ -84456,7 +85268,7 @@ class OpencodeSdkHarness {
84456
85268
  }
84457
85269
  }
84458
85270
  var OPENCODE_COMMAND3 = "opencode", SERVE_STARTUP_TIMEOUT_MS2 = 1e4, startOpencodeSdkHarness = async (config3) => {
84459
- const childProcess = spawn4(OPENCODE_COMMAND3, ["serve", "--print-logs"], {
85271
+ const childProcess = spawn6(OPENCODE_COMMAND3, ["serve", "--print-logs"], {
84460
85272
  cwd: config3.workingDir,
84461
85273
  stdio: ["pipe", "pipe", "pipe"],
84462
85274
  shell: false,
@@ -85818,10 +86630,10 @@ class BufferedJournalFactory {
85818
86630
  const waitForInProgress = () => {
85819
86631
  if (!flushInProgress)
85820
86632
  return Promise.resolve();
85821
- return new Promise((resolve4) => {
86633
+ return new Promise((resolve5) => {
85822
86634
  const check3 = () => {
85823
86635
  if (!flushInProgress)
85824
- resolve4();
86636
+ resolve5();
85825
86637
  else
85826
86638
  setTimeout(check3, 10);
85827
86639
  };
@@ -85939,21 +86751,64 @@ var init_start_subscriptions = __esm(() => {
85939
86751
  init_convex_session_repository();
85940
86752
  });
85941
86753
 
86754
+ // src/infrastructure/services/workspace/assert-registered-working-dir.ts
86755
+ async function assertRegisteredWorkingDir(session2, workingDir) {
86756
+ const workspaces = await getWorkspacesForMachine({
86757
+ workspaceListStore: session2.workspaceListStore,
86758
+ sessionId: session2.sessionId,
86759
+ machineId: session2.machineId,
86760
+ backend: session2.backend
86761
+ });
86762
+ if (!workspaces.some((w) => w.workingDir === workingDir)) {
86763
+ return { ok: false, error: "Workspace not registered for this machine" };
86764
+ }
86765
+ return { ok: true };
86766
+ }
86767
+ var init_assert_registered_working_dir = __esm(() => {
86768
+ init_workspace_cache();
86769
+ });
86770
+
85942
86771
  // src/commands/machine/daemon-start/file-content-fulfillment.ts
85943
86772
  import { readFile as readFile9 } from "node:fs/promises";
85944
- import { resolve as resolve4 } from "node:path";
85945
- import { gzipSync as gzipSync4 } from "node:zlib";
85946
- function isBinaryFile(path3) {
85947
- const lastDot = path3.lastIndexOf(".");
86773
+ import { gzipSync as gzipSync6 } from "node:zlib";
86774
+ function getErrorCause(error) {
86775
+ if (typeof error === "object" && error !== null && "cause" in error && error.cause !== undefined) {
86776
+ return error.cause;
86777
+ }
86778
+ return error;
86779
+ }
86780
+ function isENOENT(error) {
86781
+ const cause3 = getErrorCause(error);
86782
+ return typeof cause3 === "object" && cause3 !== null && "code" in cause3 && cause3.code === "ENOENT";
86783
+ }
86784
+ function isBinaryFile(path6) {
86785
+ const lastDot = path6.lastIndexOf(".");
85948
86786
  if (lastDot === -1)
85949
86787
  return false;
85950
- return BINARY_EXTENSIONS.has(path3.slice(lastDot).toLowerCase());
86788
+ return BINARY_EXTENSIONS.has(path6.slice(lastDot).toLowerCase());
86789
+ }
86790
+ function gzipPlainText(text) {
86791
+ return gzipSync6(Buffer.from(text)).toString("base64");
86792
+ }
86793
+ function fulfillGzippedContentEffect(session2, workingDir, filePath, plainText, truncated) {
86794
+ return exports_Effect.catchAll(exports_Effect.tryPromise(() => session2.backend.mutation(api.workspaceFiles.fulfillFileContentV2, {
86795
+ sessionId: session2.sessionId,
86796
+ machineId: session2.machineId,
86797
+ workingDir,
86798
+ filePath,
86799
+ data: { compression: "gzip", content: gzipPlainText(plainText) },
86800
+ encoding: "utf8",
86801
+ truncated
86802
+ })), () => exports_Effect.void);
85951
86803
  }
85952
86804
  var MAX_CONTENT_BYTES, BINARY_EXTENSIONS, fulfillFileContentRequestsEffect;
85953
86805
  var init_file_content_fulfillment = __esm(() => {
85954
86806
  init_esm();
85955
86807
  init_daemon_services();
85956
86808
  init_api3();
86809
+ init_assert_registered_working_dir();
86810
+ init_workspace_path_security();
86811
+ init_workspace_visibility_policy();
85957
86812
  MAX_CONTENT_BYTES = 500 * 1024;
85958
86813
  BINARY_EXTENSIONS = new Set([
85959
86814
  ".png",
@@ -86007,40 +86862,55 @@ var init_file_content_fulfillment = __esm(() => {
86007
86862
  for (const request2 of requests) {
86008
86863
  const startTime = Date.now();
86009
86864
  const { workingDir, filePath } = request2;
86010
- if (filePath.includes("..") || filePath.startsWith("/")) {
86011
- console.warn(`[${formatTimestamp()}] ⚠️ Rejected path traversal attempt: ${filePath}`);
86865
+ const registered = yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => assertRegisteredWorkingDir(session2, workingDir)), () => exports_Effect.succeed({ ok: false, error: "Workspace check failed" }));
86866
+ if (!registered.ok) {
86867
+ console.warn(`[${formatTimestamp()}] ⚠️ Rejected unregistered workspace: ${workingDir} (${registered.error})`);
86868
+ yield* fulfillGzippedContentEffect(session2, workingDir, filePath, "[Error: workspace not registered]", false);
86012
86869
  continue;
86013
86870
  }
86014
- const absolutePath = resolve4(workingDir, filePath);
86015
- if (!absolutePath.startsWith(resolve4(workingDir))) {
86016
- console.warn(`[${formatTimestamp()}] ⚠️ Path escapes workspace: ${absolutePath}`);
86871
+ const resolved = yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => resolvePathWithinWorkspace(workingDir, filePath)), () => exports_Effect.succeed({ ok: false, error: "Invalid file path" }));
86872
+ if (!resolved.ok) {
86873
+ console.warn(`[${formatTimestamp()}] ⚠️ Rejected file path: ${filePath} (${resolved.error})`);
86874
+ yield* fulfillGzippedContentEffect(session2, workingDir, filePath, "[Error reading file]", false);
86017
86875
  continue;
86018
86876
  }
86877
+ const absolutePath = resolved.absolutePath;
86019
86878
  if (isBinaryFile(filePath)) {
86020
- const binaryCompressed = gzipSync4(Buffer.from("[Binary file]")).toString("base64");
86021
- yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => session2.backend.mutation(api.workspaceFiles.fulfillFileContentV2, {
86022
- sessionId: session2.sessionId,
86023
- machineId: session2.machineId,
86024
- workingDir,
86025
- filePath,
86026
- data: { compression: "gzip", content: binaryCompressed },
86027
- encoding: "utf8",
86028
- truncated: false
86029
- })), () => exports_Effect.void);
86879
+ yield* fulfillGzippedContentEffect(session2, workingDir, filePath, "[Binary file]", false);
86030
86880
  const elapsed4 = Date.now() - startTime;
86031
86881
  console.log(`[${formatTimestamp()}] \uD83D\uDCC4 File content synced to Convex: ${filePath} [binary] (${elapsed4}ms)`);
86032
86882
  continue;
86033
86883
  }
86034
- const { content, truncated } = yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => readFile9(absolutePath)).pipe(exports_Effect.map((buffer) => {
86884
+ if (!isPathContentReadable(filePath)) {
86885
+ yield* fulfillGzippedContentEffect(session2, workingDir, filePath, "[File blocked: cannot open sensitive path in remote explorer]", false);
86886
+ const elapsed4 = Date.now() - startTime;
86887
+ console.log(`[${formatTimestamp()}] \uD83D\uDCC4 File content blocked: ${filePath} [secret] (${elapsed4}ms)`);
86888
+ continue;
86889
+ }
86890
+ const readOutcome = yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => readFile9(absolutePath)).pipe(exports_Effect.map((buffer) => {
86035
86891
  if (buffer.length > MAX_CONTENT_BYTES) {
86036
86892
  return {
86893
+ kind: "ok",
86037
86894
  content: buffer.subarray(0, MAX_CONTENT_BYTES).toString("utf8"),
86038
86895
  truncated: true
86039
86896
  };
86040
86897
  }
86041
- return { content: buffer.toString("utf8"), truncated: false };
86042
- })), () => exports_Effect.succeed({ content: "[Error reading file]", truncated: false }));
86043
- const compressed = gzipSync4(Buffer.from(content));
86898
+ return {
86899
+ kind: "ok",
86900
+ content: buffer.toString("utf8"),
86901
+ truncated: false
86902
+ };
86903
+ })), (error) => isENOENT(error) ? exports_Effect.succeed({ kind: "missing" }) : exports_Effect.succeed({
86904
+ kind: "error",
86905
+ content: "[Error reading file]",
86906
+ truncated: false
86907
+ }));
86908
+ if (readOutcome.kind === "missing") {
86909
+ console.log(`[${formatTimestamp()}] ⏳ File not on disk yet, deferring content sync: ${filePath}`);
86910
+ continue;
86911
+ }
86912
+ const { content, truncated } = readOutcome.kind === "ok" ? readOutcome : { content: readOutcome.content, truncated: readOutcome.truncated };
86913
+ const compressed = gzipSync6(Buffer.from(content));
86044
86914
  const contentCompressed = compressed.toString("base64");
86045
86915
  yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => session2.backend.mutation(api.workspaceFiles.fulfillFileContentV2, {
86046
86916
  sessionId: session2.sessionId,
@@ -86094,217 +86964,272 @@ var init_file_content_subscription = __esm(() => {
86094
86964
  init_convex_error();
86095
86965
  });
86096
86966
 
86097
- // src/infrastructure/services/workspace/file-tree-scanner.ts
86098
- import { exec as exec6 } from "node:child_process";
86099
- import { promises as fsPromises } from "node:fs";
86100
- import path3 from "node:path";
86101
- import { promisify as promisify5 } from "node:util";
86102
- async function scanFileTree(rootDir, options) {
86103
- const maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
86104
- const scannedAt = Date.now();
86105
- const filePaths = await isGitRepo2(rootDir) ? await getGitFiles(rootDir) : await walkFsFallback(rootDir, maxEntries, FS_FALLBACK_MAX_SUBTREE_BYTES);
86106
- const filteredPaths = filePaths.filter((p) => !isExcluded(p) && !matchesExcludePattern(p));
86107
- const entries2 = buildEntries(filteredPaths, rootDir, maxEntries);
86108
- return {
86109
- entries: entries2,
86110
- scannedAt,
86111
- rootDir
86112
- };
86967
+ // src/commands/machine/daemon-start/file-write-errors.ts
86968
+ function unsupportedFileWriteOperationMessage(operation) {
86969
+ 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).";
86970
+ }
86971
+
86972
+ // src/commands/machine/daemon-start/file-write-fulfillment.ts
86973
+ import { access as access5, mkdir as mkdir8, rename as rename3, rm as rm2, writeFile as writeFile7 } from "node:fs/promises";
86974
+ import { dirname as dirname9 } from "node:path";
86975
+ import { gzipSync as gzipSync7 } from "node:zlib";
86976
+ function isTerminalFileWriteError(errorMessage) {
86977
+ const terminalMessages = new Set([
86978
+ "Invalid file path",
86979
+ "Path escapes workspace",
86980
+ "Missing file data",
86981
+ "File content too large",
86982
+ "File already exists",
86983
+ "File does not exist",
86984
+ "Cannot delete workspace root",
86985
+ "Target path already exists",
86986
+ "Target path is required for rename",
86987
+ "Rename target must differ from source path",
86988
+ "Directory already exists",
86989
+ "Workspace not registered for this machine"
86990
+ ]);
86991
+ if (errorMessage.startsWith("Unsupported file write operation"))
86992
+ return true;
86993
+ return terminalMessages.has(errorMessage);
86994
+ }
86995
+ function parentDirPath2(filePath) {
86996
+ const idx = filePath.lastIndexOf("/");
86997
+ return idx === -1 ? "" : filePath.slice(0, idx);
86998
+ }
86999
+ async function syncParentDirListingAfterWrite(session2, workingDir, filePath) {
87000
+ const dirPath = parentDirPath2(filePath);
87001
+ const listing = await listDirectory(workingDir, dirPath);
87002
+ const json = JSON.stringify(listing);
87003
+ const dataHash = computeDirListingContentHash(listing);
87004
+ const compressed = gzipSync7(Buffer.from(json)).toString("base64");
87005
+ await session2.backend.mutation(api.workspaceFiles.syncDirListingV2, {
87006
+ sessionId: session2.sessionId,
87007
+ machineId: session2.machineId,
87008
+ workingDir,
87009
+ dirPath,
87010
+ data: { compression: "gzip", content: compressed },
87011
+ dataHash,
87012
+ scannedAt: listing.scannedAt,
87013
+ truncated: listing.truncated,
87014
+ totalCount: listing.totalCount
87015
+ });
86113
87016
  }
86114
- async function isGitRepo2(rootDir) {
86115
- try {
86116
- const { stdout } = await execAsync4("git rev-parse --is-inside-work-tree", {
86117
- cwd: rootDir,
86118
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_PAGER: "cat", NO_COLOR: "1" },
86119
- maxBuffer: 1024 * 1024
86120
- });
86121
- return stdout.trim() === "true";
86122
- } catch {
86123
- return false;
86124
- }
87017
+ async function completeWriteRequest(session2, requestId, result) {
87018
+ await session2.backend.mutation(api.workspaceFiles.completeFileWriteRequest, {
87019
+ sessionId: session2.sessionId,
87020
+ requestId,
87021
+ status: result.status,
87022
+ errorMessage: result.status === "error" ? result.errorMessage : undefined
87023
+ });
86125
87024
  }
86126
- async function walkFsFallback(rootDir, maxEntries, maxSubtreeBytes) {
86127
- const collected = [];
86128
- await walkSubtree(rootDir, "", collected, maxEntries, maxSubtreeBytes);
86129
- return collected;
87025
+ async function fileExistsAt(absolutePath) {
87026
+ return access5(absolutePath).then(() => true).catch(() => false);
86130
87027
  }
86131
- async function walkSubtree(absRoot, relDir, collected, maxEntries, maxSubtreeBytes) {
86132
- if (collected.length >= maxEntries)
86133
- return 0;
86134
- const absDir = relDir ? path3.join(absRoot, relDir) : absRoot;
86135
- let dirents;
86136
- try {
86137
- dirents = await fsPromises.readdir(absDir, { withFileTypes: true });
86138
- } catch {
86139
- return 0;
87028
+ function decodeWritePayload(request2) {
87029
+ if (!request2.data) {
87030
+ return { ok: false, errorMessage: "Missing file data" };
86140
87031
  }
86141
- const staged = [];
86142
- let subtreeBytes = 0;
86143
- for (const ent of dirents) {
86144
- if (collected.length + staged.length >= maxEntries)
86145
- break;
86146
- if (ALWAYS_EXCLUDE.has(ent.name))
86147
- continue;
86148
- const childRel = relDir ? `${relDir}/${ent.name}` : ent.name;
86149
- if (ent.isDirectory()) {
86150
- const subCollected = [];
86151
- const subSize = await walkSubtree(absRoot, childRel, subCollected, maxEntries - collected.length - staged.length, maxSubtreeBytes);
86152
- if (subSize === null) {
86153
- continue;
86154
- }
86155
- subtreeBytes += subSize;
86156
- if (subtreeBytes > maxSubtreeBytes) {
86157
- return null;
86158
- }
86159
- staged.push(...subCollected);
86160
- } else if (ent.isFile()) {
86161
- try {
86162
- const st = await fsPromises.stat(path3.join(absRoot, childRel));
86163
- subtreeBytes += st.size;
86164
- if (subtreeBytes > maxSubtreeBytes) {
86165
- return null;
86166
- }
86167
- staged.push(childRel);
86168
- } catch {}
86169
- }
87032
+ return gunzipBase64Payload(request2.data.content, MAX_CONTENT_BYTES2);
87033
+ }
87034
+ async function validateWriteOperation(operation, absolutePath) {
87035
+ const exists3 = await fileExistsAt(absolutePath);
87036
+ if (operation === "create" && exists3) {
87037
+ return { ok: false, errorMessage: "File already exists" };
86170
87038
  }
86171
- for (const p of staged) {
86172
- if (collected.length >= maxEntries)
86173
- break;
86174
- collected.push(p);
87039
+ if (operation === "update" && !exists3) {
87040
+ return { ok: false, errorMessage: "File does not exist" };
86175
87041
  }
86176
- return subtreeBytes;
86177
- }
86178
- async function getGitFiles(rootDir) {
86179
- const env2 = {
86180
- ...process.env,
86181
- GIT_TERMINAL_PROMPT: "0",
86182
- GIT_PAGER: "cat",
86183
- NO_COLOR: "1"
86184
- };
86185
- try {
86186
- const tracked = await execAsync4("git ls-files", {
86187
- cwd: rootDir,
86188
- env: env2,
86189
- maxBuffer: 10 * 1024 * 1024
86190
- });
86191
- const untracked = await execAsync4("git ls-files --others --exclude-standard", {
86192
- cwd: rootDir,
86193
- env: env2,
86194
- maxBuffer: 10 * 1024 * 1024
86195
- });
86196
- const trackedFiles = parseLines(tracked.stdout);
86197
- const untrackedFiles = parseLines(untracked.stdout);
86198
- const allFiles = new Set([...trackedFiles, ...untrackedFiles]);
86199
- return Array.from(allFiles);
86200
- } catch {
86201
- return [];
87042
+ if (operation === "delete" && !exists3) {
87043
+ return { ok: false, errorMessage: "File does not exist" };
86202
87044
  }
87045
+ return { ok: true };
86203
87046
  }
86204
- function parseLines(output) {
86205
- return output.split(`
86206
- `).map((line) => line.trim()).filter((line) => line.length > 0);
86207
- }
86208
- function isExcluded(filePath) {
86209
- const segments = filePath.split("/");
86210
- return segments.some((segment) => ALWAYS_EXCLUDE.has(segment));
86211
- }
86212
- function matchesExcludePattern(filePath) {
86213
- return EXCLUDE_PATTERNS.some((pattern) => pattern.test(filePath));
87047
+ async function writePayloadToDisk(absolutePath, operation, content) {
87048
+ if (operation === "create") {
87049
+ await mkdir8(dirname9(absolutePath), { recursive: true });
87050
+ }
87051
+ await writeFile7(absolutePath, content);
86214
87052
  }
86215
- function buildEntries(filePaths, rootDir, maxEntries) {
86216
- const directories = new Set;
86217
- for (const filePath of filePaths) {
86218
- const parts2 = filePath.split("/");
86219
- for (let i2 = 1;i2 < parts2.length; i2++) {
86220
- directories.add(parts2.slice(0, i2).join("/"));
86221
- }
87053
+ async function fulfillOneFileWriteRequest(session2, request2) {
87054
+ const startTime = Date.now();
87055
+ const { workingDir, filePath, operation } = request2;
87056
+ const registered = await assertRegisteredWorkingDir(session2, workingDir);
87057
+ if (!registered.ok) {
87058
+ await completeWriteRequest(session2, request2._id, {
87059
+ status: "error",
87060
+ errorMessage: registered.error
87061
+ });
87062
+ return;
86222
87063
  }
86223
- const entries2 = [];
86224
- const sortedDirs = Array.from(directories).sort();
86225
- for (const dir of sortedDirs) {
86226
- if (entries2.length >= maxEntries)
86227
- break;
86228
- entries2.push({ path: dir, type: "directory" });
87064
+ const resolved = await resolvePathWithinWorkspace(workingDir, filePath);
87065
+ if (!resolved.ok) {
87066
+ await completeWriteRequest(session2, request2._id, {
87067
+ status: "error",
87068
+ errorMessage: resolved.error
87069
+ });
87070
+ return;
86229
87071
  }
86230
- const sortedFiles = filePaths.slice().sort();
86231
- for (const file of sortedFiles) {
86232
- if (entries2.length >= maxEntries)
86233
- break;
86234
- entries2.push({ path: file, type: "file" });
87072
+ try {
87073
+ if (operation === "rename") {
87074
+ if (!request2.targetFilePath) {
87075
+ await completeWriteRequest(session2, request2._id, {
87076
+ status: "error",
87077
+ errorMessage: "Target path is required for rename"
87078
+ });
87079
+ return;
87080
+ }
87081
+ if (request2.targetFilePath === filePath) {
87082
+ await completeWriteRequest(session2, request2._id, {
87083
+ status: "error",
87084
+ errorMessage: "Rename target must differ from source path"
87085
+ });
87086
+ return;
87087
+ }
87088
+ const targetResolved = await resolvePathWithinWorkspace(workingDir, request2.targetFilePath);
87089
+ if (!targetResolved.ok) {
87090
+ await completeWriteRequest(session2, request2._id, {
87091
+ status: "error",
87092
+ errorMessage: targetResolved.error
87093
+ });
87094
+ return;
87095
+ }
87096
+ const sourceExists = await fileExistsAt(resolved.absolutePath);
87097
+ if (!sourceExists) {
87098
+ await completeWriteRequest(session2, request2._id, {
87099
+ status: "error",
87100
+ errorMessage: "File does not exist"
87101
+ });
87102
+ return;
87103
+ }
87104
+ const targetExists = await fileExistsAt(targetResolved.absolutePath);
87105
+ if (targetExists) {
87106
+ await completeWriteRequest(session2, request2._id, {
87107
+ status: "error",
87108
+ errorMessage: "Target path already exists"
87109
+ });
87110
+ return;
87111
+ }
87112
+ await mkdir8(dirname9(targetResolved.absolutePath), { recursive: true });
87113
+ await rename3(resolved.absolutePath, targetResolved.absolutePath);
87114
+ await syncParentDirListingAfterWrite(session2, workingDir, filePath);
87115
+ if (parentDirPath2(filePath) !== parentDirPath2(request2.targetFilePath)) {
87116
+ await syncParentDirListingAfterWrite(session2, workingDir, request2.targetFilePath);
87117
+ }
87118
+ await completeWriteRequest(session2, request2._id, { status: "done" });
87119
+ const elapsed4 = Date.now() - startTime;
87120
+ console.log(`[${formatTimestamp()}] ✏️ File rename fulfilled: ${filePath} → ${request2.targetFilePath} (${elapsed4}ms)`);
87121
+ return;
87122
+ }
87123
+ if (operation === "mkdir") {
87124
+ const exists3 = await fileExistsAt(resolved.absolutePath);
87125
+ if (exists3) {
87126
+ await completeWriteRequest(session2, request2._id, {
87127
+ status: "error",
87128
+ errorMessage: "Directory already exists"
87129
+ });
87130
+ return;
87131
+ }
87132
+ await mkdir8(resolved.absolutePath, { recursive: true });
87133
+ await syncParentDirListingAfterWrite(session2, workingDir, filePath);
87134
+ await completeWriteRequest(session2, request2._id, { status: "done" });
87135
+ const elapsed4 = Date.now() - startTime;
87136
+ console.log(`[${formatTimestamp()}] ✏️ Directory mkdir fulfilled: ${filePath} (${elapsed4}ms)`);
87137
+ return;
87138
+ }
87139
+ if (operation === "delete") {
87140
+ if (filePath === "") {
87141
+ await completeWriteRequest(session2, request2._id, {
87142
+ status: "error",
87143
+ errorMessage: "Cannot delete workspace root"
87144
+ });
87145
+ return;
87146
+ }
87147
+ const operationCheck2 = await validateWriteOperation(operation, resolved.absolutePath);
87148
+ if (!operationCheck2.ok) {
87149
+ await completeWriteRequest(session2, request2._id, {
87150
+ status: "error",
87151
+ errorMessage: operationCheck2.errorMessage
87152
+ });
87153
+ return;
87154
+ }
87155
+ await rm2(resolved.absolutePath, { recursive: true, force: false });
87156
+ await syncParentDirListingAfterWrite(session2, workingDir, filePath);
87157
+ await completeWriteRequest(session2, request2._id, { status: "done" });
87158
+ const elapsed4 = Date.now() - startTime;
87159
+ console.log(`[${formatTimestamp()}] ✏️ File delete fulfilled: ${filePath} (${elapsed4}ms)`);
87160
+ return;
87161
+ }
87162
+ if (operation !== "create" && operation !== "update") {
87163
+ await completeWriteRequest(session2, request2._id, {
87164
+ status: "error",
87165
+ errorMessage: unsupportedFileWriteOperationMessage(operation)
87166
+ });
87167
+ return;
87168
+ }
87169
+ const payload = decodeWritePayload(request2);
87170
+ if (!payload.ok) {
87171
+ await completeWriteRequest(session2, request2._id, {
87172
+ status: "error",
87173
+ errorMessage: payload.errorMessage
87174
+ });
87175
+ return;
87176
+ }
87177
+ const operationCheck = await validateWriteOperation(operation, resolved.absolutePath);
87178
+ if (!operationCheck.ok) {
87179
+ await completeWriteRequest(session2, request2._id, {
87180
+ status: "error",
87181
+ errorMessage: operationCheck.errorMessage
87182
+ });
87183
+ return;
87184
+ }
87185
+ await writePayloadToDisk(resolved.absolutePath, operation, payload.content);
87186
+ await syncParentDirListingAfterWrite(session2, workingDir, filePath);
87187
+ await completeWriteRequest(session2, request2._id, { status: "done" });
87188
+ const elapsed3 = Date.now() - startTime;
87189
+ console.log(`[${formatTimestamp()}] ✏️ File write fulfilled: ${filePath} (${operation}, ${(payload.content.length / 1024).toFixed(1)}KB, ${elapsed3}ms)`);
87190
+ } catch (err) {
87191
+ const message = err instanceof Error ? err.message : "Write failed";
87192
+ if (isTerminalFileWriteError(message)) {
87193
+ await completeWriteRequest(session2, request2._id, {
87194
+ status: "error",
87195
+ errorMessage: message
87196
+ });
87197
+ console.warn(`[${formatTimestamp()}] ⚠️ File write failed for ${filePath}: ${message}`);
87198
+ return;
87199
+ }
87200
+ console.warn(`[${formatTimestamp()}] ⚠️ File write transient failure for ${filePath}: ${message} (will retry)`);
86235
87201
  }
86236
- return entries2;
86237
87202
  }
86238
- var execAsync4, DEFAULT_MAX_ENTRIES = 1e4, FS_FALLBACK_MAX_SUBTREE_BYTES, ALWAYS_EXCLUDE, EXCLUDE_PATTERNS;
86239
- var init_file_tree_scanner = __esm(() => {
86240
- execAsync4 = promisify5(exec6);
86241
- FS_FALLBACK_MAX_SUBTREE_BYTES = 50 * 1024 * 1024;
86242
- ALWAYS_EXCLUDE = new Set([
86243
- "node_modules",
86244
- ".git",
86245
- "dist",
86246
- "build",
86247
- ".next",
86248
- "coverage",
86249
- "__pycache__",
86250
- ".turbo",
86251
- ".cache",
86252
- ".tmp",
86253
- "tmp",
86254
- ".DS_Store"
86255
- ]);
86256
- EXCLUDE_PATTERNS = [
86257
- /node_modules/i,
86258
- /\.git/i,
86259
- /dist/i,
86260
- /build/i,
86261
- /\.next/i,
86262
- /coverage/i,
86263
- /__pycache__/i,
86264
- /\.turbo/i,
86265
- /\.cache/i,
86266
- /\.tmp/i,
86267
- /tmp/i,
86268
- /\.DS_Store/i
86269
- ];
87203
+ var MAX_CONTENT_BYTES2, fulfillFileWriteRequestsEffect;
87204
+ var init_file_write_fulfillment = __esm(() => {
87205
+ init_esm();
87206
+ init_daemon_services();
87207
+ init_api3();
87208
+ init_assert_registered_working_dir();
87209
+ init_dir_listing_content_hash();
87210
+ init_dir_listing_scanner();
87211
+ init_workspace_path_security();
87212
+ MAX_CONTENT_BYTES2 = 512 * 1024;
87213
+ fulfillFileWriteRequestsEffect = exports_Effect.gen(function* () {
87214
+ const session2 = yield* DaemonSessionService;
87215
+ const requests = yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => session2.backend.query(api.workspaceFiles.getPendingFileWriteRequests, {
87216
+ sessionId: session2.sessionId,
87217
+ machineId: session2.machineId
87218
+ })), () => exports_Effect.succeed([]));
87219
+ if (requests.length === 0)
87220
+ return;
87221
+ console.log(`[${formatTimestamp()}] ✏️ Received ${requests.length} pending file write request(s): ${requests.map((r) => r.filePath).join(", ")}`);
87222
+ for (const request2 of requests) {
87223
+ yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => fulfillOneFileWriteRequest(session2, request2)), () => exports_Effect.void);
87224
+ }
87225
+ });
86270
87226
  });
86271
87227
 
86272
- // src/commands/machine/daemon-start/file-tree-subscription.ts
86273
- import { createHash as createHash6 } from "node:crypto";
86274
- import { gzipSync as gzipSync5 } from "node:zlib";
86275
- var fulfillFileTreeRequestsEffect = (session2, requests) => exports_Effect.gen(function* () {
86276
- for (const request2 of requests) {
86277
- const startTime = Date.now();
86278
- yield* exports_Effect.catchAll(exports_Effect.gen(function* () {
86279
- const tree = yield* exports_Effect.tryPromise(() => scanFileTree(request2.workingDir));
86280
- const treeJson = JSON.stringify(tree);
86281
- const treeHash = createHash6("md5").update(treeJson).digest("hex");
86282
- const compressed = gzipSync5(Buffer.from(treeJson));
86283
- const treeJsonCompressed = compressed.toString("base64");
86284
- yield* exports_Effect.tryPromise(() => session2.backend.mutation(api.workspaceFiles.syncFileTreeV2, {
86285
- sessionId: session2.sessionId,
86286
- machineId: session2.machineId,
86287
- workingDir: request2.workingDir,
86288
- data: { compression: "gzip", content: treeJsonCompressed },
86289
- dataHash: treeHash,
86290
- scannedAt: tree.scannedAt
86291
- }));
86292
- yield* exports_Effect.tryPromise(() => session2.backend.mutation(api.workspaceFiles.fulfillFileTreeRequest, {
86293
- sessionId: session2.sessionId,
86294
- machineId: session2.machineId,
86295
- workingDir: request2.workingDir
86296
- }));
86297
- const elapsed3 = Date.now() - startTime;
86298
- console.log(`[${formatTimestamp()}] \uD83C\uDF33 File tree fulfilled: ${request2.workingDir} (${tree.entries.length} entries, ${(Buffer.byteLength(treeJson) / 1024).toFixed(1)}KB → ${(compressed.length / 1024).toFixed(1)}KB gzip, ${elapsed3}ms)`);
86299
- }), (err) => {
86300
- console.warn(`[${formatTimestamp()}] ⚠️ File tree fulfillment failed for ${request2.workingDir}: ${getErrorMessage(err)}`);
86301
- return exports_Effect.void;
86302
- });
86303
- }
86304
- }), startFileTreeSubscriptionEffect = (wsClient2) => exports_Effect.gen(function* () {
87228
+ // src/commands/machine/daemon-start/file-write-subscription.ts
87229
+ var startFileWriteSubscriptionEffect = (wsClient2) => exports_Effect.gen(function* () {
86305
87230
  const session2 = yield* DaemonSessionService;
86306
87231
  let processing = false;
86307
- const unsubscribe = wsClient2.onUpdate(api.workspaceFiles.getPendingFileTreeRequests, {
87232
+ const unsubscribe = wsClient2.onUpdate(api.workspaceFiles.getPendingFileWriteRequests, {
86308
87233
  sessionId: session2.sessionId,
86309
87234
  machineId: session2.machineId
86310
87235
  }, (requests) => {
@@ -86313,27 +87238,27 @@ var fulfillFileTreeRequestsEffect = (session2, requests) => exports_Effect.gen(f
86313
87238
  if (processing)
86314
87239
  return;
86315
87240
  processing = true;
86316
- exports_Effect.runPromise(fulfillFileTreeRequestsEffect(session2, requests)).catch((err) => {
86317
- console.warn(`[${formatTimestamp()}] ⚠️ File tree subscription processing failed: ${getErrorMessage(err)}`);
87241
+ exports_Effect.runPromise(fulfillFileWriteRequestsEffect.pipe(exports_Effect.provideService(DaemonSessionService, session2))).catch((err) => {
87242
+ console.warn(`[${formatTimestamp()}] ⚠️ File write subscription processing failed: ${getErrorMessage(err)}`);
86318
87243
  }).finally(() => {
86319
87244
  processing = false;
86320
87245
  });
86321
87246
  }, (err) => {
86322
- console.warn(`[${formatTimestamp()}] ⚠️ File tree subscription error: ${getErrorMessage(err)}`);
87247
+ console.warn(`[${formatTimestamp()}] ⚠️ File write subscription error: ${getErrorMessage(err)}`);
86323
87248
  });
86324
- console.log(`[${formatTimestamp()}] \uD83C\uDF33 File tree subscription started (reactive)`);
87249
+ console.log(`[${formatTimestamp()}] ✏️ File write subscription started (reactive)`);
86325
87250
  return {
86326
87251
  stop: () => {
86327
87252
  unsubscribe();
86328
- console.log(`[${formatTimestamp()}] \uD83C\uDF33 File tree subscription stopped`);
87253
+ console.log(`[${formatTimestamp()}] ✏️ File write subscription stopped`);
86329
87254
  }
86330
87255
  };
86331
87256
  });
86332
- var init_file_tree_subscription = __esm(() => {
87257
+ var init_file_write_subscription = __esm(() => {
86333
87258
  init_esm();
86334
87259
  init_daemon_services();
87260
+ init_file_write_fulfillment();
86335
87261
  init_api3();
86336
- init_file_tree_scanner();
86337
87262
  init_convex_error();
86338
87263
  });
86339
87264
 
@@ -88805,11 +89730,11 @@ class AgentProcessManager {
88805
89730
  const initPrompt = await this.fetchInitPromptResult(opts, slot);
88806
89731
  if (!initPrompt.ok)
88807
89732
  return initPrompt.result;
88808
- const spawn5 = await this.spawnAgentForEnsureRunning(key, slot, opts, initPrompt, wantResume);
88809
- if (!spawn5.ok)
88810
- return spawn5.result;
88811
- await this.finalizeRunningSlot(key, slot, opts, spawn5.spawnResult, wantResume);
88812
- return { success: true, pid: spawn5.spawnResult.pid };
89733
+ const spawn7 = await this.spawnAgentForEnsureRunning(key, slot, opts, initPrompt, wantResume);
89734
+ if (!spawn7.ok)
89735
+ return spawn7.result;
89736
+ await this.finalizeRunningSlot(key, slot, opts, spawn7.spawnResult, wantResume);
89737
+ return { success: true, pid: spawn7.spawnResult.pid };
88813
89738
  } catch (e) {
88814
89739
  this.resetSlotIdle(slot);
88815
89740
  return { success: false, error: `Unexpected error: ${e.message}` };
@@ -90016,10 +90941,10 @@ function mergeDefs(...defs) {
90016
90941
  function cloneDef(schema) {
90017
90942
  return mergeDefs(schema._zod.def);
90018
90943
  }
90019
- function getElementAtPath(obj, path4) {
90020
- if (!path4)
90944
+ function getElementAtPath(obj, path6) {
90945
+ if (!path6)
90021
90946
  return obj;
90022
- return path4.reduce((acc, key) => acc?.[key], obj);
90947
+ return path6.reduce((acc, key) => acc?.[key], obj);
90023
90948
  }
90024
90949
  function promiseAllObject(promisesObj) {
90025
90950
  const keys3 = Object.keys(promisesObj);
@@ -90347,11 +91272,11 @@ function explicitlyAborted(x, startIndex = 0) {
90347
91272
  }
90348
91273
  return false;
90349
91274
  }
90350
- function prefixIssues(path4, issues) {
91275
+ function prefixIssues(path6, issues) {
90351
91276
  return issues.map((iss) => {
90352
91277
  var _a3;
90353
91278
  (_a3 = iss).path ?? (_a3.path = []);
90354
- iss.path.unshift(path4);
91279
+ iss.path.unshift(path6);
90355
91280
  return iss;
90356
91281
  });
90357
91282
  }
@@ -90564,16 +91489,16 @@ function flattenError(error, mapper = (issue2) => issue2.message) {
90564
91489
  }
90565
91490
  function formatError2(error, mapper = (issue2) => issue2.message) {
90566
91491
  const fieldErrors = { _errors: [] };
90567
- const processError = (error2, path4 = []) => {
91492
+ const processError = (error2, path6 = []) => {
90568
91493
  for (const issue2 of error2.issues) {
90569
91494
  if (issue2.code === "invalid_union" && issue2.errors.length) {
90570
- issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
91495
+ issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
90571
91496
  } else if (issue2.code === "invalid_key") {
90572
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
91497
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
90573
91498
  } else if (issue2.code === "invalid_element") {
90574
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
91499
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
90575
91500
  } else {
90576
- const fullpath = [...path4, ...issue2.path];
91501
+ const fullpath = [...path6, ...issue2.path];
90577
91502
  if (fullpath.length === 0) {
90578
91503
  fieldErrors._errors.push(mapper(issue2));
90579
91504
  } else {
@@ -90600,17 +91525,17 @@ function formatError2(error, mapper = (issue2) => issue2.message) {
90600
91525
  }
90601
91526
  function treeifyError(error, mapper = (issue2) => issue2.message) {
90602
91527
  const result = { errors: [] };
90603
- const processError = (error2, path4 = []) => {
91528
+ const processError = (error2, path6 = []) => {
90604
91529
  var _a3, _b2;
90605
91530
  for (const issue2 of error2.issues) {
90606
91531
  if (issue2.code === "invalid_union" && issue2.errors.length) {
90607
- issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
91532
+ issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
90608
91533
  } else if (issue2.code === "invalid_key") {
90609
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
91534
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
90610
91535
  } else if (issue2.code === "invalid_element") {
90611
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
91536
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
90612
91537
  } else {
90613
- const fullpath = [...path4, ...issue2.path];
91538
+ const fullpath = [...path6, ...issue2.path];
90614
91539
  if (fullpath.length === 0) {
90615
91540
  result.errors.push(mapper(issue2));
90616
91541
  continue;
@@ -90642,8 +91567,8 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
90642
91567
  }
90643
91568
  function toDotPath(_path) {
90644
91569
  const segs = [];
90645
- const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
90646
- for (const seg of path4) {
91570
+ const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
91571
+ for (const seg of path6) {
90647
91572
  if (typeof seg === "number")
90648
91573
  segs.push(`[${seg}]`);
90649
91574
  else if (typeof seg === "symbol")
@@ -103646,13 +104571,13 @@ function resolveRef(ref, ctx) {
103646
104571
  if (!ref.startsWith("#")) {
103647
104572
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
103648
104573
  }
103649
- const path4 = ref.slice(1).split("/").filter(Boolean);
103650
- if (path4.length === 0) {
104574
+ const path6 = ref.slice(1).split("/").filter(Boolean);
104575
+ if (path6.length === 0) {
103651
104576
  return ctx.rootSchema;
103652
104577
  }
103653
104578
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
103654
- if (path4[0] === defsKey) {
103655
- const key = path4[1];
104579
+ if (path6[0] === defsKey) {
104580
+ const key = path6[1];
103656
104581
  if (!key || !ctx.defs[key]) {
103657
104582
  throw new Error(`Reference not found: ${ref}`);
103658
104583
  }
@@ -104460,6 +105385,7 @@ var init_assigned_task_monitor_contract = __esm(() => {
104460
105385
  workingDir: exports_external.string().optional(),
104461
105386
  assignedTo: exports_external.string().optional(),
104462
105387
  lastSeenAction: exports_external.string().nullable().optional(),
105388
+ lastStatus: exports_external.string().nullable().optional(),
104463
105389
  spawnedAgentPid: exports_external.number().optional(),
104464
105390
  desiredState: exports_external.string().optional(),
104465
105391
  sessionAugmentation: sessionAugmentationSchema.optional()
@@ -104621,7 +105547,7 @@ function bootstrapMonitorRowFromSignal(signal) {
104621
105547
  participant: {
104622
105548
  lastSeenAction: signal.lastSeenAction ?? null,
104623
105549
  lastSeenAt: null,
104624
- lastStatus: null
105550
+ lastStatus: signal.lastStatus ?? null
104625
105551
  }
104626
105552
  };
104627
105553
  }
@@ -104637,7 +105563,7 @@ function patchMonitorRowFromSignal(existing, signal) {
104637
105563
  participant: {
104638
105564
  lastSeenAction: signal.lastSeenAction ?? existing.participant?.lastSeenAction ?? null,
104639
105565
  lastSeenAt: existing.participant?.lastSeenAt ?? null,
104640
- lastStatus: existing.participant?.lastStatus ?? null
105566
+ lastStatus: signal.lastStatus ?? existing.participant?.lastStatus ?? null
104641
105567
  }
104642
105568
  };
104643
105569
  }
@@ -105588,6 +106514,8 @@ var init_command_loop = __esm(() => {
105588
106514
  init_featureFlags();
105589
106515
  init_reliability();
105590
106516
  init_esm();
106517
+ init_dir_listing_subscription();
106518
+ init_dir_listing_watch_subscription();
105591
106519
  init_api3();
105592
106520
  init_on_request_start_agent();
105593
106521
  init_on_request_stop_agent();
@@ -105601,7 +106529,7 @@ var init_command_loop = __esm(() => {
105601
106529
  init_daemon_services();
105602
106530
  init_start_subscriptions();
105603
106531
  init_file_content_subscription();
105604
- init_file_tree_subscription();
106532
+ init_file_write_subscription();
105605
106533
  init_git_heartbeat();
105606
106534
  init_git_subscription();
105607
106535
  init_command_runner();
@@ -105647,7 +106575,9 @@ var init_command_loop = __esm(() => {
105647
106575
  heartbeatTimer.unref();
105648
106576
  let gitSubscriptionHandle = null;
105649
106577
  let fileContentSubscriptionHandle = null;
105650
- let fileTreeSubscriptionHandle = null;
106578
+ let fileWriteSubscriptionHandle = null;
106579
+ let dirListingSubscriptionHandle = null;
106580
+ let dirListingWatchSubscriptionHandle = null;
105651
106581
  let workspaceListSubscriptionHandle = null;
105652
106582
  let observedSyncSubscriptionHandle = null;
105653
106583
  let logObserverSubscriptionHandle = null;
@@ -105715,7 +106645,9 @@ var init_command_loop = __esm(() => {
105715
106645
  const stopSubscriptions = () => {
105716
106646
  gitSubscriptionHandle?.stop();
105717
106647
  fileContentSubscriptionHandle?.stop();
105718
- fileTreeSubscriptionHandle?.stop();
106648
+ fileWriteSubscriptionHandle?.stop();
106649
+ dirListingSubscriptionHandle?.stop();
106650
+ dirListingWatchSubscriptionHandle?.stop();
105719
106651
  workspaceListSubscriptionHandle?.stop();
105720
106652
  observedSyncSubscriptionHandle?.stop();
105721
106653
  taskMonitorHandle?.stop();
@@ -105759,7 +106691,9 @@ var init_command_loop = __esm(() => {
105759
106691
  const wsClient2 = yield* exports_Effect.promise(() => getConvexWsClient());
105760
106692
  gitSubscriptionHandle = yield* startGitRequestSubscriptionEffect(wsClient2);
105761
106693
  fileContentSubscriptionHandle = yield* startFileContentSubscriptionEffect(wsClient2);
105762
- fileTreeSubscriptionHandle = yield* startFileTreeSubscriptionEffect(wsClient2);
106694
+ fileWriteSubscriptionHandle = yield* startFileWriteSubscriptionEffect(wsClient2);
106695
+ dirListingSubscriptionHandle = yield* startDirListingSubscriptionEffect(wsClient2);
106696
+ dirListingWatchSubscriptionHandle = yield* startDirListingWatchSubscriptionEffect(wsClient2);
105763
106697
  workspaceListSubscriptionHandle = yield* startWorkspaceListSubscriptionEffect(wsClient2);
105764
106698
  if (observedSyncEnabled) {
105765
106699
  observedSyncSubscriptionHandle = yield* startObservedSyncSubscriptionEffect(wsClient2);
@@ -105961,7 +106895,7 @@ __export(exports_opencode_install, {
105961
106895
  installTool: () => installTool
105962
106896
  });
105963
106897
  import * as os2 from "os";
105964
- import * as path4 from "path";
106898
+ import * as path6 from "path";
105965
106899
  async function isChatroomInstalledDefault() {
105966
106900
  try {
105967
106901
  const { execSync: execSync3 } = await import("child_process");
@@ -106331,9 +107265,9 @@ After logging in, try this command again.\`;
106331
107265
  const fsService = yield* OpenCodeInstallFsService;
106332
107266
  const { checkExisting = true } = options;
106333
107267
  const homeDir = os2.homedir();
106334
- const toolDir = path4.join(homeDir, ".config", "opencode", "tool");
106335
- const toolPath = path4.join(toolDir, "chatroom.ts");
106336
- const handoffToolPath = path4.join(toolDir, "chatroom-handoff.ts");
107268
+ const toolDir = path6.join(homeDir, ".config", "opencode", "tool");
107269
+ const toolPath = path6.join(toolDir, "chatroom.ts");
107270
+ const handoffToolPath = path6.join(toolDir, "chatroom-handoff.ts");
106337
107271
  if (checkExisting) {
106338
107272
  const existingFiles = [];
106339
107273
  const toolExists = yield* fsService.access(toolPath);
@@ -106888,4 +107822,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
106888
107822
  });
106889
107823
  program2.parse();
106890
107824
 
106891
- //# debugId=B4F2E215BBCC825564756E2164756E21
107825
+ //# debugId=1F6FB9F04F102BC664756E2164756E21