@remixhq/core 0.1.36 → 0.1.37

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CoreConfig } from './config.js';
2
2
  import { T as TokenProvider } from './tokenProvider-B9so5Pm3.js';
3
- import { T as TurnUsage } from './contracts-BqC3Lj7W.js';
3
+ import { T as TurnUsage } from './contracts-DTeSJnoC.js';
4
4
  import 'zod';
5
5
 
6
6
  type HistoryImportProvider = "claude_code" | "cursor";
@@ -264,6 +264,50 @@ type SyncLocalResponse = {
264
264
  };
265
265
  warnings: string[];
266
266
  };
267
+ type LocalCheckoutRecommendedAction = "pull" | "reconcile" | "await_finalize" | "record" | "no_action";
268
+ type LocalAlignmentEventKind = "sync_local" | "reconcile_local";
269
+ type ReportLocalCheckoutStatePayload = {
270
+ checkoutId: string;
271
+ collabLaneId?: string;
272
+ branch?: string;
273
+ repoFingerprint?: string;
274
+ localState?: string;
275
+ recommendedAction: LocalCheckoutRecommendedAction;
276
+ materializationStatus?: string;
277
+ alignmentRequired?: string;
278
+ baselineRevisionId?: string | null;
279
+ localRevisionId?: string | null;
280
+ serverRevisionId?: string | null;
281
+ localHeadHash?: string;
282
+ serverHeadCommitId?: string | null;
283
+ serverHeadHash?: string;
284
+ hasLocalChanges?: boolean;
285
+ hasUnrecordedChanges?: boolean;
286
+ pendingFinalizeCount?: number;
287
+ lastSeenAt?: string;
288
+ metadata?: Record<string, unknown>;
289
+ };
290
+ type RecordLocalAlignmentEventPayload = {
291
+ eventKind: LocalAlignmentEventKind;
292
+ branch?: string;
293
+ collabLaneId?: string;
294
+ baseRevisionId?: string | null;
295
+ targetRevisionId?: string | null;
296
+ resultRevisionId?: string | null;
297
+ baseHeadCommitId?: string | null;
298
+ baseHeadHash?: string;
299
+ targetHeadCommitId?: string | null;
300
+ targetHeadHash?: string;
301
+ resultHeadCommitId?: string | null;
302
+ resultHeadHash?: string;
303
+ changedFilesCount?: number;
304
+ insertions?: number;
305
+ deletions?: number;
306
+ localCommitCount?: number;
307
+ summary?: string;
308
+ idempotencyKey?: string;
309
+ metadata?: Record<string, unknown>;
310
+ };
267
311
  type SyncUpstreamResponse = {
268
312
  status: "up-to-date" | "queued";
269
313
  mergeRequestId?: string;
@@ -1110,6 +1154,9 @@ type ApiClient = {
1110
1154
  defaultBranch?: string;
1111
1155
  dryRun?: boolean;
1112
1156
  }): Promise<Json>;
1157
+ reportLocalCheckoutState(appId: string, payload: ReportLocalCheckoutStatePayload): Promise<Json>;
1158
+ getLatestLocalCheckoutState(appId: string): Promise<Json>;
1159
+ recordLocalAlignmentEvent(appId: string, payload: RecordLocalAlignmentEventPayload): Promise<Json>;
1113
1160
  initiateBundle(appId: string, payload: InitiateBundleRequest): Promise<Json>;
1114
1161
  getBundle(appId: string, bundleId: string): Promise<Json>;
1115
1162
  getBundleDownloadUrl(appId: string, bundleId: string, options?: {
package/dist/api.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApiClient
3
- } from "./chunk-MNVBXTSV.js";
3
+ } from "./chunk-QIESQM7X.js";
4
4
  import "./chunk-7XJGOKEO.js";
5
5
  export {
6
6
  createApiClient
@@ -548,6 +548,15 @@ function createApiClient(config, opts) {
548
548
  method: "POST",
549
549
  body: JSON.stringify(payload)
550
550
  }),
551
+ reportLocalCheckoutState: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/local-alignment/checkout-state`, {
552
+ method: "POST",
553
+ body: JSON.stringify(payload)
554
+ }),
555
+ getLatestLocalCheckoutState: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}/local-alignment/checkout-state/latest`, { method: "GET" }),
556
+ recordLocalAlignmentEvent: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/local-alignment/events`, {
557
+ method: "POST",
558
+ body: JSON.stringify(payload)
559
+ }),
551
560
  initiateBundle: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/bundles`, { method: "POST", body: JSON.stringify(payload) }),
552
561
  getBundle: (appId, bundleId) => request(`/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}`, { method: "GET" }),
553
562
  getBundleDownloadUrl: (appId, bundleId, options) => request(
package/dist/collab.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as CollabApiClient, a as TurnUsagePayload, b as CollabFinalizeTurnResult, c as CollabRecordingPreflight, d as CollabApproveMode, e as CollabApprovalStrategy, f as CollabApproveResult, M as MergeRequestQueue, g as MergeRequest, I as InvitationScopeType, h as CollabMember, A as AppProfileInput, J as JsonObject, i as AppDeltaResponse, j as CollabStatus, k as MergeRequestReview } from './contracts-BqC3Lj7W.js';
2
- export { l as AppHeadResponse, m as AppMember, n as AppMemberRole, o as AppReconcileResponse, p as CollabFinalizeTurnAutoSync, q as CollabFinalizeTurnMode, r as CollabPendingFinalizeState, s as CollabPendingFinalizeSummary, t as CollabRecordingPreflightStatus, u as CollabRepoStateKind, v as CollabStatusBlockedReason, w as CollabStatusRecommendedAction, x as CollabTurn, y as MembershipScopeType, z as MergeRequestStatus, B as ModelCall, O as OrganizationMember, D as OrganizationMemberRole, P as ProjectMember, E as ProjectMemberRole, R as ReconcilePreflightResponse, S as ServerToolUsage, F as SyncLocalResponse, G as SyncUpstreamResponse, T as TurnUsage, H as TurnUsageCaptureSource, K as TurnUsageConfidence, L as TurnUsagePreviousPatch } from './contracts-BqC3Lj7W.js';
1
+ import { C as CollabApiClient, a as TurnUsagePayload, b as CollabFinalizeTurnResult, c as CollabRecordingPreflight, d as CollabApproveMode, e as CollabApprovalStrategy, f as CollabApproveResult, M as MergeRequestQueue, g as MergeRequest, I as InvitationScopeType, h as CollabMember, A as AppProfileInput, J as JsonObject, i as AppDeltaResponse, j as CollabStatus, k as MergeRequestReview } from './contracts-DTeSJnoC.js';
2
+ export { l as AppHeadResponse, m as AppMember, n as AppMemberRole, o as AppReconcileResponse, p as CollabFinalizeTurnAutoSync, q as CollabFinalizeTurnMode, r as CollabPendingFinalizeState, s as CollabPendingFinalizeSummary, t as CollabRecordingPreflightStatus, u as CollabRepoStateKind, v as CollabStatusBlockedReason, w as CollabStatusRecommendedAction, x as CollabTurn, y as MembershipScopeType, z as MergeRequestStatus, B as ModelCall, O as OrganizationMember, D as OrganizationMemberRole, P as ProjectMember, E as ProjectMemberRole, R as ReconcilePreflightResponse, S as ServerToolUsage, F as SyncLocalResponse, G as SyncUpstreamResponse, T as TurnUsage, H as TurnUsageCaptureSource, K as TurnUsageConfidence, L as TurnUsagePreviousPatch } from './contracts-DTeSJnoC.js';
3
3
  import { B as BranchBindingMode } from './binding-WiIRI2fl.js';
4
4
 
