chatroom-cli 1.59.1 → 1.60.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -76485,6 +76485,14 @@ Keep one slice ≈ one focused review surface. Delegate slices incrementally —
76485
76485
  }
76486
76486
  var init_planner_to_builder = () => {};
76487
76487
 
76488
+ // ../../services/backend/prompts/utils/unresolved-decisions.ts
76489
+ function getUnresolvedDecisionsSectionBlock() {
76490
+ return `## Unresolved Decisions
76491
+ <!-- Decisions that need user input before work can proceed. -->
76492
+ - <decision or question — options considered, recommendation if any, or "Not Applicable">
76493
+ <Carry forward decisions still open from earlier handoffs in this chatroom. Remove items the user has resolved. Do not decide on the user's behalf unless they explicitly asked you to. Write \`Not Applicable\` only when there are truly no open decisions.>`;
76494
+ }
76495
+
76488
76496
  // ../../services/backend/prompts/teams/duo/handoff-templates/planner-to-user.ts
76489
76497
  function getPlannerToUserReportTemplate(roleGuidanceContext) {
76490
76498
  return `${getHandoffRecipientVisibilityCallout("user")}
@@ -76549,6 +76557,8 @@ flowchart TD
76549
76557
  ## Code Change Verification
76550
76558
  ${CODE_CHANGE_VERIFICATION_CONFIRMATION}
76551
76559
 
76560
+ ${getUnresolvedDecisionsSectionBlock()}
76561
+
76552
76562
  ## Notes / Next steps
76553
76563
  <anything the user should know, follow-ups, or open questions, or "Not Applicable">
76554
76564
  \`\`\``;
@@ -76647,6 +76657,8 @@ flowchart TD
76647
76657
  ## Code Change Verification
76648
76658
  ${CODE_CHANGE_VERIFICATION_CONFIRMATION}
76649
76659
 
76660
+ ${getUnresolvedDecisionsSectionBlock()}
76661
+
76650
76662
  ## Notes / Next steps
76651
76663
  <anything the user should know, follow-ups, or open questions, or "Not Applicable">
76652
76664
  \`\`\``;
@@ -79917,6 +79929,765 @@ var init_daemon_services = __esm(() => {
79917
79929
  };
79918
79930
  });
79919
79931
 
