chatroom-cli 1.98.2 → 1.98.4
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 +1264 -485
- package/dist/index.js.map +34 -18
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -110218,7 +110218,8 @@ var init_file_content_fulfillment = __esm(() => {
|
|
|
110218
110218
|
return exports_Effect.succeed(Buffer.from("[Error reading file]"));
|
|
110219
110219
|
});
|
|
110220
110220
|
if (fileNotFound) {
|
|
110221
|
-
|
|
110221
|
+
yield* fulfillGzippedContentEffect(session2, workingDir, filePath, "[File not found]", false);
|
|
110222
|
+
console.log(`[${formatTimestamp()}] \uD83D\uDCC4 File not found, fulfilled content request: ${filePath}`);
|
|
110222
110223
|
continue;
|
|
110223
110224
|
}
|
|
110224
110225
|
const classification2 = classifyFileContent(filePath, new Uint8Array(buffer));
|
|
@@ -110258,49 +110259,6 @@ var init_file_content_subscription = __esm(() => {
|
|
|
110258
110259
|
init_daemon_services();
|
|
110259
110260
|
});
|
|
110260
110261
|
|
|
110261
|
-
// ../../services/backend/src/domain/workspace-file-tree/types.ts
|
|
110262
|
-
var MAX_TREE_JSON_BYTES;
|
|
110263
|
-
var init_types2 = __esm(() => {
|
|
110264
|
-
MAX_TREE_JSON_BYTES = 900 * 1024;
|
|
110265
|
-
});
|
|
110266
|
-
// ../../services/backend/src/domain/workspace-file-tree/select-strategy.ts
|
|
110267
|
-
function treeJsonByteLength(tree) {
|
|
110268
|
-
return Buffer.byteLength(JSON.stringify(tree), "utf8");
|
|
110269
|
-
}
|
|
110270
|
-
function selectFileTreeSnapshotStrategyId(tree) {
|
|
110271
|
-
return treeJsonByteLength(tree) > MAX_TREE_JSON_BYTES ? "sharded" : "blob";
|
|
110272
|
-
}
|
|
110273
|
-
var init_select_strategy = __esm(() => {
|
|
110274
|
-
init_types2();
|
|
110275
|
-
});
|
|
110276
|
-
|
|
110277
|
-
// ../../services/backend/src/domain/workspace-file-tree/registry.ts
|
|
110278
|
-
var init_registry5 = __esm(() => {
|
|
110279
|
-
init_select_strategy();
|
|
110280
|
-
});
|
|
110281
|
-
// ../../services/backend/src/domain/workspace-file-tree/transport/blob-snapshot.ts
|
|
110282
|
-
function toLegacyBlobSyncArgs(payload) {
|
|
110283
|
-
return { data: payload.data, dataHash: payload.dataHash, scannedAt: payload.scannedAt };
|
|
110284
|
-
}
|
|
110285
|
-
|
|
110286
|
-
// ../../services/backend/src/domain/workspace-file-tree/transport/sharded-snapshot.ts
|
|
110287
|
-
function toLegacyShardBatchItem(shard) {
|
|
110288
|
-
return { ...shard };
|
|
110289
|
-
}
|
|
110290
|
-
function toLegacyManifestSyncArgs(manifest) {
|
|
110291
|
-
return { ...manifest };
|
|
110292
|
-
}
|
|
110293
|
-
// ../../services/backend/src/domain/workspace-file-tree/transport/index.ts
|
|
110294
|
-
var init_transport = () => {};
|
|
110295
|
-
|
|
110296
|
-
// ../../services/backend/src/domain/workspace-file-tree/index.ts
|
|
110297
|
-
var init_workspace_file_tree = __esm(() => {
|
|
110298
|
-
init_types2();
|
|
110299
|
-
init_select_strategy();
|
|
110300
|
-
init_registry5();
|
|
110301
|
-
init_transport();
|
|
110302
|
-
});
|
|
110303
|
-
|
|
110304
110262
|
// src/infrastructure/services/workspace/file-tree-data-hash.ts
|
|
110305
110263
|
import { createHash as createHash6 } from "node:crypto";
|
|
110306
110264
|
function computeFileTreeDataHash(tree) {
|
|
@@ -110308,166 +110266,212 @@ function computeFileTreeDataHash(tree) {
|
|
|
110308
110266
|
}
|
|
110309
110267
|
var init_file_tree_data_hash = () => {};
|
|
110310
110268
|
|
|
110311
|
-
// src/infrastructure/services/workspace/
|
|
110312
|
-
|
|
110313
|
-
|
|
110314
|
-
|
|
110315
|
-
|
|
110316
|
-
|
|
110317
|
-
|
|
110318
|
-
|
|
110319
|
-
|
|
110320
|
-
}
|
|
110321
|
-
|
|
110322
|
-
|
|
110323
|
-
|
|
110324
|
-
|
|
110325
|
-
|
|
110326
|
-
|
|
110327
|
-
|
|
110328
|
-
|
|
110329
|
-
|
|
110330
|
-
|
|
110331
|
-
|
|
110332
|
-
|
|
110333
|
-
|
|
110334
|
-
|
|
110335
|
-
|
|
110336
|
-
|
|
110337
|
-
|
|
110338
|
-
|
|
110339
|
-
|
|
110340
|
-
|
|
110341
|
-
|
|
110342
|
-
|
|
110343
|
-
|
|
110344
|
-
|
|
110345
|
-
|
|
110346
|
-
|
|
110347
|
-
return
|
|
110348
|
-
}
|
|
110349
|
-
function
|
|
110350
|
-
|
|
110351
|
-
|
|
110352
|
-
|
|
110353
|
-
|
|
110354
|
-
|
|
110355
|
-
|
|
110356
|
-
|
|
110357
|
-
|
|
110358
|
-
|
|
110359
|
-
|
|
110360
|
-
|
|
110361
|
-
|
|
110362
|
-
|
|
110363
|
-
|
|
110364
|
-
|
|
110365
|
-
|
|
110366
|
-
|
|
110367
|
-
|
|
110368
|
-
|
|
110369
|
-
|
|
110370
|
-
|
|
110371
|
-
|
|
110372
|
-
|
|
110373
|
-
|
|
110374
|
-
|
|
110375
|
-
|
|
110376
|
-
|
|
110377
|
-
|
|
110378
|
-
|
|
110379
|
-
|
|
110380
|
-
|
|
110381
|
-
|
|
110382
|
-
|
|
110383
|
-
|
|
110384
|
-
|
|
110385
|
-
|
|
110386
|
-
|
|
110387
|
-
|
|
110388
|
-
|
|
110389
|
-
|
|
110390
|
-
|
|
110391
|
-
|
|
110392
|
-
|
|
110393
|
-
|
|
110394
|
-
|
|
110395
|
-
|
|
110396
|
-
|
|
110397
|
-
|
|
110398
|
-
|
|
110399
|
-
|
|
110400
|
-
|
|
110401
|
-
|
|
110402
|
-
|
|
110403
|
-
|
|
110404
|
-
|
|
110405
|
-
|
|
110406
|
-
|
|
110407
|
-
|
|
110408
|
-
|
|
110409
|
-
|
|
110410
|
-
|
|
110411
|
-
|
|
110412
|
-
|
|
110413
|
-
|
|
110414
|
-
|
|
110415
|
-
|
|
110416
|
-
|
|
110417
|
-
|
|
110418
|
-
|
|
110419
|
-
|
|
110420
|
-
|
|
110421
|
-
|
|
110422
|
-
|
|
110423
|
-
|
|
110424
|
-
|
|
110425
|
-
|
|
110426
|
-
|
|
110427
|
-
|
|
110428
|
-
|
|
110429
|
-
|
|
110430
|
-
|
|
110431
|
-
|
|
110432
|
-
|
|
110433
|
-
|
|
110434
|
-
|
|
110435
|
-
|
|
110436
|
-
|
|
110437
|
-
|
|
110438
|
-
|
|
110439
|
-
|
|
110440
|
-
|
|
110441
|
-
|
|
110442
|
-
|
|
110443
|
-
|
|
110444
|
-
|
|
110445
|
-
|
|
110446
|
-
|
|
110447
|
-
|
|
110448
|
-
|
|
110449
|
-
|
|
110450
|
-
|
|
110451
|
-
|
|
110452
|
-
|
|
110453
|
-
|
|
110454
|
-
|
|
110455
|
-
|
|
110456
|
-
|
|
110457
|
-
|
|
110458
|
-
|
|
110459
|
-
|
|
110460
|
-
|
|
110461
|
-
|
|
110462
|
-
|
|
110463
|
-
|
|
110464
|
-
|
|
110465
|
-
|
|
110466
|
-
|
|
110467
|
-
|
|
110468
|
-
|
|
110469
|
-
|
|
110470
|
-
|
|
110269
|
+
// src/infrastructure/services/workspace/file-tree-sync-ranker.ts
|
|
110270
|
+
function fileExtension(p) {
|
|
110271
|
+
const b = (p.split("/").pop() ?? p).toLowerCase();
|
|
110272
|
+
if (b.endsWith(".min.js"))
|
|
110273
|
+
return ".min.js";
|
|
110274
|
+
if (b.endsWith(".min.css"))
|
|
110275
|
+
return ".min.css";
|
|
110276
|
+
const i2 = b.lastIndexOf(".");
|
|
110277
|
+
return i2 <= 0 ? "" : b.slice(i2);
|
|
110278
|
+
}
|
|
110279
|
+
function pathSegments(p) {
|
|
110280
|
+
return p.split("/").filter(Boolean);
|
|
110281
|
+
}
|
|
110282
|
+
function parentDir(p) {
|
|
110283
|
+
const i2 = p.lastIndexOf("/");
|
|
110284
|
+
return i2 < 0 ? "" : p.slice(0, i2);
|
|
110285
|
+
}
|
|
110286
|
+
function isSourceDirPath(p) {
|
|
110287
|
+
return pathSegments(p).slice(0, -1).some((s) => SOURCE_DIR_NAMES.has(s));
|
|
110288
|
+
}
|
|
110289
|
+
function isBuildDirPath(p) {
|
|
110290
|
+
return pathSegments(p).some((s) => BUILD_DIR_NAMES.has(s));
|
|
110291
|
+
}
|
|
110292
|
+
function isMarkdownFile(p) {
|
|
110293
|
+
return MARKDOWN_EXTENSIONS.has(fileExtension(p));
|
|
110294
|
+
}
|
|
110295
|
+
function isConfigFile(p) {
|
|
110296
|
+
return CONFIG_BASENAMES.has((p.split("/").pop() ?? "").toLowerCase()) || CONFIG_EXTENSIONS.has(fileExtension(p));
|
|
110297
|
+
}
|
|
110298
|
+
function isSourceFile(p) {
|
|
110299
|
+
return SOURCE_EXTENSIONS.has(fileExtension(p));
|
|
110300
|
+
}
|
|
110301
|
+
function isBuildOutputFile(p) {
|
|
110302
|
+
return BUILD_OUTPUT_EXTENSIONS.has(fileExtension(p));
|
|
110303
|
+
}
|
|
110304
|
+
function isDataFile(p) {
|
|
110305
|
+
return !isConfigFile(p) && DATA_EXTENSIONS.has(fileExtension(p));
|
|
110306
|
+
}
|
|
110307
|
+
function countDataFilesByParent(ps) {
|
|
110308
|
+
const m = new Map;
|
|
110309
|
+
for (const p of ps)
|
|
110310
|
+
if (isDataFile(p)) {
|
|
110311
|
+
const d = parentDir(p);
|
|
110312
|
+
m.set(d, (m.get(d) ?? 0) + 1);
|
|
110313
|
+
}
|
|
110314
|
+
return m;
|
|
110315
|
+
}
|
|
110316
|
+
function scoreFile(p, m) {
|
|
110317
|
+
let s = 0;
|
|
110318
|
+
if (isSourceFile(p))
|
|
110319
|
+
s += isSourceDirPath(p) ? SCORE_SOURCE_IN_SOURCE_DIR : SCORE_SOURCE_ELSEWHERE;
|
|
110320
|
+
if (isMarkdownFile(p))
|
|
110321
|
+
s += SCORE_MARKDOWN;
|
|
110322
|
+
if (isConfigFile(p))
|
|
110323
|
+
s += SCORE_CONFIG;
|
|
110324
|
+
if (isBuildDirPath(p))
|
|
110325
|
+
s -= PENALTY_BUILD_DIR;
|
|
110326
|
+
if (isBuildOutputFile(p))
|
|
110327
|
+
s -= PENALTY_BUILD_EXT;
|
|
110328
|
+
const n = m.get(parentDir(p)) ?? 0;
|
|
110329
|
+
return s - n * n;
|
|
110330
|
+
}
|
|
110331
|
+
function compareRankedFiles(a, as7, b, bs) {
|
|
110332
|
+
if (as7 !== bs)
|
|
110333
|
+
return bs - as7;
|
|
110334
|
+
const d = pathSegments(a).length - pathSegments(b).length;
|
|
110335
|
+
return d || a.localeCompare(b);
|
|
110336
|
+
}
|
|
110337
|
+
var SOURCE_DIR_NAMES, BUILD_DIR_NAMES, SOURCE_EXTENSIONS, MARKDOWN_EXTENSIONS, CONFIG_EXTENSIONS, CONFIG_BASENAMES, BUILD_OUTPUT_EXTENSIONS, DATA_EXTENSIONS, SCORE_SOURCE_IN_SOURCE_DIR = 1000, SCORE_MARKDOWN = 800, SCORE_CONFIG = 700, SCORE_SOURCE_ELSEWHERE = 200, PENALTY_BUILD_DIR = 5000, PENALTY_BUILD_EXT = 2000;
|
|
110338
|
+
var init_file_tree_sync_ranker = __esm(() => {
|
|
110339
|
+
SOURCE_DIR_NAMES = new Set([
|
|
110340
|
+
"src",
|
|
110341
|
+
"lib",
|
|
110342
|
+
"app",
|
|
110343
|
+
"apps",
|
|
110344
|
+
"packages",
|
|
110345
|
+
"services",
|
|
110346
|
+
"server",
|
|
110347
|
+
"client",
|
|
110348
|
+
"backend",
|
|
110349
|
+
"frontend",
|
|
110350
|
+
"api",
|
|
110351
|
+
"cmd",
|
|
110352
|
+
"internal",
|
|
110353
|
+
"pkg",
|
|
110354
|
+
"components",
|
|
110355
|
+
"modules",
|
|
110356
|
+
"pages",
|
|
110357
|
+
"routes",
|
|
110358
|
+
"hooks",
|
|
110359
|
+
"domain",
|
|
110360
|
+
"infrastructure",
|
|
110361
|
+
"entry"
|
|
110362
|
+
]);
|
|
110363
|
+
BUILD_DIR_NAMES = new Set([
|
|
110364
|
+
"dist",
|
|
110365
|
+
"build",
|
|
110366
|
+
"out",
|
|
110367
|
+
"output",
|
|
110368
|
+
"coverage",
|
|
110369
|
+
".next",
|
|
110370
|
+
".turbo",
|
|
110371
|
+
".nx",
|
|
110372
|
+
"_generated",
|
|
110373
|
+
"generated",
|
|
110374
|
+
"__pycache__"
|
|
110375
|
+
]);
|
|
110376
|
+
SOURCE_EXTENSIONS = new Set([
|
|
110377
|
+
".ts",
|
|
110378
|
+
".tsx",
|
|
110379
|
+
".js",
|
|
110380
|
+
".jsx",
|
|
110381
|
+
".mjs",
|
|
110382
|
+
".cjs",
|
|
110383
|
+
".py",
|
|
110384
|
+
".go",
|
|
110385
|
+
".rs",
|
|
110386
|
+
".java",
|
|
110387
|
+
".kt",
|
|
110388
|
+
".swift",
|
|
110389
|
+
".rb",
|
|
110390
|
+
".php",
|
|
110391
|
+
".cs",
|
|
110392
|
+
".c",
|
|
110393
|
+
".cc",
|
|
110394
|
+
".cpp",
|
|
110395
|
+
".h",
|
|
110396
|
+
".hpp",
|
|
110397
|
+
".vue",
|
|
110398
|
+
".svelte",
|
|
110399
|
+
".css",
|
|
110400
|
+
".scss",
|
|
110401
|
+
".less"
|
|
110402
|
+
]);
|
|
110403
|
+
MARKDOWN_EXTENSIONS = new Set([".md", ".mdx", ".markdown"]);
|
|
110404
|
+
CONFIG_EXTENSIONS = new Set([".yml", ".yaml", ".toml", ".jsonc", ".ini", ".cfg"]);
|
|
110405
|
+
CONFIG_BASENAMES = new Set([
|
|
110406
|
+
"package.json",
|
|
110407
|
+
"package-lock.json",
|
|
110408
|
+
"pnpm-lock.yaml",
|
|
110409
|
+
"pnpm-workspace.yaml",
|
|
110410
|
+
"yarn.lock",
|
|
110411
|
+
"bun.lock",
|
|
110412
|
+
"tsconfig.json",
|
|
110413
|
+
"jsconfig.json",
|
|
110414
|
+
"turbo.json",
|
|
110415
|
+
"nx.json",
|
|
110416
|
+
"cargo.toml",
|
|
110417
|
+
"go.mod",
|
|
110418
|
+
"go.sum",
|
|
110419
|
+
"pyproject.toml",
|
|
110420
|
+
"requirements.txt",
|
|
110421
|
+
"dockerfile",
|
|
110422
|
+
"docker-compose.yml",
|
|
110423
|
+
"docker-compose.yaml",
|
|
110424
|
+
"makefile",
|
|
110425
|
+
".gitignore",
|
|
110426
|
+
".cursorignore",
|
|
110427
|
+
".editorconfig",
|
|
110428
|
+
".prettierrc",
|
|
110429
|
+
".nvmrc",
|
|
110430
|
+
"eslint.config.mjs",
|
|
110431
|
+
"eslint.config.js",
|
|
110432
|
+
"biome.json",
|
|
110433
|
+
".fallowrc.jsonc"
|
|
110434
|
+
]);
|
|
110435
|
+
BUILD_OUTPUT_EXTENSIONS = new Set([
|
|
110436
|
+
".map",
|
|
110437
|
+
".min.js",
|
|
110438
|
+
".min.css",
|
|
110439
|
+
".pyc",
|
|
110440
|
+
".pyo",
|
|
110441
|
+
".o",
|
|
110442
|
+
".obj",
|
|
110443
|
+
".wasm",
|
|
110444
|
+
".class",
|
|
110445
|
+
".jar",
|
|
110446
|
+
".exe",
|
|
110447
|
+
".dll",
|
|
110448
|
+
".so"
|
|
110449
|
+
]);
|
|
110450
|
+
DATA_EXTENSIONS = new Set([
|
|
110451
|
+
".json",
|
|
110452
|
+
".csv",
|
|
110453
|
+
".tsv",
|
|
110454
|
+
".ndjson",
|
|
110455
|
+
".parquet",
|
|
110456
|
+
".avro",
|
|
110457
|
+
".bin",
|
|
110458
|
+
".dat",
|
|
110459
|
+
".pb",
|
|
110460
|
+
".sqlite",
|
|
110461
|
+
".db",
|
|
110462
|
+
".log",
|
|
110463
|
+
".gz",
|
|
110464
|
+
".zip",
|
|
110465
|
+
".tar",
|
|
110466
|
+
".png",
|
|
110467
|
+
".jpg",
|
|
110468
|
+
".jpeg",
|
|
110469
|
+
".webp",
|
|
110470
|
+
".gif",
|
|
110471
|
+
".mp4",
|
|
110472
|
+
".woff",
|
|
110473
|
+
".woff2"
|
|
110474
|
+
]);
|
|
110471
110475
|
});
|
|
110472
110476
|
|
|
110473
110477
|
// ../../node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
|
|
@@ -110800,10 +110804,68 @@ var require_ignore = __commonJS((exports, module) => {
|
|
|
110800
110804
|
// src/infrastructure/services/workspace/workspace-ignore.ts
|
|
110801
110805
|
import { promises as fsPromises } from "node:fs";
|
|
110802
110806
|
import { readFile as readFile10 } from "node:fs/promises";
|
|
110807
|
+
import { homedir as homedir7 } from "node:os";
|
|
110803
110808
|
import path3 from "node:path";
|
|
110804
110809
|
function normalizeRelativePath(relativePath) {
|
|
110805
110810
|
return relativePath.replace(/\\/g, "/").replace(/^\.?\//, "").replace(/\/+$/, "");
|
|
110806
110811
|
}
|
|
110812
|
+
function expandUserPath(filePath) {
|
|
110813
|
+
if (filePath === "~")
|
|
110814
|
+
return homedir7();
|
|
110815
|
+
if (filePath.startsWith("~/"))
|
|
110816
|
+
return path3.join(homedir7(), filePath.slice(2));
|
|
110817
|
+
return filePath;
|
|
110818
|
+
}
|
|
110819
|
+
function readExcludesFileFromGitconfig(content) {
|
|
110820
|
+
let inCore = false;
|
|
110821
|
+
for (const line of content.split(/\r?\n/)) {
|
|
110822
|
+
const section = line.match(/^\s*\[([^\]]+)\]/);
|
|
110823
|
+
if (section) {
|
|
110824
|
+
inCore = section[1].trim().toLowerCase() === "core";
|
|
110825
|
+
continue;
|
|
110826
|
+
}
|
|
110827
|
+
if (!inCore)
|
|
110828
|
+
continue;
|
|
110829
|
+
const match17 = line.match(/^\s*excludesfile\s*=\s*(.*?)\s*(?:[#;].*)?$/i);
|
|
110830
|
+
if (!match17)
|
|
110831
|
+
continue;
|
|
110832
|
+
const value = match17[1].trim().replace(/^(["'])(.*)\1$/, "$2");
|
|
110833
|
+
return value ? expandUserPath(value) : null;
|
|
110834
|
+
}
|
|
110835
|
+
return null;
|
|
110836
|
+
}
|
|
110837
|
+
async function readGitconfigExcludesFilePath(configPath) {
|
|
110838
|
+
const content = await readIgnoreFile(configPath);
|
|
110839
|
+
if (content === null)
|
|
110840
|
+
return null;
|
|
110841
|
+
const raw = readExcludesFileFromGitconfig(content);
|
|
110842
|
+
if (!raw)
|
|
110843
|
+
return null;
|
|
110844
|
+
return path3.isAbsolute(raw) ? expandUserPath(raw) : path3.resolve(path3.dirname(configPath), raw);
|
|
110845
|
+
}
|
|
110846
|
+
async function resolveGitExtraExcludeFilePaths(rootDir) {
|
|
110847
|
+
const globalConfig2 = process.env.GIT_CONFIG_GLOBAL?.length ? process.env.GIT_CONFIG_GLOBAL : path3.join(homedir7(), ".gitconfig");
|
|
110848
|
+
const localConfig = path3.join(rootDir, ".git", "config");
|
|
110849
|
+
let excludesFile = await readGitconfigExcludesFilePath(localConfig) ?? await readGitconfigExcludesFilePath(globalConfig2);
|
|
110850
|
+
if (!excludesFile) {
|
|
110851
|
+
excludesFile = path3.join(process.env.XDG_CONFIG_HOME?.length ? process.env.XDG_CONFIG_HOME : path3.join(homedir7(), ".config"), "git", "ignore");
|
|
110852
|
+
}
|
|
110853
|
+
const candidates = [excludesFile, path3.join(rootDir, ".git", "info", "exclude")];
|
|
110854
|
+
const existing = [];
|
|
110855
|
+
for (const candidate of candidates)
|
|
110856
|
+
if (await readIgnoreFile(candidate) !== null)
|
|
110857
|
+
existing.push(candidate);
|
|
110858
|
+
return existing;
|
|
110859
|
+
}
|
|
110860
|
+
async function loadGitExtraExcludeRuleSets(rootDir) {
|
|
110861
|
+
const result = [];
|
|
110862
|
+
for (const filePath of await resolveGitExtraExcludeFilePaths(rootDir)) {
|
|
110863
|
+
const content = await readIgnoreFile(filePath);
|
|
110864
|
+
if (content !== null)
|
|
110865
|
+
result.push({ baseDir: "", matcher: import_ignore.default().add(content) });
|
|
110866
|
+
}
|
|
110867
|
+
return result;
|
|
110868
|
+
}
|
|
110807
110869
|
async function readIgnoreFile(filePath) {
|
|
110808
110870
|
try {
|
|
110809
110871
|
return await readFile10(filePath, "utf-8");
|
|
@@ -110815,7 +110877,7 @@ async function loadDirectoryIgnoreRuleSets(rootDir, relativeDir) {
|
|
|
110815
110877
|
const normalizedDir = normalizeRelativePath(relativeDir);
|
|
110816
110878
|
const absoluteDir = normalizedDir ? path3.join(rootDir, normalizedDir) : rootDir;
|
|
110817
110879
|
const names = normalizedDir ? [".gitignore"] : IGNORE_FILES;
|
|
110818
|
-
const result = [];
|
|
110880
|
+
const result = normalizedDir ? [] : await loadGitExtraExcludeRuleSets(rootDir);
|
|
110819
110881
|
for (const name of names) {
|
|
110820
110882
|
const content = await readIgnoreFile(path3.join(absoluteDir, name));
|
|
110821
110883
|
if (content === null)
|
|
@@ -110912,48 +110974,57 @@ async function walkWorkspaceFiles(rootDir, options) {
|
|
|
110912
110974
|
const filePaths = [];
|
|
110913
110975
|
const directoryStubs = [];
|
|
110914
110976
|
let truncated = false;
|
|
110915
|
-
|
|
110916
|
-
|
|
110977
|
+
let currentLevel = [{ relDir: "", inheritedRuleSets: [], siblingCount: 0 }];
|
|
110978
|
+
while (currentLevel.length > 0) {
|
|
110979
|
+
if (filePaths.length >= maxFilePaths) {
|
|
110917
110980
|
truncated = true;
|
|
110918
|
-
|
|
110981
|
+
break;
|
|
110919
110982
|
}
|
|
110920
|
-
const
|
|
110921
|
-
const
|
|
110922
|
-
|
|
110923
|
-
if (!dirents)
|
|
110924
|
-
return;
|
|
110925
|
-
for (const ent of dirents) {
|
|
110926
|
-
if (truncated || filePaths.length >= maxFilePaths) {
|
|
110983
|
+
const nextLevel = [];
|
|
110984
|
+
for (const frame of currentLevel) {
|
|
110985
|
+
if (filePaths.length >= maxFilePaths) {
|
|
110927
110986
|
truncated = true;
|
|
110928
|
-
|
|
110987
|
+
break;
|
|
110929
110988
|
}
|
|
110930
|
-
const
|
|
110931
|
-
|
|
110989
|
+
const localRuleSets = await loadDirectoryIgnoreRuleSets(rootDir, frame.relDir);
|
|
110990
|
+
const ruleSets = mergeWorkspaceIgnoreRuleSets(frame.inheritedRuleSets, localRuleSets);
|
|
110991
|
+
const dirents = await readWorkspaceDirectoryDirents(rootDir, frame.relDir);
|
|
110992
|
+
if (!dirents)
|
|
110932
110993
|
continue;
|
|
110933
|
-
|
|
110934
|
-
|
|
110935
|
-
|
|
110936
|
-
const syncMode = classifyDirectorySyncMode(ent.name, {
|
|
110937
|
-
relativePath,
|
|
110938
|
-
immediateSiblingCount: siblingCount,
|
|
110939
|
-
immediateChildCount: dirents.length
|
|
110940
|
-
});
|
|
110941
|
-
if (syncMode === "hidden")
|
|
110994
|
+
for (const ent of dirents) {
|
|
110995
|
+
const relativePath = frame.relDir ? `${frame.relDir}/${ent.name}` : ent.name;
|
|
110996
|
+
if (!isPathVisible(relativePath))
|
|
110942
110997
|
continue;
|
|
110943
|
-
|
|
110944
|
-
|
|
110945
|
-
|
|
110946
|
-
|
|
110947
|
-
|
|
110948
|
-
|
|
110949
|
-
|
|
110950
|
-
|
|
110951
|
-
|
|
110952
|
-
|
|
110998
|
+
if (ent.isDirectory()) {
|
|
110999
|
+
if (isPathIgnoredByRuleSets(ruleSets, relativePath))
|
|
111000
|
+
continue;
|
|
111001
|
+
const syncMode = classifyDirectorySyncMode(ent.name, {
|
|
111002
|
+
relativePath,
|
|
111003
|
+
immediateSiblingCount: frame.siblingCount,
|
|
111004
|
+
immediateChildCount: dirents.length
|
|
111005
|
+
});
|
|
111006
|
+
if (syncMode === "hidden")
|
|
111007
|
+
continue;
|
|
111008
|
+
directoryStubs.push(relativePath);
|
|
111009
|
+
if (syncMode === "full")
|
|
111010
|
+
nextLevel.push({
|
|
111011
|
+
relDir: relativePath,
|
|
111012
|
+
inheritedRuleSets: ruleSets,
|
|
111013
|
+
siblingCount: dirents.length
|
|
111014
|
+
});
|
|
111015
|
+
} else if (ent.isFile() && !isPathIgnoredByRuleSets(ruleSets, relativePath)) {
|
|
111016
|
+
if (filePaths.length >= maxFilePaths) {
|
|
111017
|
+
truncated = true;
|
|
111018
|
+
continue;
|
|
111019
|
+
}
|
|
111020
|
+
filePaths.push(relativePath);
|
|
111021
|
+
if (filePaths.length >= maxFilePaths)
|
|
111022
|
+
truncated = true;
|
|
111023
|
+
}
|
|
110953
111024
|
}
|
|
110954
111025
|
}
|
|
111026
|
+
currentLevel = truncated ? [] : nextLevel;
|
|
110955
111027
|
}
|
|
110956
|
-
await visitDir("", [], 0);
|
|
110957
111028
|
return { filePaths, directoryStubs, truncated };
|
|
110958
111029
|
}
|
|
110959
111030
|
var init_workspace_file_walk = __esm(() => {
|
|
@@ -110965,7 +111036,7 @@ var init_workspace_file_walk = __esm(() => {
|
|
|
110965
111036
|
async function scanFileTree(rootDir, options) {
|
|
110966
111037
|
const maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
110967
111038
|
const scannedAt = Date.now();
|
|
110968
|
-
const walk = await walkWorkspaceFiles(rootDir, { maxFilePaths:
|
|
111039
|
+
const walk = await walkWorkspaceFiles(rootDir, { maxFilePaths: DEFAULT_CANDIDATE_FILE_PATHS });
|
|
110969
111040
|
const filteredPaths = walk.filePaths.filter((p) => !isExcluded(p));
|
|
110970
111041
|
const filteredStubs = walk.directoryStubs.filter((p) => !isExcluded(p));
|
|
110971
111042
|
const entries2 = buildEntries(filteredPaths, filteredStubs, maxEntries);
|
|
@@ -110982,30 +111053,35 @@ function entryDepth(entryPath) {
|
|
|
110982
111053
|
return entryPath.split("/").length;
|
|
110983
111054
|
}
|
|
110984
111055
|
function buildEntries(filePaths, directoryStubs, maxEntries) {
|
|
110985
|
-
const
|
|
110986
|
-
|
|
110987
|
-
|
|
110988
|
-
|
|
110989
|
-
|
|
110990
|
-
|
|
111056
|
+
const selected = new Map;
|
|
111057
|
+
const data = countDataFilesByParent(filePaths);
|
|
111058
|
+
const ranked = [...filePaths].sort((a, b) => compareRankedFiles(a, scoreFile(a, data), b, scoreFile(b, data)));
|
|
111059
|
+
for (const file2 of ranked) {
|
|
111060
|
+
const parts2 = file2.split("/");
|
|
111061
|
+
const needed = [file2, ...parts2.slice(0, -1).map((_, i2) => parts2.slice(0, i2 + 1).join("/"))];
|
|
111062
|
+
const missing = needed.filter((p) => !selected.has(p));
|
|
111063
|
+
if (selected.size + missing.length > maxEntries)
|
|
111064
|
+
continue;
|
|
111065
|
+
for (const p of needed)
|
|
111066
|
+
selected.set(p, { path: p, type: p === file2 ? "file" : "directory" });
|
|
110991
111067
|
}
|
|
110992
|
-
const
|
|
110993
|
-
|
|
110994
|
-
|
|
110995
|
-
|
|
110996
|
-
|
|
110997
|
-
|
|
110998
|
-
uniqueByPath.set(entry.path, entry);
|
|
111068
|
+
for (const dir of [...directoryStubs].sort((a, b) => entryDepth(a) - entryDepth(b) || a.localeCompare(b))) {
|
|
111069
|
+
if (selected.has(dir))
|
|
111070
|
+
continue;
|
|
111071
|
+
if (selected.size >= maxEntries)
|
|
111072
|
+
break;
|
|
111073
|
+
selected.set(dir, { path: dir, type: "directory" });
|
|
110999
111074
|
}
|
|
111000
|
-
return Array.from(
|
|
111075
|
+
return Array.from(selected.values()).sort((left3, right3) => {
|
|
111001
111076
|
const depthDelta = entryDepth(left3.path) - entryDepth(right3.path);
|
|
111002
111077
|
if (depthDelta !== 0)
|
|
111003
111078
|
return depthDelta;
|
|
111004
111079
|
return left3.path.localeCompare(right3.path);
|
|
111005
|
-
})
|
|
111080
|
+
});
|
|
111006
111081
|
}
|
|
111007
|
-
var DEFAULT_MAX_ENTRIES = 1e4;
|
|
111082
|
+
var DEFAULT_MAX_ENTRIES = 1e4, DEFAULT_CANDIDATE_FILE_PATHS = 50000;
|
|
111008
111083
|
var init_file_tree_scanner = __esm(() => {
|
|
111084
|
+
init_file_tree_sync_ranker();
|
|
111009
111085
|
init_workspace_file_walk();
|
|
111010
111086
|
init_workspace_visibility_policy();
|
|
111011
111087
|
});
|
|
@@ -112041,12 +112117,12 @@ class NodeFsHandler {
|
|
|
112041
112117
|
});
|
|
112042
112118
|
}
|
|
112043
112119
|
async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath3) {
|
|
112044
|
-
const
|
|
112045
|
-
const tracked =
|
|
112120
|
+
const parentDir2 = this.fsw._getWatchedDir(sp.dirname(dir));
|
|
112121
|
+
const tracked = parentDir2.has(sp.basename(dir));
|
|
112046
112122
|
if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) {
|
|
112047
112123
|
this.fsw._emit(EV.ADD_DIR, dir, stats);
|
|
112048
112124
|
}
|
|
112049
|
-
|
|
112125
|
+
parentDir2.add(sp.basename(dir));
|
|
112050
112126
|
this.fsw._getWatchedDir(dir);
|
|
112051
112127
|
let throttler;
|
|
112052
112128
|
let closer;
|
|
@@ -113317,13 +113393,13 @@ function diffPathIndexes(previous, next4) {
|
|
|
113317
113393
|
}
|
|
113318
113394
|
|
|
113319
113395
|
// src/infrastructure/services/workspace/workspace-sync-state.ts
|
|
113320
|
-
import { createHash as
|
|
113396
|
+
import { createHash as createHash7, randomUUID as randomUUID9 } from "node:crypto";
|
|
113321
113397
|
import * as fs11 from "node:fs/promises";
|
|
113322
|
-
import { homedir as
|
|
113398
|
+
import { homedir as homedir8 } from "node:os";
|
|
113323
113399
|
import { join as join24 } from "node:path";
|
|
113324
113400
|
function workspaceKeyFor(workingDir) {
|
|
113325
113401
|
const normalized = normalizeWorkingDirForLookup(workingDir);
|
|
113326
|
-
return
|
|
113402
|
+
return createHash7("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
113327
113403
|
}
|
|
113328
113404
|
function buildPathIndex(entries2) {
|
|
113329
113405
|
const paths = {};
|
|
@@ -113397,7 +113473,7 @@ function entriesFromPathIndex(paths) {
|
|
|
113397
113473
|
}
|
|
113398
113474
|
var SYNC_STATE_VERSION = "2", SYNC_STATE_DIR;
|
|
113399
113475
|
var init_workspace_sync_state = __esm(() => {
|
|
113400
|
-
SYNC_STATE_DIR = join24(
|
|
113476
|
+
SYNC_STATE_DIR = join24(homedir8(), ".chatroom", "sync-state");
|
|
113401
113477
|
});
|
|
113402
113478
|
|
|
113403
113479
|
// src/infrastructure/services/workspace/workspace-file-tree-coordinator.ts
|
|
@@ -113547,11 +113623,6 @@ async function startWorkspaceFileTreeCoordinator(options) {
|
|
|
113547
113623
|
if (!delta)
|
|
113548
113624
|
break;
|
|
113549
113625
|
const result = await options.onDelta(delta, manifest.backendRevision);
|
|
113550
|
-
if (result.status === "conflict") {
|
|
113551
|
-
manifest.backendRevision = result.revision;
|
|
113552
|
-
await saveWorkspaceSyncManifest(manifest);
|
|
113553
|
-
continue;
|
|
113554
|
-
}
|
|
113555
113626
|
manifest.backendRevision = result.revision;
|
|
113556
113627
|
manifest.pendingDeltas.shift();
|
|
113557
113628
|
await saveWorkspaceSyncManifest(manifest);
|
|
@@ -113644,137 +113715,899 @@ async function startWorkspaceFileTreeCoordinator(options) {
|
|
|
113644
113715
|
await active2.stop();
|
|
113645
113716
|
const fs12 = createWorkspaceFsWatcher({
|
|
113646
113717
|
workingDir,
|
|
113647
|
-
shouldIgnore: shouldIgnorePath,
|
|
113648
|
-
onEvents: handleChangeSourceEvents,
|
|
113649
|
-
onError: handleChangeSourceError
|
|
113718
|
+
shouldIgnore: shouldIgnorePath,
|
|
113719
|
+
onEvents: handleChangeSourceEvents,
|
|
113720
|
+
onError: handleChangeSourceError
|
|
113721
|
+
});
|
|
113722
|
+
changeSource = fs12;
|
|
113723
|
+
await fs12.ready;
|
|
113724
|
+
console.log("[workspace-file-tree] change source: fs (degraded from git)");
|
|
113725
|
+
};
|
|
113726
|
+
const change = await createWorkspaceChangeSource({
|
|
113727
|
+
workingDir,
|
|
113728
|
+
pollIntervalMs: options.changeSourcePollIntervalMs,
|
|
113729
|
+
shouldIgnore: shouldIgnorePath,
|
|
113730
|
+
getKnownPaths: () => manifest.paths,
|
|
113731
|
+
onEvents: handleChangeSourceEvents,
|
|
113732
|
+
onNeedsReconcile: () => enqueueSerial(reconcileNow),
|
|
113733
|
+
onError: handleChangeSourceError,
|
|
113734
|
+
onPersistentFailure: () => degradeGitToFs("persistent git poll failures")
|
|
113735
|
+
});
|
|
113736
|
+
changeSource = change.source;
|
|
113737
|
+
changeSourceMode = change.mode;
|
|
113738
|
+
if (change.mode === "git") {
|
|
113739
|
+
console.log(`[workspace-file-tree] change source: git (${change.gitRepoCount ?? 1} repo${(change.gitRepoCount ?? 1) === 1 ? "" : "s"})`);
|
|
113740
|
+
} else {
|
|
113741
|
+
console.log("[workspace-file-tree] change source: fs");
|
|
113742
|
+
}
|
|
113743
|
+
await changeSource.ready;
|
|
113744
|
+
scheduleReconcile();
|
|
113745
|
+
return {
|
|
113746
|
+
workingDir,
|
|
113747
|
+
getManifest: () => manifest,
|
|
113748
|
+
getTree: () => treeFromManifest(manifest),
|
|
113749
|
+
checkpoint: () => enqueueSerial(publishCheckpoint),
|
|
113750
|
+
reconcile: () => enqueueSerial(reconcileNow),
|
|
113751
|
+
stop: async () => {
|
|
113752
|
+
stopped = true;
|
|
113753
|
+
if (reconcileTimer)
|
|
113754
|
+
clearTimeout(reconcileTimer);
|
|
113755
|
+
reconcileTimer = null;
|
|
113756
|
+
await changeSource?.stop();
|
|
113757
|
+
changeSource = null;
|
|
113758
|
+
await serial;
|
|
113759
|
+
await saveWorkspaceSyncManifest(manifest);
|
|
113760
|
+
}
|
|
113761
|
+
};
|
|
113762
|
+
}
|
|
113763
|
+
var DEFAULT_RECONCILE_INTERVAL_MS, DEFAULT_CHECKPOINT_EVERY_REVISIONS = 100, RECONCILE_JITTER_RATIO = 0.2, MAX_DELTA_OPERATIONS = 100;
|
|
113764
|
+
var init_workspace_file_tree_coordinator = __esm(() => {
|
|
113765
|
+
init_file_tree_data_hash();
|
|
113766
|
+
init_file_tree_scanner();
|
|
113767
|
+
init_git_workspace_porcelain();
|
|
113768
|
+
init_workspace_change_source();
|
|
113769
|
+
init_workspace_fs_watcher();
|
|
113770
|
+
init_workspace_ignore();
|
|
113771
|
+
init_workspace_sync_state();
|
|
113772
|
+
DEFAULT_RECONCILE_INTERVAL_MS = 10 * 60 * 1000;
|
|
113773
|
+
});
|
|
113774
|
+
|
|
113775
|
+
// src/infrastructure/services/workspace/workspace-sync-config.ts
|
|
113776
|
+
var FILE_TREE_SYNC_DEBOUNCE_MS = 5000;
|
|
113777
|
+
|
|
113778
|
+
// src/infrastructure/services/workspace/workspace-sync-queue.ts
|
|
113779
|
+
function queueKey(machineId, workingDir) {
|
|
113780
|
+
return `${machineId}\x00${workingDir}`;
|
|
113781
|
+
}
|
|
113782
|
+
function getOrCreateState(key) {
|
|
113783
|
+
let state = queues.get(key);
|
|
113784
|
+
if (!state) {
|
|
113785
|
+
state = { drainPromise: null, pendingTask: null, scheduled: false };
|
|
113786
|
+
queues.set(key, state);
|
|
113787
|
+
}
|
|
113788
|
+
return state;
|
|
113789
|
+
}
|
|
113790
|
+
function sleep7(ms) {
|
|
113791
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
113792
|
+
}
|
|
113793
|
+
async function drainQueue(state, key, debounceMs) {
|
|
113794
|
+
try {
|
|
113795
|
+
while (state.pendingTask) {
|
|
113796
|
+
const lastCompletedAt = lastCompletedAtByKey.get(key) ?? 0;
|
|
113797
|
+
const elapsed3 = Date.now() - lastCompletedAt;
|
|
113798
|
+
const waitMs = lastCompletedAt === 0 ? 0 : Math.max(0, debounceMs - elapsed3);
|
|
113799
|
+
if (waitMs > 0) {
|
|
113800
|
+
await sleep7(waitMs);
|
|
113801
|
+
}
|
|
113802
|
+
const task = state.pendingTask;
|
|
113803
|
+
if (!task)
|
|
113804
|
+
break;
|
|
113805
|
+
state.pendingTask = null;
|
|
113806
|
+
await task();
|
|
113807
|
+
lastCompletedAtByKey.set(key, Date.now());
|
|
113808
|
+
}
|
|
113809
|
+
} finally {
|
|
113810
|
+
state.drainPromise = null;
|
|
113811
|
+
state.scheduled = false;
|
|
113812
|
+
if (state.pendingTask) {
|
|
113813
|
+
kickDrain(state, key, debounceMs);
|
|
113814
|
+
} else {
|
|
113815
|
+
queues.delete(key);
|
|
113816
|
+
}
|
|
113817
|
+
}
|
|
113818
|
+
}
|
|
113819
|
+
function kickDrain(state, key, debounceMs) {
|
|
113820
|
+
if (state.scheduled || state.drainPromise)
|
|
113821
|
+
return;
|
|
113822
|
+
state.scheduled = true;
|
|
113823
|
+
state.drainPromise = new Promise((resolve8, reject) => {
|
|
113824
|
+
queueMicrotask(() => {
|
|
113825
|
+
drainQueue(state, key, debounceMs).then(resolve8, reject);
|
|
113826
|
+
});
|
|
113827
|
+
});
|
|
113828
|
+
}
|
|
113829
|
+
async function enqueueFileTreeSync(machineId, workingDir, task, options) {
|
|
113830
|
+
const debounceMs = options?.debounceMs ?? FILE_TREE_SYNC_DEBOUNCE_MS;
|
|
113831
|
+
const key = queueKey(machineId, workingDir);
|
|
113832
|
+
const state = getOrCreateState(key);
|
|
113833
|
+
state.pendingTask = task;
|
|
113834
|
+
kickDrain(state, key, debounceMs);
|
|
113835
|
+
return state.drainPromise ?? Promise.resolve();
|
|
113836
|
+
}
|
|
113837
|
+
var queues, lastCompletedAtByKey;
|
|
113838
|
+
var init_workspace_sync_queue = __esm(() => {
|
|
113839
|
+
queues = new Map;
|
|
113840
|
+
lastCompletedAtByKey = new Map;
|
|
113841
|
+
});
|
|
113842
|
+
|
|
113843
|
+
// src/daemon/infrastructure/outbox/lib/durable-coalescing-state-schema.ts
|
|
113844
|
+
var DURABLE_COALESCING_STATE_MIGRATIONS;
|
|
113845
|
+
var init_durable_coalescing_state_schema = __esm(() => {
|
|
113846
|
+
DURABLE_COALESCING_STATE_MIGRATIONS = [
|
|
113847
|
+
`CREATE TABLE IF NOT EXISTS schema_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)`,
|
|
113848
|
+
`CREATE TABLE IF NOT EXISTS coalescing_outbox_entries (delivery_key TEXT PRIMARY KEY, payload_json TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', attempts INTEGER NOT NULL DEFAULT 0, last_error TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`
|
|
113849
|
+
];
|
|
113850
|
+
});
|
|
113851
|
+
|
|
113852
|
+
// src/daemon/infrastructure/outbox/lib/durable-coalescing-state-store.ts
|
|
113853
|
+
import { mkdirSync as mkdirSync6 } from "node:fs";
|
|
113854
|
+
import { dirname as dirname12 } from "node:path";
|
|
113855
|
+
import { DatabaseSync } from "node:sqlite";
|
|
113856
|
+
function openDurableCoalescingStateStore(path8) {
|
|
113857
|
+
mkdirSync6(dirname12(path8), { recursive: true });
|
|
113858
|
+
const db = new DatabaseSync(path8);
|
|
113859
|
+
for (const sql of DURABLE_COALESCING_STATE_MIGRATIONS)
|
|
113860
|
+
db.exec(sql);
|
|
113861
|
+
db.prepare("UPDATE coalescing_outbox_entries SET status='pending' WHERE status='in_flight'").run();
|
|
113862
|
+
return { upsertPending: (key, payload) => {
|
|
113863
|
+
const now = Date.now();
|
|
113864
|
+
db.prepare("INSERT INTO coalescing_outbox_entries(delivery_key,payload_json,status,created_at,updated_at) VALUES(?,?,'pending',?,?) ON CONFLICT(delivery_key) DO UPDATE SET payload_json=excluded.payload_json,status='pending',updated_at=excluded.updated_at").run(key, payload, now, now);
|
|
113865
|
+
}, getPending: (key) => db.prepare("SELECT payload_json as payloadJson, attempts FROM coalescing_outbox_entries WHERE delivery_key=? AND status IN ('pending','in_flight')").get(key) ?? null, markDone: (key) => {
|
|
113866
|
+
db.prepare("DELETE FROM coalescing_outbox_entries WHERE delivery_key=?").run(key);
|
|
113867
|
+
}, markPendingRetry: (key, error51) => {
|
|
113868
|
+
db.prepare("UPDATE coalescing_outbox_entries SET status='pending',last_error=?,attempts=attempts+1,updated_at=? WHERE delivery_key=?").run(String(error51), Date.now(), key);
|
|
113869
|
+
}, listPendingKeys: () => db.prepare("SELECT delivery_key as key FROM coalescing_outbox_entries WHERE status IN ('pending','in_flight')").all().map((r) => String(r.key)), close: () => db.close() };
|
|
113870
|
+
}
|
|
113871
|
+
var init_durable_coalescing_state_store = __esm(() => {
|
|
113872
|
+
init_durable_coalescing_state_schema();
|
|
113873
|
+
});
|
|
113874
|
+
|
|
113875
|
+
// src/daemon/infrastructure/outbox/lib/outbox-retry-backoff.ts
|
|
113876
|
+
function computeExponentialRetryDelayMs(attempt, retryDelayMs, maxRetryDelayMs) {
|
|
113877
|
+
const exponent = Math.min(Math.max(0, attempt), MAX_RETRY_EXPONENT);
|
|
113878
|
+
return Math.min(retryDelayMs * 2 ** exponent, maxRetryDelayMs);
|
|
113879
|
+
}
|
|
113880
|
+
var MAX_RETRY_EXPONENT = 20;
|
|
113881
|
+
|
|
113882
|
+
// src/daemon/infrastructure/outbox/lib/coalescing-state-outbox.ts
|
|
113883
|
+
function createCoalescingStateOutbox(options) {
|
|
113884
|
+
const durable = [options.store, options.deliveryKey, options.serialize, options.deserialize];
|
|
113885
|
+
if (durable.some(Boolean) && durable.some((value) => !value))
|
|
113886
|
+
throw new Error("Durable outbox requires store, deliveryKey, serialize, and deserialize");
|
|
113887
|
+
const minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS;
|
|
113888
|
+
const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
113889
|
+
const maxRetryDelayMs = options.maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;
|
|
113890
|
+
let pending3 = null;
|
|
113891
|
+
let inFlight2 = null;
|
|
113892
|
+
let timer = null;
|
|
113893
|
+
let lastSentAt = 0;
|
|
113894
|
+
let retryAttempt = 0;
|
|
113895
|
+
let stopped = false;
|
|
113896
|
+
if (options.store && options.deliveryKey && options.deserialize) {
|
|
113897
|
+
const recovered = options.store.getPending(options.deliveryKey);
|
|
113898
|
+
if (recovered) {
|
|
113899
|
+
pending3 = { state: options.deserialize(recovered.payloadJson), waiters: [] };
|
|
113900
|
+
retryAttempt = recovered.attempts;
|
|
113901
|
+
}
|
|
113902
|
+
}
|
|
113903
|
+
const rejectWaiters = (waiters, error51) => {
|
|
113904
|
+
for (const waiter of waiters)
|
|
113905
|
+
waiter.reject(error51);
|
|
113906
|
+
};
|
|
113907
|
+
const mergePending = (older, newer) => ({
|
|
113908
|
+
state: newer?.state ?? older.state,
|
|
113909
|
+
waiters: [...older.waiters, ...newer?.waiters ?? []]
|
|
113910
|
+
});
|
|
113911
|
+
const clearTimer = () => {
|
|
113912
|
+
if (timer)
|
|
113913
|
+
clearTimeout(timer);
|
|
113914
|
+
timer = null;
|
|
113915
|
+
};
|
|
113916
|
+
const schedule2 = (delayMs) => {
|
|
113917
|
+
if (stopped || !pending3 || inFlight2 || timer)
|
|
113918
|
+
return;
|
|
113919
|
+
timer = setTimeout(() => {
|
|
113920
|
+
timer = null;
|
|
113921
|
+
drain2().catch(() => {
|
|
113922
|
+
return;
|
|
113923
|
+
});
|
|
113924
|
+
}, Math.max(0, delayMs));
|
|
113925
|
+
timer.unref?.();
|
|
113926
|
+
};
|
|
113927
|
+
if (pending3) {
|
|
113928
|
+
schedule2(retryAttempt > 0 ? computeExponentialRetryDelayMs(retryAttempt - 1, retryDelayMs, maxRetryDelayMs) : 0);
|
|
113929
|
+
}
|
|
113930
|
+
async function drain2() {
|
|
113931
|
+
if (stopped || !pending3 || inFlight2)
|
|
113932
|
+
return;
|
|
113933
|
+
const batch = pending3;
|
|
113934
|
+
pending3 = null;
|
|
113935
|
+
const sendPromise = Promise.resolve().then(() => options.send(batch.state));
|
|
113936
|
+
inFlight2 = { ...batch, promise: sendPromise };
|
|
113937
|
+
let retryDelay = null;
|
|
113938
|
+
const store = options.store;
|
|
113939
|
+
const deliveryKey = options.deliveryKey;
|
|
113940
|
+
try {
|
|
113941
|
+
const result = await sendPromise;
|
|
113942
|
+
lastSentAt = Date.now();
|
|
113943
|
+
retryAttempt = 0;
|
|
113944
|
+
if (store && deliveryKey)
|
|
113945
|
+
store.markDone(deliveryKey);
|
|
113946
|
+
for (const waiter of batch.waiters)
|
|
113947
|
+
waiter.resolve(result);
|
|
113948
|
+
} catch (error51) {
|
|
113949
|
+
options.onError?.(error51);
|
|
113950
|
+
if (store && deliveryKey)
|
|
113951
|
+
store.markPendingRetry(deliveryKey, error51);
|
|
113952
|
+
pending3 = mergePending(batch, pending3);
|
|
113953
|
+
retryDelay = computeExponentialRetryDelayMs(retryAttempt, retryDelayMs, maxRetryDelayMs);
|
|
113954
|
+
retryAttempt++;
|
|
113955
|
+
throw error51;
|
|
113956
|
+
} finally {
|
|
113957
|
+
inFlight2 = null;
|
|
113958
|
+
if (pending3) {
|
|
113959
|
+
const intervalDelay = Math.max(0, minIntervalMs - (Date.now() - lastSentAt));
|
|
113960
|
+
schedule2(retryDelay ?? intervalDelay);
|
|
113961
|
+
}
|
|
113962
|
+
}
|
|
113963
|
+
}
|
|
113964
|
+
const enqueue = (state) => {
|
|
113965
|
+
if (stopped)
|
|
113966
|
+
return Promise.reject(new Error("Outbox is stopped"));
|
|
113967
|
+
const promise4 = new Promise((resolve8, reject) => {
|
|
113968
|
+
const waiter = { resolve: resolve8, reject };
|
|
113969
|
+
if (pending3) {
|
|
113970
|
+
pending3 = { state, waiters: [...pending3.waiters, waiter] };
|
|
113971
|
+
} else {
|
|
113972
|
+
pending3 = { state, waiters: [waiter] };
|
|
113973
|
+
}
|
|
113974
|
+
if (options.store && options.deliveryKey && options.serialize) {
|
|
113975
|
+
options.store.upsertPending(options.deliveryKey, options.serialize(state));
|
|
113976
|
+
}
|
|
113977
|
+
});
|
|
113978
|
+
if (!inFlight2 && !timer) {
|
|
113979
|
+
const delay3 = lastSentAt === 0 ? 0 : Math.max(0, minIntervalMs - (Date.now() - lastSentAt));
|
|
113980
|
+
schedule2(delay3);
|
|
113981
|
+
}
|
|
113982
|
+
return promise4;
|
|
113983
|
+
};
|
|
113984
|
+
const flushNow = async () => {
|
|
113985
|
+
if (stopped)
|
|
113986
|
+
return;
|
|
113987
|
+
clearTimer();
|
|
113988
|
+
if (inFlight2) {
|
|
113989
|
+
await inFlight2.promise.catch(() => {
|
|
113990
|
+
return;
|
|
113991
|
+
});
|
|
113992
|
+
}
|
|
113993
|
+
if (pending3)
|
|
113994
|
+
await drain2();
|
|
113995
|
+
};
|
|
113996
|
+
const stop4 = async () => {
|
|
113997
|
+
if (stopped)
|
|
113998
|
+
return;
|
|
113999
|
+
await flushNow();
|
|
114000
|
+
stopped = true;
|
|
114001
|
+
clearTimer();
|
|
114002
|
+
if (inFlight2)
|
|
114003
|
+
await inFlight2.promise.catch(() => {
|
|
114004
|
+
return;
|
|
114005
|
+
});
|
|
114006
|
+
const unsent = pending3;
|
|
114007
|
+
pending3 = null;
|
|
114008
|
+
if (unsent)
|
|
114009
|
+
rejectWaiters(unsent.waiters, new Error("Outbox stopped before send completed"));
|
|
114010
|
+
};
|
|
114011
|
+
return { enqueue, flushNow, stop: stop4 };
|
|
114012
|
+
}
|
|
114013
|
+
var DEFAULT_MIN_INTERVAL_MS = 5000, DEFAULT_RETRY_DELAY_MS = 500, DEFAULT_MAX_RETRY_DELAY_MS = 5000;
|
|
114014
|
+
var init_coalescing_state_outbox = () => {};
|
|
114015
|
+
|
|
114016
|
+
// src/daemon/infrastructure/outbox/lib/keyed-coalescing-state-outbox-registry.ts
|
|
114017
|
+
function createKeyedCoalescingStateOutboxRegistry(options) {
|
|
114018
|
+
const outboxes = new Map;
|
|
114019
|
+
const getOrCreate = (key) => {
|
|
114020
|
+
let outbox = outboxes.get(key);
|
|
114021
|
+
if (!outbox) {
|
|
114022
|
+
outbox = createCoalescingStateOutbox({
|
|
114023
|
+
send: (state) => options.createSend(key)(state),
|
|
114024
|
+
minIntervalMs: options.minIntervalMs,
|
|
114025
|
+
retryDelayMs: options.retryDelayMs,
|
|
114026
|
+
maxRetryDelayMs: options.maxRetryDelayMs,
|
|
114027
|
+
onError: (error51) => options.onError?.(key, error51),
|
|
114028
|
+
...options.store ? { store: options.store, deliveryKey: key, serialize: options.serialize, deserialize: options.deserialize } : {}
|
|
114029
|
+
});
|
|
114030
|
+
outboxes.set(key, outbox);
|
|
114031
|
+
}
|
|
114032
|
+
return outbox;
|
|
114033
|
+
};
|
|
114034
|
+
const stop4 = async (key) => {
|
|
114035
|
+
const outbox = outboxes.get(key);
|
|
114036
|
+
outboxes.delete(key);
|
|
114037
|
+
await outbox?.flushNow().catch(() => {
|
|
114038
|
+
return;
|
|
114039
|
+
});
|
|
114040
|
+
await outbox?.stop().catch(() => {
|
|
114041
|
+
return;
|
|
114042
|
+
});
|
|
114043
|
+
};
|
|
114044
|
+
return {
|
|
114045
|
+
enqueue: (key, state) => getOrCreate(key).enqueue(state),
|
|
114046
|
+
stop: stop4,
|
|
114047
|
+
stopAll: async () => {
|
|
114048
|
+
for (const key of [...outboxes.keys()])
|
|
114049
|
+
await stop4(key);
|
|
114050
|
+
options.store?.close();
|
|
114051
|
+
}
|
|
114052
|
+
};
|
|
114053
|
+
}
|
|
114054
|
+
var init_keyed_coalescing_state_outbox_registry = __esm(() => {
|
|
114055
|
+
init_coalescing_state_outbox();
|
|
114056
|
+
});
|
|
114057
|
+
|
|
114058
|
+
// src/daemon/infrastructure/outbox/outbox-db-path.ts
|
|
114059
|
+
import { homedir as homedir9 } from "node:os";
|
|
114060
|
+
import { join as join25 } from "node:path";
|
|
114061
|
+
function resolveOutboxDbPath(machineId, kind) {
|
|
114062
|
+
return join25(homedir9(), ".chatroom", "daemon", machineId, `outbox-${kind}.sqlite`);
|
|
114063
|
+
}
|
|
114064
|
+
var init_outbox_db_path = () => {};
|
|
114065
|
+
|
|
114066
|
+
// src/daemon/infrastructure/outbox/workspace-file-tree-checkpoint-outbox.ts
|
|
114067
|
+
function createWorkspaceFileTreeCheckpointOutboxRegistry(machineId, createSend, options) {
|
|
114068
|
+
return createKeyedCoalescingStateOutboxRegistry({
|
|
114069
|
+
store: openDurableCoalescingStateStore(resolveOutboxDbPath(machineId, "file-tree-checkpoint")),
|
|
114070
|
+
serialize: JSON.stringify,
|
|
114071
|
+
deserialize: JSON.parse,
|
|
114072
|
+
createSend,
|
|
114073
|
+
minIntervalMs: WORKSPACE_FILE_TREE_CHECKPOINT_OUTBOX_MIN_INTERVAL_MS,
|
|
114074
|
+
retryDelayMs: WORKSPACE_FILE_TREE_CHECKPOINT_OUTBOX_RETRY_DELAY_MS,
|
|
114075
|
+
maxRetryDelayMs: WORKSPACE_FILE_TREE_CHECKPOINT_OUTBOX_MAX_RETRY_DELAY_MS,
|
|
114076
|
+
onError: options?.onError
|
|
114077
|
+
});
|
|
114078
|
+
}
|
|
114079
|
+
var WORKSPACE_FILE_TREE_CHECKPOINT_OUTBOX_MIN_INTERVAL_MS = 5000, WORKSPACE_FILE_TREE_CHECKPOINT_OUTBOX_RETRY_DELAY_MS = 5000, WORKSPACE_FILE_TREE_CHECKPOINT_OUTBOX_MAX_RETRY_DELAY_MS;
|
|
114080
|
+
var init_workspace_file_tree_checkpoint_outbox = __esm(() => {
|
|
114081
|
+
init_durable_coalescing_state_store();
|
|
114082
|
+
init_keyed_coalescing_state_outbox_registry();
|
|
114083
|
+
init_outbox_db_path();
|
|
114084
|
+
WORKSPACE_FILE_TREE_CHECKPOINT_OUTBOX_MAX_RETRY_DELAY_MS = 5 * 60000;
|
|
114085
|
+
});
|
|
114086
|
+
|
|
114087
|
+
// src/daemon/infrastructure/outbox/lib/durable-fifo-queue-schema.ts
|
|
114088
|
+
var DURABLE_FIFO_QUEUE_MIGRATIONS;
|
|
114089
|
+
var init_durable_fifo_queue_schema = __esm(() => {
|
|
114090
|
+
DURABLE_FIFO_QUEUE_MIGRATIONS = [
|
|
114091
|
+
`CREATE TABLE IF NOT EXISTS schema_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)`,
|
|
114092
|
+
`CREATE TABLE IF NOT EXISTS fifo_outbox_entries (id INTEGER PRIMARY KEY AUTOINCREMENT, delivery_key TEXT NOT NULL, payload_json TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', attempts INTEGER NOT NULL DEFAULT 0, last_error TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
|
|
114093
|
+
`CREATE INDEX IF NOT EXISTS idx_fifo_outbox_pending ON fifo_outbox_entries(delivery_key, status, id)`
|
|
114094
|
+
];
|
|
114095
|
+
});
|
|
114096
|
+
|
|
114097
|
+
// src/daemon/infrastructure/outbox/lib/durable-fifo-queue-store.ts
|
|
114098
|
+
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
114099
|
+
import { mkdirSync as mkdirSync7 } from "node:fs";
|
|
114100
|
+
import { dirname as dirname13 } from "node:path";
|
|
114101
|
+
function openDurableFifoQueueStore(dbPath) {
|
|
114102
|
+
mkdirSync7(dirname13(dbPath), { recursive: true });
|
|
114103
|
+
const db = new DatabaseSync2(dbPath);
|
|
114104
|
+
for (const sql of DURABLE_FIFO_QUEUE_MIGRATIONS)
|
|
114105
|
+
db.exec(sql);
|
|
114106
|
+
db.prepare("UPDATE fifo_outbox_entries SET status='pending', updated_at=? WHERE status='in_flight'").run(Date.now());
|
|
114107
|
+
return {
|
|
114108
|
+
enqueue: (key, payloadJson) => {
|
|
114109
|
+
const now = Date.now();
|
|
114110
|
+
return Number(db.prepare("INSERT INTO fifo_outbox_entries(delivery_key,payload_json,created_at,updated_at) VALUES(?,?,?,?) RETURNING id").get(key, payloadJson, now, now).id);
|
|
114111
|
+
},
|
|
114112
|
+
claimNextBatch: (key, limit) => db.prepare("UPDATE fifo_outbox_entries SET status='in_flight', attempts=attempts+1, updated_at=? WHERE id IN (SELECT id FROM fifo_outbox_entries WHERE delivery_key=? AND status='pending' ORDER BY id LIMIT ?) RETURNING id, delivery_key as deliveryKey, payload_json as payloadJson, attempts").all(Date.now(), key, limit),
|
|
114113
|
+
markDone: (id3) => {
|
|
114114
|
+
db.prepare("DELETE FROM fifo_outbox_entries WHERE id=?").run(id3);
|
|
114115
|
+
},
|
|
114116
|
+
markPending: (id3) => {
|
|
114117
|
+
db.prepare("UPDATE fifo_outbox_entries SET status='pending', last_error=NULL, updated_at=? WHERE id=?").run(Date.now(), id3);
|
|
114118
|
+
},
|
|
114119
|
+
updatePayload: (id3, payloadJson) => {
|
|
114120
|
+
db.prepare("UPDATE fifo_outbox_entries SET payload_json=?, updated_at=? WHERE id=?").run(payloadJson, Date.now(), id3);
|
|
114121
|
+
},
|
|
114122
|
+
markPendingRetry: (id3, error51) => {
|
|
114123
|
+
db.prepare("UPDATE fifo_outbox_entries SET status='pending', last_error=?, updated_at=? WHERE id=?").run(String(error51), Date.now(), id3);
|
|
114124
|
+
},
|
|
114125
|
+
listPendingForRecovery: (key) => db.prepare("SELECT id, delivery_key as deliveryKey, payload_json as payloadJson, attempts FROM fifo_outbox_entries WHERE delivery_key=? AND status IN ('pending','in_flight') ORDER BY id").all(key),
|
|
114126
|
+
close: () => db.close()
|
|
114127
|
+
};
|
|
114128
|
+
}
|
|
114129
|
+
var init_durable_fifo_queue_store = __esm(() => {
|
|
114130
|
+
init_durable_fifo_queue_schema();
|
|
114131
|
+
});
|
|
114132
|
+
|
|
114133
|
+
// src/daemon/infrastructure/outbox/lib/fifo-batched-outbox.ts
|
|
114134
|
+
function createFifoBatchedOutbox(o) {
|
|
114135
|
+
const waiters = new Map;
|
|
114136
|
+
let running3;
|
|
114137
|
+
let stopped = false;
|
|
114138
|
+
let retryTimer;
|
|
114139
|
+
let retryAttempt = 0;
|
|
114140
|
+
const retryDelay = () => computeExponentialRetryDelayMs(retryAttempt++, o.retryDelayMs ?? 500, o.maxRetryDelayMs ?? 5000);
|
|
114141
|
+
const scheduleRetry = () => {
|
|
114142
|
+
if (!stopped && !retryTimer) {
|
|
114143
|
+
retryTimer = setTimeout(() => {
|
|
114144
|
+
retryTimer = undefined;
|
|
114145
|
+
drain2().catch(() => {
|
|
114146
|
+
return;
|
|
114147
|
+
});
|
|
114148
|
+
}, retryDelay());
|
|
114149
|
+
retryTimer.unref?.();
|
|
114150
|
+
}
|
|
114151
|
+
};
|
|
114152
|
+
const drain2 = async () => {
|
|
114153
|
+
if (running3)
|
|
114154
|
+
return running3;
|
|
114155
|
+
running3 = (async () => {
|
|
114156
|
+
while (true) {
|
|
114157
|
+
const rows2 = o.store.claimNextBatch(o.deliveryKey, o.batchSize);
|
|
114158
|
+
if (!rows2.length)
|
|
114159
|
+
break;
|
|
114160
|
+
try {
|
|
114161
|
+
const results = await o.send(rows2.map((r) => o.deserialize(r.payloadJson)));
|
|
114162
|
+
let hasRetry = false;
|
|
114163
|
+
for (let i2 = 0;i2 < results.length; i2++) {
|
|
114164
|
+
const item = o.deserialize(rows2[i2].payloadJson);
|
|
114165
|
+
const outcome = o.classifyOutcome?.(results[i2], item) ?? { kind: "success" };
|
|
114166
|
+
if (outcome.kind === "retry") {
|
|
114167
|
+
o.store.updatePayload(rows2[i2].id, o.serialize(outcome.item));
|
|
114168
|
+
o.store.markPending(rows2[i2].id);
|
|
114169
|
+
hasRetry = true;
|
|
114170
|
+
continue;
|
|
114171
|
+
}
|
|
114172
|
+
o.store.markDone(rows2[i2].id);
|
|
114173
|
+
waiters.get(rows2[i2].id)?.resolve(results[i2]);
|
|
114174
|
+
waiters.delete(rows2[i2].id);
|
|
114175
|
+
}
|
|
114176
|
+
for (let i2 = results.length;i2 < rows2.length; i2++)
|
|
114177
|
+
o.store.markPending(rows2[i2].id);
|
|
114178
|
+
if (hasRetry) {
|
|
114179
|
+
scheduleRetry();
|
|
114180
|
+
break;
|
|
114181
|
+
}
|
|
114182
|
+
retryAttempt = 0;
|
|
114183
|
+
} catch (e) {
|
|
114184
|
+
o.onError?.(e);
|
|
114185
|
+
for (const r of rows2)
|
|
114186
|
+
o.store.markPendingRetry(r.id, e);
|
|
114187
|
+
scheduleRetry();
|
|
114188
|
+
break;
|
|
114189
|
+
}
|
|
114190
|
+
}
|
|
114191
|
+
})().finally(() => {
|
|
114192
|
+
running3 = undefined;
|
|
114193
|
+
});
|
|
114194
|
+
return running3;
|
|
114195
|
+
};
|
|
114196
|
+
const enqueue = (item) => {
|
|
114197
|
+
if (stopped)
|
|
114198
|
+
return Promise.reject(new Error("Outbox is stopped"));
|
|
114199
|
+
const id3 = o.store.enqueue(o.deliveryKey, o.serialize(item));
|
|
114200
|
+
const p = new Promise((resolve8, reject) => waiters.set(id3, { resolve: resolve8, reject }));
|
|
114201
|
+
drain2().catch(() => {
|
|
114202
|
+
return;
|
|
114203
|
+
});
|
|
114204
|
+
return p;
|
|
114205
|
+
};
|
|
114206
|
+
const flushNow = async () => {
|
|
114207
|
+
while (o.store.listPendingForRecovery(o.deliveryKey).length) {
|
|
114208
|
+
await drain2();
|
|
114209
|
+
}
|
|
114210
|
+
};
|
|
114211
|
+
const stop4 = async () => {
|
|
114212
|
+
if (stopped)
|
|
114213
|
+
return;
|
|
114214
|
+
if (retryTimer)
|
|
114215
|
+
clearTimeout(retryTimer);
|
|
114216
|
+
retryTimer = undefined;
|
|
114217
|
+
await flushNow();
|
|
114218
|
+
stopped = true;
|
|
114219
|
+
};
|
|
114220
|
+
return { enqueue, flushNow, stop: stop4 };
|
|
114221
|
+
}
|
|
114222
|
+
var init_fifo_batched_outbox = () => {};
|
|
114223
|
+
|
|
114224
|
+
// src/daemon/infrastructure/outbox/lib/keyed-fifo-batched-outbox-registry.ts
|
|
114225
|
+
function createKeyedFifoBatchedOutboxRegistry(o) {
|
|
114226
|
+
const boxes = new Map;
|
|
114227
|
+
const get13 = (key) => {
|
|
114228
|
+
let b = boxes.get(key);
|
|
114229
|
+
if (!b) {
|
|
114230
|
+
b = createFifoBatchedOutbox({
|
|
114231
|
+
batchSize: o.batchSize,
|
|
114232
|
+
send: o.createSend(key),
|
|
114233
|
+
store: o.store,
|
|
114234
|
+
deliveryKey: key,
|
|
114235
|
+
serialize: o.serialize,
|
|
114236
|
+
deserialize: o.deserialize,
|
|
114237
|
+
retryDelayMs: o.retryDelayMs,
|
|
114238
|
+
maxRetryDelayMs: o.maxRetryDelayMs,
|
|
114239
|
+
onError: (e) => o.onError?.(key, e),
|
|
114240
|
+
classifyOutcome: o.classifyOutcome
|
|
114241
|
+
});
|
|
114242
|
+
boxes.set(key, b);
|
|
114243
|
+
}
|
|
114244
|
+
return b;
|
|
114245
|
+
};
|
|
114246
|
+
const stop4 = async (k) => {
|
|
114247
|
+
const b = boxes.get(k);
|
|
114248
|
+
if (b) {
|
|
114249
|
+
await b.stop();
|
|
114250
|
+
boxes.delete(k);
|
|
114251
|
+
}
|
|
114252
|
+
};
|
|
114253
|
+
return {
|
|
114254
|
+
enqueue: (k, i2) => get13(k).enqueue(i2),
|
|
114255
|
+
flushNow: async (k) => {
|
|
114256
|
+
if (k)
|
|
114257
|
+
await get13(k).flushNow();
|
|
114258
|
+
else
|
|
114259
|
+
for (const b of boxes.values())
|
|
114260
|
+
await b.flushNow();
|
|
114261
|
+
},
|
|
114262
|
+
stop: stop4,
|
|
114263
|
+
stopAll: async () => {
|
|
114264
|
+
for (const k of [...boxes.keys()])
|
|
114265
|
+
await stop4(k);
|
|
114266
|
+
o.store.close();
|
|
114267
|
+
}
|
|
114268
|
+
};
|
|
114269
|
+
}
|
|
114270
|
+
var init_keyed_fifo_batched_outbox_registry = __esm(() => {
|
|
114271
|
+
init_fifo_batched_outbox();
|
|
114272
|
+
});
|
|
114273
|
+
|
|
114274
|
+
// src/daemon/infrastructure/outbox/workspace-file-tree-delta-outbox.ts
|
|
114275
|
+
function createWorkspaceFileTreeDeltaOutboxRegistry(machineId, createSend, options) {
|
|
114276
|
+
const store = openDurableFifoQueueStore(resolveOutboxDbPath(machineId, "file-tree-delta"));
|
|
114277
|
+
return createKeyedFifoBatchedOutboxRegistry({
|
|
114278
|
+
store,
|
|
114279
|
+
batchSize: options?.batchSize ?? WORKSPACE_FILE_TREE_DELTA_OUTBOX_BATCH_SIZE,
|
|
114280
|
+
createSend: (key) => async (units) => {
|
|
114281
|
+
const out = [];
|
|
114282
|
+
for (const u of units) {
|
|
114283
|
+
const r = await createSend(key)(u);
|
|
114284
|
+
out.push(r);
|
|
114285
|
+
}
|
|
114286
|
+
return out;
|
|
114287
|
+
},
|
|
114288
|
+
serialize: JSON.stringify,
|
|
114289
|
+
deserialize: JSON.parse,
|
|
114290
|
+
retryDelayMs: WORKSPACE_FILE_TREE_DELTA_OUTBOX_RETRY_DELAY_MS,
|
|
114291
|
+
maxRetryDelayMs: WORKSPACE_FILE_TREE_DELTA_OUTBOX_MAX_RETRY_DELAY_MS,
|
|
114292
|
+
onError: options?.onError,
|
|
114293
|
+
classifyOutcome: (result, unit) => result.status === "conflict" ? { kind: "retry", item: { ...unit, baseRevision: result.revision } } : { kind: "success" }
|
|
114294
|
+
});
|
|
114295
|
+
}
|
|
114296
|
+
var WORKSPACE_FILE_TREE_DELTA_OUTBOX_BATCH_SIZE = 5, WORKSPACE_FILE_TREE_DELTA_OUTBOX_RETRY_DELAY_MS = 5000, WORKSPACE_FILE_TREE_DELTA_OUTBOX_MAX_RETRY_DELAY_MS;
|
|
114297
|
+
var init_workspace_file_tree_delta_outbox = __esm(() => {
|
|
114298
|
+
init_durable_fifo_queue_store();
|
|
114299
|
+
init_keyed_fifo_batched_outbox_registry();
|
|
114300
|
+
init_outbox_db_path();
|
|
114301
|
+
WORKSPACE_FILE_TREE_DELTA_OUTBOX_MAX_RETRY_DELAY_MS = 5 * 60000;
|
|
114302
|
+
});
|
|
114303
|
+
|
|
114304
|
+
// src/daemon/infrastructure/outbox/index.ts
|
|
114305
|
+
var init_outbox = __esm(() => {
|
|
114306
|
+
init_workspace_file_tree_checkpoint_outbox();
|
|
114307
|
+
init_workspace_file_tree_delta_outbox();
|
|
114308
|
+
});
|
|
114309
|
+
|
|
114310
|
+
// ../../services/backend/src/domain/workspace-file-tree/types.ts
|
|
114311
|
+
var MAX_TREE_JSON_BYTES;
|
|
114312
|
+
var init_types2 = __esm(() => {
|
|
114313
|
+
MAX_TREE_JSON_BYTES = 900 * 1024;
|
|
114314
|
+
});
|
|
114315
|
+
// ../../services/backend/src/domain/workspace-file-tree/select-strategy.ts
|
|
114316
|
+
function treeJsonByteLength(tree) {
|
|
114317
|
+
return Buffer.byteLength(JSON.stringify(tree), "utf8");
|
|
114318
|
+
}
|
|
114319
|
+
function selectFileTreeSnapshotStrategyId(tree) {
|
|
114320
|
+
return treeJsonByteLength(tree) > MAX_TREE_JSON_BYTES ? "sharded" : "blob";
|
|
114321
|
+
}
|
|
114322
|
+
var init_select_strategy = __esm(() => {
|
|
114323
|
+
init_types2();
|
|
114324
|
+
});
|
|
114325
|
+
|
|
114326
|
+
// ../../services/backend/src/domain/workspace-file-tree/registry.ts
|
|
114327
|
+
var init_registry5 = __esm(() => {
|
|
114328
|
+
init_select_strategy();
|
|
114329
|
+
});
|
|
114330
|
+
// ../../services/backend/src/domain/workspace-file-tree/transport/blob-snapshot.ts
|
|
114331
|
+
function toLegacyBlobSyncArgs(payload) {
|
|
114332
|
+
return { data: payload.data, dataHash: payload.dataHash, scannedAt: payload.scannedAt };
|
|
114333
|
+
}
|
|
114334
|
+
|
|
114335
|
+
// ../../services/backend/src/domain/workspace-file-tree/transport/sharded-snapshot.ts
|
|
114336
|
+
function toLegacyShardBatchItem(shard) {
|
|
114337
|
+
return { ...shard };
|
|
114338
|
+
}
|
|
114339
|
+
function toLegacyManifestSyncArgs(manifest) {
|
|
114340
|
+
return { ...manifest };
|
|
114341
|
+
}
|
|
114342
|
+
// ../../services/backend/src/domain/workspace-file-tree/transport/index.ts
|
|
114343
|
+
var init_transport = () => {};
|
|
114344
|
+
|
|
114345
|
+
// ../../services/backend/src/domain/workspace-file-tree/index.ts
|
|
114346
|
+
var init_workspace_file_tree = __esm(() => {
|
|
114347
|
+
init_types2();
|
|
114348
|
+
init_select_strategy();
|
|
114349
|
+
init_registry5();
|
|
114350
|
+
init_transport();
|
|
114351
|
+
});
|
|
114352
|
+
|
|
114353
|
+
// src/infrastructure/services/workspace/transport/blob-snapshot-publish.ts
|
|
114354
|
+
import { gzipSync as gzipSync2 } from "node:zlib";
|
|
114355
|
+
function buildBlobSnapshotPayload(tree, dataHash) {
|
|
114356
|
+
const compressed = gzipSync2(Buffer.from(JSON.stringify(tree))).toString("base64");
|
|
114357
|
+
return {
|
|
114358
|
+
data: { compression: "gzip", content: compressed },
|
|
114359
|
+
dataHash,
|
|
114360
|
+
scannedAt: tree.scannedAt
|
|
114361
|
+
};
|
|
114362
|
+
}
|
|
114363
|
+
async function publishBlobSnapshot(session2, workingDir, payload) {
|
|
114364
|
+
await session2.backend.mutation(api.workspaceFiles.syncFileTreeV2, {
|
|
114365
|
+
sessionId: session2.sessionId,
|
|
114366
|
+
machineId: session2.machineId,
|
|
114367
|
+
workingDir,
|
|
114368
|
+
...toLegacyBlobSyncArgs(payload)
|
|
114369
|
+
});
|
|
114370
|
+
return {
|
|
114371
|
+
strategyId: "blob",
|
|
114372
|
+
snapshotId: payload.dataHash,
|
|
114373
|
+
scannedAt: payload.scannedAt,
|
|
114374
|
+
entryCount: 0
|
|
114375
|
+
};
|
|
114376
|
+
}
|
|
114377
|
+
var init_blob_snapshot_publish = __esm(() => {
|
|
114378
|
+
init_api3();
|
|
114379
|
+
});
|
|
114380
|
+
|
|
114381
|
+
// src/infrastructure/services/workspace/file-tree-partition.ts
|
|
114382
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
114383
|
+
import { gzipSync as gzipSync3 } from "node:zlib";
|
|
114384
|
+
function computeShardDataHash(payload) {
|
|
114385
|
+
return createHash8("md5").update(JSON.stringify(payload)).digest("hex");
|
|
114386
|
+
}
|
|
114387
|
+
function shardIdForPath(path8) {
|
|
114388
|
+
const slash = path8.indexOf("/");
|
|
114389
|
+
return slash === -1 ? "__root__" : path8.slice(0, slash);
|
|
114390
|
+
}
|
|
114391
|
+
function childShardId(path8, parentShardId) {
|
|
114392
|
+
if (parentShardId === "__root__") {
|
|
114393
|
+
return shardIdForPath(path8);
|
|
114394
|
+
}
|
|
114395
|
+
if (path8 === parentShardId) {
|
|
114396
|
+
return parentShardId;
|
|
114397
|
+
}
|
|
114398
|
+
const prefix = `${parentShardId}/`;
|
|
114399
|
+
if (!path8.startsWith(prefix)) {
|
|
114400
|
+
return parentShardId;
|
|
114401
|
+
}
|
|
114402
|
+
const remainder = path8.slice(prefix.length);
|
|
114403
|
+
const slash = remainder.indexOf("/");
|
|
114404
|
+
return slash === -1 ? parentShardId : `${parentShardId}/${remainder.slice(0, slash)}`;
|
|
114405
|
+
}
|
|
114406
|
+
function groupEntriesByShardId(entries2, parentShardId) {
|
|
114407
|
+
const groups = new Map;
|
|
114408
|
+
for (const entry of entries2) {
|
|
114409
|
+
const shardId = parentShardId === "__root__" ? shardIdForPath(entry.path) : childShardId(entry.path, parentShardId);
|
|
114410
|
+
const group = groups.get(shardId) ?? [];
|
|
114411
|
+
group.push(entry);
|
|
114412
|
+
groups.set(shardId, group);
|
|
114413
|
+
}
|
|
114414
|
+
return groups;
|
|
114415
|
+
}
|
|
114416
|
+
function buildPreparedShard(shardId, payload) {
|
|
114417
|
+
const compressed = gzipSync3(Buffer.from(JSON.stringify(payload))).toString("base64");
|
|
114418
|
+
return {
|
|
114419
|
+
shardId,
|
|
114420
|
+
payload,
|
|
114421
|
+
dataHash: computeShardDataHash(payload),
|
|
114422
|
+
entryCount: payload.entries.length,
|
|
114423
|
+
data: { compression: "gzip", content: compressed }
|
|
114424
|
+
};
|
|
114425
|
+
}
|
|
114426
|
+
function subdivideShardGroup(subgroups, tree) {
|
|
114427
|
+
const shards = [];
|
|
114428
|
+
for (const [subId, subEntries] of subgroups) {
|
|
114429
|
+
shards.push(...prepareShardGroup(subEntries, subId, tree));
|
|
114430
|
+
}
|
|
114431
|
+
return shards;
|
|
114432
|
+
}
|
|
114433
|
+
function shouldSkipParentCompression(subgroups, entryCount) {
|
|
114434
|
+
return subgroups.size > 1 && entryCount > LARGE_SHARD_ENTRY_THRESHOLD;
|
|
114435
|
+
}
|
|
114436
|
+
function prepareShardGroup(entries2, shardId, tree) {
|
|
114437
|
+
const subgroups = groupEntriesByShardId(entries2, shardId);
|
|
114438
|
+
if (shouldSkipParentCompression(subgroups, entries2.length)) {
|
|
114439
|
+
return subdivideShardGroup(subgroups, tree);
|
|
114440
|
+
}
|
|
114441
|
+
const payload = {
|
|
114442
|
+
entries: entries2,
|
|
114443
|
+
scannedAt: tree.scannedAt,
|
|
114444
|
+
rootDir: tree.rootDir
|
|
114445
|
+
};
|
|
114446
|
+
const compressed = gzipSync3(Buffer.from(JSON.stringify(payload))).toString("base64");
|
|
114447
|
+
if (Buffer.byteLength(compressed, "utf8") <= MAX_SHARD_JSON_BYTES || subgroups.size <= 1) {
|
|
114448
|
+
return [buildPreparedShard(shardId, payload)];
|
|
114449
|
+
}
|
|
114450
|
+
return subdivideShardGroup(subgroups, tree);
|
|
114451
|
+
}
|
|
114452
|
+
function partitionFileTree(tree) {
|
|
114453
|
+
const topGroups = groupEntriesByShardId(tree.entries, "__root__");
|
|
114454
|
+
const shards = [];
|
|
114455
|
+
for (const [shardId, entries2] of topGroups) {
|
|
114456
|
+
shards.push(...prepareShardGroup(entries2, shardId, tree));
|
|
114457
|
+
}
|
|
114458
|
+
return shards;
|
|
114459
|
+
}
|
|
114460
|
+
var MAX_TREE_JSON_BYTES2, MAX_SHARD_JSON_BYTES, MAX_SHARD_BATCH_SIZE = 8, LARGE_SHARD_ENTRY_THRESHOLD = 50000;
|
|
114461
|
+
var init_file_tree_partition = __esm(() => {
|
|
114462
|
+
init_workspace_file_tree();
|
|
114463
|
+
MAX_TREE_JSON_BYTES2 = 900 * 1024;
|
|
114464
|
+
MAX_SHARD_JSON_BYTES = 800 * 1024;
|
|
114465
|
+
});
|
|
114466
|
+
|
|
114467
|
+
// src/infrastructure/services/workspace/transport/sharded-snapshot-publish.ts
|
|
114468
|
+
async function publishShardedSnapshot(session2, workingDir, tree, syncGeneration) {
|
|
114469
|
+
const shards = partitionFileTree(tree);
|
|
114470
|
+
const shardIds = [];
|
|
114471
|
+
for (let i2 = 0;i2 < shards.length; i2 += MAX_SHARD_BATCH_SIZE) {
|
|
114472
|
+
const batch = shards.slice(i2, i2 + MAX_SHARD_BATCH_SIZE);
|
|
114473
|
+
const items = batch.map((s) => ({
|
|
114474
|
+
shardId: s.shardId,
|
|
114475
|
+
data: s.data,
|
|
114476
|
+
dataHash: s.dataHash,
|
|
114477
|
+
scannedAt: tree.scannedAt,
|
|
114478
|
+
entryCount: s.entryCount
|
|
114479
|
+
}));
|
|
114480
|
+
await session2.backend.mutation(api.workspaceFiles.syncFileTreeShardV3Batch, {
|
|
114481
|
+
sessionId: session2.sessionId,
|
|
114482
|
+
machineId: session2.machineId,
|
|
114483
|
+
workingDir,
|
|
114484
|
+
syncGeneration,
|
|
114485
|
+
items: items.map(toLegacyShardBatchItem)
|
|
113650
114486
|
});
|
|
113651
|
-
|
|
113652
|
-
|
|
113653
|
-
|
|
114487
|
+
for (const s of batch)
|
|
114488
|
+
shardIds.push(s.shardId);
|
|
114489
|
+
}
|
|
114490
|
+
const manifest = {
|
|
114491
|
+
syncGeneration,
|
|
114492
|
+
shardIds,
|
|
114493
|
+
totalEntryCount: tree.entries.length,
|
|
114494
|
+
complete: true,
|
|
114495
|
+
scannedAt: tree.scannedAt
|
|
113654
114496
|
};
|
|
113655
|
-
|
|
114497
|
+
await session2.backend.mutation(api.workspaceFiles.syncFileTreeManifestV3, {
|
|
114498
|
+
sessionId: session2.sessionId,
|
|
114499
|
+
machineId: session2.machineId,
|
|
113656
114500
|
workingDir,
|
|
113657
|
-
|
|
113658
|
-
shouldIgnore: shouldIgnorePath,
|
|
113659
|
-
getKnownPaths: () => manifest.paths,
|
|
113660
|
-
onEvents: handleChangeSourceEvents,
|
|
113661
|
-
onNeedsReconcile: () => enqueueSerial(reconcileNow),
|
|
113662
|
-
onError: handleChangeSourceError,
|
|
113663
|
-
onPersistentFailure: () => degradeGitToFs("persistent git poll failures")
|
|
114501
|
+
...toLegacyManifestSyncArgs(manifest)
|
|
113664
114502
|
});
|
|
113665
|
-
changeSource = change.source;
|
|
113666
|
-
changeSourceMode = change.mode;
|
|
113667
|
-
if (change.mode === "git") {
|
|
113668
|
-
console.log(`[workspace-file-tree] change source: git (${change.gitRepoCount ?? 1} repo${(change.gitRepoCount ?? 1) === 1 ? "" : "s"})`);
|
|
113669
|
-
} else {
|
|
113670
|
-
console.log("[workspace-file-tree] change source: fs");
|
|
113671
|
-
}
|
|
113672
|
-
await changeSource.ready;
|
|
113673
|
-
scheduleReconcile();
|
|
113674
114503
|
return {
|
|
113675
|
-
|
|
113676
|
-
|
|
113677
|
-
|
|
113678
|
-
|
|
113679
|
-
reconcile: () => enqueueSerial(reconcileNow),
|
|
113680
|
-
stop: async () => {
|
|
113681
|
-
stopped = true;
|
|
113682
|
-
if (reconcileTimer)
|
|
113683
|
-
clearTimeout(reconcileTimer);
|
|
113684
|
-
reconcileTimer = null;
|
|
113685
|
-
await changeSource?.stop();
|
|
113686
|
-
changeSource = null;
|
|
113687
|
-
await serial;
|
|
113688
|
-
await saveWorkspaceSyncManifest(manifest);
|
|
113689
|
-
}
|
|
114504
|
+
strategyId: "sharded",
|
|
114505
|
+
snapshotId: syncGeneration,
|
|
114506
|
+
scannedAt: tree.scannedAt,
|
|
114507
|
+
entryCount: tree.entries.length
|
|
113690
114508
|
};
|
|
113691
114509
|
}
|
|
113692
|
-
var
|
|
113693
|
-
|
|
113694
|
-
|
|
113695
|
-
init_file_tree_scanner();
|
|
113696
|
-
init_git_workspace_porcelain();
|
|
113697
|
-
init_workspace_change_source();
|
|
113698
|
-
init_workspace_fs_watcher();
|
|
113699
|
-
init_workspace_ignore();
|
|
113700
|
-
init_workspace_sync_state();
|
|
113701
|
-
DEFAULT_RECONCILE_INTERVAL_MS = 10 * 60 * 1000;
|
|
114510
|
+
var init_sharded_snapshot_publish = __esm(() => {
|
|
114511
|
+
init_api3();
|
|
114512
|
+
init_file_tree_partition();
|
|
113702
114513
|
});
|
|
113703
114514
|
|
|
113704
|
-
// src/infrastructure/
|
|
113705
|
-
|
|
113706
|
-
|
|
113707
|
-
|
|
113708
|
-
function queueKey(machineId, workingDir) {
|
|
113709
|
-
return `${machineId}\x00${workingDir}`;
|
|
114515
|
+
// src/daemon/infrastructure/outbox/workspace-file-tree-checkpoint-send.ts
|
|
114516
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
114517
|
+
function needsMoreCheckpointPrune(result) {
|
|
114518
|
+
return (result.status === "published" || result.status === "unchanged") && result.pruneComplete === false;
|
|
113710
114519
|
}
|
|
113711
|
-
function
|
|
113712
|
-
|
|
113713
|
-
|
|
113714
|
-
state = { drainPromise: null, pendingTask: null, scheduled: false };
|
|
113715
|
-
queues.set(key, state);
|
|
114520
|
+
function assertCheckpointAccepted(result) {
|
|
114521
|
+
if (result.status === "snapshot-missing" || result.status === "resync-required") {
|
|
114522
|
+
throw new Error(`File tree checkpoint rejected: ${result.status}`);
|
|
113716
114523
|
}
|
|
113717
|
-
return state;
|
|
113718
114524
|
}
|
|
113719
|
-
function
|
|
113720
|
-
|
|
113721
|
-
|
|
113722
|
-
|
|
113723
|
-
try {
|
|
113724
|
-
while (state.pendingTask) {
|
|
113725
|
-
const lastCompletedAt = lastCompletedAtByKey.get(key) ?? 0;
|
|
113726
|
-
const elapsed3 = Date.now() - lastCompletedAt;
|
|
113727
|
-
const waitMs = lastCompletedAt === 0 ? 0 : Math.max(0, debounceMs - elapsed3);
|
|
113728
|
-
if (waitMs > 0) {
|
|
113729
|
-
await sleep7(waitMs);
|
|
113730
|
-
}
|
|
113731
|
-
const task = state.pendingTask;
|
|
113732
|
-
if (!task)
|
|
113733
|
-
break;
|
|
113734
|
-
state.pendingTask = null;
|
|
113735
|
-
await task();
|
|
113736
|
-
lastCompletedAtByKey.set(key, Date.now());
|
|
113737
|
-
}
|
|
113738
|
-
} finally {
|
|
113739
|
-
state.drainPromise = null;
|
|
113740
|
-
state.scheduled = false;
|
|
113741
|
-
if (state.pendingTask) {
|
|
113742
|
-
kickDrain(state, key, debounceMs);
|
|
113743
|
-
} else {
|
|
113744
|
-
queues.delete(key);
|
|
113745
|
-
}
|
|
114525
|
+
async function syncScannedFileTree(session2, normalizedWorkingDir, tree, dataHash, syncGeneration) {
|
|
114526
|
+
if (selectFileTreeSnapshotStrategyId(tree) === "sharded") {
|
|
114527
|
+
const ref2 = await publishShardedSnapshot(session2, normalizedWorkingDir, tree, syncGeneration);
|
|
114528
|
+
return { strategyId: ref2.strategyId, snapshotId: ref2.snapshotId };
|
|
113746
114529
|
}
|
|
114530
|
+
const ref = await publishBlobSnapshot(session2, normalizedWorkingDir, buildBlobSnapshotPayload(tree, dataHash));
|
|
114531
|
+
return { strategyId: ref.strategyId, snapshotId: ref.snapshotId };
|
|
113747
114532
|
}
|
|
113748
|
-
function
|
|
113749
|
-
|
|
113750
|
-
|
|
113751
|
-
|
|
113752
|
-
|
|
113753
|
-
|
|
113754
|
-
|
|
113755
|
-
|
|
114533
|
+
async function publishCheckpointUntilPruned(session2, normalizedWorkingDir, snapshot, revision) {
|
|
114534
|
+
let checkpointRevision = revision;
|
|
114535
|
+
const publish = () => session2.backend.mutation(api.workspaceFiles.publishFileTreeCheckpoint, {
|
|
114536
|
+
sessionId: session2.sessionId,
|
|
114537
|
+
machineId: session2.machineId,
|
|
114538
|
+
workingDir: normalizedWorkingDir,
|
|
114539
|
+
revision: checkpointRevision,
|
|
114540
|
+
...snapshot
|
|
113756
114541
|
});
|
|
114542
|
+
let result = await publish();
|
|
114543
|
+
if (result.status === "resync-required") {
|
|
114544
|
+
checkpointRevision = result.expectedRevision + 1;
|
|
114545
|
+
result = await publish();
|
|
114546
|
+
}
|
|
114547
|
+
while (needsMoreCheckpointPrune(result))
|
|
114548
|
+
result = await publish();
|
|
114549
|
+
assertCheckpointAccepted(result);
|
|
114550
|
+
return checkpointRevision;
|
|
113757
114551
|
}
|
|
113758
|
-
|
|
113759
|
-
|
|
113760
|
-
|
|
113761
|
-
|
|
113762
|
-
|
|
113763
|
-
|
|
113764
|
-
return state.drainPromise ?? Promise.resolve();
|
|
114552
|
+
function createWorkspaceFileTreeCheckpointSend(session2, normalizedWorkingDir) {
|
|
114553
|
+
return async (state) => {
|
|
114554
|
+
const snapshot = await syncScannedFileTree(session2, normalizedWorkingDir, state.tree, computeFileTreeDataHash(state.tree), randomUUID11());
|
|
114555
|
+
const revision = await publishCheckpointUntilPruned(session2, normalizedWorkingDir, snapshot, state.revision);
|
|
114556
|
+
return { revision };
|
|
114557
|
+
};
|
|
113765
114558
|
}
|
|
113766
|
-
var
|
|
113767
|
-
|
|
113768
|
-
|
|
113769
|
-
|
|
114559
|
+
var init_workspace_file_tree_checkpoint_send = __esm(() => {
|
|
114560
|
+
init_workspace_file_tree();
|
|
114561
|
+
init_api3();
|
|
114562
|
+
init_file_tree_data_hash();
|
|
114563
|
+
init_blob_snapshot_publish();
|
|
114564
|
+
init_sharded_snapshot_publish();
|
|
114565
|
+
});
|
|
114566
|
+
|
|
114567
|
+
// src/daemon/infrastructure/outbox/workspace-file-tree-delta-send.ts
|
|
114568
|
+
function toDeltaOperations(delta) {
|
|
114569
|
+
return [
|
|
114570
|
+
...delta.added.map((entry) => ({
|
|
114571
|
+
o: "a",
|
|
114572
|
+
p: entry.path,
|
|
114573
|
+
e: entry.type === "directory" ? "d" : "f"
|
|
114574
|
+
})),
|
|
114575
|
+
...delta.removed.map((entryPath) => ({
|
|
114576
|
+
o: "r",
|
|
114577
|
+
p: entryPath
|
|
114578
|
+
})),
|
|
114579
|
+
...delta.typeChanged.map((entry) => ({
|
|
114580
|
+
o: "t",
|
|
114581
|
+
p: entry.path,
|
|
114582
|
+
e: entry.type === "directory" ? "d" : "f"
|
|
114583
|
+
}))
|
|
114584
|
+
];
|
|
114585
|
+
}
|
|
114586
|
+
function createWorkspaceFileTreeDeltaSend(session2, normalizedWorkingDir) {
|
|
114587
|
+
return async (unit) => {
|
|
114588
|
+
const result = await session2.backend.mutation(api.workspaceFiles.applyFileTreeDeltaBatch, {
|
|
114589
|
+
sessionId: session2.sessionId,
|
|
114590
|
+
machineId: session2.machineId,
|
|
114591
|
+
workingDir: normalizedWorkingDir,
|
|
114592
|
+
operationId: unit.delta.operationId,
|
|
114593
|
+
baseRevision: unit.baseRevision,
|
|
114594
|
+
operations: toDeltaOperations(unit.delta)
|
|
114595
|
+
});
|
|
114596
|
+
if (result.status === "resync-required") {
|
|
114597
|
+
return { status: "conflict", revision: result.expectedRevision };
|
|
114598
|
+
}
|
|
114599
|
+
return result;
|
|
114600
|
+
};
|
|
114601
|
+
}
|
|
114602
|
+
var init_workspace_file_tree_delta_send = __esm(() => {
|
|
114603
|
+
init_api3();
|
|
113770
114604
|
});
|
|
113771
114605
|
|
|
113772
114606
|
// src/daemon/entry/files/file-tree-subscription.ts
|
|
113773
|
-
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
113774
114607
|
function logSubscriptionWarn(label, err) {
|
|
113775
114608
|
console.warn(`[${formatTimestamp()}] ⚠️ ${label}: ${getErrorMessage(err)}`);
|
|
113776
114609
|
}
|
|
113777
|
-
async function stopCoordinatorForWorkingDir(coordinators, normalized) {
|
|
114610
|
+
async function stopCoordinatorForWorkingDir(coordinators, checkpointOutboxRegistry, deltaOutboxRegistry, normalized) {
|
|
113778
114611
|
const coordinatorPromise = coordinators.get(normalized);
|
|
113779
114612
|
if (!coordinatorPromise)
|
|
113780
114613
|
return;
|
|
@@ -113782,8 +114615,10 @@ async function stopCoordinatorForWorkingDir(coordinators, normalized) {
|
|
|
113782
114615
|
await coordinatorPromise.then((coordinator2) => coordinator2.stop()).catch(() => {
|
|
113783
114616
|
return;
|
|
113784
114617
|
});
|
|
114618
|
+
await checkpointOutboxRegistry.stop(normalized);
|
|
114619
|
+
await deltaOutboxRegistry.stop(normalized);
|
|
113785
114620
|
}
|
|
113786
|
-
async function drainPendingFileTreeReleaseRequests(session2, coordinators) {
|
|
114621
|
+
async function drainPendingFileTreeReleaseRequests(session2, coordinators, checkpointOutboxRegistry, deltaOutboxRegistry) {
|
|
113787
114622
|
const releases = await session2.backend.query(api.workspaceFiles.getPendingFileTreeReleaseRequests, {
|
|
113788
114623
|
sessionId: session2.sessionId,
|
|
113789
114624
|
machineId: session2.machineId
|
|
@@ -113795,7 +114630,7 @@ async function drainPendingFileTreeReleaseRequests(session2, coordinators) {
|
|
|
113795
114630
|
releasesByDir.add(normalizeWorkingDirForLookup(release.workingDir));
|
|
113796
114631
|
}
|
|
113797
114632
|
for (const normalized of releasesByDir) {
|
|
113798
|
-
await stopCoordinatorForWorkingDir(coordinators, normalized).then(() => session2.backend.mutation(api.workspaceFiles.fulfillFileTreeReleaseRequest, {
|
|
114633
|
+
await stopCoordinatorForWorkingDir(coordinators, checkpointOutboxRegistry, deltaOutboxRegistry, normalized).then(() => session2.backend.mutation(api.workspaceFiles.fulfillFileTreeReleaseRequest, {
|
|
113799
114634
|
sessionId: session2.sessionId,
|
|
113800
114635
|
machineId: session2.machineId,
|
|
113801
114636
|
workingDir: normalized
|
|
@@ -113836,63 +114671,20 @@ async function drainPendingFileTreeRequests(session2, coordinators, ensureCoordi
|
|
|
113836
114671
|
});
|
|
113837
114672
|
await processPendingFileTreeRequests(session2, coordinators, ensureCoordinator, requests);
|
|
113838
114673
|
}
|
|
113839
|
-
async function syncScannedFileTree(session2, normalizedWorkingDir, tree, dataHash, syncGeneration) {
|
|
113840
|
-
if (selectFileTreeSnapshotStrategyId(tree) === "sharded") {
|
|
113841
|
-
const ref2 = await publishShardedSnapshot(session2, normalizedWorkingDir, tree, syncGeneration);
|
|
113842
|
-
return { strategyId: ref2.strategyId, snapshotId: ref2.snapshotId };
|
|
113843
|
-
}
|
|
113844
|
-
const ref = await publishBlobSnapshot(session2, normalizedWorkingDir, buildBlobSnapshotPayload(tree, dataHash));
|
|
113845
|
-
return { strategyId: ref.strategyId, snapshotId: ref.snapshotId };
|
|
113846
|
-
}
|
|
113847
|
-
function toDeltaOperations(delta) {
|
|
113848
|
-
return [
|
|
113849
|
-
...delta.added.map((entry) => ({
|
|
113850
|
-
o: "a",
|
|
113851
|
-
p: entry.path,
|
|
113852
|
-
e: entry.type === "directory" ? "d" : "f"
|
|
113853
|
-
})),
|
|
113854
|
-
...delta.removed.map((entryPath) => ({
|
|
113855
|
-
o: "r",
|
|
113856
|
-
p: entryPath
|
|
113857
|
-
})),
|
|
113858
|
-
...delta.typeChanged.map((entry) => ({
|
|
113859
|
-
o: "t",
|
|
113860
|
-
p: entry.path,
|
|
113861
|
-
e: entry.type === "directory" ? "d" : "f"
|
|
113862
|
-
}))
|
|
113863
|
-
];
|
|
113864
|
-
}
|
|
113865
|
-
async function publishCheckpoint(session2, normalizedWorkingDir, tree, revision) {
|
|
113866
|
-
const dataHash = computeFileTreeDataHash(tree);
|
|
113867
|
-
const syncGeneration = randomUUID11();
|
|
113868
|
-
const snapshot = await syncScannedFileTree(session2, normalizedWorkingDir, tree, dataHash, syncGeneration);
|
|
113869
|
-
let checkpointRevision = revision;
|
|
113870
|
-
let result = await session2.backend.mutation(api.workspaceFiles.publishFileTreeCheckpoint, {
|
|
113871
|
-
sessionId: session2.sessionId,
|
|
113872
|
-
machineId: session2.machineId,
|
|
113873
|
-
workingDir: normalizedWorkingDir,
|
|
113874
|
-
revision: checkpointRevision,
|
|
113875
|
-
...snapshot
|
|
113876
|
-
});
|
|
113877
|
-
if (result.status === "resync-required") {
|
|
113878
|
-
checkpointRevision = result.expectedRevision + 1;
|
|
113879
|
-
result = await session2.backend.mutation(api.workspaceFiles.publishFileTreeCheckpoint, {
|
|
113880
|
-
sessionId: session2.sessionId,
|
|
113881
|
-
machineId: session2.machineId,
|
|
113882
|
-
workingDir: normalizedWorkingDir,
|
|
113883
|
-
revision: checkpointRevision,
|
|
113884
|
-
...snapshot
|
|
113885
|
-
});
|
|
113886
|
-
}
|
|
113887
|
-
if (result.status === "snapshot-missing") {
|
|
113888
|
-
throw new Error(`File tree checkpoint rejected: ${result.status}`);
|
|
113889
|
-
}
|
|
113890
|
-
console.log(`[${formatTimestamp()}] \uD83C\uDF33 File tree checkpoint: ${normalizedWorkingDir} (${tree.entries.length} entries, revision ${checkpointRevision})`);
|
|
113891
|
-
return { revision: checkpointRevision };
|
|
113892
|
-
}
|
|
113893
114674
|
var startFileTreeSubscriptionEffect = () => exports_Effect.gen(function* () {
|
|
113894
114675
|
const session2 = yield* DaemonSessionService;
|
|
113895
114676
|
const coordinators = new Map;
|
|
114677
|
+
const checkpointOutboxRegistry = createWorkspaceFileTreeCheckpointOutboxRegistry(session2.machineId, (normalized) => {
|
|
114678
|
+
const send = createWorkspaceFileTreeCheckpointSend(session2, normalized);
|
|
114679
|
+
return async (state) => {
|
|
114680
|
+
const result = await send(state);
|
|
114681
|
+
console.log(`[${formatTimestamp()}] \uD83C\uDF33 File tree checkpoint: ${normalized} (${state.tree.entries.length} entries, revision ${result.revision})`);
|
|
114682
|
+
return result;
|
|
114683
|
+
};
|
|
114684
|
+
}, {
|
|
114685
|
+
onError: (normalized, error51) => logSubscriptionWarn(`File tree checkpoint outbox failed for ${normalized}`, error51)
|
|
114686
|
+
});
|
|
114687
|
+
const deltaOutboxRegistry = createWorkspaceFileTreeDeltaOutboxRegistry(session2.machineId, (normalized) => createWorkspaceFileTreeDeltaSend(session2, normalized));
|
|
113896
114688
|
const ensureCoordinator = (workingDir, forceReconcile) => {
|
|
113897
114689
|
const normalized = normalizeWorkingDirForLookup(workingDir);
|
|
113898
114690
|
let coordinatorPromise = coordinators.get(normalized);
|
|
@@ -113900,23 +114692,8 @@ var startFileTreeSubscriptionEffect = () => exports_Effect.gen(function* () {
|
|
|
113900
114692
|
coordinatorPromise = startWorkspaceFileTreeCoordinator({
|
|
113901
114693
|
machineId: session2.machineId,
|
|
113902
114694
|
workingDir: normalized,
|
|
113903
|
-
onDelta:
|
|
113904
|
-
|
|
113905
|
-
const result = await session2.backend.mutation(api.workspaceFiles.applyFileTreeDeltaBatch, {
|
|
113906
|
-
sessionId: session2.sessionId,
|
|
113907
|
-
machineId: session2.machineId,
|
|
113908
|
-
workingDir: normalized,
|
|
113909
|
-
operationId: delta.operationId,
|
|
113910
|
-
baseRevision,
|
|
113911
|
-
operations
|
|
113912
|
-
});
|
|
113913
|
-
if (result.status === "resync-required") {
|
|
113914
|
-
return { status: "conflict", revision: result.expectedRevision };
|
|
113915
|
-
}
|
|
113916
|
-
console.log(`[${formatTimestamp()}] \uD83C\uDF33 File tree delta: ${normalized} (${operations.length} operations, ${Buffer.byteLength(JSON.stringify(operations))} bytes, revision ${result.revision})`);
|
|
113917
|
-
return result;
|
|
113918
|
-
},
|
|
113919
|
-
onCheckpoint: (tree, revision) => publishCheckpoint(session2, normalized, tree, revision),
|
|
114695
|
+
onDelta: (delta, baseRevision) => deltaOutboxRegistry.enqueue(normalized, { delta, baseRevision }),
|
|
114696
|
+
onCheckpoint: (tree, revision) => checkpointOutboxRegistry.enqueue(normalized, { tree, revision }),
|
|
113920
114697
|
onError: (error51) => logSubscriptionWarn(`File tree coordinator failed for ${normalized}`, error51),
|
|
113921
114698
|
onReconciled: (correctedPathCount) => {
|
|
113922
114699
|
console.log(`[${formatTimestamp()}] \uD83C\uDF33 File tree reconciled: ${normalized} (${correctedPathCount} corrected paths)`);
|
|
@@ -113942,25 +114719,27 @@ var startFileTreeSubscriptionEffect = () => exports_Effect.gen(function* () {
|
|
|
113942
114719
|
};
|
|
113943
114720
|
return {
|
|
113944
114721
|
drainPendingFileTreeRequests: () => drainPendingFileTreeRequests(session2, coordinators, ensureCoordinator),
|
|
113945
|
-
drainPendingFileTreeReleaseRequests: () => drainPendingFileTreeReleaseRequests(session2, coordinators),
|
|
114722
|
+
drainPendingFileTreeReleaseRequests: () => drainPendingFileTreeReleaseRequests(session2, coordinators, checkpointOutboxRegistry, deltaOutboxRegistry),
|
|
113946
114723
|
stop: () => {
|
|
113947
|
-
|
|
113948
|
-
|
|
113949
|
-
|
|
113950
|
-
|
|
114724
|
+
(async () => {
|
|
114725
|
+
const keys3 = [...coordinators.keys()];
|
|
114726
|
+
await Promise.all(keys3.map((normalized) => stopCoordinatorForWorkingDir(coordinators, checkpointOutboxRegistry, deltaOutboxRegistry, normalized)));
|
|
114727
|
+
await deltaOutboxRegistry.stopAll();
|
|
114728
|
+
await checkpointOutboxRegistry.stopAll();
|
|
114729
|
+
coordinators.clear();
|
|
114730
|
+
})();
|
|
113951
114731
|
}
|
|
113952
114732
|
};
|
|
113953
114733
|
});
|
|
113954
114734
|
var init_file_tree_subscription = __esm(() => {
|
|
113955
|
-
init_workspace_file_tree();
|
|
113956
114735
|
init_esm();
|
|
113957
114736
|
init_api3();
|
|
113958
|
-
init_file_tree_data_hash();
|
|
113959
|
-
init_blob_snapshot_publish();
|
|
113960
|
-
init_sharded_snapshot_publish();
|
|
113961
114737
|
init_workspace_file_tree_coordinator();
|
|
113962
114738
|
init_workspace_sync_queue();
|
|
113963
114739
|
init_convex_error();
|
|
114740
|
+
init_outbox();
|
|
114741
|
+
init_workspace_file_tree_checkpoint_send();
|
|
114742
|
+
init_workspace_file_tree_delta_send();
|
|
113964
114743
|
init_daemon_services();
|
|
113965
114744
|
});
|
|
113966
114745
|
|
|
@@ -114074,7 +114853,7 @@ var init_file_write_request_priority = __esm(() => {
|
|
|
114074
114853
|
|
|
114075
114854
|
// src/daemon/entry/files/file-write-fulfillment.ts
|
|
114076
114855
|
import { access as access4, mkdir as mkdir10, rename as rename4, rm as rm4, writeFile as writeFile9 } from "node:fs/promises";
|
|
114077
|
-
import { dirname as
|
|
114856
|
+
import { dirname as dirname14 } from "node:path";
|
|
114078
114857
|
function isTerminalFileWriteError(errorMessage) {
|
|
114079
114858
|
const terminalMessages = new Set([
|
|
114080
114859
|
"Invalid file path",
|
|
@@ -114173,7 +114952,7 @@ async function validateWriteOperation(operation, absolutePath) {
|
|
|
114173
114952
|
}
|
|
114174
114953
|
async function writePayloadToDisk(absolutePath, operation, content) {
|
|
114175
114954
|
if (operation === "create") {
|
|
114176
|
-
await mkdir10(
|
|
114955
|
+
await mkdir10(dirname14(absolutePath), { recursive: true });
|
|
114177
114956
|
await writeFile9(absolutePath, content, { flag: "wx" });
|
|
114178
114957
|
return;
|
|
114179
114958
|
}
|
|
@@ -114251,7 +115030,7 @@ async function fulfillOneFileWriteRequest(session2, request2) {
|
|
|
114251
115030
|
});
|
|
114252
115031
|
return;
|
|
114253
115032
|
}
|
|
114254
|
-
await mkdir10(
|
|
115033
|
+
await mkdir10(dirname14(targetResolved.absolutePath), { recursive: true });
|
|
114255
115034
|
await rename4(resolved.absolutePath, targetResolved.absolutePath);
|
|
114256
115035
|
await completeWriteRequest(session2, claimed._id, claimed.revision, { status: "done" });
|
|
114257
115036
|
const elapsed4 = Date.now() - startTime;
|
|
@@ -116551,10 +117330,10 @@ var init_deps = __esm(() => {
|
|
|
116551
117330
|
});
|
|
116552
117331
|
|
|
116553
117332
|
// src/daemon/entry/persistence-path.ts
|
|
116554
|
-
import { homedir as
|
|
116555
|
-
import { join as
|
|
117333
|
+
import { homedir as homedir10 } from "node:os";
|
|
117334
|
+
import { join as join26 } from "node:path";
|
|
116556
117335
|
function resolvePersistenceDbPath(machineId) {
|
|
116557
|
-
return
|
|
117336
|
+
return join26(homedir10(), ".chatroom", "daemon", machineId, "events.sqlite");
|
|
116558
117337
|
}
|
|
116559
117338
|
var init_persistence_path = () => {};
|
|
116560
117339
|
|
|
@@ -117590,10 +118369,10 @@ function resolveEnvSlug(convexUrl) {
|
|
|
117590
118369
|
}
|
|
117591
118370
|
|
|
117592
118371
|
// src/infrastructure/log-server/resolve-logs-db-path.ts
|
|
117593
|
-
import { homedir as
|
|
117594
|
-
import { join as
|
|
118372
|
+
import { homedir as homedir11 } from "node:os";
|
|
118373
|
+
import { join as join27 } from "node:path";
|
|
117595
118374
|
function resolveLogsDbPath(convexUrl) {
|
|
117596
|
-
return
|
|
118375
|
+
return join27(homedir11(), ".chatroom", resolveEnvSlug(convexUrl ?? getConvexUrl()), "logs.sqlite");
|
|
117597
118376
|
}
|
|
117598
118377
|
var init_resolve_logs_db_path = __esm(() => {
|
|
117599
118378
|
init_client2();
|
|
@@ -117626,12 +118405,12 @@ var init_log_schema = __esm(() => {
|
|
|
117626
118405
|
});
|
|
117627
118406
|
|
|
117628
118407
|
// src/infrastructure/log-server/open-log-database.ts
|
|
117629
|
-
import { mkdirSync as
|
|
117630
|
-
import { dirname as
|
|
117631
|
-
import { DatabaseSync } from "node:sqlite";
|
|
118408
|
+
import { mkdirSync as mkdirSync8 } from "node:fs";
|
|
118409
|
+
import { dirname as dirname15 } from "node:path";
|
|
118410
|
+
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
117632
118411
|
function openLogDatabase(dbPath) {
|
|
117633
|
-
|
|
117634
|
-
const db = new
|
|
118412
|
+
mkdirSync8(dirname15(dbPath), { recursive: true });
|
|
118413
|
+
const db = new DatabaseSync3(dbPath);
|
|
117635
118414
|
db.exec("PRAGMA journal_mode = WAL");
|
|
117636
118415
|
db.exec("PRAGMA busy_timeout = 5000");
|
|
117637
118416
|
db.exec("PRAGMA synchronous = NORMAL");
|
|
@@ -117857,12 +118636,12 @@ var init_schema = __esm(() => {
|
|
|
117857
118636
|
});
|
|
117858
118637
|
|
|
117859
118638
|
// src/daemon/infrastructure/persistence/open-database.ts
|
|
117860
|
-
import { mkdirSync as
|
|
117861
|
-
import { dirname as
|
|
117862
|
-
import { DatabaseSync as
|
|
118639
|
+
import { mkdirSync as mkdirSync9 } from "node:fs";
|
|
118640
|
+
import { dirname as dirname16 } from "node:path";
|
|
118641
|
+
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
117863
118642
|
function openDatabase(dbPath) {
|
|
117864
|
-
|
|
117865
|
-
const db = new
|
|
118643
|
+
mkdirSync9(dirname16(dbPath), { recursive: true });
|
|
118644
|
+
const db = new DatabaseSync4(dbPath);
|
|
117866
118645
|
for (const sql of MIGRATIONS2) {
|
|
117867
118646
|
db.exec(sql);
|
|
117868
118647
|
}
|
|
@@ -131572,19 +132351,19 @@ function routeRequest(req, res, deps) {
|
|
|
131572
132351
|
|
|
131573
132352
|
// src/daemon/local-web/server/serve-static.ts
|
|
131574
132353
|
import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
|
|
131575
|
-
import { dirname as
|
|
132354
|
+
import { dirname as dirname17, join as join28 } from "node:path";
|
|
131576
132355
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
131577
132356
|
function clientDistCandidates(here) {
|
|
131578
132357
|
return [
|
|
131579
|
-
|
|
131580
|
-
|
|
131581
|
-
|
|
132358
|
+
join28(here, "client/build"),
|
|
132359
|
+
join28(here, "../client/build"),
|
|
132360
|
+
join28(here, "../src/daemon/local-web/client/build")
|
|
131582
132361
|
];
|
|
131583
132362
|
}
|
|
131584
|
-
function resolveClientDistDir(here =
|
|
132363
|
+
function resolveClientDistDir(here = dirname17(fileURLToPath7(import.meta.url))) {
|
|
131585
132364
|
const candidates = clientDistCandidates(here);
|
|
131586
132365
|
for (const dir of candidates) {
|
|
131587
|
-
if (existsSync10(
|
|
132366
|
+
if (existsSync10(join28(dir, "index.html")))
|
|
131588
132367
|
return dir;
|
|
131589
132368
|
}
|
|
131590
132369
|
return candidates[0];
|
|
@@ -131597,7 +132376,7 @@ function tryServeStatic(req, res, distDir) {
|
|
|
131597
132376
|
return false;
|
|
131598
132377
|
}
|
|
131599
132378
|
const safePath = pathname === "/" ? "/index.html" : pathname;
|
|
131600
|
-
const filePath =
|
|
132379
|
+
const filePath = join28(distDir, safePath);
|
|
131601
132380
|
if (!filePath.startsWith(distDir)) {
|
|
131602
132381
|
res.writeHead(403);
|
|
131603
132382
|
res.end();
|
|
@@ -131614,7 +132393,7 @@ function tryServeStatic(req, res, distDir) {
|
|
|
131614
132393
|
}
|
|
131615
132394
|
return true;
|
|
131616
132395
|
}
|
|
131617
|
-
const indexPath =
|
|
132396
|
+
const indexPath = join28(distDir, "index.html");
|
|
131618
132397
|
if (existsSync10(indexPath)) {
|
|
131619
132398
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
131620
132399
|
if (req.method === "HEAD") {
|
|
@@ -133345,4 +134124,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
133345
134124
|
});
|
|
133346
134125
|
program2.parse();
|
|
133347
134126
|
|
|
133348
|
-
//# debugId=
|
|
134127
|
+
//# debugId=6381A1F83F16A2C964756E2164756E21
|