5
5
  declare function collabFinalizeTurn(params: {
package/dist/collab.js CHANGED
@@ -952,6 +952,25 @@ function matchesJobScope(job, scope) {
952
952
  if (scope.repoFingerprint && job.repoFingerprint && job.repoFingerprint !== scope.repoFingerprint) return false;
953
953
  return true;
954
954
  }
955
+ function isSameProcessingScope(left, right) {
956
+ return left.id !== right.id && left.repoRoot === right.repoRoot && left.currentAppId === right.currentAppId && (left.laneId ?? null) === (right.laneId ?? null) && (left.repoFingerprint ?? null) === (right.repoFingerprint ?? null);
957
+ }
958
+ async function hasFreshJobLock(jobId) {
959
+ try {
960
+ const stat = await fs4.stat(getJobLockPath2(jobId));
961
+ return Date.now() - stat.mtimeMs < FINALIZE_JOB_LOCK_STALE_MS;
962
+ } catch (error) {
963
+ if (error?.code === "ENOENT") {
964
+ return false;
965
+ }
966
+ throw error;
967
+ }
968
+ }
969
+ async function blocksFinalizeClaim(candidate, blocker) {
970
+ if (!isSameProcessingScope(candidate, blocker)) return false;
971
+ if (blocker.status === "processing") return hasFreshJobLock(blocker.id);
972
+ return blocker.status === "queued" && isPastDue(blocker.nextRetryAt) && blocker.capturedAt < candidate.capturedAt;
973
+ }
955
974
  function createEmptyPendingFinalizeQueueSummary() {
956
975
  return {
957
976
  state: "idle",
@@ -1225,6 +1244,13 @@ async function claimPendingFinalizeJob(jobId) {
1225
1244
  await release();
1226
1245
  return null;
1227
1246
  }
1247
+ const jobs = await listPendingFinalizeJobs();
1248
+ for (const job of jobs) {
1249
+ if (await blocksFinalizeClaim(existing, job)) {
1250
+ await release();
1251
+ return null;
1252
+ }
1253
+ }
1228
1254
  const now = (/* @__PURE__ */ new Date()).toISOString();
1229
1255
  const claimed = await updatePendingFinalizeJob(jobId, {
1230
1256
  status: "processing",
@@ -2710,6 +2736,188 @@ function validateSucceededChangeStepForBaseline(params) {
2710
2736
  };
2711
2737
  }
2712
2738
 
2739
+ // src/infrastructure/collab/checkoutIdentityStore.ts
2740
+ import { randomUUID as randomUUID4 } from "crypto";
2741
+ import fs5 from "fs/promises";
2742
+ import path6 from "path";
2743
+ function getCheckoutIdentityPath(params) {
2744
+ return path6.join(getCollabStateRoot(), "checkouts", `${buildLaneStateKey(params)}.json`);
2745
+ }
2746
+ async function getCheckoutIdentity(params) {
2747
+ const filePath = getCheckoutIdentityPath(params);
2748
+ try {
2749
+ const raw = await fs5.readFile(filePath, "utf8");
2750
+ const parsed = JSON.parse(raw);
2751
+ const checkoutId = typeof parsed?.checkoutId === "string" ? parsed.checkoutId.trim() : "";
2752
+ if (checkoutId) return checkoutId;
2753
+ } catch {
2754
+ }
2755
+ const next = {
2756
+ schemaVersion: 1,
2757
+ checkoutId: randomUUID4(),
2758
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2759
+ };
2760
+ await writeJsonAtomic(filePath, next);
2761
+ return next.checkoutId;
2762
+ }
2763
+
2764
+ // src/application/collab/localAlignmentReporting.ts
2765
+ function toCheckoutAction(action) {
2766
+ if (action === "pull" || action === "reconcile" || action === "await_finalize" || action === "record") return action;
2767
+ return "no_action";
2768
+ }
2769
+ function actionForRepoState(repoState) {
2770
+ if (repoState === "server_only_changed") return "pull";
2771
+ if (repoState === "both_changed" || repoState === "external_local_base_changed" || repoState === "baseline_missing") {
2772
+ return "reconcile";
2773
+ }
2774
+ if (repoState === "local_only_changed") return "record";
2775
+ return "no_action";
2776
+ }
2777
+ function alignmentRequiredForAction(action) {
2778
+ if (action === "pull") return "sync";
2779
+ if (action === "reconcile") return "reconcile";
2780
+ if (action === "await_finalize") return "finalize";
2781
+ if (action === "record") return "record";
2782
+ return void 0;
2783
+ }
2784
+ async function reportCheckoutState(params) {
2785
+ try {
2786
+ const checkoutId = await getCheckoutIdentity({
2787
+ repoRoot: params.repoRoot,
2788
+ repoFingerprint: params.repoFingerprint ?? null,
2789
+ laneId: params.laneId ?? null
2790
+ });
2791
+ await params.api.reportLocalCheckoutState(params.appId, {
2792
+ checkoutId,
2793
+ collabLaneId: params.laneId ?? void 0,
2794
+ branch: params.branch ?? void 0,
2795
+ repoFingerprint: params.repoFingerprint ?? void 0,
2796
+ localState: params.localState ?? void 0,
2797
+ recommendedAction: params.recommendedAction,
2798
+ materializationStatus: params.materializationStatus ?? void 0,
2799
+ alignmentRequired: params.alignmentRequired ?? alignmentRequiredForAction(params.recommendedAction),
2800
+ baselineRevisionId: params.baselineRevisionId,
2801
+ localRevisionId: params.localRevisionId,
2802
+ serverRevisionId: params.serverRevisionId,
2803
+ localHeadHash: params.localHeadHash ?? void 0,
2804
+ serverHeadCommitId: params.serverHeadCommitId,
2805
+ serverHeadHash: params.serverHeadHash ?? void 0,
2806
+ hasLocalChanges: params.hasLocalChanges,
2807
+ hasUnrecordedChanges: params.hasUnrecordedChanges,
2808
+ pendingFinalizeCount: params.pendingFinalizeCount,
2809
+ metadata: params.metadata
2810
+ });
2811
+ return { warnings: [] };
2812
+ } catch (err) {
2813
+ const message = err instanceof Error ? err.message : String(err ?? "unknown error");
2814
+ return { warnings: [`Failed to report local checkout state: ${message}`] };
2815
+ }
2816
+ }
2817
+ async function reportCheckoutStateFromStatus(params) {
2818
+ const { api, status } = params;
2819
+ const repoRoot = status.repo.repoRoot;
2820
+ const appId = status.binding.currentAppId;
2821
+ if (!api || !repoRoot || !appId) return { warnings: [] };
2822
+ return reportCheckoutState({
2823
+ api,
2824
+ appId,
2825
+ repoRoot,
2826
+ repoFingerprint: status.binding.repoFingerprint,
2827
+ laneId: status.binding.laneId,
2828
+ branch: status.repo.branch ?? status.binding.branchName,
2829
+ localState: status.alignment.repoState,
2830
+ recommendedAction: toCheckoutAction(status.recommendedAction),
2831
+ materializationStatus: status.alignment.checked ? "checked" : void 0,
2832
+ baselineRevisionId: status.alignment.baseline.lastServerRevisionId,
2833
+ serverRevisionId: status.alignment.current.serverRevisionId,
2834
+ localHeadHash: status.alignment.current.localCommitHash ?? status.repo.headCommitHash,
2835
+ serverHeadCommitId: status.alignment.current.serverHeadCommitId,
2836
+ serverHeadHash: status.alignment.current.serverHeadHash,
2837
+ hasLocalChanges: status.alignment.repoState === "local_only_changed" || status.alignment.repoState === "both_changed",
2838
+ hasUnrecordedChanges: status.alignment.repoState === "local_only_changed" || status.alignment.repoState === "both_changed",
2839
+ pendingFinalizeCount: status.alignment.pendingFinalize.activeJobCount,
2840
+ metadata: {
2841
+ source: params.source,
2842
+ branchMismatch: status.repo.branchMismatch
2843
+ }
2844
+ });
2845
+ }
2846
+ async function reportAlignedCheckoutState(params) {
2847
+ return reportCheckoutState({
2848
+ api: params.api,
2849
+ appId: params.appId,
2850
+ repoRoot: params.repoRoot,
2851
+ repoFingerprint: params.repoFingerprint,
2852
+ laneId: params.laneId,
2853
+ branch: params.branch,
2854
+ localState: "idle",
2855
+ recommendedAction: "no_action",
2856
+ materializationStatus: "applied",
2857
+ baselineRevisionId: params.baselineRevisionId,
2858
+ serverRevisionId: params.serverRevisionId,
2859
+ localHeadHash: params.localHeadHash,
2860
+ serverHeadCommitId: params.serverHeadCommitId,
2861
+ serverHeadHash: params.serverHeadHash,
2862
+ hasLocalChanges: false,
2863
+ hasUnrecordedChanges: false,
2864
+ pendingFinalizeCount: 0,
2865
+ metadata: { source: params.source }
2866
+ });
2867
+ }
2868
+ async function reportCheckoutStateFromFinalize(params) {
2869
+ const recommendedAction = params.materializationStatus.startsWith("queued") ? "await_finalize" : actionForRepoState(params.repoState);
2870
+ const hasLocalChanges = params.repoState === "local_only_changed" || params.repoState === "both_changed";
2871
+ return reportCheckoutState({
2872
+ api: params.api,
2873
+ appId: params.appId,
2874
+ repoRoot: params.repoRoot,
2875
+ repoFingerprint: params.repoFingerprint,
2876
+ laneId: params.laneId,
2877
+ branch: params.branch,
2878
+ localState: params.repoState,
2879
+ recommendedAction,
2880
+ materializationStatus: params.materializationStatus,
2881
+ alignmentRequired: params.alignmentRequired,
2882
+ baselineRevisionId: params.baselineRevisionId,
2883
+ serverRevisionId: params.serverRevisionId,
2884
+ localHeadHash: params.localHeadHash,
2885
+ serverHeadCommitId: params.serverHeadCommitId,
2886
+ serverHeadHash: params.serverHeadHash,
2887
+ hasLocalChanges,
2888
+ hasUnrecordedChanges: recommendedAction === "record" || recommendedAction === "reconcile",
2889
+ pendingFinalizeCount: params.pendingFinalizeCount,
2890
+ metadata: { source: params.source }
2891
+ });
2892
+ }
2893
+ async function recordLocalAlignmentEvent(params) {
2894
+ try {
2895
+ await params.api.recordLocalAlignmentEvent(params.appId, {
2896
+ eventKind: params.eventKind,
2897
+ branch: params.branch,
2898
+ collabLaneId: params.collabLaneId ?? void 0,
2899
+ baseRevisionId: params.baseRevisionId,
2900
+ targetRevisionId: params.targetRevisionId,
2901
+ resultRevisionId: params.resultRevisionId,
2902
+ baseHeadHash: params.baseHeadHash ?? void 0,
2903
+ targetHeadCommitId: params.targetHeadCommitId,
2904
+ targetHeadHash: params.targetHeadHash ?? void 0,
2905
+ resultHeadHash: params.resultHeadHash ?? void 0,
2906
+ changedFilesCount: params.changedFilesCount,
2907
+ insertions: params.insertions,
2908
+ deletions: params.deletions,
2909
+ localCommitCount: params.localCommitCount,
2910
+ summary: params.summary,
2911
+ idempotencyKey: params.idempotencyKey,
2912
+ metadata: { source: params.eventKind }
2913
+ });
2914
+ return { warnings: [] };
2915
+ } catch (err) {
2916
+ const message = err instanceof Error ? err.message : String(err ?? "unknown error");
2917
+ return { warnings: [`Failed to record local alignment event: ${message}`] };
2918
+ }
2919
+ }
2920
+
2713
2921
  // src/application/collab/collabFinalizeProcessing.ts
2714
2922
  var FINALIZE_RETRY_BASE_DELAY_MS = 15e3;
2715
2923
  var FINALIZE_RETRY_MAX_DELAY_MS = 5 * 60 * 1e3;
@@ -2834,6 +3042,23 @@ async function writeBaselineFromSucceededChangeStep(params) {
2834
3042
  lastSeenLocalCommitHash: params.snapshot.localCommitHash
2835
3043
  });
2836
3044
  }
3045
+ async function reportCompletedFinalizeCheckoutState(params) {
3046
+ const result = await reportAlignedCheckoutState({
3047
+ api: params.api,
3048
+ appId: params.job.currentAppId,
3049
+ repoRoot: params.job.repoRoot,
3050
+ repoFingerprint: params.job.repoFingerprint,
3051
+ laneId: params.job.laneId,
3052
+ branch: params.job.branchName,
3053
+ baselineRevisionId: params.serverRevisionId,
3054
+ serverRevisionId: params.serverRevisionId,
3055
+ localHeadHash: params.snapshot.localCommitHash,
3056
+ serverHeadCommitId: params.serverHeadCommitId,
3057
+ serverHeadHash: params.serverHeadHash,
3058
+ source: params.source
3059
+ });
3060
+ return result.warnings;
3061
+ }
2837
3062
  async function harvestPreTurnEvents(repoRoot, fromCommit, toCommit) {
2838
3063
  if (!toCommit) return null;
2839
3064
  try {
@@ -3002,6 +3227,15 @@ async function processClaimedPendingFinalizeJobInner(params) {
3002
3227
  status: "completed",
3003
3228
  metadata: { collabTurnId: collabTurn.id }
3004
3229
  });
3230
+ const reportWarnings2 = await reportCompletedFinalizeCheckoutState({
3231
+ api: params.api,
3232
+ job,
3233
+ snapshot,
3234
+ serverRevisionId: appHead.headRevisionId ?? null,
3235
+ serverHeadCommitId: appHead.headCommitId,
3236
+ serverHeadHash: appHead.headCommitHash,
3237
+ source: "finalize_no_diff_turn"
3238
+ });
3005
3239
  return {
3006
3240
  mode: "no_diff_turn",
3007
3241
  idempotencyKey: job.idempotencyKey ?? "",
@@ -3011,7 +3245,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
3011
3245
  changeStep: null,
3012
3246
  collabTurn,
3013
3247
  autoSync: null,
3014
- warnings: []
3248
+ warnings: reportWarnings2
3015
3249
  };
3016
3250
  }
3017
3251
  const localBaselineAdvanced = baseline.lastSnapshotId !== job.baselineSnapshotId;
@@ -3062,6 +3296,15 @@ async function processClaimedPendingFinalizeJobInner(params) {
3062
3296
  changeStepId: String(changeStep2.id ?? "")
3063
3297
  }
3064
3298
  });
3299
+ const reportWarnings2 = await reportCompletedFinalizeCheckoutState({
3300
+ api: params.api,
3301
+ job,
3302
+ snapshot,
3303
+ serverRevisionId: String(changeStep2.resultRevisionId ?? "") || null,
3304
+ serverHeadCommitId: String(changeStep2.commitId ?? "") || null,
3305
+ serverHeadHash: String(changeStep2.headCommitHash ?? "") || null,
3306
+ source: "finalize_changed_turn"
3307
+ });
3065
3308
  return {
3066
3309
  mode: "changed_turn",
3067
3310
  idempotencyKey: job.idempotencyKey ?? "",
@@ -3071,7 +3314,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
3071
3314
  changeStep: changeStep2,
3072
3315
  collabTurn: null,
3073
3316
  autoSync: null,
3074
- warnings: []
3317
+ warnings: reportWarnings2
3075
3318
  };
3076
3319
  }
