@remixhq/core 0.1.17 → 0.1.18
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/api.d.ts +29 -1
- package/dist/api.js +2 -2
- package/dist/auth.js +2 -2
- package/dist/binding.js +3 -3
- package/dist/chunk-7XJGOKEO.js +23 -0
- package/dist/{chunk-YCFLOHJV.js → chunk-DBVN42RF.js} +2 -2
- package/dist/{chunk-US5SM7ZC.js → chunk-RCNOSZP6.js} +20 -5
- package/dist/{chunk-WT6VRLXU.js → chunk-S4ECO35X.js} +1 -1
- package/dist/{chunk-VM3CGCNX.js → chunk-XCZRNB35.js} +1 -1
- package/dist/{chunk-P6JHXOV4.js → chunk-XETDXVGM.js} +1 -1
- package/dist/collab.d.ts +6 -127
- package/dist/collab.js +215 -324
- package/dist/config.js +2 -2
- package/dist/{contracts-CHmD-fMj.d.ts → contracts-DnNP-K3V.d.ts} +28 -2
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +1 -1
- package/dist/history.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -4
- package/dist/repo.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-YZ34ICNN.js +0 -17
package/dist/collab.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
writeCollabBinding,
|
|
9
9
|
writeCollabBindingSnapshot,
|
|
10
10
|
writeJsonAtomic
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-DBVN42RF.js";
|
|
12
12
|
import {
|
|
13
13
|
applyUnifiedDiffToWorktree,
|
|
14
14
|
assertRepoSnapshotUnchanged,
|
|
@@ -30,13 +30,13 @@ import {
|
|
|
30
30
|
requireCurrentBranch,
|
|
31
31
|
setRemoteOriginUrl,
|
|
32
32
|
summarizeUnifiedDiff
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-S4ECO35X.js";
|
|
34
34
|
import {
|
|
35
35
|
REMIX_ERROR_CODES
|
|
36
36
|
} from "./chunk-GC2MOT3U.js";
|
|
37
37
|
import {
|
|
38
38
|
RemixError
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-7XJGOKEO.js";
|
|
40
40
|
|
|
41
41
|
// src/application/collab/appDeltaCache.ts
|
|
42
42
|
var APP_DELTA_CACHE_TTL_MS = 5e3;
|
|
@@ -47,6 +47,8 @@ function buildAppDeltaCacheKey(appId, payload) {
|
|
|
47
47
|
appId,
|
|
48
48
|
payload.baseHeadHash,
|
|
49
49
|
payload.targetHeadHash ?? "",
|
|
50
|
+
payload.baseRevisionId ?? "",
|
|
51
|
+
payload.targetRevisionId ?? "",
|
|
50
52
|
payload.localSnapshotHash ?? "",
|
|
51
53
|
payload.repoFingerprint ?? "",
|
|
52
54
|
payload.remoteUrl ?? "",
|
|
@@ -189,9 +191,6 @@ function getAsyncJobDir(jobId) {
|
|
|
189
191
|
function getAsyncJobFilePath(jobId) {
|
|
190
192
|
return path.join(getAsyncJobDir(jobId), "job.json");
|
|
191
193
|
}
|
|
192
|
-
function getAsyncJobBundlePath(jobId) {
|
|
193
|
-
return path.join(getAsyncJobDir(jobId), "bundle.bundle");
|
|
194
|
-
}
|
|
195
194
|
function getLogsRoot() {
|
|
196
195
|
return path.join(getCollabStateRoot(), "logs");
|
|
197
196
|
}
|
|
@@ -493,11 +492,11 @@ async function readLocalBaseline(params) {
|
|
|
493
492
|
const raw = await fs2.readFile(getBaselinePath(params), "utf8");
|
|
494
493
|
const parsed = JSON.parse(raw);
|
|
495
494
|
if (!parsed || typeof parsed !== "object") return null;
|
|
496
|
-
if (parsed.schemaVersion
|
|
495
|
+
if (![1, 2].includes(Number(parsed.schemaVersion)) || typeof parsed.key !== "string" || typeof parsed.repoRoot !== "string") {
|
|
497
496
|
return null;
|
|
498
497
|
}
|
|
499
498
|
return {
|
|
500
|
-
schemaVersion: 1,
|
|
499
|
+
schemaVersion: Number(parsed.schemaVersion) === 2 ? 2 : 1,
|
|
501
500
|
key: parsed.key,
|
|
502
501
|
repoRoot: parsed.repoRoot,
|
|
503
502
|
repoFingerprint: parsed.repoFingerprint ?? null,
|
|
@@ -506,6 +505,8 @@ async function readLocalBaseline(params) {
|
|
|
506
505
|
branchName: parsed.branchName ?? null,
|
|
507
506
|
lastSnapshotId: parsed.lastSnapshotId ?? null,
|
|
508
507
|
lastSnapshotHash: parsed.lastSnapshotHash ?? null,
|
|
508
|
+
lastServerRevisionId: parsed.lastServerRevisionId ?? null,
|
|
509
|
+
lastServerTreeHash: parsed.lastServerTreeHash ?? null,
|
|
509
510
|
lastServerHeadHash: parsed.lastServerHeadHash ?? null,
|
|
510
511
|
lastSeenLocalCommitHash: parsed.lastSeenLocalCommitHash ?? null,
|
|
511
512
|
updatedAt: String(parsed.updatedAt ?? "")
|
|
@@ -517,7 +518,7 @@ async function readLocalBaseline(params) {
|
|
|
517
518
|
async function writeLocalBaseline(baseline) {
|
|
518
519
|
const key = buildLaneStateKey(baseline);
|
|
519
520
|
const normalized = {
|
|
520
|
-
schemaVersion:
|
|
521
|
+
schemaVersion: 2,
|
|
521
522
|
key,
|
|
522
523
|
repoRoot: baseline.repoRoot,
|
|
523
524
|
repoFingerprint: baseline.repoFingerprint ?? null,
|
|
@@ -526,6 +527,8 @@ async function writeLocalBaseline(baseline) {
|
|
|
526
527
|
branchName: baseline.branchName ?? null,
|
|
527
528
|
lastSnapshotId: baseline.lastSnapshotId ?? null,
|
|
528
529
|
lastSnapshotHash: baseline.lastSnapshotHash ?? null,
|
|
530
|
+
lastServerRevisionId: baseline.lastServerRevisionId ?? null,
|
|
531
|
+
lastServerTreeHash: baseline.lastServerTreeHash ?? null,
|
|
529
532
|
lastServerHeadHash: baseline.lastServerHeadHash ?? null,
|
|
530
533
|
lastSeenLocalCommitHash: baseline.lastSeenLocalCommitHash ?? null,
|
|
531
534
|
updatedAt: baseline.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -912,6 +915,7 @@ function normalizeJob2(input) {
|
|
|
912
915
|
prompt: input.prompt,
|
|
913
916
|
assistantResponse: input.assistantResponse,
|
|
914
917
|
baselineSnapshotId: input.baselineSnapshotId ?? null,
|
|
918
|
+
baselineServerRevisionId: input.baselineServerRevisionId ?? null,
|
|
915
919
|
baselineServerHeadHash: input.baselineServerHeadHash ?? null,
|
|
916
920
|
currentSnapshotId: input.currentSnapshotId,
|
|
917
921
|
capturedAt: input.capturedAt ?? now,
|
|
@@ -946,6 +950,7 @@ async function readPendingFinalizeJob(jobId) {
|
|
|
946
950
|
prompt: String(parsed.prompt ?? ""),
|
|
947
951
|
assistantResponse: String(parsed.assistantResponse ?? ""),
|
|
948
952
|
baselineSnapshotId: parsed.baselineSnapshotId ?? null,
|
|
953
|
+
baselineServerRevisionId: parsed.baselineServerRevisionId ?? null,
|
|
949
954
|
baselineServerHeadHash: parsed.baselineServerHeadHash ?? null,
|
|
950
955
|
currentSnapshotId: String(parsed.currentSnapshotId ?? ""),
|
|
951
956
|
capturedAt: parsed.capturedAt,
|
|
@@ -1701,6 +1706,8 @@ function buildBaseState() {
|
|
|
1701
1706
|
branchName: null,
|
|
1702
1707
|
localCommitHash: null,
|
|
1703
1708
|
currentSnapshotHash: null,
|
|
1709
|
+
currentServerRevisionId: null,
|
|
1710
|
+
currentServerTreeHash: null,
|
|
1704
1711
|
currentServerHeadHash: null,
|
|
1705
1712
|
currentServerHeadCommitId: null,
|
|
1706
1713
|
worktreeClean: false,
|
|
@@ -1734,6 +1741,8 @@ function buildBaseState() {
|
|
|
1734
1741
|
baseline: {
|
|
1735
1742
|
lastSnapshotId: null,
|
|
1736
1743
|
lastSnapshotHash: null,
|
|
1744
|
+
lastServerRevisionId: null,
|
|
1745
|
+
lastServerTreeHash: null,
|
|
1737
1746
|
lastServerHeadHash: null,
|
|
1738
1747
|
lastSeenLocalCommitHash: null
|
|
1739
1748
|
}
|
|
@@ -1860,6 +1869,8 @@ async function collabDetectRepoState(params) {
|
|
|
1860
1869
|
summarizeAsyncJobs({ repoRoot, branchName: binding.branchName ?? null })
|
|
1861
1870
|
]);
|
|
1862
1871
|
const appHead = unwrapResponseObject(headResp, "app head");
|
|
1872
|
+
detected.currentServerRevisionId = appHead.headRevisionId ?? null;
|
|
1873
|
+
detected.currentServerTreeHash = appHead.treeHash ?? null;
|
|
1863
1874
|
detected.currentServerHeadHash = appHead.headCommitHash;
|
|
1864
1875
|
detected.currentServerHeadCommitId = appHead.headCommitId;
|
|
1865
1876
|
detected.currentSnapshotHash = inspection.snapshotHash;
|
|
@@ -1868,6 +1879,8 @@ async function collabDetectRepoState(params) {
|
|
|
1868
1879
|
detected.baseline = {
|
|
1869
1880
|
lastSnapshotId: baseline?.lastSnapshotId ?? null,
|
|
1870
1881
|
lastSnapshotHash: baseline?.lastSnapshotHash ?? null,
|
|
1882
|
+
lastServerRevisionId: baseline?.lastServerRevisionId ?? null,
|
|
1883
|
+
lastServerTreeHash: baseline?.lastServerTreeHash ?? null,
|
|
1871
1884
|
lastServerHeadHash: baseline?.lastServerHeadHash ?? null,
|
|
1872
1885
|
lastSeenLocalCommitHash: baseline?.lastSeenLocalCommitHash ?? null
|
|
1873
1886
|
};
|
|
@@ -1877,6 +1890,7 @@ async function collabDetectRepoState(params) {
|
|
|
1877
1890
|
const bootstrapResp = await params.api.getAppDelta(binding.currentAppId, {
|
|
1878
1891
|
baseHeadHash: localCommitHash,
|
|
1879
1892
|
targetHeadHash: appHead.headCommitHash,
|
|
1893
|
+
targetRevisionId: appHead.headRevisionId,
|
|
1880
1894
|
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
1881
1895
|
remoteUrl: binding.remoteUrl ?? void 0,
|
|
1882
1896
|
defaultBranch: binding.defaultBranch ?? void 0
|
|
@@ -1899,7 +1913,7 @@ async function collabDetectRepoState(params) {
|
|
|
1899
1913
|
}
|
|
1900
1914
|
}
|
|
1901
1915
|
detected.repoState = "external_local_base_changed";
|
|
1902
|
-
detected.hint = "No local Remix baseline exists for this lane yet. Run `remix collab
|
|
1916
|
+
detected.hint = "No local Remix revision baseline exists for this lane yet. Run `remix collab init` or sync this lane to seed the baseline.";
|
|
1903
1917
|
return detected;
|
|
1904
1918
|
}
|
|
1905
1919
|
const localHeadMovedSinceBaseline = Boolean(baseline.lastSeenLocalCommitHash) && localCommitHash !== baseline.lastSeenLocalCommitHash;
|
|
@@ -1918,7 +1932,30 @@ async function collabDetectRepoState(params) {
|
|
|
1918
1932
|
return detected;
|
|
1919
1933
|
}
|
|
1920
1934
|
const localChanged = inspection.snapshotHash !== baseline.lastSnapshotHash;
|
|
1921
|
-
const
|
|
1935
|
+
const serverHeadChanged = appHead.headCommitHash !== baseline.lastServerHeadHash;
|
|
1936
|
+
const revisionChanged = Boolean(
|
|
1937
|
+
baseline.lastServerRevisionId && (appHead.headRevisionId ?? null) !== baseline.lastServerRevisionId
|
|
1938
|
+
);
|
|
1939
|
+
const equivalentRevisionDrift = revisionChanged && !serverHeadChanged;
|
|
1940
|
+
if (equivalentRevisionDrift) {
|
|
1941
|
+
await writeLocalBaseline({
|
|
1942
|
+
repoRoot,
|
|
1943
|
+
repoFingerprint: binding.repoFingerprint,
|
|
1944
|
+
laneId: binding.laneId,
|
|
1945
|
+
currentAppId: binding.currentAppId,
|
|
1946
|
+
branchName: binding.branchName,
|
|
1947
|
+
lastSnapshotId: baseline.lastSnapshotId,
|
|
1948
|
+
lastSnapshotHash: baseline.lastSnapshotHash,
|
|
1949
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
1950
|
+
lastServerTreeHash: appHead.treeHash ?? baseline.lastServerTreeHash ?? null,
|
|
1951
|
+
lastServerHeadHash: appHead.headCommitHash,
|
|
1952
|
+
lastSeenLocalCommitHash: baseline.lastSeenLocalCommitHash
|
|
1953
|
+
});
|
|
1954
|
+
detected.baseline.lastServerRevisionId = appHead.headRevisionId ?? null;
|
|
1955
|
+
detected.baseline.lastServerTreeHash = appHead.treeHash ?? baseline.lastServerTreeHash ?? null;
|
|
1956
|
+
detected.baseline.lastServerHeadHash = appHead.headCommitHash;
|
|
1957
|
+
}
|
|
1958
|
+
const serverChanged = serverHeadChanged;
|
|
1922
1959
|
if (!localChanged && !serverChanged) {
|
|
1923
1960
|
detected.repoState = "idle";
|
|
1924
1961
|
return detected;
|
|
@@ -2347,6 +2384,7 @@ function buildWorkspaceMetadata(params) {
|
|
|
2347
2384
|
recordingMode: "boundary_delta",
|
|
2348
2385
|
baselineSnapshotId: params.baselineSnapshotId,
|
|
2349
2386
|
currentSnapshotId: params.currentSnapshotId,
|
|
2387
|
+
baselineServerRevisionId: params.baselineServerRevisionId ?? null,
|
|
2350
2388
|
baselineServerHeadHash: params.baselineServerHeadHash,
|
|
2351
2389
|
currentSnapshotHash: params.currentSnapshotHash,
|
|
2352
2390
|
localCommitHash: params.localCommitHash,
|
|
@@ -2425,12 +2463,12 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2425
2463
|
throw buildFinalizeCliError({
|
|
2426
2464
|
message: "Local baseline is missing for this queued finalize job.",
|
|
2427
2465
|
exitCode: 2,
|
|
2428
|
-
hint: "Run `remix collab
|
|
2466
|
+
hint: "Run `remix collab init` to seed this checkout's revision baseline.",
|
|
2429
2467
|
disposition: "terminal",
|
|
2430
2468
|
reason: "baseline_missing"
|
|
2431
2469
|
});
|
|
2432
2470
|
}
|
|
2433
|
-
const baselineDrifted = baseline.lastSnapshotId !== job.baselineSnapshotId || baseline.lastServerHeadHash !== job.baselineServerHeadHash;
|
|
2471
|
+
const baselineDrifted = baseline.lastSnapshotId !== job.baselineSnapshotId || (job.baselineServerRevisionId ? baseline.lastServerRevisionId !== job.baselineServerRevisionId : false) || baseline.lastServerHeadHash !== job.baselineServerHeadHash;
|
|
2434
2472
|
const appHead = unwrapResponseObject(appHeadResp, "app head");
|
|
2435
2473
|
const remoteUrl = readMetadataString(job, "remoteUrl");
|
|
2436
2474
|
const defaultBranch = readMetadataString(job, "defaultBranch");
|
|
@@ -2453,12 +2491,13 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2453
2491
|
throw buildFinalizeCliError({
|
|
2454
2492
|
message: "Finalize queue baseline drifted before this job was processed.",
|
|
2455
2493
|
exitCode: 1,
|
|
2456
|
-
hint: "Process queued finalize jobs in capture order, or
|
|
2494
|
+
hint: "Process queued finalize jobs in capture order, or run `remix collab init` to refresh the revision baseline before retrying.",
|
|
2457
2495
|
disposition: "terminal",
|
|
2458
2496
|
reason: "baseline_drifted"
|
|
2459
2497
|
});
|
|
2460
2498
|
}
|
|
2461
|
-
|
|
2499
|
+
const serverStillAtBaseline = job.baselineServerRevisionId ? appHead.headRevisionId === job.baselineServerRevisionId : appHead.headCommitHash === job.baselineServerHeadHash;
|
|
2500
|
+
if (!serverStillAtBaseline) {
|
|
2462
2501
|
throw buildFinalizeCliError({
|
|
2463
2502
|
message: "Server lane changed before a no-diff turn could be recorded.",
|
|
2464
2503
|
exitCode: 2,
|
|
@@ -2480,6 +2519,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2480
2519
|
defaultBranch,
|
|
2481
2520
|
baselineSnapshotId: job.baselineSnapshotId,
|
|
2482
2521
|
currentSnapshotId: job.currentSnapshotId,
|
|
2522
|
+
baselineServerRevisionId: job.baselineServerRevisionId,
|
|
2483
2523
|
baselineServerHeadHash: job.baselineServerHeadHash,
|
|
2484
2524
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
2485
2525
|
localCommitHash: snapshot.localCommitHash,
|
|
@@ -2500,6 +2540,8 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2500
2540
|
branchName: job.branchName,
|
|
2501
2541
|
lastSnapshotId: snapshot.id,
|
|
2502
2542
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
2543
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
2544
|
+
lastServerTreeHash: appHead.treeHash ?? null,
|
|
2503
2545
|
lastServerHeadHash: appHead.headCommitHash,
|
|
2504
2546
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
2505
2547
|
});
|
|
@@ -2520,14 +2562,14 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2520
2562
|
};
|
|
2521
2563
|
}
|
|
2522
2564
|
const localBaselineAdvanced = baseline.lastSnapshotId !== job.baselineSnapshotId;
|
|
2523
|
-
const serverHeadAdvanced = appHead.headCommitHash !== job.baselineServerHeadHash;
|
|
2565
|
+
const serverHeadAdvanced = job.baselineServerRevisionId ? appHead.headRevisionId !== job.baselineServerRevisionId : appHead.headCommitHash !== job.baselineServerHeadHash;
|
|
2524
2566
|
if (baselineDrifted) {
|
|
2525
2567
|
const consistentAdvance = localBaselineAdvanced && serverHeadAdvanced;
|
|
2526
2568
|
if (!consistentAdvance) {
|
|
2527
2569
|
throw buildFinalizeCliError({
|
|
2528
2570
|
message: `Finalize queue baseline advanced inconsistently before this job was processed (localBaselineAdvanced=${localBaselineAdvanced}, serverHeadAdvanced=${serverHeadAdvanced}, jobBaselineSnapshotId=${job.baselineSnapshotId ?? "null"}, liveBaselineSnapshotId=${baseline.lastSnapshotId ?? "null"}, jobBaselineServerHeadHash=${job.baselineServerHeadHash ?? "null"}, liveBaselineServerHeadHash=${baseline.lastServerHeadHash ?? "null"}, currentAppHeadHash=${appHead.headCommitHash}). This indicates local Remix state diverged from the backend in a way that should not be reachable in normal operation; please report this as a bug.`,
|
|
2529
2571
|
exitCode: 1,
|
|
2530
|
-
hint: "Run `remix collab status` to inspect, then
|
|
2572
|
+
hint: "Run `remix collab status` to inspect, then sync or reconcile before retrying.",
|
|
2531
2573
|
disposition: "terminal",
|
|
2532
2574
|
reason: "baseline_drifted"
|
|
2533
2575
|
});
|
|
@@ -2535,6 +2577,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2535
2577
|
}
|
|
2536
2578
|
let submissionDiff = diffResult.diff;
|
|
2537
2579
|
let submissionBaseHeadHash = job.baselineServerHeadHash;
|
|
2580
|
+
let submissionBaseRevisionId = job.baselineServerRevisionId;
|
|
2538
2581
|
let replayedFromBaseHash = null;
|
|
2539
2582
|
if (!submissionBaseHeadHash) {
|
|
2540
2583
|
throw buildFinalizeCliError({
|
|
@@ -2552,7 +2595,9 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2552
2595
|
assistantResponse: job.assistantResponse,
|
|
2553
2596
|
diff: diffResult.diff,
|
|
2554
2597
|
baseCommitHash: submissionBaseHeadHash,
|
|
2598
|
+
baseRevisionId: job.baselineServerRevisionId,
|
|
2555
2599
|
targetHeadCommitHash: appHead.headCommitHash,
|
|
2600
|
+
targetRevisionId: appHead.headRevisionId,
|
|
2556
2601
|
expectedPaths: diffResult.changedPaths,
|
|
2557
2602
|
actor,
|
|
2558
2603
|
workspaceMetadata: buildWorkspaceMetadata({
|
|
@@ -2562,6 +2607,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2562
2607
|
defaultBranch,
|
|
2563
2608
|
baselineSnapshotId: job.baselineSnapshotId,
|
|
2564
2609
|
currentSnapshotId: job.currentSnapshotId,
|
|
2610
|
+
baselineServerRevisionId: job.baselineServerRevisionId,
|
|
2565
2611
|
baselineServerHeadHash: job.baselineServerHeadHash,
|
|
2566
2612
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
2567
2613
|
localCommitHash: snapshot.localCommitHash,
|
|
@@ -2587,6 +2633,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2587
2633
|
submissionDiff = replayDiff.diff;
|
|
2588
2634
|
replayedFromBaseHash = submissionBaseHeadHash;
|
|
2589
2635
|
submissionBaseHeadHash = appHead.headCommitHash;
|
|
2636
|
+
submissionBaseRevisionId = appHead.headRevisionId;
|
|
2590
2637
|
} catch (error) {
|
|
2591
2638
|
if (error instanceof RemixError && error.finalizeDisposition === void 0) {
|
|
2592
2639
|
const detail = error.hint ? `${error.message} (${error.hint})` : error.message;
|
|
@@ -2608,6 +2655,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2608
2655
|
assistantResponse: job.assistantResponse,
|
|
2609
2656
|
diff: submissionDiff,
|
|
2610
2657
|
baseCommitHash: submissionBaseHeadHash,
|
|
2658
|
+
baseRevisionId: submissionBaseRevisionId,
|
|
2611
2659
|
headCommitHash: submissionBaseHeadHash,
|
|
2612
2660
|
changedFilesCount: diffResult.stats.changedFilesCount,
|
|
2613
2661
|
insertions: diffResult.stats.insertions,
|
|
@@ -2620,6 +2668,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2620
2668
|
defaultBranch,
|
|
2621
2669
|
baselineSnapshotId: job.baselineSnapshotId,
|
|
2622
2670
|
currentSnapshotId: job.currentSnapshotId,
|
|
2671
|
+
baselineServerRevisionId: job.baselineServerRevisionId,
|
|
2623
2672
|
baselineServerHeadHash: job.baselineServerHeadHash,
|
|
2624
2673
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
2625
2674
|
localCommitHash: snapshot.localCommitHash,
|
|
@@ -2641,11 +2690,28 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2641
2690
|
throw buildFinalizeCliError({
|
|
2642
2691
|
message: "Backend returned a succeeded change step without a head commit hash.",
|
|
2643
2692
|
exitCode: 1,
|
|
2644
|
-
hint: "This is a backend invariant violation; retry will not help.
|
|
2693
|
+
hint: "This is a backend invariant violation; retry will not help. Run `remix collab status` before trying again.",
|
|
2645
2694
|
disposition: "terminal",
|
|
2646
2695
|
reason: "missing_head_commit_hash"
|
|
2647
2696
|
});
|
|
2648
2697
|
}
|
|
2698
|
+
let nextServerRevisionId = typeof changeStep.resultRevisionId === "string" ? changeStep.resultRevisionId.trim() : "";
|
|
2699
|
+
let nextServerTreeHash = null;
|
|
2700
|
+
if (!nextServerRevisionId) {
|
|
2701
|
+
const freshHeadResp = await params.api.getAppHead(job.currentAppId);
|
|
2702
|
+
const freshHead = unwrapResponseObject(freshHeadResp, "app head");
|
|
2703
|
+
if (freshHead.headCommitHash !== nextServerHeadHash || !freshHead.headRevisionId) {
|
|
2704
|
+
throw buildFinalizeCliError({
|
|
2705
|
+
message: "Backend returned a succeeded change step without a matching result revision.",
|
|
2706
|
+
exitCode: 1,
|
|
2707
|
+
hint: "The local baseline was not advanced because the post-step revision could not be verified. Restart the backend/CLI and retry after checking `remix collab status`.",
|
|
2708
|
+
disposition: "terminal",
|
|
2709
|
+
reason: "missing_result_revision_id"
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2712
|
+
nextServerRevisionId = freshHead.headRevisionId;
|
|
2713
|
+
nextServerTreeHash = freshHead.treeHash ?? null;
|
|
2714
|
+
}
|
|
2649
2715
|
await writeLocalBaseline({
|
|
2650
2716
|
repoRoot: job.repoRoot,
|
|
2651
2717
|
repoFingerprint: job.repoFingerprint,
|
|
@@ -2654,6 +2720,8 @@ async function processClaimedPendingFinalizeJobInner(params) {
|
|
|
2654
2720
|
branchName: job.branchName,
|
|
2655
2721
|
lastSnapshotId: snapshot.id,
|
|
2656
2722
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
2723
|
+
lastServerRevisionId: nextServerRevisionId,
|
|
2724
|
+
lastServerTreeHash: nextServerTreeHash,
|
|
2657
2725
|
lastServerHeadHash: nextServerHeadHash,
|
|
2658
2726
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
2659
2727
|
});
|
|
@@ -2702,6 +2770,7 @@ async function enqueueCapturedFinalizeTurn(params) {
|
|
|
2702
2770
|
prompt: params.prompt,
|
|
2703
2771
|
assistantResponse: params.assistantResponse,
|
|
2704
2772
|
baselineSnapshotId: params.baselineSnapshotId,
|
|
2773
|
+
baselineServerRevisionId: params.baselineServerRevisionId ?? null,
|
|
2705
2774
|
baselineServerHeadHash: params.baselineServerHeadHash,
|
|
2706
2775
|
currentSnapshotId: params.currentSnapshotId,
|
|
2707
2776
|
idempotencyKey: params.idempotencyKey,
|
|
@@ -2802,17 +2871,6 @@ async function collabFinalizeTurn(params) {
|
|
|
2802
2871
|
});
|
|
2803
2872
|
}
|
|
2804
2873
|
}
|
|
2805
|
-
const pendingReAnchor = await findPendingAsyncJob({
|
|
2806
|
-
repoRoot,
|
|
2807
|
-
branchName: binding.branchName ?? null,
|
|
2808
|
-
kind: "re_anchor"
|
|
2809
|
-
});
|
|
2810
|
-
if (pendingReAnchor) {
|
|
2811
|
-
throw new RemixError("Cannot finalize a turn while a re-anchor is still processing.", {
|
|
2812
|
-
exitCode: 2,
|
|
2813
|
-
hint: `Re-anchor job ${pendingReAnchor.id} is still in the background queue. Run \`remix collab status\` to check progress.`
|
|
2814
|
-
});
|
|
2815
|
-
}
|
|
2816
2874
|
const detected = await collabDetectRepoState({
|
|
2817
2875
|
api: params.api,
|
|
2818
2876
|
cwd: repoRoot,
|
|
@@ -2853,9 +2911,16 @@ async function collabFinalizeTurn(params) {
|
|
|
2853
2911
|
hint: detected.hint
|
|
2854
2912
|
});
|
|
2855
2913
|
}
|
|
2914
|
+
if (detected.repoState === "both_changed") {
|
|
2915
|
+
throw new RemixError("Local and server changes must be reconciled before finalizing this turn.", {
|
|
2916
|
+
code: "reconcile_required",
|
|
2917
|
+
exitCode: 2,
|
|
2918
|
+
hint: detected.hint || "Run `remix collab reconcile --dry-run` to inspect recovery options before retrying."
|
|
2919
|
+
});
|
|
2920
|
+
}
|
|
2856
2921
|
if (detected.repoState === "external_local_base_changed") {
|
|
2857
|
-
throw new RemixError("The local checkout
|
|
2858
|
-
code: "
|
|
2922
|
+
throw new RemixError("The local checkout is missing a Remix revision baseline for this lane.", {
|
|
2923
|
+
code: "baseline_missing",
|
|
2859
2924
|
exitCode: 2,
|
|
2860
2925
|
hint: detected.hint
|
|
2861
2926
|
});
|
|
@@ -2867,8 +2932,9 @@ async function collabFinalizeTurn(params) {
|
|
|
2867
2932
|
});
|
|
2868
2933
|
if (!baseline) {
|
|
2869
2934
|
throw new RemixError("Local Remix baseline is missing for this lane.", {
|
|
2935
|
+
code: "baseline_missing",
|
|
2870
2936
|
exitCode: 2,
|
|
2871
|
-
hint: "Run `remix collab
|
|
2937
|
+
hint: "Run `remix collab init` or sync this lane to create a fresh revision baseline."
|
|
2872
2938
|
});
|
|
2873
2939
|
}
|
|
2874
2940
|
const snapshot = await captureLocalSnapshot({
|
|
@@ -2879,10 +2945,11 @@ async function collabFinalizeTurn(params) {
|
|
|
2879
2945
|
});
|
|
2880
2946
|
const mode = snapshot.snapshotHash === baseline.lastSnapshotHash ? "no_diff_turn" : "changed_turn";
|
|
2881
2947
|
const idempotencyKey = params.idempotencyKey?.trim() || buildDeterministicIdempotencyKey({
|
|
2882
|
-
kind: "
|
|
2948
|
+
kind: "collab_finalize_turn_boundary_v2",
|
|
2883
2949
|
appId: binding.currentAppId,
|
|
2884
2950
|
laneId: binding.laneId,
|
|
2885
2951
|
baselineSnapshotId: baseline.lastSnapshotId,
|
|
2952
|
+
baselineServerRevisionId: baseline.lastServerRevisionId,
|
|
2886
2953
|
baselineServerHeadHash: baseline.lastServerHeadHash,
|
|
2887
2954
|
currentSnapshotId: snapshot.id,
|
|
2888
2955
|
currentSnapshotHash: snapshot.snapshotHash,
|
|
@@ -2902,6 +2969,7 @@ async function collabFinalizeTurn(params) {
|
|
|
2902
2969
|
prompt,
|
|
2903
2970
|
assistantResponse,
|
|
2904
2971
|
baselineSnapshotId: baseline.lastSnapshotId,
|
|
2972
|
+
baselineServerRevisionId: baseline.lastServerRevisionId,
|
|
2905
2973
|
baselineServerHeadHash: baseline.lastServerHeadHash,
|
|
2906
2974
|
currentSnapshotId: snapshot.id,
|
|
2907
2975
|
idempotencyKey,
|
|
@@ -2950,9 +3018,10 @@ var FINALIZE_PREFLIGHT_FAILURE_CODES = [
|
|
|
2950
3018
|
// Server has commits we don't. Fix: `remix collab sync` (safe to
|
|
2951
3019
|
// auto-run for fast-forward; non-FF refused by the command itself).
|
|
2952
3020
|
"pull_required",
|
|
2953
|
-
//
|
|
2954
|
-
|
|
2955
|
-
|
|
3021
|
+
// Both local and server changed. Fix: inspect and apply reconcile.
|
|
3022
|
+
"reconcile_required",
|
|
3023
|
+
// Local revision baseline is missing. Fix: `remix collab init` or sync.
|
|
3024
|
+
"baseline_missing"
|
|
2956
3025
|
];
|
|
2957
3026
|
var CODE_SET = new Set(FINALIZE_PREFLIGHT_FAILURE_CODES);
|
|
2958
3027
|
function isFinalizePreflightFailureCode(value) {
|
|
@@ -3011,7 +3080,7 @@ async function collabRecordingPreflight(params) {
|
|
|
3011
3080
|
if (detected.status === "branch_mismatch") return { status: "branch_mismatch", ...base };
|
|
3012
3081
|
if (detected.repoState === "server_only_changed") return { status: "pull_required", ...base };
|
|
3013
3082
|
if (detected.repoState === "both_changed") return { status: "reconcile_required", ...base };
|
|
3014
|
-
if (detected.repoState === "external_local_base_changed") return { status: "
|
|
3083
|
+
if (detected.repoState === "external_local_base_changed") return { status: "baseline_missing", ...base };
|
|
3015
3084
|
return { status: "ready", ...base };
|
|
3016
3085
|
}
|
|
3017
3086
|
|
|
@@ -3238,7 +3307,7 @@ async function ensureWorkspaceMatchesBaseline(params) {
|
|
|
3238
3307
|
if (!baseline?.lastSnapshotHash || !baseline.lastServerHeadHash) {
|
|
3239
3308
|
throw new RemixError("Local Remix baseline is missing for this lane.", {
|
|
3240
3309
|
exitCode: 2,
|
|
3241
|
-
hint: "Run `remix collab
|
|
3310
|
+
hint: "Run `remix collab init` or sync from a checkout with a valid revision baseline before applying server changes."
|
|
3242
3311
|
});
|
|
3243
3312
|
}
|
|
3244
3313
|
const inspection = await inspectLocalSnapshot({
|
|
@@ -3312,11 +3381,12 @@ async function collabSync(params) {
|
|
|
3312
3381
|
const repoSnapshot = await captureRepoSnapshot(repoRoot, { includeWorkspaceDiffHash: true });
|
|
3313
3382
|
const bootstrapFromLocalHead = !detected.baseline.lastSnapshotHash || !detected.baseline.lastServerHeadHash;
|
|
3314
3383
|
let baselineServerHeadHash;
|
|
3384
|
+
let baselineServerRevisionId = null;
|
|
3315
3385
|
if (bootstrapFromLocalHead) {
|
|
3316
3386
|
if (!headCommitHash) {
|
|
3317
3387
|
throw new RemixError("Failed to resolve local HEAD commit for the initial sync bootstrap.", {
|
|
3318
3388
|
exitCode: 1,
|
|
3319
|
-
hint: "Retry after Git HEAD is available, or run `remix collab
|
|
3389
|
+
hint: "Retry after Git HEAD is available, or run `remix collab init` to seed this checkout's revision baseline."
|
|
3320
3390
|
});
|
|
3321
3391
|
}
|
|
3322
3392
|
baselineServerHeadHash = headCommitHash;
|
|
@@ -3331,13 +3401,15 @@ async function collabSync(params) {
|
|
|
3331
3401
|
if (!baseline.lastServerHeadHash) {
|
|
3332
3402
|
throw new RemixError("Local Remix baseline is missing the last acknowledged server head.", {
|
|
3333
3403
|
exitCode: 2,
|
|
3334
|
-
hint: "Run `remix collab
|
|
3404
|
+
hint: "Run `remix collab init` or sync from a checkout with a valid revision baseline before pulling server changes."
|
|
3335
3405
|
});
|
|
3336
3406
|
}
|
|
3337
3407
|
baselineServerHeadHash = baseline.lastServerHeadHash;
|
|
3408
|
+
baselineServerRevisionId = baseline.lastServerRevisionId;
|
|
3338
3409
|
}
|
|
3339
3410
|
const deltaResp = await params.api.getAppDelta(binding.currentAppId, {
|
|
3340
3411
|
baseHeadHash: baselineServerHeadHash,
|
|
3412
|
+
baseRevisionId: baselineServerRevisionId,
|
|
3341
3413
|
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
3342
3414
|
remoteUrl: binding.remoteUrl ?? void 0,
|
|
3343
3415
|
defaultBranch: binding.defaultBranch ?? void 0
|
|
@@ -3361,13 +3433,54 @@ async function collabSync(params) {
|
|
|
3361
3433
|
applied: false,
|
|
3362
3434
|
dryRun: params.dryRun
|
|
3363
3435
|
};
|
|
3364
|
-
if (params.dryRun
|
|
3436
|
+
if (params.dryRun) {
|
|
3365
3437
|
return previewResult;
|
|
3366
3438
|
}
|
|
3439
|
+
if (delta.status === "up_to_date") {
|
|
3440
|
+
if (!bootstrapFromLocalHead) {
|
|
3441
|
+
return previewResult;
|
|
3442
|
+
}
|
|
3443
|
+
return withRepoMutationLock(
|
|
3444
|
+
{
|
|
3445
|
+
cwd: repoRoot,
|
|
3446
|
+
operation: "collabSync"
|
|
3447
|
+
},
|
|
3448
|
+
async ({ repoRoot: lockedRepoRoot, warnings }) => {
|
|
3449
|
+
await assertRepoSnapshotUnchanged(lockedRepoRoot, repoSnapshot, {
|
|
3450
|
+
operation: "`remix collab sync`",
|
|
3451
|
+
recoveryHint: "The repository changed before the first local Remix baseline could be created. Review the local changes and rerun `remix collab sync`."
|
|
3452
|
+
});
|
|
3453
|
+
const snapshot = await captureLocalSnapshot({
|
|
3454
|
+
repoRoot: lockedRepoRoot,
|
|
3455
|
+
repoFingerprint: binding.repoFingerprint,
|
|
3456
|
+
laneId: binding.laneId,
|
|
3457
|
+
branchName: binding.branchName
|
|
3458
|
+
});
|
|
3459
|
+
await writeLocalBaseline({
|
|
3460
|
+
repoRoot: lockedRepoRoot,
|
|
3461
|
+
repoFingerprint: binding.repoFingerprint,
|
|
3462
|
+
laneId: binding.laneId,
|
|
3463
|
+
currentAppId: binding.currentAppId,
|
|
3464
|
+
branchName: binding.branchName,
|
|
3465
|
+
lastSnapshotId: snapshot.id,
|
|
3466
|
+
lastSnapshotHash: snapshot.snapshotHash,
|
|
3467
|
+
lastServerRevisionId: delta.targetRevisionId ?? null,
|
|
3468
|
+
lastServerTreeHash: delta.targetTreeHash ?? null,
|
|
3469
|
+
lastServerHeadHash: delta.targetHeadHash,
|
|
3470
|
+
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
3471
|
+
});
|
|
3472
|
+
return {
|
|
3473
|
+
...previewResult,
|
|
3474
|
+
localCommitHash: snapshot.localCommitHash,
|
|
3475
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
3476
|
+
};
|
|
3477
|
+
}
|
|
3478
|
+
);
|
|
3479
|
+
}
|
|
3367
3480
|
if (delta.status === "base_unknown") {
|
|
3368
3481
|
throw new RemixError("Direct pull is unavailable because Remix can no longer diff from the last acknowledged server head.", {
|
|
3369
3482
|
exitCode: 2,
|
|
3370
|
-
hint: "Run `remix collab reconcile --dry-run` to inspect recovery options before retrying. If this checkout has no local Remix baseline yet for this lane, `remix collab
|
|
3483
|
+
hint: "Run `remix collab reconcile --dry-run` to inspect recovery options before retrying. If this checkout has no local Remix baseline yet for this lane, run `remix collab init` to seed one."
|
|
3371
3484
|
});
|
|
3372
3485
|
}
|
|
3373
3486
|
if (delta.status !== "delta_ready") {
|
|
@@ -3411,6 +3524,8 @@ async function collabSync(params) {
|
|
|
3411
3524
|
branchName: binding.branchName,
|
|
3412
3525
|
lastSnapshotId: snapshot.id,
|
|
3413
3526
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
3527
|
+
lastServerRevisionId: delta.targetRevisionId ?? null,
|
|
3528
|
+
lastServerTreeHash: delta.targetTreeHash ?? null,
|
|
3414
3529
|
lastServerHeadHash: delta.targetHeadHash,
|
|
3415
3530
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
3416
3531
|
});
|
|
@@ -3705,6 +3820,8 @@ async function collabCheckout(params) {
|
|
|
3705
3820
|
branchName: branchNameForBaseline,
|
|
3706
3821
|
lastSnapshotId: snapshot.id,
|
|
3707
3822
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
3823
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
3824
|
+
lastServerTreeHash: appHead.treeHash ?? null,
|
|
3708
3825
|
lastServerHeadHash: appHead.headCommitHash,
|
|
3709
3826
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
3710
3827
|
});
|
|
@@ -4107,6 +4224,8 @@ async function trySeedEquivalentBranchBaseline(params) {
|
|
|
4107
4224
|
branchName: params.branchName,
|
|
4108
4225
|
lastSnapshotId: snapshot.id,
|
|
4109
4226
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
4227
|
+
lastServerRevisionId: params.appHeadRevisionId,
|
|
4228
|
+
lastServerTreeHash: params.appTreeHash,
|
|
4110
4229
|
lastServerHeadHash: params.appHeadHash,
|
|
4111
4230
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
4112
4231
|
});
|
|
@@ -4118,11 +4237,11 @@ async function resolveInitBaselineStatus(params) {
|
|
|
4118
4237
|
laneId: params.laneId,
|
|
4119
4238
|
repoRoot: params.repoRoot
|
|
4120
4239
|
});
|
|
4121
|
-
if (baseline?.lastSnapshotHash && baseline.lastServerHeadHash) {
|
|
4240
|
+
if (baseline?.lastSnapshotHash && (baseline.lastServerRevisionId || baseline.lastServerHeadHash)) {
|
|
4122
4241
|
return "existing";
|
|
4123
4242
|
}
|
|
4124
4243
|
const localHeadCommitHash = await getHeadCommitHash(params.repoRoot);
|
|
4125
|
-
if (!localHeadCommitHash) return "
|
|
4244
|
+
if (!localHeadCommitHash) return "baseline_missing";
|
|
4126
4245
|
const appHead = unwrapResponseObject(
|
|
4127
4246
|
await params.api.getAppHead(params.currentAppId),
|
|
4128
4247
|
"app head"
|
|
@@ -4149,6 +4268,7 @@ async function resolveInitBaselineStatus(params) {
|
|
|
4149
4268
|
const deltaResp = await params.api.getAppDelta(params.currentAppId, {
|
|
4150
4269
|
baseHeadHash: localHeadCommitHash,
|
|
4151
4270
|
targetHeadHash: appHead.headCommitHash,
|
|
4271
|
+
targetRevisionId: appHead.headRevisionId,
|
|
4152
4272
|
localSnapshotHash,
|
|
4153
4273
|
repoFingerprint: params.repoFingerprint,
|
|
4154
4274
|
remoteUrl: params.remoteUrl ?? void 0,
|
|
@@ -4178,7 +4298,9 @@ async function resolveInitBaselineStatus(params) {
|
|
|
4178
4298
|
upstreamAppId: params.upstreamAppId ?? null,
|
|
4179
4299
|
branchName: params.branchName,
|
|
4180
4300
|
defaultBranch: params.defaultBranch,
|
|
4181
|
-
appHeadHash: appHead.headCommitHash
|
|
4301
|
+
appHeadHash: appHead.headCommitHash,
|
|
4302
|
+
appHeadRevisionId: appHead.headRevisionId ?? null,
|
|
4303
|
+
appTreeHash: appHead.treeHash ?? null
|
|
4182
4304
|
});
|
|
4183
4305
|
if (equivalentBaseline) {
|
|
4184
4306
|
return equivalentBaseline;
|
|
@@ -4186,7 +4308,7 @@ async function resolveInitBaselineStatus(params) {
|
|
|
4186
4308
|
}
|
|
4187
4309
|
} catch {
|
|
4188
4310
|
}
|
|
4189
|
-
return "
|
|
4311
|
+
return "baseline_missing";
|
|
4190
4312
|
}
|
|
4191
4313
|
async function seedImportedInitBaseline(params) {
|
|
4192
4314
|
const appHead = unwrapResponseObject(
|
|
@@ -4207,6 +4329,8 @@ async function seedImportedInitBaseline(params) {
|
|
|
4207
4329
|
branchName: params.branchName,
|
|
4208
4330
|
lastSnapshotId: snapshot.id,
|
|
4209
4331
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
4332
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
4333
|
+
lastServerTreeHash: appHead.treeHash ?? null,
|
|
4210
4334
|
lastServerHeadHash: appHead.headCommitHash,
|
|
4211
4335
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
4212
4336
|
});
|
|
@@ -4220,7 +4344,6 @@ async function collabInit(params) {
|
|
|
4220
4344
|
},
|
|
4221
4345
|
async ({ repoRoot, warnings }) => {
|
|
4222
4346
|
await ensureGitInfoExcludeEntries(repoRoot, [".remix/"]);
|
|
4223
|
-
await ensureCleanWorktree(repoRoot, "`remix collab init`");
|
|
4224
4347
|
if (params.path?.trim()) {
|
|
4225
4348
|
throw new RemixError("`remix collab init --path` is not supported.", {
|
|
4226
4349
|
exitCode: 2,
|
|
@@ -4228,6 +4351,10 @@ async function collabInit(params) {
|
|
|
4228
4351
|
});
|
|
4229
4352
|
}
|
|
4230
4353
|
const localBindingState = await readCollabBindingState(repoRoot, { persist: true });
|
|
4354
|
+
const hasExistingBinding = localBindingState != null && Object.keys(localBindingState.branchBindings ?? {}).length > 0;
|
|
4355
|
+
if (params.forceNew || !hasExistingBinding) {
|
|
4356
|
+
await ensureCleanWorktree(repoRoot, "`remix collab init`");
|
|
4357
|
+
}
|
|
4231
4358
|
const persistedRemoteUrl = normalizeGitRemote(localBindingState?.remoteUrl ?? null);
|
|
4232
4359
|
const currentBranch = await getCurrentBranch(repoRoot);
|
|
4233
4360
|
const defaultBranch = localBindingState?.defaultBranch ?? await getDefaultBranch(repoRoot) ?? currentBranch;
|
|
@@ -5081,10 +5208,11 @@ async function collabList(params) {
|
|
|
5081
5208
|
};
|
|
5082
5209
|
}
|
|
5083
5210
|
|
|
5084
|
-
// src/application/collab/
|
|
5085
|
-
import { randomUUID as randomUUID5 } from "crypto";
|
|
5211
|
+
// src/application/collab/collabReconcile.ts
|
|
5086
5212
|
import fs11 from "fs/promises";
|
|
5213
|
+
import os5 from "os";
|
|
5087
5214
|
import path10 from "path";
|
|
5215
|
+
import { execa as execa3 } from "execa";
|
|
5088
5216
|
|
|
5089
5217
|
// src/application/collab/pendingFinalize.ts
|
|
5090
5218
|
function hasPendingFinalize(summary) {
|
|
@@ -5094,258 +5222,7 @@ function buildPendingFinalizeHint() {
|
|
|
5094
5222
|
return "Drain or await the local finalize queue first, then retry after the queued Remix turn finishes recording remotely.";
|
|
5095
5223
|
}
|
|
5096
5224
|
|
|
5097
|
-
// src/application/collab/collabReAnchor.ts
|
|
5098
|
-
async function collabReAnchor(params) {
|
|
5099
|
-
const repoRoot = await findGitRoot(params.cwd);
|
|
5100
|
-
const binding = await ensureActiveLaneBinding({
|
|
5101
|
-
repoRoot,
|
|
5102
|
-
api: params.api,
|
|
5103
|
-
operation: "`remix collab re-anchor`"
|
|
5104
|
-
});
|
|
5105
|
-
if (!binding) {
|
|
5106
|
-
throw new RemixError("Repository is not bound to Remix.", {
|
|
5107
|
-
exitCode: 2,
|
|
5108
|
-
hint: "Run `remix collab init` first."
|
|
5109
|
-
});
|
|
5110
|
-
}
|
|
5111
|
-
const detected = await collabDetectRepoState({
|
|
5112
|
-
api: params.api,
|
|
5113
|
-
cwd: repoRoot,
|
|
5114
|
-
allowBranchMismatch: params.allowBranchMismatch
|
|
5115
|
-
});
|
|
5116
|
-
if (detected.status === "metadata_conflict" || detected.status === "branch_mismatch") {
|
|
5117
|
-
throw new RemixError("Repository must be realigned before seeding a fresh local Remix baseline.", {
|
|
5118
|
-
exitCode: 2,
|
|
5119
|
-
hint: detected.hint
|
|
5120
|
-
});
|
|
5121
|
-
}
|
|
5122
|
-
if (detected.status !== "ready" || !detected.binding) {
|
|
5123
|
-
throw new RemixError(detected.hint || "Repository is not ready for re-anchor.", {
|
|
5124
|
-
exitCode: 2,
|
|
5125
|
-
hint: detected.hint
|
|
5126
|
-
});
|
|
5127
|
-
}
|
|
5128
|
-
if (detected.repoState === "server_only_changed") {
|
|
5129
|
-
throw new RemixError("This checkout is already on a server-known base and only needs a local pull.", {
|
|
5130
|
-
exitCode: 2,
|
|
5131
|
-
hint: "Run `remix collab sync` instead of `remix collab re-anchor`."
|
|
5132
|
-
});
|
|
5133
|
-
}
|
|
5134
|
-
if (detected.repoState === "both_changed") {
|
|
5135
|
-
throw new RemixError("Both the local workspace and the server lane changed since the last agreed baseline.", {
|
|
5136
|
-
exitCode: 2,
|
|
5137
|
-
hint: "Run `remix collab reconcile` to replay the local boundary onto the newer server head."
|
|
5138
|
-
});
|
|
5139
|
-
}
|
|
5140
|
-
if (detected.repoState === "local_only_changed") {
|
|
5141
|
-
if (hasPendingFinalize(detected.pendingFinalize)) {
|
|
5142
|
-
throw new RemixError("Re-anchor is not needed while queued Remix turn recording is still processing.", {
|
|
5143
|
-
exitCode: 2,
|
|
5144
|
-
hint: buildPendingFinalizeHint()
|
|
5145
|
-
});
|
|
5146
|
-
}
|
|
5147
|
-
throw new RemixError("Re-anchor is not the right command for local content changes.", {
|
|
5148
|
-
exitCode: 2,
|
|
5149
|
-
hint: "Remix is source-blind: any local content change since the last recorded turn \u2014 including manual commits, pulls, merges, and rebases \u2014 is recorded with `remix collab finalize-turn`. Use `remix collab re-anchor` only when no local Remix baseline exists yet for this lane (status reports `re_anchor`)."
|
|
5150
|
-
});
|
|
5151
|
-
}
|
|
5152
|
-
if (detected.repoState === "idle") {
|
|
5153
|
-
throw new RemixError("This checkout is already aligned with Remix.", {
|
|
5154
|
-
exitCode: 2,
|
|
5155
|
-
hint: "No re-anchor step is needed. Re-anchor only applies when no local Remix baseline exists yet for this lane."
|
|
5156
|
-
});
|
|
5157
|
-
}
|
|
5158
|
-
await ensureCleanWorktree(repoRoot, "`remix collab re-anchor`");
|
|
5159
|
-
const branch = await requireCurrentBranch(repoRoot);
|
|
5160
|
-
const headCommitHash = await getHeadCommitHash(repoRoot);
|
|
5161
|
-
if (!headCommitHash) {
|
|
5162
|
-
throw new RemixError("Failed to resolve local HEAD commit.", { exitCode: 1 });
|
|
5163
|
-
}
|
|
5164
|
-
if (params.asyncSubmit && !params.dryRun) {
|
|
5165
|
-
const pending = await findPendingAsyncJob({
|
|
5166
|
-
repoRoot,
|
|
5167
|
-
branchName: binding.branchName ?? branch,
|
|
5168
|
-
kind: "re_anchor"
|
|
5169
|
-
});
|
|
5170
|
-
if (pending) {
|
|
5171
|
-
return {
|
|
5172
|
-
status: "queued",
|
|
5173
|
-
queued: true,
|
|
5174
|
-
jobId: pending.id,
|
|
5175
|
-
repoRoot,
|
|
5176
|
-
branch,
|
|
5177
|
-
currentAppId: binding.currentAppId,
|
|
5178
|
-
dryRun: false,
|
|
5179
|
-
applied: false
|
|
5180
|
-
};
|
|
5181
|
-
}
|
|
5182
|
-
}
|
|
5183
|
-
const preflightResp = await params.api.preflightAppReconcile(binding.currentAppId, {
|
|
5184
|
-
localHeadCommitHash: headCommitHash,
|
|
5185
|
-
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
5186
|
-
remoteUrl: binding.remoteUrl ?? void 0,
|
|
5187
|
-
defaultBranch: binding.defaultBranch ?? void 0
|
|
5188
|
-
});
|
|
5189
|
-
const preflight = unwrapResponseObject(preflightResp, "reconcile preflight");
|
|
5190
|
-
if (preflight.status === "metadata_conflict") {
|
|
5191
|
-
throw new RemixError("Local repository metadata conflicts with the bound Remix app.", {
|
|
5192
|
-
exitCode: 2,
|
|
5193
|
-
hint: preflight.warnings.join("\n") || "Run the command from the correct bound repository."
|
|
5194
|
-
});
|
|
5195
|
-
}
|
|
5196
|
-
const preview = {
|
|
5197
|
-
status: preflight.status === "up_to_date" ? "reanchored" : "re_anchor_required",
|
|
5198
|
-
repoRoot,
|
|
5199
|
-
branch,
|
|
5200
|
-
currentAppId: binding.currentAppId,
|
|
5201
|
-
localHeadCommitHash: headCommitHash,
|
|
5202
|
-
targetHeadCommitHash: preflight.targetHeadCommitHash,
|
|
5203
|
-
targetHeadCommitId: preflight.targetHeadCommitId,
|
|
5204
|
-
warnings: preflight.warnings,
|
|
5205
|
-
applied: false,
|
|
5206
|
-
dryRun: params.dryRun === true
|
|
5207
|
-
};
|
|
5208
|
-
if (params.dryRun) {
|
|
5209
|
-
return preview;
|
|
5210
|
-
}
|
|
5211
|
-
let anchoredServerHeadHash = preflight.targetHeadCommitHash;
|
|
5212
|
-
if (params.asyncSubmit && preflight.status === "ready_to_reconcile") {
|
|
5213
|
-
const failed = await findFailedAsyncJob({
|
|
5214
|
-
repoRoot,
|
|
5215
|
-
branchName: binding.branchName ?? branch,
|
|
5216
|
-
kind: "re_anchor"
|
|
5217
|
-
});
|
|
5218
|
-
if (failed) {
|
|
5219
|
-
await deleteAsyncJob(failed.id);
|
|
5220
|
-
}
|
|
5221
|
-
const { bundlePath: tmpBundlePath, headCommitHash: bundledHeadCommitHash } = await createGitBundle(
|
|
5222
|
-
repoRoot,
|
|
5223
|
-
"re-anchor.bundle"
|
|
5224
|
-
);
|
|
5225
|
-
const tmpBundleDir = path10.dirname(tmpBundlePath);
|
|
5226
|
-
try {
|
|
5227
|
-
const jobId = randomUUID5();
|
|
5228
|
-
const durableBundlePath = getAsyncJobBundlePath(jobId);
|
|
5229
|
-
await fs11.mkdir(getAsyncJobDir(jobId), { recursive: true });
|
|
5230
|
-
try {
|
|
5231
|
-
await fs11.rename(tmpBundlePath, durableBundlePath);
|
|
5232
|
-
} catch (error) {
|
|
5233
|
-
if (error?.code !== "EXDEV") throw error;
|
|
5234
|
-
await fs11.copyFile(tmpBundlePath, durableBundlePath);
|
|
5235
|
-
await fs11.unlink(tmpBundlePath).catch(() => void 0);
|
|
5236
|
-
}
|
|
5237
|
-
const bundleSha = await sha256FileHex(durableBundlePath);
|
|
5238
|
-
const job = await enqueueAsyncJob({
|
|
5239
|
-
id: jobId,
|
|
5240
|
-
kind: "re_anchor",
|
|
5241
|
-
status: "queued",
|
|
5242
|
-
repoRoot,
|
|
5243
|
-
repoFingerprint: binding.repoFingerprint,
|
|
5244
|
-
branchName: binding.branchName ?? branch,
|
|
5245
|
-
laneId: binding.laneId,
|
|
5246
|
-
retryCount: 0,
|
|
5247
|
-
error: null,
|
|
5248
|
-
idempotencyKey: null,
|
|
5249
|
-
payload: {
|
|
5250
|
-
bundlePath: durableBundlePath,
|
|
5251
|
-
bundleSha256: bundleSha,
|
|
5252
|
-
localHeadCommitHash: bundledHeadCommitHash,
|
|
5253
|
-
targetHeadCommitHash: preflight.targetHeadCommitHash,
|
|
5254
|
-
appId: binding.currentAppId
|
|
5255
|
-
}
|
|
5256
|
-
});
|
|
5257
|
-
await logDrainerEvent(job.id, "submitted", { kind: "re_anchor" });
|
|
5258
|
-
return {
|
|
5259
|
-
status: "queued",
|
|
5260
|
-
queued: true,
|
|
5261
|
-
jobId: job.id,
|
|
5262
|
-
repoRoot,
|
|
5263
|
-
branch,
|
|
5264
|
-
currentAppId: binding.currentAppId,
|
|
5265
|
-
localHeadCommitHash: bundledHeadCommitHash,
|
|
5266
|
-
targetHeadCommitHash: preflight.targetHeadCommitHash,
|
|
5267
|
-
warnings: preflight.warnings,
|
|
5268
|
-
dryRun: false,
|
|
5269
|
-
applied: false
|
|
5270
|
-
};
|
|
5271
|
-
} finally {
|
|
5272
|
-
await fs11.rm(tmpBundleDir, { recursive: true, force: true }).catch(() => void 0);
|
|
5273
|
-
}
|
|
5274
|
-
}
|
|
5275
|
-
if (preflight.status === "ready_to_reconcile") {
|
|
5276
|
-
const { bundlePath, headCommitHash: bundledHeadCommitHash } = await createGitBundle(repoRoot, "re-anchor.bundle");
|
|
5277
|
-
const bundleTempDir = path10.dirname(bundlePath);
|
|
5278
|
-
try {
|
|
5279
|
-
const bundleStat = await fs11.stat(bundlePath);
|
|
5280
|
-
const checksumSha256 = await sha256FileHex(bundlePath);
|
|
5281
|
-
const presignResp = await params.api.presignImportUploadFirstParty({
|
|
5282
|
-
file: {
|
|
5283
|
-
name: path10.basename(bundlePath),
|
|
5284
|
-
mimeType: "application/x-git-bundle",
|
|
5285
|
-
size: bundleStat.size,
|
|
5286
|
-
checksumSha256
|
|
5287
|
-
}
|
|
5288
|
-
});
|
|
5289
|
-
const uploadTarget = unwrapResponseObject(presignResp, "import upload target");
|
|
5290
|
-
await uploadPresigned({
|
|
5291
|
-
uploadUrl: String(uploadTarget.uploadUrl),
|
|
5292
|
-
filePath: bundlePath,
|
|
5293
|
-
headers: uploadTarget.headers ?? {}
|
|
5294
|
-
});
|
|
5295
|
-
const startResp = await params.api.startAppReconcile(binding.currentAppId, {
|
|
5296
|
-
uploadId: String(uploadTarget.uploadId),
|
|
5297
|
-
localHeadCommitHash: bundledHeadCommitHash,
|
|
5298
|
-
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
5299
|
-
remoteUrl: binding.remoteUrl ?? void 0,
|
|
5300
|
-
defaultBranch: binding.defaultBranch ?? void 0,
|
|
5301
|
-
idempotencyKey: buildDeterministicIdempotencyKey({
|
|
5302
|
-
kind: "collab_re_anchor_v1",
|
|
5303
|
-
appId: binding.currentAppId,
|
|
5304
|
-
localHeadCommitHash: bundledHeadCommitHash,
|
|
5305
|
-
targetHeadCommitHash: preflight.targetHeadCommitHash
|
|
5306
|
-
})
|
|
5307
|
-
});
|
|
5308
|
-
const started = unwrapResponseObject(startResp, "reconcile");
|
|
5309
|
-
const reconcile = await pollReconcile(params.api, binding.currentAppId, started.id);
|
|
5310
|
-
anchoredServerHeadHash = reconcile.reconciledHeadCommitHash ?? reconcile.targetHeadCommitHash ?? preflight.targetHeadCommitHash;
|
|
5311
|
-
} finally {
|
|
5312
|
-
await fs11.rm(bundleTempDir, { recursive: true, force: true });
|
|
5313
|
-
}
|
|
5314
|
-
}
|
|
5315
|
-
const snapshot = await captureLocalSnapshot({
|
|
5316
|
-
repoRoot,
|
|
5317
|
-
repoFingerprint: binding.repoFingerprint,
|
|
5318
|
-
laneId: binding.laneId,
|
|
5319
|
-
branchName: binding.branchName
|
|
5320
|
-
});
|
|
5321
|
-
await writeLocalBaseline({
|
|
5322
|
-
repoRoot,
|
|
5323
|
-
repoFingerprint: binding.repoFingerprint,
|
|
5324
|
-
laneId: binding.laneId,
|
|
5325
|
-
currentAppId: binding.currentAppId,
|
|
5326
|
-
branchName: binding.branchName,
|
|
5327
|
-
lastSnapshotId: snapshot.id,
|
|
5328
|
-
lastSnapshotHash: snapshot.snapshotHash,
|
|
5329
|
-
lastServerHeadHash: anchoredServerHeadHash,
|
|
5330
|
-
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
5331
|
-
});
|
|
5332
|
-
return {
|
|
5333
|
-
...preview,
|
|
5334
|
-
status: "reanchored",
|
|
5335
|
-
targetHeadCommitHash: anchoredServerHeadHash,
|
|
5336
|
-
applied: true,
|
|
5337
|
-
dryRun: false
|
|
5338
|
-
};
|
|
5339
|
-
}
|
|
5340
|
-
async function collabReAnchorSubmit(params) {
|
|
5341
|
-
return collabReAnchor({ ...params, asyncSubmit: true });
|
|
5342
|
-
}
|
|
5343
|
-
|
|
5344
5225
|
// src/application/collab/collabReconcile.ts
|
|
5345
|
-
import fs12 from "fs/promises";
|
|
5346
|
-
import os5 from "os";
|
|
5347
|
-
import path11 from "path";
|
|
5348
|
-
import { execa as execa3 } from "execa";
|
|
5349
5226
|
async function reconcileBothChanged(params) {
|
|
5350
5227
|
const repoRoot = await findGitRoot(params.cwd);
|
|
5351
5228
|
const binding = await ensureActiveLaneBinding({
|
|
@@ -5368,7 +5245,7 @@ async function reconcileBothChanged(params) {
|
|
|
5368
5245
|
if (!baseline?.lastSnapshotId || !baseline.lastServerHeadHash) {
|
|
5369
5246
|
throw new RemixError("Local Remix baseline is missing for this lane.", {
|
|
5370
5247
|
exitCode: 2,
|
|
5371
|
-
hint: "Run `remix collab
|
|
5248
|
+
hint: "Run `remix collab init` or sync from a checkout with a valid revision baseline first."
|
|
5372
5249
|
});
|
|
5373
5250
|
}
|
|
5374
5251
|
const currentSnapshot = await captureLocalSnapshot({
|
|
@@ -5391,6 +5268,7 @@ async function reconcileBothChanged(params) {
|
|
|
5391
5268
|
params.api.getAppHead(binding.currentAppId),
|
|
5392
5269
|
params.api.getAppDelta(binding.currentAppId, {
|
|
5393
5270
|
baseHeadHash: baseline.lastServerHeadHash,
|
|
5271
|
+
baseRevisionId: baseline.lastServerRevisionId,
|
|
5394
5272
|
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
5395
5273
|
remoteUrl: binding.remoteUrl ?? void 0,
|
|
5396
5274
|
defaultBranch: binding.defaultBranch ?? void 0
|
|
@@ -5408,7 +5286,7 @@ async function reconcileBothChanged(params) {
|
|
|
5408
5286
|
if (delta.status === "base_unknown") {
|
|
5409
5287
|
throw new RemixError("Reconcile cannot pull the newer server state from the last acknowledged baseline.", {
|
|
5410
5288
|
exitCode: 2,
|
|
5411
|
-
hint: "Run `remix collab
|
|
5289
|
+
hint: "Run `remix collab init` to seed a fresh revision baseline for this checkout before retrying."
|
|
5412
5290
|
});
|
|
5413
5291
|
}
|
|
5414
5292
|
if (delta.status !== "delta_ready" && delta.status !== "up_to_date") {
|
|
@@ -5439,7 +5317,9 @@ async function reconcileBothChanged(params) {
|
|
|
5439
5317
|
assistantResponse: "Replay the local boundary delta onto the latest server head without recording a new change step.",
|
|
5440
5318
|
diff: diffResult.diff,
|
|
5441
5319
|
baseCommitHash: baseline.lastServerHeadHash,
|
|
5320
|
+
baseRevisionId: baseline.lastServerRevisionId,
|
|
5442
5321
|
targetHeadCommitHash: appHead.headCommitHash,
|
|
5322
|
+
targetRevisionId: appHead.headRevisionId,
|
|
5443
5323
|
expectedPaths: diffResult.changedPaths,
|
|
5444
5324
|
workspaceMetadata: {
|
|
5445
5325
|
recordingMode: "boundary_delta",
|
|
@@ -5447,6 +5327,7 @@ async function reconcileBothChanged(params) {
|
|
|
5447
5327
|
branch,
|
|
5448
5328
|
baselineSnapshotId: baseline.lastSnapshotId,
|
|
5449
5329
|
currentSnapshotId: currentSnapshot.id,
|
|
5330
|
+
baselineServerRevisionId: baseline.lastServerRevisionId,
|
|
5450
5331
|
baselineServerHeadHash: baseline.lastServerHeadHash,
|
|
5451
5332
|
currentSnapshotHash: currentSnapshot.snapshotHash,
|
|
5452
5333
|
localCommitHash: currentSnapshot.localCommitHash,
|
|
@@ -5465,12 +5346,12 @@ async function reconcileBothChanged(params) {
|
|
|
5465
5346
|
const replay = await pollChangeStepReplay(params.api, binding.currentAppId, String(replayStart.id));
|
|
5466
5347
|
const replayDiffResp = await params.api.getChangeStepReplayDiff(binding.currentAppId, replay.id);
|
|
5467
5348
|
const replayDiff = unwrapResponseObject(replayDiffResp, "change step replay diff");
|
|
5468
|
-
const tempRoot = await
|
|
5349
|
+
const tempRoot = await fs11.mkdtemp(path10.join(os5.tmpdir(), "remix-reconcile-"));
|
|
5469
5350
|
let serverHeadSnapshot = null;
|
|
5470
5351
|
let mergedSnapshot = null;
|
|
5471
5352
|
try {
|
|
5472
|
-
const tempRepoRoot =
|
|
5473
|
-
await
|
|
5353
|
+
const tempRepoRoot = path10.join(tempRoot, "repo");
|
|
5354
|
+
await fs11.mkdir(tempRepoRoot, { recursive: true });
|
|
5474
5355
|
await execa3("git", ["init"], { cwd: tempRepoRoot, stderr: "ignore" });
|
|
5475
5356
|
await materializeLocalSnapshot(baseline.lastSnapshotId, tempRepoRoot);
|
|
5476
5357
|
if (delta.status === "delta_ready" && delta.diff.trim()) {
|
|
@@ -5492,7 +5373,7 @@ async function reconcileBothChanged(params) {
|
|
|
5492
5373
|
branchName: binding.branchName
|
|
5493
5374
|
});
|
|
5494
5375
|
} finally {
|
|
5495
|
-
await
|
|
5376
|
+
await fs11.rm(tempRoot, { recursive: true, force: true }).catch(() => void 0);
|
|
5496
5377
|
}
|
|
5497
5378
|
if (!serverHeadSnapshot || !mergedSnapshot) {
|
|
5498
5379
|
throw new RemixError("Failed to materialize the reconciled local workspace.", { exitCode: 1 });
|
|
@@ -5529,6 +5410,8 @@ async function reconcileBothChanged(params) {
|
|
|
5529
5410
|
branchName: binding.branchName,
|
|
5530
5411
|
lastSnapshotId: serverHeadSnapshot.id,
|
|
5531
5412
|
lastSnapshotHash: serverHeadSnapshot.snapshotHash,
|
|
5413
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
5414
|
+
lastServerTreeHash: appHead.treeHash ?? null,
|
|
5532
5415
|
lastServerHeadHash: appHead.headCommitHash,
|
|
5533
5416
|
lastSeenLocalCommitHash: restoredSnapshot.localCommitHash
|
|
5534
5417
|
});
|
|
@@ -5564,7 +5447,10 @@ async function collabReconcile(params) {
|
|
|
5564
5447
|
return reconcileBothChanged(params);
|
|
5565
5448
|
}
|
|
5566
5449
|
if (detected.repoState === "external_local_base_changed") {
|
|
5567
|
-
|
|
5450
|
+
throw new RemixError("This checkout needs a local Remix revision baseline before reconciliation.", {
|
|
5451
|
+
exitCode: 2,
|
|
5452
|
+
hint: detected.hint || "Run `remix collab init` or `remix collab sync` to seed the baseline."
|
|
5453
|
+
});
|
|
5568
5454
|
}
|
|
5569
5455
|
if (detected.repoState === "local_only_changed") {
|
|
5570
5456
|
if (hasPendingFinalize(detected.pendingFinalize)) {
|
|
@@ -5675,6 +5561,8 @@ async function collabRemix(params) {
|
|
|
5675
5561
|
branchName: branchNameForBaseline,
|
|
5676
5562
|
lastSnapshotId: snapshot.id,
|
|
5677
5563
|
lastSnapshotHash: snapshot.snapshotHash,
|
|
5564
|
+
lastServerRevisionId: appHead.headRevisionId ?? null,
|
|
5565
|
+
lastServerTreeHash: appHead.treeHash ?? null,
|
|
5678
5566
|
lastServerHeadHash: appHead.headCommitHash,
|
|
5679
5567
|
lastSeenLocalCommitHash: snapshot.localCommitHash
|
|
5680
5568
|
});
|
|
@@ -5799,11 +5687,15 @@ function createBaseStatus() {
|
|
|
5799
5687
|
baseline: {
|
|
5800
5688
|
lastSnapshotId: null,
|
|
5801
5689
|
lastSnapshotHash: null,
|
|
5690
|
+
lastServerRevisionId: null,
|
|
5691
|
+
lastServerTreeHash: null,
|
|
5802
5692
|
lastServerHeadHash: null,
|
|
5803
5693
|
lastSeenLocalCommitHash: null
|
|
5804
5694
|
},
|
|
5805
5695
|
current: {
|
|
5806
5696
|
snapshotHash: null,
|
|
5697
|
+
serverRevisionId: null,
|
|
5698
|
+
serverTreeHash: null,
|
|
5807
5699
|
serverHeadHash: null,
|
|
5808
5700
|
serverHeadCommitId: null,
|
|
5809
5701
|
localCommitHash: null
|
|
@@ -5890,6 +5782,8 @@ async function collabStatus(params) {
|
|
|
5890
5782
|
status.alignment.baseline = detected.baseline;
|
|
5891
5783
|
status.alignment.current = {
|
|
5892
5784
|
snapshotHash: detected.currentSnapshotHash,
|
|
5785
|
+
serverRevisionId: detected.currentServerRevisionId,
|
|
5786
|
+
serverTreeHash: detected.currentServerTreeHash,
|
|
5893
5787
|
serverHeadHash: detected.currentServerHeadHash,
|
|
5894
5788
|
serverHeadCommitId: detected.currentServerHeadCommitId,
|
|
5895
5789
|
localCommitHash: detected.localCommitHash
|
|
@@ -5953,7 +5847,7 @@ async function collabStatus(params) {
|
|
|
5953
5847
|
status.reconcile.canApply = !status.repo.branchMismatch;
|
|
5954
5848
|
status.recommendedAction = "reconcile";
|
|
5955
5849
|
} else if (detected.repoState === "external_local_base_changed") {
|
|
5956
|
-
status.recommendedAction = "
|
|
5850
|
+
status.recommendedAction = "init";
|
|
5957
5851
|
addBlockedReason(status.sync, "baseline_missing");
|
|
5958
5852
|
addBlockedReason(status.reconcile, "baseline_missing");
|
|
5959
5853
|
} else if (detected.repoState === "local_only_changed") {
|
|
@@ -6117,8 +6011,8 @@ async function collabView(params) {
|
|
|
6117
6011
|
}
|
|
6118
6012
|
|
|
6119
6013
|
// src/application/collab/collabAsyncProcessing.ts
|
|
6120
|
-
import
|
|
6121
|
-
import
|
|
6014
|
+
import fs12 from "fs/promises";
|
|
6015
|
+
import path11 from "path";
|
|
6122
6016
|
var MAX_TRANSIENT_RETRIES = 5;
|
|
6123
6017
|
var TRANSIENT_NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
6124
6018
|
"ECONNREFUSED",
|
|
@@ -6214,10 +6108,10 @@ async function processInitJob(job, api) {
|
|
|
6214
6108
|
try {
|
|
6215
6109
|
await updateAsyncJob(job.id, { status: "submitting", error: null });
|
|
6216
6110
|
await logDrainerEvent(job.id, "claimed", { kind: "init" });
|
|
6217
|
-
const bundleStat = await
|
|
6111
|
+
const bundleStat = await fs12.stat(job.payload.bundlePath);
|
|
6218
6112
|
const presignResp = await api.presignImportUploadFirstParty({
|
|
6219
6113
|
file: {
|
|
6220
|
-
name:
|
|
6114
|
+
name: path11.basename(job.payload.bundlePath),
|
|
6221
6115
|
mimeType: "application/x-git-bundle",
|
|
6222
6116
|
size: bundleStat.size,
|
|
6223
6117
|
checksumSha256: job.payload.bundleSha256
|
|
@@ -6234,7 +6128,7 @@ async function processInitJob(job, api) {
|
|
|
6234
6128
|
await updateAsyncJob(job.id, { status: "server_processing" });
|
|
6235
6129
|
const importResp = await api.importFromUploadFirstParty({
|
|
6236
6130
|
uploadId: String(presign.uploadId),
|
|
6237
|
-
appName: job.payload.appName?.trim() ||
|
|
6131
|
+
appName: job.payload.appName?.trim() || path11.basename(job.repoRoot),
|
|
6238
6132
|
platform: "generic",
|
|
6239
6133
|
isPublic: false,
|
|
6240
6134
|
branch: job.payload.defaultBranch && job.branchName && job.branchName !== job.payload.defaultBranch ? job.payload.defaultBranch : job.branchName ?? void 0,
|
|
@@ -6428,7 +6322,7 @@ async function processInitPostJob(job, api) {
|
|
|
6428
6322
|
if (outcome.status === "failed") {
|
|
6429
6323
|
const bindingPath = getCollabBindingPath(job.repoRoot);
|
|
6430
6324
|
try {
|
|
6431
|
-
await
|
|
6325
|
+
await fs12.unlink(bindingPath);
|
|
6432
6326
|
await logDrainerEvent(job.id, "binding_cleared", {
|
|
6433
6327
|
kind: "init_post",
|
|
6434
6328
|
appId: job.payload.appId,
|
|
@@ -6469,10 +6363,10 @@ async function processReAnchorJob(job, api) {
|
|
|
6469
6363
|
}
|
|
6470
6364
|
let anchoredServerHeadHash = preflight.targetHeadCommitHash;
|
|
6471
6365
|
if (preflight.status === "ready_to_reconcile") {
|
|
6472
|
-
const bundleStat = await
|
|
6366
|
+
const bundleStat = await fs12.stat(job.payload.bundlePath);
|
|
6473
6367
|
const presignResp = await api.presignImportUploadFirstParty({
|
|
6474
6368
|
file: {
|
|
6475
|
-
name:
|
|
6369
|
+
name: path11.basename(job.payload.bundlePath),
|
|
6476
6370
|
mimeType: "application/x-git-bundle",
|
|
6477
6371
|
size: bundleStat.size,
|
|
6478
6372
|
checksumSha256: job.payload.bundleSha256
|
|
@@ -6576,9 +6470,9 @@ async function collabReAnchorProcess(jobId, opts) {
|
|
|
6576
6470
|
}
|
|
6577
6471
|
async function acquireDrainerPidLock() {
|
|
6578
6472
|
const pidPath = getDrainerPidPath();
|
|
6579
|
-
await
|
|
6473
|
+
await fs12.mkdir(path11.dirname(pidPath), { recursive: true });
|
|
6580
6474
|
try {
|
|
6581
|
-
const existing = await
|
|
6475
|
+
const existing = await fs12.readFile(pidPath, "utf8").catch(() => "");
|
|
6582
6476
|
const existingPid = parseInt(existing.trim(), 10);
|
|
6583
6477
|
if (Number.isFinite(existingPid) && existingPid > 0 && existingPid !== process.pid) {
|
|
6584
6478
|
try {
|
|
@@ -6588,13 +6482,13 @@ async function acquireDrainerPidLock() {
|
|
|
6588
6482
|
if (error?.code !== "ESRCH") return null;
|
|
6589
6483
|
}
|
|
6590
6484
|
}
|
|
6591
|
-
await
|
|
6485
|
+
await fs12.writeFile(pidPath, String(process.pid), "utf8");
|
|
6592
6486
|
return {
|
|
6593
6487
|
release: async () => {
|
|
6594
6488
|
try {
|
|
6595
|
-
const current = (await
|
|
6489
|
+
const current = (await fs12.readFile(pidPath, "utf8")).trim();
|
|
6596
6490
|
if (current === String(process.pid)) {
|
|
6597
|
-
await
|
|
6491
|
+
await fs12.unlink(pidPath).catch(() => void 0);
|
|
6598
6492
|
}
|
|
6599
6493
|
} catch {
|
|
6600
6494
|
}
|
|
@@ -6645,9 +6539,6 @@ export {
|
|
|
6645
6539
|
collabList,
|
|
6646
6540
|
collabListMembers,
|
|
6647
6541
|
collabListMergeRequests,
|
|
6648
|
-
collabReAnchor,
|
|
6649
|
-
collabReAnchorProcess,
|
|
6650
|
-
collabReAnchorSubmit,
|
|
6651
6542
|
collabReconcile,
|
|
6652
6543
|
collabRecordingPreflight,
|
|
6653
6544
|
collabReject,
|