79932
+ // src/commands/machine/daemon-start/utils.ts
79933
+ function formatTimestamp() {
79934
+ return new Date().toISOString().replace("T", " ").substring(0, 19);
79935
+ }
79936
+
79937
+ // src/infrastructure/services/workspace/dir-listing-content-hash.ts
79938
+ import { createHash as createHash2 } from "node:crypto";
79939
+ function computeDirListingContentHash(listing) {
79940
+ const payload = {
79941
+ entries: listing.entries,
79942
+ truncated: listing.truncated,
79943
+ totalCount: listing.totalCount
79944
+ };
79945
+ return createHash2("md5").update(JSON.stringify(payload)).digest("hex");
79946
+ }
79947
+ var init_dir_listing_content_hash = () => {};
79948
+
79949
+ // src/infrastructure/services/workspace/workspace-path-security.ts
79950
+ import { realpath } from "node:fs/promises";
79951
+ import { basename, dirname as dirname8, isAbsolute, relative, resolve as resolve3, sep } from "node:path";
79952
+ import { gunzipSync } from "node:zlib";
79953
+ function validateRelativePathSegments(filePath) {
79954
+ if (filePath.includes("\x00"))
79955
+ return { ok: false, error: "Invalid file path" };
79956
+ if (filePath.includes(".."))
79957
+ return { ok: false, error: "Invalid file path" };
79958
+ if (filePath.startsWith("/"))
79959
+ return { ok: false, error: "Invalid file path" };
79960
+ return { ok: true };
79961
+ }
79962
+ function isPathInsideRoot(workspaceRoot, targetPath) {
79963
+ const rel = relative(workspaceRoot, targetPath);
79964
+ if (rel === "")
79965
+ return true;
79966
+ if (rel.startsWith("..") || rel.includes(`..${sep}`))
79967
+ return false;
79968
+ if (isAbsolute(rel))
79969
+ return false;
79970
+ return true;
79971
+ }
79972
+ async function resolvePathWithinWorkspace(workingDir, filePath) {
79973
+ const basic = validateRelativePathSegments(filePath);
79974
+ if (!basic.ok)
79975
+ return basic;
79976
+ let workspaceRoot;
79977
+ try {
79978
+ workspaceRoot = await realpath(resolve3(workingDir));
79979
+ } catch {
79980
+ return { ok: false, error: "Working directory not found" };
79981
+ }
79982
+ const candidate = resolve3(workspaceRoot, filePath);
79983
+ if (!isPathInsideRoot(workspaceRoot, candidate)) {
79984
+ return { ok: false, error: "Path escapes workspace" };
79985
+ }
79986
+ try {
79987
+ const resolved = await realpath(candidate);
79988
+ if (!isPathInsideRoot(workspaceRoot, resolved)) {
79989
+ return { ok: false, error: "Path escapes workspace" };
79990
+ }
79991
+ return { ok: true, absolutePath: resolved };
79992
+ } catch (err) {
79993
+ const code2 = err?.code;
79994
+ if (code2 !== "ENOENT") {
79995
+ return { ok: false, error: "Invalid file path" };
79996
+ }
79997
+ const parent = dirname8(candidate);
79998
+ if (parent === workspaceRoot || isPathInsideRoot(workspaceRoot, parent)) {
79999
+ try {
80000
+ const parentReal = await realpath(parent);
80001
+ if (!isPathInsideRoot(workspaceRoot, parentReal)) {
80002
+ return { ok: false, error: "Path escapes workspace" };
80003
+ }
80004
+ const resolved = resolve3(parentReal, basename(candidate));
80005
+ if (!isPathInsideRoot(workspaceRoot, resolved)) {
80006
+ return { ok: false, error: "Path escapes workspace" };
80007
+ }
80008
+ return { ok: true, absolutePath: resolved };
80009
+ } catch {
80010
+ return { ok: true, absolutePath: candidate };
80011
+ }
80012
+ }
80013
+ return { ok: false, error: "Path escapes workspace" };
80014
+ }
80015
+ }
80016
+ function gunzipBase64Payload(base64, maxBytes) {
80017
+ let compressed;
80018
+ try {
80019
+ compressed = Buffer.from(base64, "base64");
80020
+ } catch {
80021
+ return { ok: false, errorMessage: "Missing file data" };
80022
+ }
80023
+ if (compressed.length > maxBytes * 10) {
80024
+ return { ok: false, errorMessage: "File content too large" };
80025
+ }
80026
+ try {
80027
+ const content = gunzipSync(compressed, { maxOutputLength: maxBytes });
80028
+ if (content.length > maxBytes) {
80029
+ return { ok: false, errorMessage: "File content too large" };
80030
+ }
80031
+ return { ok: true, content };
80032
+ } catch {
80033
+ return { ok: false, errorMessage: "File content too large" };
80034
+ }
80035
+ }
80036
+ var init_workspace_path_security = () => {};
80037
+
80038
+ // src/infrastructure/services/workspace/workspace-visibility-policy.ts
80039
+ import { exec as exec2, spawn as spawn3 } from "node:child_process";
80040
+ import { promisify as promisify2 } from "node:util";
80041
+ function isAlwaysExcludedDirName(name) {
80042
+ return ALWAYS_EXCLUDE_DIR_NAMES.has(name);
80043
+ }
80044
+ function isSecretPath(relativePath) {
80045
+ const normalized = relativePath.replace(/\\/g, "/");
80046
+ return SECRET_PATH_PATTERNS.some((pattern) => pattern.test(normalized));
80047
+ }
80048
+ function hasExcludedDirSegment(relativePath) {
80049
+ const segments = relativePath.split("/");
80050
+ return segments.some((segment) => isAlwaysExcludedDirName(segment));
80051
+ }
80052
+ function isPathVisible(relativePath) {
80053
+ if (!relativePath)
80054
+ return true;
80055
+ return !isSecretPath(relativePath) && !hasExcludedDirSegment(relativePath);
80056
+ }
80057
+ function isPathContentReadable(relativePath) {
80058
+ return isPathVisible(relativePath);
80059
+ }
80060
+ async function isGitRepo(rootDir) {
80061
+ try {
80062
+ const { stdout } = await execAsync2("git rev-parse --is-inside-work-tree", {
80063
+ cwd: rootDir,
80064
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_PAGER: "cat", NO_COLOR: "1" },
80065
+ maxBuffer: 1024 * 1024
80066
+ });
80067
+ return stdout.trim() === "true";
80068
+ } catch {
80069
+ return false;
80070
+ }
80071
+ }
80072
+ async function filterGitIgnored(rootDir, relativePaths) {
80073
+ if (relativePaths.length === 0)
80074
+ return new Set;
80075
+ const inRepo = await isGitRepo(rootDir);
80076
+ if (!inRepo)
80077
+ return new Set;
80078
+ try {
80079
+ const stdout = await new Promise((resolve4, reject) => {
80080
+ const child = spawn3("git", ["check-ignore", "--stdin", "-z"], {
80081
+ cwd: rootDir,
80082
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_PAGER: "cat", NO_COLOR: "1" }
80083
+ });
80084
+ let output = "";
80085
+ child.stdout.on("data", (chunk2) => {
80086
+ output += chunk2.toString();
80087
+ });
80088
+ child.on("error", reject);
80089
+ child.on("close", () => resolve4(output));
80090
+ child.stdin.write(relativePaths.join(`
80091
+ `));
80092
+ child.stdin.end();
80093
+ });
80094
+ const ignored = new Set;
80095
+ if (!stdout)
80096
+ return ignored;
80097
+ for (const entry of stdout.split("\x00")) {
80098
+ const trimmed = entry.trim();
80099
+ if (trimmed)
80100
+ ignored.add(trimmed);
80101
+ }
80102
+ return ignored;
80103
+ } catch {
80104
+ return new Set;
80105
+ }
80106
+ }
80107
+ var execAsync2, ALWAYS_EXCLUDE_DIR_NAMES, SECRET_PATH_PATTERNS;
80108
+ var init_workspace_visibility_policy = __esm(() => {
80109
+ execAsync2 = promisify2(exec2);
80110
+ ALWAYS_EXCLUDE_DIR_NAMES = new Set([
80111
+ "node_modules",
80112
+ ".git",
80113
+ "dist",
80114
+ "build",
80115
+ ".next",
80116
+ "coverage",
80117
+ "__pycache__",
80118
+ ".turbo",
80119
+ ".cache",
80120
+ ".tmp",
80121
+ "tmp",
80122
+ "_generated",
80123
+ ".vercel"
80124
+ ]);
80125
+ SECRET_PATH_PATTERNS = [
80126
+ /^\.env$/,
80127
+ /^\.env\./,
80128
+ /\.pem$/,
80129
+ /\.key$/,
80130
+ /id_rsa$/,
80131
+ /credentials\.json$/,
80132
+ /^secrets(\/|$)/,
80133
+ /^\.aws(\/|$)/
80134
+ ];
80135
+ });
80136
+
80137
+ // src/infrastructure/services/workspace/dir-listing-scanner.ts
80138
+ import { promises as fsPromises } from "node:fs";
80139
+ import path3 from "node:path";
80140
+ async function listDirectory(rootDir, dirPath, options) {
80141
+ const maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
80142
+ const scannedAt = Date.now();
80143
+ const absDir = dirPath ? path3.join(rootDir, dirPath) : rootDir;
80144
+ const resolvedRoot = path3.resolve(rootDir);
80145
+ const resolvedDir = path3.resolve(absDir);
80146
+ if (!isPathInsideRoot(resolvedRoot, resolvedDir)) {
80147
+ return { dirPath, entries: [], scannedAt, truncated: false, totalCount: 0 };
80148
+ }
80149
+ let dirents;
80150
+ try {
80151
+ dirents = await fsPromises.readdir(absDir, { withFileTypes: true });
80152
+ } catch {
80153
+ return { dirPath, entries: [], scannedAt, truncated: false, totalCount: 0 };
80154
+ }
80155
+ const candidates = [];
80156
+ for (const ent of dirents) {
80157
+ if (isAlwaysExcludedDirName(ent.name))
80158
+ continue;
80159
+ const relativePath = dirPath ? `${dirPath}/${ent.name}` : ent.name;
80160
+ if (!isPathVisible(relativePath))
80161
+ continue;
80162
+ if (ent.isDirectory()) {
80163
+ candidates.push({ name: ent.name, path: relativePath, type: "directory" });
80164
+ } else if (ent.isFile()) {
80165
+ let size9;
80166
+ try {
80167
+ const st = await fsPromises.stat(path3.join(absDir, ent.name));
80168
+ size9 = st.size;
80169
+ } catch {}
80170
+ candidates.push({ name: ent.name, path: relativePath, type: "file", size: size9 });
80171
+ }
80172
+ }
80173
+ const ignored = await filterGitIgnored(rootDir, candidates.map((c) => c.path));
80174
+ const visible = candidates.filter((c) => !ignored.has(c.path));
80175
+ visible.sort((a, b) => {
80176
+ if (a.type === "directory" && b.type === "file")
80177
+ return -1;
80178
+ if (a.type === "file" && b.type === "directory")
80179
+ return 1;
80180
+ return a.name.localeCompare(b.name, undefined, { sensitivity: "base" });
80181
+ });
80182
+ const totalCount = visible.length;
80183
+ const truncated = totalCount > maxEntries;
80184
+ const entries2 = visible.slice(0, maxEntries);
80185
+ return { dirPath, entries: entries2, scannedAt, truncated, totalCount };
80186
+ }
80187
+ var DEFAULT_MAX_ENTRIES = 500;
80188
+ var init_dir_listing_scanner = __esm(() => {
80189
+ init_workspace_path_security();
80190
+ init_workspace_visibility_policy();
80191
+ });
80192
+
80193
+ // src/infrastructure/services/workspace/dir-listing-sync.ts
80194
+ import { gzipSync } from "node:zlib";
80195
+ function listingCacheKey(workingDir, dirPath) {
80196
+ return `${workingDir}\x00${dirPath}`;
80197
+ }
80198
+ async function scanDirListingForSync(workingDir, dirPath) {
80199
+ const listing = await listDirectory(workingDir, dirPath);
80200
+ const dataHash = computeDirListingContentHash(listing);
80201
+ const cacheKey = listingCacheKey(workingDir, dirPath);
80202
+ if (lastSyncedContentHash.get(cacheKey) === dataHash)
80203
+ return null;
80204
+ const json = JSON.stringify(listing);
80205
+ return {
80206
+ dirPath,
80207
+ data: { compression: "gzip", content: gzipSync(Buffer.from(json)).toString("base64") },
80208
+ dataHash,
80209
+ scannedAt: listing.scannedAt,
80210
+ truncated: listing.truncated,
80211
+ totalCount: listing.totalCount
80212
+ };
80213
+ }
80214
+ async function scanDirListingsWithConcurrency(workingDir, dirPaths) {
80215
+ const items = [];
80216
+ let index = 0;
80217
+ async function worker() {
80218
+ while (index < dirPaths.length) {
80219
+ const currentIndex = index;
80220
+ index += 1;
80221
+ const dirPath = dirPaths[currentIndex];
80222
+ if (dirPath === undefined)
80223
+ continue;
80224
+ const item = await scanDirListingForSync(workingDir, dirPath);
80225
+ if (item)
80226
+ items.push(item);
80227
+ }
80228
+ }
80229
+ const workerCount = Math.min(SCAN_CONCURRENCY, dirPaths.length);
80230
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
80231
+ return items;
80232
+ }
80233
+ function markItemsSynced(workingDir, items) {
80234
+ for (const item of items) {
80235
+ lastSyncedContentHash.set(listingCacheKey(workingDir, item.dirPath), item.dataHash);
80236
+ }
80237
+ }
80238
+ async function syncDirListingToBackend(session2, workingDir, dirPath) {
80239
+ const item = await scanDirListingForSync(workingDir, dirPath);
80240
+ if (!item)
80241
+ return;
80242
+ await session2.backend.mutation(api.workspaceFiles.syncDirListingV2, {
80243
+ sessionId: session2.sessionId,
80244
+ machineId: session2.machineId,
80245
+ workingDir,
80246
+ dirPath: item.dirPath,
80247
+ data: item.data,
80248
+ dataHash: item.dataHash,
80249
+ scannedAt: item.scannedAt,
80250
+ truncated: item.truncated,
80251
+ totalCount: item.totalCount
80252
+ });
80253
+ markItemsSynced(workingDir, [item]);
80254
+ }
80255
+ async function syncDirListingsToBackend(session2, workingDir, dirPaths) {
80256
+ const unique = [...new Set(dirPaths)];
80257
+ if (unique.length === 0)
80258
+ return;
80259
+ if (unique.length === 1) {
80260
+ const dirPath = unique[0];
80261
+ if (dirPath !== undefined) {
80262
+ await syncDirListingToBackend(session2, workingDir, dirPath);
80263
+ }
80264
+ return;
80265
+ }
80266
+ const items = await scanDirListingsWithConcurrency(workingDir, unique);
80267
+ if (items.length === 0)
80268
+ return;
80269
+ for (let i2 = 0;i2 < items.length; i2 += MAX_BATCH_SIZE) {
80270
+ const chunk2 = items.slice(i2, i2 + MAX_BATCH_SIZE);
80271
+ await session2.backend.mutation(api.workspaceFiles.syncDirListingV2Batch, {
80272
+ sessionId: session2.sessionId,
80273
+ machineId: session2.machineId,
80274
+ workingDir,
80275
+ items: chunk2
80276
+ });
80277
+ markItemsSynced(workingDir, chunk2);
80278
+ }
80279
+ }
80280
+ var MAX_BATCH_SIZE = 25, SCAN_CONCURRENCY = 4, lastSyncedContentHash;
80281
+ var init_dir_listing_sync = __esm(() => {
80282
+ init_dir_listing_content_hash();
80283
+ init_dir_listing_scanner();
80284
+ init_api3();
80285
+ lastSyncedContentHash = new Map;
80286
+ });
80287
+
80288
+ // src/infrastructure/services/workspace/workspace-file-search.ts
80289
+ import { promises as fsPromises2 } from "node:fs";
80290
+ import path4 from "node:path";
80291
+ async function searchWorkspaceFiles(rootDir, query, options) {
80292
+ const maxResults = options?.maxResults ?? DEFAULT_MAX_RESULTS;
80293
+ const scannedAt = Date.now();
80294
+ const normalizedQuery = query.trim().toLowerCase();
80295
+ const matches = [];
80296
+ let visitedDirs = 0;
80297
+ let truncated = false;
80298
+ async function visitDir(relDir) {
80299
+ if (visitedDirs >= MAX_VISIT_DIRS || matches.length >= maxResults) {
80300
+ truncated = true;
80301
+ return;
80302
+ }
80303
+ visitedDirs++;
80304
+ const absDir = relDir ? path4.join(rootDir, relDir) : rootDir;
80305
+ let dirents;
80306
+ try {
80307
+ dirents = await fsPromises2.readdir(absDir, { withFileTypes: true });
80308
+ } catch {
80309
+ return;
80310
+ }
80311
+ const fileCandidates = [];
80312
+ const subdirs = [];
80313
+ for (const ent of dirents) {
80314
+ if (isAlwaysExcludedDirName(ent.name))
80315
+ continue;
80316
+ const relativePath = relDir ? `${relDir}/${ent.name}` : ent.name;
80317
+ if (!isPathVisible(relativePath))
80318
+ continue;
80319
+ if (ent.isDirectory()) {
80320
+ subdirs.push(relativePath);
80321
+ } else if (ent.isFile()) {
80322
+ fileCandidates.push(relativePath);
80323
+ }
80324
+ }
80325
+ const ignored = await filterGitIgnored(rootDir, fileCandidates);
80326
+ for (const filePath of fileCandidates) {
80327
+ if (ignored.has(filePath))
80328
+ continue;
80329
+ const fileName = path4.basename(filePath).toLowerCase();
80330
+ if (normalizedQuery === "" || fileName.includes(normalizedQuery)) {
80331
+ matches.push({ path: filePath, type: "file" });
80332
+ if (matches.length >= maxResults) {
80333
+ truncated = true;
80334
+ return;
80335
+ }
80336
+ }
80337
+ }
80338
+ for (const sub of subdirs) {
80339
+ if (matches.length >= maxResults || visitedDirs >= MAX_VISIT_DIRS) {
80340
+ truncated = true;
80341
+ return;
80342
+ }
80343
+ await visitDir(sub);
80344
+ }
80345
+ }
80346
+ await visitDir("");
80347
+ return {
80348
+ query,
80349
+ entries: matches,
80350
+ scannedAt,
80351
+ truncated,
80352
+ totalCount: matches.length
80353
+ };
80354
+ }
80355
+ var DEFAULT_MAX_RESULTS = 300, MAX_VISIT_DIRS = 2000;
80356
+ var init_workspace_file_search = __esm(() => {
80357
+ init_workspace_visibility_policy();
80358
+ });
80359
+
80360
+ // src/commands/machine/daemon-start/dir-listing-subscription.ts
80361
+ import { createHash as createHash3 } from "node:crypto";
80362
+ import { gzipSync as gzipSync2 } from "node:zlib";
80363
+ function logSubscriptionWarn(label, err) {
80364
+ console.warn(`[${formatTimestamp()}] ⚠️ ${label}: ${getErrorMessage(err)}`);
80365
+ }
80366
+ async function uploadDirListing(session2, workingDir, dirPath) {
80367
+ await syncDirListingToBackend(session2, workingDir, dirPath);
80368
+ await session2.backend.mutation(api.workspaceFiles.fulfillDirListingRequest, {
80369
+ sessionId: session2.sessionId,
80370
+ machineId: session2.machineId,
80371
+ workingDir,
80372
+ dirPath
80373
+ });
80374
+ }
80375
+ async function uploadFileSearch(session2, workingDir, query) {
80376
+ const result = await searchWorkspaceFiles(workingDir, query);
80377
+ const json = JSON.stringify(result);
80378
+ const dataHash = createHash3("md5").update(json).digest("hex");
80379
+ const compressed = gzipSync2(Buffer.from(json)).toString("base64");
80380
+ await session2.backend.mutation(api.workspaceFiles.syncFileSearchV2, {
80381
+ sessionId: session2.sessionId,
80382
+ machineId: session2.machineId,
80383
+ workingDir,
80384
+ query,
80385
+ data: { compression: "gzip", content: compressed },
80386
+ dataHash,
80387
+ scannedAt: result.scannedAt,
80388
+ truncated: result.truncated,
80389
+ totalCount: result.totalCount
80390
+ });
80391
+ await session2.backend.mutation(api.workspaceFiles.fulfillFileSearchRequest, {
80392
+ sessionId: session2.sessionId,
80393
+ machineId: session2.machineId,
80394
+ workingDir,
80395
+ query
80396
+ });
80397
+ }
80398
+ var fulfillDirListingRequestsEffect = (session2, requests) => exports_Effect.gen(function* () {
80399
+ for (const request2 of requests) {
80400
+ yield* exports_Effect.catchAll(exports_Effect.gen(function* () {
80401
+ const start3 = Date.now();
80402
+ yield* exports_Effect.tryPromise(() => uploadDirListing(session2, request2.workingDir, request2.dirPath));
80403
+ console.log(`[${formatTimestamp()}] \uD83D\uDCC2 Dir listing fulfilled: ${request2.workingDir}/${request2.dirPath || "(root)"} (${Date.now() - start3}ms)`);
80404
+ }), (err) => {
80405
+ logSubscriptionWarn(`Dir listing failed for ${request2.workingDir}/${request2.dirPath}`, err);
80406
+ return exports_Effect.void;
80407
+ });
80408
+ }
80409
+ }), fulfillFileSearchRequestsEffect = (session2, requests) => exports_Effect.gen(function* () {
80410
+ for (const request2 of requests) {
80411
+ yield* exports_Effect.catchAll(exports_Effect.gen(function* () {
80412
+ const start3 = Date.now();
80413
+ yield* exports_Effect.tryPromise(() => uploadFileSearch(session2, request2.workingDir, request2.query));
80414
+ console.log(`[${formatTimestamp()}] \uD83D\uDD0D File search fulfilled: ${request2.workingDir} query="${request2.query}" (${Date.now() - start3}ms)`);
80415
+ }), (err) => {
80416
+ logSubscriptionWarn(`File search failed for ${request2.workingDir}`, err);
80417
+ return exports_Effect.void;
80418
+ });
80419
+ }
80420
+ }), startDirListingSubscriptionEffect = (wsClient2) => exports_Effect.gen(function* () {
80421
+ const session2 = yield* DaemonSessionService;
80422
+ let processingDir = false;
80423
+ let processingSearch = false;
80424
+ const unsubDir = wsClient2.onUpdate(api.workspaceFiles.getPendingDirListingRequests, { sessionId: session2.sessionId, machineId: session2.machineId }, (requests) => {
80425
+ if (!requests?.length || processingDir)
80426
+ return;
80427
+ processingDir = true;
80428
+ exports_Effect.runPromise(fulfillDirListingRequestsEffect(session2, requests)).catch((err) => {
80429
+ logSubscriptionWarn("Dir listing subscription processing failed", err);
80430
+ }).finally(() => {
80431
+ processingDir = false;
80432
+ });
80433
+ }, (err) => {
80434
+ logSubscriptionWarn("Dir listing subscription error", err);
80435
+ });
80436
+ const unsubSearch = wsClient2.onUpdate(api.workspaceFiles.getPendingFileSearchRequests, { sessionId: session2.sessionId, machineId: session2.machineId }, (requests) => {
80437
+ if (!requests?.length || processingSearch)
80438
+ return;
80439
+ processingSearch = true;
80440
+ exports_Effect.runPromise(fulfillFileSearchRequestsEffect(session2, requests)).catch((err) => {
80441
+ logSubscriptionWarn("File search subscription processing failed", err);
80442
+ }).finally(() => {
80443
+ processingSearch = false;
80444
+ });
80445
+ }, (err) => {
80446
+ logSubscriptionWarn("File search subscription error", err);
80447
+ });
80448
+ console.log(`[${formatTimestamp()}] \uD83D\uDCC2 Dir listing subscription started (reactive)`);
80449
+ return {
80450
+ stop: () => {
80451
+ unsubDir();
80452
+ unsubSearch();
80453
+ console.log(`[${formatTimestamp()}] \uD83D\uDCC2 Dir listing subscription stopped`);
80454
+ }
80455
+ };
80456
+ });
80457
+ var init_dir_listing_subscription = __esm(() => {
80458
+ init_esm();
80459
+ init_daemon_services();
80460
+ init_api3();
80461
+ init_dir_listing_sync();
80462
+ init_workspace_file_search();
80463
+ init_convex_error();
80464
+ });
80465
+
80466
+ // src/infrastructure/services/workspace/workspace-fs-watch-paths.ts
80467
+ function parentDirPath(relativePath) {
80468
+ const normalized = relativePath.replace(/\\/g, "/");
80469
+ const idx = normalized.lastIndexOf("/");
80470
+ return idx === -1 ? "" : normalized.slice(0, idx);
80471
+ }
80472
+ function shouldIgnoreWatchRelativePath(relativePath) {
80473
+ const normalized = relativePath.replace(/\\/g, "/").replace(/\/+$/, "");
80474
+ if (!normalized || normalized === ".")
80475
+ return false;
80476
+ return hasExcludedDirSegment(normalized);
80477
+ }
80478
+ function dirsToRefreshForEvent(relativePath, isDirectory = false) {
80479
+ const normalized = relativePath.replace(/\\/g, "/").replace(/\/+$/, "");
80480
+ if (!normalized || normalized === ".")
80481
+ return [""];
80482
+ const dirs = new Set;
80483
+ dirs.add(parentDirPath(normalized));
80484
+ if (isDirectory)
80485
+ dirs.add(normalized);
80486
+ return [...dirs];
80487
+ }
80488
+ function filterDirsByActiveSet(dirs, activeDirPaths) {
80489
+ return dirs.filter((d) => activeDirPaths.has(d));
80490
+ }
80491
+ var init_workspace_fs_watch_paths = __esm(() => {
80492
+ init_workspace_visibility_policy();
80493
+ });
80494
+
80495
+ // src/infrastructure/services/workspace/workspace-fs-watcher.ts
80496
+ import { watch } from "node:fs";
80497
+ import path5 from "node:path";
80498
+ function isRecursiveWatchPlatform() {
80499
+ return process.platform === "darwin" || process.platform === "win32";
80500
+ }
80501
+ function normalizeFilename(filename) {
80502
+ if (filename == null)
80503
+ return null;
80504
+ const value = typeof filename === "string" ? filename : filename.toString();
80505
+ if (!value || value === "." || value === "..")
80506
+ return null;
80507
+ return value.replace(/\\/g, "/");
80508
+ }
80509
+ function createWorkspaceFsWatcher(options) {
80510
+ const absWorkingDir = path5.resolve(options.workingDir);
80511
+ const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
80512
+ let activeDirPaths = new Set(options.activeDirPaths);
80513
+ let stopped = false;
80514
+ let debounceTimer = null;
80515
+ const pendingDirs = new Set;
80516
+ let rootWatcher = null;
80517
+ const dirWatchers = new Map;
80518
+ const scheduleFlush = () => {
80519
+ if (debounceTimer)
80520
+ clearTimeout(debounceTimer);
80521
+ debounceTimer = setTimeout(() => {
80522
+ debounceTimer = null;
80523
+ if (pendingDirs.size === 0)
80524
+ return;
80525
+ const dirs = [...pendingDirs];
80526
+ pendingDirs.clear();
80527
+ Promise.resolve(options.onRefreshDirs(dirs));
80528
+ }, debounceMs);
80529
+ debounceTimer.unref?.();
80530
+ };
80531
+ const enqueueDirs = (dirs) => {
80532
+ for (const dir of dirs)
80533
+ pendingDirs.add(dir);
80534
+ scheduleFlush();
80535
+ };
80536
+ const handleRelativePath = (relativePath, isDirectory = false) => {
80537
+ if (stopped)
80538
+ return;
80539
+ if (shouldIgnoreWatchRelativePath(relativePath))
80540
+ return;
80541
+ const toRefresh = filterDirsByActiveSet(dirsToRefreshForEvent(relativePath, isDirectory), activeDirPaths);
80542
+ if (toRefresh.length > 0)
80543
+ enqueueDirs(toRefresh);
80544
+ };
80545
+ const onWatchEvent = (watchedDirPath, filename) => {
80546
+ const normalizedFilename = normalizeFilename(filename);
80547
+ if (!normalizedFilename)
80548
+ return;
80549
+ let relativePath;
80550
+ if (isRecursiveWatchPlatform()) {
80551
+ relativePath = normalizedFilename;
80552
+ } else if (watchedDirPath === "") {
80553
+ relativePath = normalizedFilename;
80554
+ } else {
80555
+ relativePath = `${watchedDirPath}/${normalizedFilename}`;
80556
+ }
80557
+ const isDirectory = relativePath.endsWith("/");
80558
+ handleRelativePath(relativePath.replace(/\/+$/, ""), isDirectory);
80559
+ };
80560
+ const closePerDirWatchers = () => {
80561
+ for (const watcher of dirWatchers.values())
80562
+ watcher.close();
80563
+ dirWatchers.clear();
80564
+ };
80565
+ const startPerDirWatch = (dirPath) => {
80566
+ if (dirPath === "" || dirWatchers.has(dirPath))
80567
+ return;
80568
+ const absDir = path5.join(absWorkingDir, dirPath);
80569
+ const watcher = watch(absDir, (_eventType, filename) => {
80570
+ onWatchEvent(dirPath, filename);
80571
+ });
80572
+ dirWatchers.set(dirPath, watcher);
80573
+ };
80574
+ const rewirePerDirWatches = () => {
80575
+ const wanted = new Set;
80576
+ for (const dirPath of activeDirPaths) {
80577
+ if (dirPath !== "")
80578
+ wanted.add(dirPath);
80579
+ }
80580
+ for (const [dirPath, watcher] of dirWatchers) {
80581
+ if (!wanted.has(dirPath)) {
80582
+ watcher.close();
80583
+ dirWatchers.delete(dirPath);
80584
+ }
80585
+ }
80586
+ for (const dirPath of wanted) {
80587
+ if (!dirWatchers.has(dirPath))
80588
+ startPerDirWatch(dirPath);
80589
+ }
80590
+ };
80591
+ const startWatching = () => {
80592
+ if (isRecursiveWatchPlatform()) {
80593
+ rootWatcher = watch(absWorkingDir, { recursive: true }, (_eventType, filename) => {
80594
+ onWatchEvent("", filename);
80595
+ });
80596
+ return;
80597
+ }
80598
+ rootWatcher = watch(absWorkingDir, (_eventType, filename) => {
80599
+ onWatchEvent("", filename);
80600
+ });
80601
+ rewirePerDirWatches();
80602
+ };
80603
+ startWatching();
80604
+ return {
80605
+ updateActiveDirPaths: (paths) => {
80606
+ activeDirPaths = new Set(paths);
80607
+ if (!isRecursiveWatchPlatform() && !stopped) {
80608
+ rewirePerDirWatches();
80609
+ }
80610
+ },
80611
+ stop: () => {
80612
+ stopped = true;
80613
+ if (debounceTimer) {
80614
+ clearTimeout(debounceTimer);
80615
+ debounceTimer = null;
80616
+ }
80617
+ rootWatcher?.close();
80618
+ rootWatcher = null;
80619
+ closePerDirWatchers();
80620
+ pendingDirs.clear();
80621
+ }
80622
+ };
80623
+ }
80624
+ var DEFAULT_DEBOUNCE_MS = 400;
80625
+ var init_workspace_fs_watcher = __esm(() => {
80626
+ init_workspace_fs_watch_paths();
80627
+ });
80628
+
80629
+ // src/commands/machine/daemon-start/dir-listing-watch-subscription.ts
80630
+ var startDirListingWatchSubscriptionEffect = (wsClient2) => exports_Effect.gen(function* () {
80631
+ const session2 = yield* DaemonSessionService;
80632
+ const watchers = new Map;
80633
+ const applyTargets = (targets) => {
80634
+ const nextWorkingDirs = new Set(targets.map((t) => t.workingDir));
80635
+ for (const [workingDir, handle] of watchers) {
80636
+ if (!nextWorkingDirs.has(workingDir)) {
80637
+ handle.stop();
80638
+ watchers.delete(workingDir);
80639
+ }
80640
+ }
80641
+ for (const target of targets) {
80642
+ const activeSet = new Set(target.activeDirPaths);
80643
+ const existing = watchers.get(target.workingDir);
80644
+ if (existing) {
80645
+ existing.updateActiveDirPaths(activeSet);
80646
+ continue;
80647
+ }
80648
+ const handle = createWorkspaceFsWatcher({
80649
+ workingDir: target.workingDir,
80650
+ activeDirPaths: activeSet,
80651
+ onRefreshDirs: async (dirPaths) => {
80652
+ try {
80653
+ await syncDirListingsToBackend(session2, target.workingDir, dirPaths);
80654
+ } catch (err) {
80655
+ console.warn(`[${formatTimestamp()}] ⚠️ FS watch sync failed for ${target.workingDir}: ${getErrorMessage(err)}`);
80656
+ }
80657
+ }
80658
+ });
80659
+ watchers.set(target.workingDir, handle);
80660
+ }
80661
+ };
80662
+ let stopped = false;
80663
+ const unsub = wsClient2.onUpdate(api.workspaceFiles.listDirListingWatchTargets, { sessionId: session2.sessionId, machineId: session2.machineId }, (targets) => {
80664
+ if (stopped)
80665
+ return;
80666
+ applyTargets(targets ?? []);
80667
+ }, (err) => {
80668
+ console.warn(`[${formatTimestamp()}] ⚠️ Dir listing watch subscription error: ${getErrorMessage(err)}`);
80669
+ });
80670
+ console.log(`[${formatTimestamp()}] \uD83D\uDC41️ Dir listing FS watch subscription started`);
80671
+ return {
80672
+ stop: () => {
80673
+ stopped = true;
80674
+ unsub();
80675
+ for (const handle of watchers.values())
80676
+ handle.stop();
80677
+ watchers.clear();
80678
+ console.log(`[${formatTimestamp()}] \uD83D\uDC41️ Dir listing FS watch subscription stopped`);
80679
+ }
80680
+ };
80681
+ });
80682
+ var init_dir_listing_watch_subscription = __esm(() => {
80683
+ init_esm();
80684
+ init_daemon_services();
80685
+ init_api3();
80686
+ init_dir_listing_sync();
80687
+ init_workspace_fs_watcher();
80688
+ init_convex_error();
80689
+ });
80690
+
79920
80691
  // src/events/daemon/agent/on-request-start-agent.ts