3077
3320
  }
@@ -3179,6 +3422,15 @@ async function processClaimedPendingFinalizeJobInner(params) {
3179
3422
  changeStepId: String(changeStep.id ?? "")
3180
3423
  }
3181
3424
  });
3425
+ const reportWarnings = await reportCompletedFinalizeCheckoutState({
3426
+ api: params.api,
3427
+ job,
3428
+ snapshot,
3429
+ serverRevisionId: String(changeStep.resultRevisionId ?? "") || null,
3430
+ serverHeadCommitId: String(changeStep.commitId ?? "") || null,
3431
+ serverHeadHash: String(changeStep.headCommitHash ?? "") || null,
3432
+ source: "finalize_changed_turn"
3433
+ });
3182
3434
  return {
3183
3435
  mode: "changed_turn",
3184
3436
  idempotencyKey: job.idempotencyKey ?? "",
@@ -3188,7 +3440,7 @@ async function processClaimedPendingFinalizeJobInner(params) {
3188
3440
  changeStep,
3189
3441
  collabTurn: null,
3190
3442
  autoSync: null,
3191
- warnings: []
3443
+ warnings: reportWarnings
3192
3444
  };
3193
3445
  }
3194
3446
  async function processPendingFinalizeJob(params) {
@@ -3396,6 +3648,23 @@ async function recordConversationOnlyTurn(params) {
3396
3648
  idempotencyKey
3397
3649
  });
3398
3650
  const collabTurn = unwrapResponseObject(collabTurnResp, "collab turn");
3651
+ const reportResult = await reportCheckoutStateFromFinalize({
3652
+ api: params.api,
3653
+ appId: params.binding.currentAppId,
3654
+ repoRoot: params.repoRoot,
3655
+ repoFingerprint: params.binding.repoFingerprint,
3656
+ laneId: params.binding.laneId,
3657
+ branch: params.binding.branchName,
3658
+ repoState: params.repoState,
3659
+ materializationStatus: params.materializationStatus,
3660
+ alignmentRequired: params.alignmentRequired,
3661
+ baselineRevisionId: params.baselineServerRevisionId,
3662
+ serverRevisionId: params.currentServerRevisionId,
3663
+ localHeadHash: params.localCommitHash,
3664
+ serverHeadCommitId: params.currentServerHeadCommitId,
3665
+ serverHeadHash: params.currentServerHeadHash,
3666
+ source: "finalize_conversation_only"
3667
+ });
3399
3668
  return {
3400
3669
  mode: "conversation_only",
3401
3670
  idempotencyKey,
@@ -3405,7 +3674,7 @@ async function recordConversationOnlyTurn(params) {
3405
3674
  changeStep: null,
3406
3675
  collabTurn,
3407
3676
  autoSync: null,
3408
- warnings: [CONVERSATION_ONLY_WARNING, ...collectWarnings(params.warnings ?? [])]
3677
+ warnings: [CONVERSATION_ONLY_WARNING, ...collectWarnings(params.warnings ?? []), ...reportResult.warnings]
3409
3678
  };
3410
3679
  }
3411
3680
  async function collabFinalizeTurn(params) {
@@ -3668,6 +3937,24 @@ async function collabFinalizeTurn(params) {
3668
3937
  },
3669
3938
  nextRetryAt
3670
3939
  });
3940
+ const reportResult = await reportCheckoutStateFromFinalize({
3941
+ api: params.api,
3942
+ appId: binding.currentAppId,
3943
+ repoRoot,
3944
+ repoFingerprint: binding.repoFingerprint,
3945
+ laneId: binding.laneId,
3946
+ branch: binding.branchName,
3947
+ repoState: detected.repoState,
3948
+ materializationStatus: "queued_finalize",
3949
+ alignmentRequired: "finalize",
3950
+ baselineRevisionId: baseline.lastServerRevisionId,
3951
+ serverRevisionId: detected.currentServerRevisionId,
3952
+ localHeadHash: snapshot.localCommitHash,
3953
+ serverHeadCommitId: detected.currentServerHeadCommitId,
3954
+ serverHeadHash: detected.currentServerHeadHash,
3955
+ pendingFinalizeCount: 1,
3956
+ source: "finalize_queue"
3957
+ });
3671
3958
  return {
3672
3959
  mode,
3673
3960
  idempotencyKey,
@@ -3677,7 +3964,7 @@ async function collabFinalizeTurn(params) {
3677
3964
  changeStep: null,
3678
3965
  collabTurn: null,
3679
3966
  autoSync: null,
3680
- warnings: [FINALIZE_QUEUED_WARNING, ...collectWarnings([...pendingFinalizeDrainWarnings, ...detected.warnings])]
3967
+ warnings: [FINALIZE_QUEUED_WARNING, ...collectWarnings([...pendingFinalizeDrainWarnings, ...detected.warnings]), ...reportResult.warnings]
3681
3968
  };
3682
3969
  }
3683
3970
 
@@ -3773,9 +4060,9 @@ async function collabRecordingPreflight(params) {
3773
4060
  }
3774
4061
 
3775
4062
  // src/infrastructure/locking/repoMutationLock.ts
3776
- import fs5 from "fs/promises";
4063
+ import fs6 from "fs/promises";
3777
4064
  import os3 from "os";
3778
- import path6 from "path";
4065
+ import path7 from "path";
3779
4066
  var DEFAULT_ACQUIRE_TIMEOUT_MS = 15e3;
3780
4067
  var DEFAULT_STALE_MS = 45e3;
3781
4068
  var DEFAULT_HEARTBEAT_MS = 5e3;
@@ -3798,12 +4085,12 @@ function createOwner(params) {
3798
4085
  };
3799
4086
  }