79921
80692
  function notifyAgentStartFailed(backend2, opts) {
79922
80693
  backend2.mutation(api.machines.emitAgentStartFailed, opts).catch((err) => {
@@ -80016,13 +80787,8 @@ var init_on_request_stop_agent = __esm(() => {
80016
80787
  init_stop_agent();
80017
80788
  });
80018
80789
 
80019
- // src/commands/machine/daemon-start/utils.ts
80020
- function formatTimestamp() {
80021
- return new Date().toISOString().replace("T", " ").substring(0, 19);
80022
- }
80023
-
80024
80790
  // src/commands/machine/daemon-start/handlers/orphan-tracker.ts
80025
- import { createHash as createHash2 } from "node:crypto";
80791
+ import { createHash as createHash4 } from "node:crypto";
80026
80792
  import {
80027
80793
  appendFileSync,
80028
80794
  existsSync as existsSync5,
@@ -80036,7 +80802,7 @@ import { homedir as homedir5 } from "node:os";
80036
80802
  import { join as join15 } from "node:path";
80037
80803
  function getUrlHash() {
80038
80804
  const url2 = getConvexUrl();
80039
- return createHash2("sha256").update(url2).digest("hex").substring(0, 8);
80805
+ return createHash4("sha256").update(url2).digest("hex").substring(0, 8);
80040
80806
  }
80041
80807
  function getChildPidsFilePath() {
80042
80808
  const dir = join15(homedir5(), ".chatroom");
@@ -80230,19 +80996,19 @@ class ProcessManager {
80230
80996
  this.pendingStops.clear();
80231
80997
  }
80232
80998
  waitForExit(runId, ms) {
80233
- return new Promise((resolve3) => {
80999
+ return new Promise((resolve4) => {
80234
81000
  const interval = 100;
80235
81001
  let elapsed3 = 0;
80236
81002
  const timer = setInterval(() => {
80237
81003
  if (!this.runningProcesses.has(runId)) {
80238
81004
  clearInterval(timer);
80239
- resolve3(true);
81005
+ resolve4(true);
80240
81006
  return;
80241
81007
  }
80242
81008
  elapsed3 += interval;
80243
81009
  if (elapsed3 >= ms) {
80244
81010
  clearInterval(timer);
80245
- resolve3(false);
81011
+ resolve4(false);
80246
81012
  }
80247
81013
  }, interval);
80248
81014
  });
@@ -80277,9 +81043,9 @@ var init_killer = __esm(() => {
80277
81043
  });
80278
81044
 
80279
81045
  // ../../services/backend/src/output-encoding.ts
80280
- import { gunzipSync, gzipSync } from "node:zlib";
81046
+ import { gunzipSync as gunzipSync2, gzipSync as gzipSync3 } from "node:zlib";
80281
81047
  function encodeOutput(plain) {
80282
- const compressed = gzipSync(Buffer.from(plain, "utf-8"));
81048
+ const compressed = gzipSync3(Buffer.from(plain, "utf-8"));
80283
81049
  return {
80284
81050
  compression: "gzip",
80285
81051
  content: compressed.toString("base64")
@@ -80457,7 +81223,7 @@ var init_output_store = __esm(() => {
80457
81223
  });
80458
81224
 
80459
81225
  // src/commands/machine/daemon-start/handlers/process/spawner.ts
80460
- import { spawn as spawn3 } from "node:child_process";
81226
+ import { spawn as spawn4 } from "node:child_process";
80461
81227
  async function flushTailV2(deps, tracked, force = false) {
80462
81228
  if (!force && !isRunLogObserved(tracked.runId))
80463
81229
  return;
@@ -80540,7 +81306,7 @@ async function spawnCommandProcess(deps, event, commandKey) {
80540
81306
  tempDirReady = true;
80541
81307
  }
80542
81308
  const store = createOutputStore(runIdStr);
80543
- const child = spawn3("sh", ["-c", script], {
81309
+ const child = spawn4("sh", ["-c", script], {
80544
81310
  cwd: workingDir,
80545
81311
  env: buildChatroomSpawnEnv(deps.convexUrl),
80546
81312
  stdio: ["ignore", "pipe", "pipe"],
@@ -80802,8 +81568,8 @@ var init_command_runner = __esm(() => {
80802
81568
  }).catch((err) => {
80803
81569
  console.warn(`[${formatTimestamp()}] ⚠️ Failed to mark run as killed on shutdown: ${getErrorMessage(err)}`);
80804
81570
  })));
80805
- yield* exports_Effect.promise(() => new Promise((resolve3) => {
80806
- const t = setTimeout(resolve3, 3000);
81571
+ yield* exports_Effect.promise(() => new Promise((resolve4) => {
81572
+ const t = setTimeout(resolve4, 3000);
80807
81573
  t.unref?.();
80808
81574
  }));
80809
81575
  for (const [, tracked] of trackedEntries) {
@@ -80815,8 +81581,8 @@ var init_command_runner = __esm(() => {
80815
81581
  clearTrackedPids();
80816
81582
  yield* exports_Effect.promise(() => Promise.race([
80817
81583
  statusUpdates,
80818
- new Promise((resolve3) => {
80819
- const t = setTimeout(resolve3, 2000);
81584
+ new Promise((resolve4) => {
81585
+ const t = setTimeout(resolve4, 2000);
80820
81586
  t.unref?.();
80821
81587
  })
80822
81588
  ]));
@@ -80851,28 +81617,81 @@ var init_on_daemon_shutdown = __esm(() => {
80851
81617
  connected: false
80852
81618
  }).catch(() => {}));
80853
81619
  });
80854
- });
80855
-
80856
- // src/infrastructure/git/types.ts
80857
- function makeGitStateKey(machineId, workingDir) {
80858
- return `${machineId}::${workingDir}`;
81620
+ });
81621
+
81622
+ // src/infrastructure/git/types.ts
81623
+ function makeGitStateKey(machineId, workingDir) {
81624
+ return `${machineId}::${workingDir}`;
81625
+ }
81626
+ var FULL_DIFF_MAX_BYTES = 500000, COMMITS_PER_PAGE = 20;
81627
+
81628
+ // src/infrastructure/git/run-command.ts
81629
+ import { spawn as spawn5 } from "node:child_process";
81630
+ function runCommandSpawn(command, args2, cwd, options) {
81631
+ return new Promise((resolve4) => {
81632
+ const child = spawn5(command, args2, {
81633
+ cwd,
81634
+ env: options?.env ?? DEFAULT_GIT_ENV,
81635
+ stdio: ["ignore", "pipe", "pipe"]
81636
+ });
81637
+ let stdout = "";
81638
+ let stderr = "";
81639
+ const maxBuffer = options?.maxBuffer ?? 10 * 1024 * 1024;
81640
+ const onData = (chunk2, target) => {
81641
+ const str = chunk2.toString();
81642
+ if (target === "stdout")
81643
+ stdout += str;
81644
+ else
81645
+ stderr += str;
81646
+ if (stdout.length + stderr.length > maxBuffer) {
81647
+ child.kill("SIGTERM");
81648
+ }
81649
+ };
81650
+ child.stdout?.on("data", (c) => onData(c, "stdout"));
81651
+ child.stderr?.on("data", (c) => onData(c, "stderr"));
81652
+ const timer = options?.timeout ? setTimeout(() => child.kill("SIGTERM"), options.timeout) : undefined;
81653
+ child.on("close", (code2) => {
81654
+ if (timer)
81655
+ clearTimeout(timer);
81656
+ if (code2 === 0)
81657
+ resolve4({ stdout, stderr });
81658
+ else {
81659
+ resolve4({
81660
+ error: Object.assign(new Error(stderr || stdout || `exit ${code2}`), {
81661
+ code: code2 ?? undefined
81662
+ })
81663
+ });
81664
+ }
81665
+ });
81666
+ child.on("error", (err) => {
81667
+ if (timer)
81668
+ clearTimeout(timer);
81669
+ resolve4({ error: err });
81670
+ });
81671
+ });
80859
81672
  }
80860
- var FULL_DIFF_MAX_BYTES = 500000, COMMITS_PER_PAGE = 20;
81673
+ function runGit(args2, cwd, options) {
81674
+ return runCommandSpawn("git", args2, cwd, options);
81675
+ }
81676
+ function runGh(args2, cwd, options) {
81677
+ return runCommandSpawn("gh", args2, cwd, {
81678
+ timeout: options?.timeout,
81679
+ env: { ...process.env, NO_COLOR: "1" }
81680
+ });
81681
+ }
81682
+ var DEFAULT_GIT_ENV;
81683
+ var init_run_command = __esm(() => {
81684
+ DEFAULT_GIT_ENV = {
81685
+ ...process.env,
81686
+ GIT_TERMINAL_PROMPT: "0",
81687
+ GIT_PAGER: "cat",
81688
+ NO_COLOR: "1"
81689
+ };
81690
+ });
80861
81691
 
80862
81692
  // 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
- }
81693
+ function repoArgs(repoSlug) {
81694
+ return repoSlug ? ["--repo", repoSlug] : [];
80876
81695
  }
80877
81696
  function isGitNotInstalled(message) {
80878
81697
  return message.includes("command not found") || message.includes("ENOENT") || message.includes("not found") || message.includes("'git' is not recognized");
@@ -80898,14 +81717,14 @@ function classifyError(errMessage) {
80898
81717
  }
80899
81718
  return { status: "error", message: errMessage.trim() };
80900
81719
  }
80901
- async function isGitRepo(workingDir) {
80902
- const result = await runGit("rev-parse --git-dir", workingDir);
81720
+ async function isGitRepo2(workingDir) {
81721
+ const result = await runGit(["rev-parse", "--git-dir"], workingDir);
80903
81722
  if ("error" in result)
80904
81723
  return false;
80905
81724
  return result.stdout.trim().length > 0;
80906
81725
  }
80907
81726
  async function getBranch(workingDir) {
80908
- const result = await runGit("rev-parse --abbrev-ref HEAD", workingDir);
81727
+ const result = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], workingDir);
80909
81728
  if ("error" in result) {
80910
81729
  const errMsg = result.error.message;
80911
81730
  if (errMsg.includes("unknown revision") || errMsg.includes("No such file or directory") || errMsg.includes("does not have any commits")) {
@@ -80920,7 +81739,7 @@ async function getBranch(workingDir) {
80920
81739
  return { status: "available", branch };
80921
81740
  }
80922
81741
  async function isDirty(workingDir) {
80923
- const result = await runGit("status --porcelain", workingDir);
81742
+ const result = await runGit(["status", "--porcelain"], workingDir);
80924
81743
  if ("error" in result)
80925
81744
  return false;
80926
81745
  return result.stdout.trim().length > 0;
@@ -80936,7 +81755,7 @@ function parseDiffStatLine(statLine) {
80936
81755
  };
80937
81756
  }
80938
81757
  async function getDiffStat(workingDir) {
80939
- const result = await runGit("diff HEAD --stat", workingDir);
81758
+ const result = await runGit(["diff", "HEAD", "--stat"], workingDir);
80940
81759
  if ("error" in result) {
80941
81760
  const errMsg = result.error.message;
80942
81761
  if (isEmptyRepo(result.error.message)) {
@@ -80965,7 +81784,9 @@ async function getDiffStat(workingDir) {
80965
81784
  return { status: "available", diffStat };
80966
81785
  }
80967
81786
  async function getFullDiff(workingDir) {
80968
- const result = await runGit("diff HEAD", workingDir);
81787
+ const result = await runGit(["diff", "HEAD"], workingDir, {
81788
+ maxBuffer: FULL_DIFF_MAX_BYTES + 64 * 1024
81789
+ });
80969
81790
  if ("error" in result) {
80970
81791
  const errMsg = result.error.message;
80971
81792
  if (isEmptyRepo(errMsg)) {
@@ -80990,8 +81811,7 @@ async function getFullDiff(workingDir) {
80990
81811
  }
80991
81812
  async function getPRDiffByNumber(cwd, prNumber) {
80992
81813
  const repoSlug = await getOriginRepoSlug(cwd);
80993
- const repoFlag = repoSlug ? ` --repo ${JSON.stringify(repoSlug)}` : "";
80994
- const result = await runCommand(`gh pr diff ${prNumber}${repoFlag}`, cwd);
81814
+ const result = await runGh(["pr", "diff", String(prNumber), ...repoArgs(repoSlug)], cwd);
80995
81815
  if ("error" in result) {
80996
81816
  return { status: "error", message: result.error.message };
80997
81817
  }
@@ -81008,8 +81828,11 @@ async function getPRDiffByNumber(cwd, prNumber) {
81008
81828
  }
81009
81829
  async function getRecentCommits(workingDir, count3 = 20, skip = 0) {
81010
81830
  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);
81831
+ const logArgs = ["log", `-${count3}`];
81832
+ if (skip > 0)
81833
+ logArgs.push(`--skip=${skip}`);
81834
+ logArgs.push(`--format=${format4}`);
81835
+ const result = await runGit(logArgs, workingDir);
81013
81836
  if ("error" in result) {
81014
81837
  return [];
81015
81838
  }
@@ -81031,7 +81854,7 @@ async function getRecentCommits(workingDir, count3 = 20, skip = 0) {
81031
81854
  return commits;
81032
81855
  }
81033
81856
  async function getCommitDetail(workingDir, sha) {
81034
- const result = await runGit(`show ${sha} --format="" --stat -p`, workingDir);
81857
+ const result = await runGit(["show", sha, "--format=", "--stat", "-p"], workingDir);
81035
81858
  if ("error" in result) {
81036
81859
  const errMsg = result.error.message;
81037
81860
  const classified = classifyError(errMsg);
@@ -81052,7 +81875,7 @@ async function getCommitDetail(workingDir, sha) {
81052
81875
  }
81053
81876
  async function getCommitMetadata(workingDir, sha) {
81054
81877
  const format4 = "%s%x1f%b%x1f%an%x1f%aI";
81055
- const result = await runGit(`log -1 --format=${format4} ${sha}`, workingDir);
81878
+ const result = await runGit(["log", "-1", `--format=${format4}`, sha], workingDir);
81056
81879
  if ("error" in result)
81057
81880
  return null;
81058
81881
  const output = result.stdout.trim();
@@ -81065,18 +81888,6 @@ async function getCommitMetadata(workingDir, sha) {
81065
81888
  const body = rawBody.trim();
81066
81889
  return { message, ...body ? { body } : {}, author, date };
81067
81890
  }
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
81891
  function parseRepoSlug(remoteUrl) {
81081
81892
  const trimmed = remoteUrl.trim();
81082
81893
  const httpsMatch = trimmed.match(/https?:\/\/[^/]+\/([^/]+)\/([^/]+?)(?:\.git)?$/);
@@ -81090,7 +81901,7 @@ function parseRepoSlug(remoteUrl) {
81090
81901
  return null;
81091
81902
  }
81092
81903
  async function getOriginRepoSlug(cwd) {
81093
- const result = await runGit("remote get-url origin", cwd);
81904
+ const result = await runGit(["remote", "get-url", "origin"], cwd);
81094
81905
  if ("error" in result)
81095
81906
  return null;
81096
81907
  const url2 = result.stdout.trim();
@@ -81100,9 +81911,22 @@ async function getOriginRepoSlug(cwd) {
81100
81911
  }
81101
81912
  async function getOpenPRsForBranch(cwd, branch) {
81102
81913
  const repoSlug = await getOriginRepoSlug(cwd);
81103
- const repoFlag = repoSlug ? ` --repo ${JSON.stringify(repoSlug)}` : "";
81104
81914
  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);
81915
+ const result = await runGh([
81916
+ "pr",
81917
+ "list",
81918
+ "--head",
81919
+ branch,
81920
+ "--state",
81921
+ "open",
81922
+ "--author",
81923
+ "@me",
81924
+ "--json",
81925
+ "number,title,url,headRefName,state,headRepositoryOwner",
81926
+ "--limit",
81927
+ "5",
81928
+ ...repoArgs(repoSlug)
81929
+ ], cwd);
81106
81930
  if ("error" in result) {
81107
81931
  return [];
81108
81932
  }
@@ -81133,8 +81957,17 @@ function isGHPRItem(item) {
81133
81957
  }
81134
81958
  async function getAllPRs(cwd) {
81135
81959
  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);
81960
+ const result = await runGh([
81961
+ "pr",
81962
+ "list",
81963
+ "--limit",
81964
+ "20",
81965
+ "--state",
81966
+ "all",
81967
+ "--json",
81968
+ "number,title,state,headRefName,baseRefName,url,author,createdAt,updatedAt,mergedAt,closedAt,isDraft",
81969
+ ...repoArgs(repoSlug)
81970
+ ], cwd);
81138
81971
  if ("error" in result) {
81139
81972
  return [];
81140
81973
  }
@@ -81168,8 +82001,7 @@ async function getAllPRs(cwd) {
81168
82001
  }
81169
82002
  async function getPRCommits(cwd, prNumber) {
81170
82003
  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);
82004
+ const result = await runGh(["pr", "view", String(prNumber), "--json", "commits", ...repoArgs(repoSlug)], cwd);
81173
82005
  if ("error" in result) {
81174
82006
  return [];
81175
82007
  }
@@ -81206,7 +82038,7 @@ async function getPRCommits(cwd, prNumber) {
81206
82038
  }
81207
82039
  }
81208
82040
  async function getRemotes(cwd) {
81209
- const result = await runGit("remote -v", cwd);
82041
+ const result = await runGit(["remote", "-v"], cwd);
81210
82042
  if ("error" in result)
81211
82043
  return [];
81212
82044
  if (!result.stdout.trim())
@@ -81230,14 +82062,14 @@ async function getRemotes(cwd) {
81230
82062
  return remotes;
81231
82063
  }
81232
82064
  async function getCommitsAhead(workingDir) {
81233
- const result = await runGit("rev-list --count @{upstream}..HEAD", workingDir);
82065
+ const result = await runGit(["rev-list", "--count", "@{upstream}..HEAD"], workingDir);
81234
82066
  if ("error" in result)
81235
82067
  return 0;
81236
82068
  const count3 = parseInt(result.stdout.trim(), 10);
81237
82069
  return Number.isNaN(count3) ? 0 : count3;
81238
82070
  }
81239
82071
  async function getCommitsBehind(workingDir) {
81240
- const result = await runGit("rev-list --count HEAD..@{upstream}", workingDir);
82072
+ const result = await runGit(["rev-list", "--count", "HEAD..@{upstream}"], workingDir);
81241
82073
  if ("error" in result)
81242
82074
  return 0;
81243
82075
  const count3 = parseInt(result.stdout.trim(), 10);
@@ -81249,8 +82081,18 @@ async function getCommitStatusChecks(cwd, ref) {
81249
82081
  return null;
81250
82082
  try {
81251
82083
  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)
82084
+ runGh([
82085
+ "api",
82086
+ `repos/${repoSlug}/commits/${encodeURIComponent(ref)}/check-runs`,
82087
+ "--jq",
82088
+ "{check_runs: [.check_runs[] | {name: .name, status: .status, conclusion: .conclusion}], total_count: .total_count}"
82089
+ ], cwd),
82090
+ runGh([
82091
+ "api",
82092
+ `repos/${repoSlug}/commits/${encodeURIComponent(ref)}/statuses`,
82093
+ "--jq",
82094
+ "[group_by(.context)[] | max_by(.created_at) | {context: .context, state: .state, target_url: .target_url}]"
82095
+ ], cwd)
81254
82096
  ]);
81255
82097
  if ("error" in checkRunsResult || "error" in legacyStatusesResult)
81256
82098
  return null;
@@ -81303,49 +82145,34 @@ async function getCommitStatusChecks(cwd, ref) {
81303
82145
  return null;
81304
82146
  }
81305
82147
  }
81306
- var execAsync2;
81307
82148
  var init_git_reader = __esm(() => {
81308
- execAsync2 = promisify2(exec2);
82149
+ init_run_command();
81309
82150
  });
81310
82151
 
81311
82152
  // 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
82153
  function classifyError2(errMessage) {
81327
82154
  return { status: "error", message: errMessage.trim() };
81328
82155
  }
81329
82156
  async function discardFile(workingDir, filePath) {
81330
- const result = await runGit2(`checkout -- ${filePath}`, workingDir);
82157
+ const result = await runGit(["checkout", "--", filePath], workingDir);
81331
82158
  if ("error" in result) {
81332
82159
  return classifyError2(result.error.message);
81333
82160
  }
81334
82161
  return { status: "available" };
81335
82162
  }
81336
82163
  async function discardAllChanges(workingDir) {
81337
- const checkoutResult = await runGit2("checkout -- .", workingDir);
82164
+ const checkoutResult = await runGit(["checkout", "--", "."], workingDir);
81338
82165
  if ("error" in checkoutResult) {
81339
82166
  return classifyError2(checkoutResult.error.message);
81340
82167
  }
81341
- const cleanResult = await runGit2("clean -fd", workingDir);
82168
+ const cleanResult = await runGit(["clean", "-fd"], workingDir);
81342
82169
  if ("error" in cleanResult) {
81343
82170
  return classifyError2(cleanResult.error.message);
81344
82171
  }
81345
82172
  return { status: "available" };
81346
82173
  }
81347
82174
  async function gitPull(workingDir) {
81348
- const result = await runGit2("pull", workingDir);
82175
+ const result = await runGit(["pull"], workingDir);
81349
82176
  if ("error" in result) {
81350
82177
  const message = result.error.message;
81351
82178
  if (message.includes("no tracking branch")) {
@@ -81372,7 +82199,7 @@ async function gitPull(workingDir) {
81372
82199
  return { status: "available" };
81373
82200
  }
81374
82201
  async function gitPush(workingDir) {
81375
- const result = await runGit2("push", workingDir);
82202
+ const result = await runGit(["push"], workingDir);
81376
82203
  if ("error" in result) {
81377
82204
  const message = result.error.message;
81378
82205
  if (message.includes("no upstream branch")) {
@@ -81402,9 +82229,8 @@ async function gitSync(workingDir) {
81402
82229
  }
81403
82230
  return gitPush(workingDir);
81404
82231
  }
81405
- var execAsync3;
81406
82232
  var init_git_writer = __esm(() => {
81407
- execAsync3 = promisify3(exec3);
82233
+ init_run_command();
81408
82234
  });
81409
82235
 
81410
82236
  // src/infrastructure/git/index.ts
@@ -81414,7 +82240,7 @@ var init_git = __esm(() => {
81414
82240
  });
81415
82241
 
81416
82242
  // src/infrastructure/local-actions/execute-local-action.ts
81417
- import { exec as exec4 } from "node:child_process";
82243
+ import { exec as exec3 } from "node:child_process";
81418
82244
  import { access as access3 } from "node:fs/promises";
81419
82245
  function escapeShellArg(arg) {
81420
82246
  return `"${arg.replace(/"/g, "\\\"")}"`;
@@ -81423,14 +82249,14 @@ function resolveWhichCommand(name) {
81423
82249
  return process.platform === "win32" ? `where ${name}` : `which ${name}`;
81424
82250
  }
81425
82251
  function isCliAvailable(cliName) {
81426
- return new Promise((resolve3) => {
81427
- exec4(resolveWhichCommand(cliName), (err) => {
81428
- resolve3(!err);
82252
+ return new Promise((resolve4) => {
82253
+ exec3(resolveWhichCommand(cliName), (err) => {
82254
+ resolve4(!err);
81429
82255
  });
81430
82256
  });
81431
82257
  }
81432
82258
  function execFireAndForget(command, logTag) {
81433
- exec4(command, (err) => {
82259
+ exec3(command, (err) => {
81434
82260
  if (err) {
81435
82261
  console.warn(`[${logTag}] exec failed: ${err.message}`);
81436
82262
  }
@@ -81546,13 +82372,13 @@ var init_local_actions = __esm(() => {
81546
82372
  });
81547
82373
 
81548
82374
  // src/commands/machine/pid.ts
81549
- import { createHash as createHash3 } from "node:crypto";
82375
+ import { createHash as createHash5 } from "node:crypto";
81550
82376
  import { existsSync as existsSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, mkdirSync as mkdirSync5 } from "node:fs";
81551
82377
  import { homedir as homedir6 } from "node:os";
81552
82378
  import { join as join17 } from "node:path";
81553
82379
  function getUrlHash2() {
81554
82380
  const url2 = getConvexUrl();
81555
- return createHash3("sha256").update(url2).digest("hex").substring(0, 8);
82381
+ return createHash5("sha256").update(url2).digest("hex").substring(0, 8);
81556
82382
  }
81557
82383
  function getPidFileName() {
81558
82384
  return `daemon-${getUrlHash2()}.pid`;
@@ -81645,7 +82471,7 @@ async function waitForLockOrTimeout(deadline, intervalMs, sleep5) {
81645
82471
  async function acquireLockWithRetry(options) {
81646
82472
  const intervalMs = options?.intervalMs ?? LOCK_RETRY_INTERVAL_MS;
81647
82473
  const maxWaitMs = options?.maxWaitMs ?? LOCK_RETRY_MAX_WAIT_MS;
81648
- const sleep5 = options?.sleep ?? ((ms) => new Promise((resolve3) => setTimeout(resolve3, ms)));
82474
+ const sleep5 = options?.sleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
81649
82475
  const deadline = Date.now() + maxWaitMs;
81650
82476
  if (await waitForLockOrTimeout(deadline, intervalMs, sleep5)) {
81651
82477
  return true;
@@ -82573,7 +83399,7 @@ var init_jsonc = __esm(() => {
82573
83399
 
82574
83400
  // src/infrastructure/services/workspace/workspace-resolver.ts
82575
83401
  import { readFile as readFile7, readdir, stat as stat2 } from "node:fs/promises";
82576
- import { join as join18, basename, resolve as resolve3 } from "node:path";
83402
+ import { join as join18, basename as basename2, resolve as resolve4 } from "node:path";
82577
83403
  async function resolveGlobPatternStar(rootDir, cleaned) {
82578
83404
  const parentDir = join18(rootDir, cleaned.slice(0, -2));
82579
83405
  try {
@@ -82583,7 +83409,7 @@ async function resolveGlobPatternStar(rootDir, cleaned) {
82583
83409
  if (!entry.isDirectory())
82584
83410
  continue;
82585
83411
  const dirPath = join18(parentDir, entry.name);
82586
- if (resolve3(dirPath).startsWith(resolve3(rootDir))) {
83412
+ if (resolve4(dirPath).startsWith(resolve4(rootDir))) {
82587
83413
  dirs.push(dirPath);
82588
83414
  }
82589
83415
  }
@@ -82595,7 +83421,7 @@ async function resolveGlobPatternStar(rootDir, cleaned) {
82595
83421
  async function resolveLiteralPath(rootDir, cleaned) {
82596
83422
  const dir = join18(rootDir, cleaned);
82597
83423
  try {
82598
- if (!resolve3(dir).startsWith(resolve3(rootDir)))
83424
+ if (!resolve4(dir).startsWith(resolve4(rootDir)))
82599
83425
  return [];
82600
83426
  const s = await stat2(dir);
82601
83427
  if (s.isDirectory())
@@ -82616,7 +83442,7 @@ async function readPackageJson(dir) {
82616
83442
  const content = await readFile7(join18(dir, "package.json"), "utf-8");
82617
83443
  const pkg = JSON.parse(content);
82618
83444
  return {
82619
- name: pkg.name || basename(dir),
83445
+ name: pkg.name || basename2(dir),
82620
83446
  scripts: pkg.scripts && typeof pkg.scripts === "object" ? pkg.scripts : {}
82621
83447
  };
82622
83448
  } catch {
@@ -82701,7 +83527,7 @@ var init_workspace_resolver = () => {};
82701
83527
 
82702
83528
  // src/infrastructure/services/workspace/command-discovery.ts
82703
83529
  import { access as access4, readFile as readFile8 } from "node:fs/promises";
82704
- import { join as join19, relative, basename as basename2 } from "node:path";
83530
+ import { join as join19, relative as relative2, basename as basename3 } from "node:path";
82705
83531
  async function detectPackageManager(workingDir) {
82706
83532
  for (const { file, manager } of LOCKFILE_MAP) {
82707
83533
  try {
@@ -82773,7 +83599,7 @@ function collectRootScriptCommands(scripts, pm, scriptPrefix, rootSw) {
82773
83599
  return commands;
82774
83600
  }
82775
83601
  async function readRootPackageJson(workingDir, pm, scriptPrefix) {
82776
- let rootPackageName = basename2(workingDir);
83602
+ let rootPackageName = basename3(workingDir);
82777
83603
  const pkg = await readJsonFile(join19(workingDir, "package.json"), "root package.json");
82778
83604
  if (!pkg)
82779
83605
  return { commands: [], rootPackageName };
@@ -82819,7 +83645,7 @@ async function discoverCommands(workingDir) {
82819
83645
  }
82820
83646
  const subWorkspaces = await resolveSubWorkspaces(workingDir, pm);
82821
83647
  for (const pkg of subWorkspaces) {
82822
- const wsPath = relative(workingDir, pkg.dir) || ".";
83648
+ const wsPath = relative2(workingDir, pkg.dir) || ".";
82823
83649
  const pkgSubWorkspace = { type: "npm", path: wsPath, name: pkg.name };
82824
83650
  await addSubWorkspaceCommands(commands, pm, pkg, wsPath, pkgSubWorkspace, turboTaskNames);
82825
83651
  }
@@ -82859,14 +83685,14 @@ var init_command_discovery = __esm(() => {
82859
83685
  });
82860
83686
 
82861
83687
  // src/commands/machine/daemon-start/command-sync-heartbeat.ts
82862
- import { createHash as createHash4 } from "node:crypto";
83688
+ import { createHash as createHash6 } from "node:crypto";
82863
83689
  var pushCommandsEffect, pushSingleWorkspaceCommandsEffect = (workingDir) => exports_Effect.gen(function* () {
82864
83690
  const session2 = yield* DaemonSessionService;
82865
83691
  const mutable = yield* DaemonMutableStateService;
82866
83692
  const lastPushedGitState = yield* exports_Ref.get(mutable.lastPushedGitState);
82867
83693
  const commands = yield* exports_Effect.promise(() => discoverCommands(workingDir));
82868
83694
  const stateKey = `commands:${session2.machineId}::${workingDir}`;
82869
- const commandsHash = createHash4("md5").update(JSON.stringify(commands)).digest("hex");
83695
+ const commandsHash = createHash6("md5").update(JSON.stringify(commands)).digest("hex");
82870
83696
  if (lastPushedGitState.get(stateKey) === commandsHash) {
82871
83697
  return;
82872
83698
  }
@@ -82908,7 +83734,7 @@ var init_command_sync_heartbeat = __esm(() => {
82908
83734
  });
82909
83735
 
82910
83736
  // src/infrastructure/git/git-state-pipeline.ts
82911
- import { createHash as createHash5 } from "node:crypto";
83737
+ import { createHash as createHash7 } from "node:crypto";
82912
83738
 
82913
83739
  class GitStatePipeline {
82914
83740
  fields;
@@ -82940,7 +83766,7 @@ class GitStatePipeline {
82940
83766
  const raw = values3.get(field.key) ?? field.defaultValue;
82941
83767
  hashInput[field.key] = field.toHashable(raw);
82942
83768
  }
82943
- return createHash5("md5").update(JSON.stringify(hashInput)).digest("hex");
83769
+ return createHash7("md5").update(JSON.stringify(hashInput)).digest("hex");
82944
83770
  }
82945
83771
  toMutationArgs(values3, slim) {
82946
83772
  const args2 = {};
@@ -82988,7 +83814,7 @@ function buildGitStateDeps(session2, lastPushedGitState) {
82988
83814
  }
82989
83815
  async function pushSingleWorkspaceGitStateImpl(ctx, workingDir) {
82990
83816
  const stateKey = makeGitStateKey(ctx.machineId, workingDir);
82991
- const isRepo = await isGitRepo(workingDir);
83817
+ const isRepo = await isGitRepo2(workingDir);
82992
83818
  if (!isRepo) {
82993
83819
  await pushNotFoundGitState(ctx, workingDir, stateKey);
82994
83820
  return;
@@ -83133,7 +83959,7 @@ var lastFullPushMs, branchField, GIT_STATE_FIELDS, pushSingleWorkspaceGitSummary
83133
83959
  const mutable = yield* DaemonMutableStateService;
83134
83960
  const lastPushedGitState = yield* exports_Ref.get(mutable.lastPushedGitState);
83135
83961
  const stateKey = makeGitStateKey(session2.machineId, workingDir);
83136
- const isRepo = yield* exports_Effect.promise(() => isGitRepo(workingDir));
83962
+ const isRepo = yield* exports_Effect.promise(() => isGitRepo2(workingDir));
83137
83963
  if (!isRepo) {
83138
83964
  yield* pushObservedNotRepoEffect(session2, lastPushedGitState, stateKey, workingDir, reason);
83139
83965
  return;
@@ -83270,9 +84096,7 @@ var init_git_heartbeat = __esm(() => {
83270
84096
  });
83271
84097
 
83272
84098
  // 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";
84099
+ import { gzipSync as gzipSync4 } from "node:zlib";
83276
84100
  function extractDiffStatFromShowOutput(content) {
83277
84101
  for (const line of content.split(`
83278
84102
  `)) {
@@ -83287,7 +84111,7 @@ async function processFullDiff(deps, req) {
83287
84111
  if (result.status === "available" || result.status === "truncated") {
83288
84112
  const diffStatResult = await getDiffStat(req.workingDir);
83289
84113
  const diffStat = diffStatResult.status === "available" ? diffStatResult.diffStat : { filesChanged: 0, insertions: 0, deletions: 0 };
83290
- const compressed = gzipSync2(Buffer.from(result.content));
84114
+ const compressed = gzipSync4(Buffer.from(result.content));
83291
84115
  const diffContentCompressed = compressed.toString("base64");
83292
84116
  await deps.backend.mutation(api.workspaces.upsertFullDiffV2, {
83293
84117
  sessionId: deps.sessionId,
@@ -83299,7 +84123,7 @@ async function processFullDiff(deps, req) {
83299
84123
  });
83300
84124
  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
84125
  } else {
83302
- const emptyCompressed = gzipSync2(Buffer.from("")).toString("base64");
84126
+ const emptyCompressed = gzipSync4(Buffer.from("")).toString("base64");
83303
84127
  await deps.backend.mutation(api.workspaces.upsertFullDiffV2, {
83304
84128
  sessionId: deps.sessionId,
83305
84129
  machineId: deps.machineId,
@@ -83349,25 +84173,24 @@ async function processPRAction(deps, req) {
83349
84173
  if (!prNumber || !action) {
83350
84174
  throw new Error("pr_action request missing prNumber or prAction");
83351
84175
  }
83352
- let cmd;
84176
+ let ghArgs;
83353
84177
  switch (action) {
83354
84178
  case "merge_squash":
83355
- cmd = `gh pr merge ${prNumber} --squash --delete-branch`;
84179
+ ghArgs = ["pr", "merge", String(prNumber), "--squash", "--delete-branch"];
83356
84180
  break;
83357
84181
  case "merge_no_squash":
83358
- cmd = `gh pr merge ${prNumber} --merge`;
84182
+ ghArgs = ["pr", "merge", String(prNumber), "--merge"];
83359
84183
  break;
83360
84184
  case "close":
83361
- cmd = `gh pr close ${prNumber}`;
84185
+ ghArgs = ["pr", "close", String(prNumber)];
83362
84186
  break;
83363
84187
  default:
83364
84188
  throw new Error(`Unknown PR action: ${action}`);
83365
84189
  }
83366
- const execAsync4 = promisify4(exec5);
83367
- const result = await execAsync4(cmd, {
83368
- cwd: req.workingDir,
83369
- timeout: EXEC_TIMEOUT_MS
83370
- });
84190
+ const result = await runGh(ghArgs, req.workingDir, { timeout: EXEC_TIMEOUT_MS });
84191
+ if ("error" in result) {
84192
+ throw result.error;
84193
+ }
83371
84194
  console.log(`[${formatTimestamp()}] ✅ PR action: ${action} on #${prNumber}${result.stdout ? ` — ${result.stdout.trim()}` : ""}`);
83372
84195
  exports_Runtime.runFork(deps.runtime)(pushGitStateEffect.pipe(exports_Effect.provide(exports_Layer.mergeAll(exports_Layer.succeed(DaemonSessionService, deps), DaemonMutableStateServiceLive({
83373
84196
  lastPushedGitState: deps.lastPushedGitState,
@@ -83401,7 +84224,7 @@ async function processCommitDetail(deps, req) {
83401
84224
  ]);
83402
84225
  await upsertCommitDetailResult(deps, req, result, metadata);
83403
84226
  if (result.status === "available" || result.status === "truncated") {
83404
- const compressed = gzipSync2(Buffer.from(result.content));
84227
+ const compressed = gzipSync4(Buffer.from(result.content));
83405
84228
  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
84229
  }
83407
84230
  }
@@ -83432,7 +84255,7 @@ async function upsertCommitDetailResult(deps, req, result, metadata) {
83432
84255
  return;
83433
84256
  }
83434
84257
  const diffStat = extractDiffStatFromShowOutput(result.content);
83435
- const compressed = gzipSync2(Buffer.from(result.content));
84258
+ const compressed = gzipSync4(Buffer.from(result.content));
83436
84259
  const diffContentCompressed = compressed.toString("base64");
83437
84260
  await deps.backend.mutation(api.workspaces.upsertCommitDetailV2, {
83438
84261
  ...baseArgs,
@@ -83581,11 +84404,12 @@ var init_git_subscription = __esm(() => {
83581
84404
  init_git_heartbeat();
83582
84405
  init_api3();
83583
84406
  init_git_reader();
84407
+ init_run_command();
83584
84408
  init_convex_error();
83585
84409
  });
83586
84410
 
83587
84411
  // src/commands/machine/daemon-start/commit-detail-sync.ts
83588
- import { gzipSync as gzipSync3 } from "node:zlib";
84412
+ import { gzipSync as gzipSync5 } from "node:zlib";
83589
84413
  function upsertCommitDetailArgs(session2, workingDir, sha, metadata, overrides) {
83590
84414
  return {
83591
84415
  sessionId: session2.sessionId,
@@ -83614,7 +84438,7 @@ var seenShas, prefetchSingleShaEffect = (session2, workingDir, sha, commits) =>
83614
84438
  return;
83615
84439
  }
83616
84440
  const diffStat = extractDiffStatFromShowOutput(result.content);
83617
- const compressed = gzipSync3(Buffer.from(result.content));
84441
+ const compressed = gzipSync5(Buffer.from(result.content));
83618
84442
  const diffContentCompressed = compressed.toString("base64");
83619
84443
  yield* exports_Effect.tryPromise(() => session2.backend.mutation(api.workspaces.upsertCommitDetailV2, upsertCommitDetailArgs(session2, workingDir, sha, metadata, {
83620
84444
  status: "available",
@@ -84035,14 +84859,14 @@ var init_sse_event_buffer = __esm(() => {
84035
84859
  }
84036
84860
  _wake() {
84037
84861
  if (this._waiter) {
84038
- const resolve4 = this._waiter;
84862
+ const resolve5 = this._waiter;
84039
84863
  this._waiter = null;
84040
- resolve4();
84864
+ resolve5();
84041
84865
  }
84042
84866
  }
84043
84867
  _waitForData() {
84044
- return new Promise((resolve4) => {
84045
- this._waiter = resolve4;
84868
+ return new Promise((resolve5) => {
84869
+ this._waiter = resolve5;
84046
84870
  });
84047
84871
  }
84048
84872
  [Symbol.asyncIterator]() {
@@ -84097,8 +84921,8 @@ class OpencodeSdkSession {
84097
84921
  async prompt(input) {
84098
84922
  if (this.closed)
84099
84923
  throw new Error("Session is closed");
84100
- const idlePromise = new Promise((resolve4) => {
84101
- this._idleResolve = resolve4;
84924
+ const idlePromise = new Promise((resolve5) => {
84925
+ this._idleResolve = resolve5;
84102
84926
  });
84103
84927
  const IDLE_TIMEOUT_MS = 300000;
84104
84928
  const timeoutPromise = new Promise((_, reject) => {
@@ -84182,7 +85006,7 @@ var init_opencode_session = __esm(() => {
84182
85006
  });
84183
85007
 
84184
85008
  // src/infrastructure/harnesses/opencode-sdk/opencode-harness.ts
84185
- import { spawn as spawn4 } from "node:child_process";
85009
+ import { spawn as spawn6 } from "node:child_process";
84186
85010
  function harnessEventSessionId(event) {
84187
85011
  const p = event.properties;
84188
85012
  if (!p)
@@ -84429,14 +85253,14 @@ class OpencodeSdkHarness {
84429
85253
  }
84430
85254
  this.sessionListeners.clear();
84431
85255
  this.childProcess.kill("SIGTERM");
84432
- await new Promise((resolve4) => {
85256
+ await new Promise((resolve5) => {
84433
85257
  const timeout3 = setTimeout(() => {
84434
85258
  this.childProcess.kill("SIGKILL");
84435
- resolve4();
85259
+ resolve5();
84436
85260
  }, 5000);
84437
85261
  this.childProcess.once("exit", () => {
84438
85262
  clearTimeout(timeout3);
84439
- resolve4();
85263
+ resolve5();
84440
85264
  });
84441
85265
  });
84442
85266
  }
@@ -84456,7 +85280,7 @@ class OpencodeSdkHarness {
84456
85280
  }
84457
85281
  }
84458
85282
  var OPENCODE_COMMAND3 = "opencode", SERVE_STARTUP_TIMEOUT_MS2 = 1e4, startOpencodeSdkHarness = async (config3) => {
84459
- const childProcess = spawn4(OPENCODE_COMMAND3, ["serve", "--print-logs"], {
85283
+ const childProcess = spawn6(OPENCODE_COMMAND3, ["serve", "--print-logs"], {
84460
85284
  cwd: config3.workingDir,
84461
85285
  stdio: ["pipe", "pipe", "pipe"],
84462
85286
  shell: false,
@@ -85818,10 +86642,10 @@ class BufferedJournalFactory {
85818
86642
  const waitForInProgress = () => {
85819
86643
  if (!flushInProgress)
85820
86644
  return Promise.resolve();
85821
- return new Promise((resolve4) => {
86645
+ return new Promise((resolve5) => {
85822
86646
  const check3 = () => {
85823
86647
  if (!flushInProgress)
85824
- resolve4();
86648
+ resolve5();
85825
86649
  else
85826
86650
  setTimeout(check3, 10);
85827
86651
  };
@@ -85939,21 +86763,64 @@ var init_start_subscriptions = __esm(() => {
85939
86763
  init_convex_session_repository();
85940
86764
  });
85941
86765
 
86766
+ // src/infrastructure/services/workspace/assert-registered-working-dir.ts
86767
+ async function assertRegisteredWorkingDir(session2, workingDir) {
86768
+ const workspaces = await getWorkspacesForMachine({
86769
+ workspaceListStore: session2.workspaceListStore,
86770
+ sessionId: session2.sessionId,
86771
+ machineId: session2.machineId,
86772
+ backend: session2.backend
86773
+ });
86774
+ if (!workspaces.some((w) => w.workingDir === workingDir)) {
86775
+ return { ok: false, error: "Workspace not registered for this machine" };
86776
+ }
86777
+ return { ok: true };
86778
+ }
86779
+ var init_assert_registered_working_dir = __esm(() => {
86780
+ init_workspace_cache();
86781
+ });
86782
+
85942
86783
  // src/commands/machine/daemon-start/file-content-fulfillment.ts
85943
86784
  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(".");
86785
+ import { gzipSync as gzipSync6 } from "node:zlib";
86786
+ function getErrorCause(error) {
86787
+ if (typeof error === "object" && error !== null && "cause" in error && error.cause !== undefined) {
86788
+ return error.cause;
86789
+ }
86790
+ return error;
86791
+ }
86792
+ function isENOENT(error) {
86793
+ const cause3 = getErrorCause(error);
86794
+ return typeof cause3 === "object" && cause3 !== null && "code" in cause3 && cause3.code === "ENOENT";
86795
+ }
86796
+ function isBinaryFile(path6) {
86797
+ const lastDot = path6.lastIndexOf(".");
85948
86798
  if (lastDot === -1)
85949
86799
  return false;
85950
- return BINARY_EXTENSIONS.has(path3.slice(lastDot).toLowerCase());
86800
+ return BINARY_EXTENSIONS.has(path6.slice(lastDot).toLowerCase());
86801
+ }
86802
+ function gzipPlainText(text) {
86803
+ return gzipSync6(Buffer.from(text)).toString("base64");
86804
+ }
86805
+ function fulfillGzippedContentEffect(session2, workingDir, filePath, plainText, truncated) {
86806
+ return exports_Effect.catchAll(exports_Effect.tryPromise(() => session2.backend.mutation(api.workspaceFiles.fulfillFileContentV2, {
86807
+ sessionId: session2.sessionId,
86808
+ machineId: session2.machineId,
86809
+ workingDir,
86810
+ filePath,
86811
+ data: { compression: "gzip", content: gzipPlainText(plainText) },
86812
+ encoding: "utf8",
86813
+ truncated
86814
+ })), () => exports_Effect.void);
85951
86815
  }
85952
86816
  var MAX_CONTENT_BYTES, BINARY_EXTENSIONS, fulfillFileContentRequestsEffect;
85953
86817
  var init_file_content_fulfillment = __esm(() => {
85954
86818
  init_esm();
85955
86819
  init_daemon_services();
85956
86820
  init_api3();
86821
+ init_assert_registered_working_dir();
86822
+ init_workspace_path_security();
86823
+ init_workspace_visibility_policy();
85957
86824
  MAX_CONTENT_BYTES = 500 * 1024;
85958
86825
  BINARY_EXTENSIONS = new Set([
85959
86826
  ".png",
@@ -86007,40 +86874,55 @@ var init_file_content_fulfillment = __esm(() => {
86007
86874
  for (const request2 of requests) {
86008
86875
  const startTime = Date.now();
86009
86876
  const { workingDir, filePath } = request2;
86010
- if (filePath.includes("..") || filePath.startsWith("/")) {
86011
- console.warn(`[${formatTimestamp()}] ⚠️ Rejected path traversal attempt: ${filePath}`);
86877
+ const registered = yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => assertRegisteredWorkingDir(session2, workingDir)), () => exports_Effect.succeed({ ok: false, error: "Workspace check failed" }));
86878
+ if (!registered.ok) {
86879
+ console.warn(`[${formatTimestamp()}] ⚠️ Rejected unregistered workspace: ${workingDir} (${registered.error})`);
86880
+ yield* fulfillGzippedContentEffect(session2, workingDir, filePath, "[Error: workspace not registered]", false);
86012
86881
  continue;
86013
86882
  }
86014
- const absolutePath = resolve4(workingDir, filePath);
86015
- if (!absolutePath.startsWith(resolve4(workingDir))) {
86016
- console.warn(`[${formatTimestamp()}] ⚠️ Path escapes workspace: ${absolutePath}`);
86883
+ const resolved = yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => resolvePathWithinWorkspace(workingDir, filePath)), () => exports_Effect.succeed({ ok: false, error: "Invalid file path" }));
86884
+ if (!resolved.ok) {
86885
+ console.warn(`[${formatTimestamp()}] ⚠️ Rejected file path: ${filePath} (${resolved.error})`);
86886
+ yield* fulfillGzippedContentEffect(session2, workingDir, filePath, "[Error reading file]", false);
86017
86887
  continue;
86018
86888
  }
86889
+ const absolutePath = resolved.absolutePath;
86019
86890
  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);
86891
+ yield* fulfillGzippedContentEffect(session2, workingDir, filePath, "[Binary file]", false);
86030
86892
  const elapsed4 = Date.now() - startTime;
86031
86893
  console.log(`[${formatTimestamp()}] \uD83D\uDCC4 File content synced to Convex: ${filePath} [binary] (${elapsed4}ms)`);
86032
86894
  continue;
86033
86895
  }
86034
- const { content, truncated } = yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => readFile9(absolutePath)).pipe(exports_Effect.map((buffer) => {
86896
+ if (!isPathContentReadable(filePath)) {
86897
+ yield* fulfillGzippedContentEffect(session2, workingDir, filePath, "[File blocked: cannot open sensitive path in remote explorer]", false);
86898
+ const elapsed4 = Date.now() - startTime;
86899
+ console.log(`[${formatTimestamp()}] \uD83D\uDCC4 File content blocked: ${filePath} [secret] (${elapsed4}ms)`);
86900
+ continue;
86901
+ }
86902
+ const readOutcome = yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => readFile9(absolutePath)).pipe(exports_Effect.map((buffer) => {
86035
86903
  if (buffer.length > MAX_CONTENT_BYTES) {
86036
86904
  return {
86905
+ kind: "ok",
86037
86906
  content: buffer.subarray(0, MAX_CONTENT_BYTES).toString("utf8"),
86038
86907
  truncated: true
86039
86908
  };
86040
86909
  }
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));
86910
+ return {
86911
+ kind: "ok",
86912
+ content: buffer.toString("utf8"),
86913
+ truncated: false
86914
+ };
86915
+ })), (error) => isENOENT(error) ? exports_Effect.succeed({ kind: "missing" }) : exports_Effect.succeed({
86916
+ kind: "error",
86917
+ content: "[Error reading file]",
86918
+ truncated: false
86919
+ }));
86920
+ if (readOutcome.kind === "missing") {
86921
+ console.log(`[${formatTimestamp()}] ⏳ File not on disk yet, deferring content sync: ${filePath}`);
86922
+ continue;
86923
+ }
86924
+ const { content, truncated } = readOutcome.kind === "ok" ? readOutcome : { content: readOutcome.content, truncated: readOutcome.truncated };
86925
+ const compressed = gzipSync6(Buffer.from(content));
86044
86926
  const contentCompressed = compressed.toString("base64");
86045
86927
  yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => session2.backend.mutation(api.workspaceFiles.fulfillFileContentV2, {
86046
86928
  sessionId: session2.sessionId,
@@ -86094,217 +86976,272 @@ var init_file_content_subscription = __esm(() => {
86094
86976
  init_convex_error();
86095
86977
  });
86096
86978
 
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
- };
86979
+ // src/commands/machine/daemon-start/file-write-errors.ts
86980
+ function unsupportedFileWriteOperationMessage(operation) {
86981
+ 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).";
86982
+ }
86983
+
86984
+ // src/commands/machine/daemon-start/file-write-fulfillment.ts
86985
+ import { access as access5, mkdir as mkdir8, rename as rename3, rm as rm2, writeFile as writeFile7 } from "node:fs/promises";
86986
+ import { dirname as dirname9 } from "node:path";
86987
+ import { gzipSync as gzipSync7 } from "node:zlib";
86988
+ function isTerminalFileWriteError(errorMessage) {
86989
+ const terminalMessages = new Set([
86990
+ "Invalid file path",
86991
+ "Path escapes workspace",
86992
+ "Missing file data",
86993
+ "File content too large",
86994
+ "File already exists",
86995
+ "File does not exist",
86996
+ "Cannot delete workspace root",
86997
+ "Target path already exists",
86998
+ "Target path is required for rename",
86999
+ "Rename target must differ from source path",
87000
+ "Directory already exists",
87001
+ "Workspace not registered for this machine"
87002
+ ]);
87003
+ if (errorMessage.startsWith("Unsupported file write operation"))
87004
+ return true;
87005
+ return terminalMessages.has(errorMessage);
87006
+ }
87007
+ function parentDirPath2(filePath) {
87008
+ const idx = filePath.lastIndexOf("/");
87009
+ return idx === -1 ? "" : filePath.slice(0, idx);
87010
+ }
87011
+ async function syncParentDirListingAfterWrite(session2, workingDir, filePath) {
87012
+ const dirPath = parentDirPath2(filePath);
87013
+ const listing = await listDirectory(workingDir, dirPath);
87014
+ const json = JSON.stringify(listing);
87015
+ const dataHash = computeDirListingContentHash(listing);
87016
+ const compressed = gzipSync7(Buffer.from(json)).toString("base64");
87017
+ await session2.backend.mutation(api.workspaceFiles.syncDirListingV2, {
87018
+ sessionId: session2.sessionId,
87019
+ machineId: session2.machineId,
87020
+ workingDir,
87021
+ dirPath,
87022
+ data: { compression: "gzip", content: compressed },
87023
+ dataHash,
87024
+ scannedAt: listing.scannedAt,
87025
+ truncated: listing.truncated,
87026
+ totalCount: listing.totalCount
87027
+ });
86113
87028
  }
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
- }
87029
+ async function completeWriteRequest(session2, requestId, result) {
87030
+ await session2.backend.mutation(api.workspaceFiles.completeFileWriteRequest, {
87031
+ sessionId: session2.sessionId,
87032
+ requestId,
87033
+ status: result.status,
87034
+ errorMessage: result.status === "error" ? result.errorMessage : undefined
87035
+ });
86125
87036
  }
86126
- async function walkFsFallback(rootDir, maxEntries, maxSubtreeBytes) {
86127
- const collected = [];
86128
- await walkSubtree(rootDir, "", collected, maxEntries, maxSubtreeBytes);
86129
- return collected;
87037
+ async function fileExistsAt(absolutePath) {
87038
+ return access5(absolutePath).then(() => true).catch(() => false);
86130
87039
  }
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;
87040
+ function decodeWritePayload(request2) {
87041
+ if (!request2.data) {
87042
+ return { ok: false, errorMessage: "Missing file data" };
86140
87043
  }
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
- }
87044
+ return gunzipBase64Payload(request2.data.content, MAX_CONTENT_BYTES2);
87045
+ }
87046
+ async function validateWriteOperation(operation, absolutePath) {
87047
+ const exists3 = await fileExistsAt(absolutePath);
87048
+ if (operation === "create" && exists3) {
87049
+ return { ok: false, errorMessage: "File already exists" };
86170
87050
  }
86171
- for (const p of staged) {
86172
- if (collected.length >= maxEntries)
86173
- break;
86174
- collected.push(p);
87051
+ if (operation === "update" && !exists3) {
87052
+ return { ok: false, errorMessage: "File does not exist" };
86175
87053
  }
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 [];
87054
+ if (operation === "delete" && !exists3) {
87055
+ return { ok: false, errorMessage: "File does not exist" };
86202
87056
  }
87057
+ return { ok: true };
86203
87058
  }
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));
87059
+ async function writePayloadToDisk(absolutePath, operation, content) {
87060
+ if (operation === "create") {
87061
+ await mkdir8(dirname9(absolutePath), { recursive: true });
87062
+ }
87063
+ await writeFile7(absolutePath, content);
86214
87064
  }
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
- }
87065
+ async function fulfillOneFileWriteRequest(session2, request2) {
87066
+ const startTime = Date.now();
87067
+ const { workingDir, filePath, operation } = request2;
87068
+ const registered = await assertRegisteredWorkingDir(session2, workingDir);
87069
+ if (!registered.ok) {
87070
+ await completeWriteRequest(session2, request2._id, {
87071
+ status: "error",
87072
+ errorMessage: registered.error
87073
+ });
87074
+ return;
86222
87075
  }
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" });
87076
+ const resolved = await resolvePathWithinWorkspace(workingDir, filePath);
87077
+ if (!resolved.ok) {
87078
+ await completeWriteRequest(session2, request2._id, {
87079
+ status: "error",
87080
+ errorMessage: resolved.error
87081
+ });
87082
+ return;
86229
87083
  }
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" });
87084
+ try {
87085
+ if (operation === "rename") {
87086
+ if (!request2.targetFilePath) {
87087
+ await completeWriteRequest(session2, request2._id, {
87088
+ status: "error",
87089
+ errorMessage: "Target path is required for rename"
87090
+ });
87091
+ return;
87092
+ }
87093
+ if (request2.targetFilePath === filePath) {
87094
+ await completeWriteRequest(session2, request2._id, {
87095
+ status: "error",
87096
+ errorMessage: "Rename target must differ from source path"
87097
+ });
87098
+ return;
87099
+ }
87100
+ const targetResolved = await resolvePathWithinWorkspace(workingDir, request2.targetFilePath);
87101
+ if (!targetResolved.ok) {
87102
+ await completeWriteRequest(session2, request2._id, {
87103
+ status: "error",
87104
+ errorMessage: targetResolved.error
87105
+ });
87106
+ return;
87107
+ }
87108
+ const sourceExists = await fileExistsAt(resolved.absolutePath);
87109
+ if (!sourceExists) {
87110
+ await completeWriteRequest(session2, request2._id, {
87111
+ status: "error",
87112
+ errorMessage: "File does not exist"
87113
+ });
87114
+ return;
87115
+ }
87116
+ const targetExists = await fileExistsAt(targetResolved.absolutePath);
87117
+ if (targetExists) {
87118
+ await completeWriteRequest(session2, request2._id, {
87119
+ status: "error",
87120
+ errorMessage: "Target path already exists"
87121
+ });
87122
+ return;
87123
+ }
87124
+ await mkdir8(dirname9(targetResolved.absolutePath), { recursive: true });
87125
+ await rename3(resolved.absolutePath, targetResolved.absolutePath);
87126
+ await syncParentDirListingAfterWrite(session2, workingDir, filePath);
87127
+ if (parentDirPath2(filePath) !== parentDirPath2(request2.targetFilePath)) {
87128
+ await syncParentDirListingAfterWrite(session2, workingDir, request2.targetFilePath);
87129
+ }
87130
+ await completeWriteRequest(session2, request2._id, { status: "done" });
87131
+ const elapsed4 = Date.now() - startTime;
87132
+ console.log(`[${formatTimestamp()}] ✏️ File rename fulfilled: ${filePath} → ${request2.targetFilePath} (${elapsed4}ms)`);
87133
+ return;
87134
+ }
87135
+ if (operation === "mkdir") {
87136
+ const exists3 = await fileExistsAt(resolved.absolutePath);
87137
+ if (exists3) {
87138
+ await completeWriteRequest(session2, request2._id, {
87139
+ status: "error",
87140
+ errorMessage: "Directory already exists"
87141
+ });
87142
+ return;
87143
+ }
87144
+ await mkdir8(resolved.absolutePath, { recursive: true });
87145
+ await syncParentDirListingAfterWrite(session2, workingDir, filePath);
87146
+ await completeWriteRequest(session2, request2._id, { status: "done" });
87147
+ const elapsed4 = Date.now() - startTime;
87148
+ console.log(`[${formatTimestamp()}] ✏️ Directory mkdir fulfilled: ${filePath} (${elapsed4}ms)`);
87149
+ return;
87150
+ }
87151
+ if (operation === "delete") {
87152
+ if (filePath === "") {
87153
+ await completeWriteRequest(session2, request2._id, {
87154
+ status: "error",
87155
+ errorMessage: "Cannot delete workspace root"
87156
+ });
87157
+ return;
87158
+ }
87159
+ const operationCheck2 = await validateWriteOperation(operation, resolved.absolutePath);
87160
+ if (!operationCheck2.ok) {
87161
+ await completeWriteRequest(session2, request2._id, {
87162
+ status: "error",
87163
+ errorMessage: operationCheck2.errorMessage
87164
+ });
87165
+ return;
87166
+ }
87167
+ await rm2(resolved.absolutePath, { recursive: true, force: false });
87168
+ await syncParentDirListingAfterWrite(session2, workingDir, filePath);
87169
+ await completeWriteRequest(session2, request2._id, { status: "done" });
87170
+ const elapsed4 = Date.now() - startTime;
87171
+ console.log(`[${formatTimestamp()}] ✏️ File delete fulfilled: ${filePath} (${elapsed4}ms)`);
87172
+ return;
87173
+ }
87174
+ if (operation !== "create" && operation !== "update") {
87175
+ await completeWriteRequest(session2, request2._id, {
87176
+ status: "error",
87177
+ errorMessage: unsupportedFileWriteOperationMessage(operation)
87178
+ });
87179
+ return;
87180
+ }
87181
+ const payload = decodeWritePayload(request2);
87182
+ if (!payload.ok) {
87183
+ await completeWriteRequest(session2, request2._id, {
87184
+ status: "error",
87185
+ errorMessage: payload.errorMessage
87186
+ });
87187
+ return;
87188
+ }
87189
+ const operationCheck = await validateWriteOperation(operation, resolved.absolutePath);
87190
+ if (!operationCheck.ok) {
87191
+ await completeWriteRequest(session2, request2._id, {
87192
+ status: "error",
87193
+ errorMessage: operationCheck.errorMessage
87194
+ });
87195
+ return;
87196
+ }
87197
+ await writePayloadToDisk(resolved.absolutePath, operation, payload.content);
87198
+ await syncParentDirListingAfterWrite(session2, workingDir, filePath);
87199
+ await completeWriteRequest(session2, request2._id, { status: "done" });
87200
+ const elapsed3 = Date.now() - startTime;
87201
+ console.log(`[${formatTimestamp()}] ✏️ File write fulfilled: ${filePath} (${operation}, ${(payload.content.length / 1024).toFixed(1)}KB, ${elapsed3}ms)`);
87202
+ } catch (err) {
87203
+ const message = err instanceof Error ? err.message : "Write failed";
87204
+ if (isTerminalFileWriteError(message)) {
87205
+ await completeWriteRequest(session2, request2._id, {
87206
+ status: "error",
87207
+ errorMessage: message
87208
+ });
87209
+ console.warn(`[${formatTimestamp()}] ⚠️ File write failed for ${filePath}: ${message}`);
87210
+ return;
87211
+ }
87212
+ console.warn(`[${formatTimestamp()}] ⚠️ File write transient failure for ${filePath}: ${message} (will retry)`);
86235
87213
  }
86236
- return entries2;
86237
87214
  }
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
- ];
87215
+ var MAX_CONTENT_BYTES2, fulfillFileWriteRequestsEffect;
87216
+ var init_file_write_fulfillment = __esm(() => {
87217
+ init_esm();
87218
+ init_daemon_services();
87219
+ init_api3();
87220
+ init_assert_registered_working_dir();
87221
+ init_dir_listing_content_hash();
87222
+ init_dir_listing_scanner();
87223
+ init_workspace_path_security();
87224
+ MAX_CONTENT_BYTES2 = 512 * 1024;
87225
+ fulfillFileWriteRequestsEffect = exports_Effect.gen(function* () {
87226
+ const session2 = yield* DaemonSessionService;
87227
+ const requests = yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => session2.backend.query(api.workspaceFiles.getPendingFileWriteRequests, {
87228
+ sessionId: session2.sessionId,
87229
+ machineId: session2.machineId
87230
+ })), () => exports_Effect.succeed([]));
87231
+ if (requests.length === 0)
87232
+ return;
87233
+ console.log(`[${formatTimestamp()}] ✏️ Received ${requests.length} pending file write request(s): ${requests.map((r) => r.filePath).join(", ")}`);
87234
+ for (const request2 of requests) {
87235
+ yield* exports_Effect.catchAll(exports_Effect.tryPromise(() => fulfillOneFileWriteRequest(session2, request2)), () => exports_Effect.void);
87236
+ }
87237
+ });
86270
87238
  });
86271
87239
 
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* () {
87240
+ // src/commands/machine/daemon-start/file-write-subscription.ts
87241
+ var startFileWriteSubscriptionEffect = (wsClient2) => exports_Effect.gen(function* () {
86305
87242
  const session2 = yield* DaemonSessionService;
86306
87243
  let processing = false;
86307
- const unsubscribe = wsClient2.onUpdate(api.workspaceFiles.getPendingFileTreeRequests, {
87244
+ const unsubscribe = wsClient2.onUpdate(api.workspaceFiles.getPendingFileWriteRequests, {
86308
87245
  sessionId: session2.sessionId,
86309
87246
  machineId: session2.machineId
86310
87247
  }, (requests) => {
@@ -86313,27 +87250,27 @@ var fulfillFileTreeRequestsEffect = (session2, requests) => exports_Effect.gen(f
86313
87250
  if (processing)
86314
87251
  return;
86315
87252
  processing = true;
86316
- exports_Effect.runPromise(fulfillFileTreeRequestsEffect(session2, requests)).catch((err) => {
86317
- console.warn(`[${formatTimestamp()}] ⚠️ File tree subscription processing failed: ${getErrorMessage(err)}`);
87253
+ exports_Effect.runPromise(fulfillFileWriteRequestsEffect.pipe(exports_Effect.provideService(DaemonSessionService, session2))).catch((err) => {
87254
+ console.warn(`[${formatTimestamp()}] ⚠️ File write subscription processing failed: ${getErrorMessage(err)}`);
86318
87255
  }).finally(() => {
86319
87256
  processing = false;
86320
87257
  });
86321
87258
  }, (err) => {
86322
- console.warn(`[${formatTimestamp()}] ⚠️ File tree subscription error: ${getErrorMessage(err)}`);
87259
+ console.warn(`[${formatTimestamp()}] ⚠️ File write subscription error: ${getErrorMessage(err)}`);
86323
87260
  });
86324
- console.log(`[${formatTimestamp()}] \uD83C\uDF33 File tree subscription started (reactive)`);
87261
+ console.log(`[${formatTimestamp()}] ✏️ File write subscription started (reactive)`);
86325
87262
  return {
86326
87263
  stop: () => {
86327
87264
  unsubscribe();
86328
- console.log(`[${formatTimestamp()}] \uD83C\uDF33 File tree subscription stopped`);
87265
+ console.log(`[${formatTimestamp()}] ✏️ File write subscription stopped`);
86329
87266
  }
86330
87267
  };
86331
87268
  });
86332
- var init_file_tree_subscription = __esm(() => {
87269
+ var init_file_write_subscription = __esm(() => {
86333
87270
  init_esm();
86334
87271
  init_daemon_services();
87272
+ init_file_write_fulfillment();
86335
87273
  init_api3();
86336
- init_file_tree_scanner();
86337
87274
  init_convex_error();
86338
87275
  });
86339
87276
 
@@ -88805,11 +89742,11 @@ class AgentProcessManager {
88805
89742
  const initPrompt = await this.fetchInitPromptResult(opts, slot);
88806
89743
  if (!initPrompt.ok)
88807
89744
  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 };
89745
+ const spawn7 = await this.spawnAgentForEnsureRunning(key, slot, opts, initPrompt, wantResume);
89746
+ if (!spawn7.ok)
89747
+ return spawn7.result;
89748
+ await this.finalizeRunningSlot(key, slot, opts, spawn7.spawnResult, wantResume);
89749
+ return { success: true, pid: spawn7.spawnResult.pid };
88813
89750
  } catch (e) {
88814
89751
  this.resetSlotIdle(slot);
88815
89752
  return { success: false, error: `Unexpected error: ${e.message}` };
@@ -90016,10 +90953,10 @@ function mergeDefs(...defs) {
90016
90953
  function cloneDef(schema) {
90017
90954
  return mergeDefs(schema._zod.def);
90018
90955
  }
90019
- function getElementAtPath(obj, path4) {
90020
- if (!path4)
90956
+ function getElementAtPath(obj, path6) {
90957
+ if (!path6)
90021
90958
  return obj;
90022
- return path4.reduce((acc, key) => acc?.[key], obj);
90959
+ return path6.reduce((acc, key) => acc?.[key], obj);
90023
90960
  }
90024
90961
  function promiseAllObject(promisesObj) {
90025
90962
  const keys3 = Object.keys(promisesObj);
@@ -90347,11 +91284,11 @@ function explicitlyAborted(x, startIndex = 0) {
90347
91284
  }
90348
91285
  return false;
90349
91286
  }
90350
- function prefixIssues(path4, issues) {
91287
+ function prefixIssues(path6, issues) {
90351
91288
  return issues.map((iss) => {
90352
91289
  var _a3;
90353
91290
  (_a3 = iss).path ?? (_a3.path = []);
90354
- iss.path.unshift(path4);
91291
+ iss.path.unshift(path6);
90355
91292
  return iss;
90356
91293
  });
90357
91294
  }
@@ -90564,16 +91501,16 @@ function flattenError(error, mapper = (issue2) => issue2.message) {
90564
91501
  }
90565
91502
  function formatError2(error, mapper = (issue2) => issue2.message) {
90566
91503
  const fieldErrors = { _errors: [] };
90567
- const processError = (error2, path4 = []) => {
91504
+ const processError = (error2, path6 = []) => {
90568
91505
  for (const issue2 of error2.issues) {
90569
91506
  if (issue2.code === "invalid_union" && issue2.errors.length) {
90570
- issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
91507
+ issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
90571
91508
  } else if (issue2.code === "invalid_key") {
90572
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
91509
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
90573
91510
  } else if (issue2.code === "invalid_element") {
90574
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
91511
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
90575
91512
  } else {
90576
- const fullpath = [...path4, ...issue2.path];
91513
+ const fullpath = [...path6, ...issue2.path];
90577
91514
  if (fullpath.length === 0) {
90578
91515
  fieldErrors._errors.push(mapper(issue2));
90579
91516
  } else {
@@ -90600,17 +91537,17 @@ function formatError2(error, mapper = (issue2) => issue2.message) {
90600
91537
  }
90601
91538
  function treeifyError(error, mapper = (issue2) => issue2.message) {
90602
91539
  const result = { errors: [] };
90603
- const processError = (error2, path4 = []) => {
91540
+ const processError = (error2, path6 = []) => {
90604
91541
  var _a3, _b2;
90605
91542
  for (const issue2 of error2.issues) {
90606
91543
  if (issue2.code === "invalid_union" && issue2.errors.length) {
90607
- issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
91544
+ issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
90608
91545
  } else if (issue2.code === "invalid_key") {
90609
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
91546
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
90610
91547
  } else if (issue2.code === "invalid_element") {
90611
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
91548
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
90612
91549
  } else {
90613
- const fullpath = [...path4, ...issue2.path];
91550
+ const fullpath = [...path6, ...issue2.path];
90614
91551
  if (fullpath.length === 0) {
90615
91552
  result.errors.push(mapper(issue2));
90616
91553
  continue;
@@ -90642,8 +91579,8 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
90642
91579
  }
90643
91580
  function toDotPath(_path) {
90644
91581
  const segs = [];
90645
- const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
90646
- for (const seg of path4) {
91582
+ const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
91583
+ for (const seg of path6) {
90647
91584
  if (typeof seg === "number")
90648
91585
  segs.push(`[${seg}]`);
90649
91586
  else if (typeof seg === "symbol")
@@ -103646,13 +104583,13 @@ function resolveRef(ref, ctx) {
103646
104583
  if (!ref.startsWith("#")) {
103647
104584
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
103648
104585
  }
103649
- const path4 = ref.slice(1).split("/").filter(Boolean);
103650
- if (path4.length === 0) {
104586
+ const path6 = ref.slice(1).split("/").filter(Boolean);
104587
+ if (path6.length === 0) {
103651
104588
  return ctx.rootSchema;
103652
104589
  }
103653
104590
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
103654
- if (path4[0] === defsKey) {
103655
- const key = path4[1];
104591
+ if (path6[0] === defsKey) {
104592
+ const key = path6[1];
103656
104593
  if (!key || !ctx.defs[key]) {
103657
104594
  throw new Error(`Reference not found: ${ref}`);
103658
104595
  }
@@ -105253,7 +106190,7 @@ async function reviveNativeTasks(tasks, localHealth, now, cooldown, runtime4, ef
105253
106190
  runNativeReviveEffect(full, runtime4, effectContext2, agentMgr);
105254
106191
  }
105255
106192
  }
105256
- async function processTasksUpdate(tasks, runtime4, effectContext2, cooldown, agentMgr, sessionDeps, machineId, pass) {
106193
+ async function processTasksUpdate(tasks, runtime4, effectContext2, cooldown, agentMgr, sessionDeps, machineId, _pass) {
105257
106194
  if (tasks.length === 0)
105258
106195
  return;
105259
106196
  const now = Date.now();
@@ -105270,9 +106207,7 @@ async function processTasksUpdate(tasks, runtime4, effectContext2, cooldown, age
105270
106207
  sessionDeps,
105271
106208
  machineId
105272
106209
  });
105273
- if (pass === "presence") {
105274
- await nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId);
105275
- }
106210
+ await nudgeStuckTasks(tasks, now, cooldown, runtime4, effectContext2, agentMgr, sessionDeps, machineId);
105276
106211
  }
105277
106212
  var startTaskMonitorEffect = (wsClient2) => exports_Effect.gen(function* () {
105278
106213
  const session2 = yield* DaemonSessionService;
@@ -105589,6 +106524,8 @@ var init_command_loop = __esm(() => {
105589
106524
  init_featureFlags();
105590
106525
  init_reliability();
105591
106526
  init_esm();
106527
+ init_dir_listing_subscription();
106528
+ init_dir_listing_watch_subscription();
105592
106529
  init_api3();
105593
106530
  init_on_request_start_agent();
105594
106531
  init_on_request_stop_agent();
@@ -105602,7 +106539,7 @@ var init_command_loop = __esm(() => {
105602
106539
  init_daemon_services();
105603
106540
  init_start_subscriptions();
105604
106541
  init_file_content_subscription();
105605
- init_file_tree_subscription();
106542
+ init_file_write_subscription();
105606
106543
  init_git_heartbeat();
105607
106544
  init_git_subscription();
105608
106545
  init_command_runner();
@@ -105648,7 +106585,9 @@ var init_command_loop = __esm(() => {
105648
106585
  heartbeatTimer.unref();
105649
106586
  let gitSubscriptionHandle = null;
105650
106587
  let fileContentSubscriptionHandle = null;
105651
- let fileTreeSubscriptionHandle = null;
106588
+ let fileWriteSubscriptionHandle = null;
106589
+ let dirListingSubscriptionHandle = null;
106590
+ let dirListingWatchSubscriptionHandle = null;
105652
106591
  let workspaceListSubscriptionHandle = null;
105653
106592
  let observedSyncSubscriptionHandle = null;
105654
106593
  let logObserverSubscriptionHandle = null;
@@ -105716,7 +106655,9 @@ var init_command_loop = __esm(() => {
105716
106655
  const stopSubscriptions = () => {
105717
106656
  gitSubscriptionHandle?.stop();
105718
106657
  fileContentSubscriptionHandle?.stop();
105719
- fileTreeSubscriptionHandle?.stop();
106658
+ fileWriteSubscriptionHandle?.stop();
106659
+ dirListingSubscriptionHandle?.stop();
106660
+ dirListingWatchSubscriptionHandle?.stop();
105720
106661
  workspaceListSubscriptionHandle?.stop();
105721
106662
  observedSyncSubscriptionHandle?.stop();
105722
106663
  taskMonitorHandle?.stop();
@@ -105760,7 +106701,9 @@ var init_command_loop = __esm(() => {
105760
106701
  const wsClient2 = yield* exports_Effect.promise(() => getConvexWsClient());
105761
106702
  gitSubscriptionHandle = yield* startGitRequestSubscriptionEffect(wsClient2);
105762
106703
  fileContentSubscriptionHandle = yield* startFileContentSubscriptionEffect(wsClient2);
105763
- fileTreeSubscriptionHandle = yield* startFileTreeSubscriptionEffect(wsClient2);
106704
+ fileWriteSubscriptionHandle = yield* startFileWriteSubscriptionEffect(wsClient2);
106705
+ dirListingSubscriptionHandle = yield* startDirListingSubscriptionEffect(wsClient2);
106706
+ dirListingWatchSubscriptionHandle = yield* startDirListingWatchSubscriptionEffect(wsClient2);
105764
106707
  workspaceListSubscriptionHandle = yield* startWorkspaceListSubscriptionEffect(wsClient2);
105765
106708
  if (observedSyncEnabled) {
105766
106709
  observedSyncSubscriptionHandle = yield* startObservedSyncSubscriptionEffect(wsClient2);
@@ -105962,7 +106905,7 @@ __export(exports_opencode_install, {
105962
106905
  installTool: () => installTool
105963
106906
  });
105964
106907
  import * as os2 from "os";
105965
- import * as path4 from "path";
106908
+ import * as path6 from "path";
105966
106909
  async function isChatroomInstalledDefault() {
105967
106910
  try {
105968
106911
  const { execSync: execSync3 } = await import("child_process");
@@ -106332,9 +107275,9 @@ After logging in, try this command again.\`;
106332
107275
  const fsService = yield* OpenCodeInstallFsService;
106333
107276
  const { checkExisting = true } = options;
106334
107277
  const homeDir = os2.homedir();
106335
- const toolDir = path4.join(homeDir, ".config", "opencode", "tool");
106336
- const toolPath = path4.join(toolDir, "chatroom.ts");
106337
- const handoffToolPath = path4.join(toolDir, "chatroom-handoff.ts");
107278
+ const toolDir = path6.join(homeDir, ".config", "opencode", "tool");
107279
+ const toolPath = path6.join(toolDir, "chatroom.ts");
107280
+ const handoffToolPath = path6.join(toolDir, "chatroom-handoff.ts");
106338
107281
  if (checkExisting) {
106339
107282
  const existingFiles = [];
106340
107283
  const toolExists = yield* fsService.access(toolPath);
@@ -106889,4 +107832,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
106889
107832
  });
106890
107833
  program2.parse();
106891
107834
 
106892
- //# debugId=8C344CD05121AB9864756E2164756E21
107835
+ //# debugId=5B3A6F0DA9D185F964756E2164756E21