3800
4087
  async function writeOwnerMetadata(ownerPath, owner) {
3801
- await fs5.writeFile(ownerPath, `${JSON.stringify(owner, null, 2)}
4088
+ await fs6.writeFile(ownerPath, `${JSON.stringify(owner, null, 2)}
3802
4089
  `, "utf8");
3803
4090
  }
3804
4091
  async function readOwnerMetadata(ownerPath) {
3805
4092
  try {
3806
- const raw = await fs5.readFile(ownerPath, "utf8");
4093
+ const raw = await fs6.readFile(ownerPath, "utf8");
3807
4094
  const parsed = JSON.parse(raw);
3808
4095
  if (!parsed || typeof parsed !== "object") return null;
3809
4096
  if (!parsed.operation || !parsed.repoRoot || typeof parsed.pid !== "number" || !parsed.startedAt || !parsed.heartbeatAt) {
@@ -3840,23 +4127,23 @@ async function getLastKnownUpdateMs(lockDir, ownerPath, owner) {
3840
4127
  if (Number.isFinite(heartbeatMs)) return heartbeatMs;
3841
4128
  const startedMs = owner ? Date.parse(owner.startedAt) : Number.NaN;
3842
4129
  if (Number.isFinite(startedMs)) return startedMs;
3843
- const stat = await fs5.stat(ownerPath).catch(() => null);
4130
+ const stat = await fs6.stat(ownerPath).catch(() => null);
3844
4131
  if (stat) return stat.mtimeMs;
3845
- const dirStat = await fs5.stat(lockDir).catch(() => null);
4132
+ const dirStat = await fs6.stat(lockDir).catch(() => null);
3846
4133
  if (dirStat) return dirStat.mtimeMs;
3847
4134
  return 0;
3848
4135
  }
3849
4136
  async function ensureLockDir(lockDir) {
3850
- await fs5.mkdir(path6.dirname(lockDir), { recursive: true });
4137
+ await fs6.mkdir(path7.dirname(lockDir), { recursive: true });
3851
4138
  }
3852
4139
  async function tryAcquireLock(lockDir, ownerPath, owner) {
3853
4140
  try {
3854
4141
  await ensureLockDir(lockDir);
3855
- await fs5.mkdir(lockDir);
4142
+ await fs6.mkdir(lockDir);
3856
4143
  try {
3857
4144
  await writeOwnerMetadata(ownerPath, owner);
3858
4145
  } catch (error) {
3859
- await fs5.rm(lockDir, { recursive: true, force: true }).catch(() => void 0);
4146
+ await fs6.rm(lockDir, { recursive: true, force: true }).catch(() => void 0);
3860
4147
  throw error;
3861
4148
  }
3862
4149
  return true;
@@ -3906,7 +4193,7 @@ async function acquirePhysicalLock(lockDir, ownerPath, owner, options) {
3906
4193
  const alive = await isProcessAlive(currentOwner2);
3907
4194
  if (ageMs >= options.staleMs && alive !== true) {
3908
4195
  notices.push(buildStaleRecoveryNotice(currentOwner2));
3909
- await fs5.rm(lockDir, { recursive: true, force: true }).catch(() => void 0);
4196
+ await fs6.rm(lockDir, { recursive: true, force: true }).catch(() => void 0);
3910
4197
  continue;
3911
4198
  }
3912
4199
  await sleep2(RETRY_DELAY_MS);
@@ -3931,7 +4218,7 @@ function startHeartbeat(lockDir, ownerPath, owner, heartbeatMs) {
3931
4218
  };
3932
4219
  owner.heartbeatAt = nextOwner.heartbeatAt;
3933
4220
  void writeOwnerMetadata(ownerPath, nextOwner).catch(() => void 0);
3934
- void fs5.utimes(lockDir, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date()).catch(() => void 0);
4221
+ void fs6.utimes(lockDir, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date()).catch(() => void 0);
3935
4222
  }, heartbeatMs);
3936
4223
  }
3937
4224
  async function releaseReentrantLock(lockDir) {
@@ -3941,12 +4228,12 @@ async function releaseReentrantLock(lockDir) {
3941
4228
  if (held.count > 0) return;
3942
4229
  clearInterval(held.heartbeatTimer);
3943
4230
  heldLocks.delete(lockDir);
3944
- await fs5.rm(lockDir, { recursive: true, force: true }).catch(() => void 0);
4231
+ await fs6.rm(lockDir, { recursive: true, force: true }).catch(() => void 0);
3945
4232
  }
3946
4233
  async function withRepoMutationLock(options, fn) {
3947
4234
  const repoRoot = await findGitRoot(options.cwd);
3948
4235
  const gitCommonDir = await getGitCommonDir(repoRoot);
3949
- const lockDir = path6.join(gitCommonDir, "remix", "locks", "repo-mutation.lock");
4236
+ const lockDir = path7.join(gitCommonDir, "remix", "locks", "repo-mutation.lock");
3950
4237
  const owner = createOwner({
3951
4238
  operation: options.operation,
3952
4239
  repoRoot,
@@ -3958,11 +4245,11 @@ async function withRepoMutationLock(options, fn) {
3958
4245
  const existing = heldLocks.get(lockDir);
3959
4246
  let notices = [];
3960
4247
  if (!existing) {
3961
- notices = await acquirePhysicalLock(lockDir, path6.join(lockDir, "owner.json"), owner, {
4248
+ notices = await acquirePhysicalLock(lockDir, path7.join(lockDir, "owner.json"), owner, {
3962
4249
  acquireTimeoutMs,
3963
4250
  staleMs
3964
4251
  });
3965
- const ownerPath = path6.join(lockDir, "owner.json");
4252
+ const ownerPath = path7.join(lockDir, "owner.json");
3966
4253
  heldLocks.set(lockDir, {
3967
4254
  count: 1,
3968
4255
  lockDir,
@@ -4158,10 +4445,24 @@ async function collabSync(params) {
4158
4445
  lastServerHeadHash: delta.targetHeadHash,
4159
4446
  lastSeenLocalCommitHash: snapshot.localCommitHash
4160
4447
  });
4448
+ const reportResult = await reportAlignedCheckoutState({
4449
+ api: params.api,
4450
+ appId: binding.currentAppId,
4451
+ repoRoot: lockedRepoRoot,
4452
+ repoFingerprint: binding.repoFingerprint,
4453
+ laneId: binding.laneId,
4454
+ branch,
4455
+ baselineRevisionId: delta.targetRevisionId ?? null,
4456
+ serverRevisionId: delta.targetRevisionId ?? null,
4457
+ localHeadHash: snapshot.localCommitHash,
4458
+ serverHeadCommitId: delta.targetHeadCommitId,
4459
+ serverHeadHash: delta.targetHeadHash,
4460
+ source: "sync_apply"
4461
+ });
4161
4462
  return {
4162
4463
  ...previewResult,
4163
4464
  localCommitHash: snapshot.localCommitHash,
4164
- ...warnings.length > 0 ? { warnings } : {}
4465
+ ...[...warnings, ...reportResult.warnings].length > 0 ? { warnings: [...warnings, ...reportResult.warnings] } : {}
4165
4466
  };
4166
4467
  }
4167
4468
  );
@@ -4218,13 +4519,56 @@ async function collabSync(params) {
4218
4519
  lastServerHeadHash: delta.targetHeadHash,
4219
4520
  lastSeenLocalCommitHash: snapshot.localCommitHash
4220
4521
  });
4522
+ const reportResult = await reportAlignedCheckoutState({
4523
+ api: params.api,
4524
+ appId: binding.currentAppId,
4525
+ repoRoot: lockedRepoRoot,
4526
+ repoFingerprint: binding.repoFingerprint,
4527
+ laneId: binding.laneId,
4528
+ branch,
4529
+ baselineRevisionId: delta.targetRevisionId ?? null,
4530
+ serverRevisionId: delta.targetRevisionId ?? null,
4531
+ localHeadHash: snapshot.localCommitHash,
4532
+ serverHeadCommitId: delta.targetHeadCommitId,
4533
+ serverHeadHash: delta.targetHeadHash,
4534
+ source: "sync_apply"
4535
+ });
4536
+ const eventResult = await recordLocalAlignmentEvent({
4537
+ api: params.api,
4538
+ appId: binding.currentAppId,
4539
+ eventKind: "sync_local",
4540
+ branch,
4541
+ collabLaneId: binding.laneId,
4542
+ baseRevisionId: baselineServerRevisionId,
4543
+ targetRevisionId: delta.targetRevisionId ?? null,
4544
+ resultRevisionId: delta.targetRevisionId ?? null,
4545
+ baseHeadHash: baselineServerHeadHash,
4546
+ targetHeadCommitId: delta.targetHeadCommitId,
4547
+ targetHeadHash: delta.targetHeadHash,
4548
+ resultHeadHash: delta.targetHeadHash,
4549
+ changedFilesCount: delta.stats.changedFilesCount,
4550
+ insertions: delta.stats.insertions,
4551
+ deletions: delta.stats.deletions,
4552
+ localCommitCount: 0,
4553
+ summary: "Synced local checkout with Remix.",
4554
+ idempotencyKey: buildDeterministicIdempotencyKey({
4555
+ kind: "collab_sync_local_alignment_v1",
4556
+ appId: binding.currentAppId,
4557
+ laneId: binding.laneId,
4558
+ baseRevisionId: baselineServerRevisionId,
4559
+ targetRevisionId: delta.targetRevisionId ?? null,
4560
+ baseHeadHash: baselineServerHeadHash,
4561
+ targetHeadHash: delta.targetHeadHash,
4562
+ diffSha256: delta.diffSha256
4563
+ })
4564
+ });
4221
4565
  return {
4222
4566
  ...previewResult,
4223
4567
  status: "delta_ready",
4224
4568
  localCommitHash: snapshot.localCommitHash,
4225
4569
  applied: true,
4226
4570
  dryRun: false,
4227
- ...warnings.length > 0 ? { warnings } : {}
4571
+ ...[...warnings, ...reportResult.warnings, ...eventResult.warnings].length > 0 ? { warnings: [...warnings, ...reportResult.warnings, ...eventResult.warnings] } : {}
4228
4572
  };
4229
4573
  }
4230
4574
  );
@@ -4322,38 +4666,38 @@ async function collabApprove(params) {
4322
4666
  }
4323
4667
 
4324
4668
  // src/application/collab/checkoutWorkspace.ts
4325
- import fs6 from "fs/promises";
4669
+ import fs7 from "fs/promises";
4326
4670
  import os4 from "os";
4327
- import path7 from "path";
4671
+ import path8 from "path";
4328
4672
  async function pathExists(targetPath) {
4329
4673
  try {
4330
- await fs6.access(targetPath);
4674
+ await fs7.access(targetPath);
4331
4675
  return true;
4332
4676
  } catch {
4333
4677
  return false;
4334
4678
  }
4335
4679
  }
4336
4680
  async function statIsDirectory(targetPath) {
4337
- const stats = await fs6.stat(targetPath).catch(() => null);
4681
+ const stats = await fs7.stat(targetPath).catch(() => null);
4338
4682
  return Boolean(stats?.isDirectory());
4339
4683
  }
4340
4684
  async function findContainingGitRoot(startPath) {
4341
- let current = path7.resolve(startPath);
4685
+ let current = path8.resolve(startPath);
4342
4686
  while (true) {
4343
- if (await pathExists(path7.join(current, ".git"))) return current;
4344
- const parent = path7.dirname(current);
4687
+ if (await pathExists(path8.join(current, ".git"))) return current;
4688
+ const parent = path8.dirname(current);
4345
4689
  if (parent === current) return null;
4346
4690
  current = parent;
4347
4691
  }
4348
4692
  }
4349
4693
  function isSubpath(parentPath, candidatePath) {
4350
- const relative = path7.relative(parentPath, candidatePath);
4351
- return relative === "" || !relative.startsWith("..") && !path7.isAbsolute(relative);
4694
+ const relative = path8.relative(parentPath, candidatePath);
4695
+ return relative === "" || !relative.startsWith("..") && !path8.isAbsolute(relative);
4352
4696
  }
4353
4697
  async function resolveCheckoutDestination(params) {
4354
4698
  if (params.outputDir?.trim()) {
4355
- const preferredRepoRoot = path7.resolve(params.outputDir.trim());
4356
- const parentDir2 = path7.dirname(preferredRepoRoot);
4699
+ const preferredRepoRoot = path8.resolve(params.outputDir.trim());
4700
+ const parentDir2 = path8.dirname(preferredRepoRoot);
4357
4701
  if (!await statIsDirectory(parentDir2)) {
4358
4702
  throw new RemixError("Remix output parent directory does not exist.", {
4359
4703
  exitCode: 2,
@@ -4366,7 +4710,7 @@ async function resolveCheckoutDestination(params) {
4366
4710
  explicitOutputDir: true
4367
4711
  };
4368
4712
  }
4369
- const parentDir = path7.resolve(params.cwd);
4713
+ const parentDir = path8.resolve(params.cwd);
4370
4714
  if (!await statIsDirectory(parentDir)) {
4371
4715
  throw new RemixError("Remix output parent directory does not exist.", {
4372
4716
  exitCode: 2,
@@ -4374,7 +4718,7 @@ async function resolveCheckoutDestination(params) {
4374
4718
  });
4375
4719
  }
4376
4720
  return {
4377
- preferredRepoRoot: path7.join(parentDir, params.defaultDirName),
4721
+ preferredRepoRoot: path8.join(parentDir, params.defaultDirName),
4378
4722
  parentDir,
4379
4723
  explicitOutputDir: false
4380
4724
  };
@@ -4408,11 +4752,11 @@ async function materializeAppCheckout(params) {
4408
4752
  explicitOutputDir: destination.explicitOutputDir
4409
4753
  });
4410
4754
  const repoRoot = destination.explicitOutputDir ? await reserveDirectory(destination.preferredRepoRoot) : await reserveAvailableDirPath(destination.preferredRepoRoot);
4411
- const bundleTempDir = await fs6.mkdtemp(path7.join(os4.tmpdir(), "remix-checkout-"));
4412
- const bundlePath = path7.join(bundleTempDir, "repository.bundle");
4755
+ const bundleTempDir = await fs7.mkdtemp(path8.join(os4.tmpdir(), "remix-checkout-"));
4756
+ const bundlePath = path8.join(bundleTempDir, "repository.bundle");
4413
4757
  try {
4414
4758
  const bundle = await params.api.downloadAppBundle(params.appId);
4415
- await fs6.writeFile(bundlePath, bundle.data);
4759
+ await fs7.writeFile(bundlePath, bundle.data);
4416
4760
  await cloneGitBundleToDirectory(bundlePath, repoRoot);
4417
4761
  if (params.expectedBranchName?.trim()) {
4418
4762
  await checkoutLocalBranch(repoRoot, params.expectedBranchName.trim());
@@ -4422,11 +4766,11 @@ async function materializeAppCheckout(params) {
4422
4766
  }
4423
4767
  await ensureGitInfoExcludeEntries(repoRoot, [".remix/"]);
4424
4768
  } catch (err) {
4425
- await fs6.rm(repoRoot, { recursive: true, force: true }).catch(() => {
4769
+ await fs7.rm(repoRoot, { recursive: true, force: true }).catch(() => {
4426
4770
  });
4427
4771
  throw err;
4428
4772
  } finally {
4429
- await fs6.rm(bundleTempDir, { recursive: true, force: true });
4773
+ await fs7.rm(bundleTempDir, { recursive: true, force: true });
4430
4774
  }
4431
4775
  const branchName = await getCurrentBranch(repoRoot) ?? params.expectedBranchName?.trim() ?? null;
4432
4776
  const remoteUrl = normalizeGitRemote(params.expectedRemoteUrl ?? await getRemoteOriginUrl(repoRoot));
@@ -4704,16 +5048,16 @@ async function collabUpdateMemberRole(params) {
4704
5048
  }
4705
5049
 
4706
5050
  // src/application/collab/collabInit.ts
4707
- import fs11 from "fs/promises";
4708
- import path10 from "path";
5051
+ import fs12 from "fs/promises";
5052
+ import path11 from "path";
4709
5053
 
4710
5054
  // src/shared/hash.ts
4711
5055
  import crypto from "crypto";
4712
- import fs7 from "fs";
5056
+ import fs8 from "fs";
4713
5057
  async function sha256FileHex(filePath) {
4714
5058
  const hash = crypto.createHash("sha256");
4715
5059
  await new Promise((resolve, reject) => {
4716
- const stream = fs7.createReadStream(filePath);
5060
+ const stream = fs8.createReadStream(filePath);
4717
5061
  stream.on("data", (chunk) => hash.update(chunk));
4718
5062
  stream.on("error", reject);
4719
5063
  stream.on("end", () => resolve());
@@ -5024,11 +5368,11 @@ function branchBindingFromLane(lane, mode, fallback) {
5024
5368
  }
5025
5369
 
5026
5370
  // src/application/collab/collabInitAsync.ts
5027
- import { randomUUID as randomUUID4 } from "crypto";
5371
+ import { randomUUID as randomUUID5 } from "crypto";
5028
5372
 
5029
5373
  // src/infrastructure/collab/drainerLog.ts
5030
- import fs8 from "fs/promises";
5031
- import path8 from "path";
5374
+ import fs9 from "fs/promises";
5375
+ import path9 from "path";
5032
5376
  async function logDrainerEvent(jobId, event, fields = {}) {
5033
5377
  const entry = {
5034
5378
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5037,8 +5381,8 @@ async function logDrainerEvent(jobId, event, fields = {}) {
5037
5381
  ...fields
5038
5382
  };
5039
5383
  try {
5040
- await fs8.mkdir(getLogsRoot(), { recursive: true });
5041
- await fs8.appendFile(getDrainerLogPath(), `${JSON.stringify(entry)}
5384
+ await fs9.mkdir(getLogsRoot(), { recursive: true });
5385
+ await fs9.appendFile(getDrainerLogPath(), `${JSON.stringify(entry)}
5042
5386
  `, "utf8");
5043
5387
  } catch {
5044
5388
  }
@@ -5118,7 +5462,7 @@ async function clearFailedAsyncInitJobs(params) {
5118
5462
  }
5119
5463
  async function enqueueInitPostJob(params) {
5120
5464
  const job = await enqueueAsyncJob({
5121
- id: randomUUID4(),
5465
+ id: randomUUID5(),
5122
5466
  kind: "init_post",
5123
5467
  status: "queued",
5124
5468
  repoRoot: params.repoRoot,
@@ -5143,19 +5487,19 @@ async function enqueueInitPostJob(params) {
5143
5487
  }
5144
5488
 
5145
5489
  // src/application/collab/collabInitImport.ts
5146
- import fs10 from "fs/promises";
5147
- import path9 from "path";
5490
+ import fs11 from "fs/promises";
5491
+ import path10 from "path";
5148
5492
 
5149
5493
  // src/shared/upload.ts
5150
- import fs9 from "fs";
5494
+ import fs10 from "fs";
5151
5495
  import { PassThrough } from "stream";
5152
5496
  async function uploadPresigned(params) {
5153
- const stats = await fs9.promises.stat(params.filePath).catch(() => null);
5497
+ const stats = await fs10.promises.stat(params.filePath).catch(() => null);
5154
5498
  if (!stats || !stats.isFile()) {
5155
5499
  throw new RemixError("Upload file not found.", { exitCode: 2 });
5156
5500
  }
5157
5501
  const totalBytes = stats.size;
5158
- const fileStream = fs9.createReadStream(params.filePath);
5502
+ const fileStream = fs10.createReadStream(params.filePath);
5159
5503
  const pass = new PassThrough();
5160
5504
  let sentBytes = 0;
5161
5505
  fileStream.on("data", (chunk) => {
@@ -5182,8 +5526,8 @@ ${text}`.trim() || null
5182
5526
 
5183
5527
  // src/application/collab/collabInitImport.ts
5184
5528
  async function uploadInitBundle(params) {
5185
- const bundleName = params.bundleName ?? path9.basename(params.bundlePath);
5186
- const bundleSize = (await fs10.stat(params.bundlePath)).size;
5529
+ const bundleName = params.bundleName ?? path10.basename(params.bundlePath);
5530
+ const bundleSize = (await fs11.stat(params.bundlePath)).size;
5187
5531
  const presignResp = await params.api.presignImportUploadFirstParty({
5188
5532
  file: {
5189
5533
  name: bundleName,
@@ -5352,7 +5696,7 @@ async function collabInit(params) {
5352
5696
  appId: boundCurrentAppId2,
5353
5697
  dashboardUrl: buildDashboardAppUrl(boundCurrentAppId2),
5354
5698
  upstreamAppId: boundUpstreamAppId2,
5355
- bindingPath: path10.join(repoRoot, ".remix", "config.json"),
5699
+ bindingPath: path11.join(repoRoot, ".remix", "config.json"),
5356
5700
  repoRoot,
5357
5701
  repoFingerprint,
5358
5702
  bindingMode: defaultBranch && branchName !== defaultBranch ? "lane" : "explicit_root",
@@ -5623,7 +5967,7 @@ async function collabInit(params) {
5623
5967
  repoRoot,
5624
5968
  "repository.bundle"
5625
5969
  );
5626
- const tmpBundleDir = path10.dirname(bundleFilePath);
5970
+ const tmpBundleDir = path11.dirname(bundleFilePath);
5627
5971
  try {
5628
5972
  const bundleSha2 = await sha256FileHex(bundleFilePath);
5629
5973
  const uploadId2 = await uploadInitBundle({
@@ -5635,7 +5979,7 @@ async function collabInit(params) {
5635
5979
  const imported2 = await importUploadedInitBundle({
5636
5980
  api: params.api,
5637
5981
  uploadId: uploadId2,
5638
- appName: params.appName?.trim() || path10.basename(repoRoot),
5982
+ appName: params.appName?.trim() || path11.basename(repoRoot),
5639
5983
  profile: params.profile ?? void 0,
5640
5984
  branch: defaultBranch && branchName && branchName !== defaultBranch ? defaultBranch : currentBranch ?? void 0,
5641
5985
  remoteUrl,
@@ -5794,7 +6138,7 @@ async function collabInit(params) {
5794
6138
  ...warnings.length > 0 ? { warnings } : {}
5795
6139
  };
5796
6140
  } finally {
5797
- await fs11.rm(tmpBundleDir, { recursive: true, force: true }).catch(() => void 0);
6141
+ await fs12.rm(tmpBundleDir, { recursive: true, force: true }).catch(() => void 0);
5798
6142
  }
5799
6143
  }
5800
6144
  const { bundlePath, headCommitHash } = await createGitBundle(repoRoot, "repository.bundle");
@@ -5808,7 +6152,7 @@ async function collabInit(params) {
5808
6152
  const imported = await importUploadedInitBundle({
5809
6153
  api: params.api,
5810
6154
  uploadId,
5811
- appName: params.appName?.trim() || path10.basename(repoRoot),
6155
+ appName: params.appName?.trim() || path11.basename(repoRoot),
5812
6156
  path: params.path?.trim() || void 0,
5813
6157
  profile: params.profile ?? void 0,
5814
6158
  branch: defaultBranch && branchName && branchName !== defaultBranch ? defaultBranch : currentBranch ?? void 0,
@@ -6017,9 +6361,9 @@ async function collabList(params) {
6017
6361
  }
6018
6362
 
6019
6363
  // src/application/collab/collabReconcile.ts
6020
- import fs12 from "fs/promises";
6364
+ import fs13 from "fs/promises";
6021
6365
  import os5 from "os";
6022
- import path11 from "path";
6366
+ import path12 from "path";
6023
6367
  import { execa as execa2 } from "execa";
6024
6368
 
6025
6369
  // src/application/collab/pendingFinalize.ts
@@ -6187,12 +6531,12 @@ async function reconcileBothChanged(params) {
6187
6531
  const replay = await pollChangeStepReplay(params.api, binding.currentAppId, String(replayStart.id));
6188
6532
  const replayDiffResp = await params.api.getChangeStepReplayDiff(binding.currentAppId, replay.id);
6189
6533
  const replayDiff = unwrapResponseObject(replayDiffResp, "change step replay diff");
6190
- const tempRoot = await fs12.mkdtemp(path11.join(os5.tmpdir(), "remix-reconcile-"));
6534
+ const tempRoot = await fs13.mkdtemp(path12.join(os5.tmpdir(), "remix-reconcile-"));
6191
6535
  let serverHeadSnapshot = null;
6192
6536
  let mergedSnapshot = null;
6193
6537
  try {
6194
- const tempRepoRoot = path11.join(tempRoot, "repo");
6195
- await fs12.mkdir(tempRepoRoot, { recursive: true });
6538
+ const tempRepoRoot = path12.join(tempRoot, "repo");
6539
+ await fs13.mkdir(tempRepoRoot, { recursive: true });
6196
6540
  await execa2("git", ["init"], { cwd: tempRepoRoot, stderr: "ignore" });
6197
6541
  await materializeLocalSnapshot(baseline.lastSnapshotId, tempRepoRoot);
6198
6542
  if (shouldApplyServerDeltaBeforeReplay({ deltaStatus: delta.status, diff: delta.diff })) {
@@ -6214,7 +6558,7 @@ async function reconcileBothChanged(params) {
6214
6558
  branchName: binding.branchName
6215
6559
  });
6216
6560
  } finally {
6217
- await fs12.rm(tempRoot, { recursive: true, force: true }).catch(() => void 0);
6561
+ await fs13.rm(tempRoot, { recursive: true, force: true }).catch(() => void 0);
6218
6562
  }
6219
6563
  if (!serverHeadSnapshot || !mergedSnapshot) {
6220
6564
  throw new RemixError("Failed to materialize the reconciled local workspace.", { exitCode: 1 });
@@ -6256,15 +6600,58 @@ async function reconcileBothChanged(params) {
6256
6600
  lastServerHeadHash: appHead.headCommitHash,
6257
6601
  lastSeenLocalCommitHash: restoredSnapshot.localCommitHash
6258
6602
  });
6603
+ const reportResult = await reportAlignedCheckoutState({
6604
+ api: params.api,
6605
+ appId: binding.currentAppId,
6606
+ repoRoot: lockedRepoRoot,
6607
+ repoFingerprint: binding.repoFingerprint,
6608
+ laneId: binding.laneId,
6609
+ branch,
6610
+ baselineRevisionId: appHead.headRevisionId ?? null,
6611
+ serverRevisionId: appHead.headRevisionId ?? null,
6612
+ localHeadHash: restoredSnapshot.localCommitHash,
6613
+ serverHeadCommitId: appHead.headCommitId,
6614
+ serverHeadHash: appHead.headCommitHash,
6615
+ source: "reconcile_apply"
6616
+ });
6617
+ const eventResult = await recordLocalAlignmentEvent({
6618
+ api: params.api,
6619
+ appId: binding.currentAppId,
6620
+ eventKind: "reconcile_local",
6621
+ branch,
6622
+ collabLaneId: binding.laneId,
6623
+ baseRevisionId: baseline.lastServerRevisionId,
6624
+ targetRevisionId: appHead.headRevisionId ?? null,
6625
+ resultRevisionId: appHead.headRevisionId ?? null,
6626
+ baseHeadHash: baseline.lastServerHeadHash,
6627
+ targetHeadCommitId: appHead.headCommitId,
6628
+ targetHeadHash: appHead.headCommitHash,
6629
+ resultHeadHash: appHead.headCommitHash,
6630
+ changedFilesCount: diffResult.stats.changedFilesCount,
6631
+ insertions: diffResult.stats.insertions,
6632
+ deletions: diffResult.stats.deletions,
6633
+ localCommitCount: 0,
6634
+ summary: "Reconciled local checkout with Remix.",
6635
+ idempotencyKey: buildDeterministicIdempotencyKey({
6636
+ kind: "collab_reconcile_local_alignment_v1",
6637
+ appId: binding.currentAppId,
6638
+ laneId: binding.laneId,
6639
+ baseRevisionId: baseline.lastServerRevisionId,
6640
+ targetRevisionId: appHead.headRevisionId ?? null,
6641
+ baseHeadHash: baseline.lastServerHeadHash,
6642
+ targetHeadHash: appHead.headCommitHash,
6643
+ localDiffSha256: diffResult.diffSha256,
6644
+ replayId: replay.id
6645
+ })
6646
+ });
6647
+ const mergedWarnings = Array.from(/* @__PURE__ */ new Set([...delta.warnings, ...warnings, ...reportResult.warnings, ...eventResult.warnings]));
6259
6648
  return {
6260
6649
  ...preview,
6261
6650
  status: "reconciled",
6262
6651
  localHeadCommitHash: restoredSnapshot.localCommitHash,
6263
6652
  applied: workspaceChanged,
6264
6653
  dryRun: false,
6265
- ...warnings.length > 0 ? {
6266
- warnings: Array.from(/* @__PURE__ */ new Set([...delta.warnings, ...warnings]))
6267
- } : {}
6654
+ ...mergedWarnings.length > 0 ? { warnings: mergedWarnings } : {}
6268
6655
  };
6269
6656
  }
6270
6657
  );
@@ -6842,6 +7229,8 @@ async function collabStatus(params) {
6842
7229
  if ((status.remote.incomingOpenMergeRequestCount ?? 0) > 0 && status.recommendedAction === "no_action") {
6843
7230
  status.recommendedAction = "review_queue";
6844
7231
  }
7232
+ const reportResult = await reportCheckoutStateFromStatus({ api: params.api, status, source: "status" });
7233
+ for (const warning of reportResult.warnings) addWarning(status, warning);
6845
7234
  return status;
6846
7235
  }
6847
7236
 
@@ -6945,11 +7334,11 @@ async function collabView(params) {
6945
7334
  }
6946
7335
 
6947
7336
  // src/application/collab/collabAsyncProcessing.ts
6948
- import fs13 from "fs/promises";
6949
- import path12 from "path";
7337
+ import fs14 from "fs/promises";
7338
+ import path13 from "path";
6950
7339
 
6951
7340
  // src/application/collab/collabDependencies.ts
6952
- import { randomUUID as randomUUID5 } from "crypto";
7341
+ import { randomUUID as randomUUID6 } from "crypto";
6953
7342
  function isFullLocalStateRepository(value) {
6954
7343
  return typeof value.bindings?.getPath === "function" && typeof value.bindings.read === "function" && typeof value.bindings.readState === "function" && typeof value.bindings.write === "function" && typeof value.bindings.writeSnapshot === "function" && typeof value.baselines?.read === "function" && typeof value.baselines.write === "function" && typeof value.baselines.clear === "function" && typeof value.snapshots?.inspect === "function" && typeof value.snapshots.capture === "function" && typeof value.snapshots.read === "function" && typeof value.snapshots.materialize === "function" && typeof value.snapshots.restoreToWorktree === "function" && typeof value.snapshots.diff === "function" && typeof value.asyncJobs?.enqueue === "function" && typeof value.asyncJobs.read === "function" && typeof value.asyncJobs.list === "function" && typeof value.asyncJobs.listForRepo === "function" && typeof value.asyncJobs.findPending === "function" && typeof value.asyncJobs.findFailed === "function" && typeof value.asyncJobs.update === "function" && typeof value.asyncJobs.delete === "function" && typeof value.asyncJobs.claim === "function" && typeof value.asyncJobs.summarize === "function" && typeof value.asyncJobs.pruneTerminal === "function" && typeof value.asyncJobs.getBundleFilePath === "function" && typeof value.asyncJobs.await === "function" && typeof value.finalizeJobs?.enqueue === "function" && typeof value.finalizeJobs.read === "function" && typeof value.finalizeJobs.list === "function" && typeof value.finalizeJobs.update === "function" && typeof value.finalizeJobs.claim === "function" && typeof value.finalizeJobs.remove === "function" && typeof value.finalizeJobs.forget === "function" && typeof value.finalizeJobs.requeue === "function" && typeof value.finalizeJobs.heartbeatLock === "function" && typeof value.finalizeJobs.cleanStaleLocks === "function" && typeof value.finalizeJobs.prune === "function" && typeof value.finalizeJobs.summarize === "function";
6955
7344
  }
@@ -6964,7 +7353,7 @@ function createCollabDependencies(overrides = {}) {
6964
7353
  const localState3 = overrides.localState ? isFullLocalStateRepository(overrides.localState) ? overrides.localState : createLocalStateRepository(overrides.localState) : createLocalStateRepository();
6965
7354
  return {
6966
7355
  clock: { ...defaultClock, ...overrides.clock },
6967
- ids: { randomUUID: randomUUID5, ...overrides.ids },
7356
+ ids: { randomUUID: randomUUID6, ...overrides.ids },
6968
7357
  timers: { ...defaultTimers, ...overrides.timers },
6969
7358
  git: overrides.git ?? createSystemGitAdapter(),
6970
7359
  localState: localState3,
@@ -7086,10 +7475,10 @@ async function processInitJob(job, api) {
7086
7475
  try {
7087
7476
  await localState2.asyncJobs.update(job.id, { status: "submitting", error: null });
7088
7477
  await logDrainerWorkflowEvent(job.id, "claimed", { kind: "init" });
7089
- const bundleStat = await fs13.stat(job.payload.bundlePath);
7478
+ const bundleStat = await fs14.stat(job.payload.bundlePath);
7090
7479
  const presignResp = await api.presignImportUploadFirstParty({
7091
7480
  file: {
7092
- name: path12.basename(job.payload.bundlePath),
7481
+ name: path13.basename(job.payload.bundlePath),
7093
7482
  mimeType: "application/x-git-bundle",
7094
7483
  size: bundleStat.size,
7095
7484
  checksumSha256: job.payload.bundleSha256
@@ -7106,7 +7495,7 @@ async function processInitJob(job, api) {
7106
7495
  await localState2.asyncJobs.update(job.id, { status: "server_processing" });
7107
7496
  const importResp = await api.importFromUploadFirstParty({
7108
7497
  uploadId: String(presign.uploadId),
7109
- appName: job.payload.appName?.trim() || path12.basename(job.repoRoot),
7498
+ appName: job.payload.appName?.trim() || path13.basename(job.repoRoot),
7110
7499
  platform: "generic",
7111
7500
  isPublic: false,
7112
7501
  branch: job.payload.defaultBranch && job.branchName && job.branchName !== job.payload.defaultBranch ? job.payload.defaultBranch : job.branchName ?? void 0,
@@ -7312,7 +7701,7 @@ async function processInitPostJob(job, api) {
7312
7701
  if (outcome.status === "failed") {
7313
7702
  const bindingPath = localState2.bindings.getPath(job.repoRoot);
7314
7703
  try {
7315
- await fs13.unlink(bindingPath);
7704
+ await fs14.unlink(bindingPath);
7316
7705
  await logDrainerWorkflowEvent(job.id, "binding_cleared", {
7317
7706
  kind: "init_post",
7318
7707
  appId: job.payload.appId,
@@ -7364,9 +7753,9 @@ async function collabInitPostProcess(jobId, opts) {
7364
7753
  }
7365
7754
  async function acquireDrainerPidLock() {
7366
7755
  const pidPath = getDrainerPidPath();
7367
- await fs13.mkdir(path12.dirname(pidPath), { recursive: true });
7756
+ await fs14.mkdir(path13.dirname(pidPath), { recursive: true });
7368
7757
  try {
7369
- const existing = await fs13.readFile(pidPath, "utf8").catch(() => "");
7758
+ const existing = await fs14.readFile(pidPath, "utf8").catch(() => "");
7370
7759
  const existingPid = parseInt(existing.trim(), 10);
7371
7760
  if (Number.isFinite(existingPid) && existingPid > 0 && existingPid !== process.pid) {
7372
7761
  try {
@@ -7376,13 +7765,13 @@ async function acquireDrainerPidLock() {
7376
7765
  if (error?.code !== "ESRCH") return null;
7377
7766
  }
7378
7767
  }
7379
- await fs13.writeFile(pidPath, String(process.pid), "utf8");
7768
+ await fs14.writeFile(pidPath, String(process.pid), "utf8");
7380
7769
  return {
7381
7770
  release: async () => {
7382
7771
  try {
7383
- const current = (await fs13.readFile(pidPath, "utf8")).trim();
7772
+ const current = (await fs14.readFile(pidPath, "utf8")).trim();
7384
7773
  if (current === String(process.pid)) {
7385
- await fs13.unlink(pidPath).catch(() => void 0);
7774
+ await fs14.unlink(pidPath).catch(() => void 0);
7386
7775
  }
7387
7776
  } catch {
7388
7777
  }
@@ -732,6 +732,49 @@ type CollabApiClient = {
732
732
  defaultBranch?: string;
733
733
  dryRun?: boolean;
734
734
  }): Promise<unknown>;
735
+ reportLocalCheckoutState(appId: string, payload: {
736
+ checkoutId: string;
737
+ collabLaneId?: string;
738
+ branch?: string;
739
+ repoFingerprint?: string;
740
+ localState?: string;
741
+ recommendedAction: "pull" | "reconcile" | "await_finalize" | "record" | "no_action";
742
+ materializationStatus?: string;
743
+ alignmentRequired?: string;
744
+ baselineRevisionId?: string | null;
745
+ localRevisionId?: string | null;
746
+ serverRevisionId?: string | null;
747
+ localHeadHash?: string;
748
+ serverHeadCommitId?: string | null;
749
+ serverHeadHash?: string;
750
+ hasLocalChanges?: boolean;
751
+ hasUnrecordedChanges?: boolean;
752
+ pendingFinalizeCount?: number;
753
+ lastSeenAt?: string;
754
+ metadata?: Record<string, unknown>;
755
+ }): Promise<unknown>;
756
+ getLatestLocalCheckoutState(appId: string): Promise<unknown>;
757
+ recordLocalAlignmentEvent(appId: string, payload: {
758
+ eventKind: "sync_local" | "reconcile_local";
759
+ branch?: string;
760
+ collabLaneId?: string;
761
+ baseRevisionId?: string | null;
762
+ targetRevisionId?: string | null;
763
+ resultRevisionId?: string | null;
764
+ baseHeadCommitId?: string | null;
765
+ baseHeadHash?: string;
766
+ targetHeadCommitId?: string | null;
767
+ targetHeadHash?: string;
768
+ resultHeadCommitId?: string | null;
769
+ resultHeadHash?: string;
770
+ changedFilesCount?: number;
771
+ insertions?: number;
772
+ deletions?: number;
773
+ localCommitCount?: number;
774
+ summary?: string;
775
+ idempotencyKey?: string;
776
+ metadata?: Record<string, unknown>;
777
+ }): Promise<unknown>;
735
778
  };
736
779
 
737
780
  export type { AppProfileInput as A, ModelCall as B, CollabApiClient as C, OrganizationMemberRole as D, ProjectMemberRole as E, SyncLocalResponse as F, SyncUpstreamResponse as G, TurnUsageCaptureSource as H, InvitationScopeType as I, JsonObject as J, TurnUsageConfidence as K, TurnUsagePreviousPatch as L, MergeRequestQueue as M, OrganizationMember as O, ProjectMember as P, ReconcilePreflightResponse as R, ServerToolUsage as S, TurnUsage as T, TurnUsagePayload as a, CollabFinalizeTurnResult as b, CollabRecordingPreflight as c, CollabApproveMode as d, CollabApprovalStrategy as e, CollabApproveResult as f, MergeRequest as g, CollabMember as h, AppDeltaResponse as i, CollabStatus as j, MergeRequestReview as k, AppHeadResponse as l, AppMember as m, AppMemberRole as n, AppReconcileResponse as o, CollabFinalizeTurnAutoSync as p, CollabFinalizeTurnMode as q, CollabPendingFinalizeState as r, CollabPendingFinalizeSummary as s, CollabRecordingPreflightStatus as t, CollabRepoStateKind as u, CollabStatusBlockedReason as v, CollabStatusRecommendedAction as w, CollabTurn as x, MembershipScopeType as y, MergeRequestStatus as z };
package/dist/history.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { T as TurnUsage } from './contracts-BqC3Lj7W.js';
1
+ import { T as TurnUsage } from './contracts-DTeSJnoC.js';
2
2
 
3
3
  type TranscriptEvent = Record<string, unknown>;
4
4
  type ReadTranscriptResult = {
package/dist/index.d.ts CHANGED
@@ -4,4 +4,4 @@ export { S as SessionStore, a as StoredSession, W as WithRefreshLock, c as creat
4
4
  export { createDefaultRefreshLock, createLocalSessionStore, createSupabaseAuthHelpers, defaultSessionFilePath, isInvalidGrantError, isNetworkError } from './auth.js';
5
5
  export { AgentMemoryItem, AgentMemoryKind, AgentMemorySearchItem, AgentMemorySearchResponse, AgentMemorySummary, AgentMemoryTimelineResponse, ApiClient, AppContext, AppContextAccessPath, AppPreviewExpectedKind, AppPreviewProcessStatus, AppPreviewQuery, AppPreviewResponse, AppReconcileResponse, AppSandboxCommandRun, AppShareLinkSummary, Bundle, BundlePlatform, BundleStatus, ChangeStepDiffResponse, CreateAppShareLinkPayload, DetectProjectRuntimeTargetsPayload, ImportProjectRuntimeEnvPayload, InitiateBundleRequest, InvitationRecord, MergeRequest, MergeRequestReview, MergeRequestStatus, ProjectRuntimeEnvMetadata, ProjectRuntimeEnvScope, ProjectRuntimeTargetMetadata, ProjectRuntimeTargetScope, ProjectTrigger, ProjectTriggerPayload, ProjectTriggerScope, ProjectTriggerStep, ProjectTriggerStepPayload, ReconcilePreflightResponse, ResolveProjectRuntimeEnvForLocalPullResponse, RunAppRuntimeTargetPayload, RunAppSandboxCommandPayload, RunAppTriggerEventPayload, RunAppTriggerPayload, RuntimeCommandKind, RuntimeTargetKind, SetProjectRuntimeEnvPayload, SetProjectRuntimeTargetPayload, SyncLocalResponse, SyncUpstreamResponse, TriggerEventMetadata, TriggerEventSource, TriggerIntegrationStatus, TriggerMode, TriggerRun, TriggerStepRun, TriggerStepType, UpdateProjectTriggerPayload, createApiClient } from './api.js';
6
6
  import 'zod';
7
- import './contracts-BqC3Lj7W.js';
7
+ import './contracts-DTeSJnoC.js';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApiClient
3
- } from "./chunk-MNVBXTSV.js";
3
+ } from "./chunk-QIESQM7X.js";
4
4
  import {
5
5
  createDefaultRefreshLock,
6
6
  createLocalSessionStore,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remixhq/core",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Remix core library",
5
5
  "homepage": "https://github.com/RemixDotOne/remix-core",
6
6
  "license": "MIT",