@remixhq/mcp 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -74,6 +74,7 @@ var ERROR_CODES = {
74
74
  DIRTY_WORKTREE: "DIRTY_WORKTREE",
75
75
  DETACHED_HEAD: "DETACHED_HEAD",
76
76
  PREFERRED_BRANCH_MISMATCH: "PREFERRED_BRANCH_MISMATCH",
77
+ FAMILY_SELECTION_AMBIGUOUS: "FAMILY_SELECTION_AMBIGUOUS",
77
78
  MISSING_HEAD: "MISSING_HEAD",
78
79
  REPO_LOCK_HELD: "REPO_LOCK_HELD",
79
80
  REPO_LOCK_TIMEOUT: "REPO_LOCK_TIMEOUT",
@@ -210,6 +211,14 @@ function normalizeByMessage(err) {
210
211
  category: "local_state"
211
212
  });
212
213
  }
214
+ if (message.includes("Multiple canonical Remix families") || message.includes("family selection is ambiguous")) {
215
+ return makeNormalized({
216
+ code: ERROR_CODES.FAMILY_SELECTION_AMBIGUOUS,
217
+ message,
218
+ hint,
219
+ category: "local_state"
220
+ });
221
+ }
213
222
  if (message.includes("Failed to resolve local HEAD")) {
214
223
  return makeNormalized({
215
224
  code: ERROR_CODES.MISSING_HEAD,
@@ -343,14 +352,6 @@ function assertConfirm(confirm, operation) {
343
352
  if (confirm) return;
344
353
  throw createPolicyError(`${operation} requires explicit confirmation.`, "Pass confirm=true to run this tool.");
345
354
  }
346
- function assertDiffWithinLimit(policy, diff) {
347
- const sizeBytes = Buffer.byteLength(diff, "utf8");
348
- if (sizeBytes <= policy.maxDiffBytes) return;
349
- throw createPolicyError(
350
- "Diff exceeds the configured maximum size for Remix MCP.",
351
- `Configured limit=${policy.maxDiffBytes} bytes actual=${sizeBytes} bytes.`
352
- );
353
- }
354
355
 
355
356
  // src/bootstrap/context.ts
356
357
  function createServerContext(params) {
@@ -498,8 +499,6 @@ var finalizeTurnInputSchema = {
498
499
  ...commonRequestFieldsSchema,
499
500
  prompt: z2.string().trim().min(1),
500
501
  assistantResponse: z2.string().trim().min(1),
501
- diffSource: z2.enum(["worktree", "external"]).optional(),
502
- externalDiff: z2.string().optional(),
503
502
  sync: z2.boolean().optional(),
504
503
  allowBranchMismatch: z2.boolean().optional(),
505
504
  idempotencyKey: z2.string().trim().min(1).optional()
@@ -507,11 +506,17 @@ var finalizeTurnInputSchema = {
507
506
  var previewInputSchema = {
508
507
  ...commonRequestFieldsSchema
509
508
  };
509
+ var drainFinalizeQueueInputSchema = {
510
+ ...commonRequestFieldsSchema
511
+ };
510
512
  var applyInputSchema = {
511
513
  ...commonRequestFieldsSchema,
512
514
  confirm: z2.boolean(),
513
515
  allowBranchMismatch: z2.boolean().optional()
514
516
  };
517
+ var reAnchorInputSchema = {
518
+ ...applyInputSchema
519
+ };
515
520
  var requestMergeInputSchema = {
516
521
  ...commonRequestFieldsSchema
517
522
  };
@@ -621,7 +626,10 @@ var initDataSchema = z2.object({
621
626
  dashboardUrl: z2.string().url(),
622
627
  upstreamAppId: z2.string(),
623
628
  bindingPath: z2.string(),
624
- repoRoot: z2.string()
629
+ repoRoot: z2.string(),
630
+ bindingMode: z2.enum(["legacy", "lane", "explicit_root"]).optional(),
631
+ createdCanonicalFamily: z2.boolean().optional(),
632
+ baselineStatus: z2.enum(["seeded", "existing", "requires_re_anchor", "requires_sync"]).optional()
625
633
  });
626
634
  var listDataSchema = z2.object({
627
635
  apps: genericArraySchema,
@@ -644,19 +652,26 @@ var checkoutDataSchema = z2.object({
644
652
  repoRoot: z2.string()
645
653
  });
646
654
  var addDataSchema = z2.object({
647
- changeStep: genericRecordSchema,
648
- autoSync: genericRecordSchema
655
+ changeStep: genericRecordSchema
649
656
  });
650
657
  var recordTurnDataSchema = genericRecordSchema;
651
658
  var finalizeTurnDataSchema = z2.object({
652
659
  mode: z2.enum(["changed_turn", "no_diff_turn"]),
653
660
  idempotencyKey: z2.string().min(1),
661
+ queued: z2.boolean(),
662
+ jobId: z2.string().nullable(),
663
+ repoState: z2.string().nullable(),
654
664
  changeStep: genericRecordSchema.nullable(),
655
665
  collabTurn: genericRecordSchema.nullable(),
656
666
  autoSync: genericRecordSchema.nullable(),
657
667
  warnings: z2.array(z2.string())
658
668
  });
669
+ var drainFinalizeQueueDataSchema = z2.object({
670
+ processed: z2.number().int().nonnegative(),
671
+ results: z2.array(genericRecordSchema)
672
+ });
659
673
  var syncDataSchema = genericRecordSchema;
674
+ var reAnchorDataSchema = genericRecordSchema;
660
675
  var requestMergeDataSchema = genericRecordSchema;
661
676
  var mergeRequestQueueDataSchema = z2.object({
662
677
  queue: mergeRequestQueueSchema,
@@ -731,7 +746,9 @@ var checkoutSuccessSchema = makeSuccessSchema(checkoutDataSchema);
731
746
  var addSuccessSchema = makeSuccessSchema(addDataSchema);
732
747
  var recordTurnSuccessSchema = makeSuccessSchema(recordTurnDataSchema);
733
748
  var finalizeTurnSuccessSchema = makeSuccessSchema(finalizeTurnDataSchema);
749
+ var drainFinalizeQueueSuccessSchema = makeSuccessSchema(drainFinalizeQueueDataSchema);
734
750
  var syncSuccessSchema = makeSuccessSchema(syncDataSchema);
751
+ var reAnchorSuccessSchema = makeSuccessSchema(reAnchorDataSchema);
735
752
  var requestMergeSuccessSchema = makeSuccessSchema(requestMergeDataSchema);
736
753
  var mergeRequestQueueSuccessSchema = makeSuccessSchema(mergeRequestQueueDataSchema);
737
754
  var viewMergeRequestSuccessSchema = makeSuccessSchema(viewMergeRequestDataSchema);
@@ -749,17 +766,18 @@ var accessDebugSuccessSchema = makeSuccessSchema(accessDebugDataSchema);
749
766
  var updateMemberRoleSuccessSchema = makeSuccessSchema(updateMemberRoleDataSchema);
750
767
 
751
768
  // src/domain/coreAdapter.ts
769
+ import { spawn } from "child_process";
752
770
  import {
753
771
  collabList as coreCollabList,
754
772
  collabListMembers as coreCollabListMembers,
755
773
  collabUpdateMemberRole as coreCollabUpdateMemberRole,
756
- collabAdd as coreCollabAdd,
774
+ drainPendingFinalizeQueue as coreDrainPendingFinalizeQueue,
757
775
  collabFinalizeTurn as coreCollabFinalizeTurn,
758
- collabRecordTurn as coreCollabRecordTurn,
759
776
  collabApprove as coreCollabApprove,
760
777
  collabCheckout as coreCollabCheckout,
761
778
  collabListMergeRequests as coreCollabListMergeRequests,
762
779
  collabInit as coreCollabInit,
780
+ collabReAnchor as coreCollabReAnchor,
763
781
  collabInvite as coreCollabInvite,
764
782
  collabReconcile as coreCollabReconcile,
765
783
  collabReject as coreCollabReject,
@@ -770,10 +788,13 @@ import {
770
788
  collabSyncUpstream as coreCollabSyncUpstream,
771
789
  collabView as coreCollabView
772
790
  } from "@remixhq/core/collab";
773
- import { findGitRoot, getHeadCommitHash, listUntrackedFiles } from "@remixhq/core/repo";
791
+ import { findGitRoot } from "@remixhq/core/repo";
774
792
  function getRiskLevel(status) {
775
793
  if (status.recommendedAction === "reconcile") return "high";
776
- if (status.recommendedAction === "sync" || status.remote.incomingOpenMergeRequestCount) return "medium";
794
+ if (status.recommendedAction === "choose_family" || status.recommendedAction === "await_finalize") return "medium";
795
+ if (status.recommendedAction === "pull" || status.recommendedAction === "re_anchor" || status.remote.incomingOpenMergeRequestCount) {
796
+ return "medium";
797
+ }
777
798
  if (status.repo.branchMismatch || !status.repo.isGitRepo || !status.binding.isBound || !status.repo.worktree.isClean) return "medium";
778
799
  return "low";
779
800
  }
@@ -786,12 +807,22 @@ function getRecommendedNextActions(status) {
786
807
  switch (status.recommendedAction) {
787
808
  case "init":
788
809
  return ["Run remix_collab_init to bind the repository to Remix before using any Remix collaboration mutation flow."];
789
- case "sync":
790
- return ["Run remix_collab_sync_preview, then remix_collab_sync_apply if the preview is acceptable. Use this instead of raw git pull or rebase for bound-repo alignment."];
810
+ case "pull":
811
+ return ["Run remix_collab_sync_preview, then remix_collab_sync_apply if the preview is acceptable. This pulls the server delta into the local working tree without rewriting local git history."];
812
+ case "re_anchor":
813
+ return ["Run remix_collab_re_anchor_preview, then remix_collab_re_anchor_apply to re-anchor the lane after manual Git/GitHub history movement."];
791
814
  case "reconcile":
792
- return ["Run remix_collab_reconcile_preview before attempting remix_collab_reconcile_apply. Reconcile is the explicit Remix recovery path when fast-forward sync is no longer possible."];
815
+ return ["Run remix_collab_reconcile_preview before attempting remix_collab_reconcile_apply. Reconcile now routes through the explicit Remix recovery/re-anchor flow for diverged state."];
816
+ case "await_finalize":
817
+ return [
818
+ "Run remix_collab_drain_finalize_queue before merge-related or recovery flows. finalize_turn is queued only until the local finalize queue is drained."
819
+ ];
793
820
  case "review_queue":
794
821
  return ["Run remix_collab_review_queue to inspect reviewable merge requests instead of using local git merge flows."];
822
+ case "choose_family":
823
+ return [
824
+ "This checkout is ambiguous across multiple canonical Remix families. Continue from a checkout already bound to the intended family, or run remix_collab_init with forceNew=true to create and bind a new canonical family."
825
+ ];
795
826
  default:
796
827
  return [];
797
828
  }
@@ -819,6 +850,17 @@ function truncateText(value, maxChars) {
819
850
  originalChars: value.length
820
851
  };
821
852
  }
853
+ function spawnFinalizeQueueDrainer() {
854
+ const entrypoint = process.argv[1];
855
+ if (!entrypoint) return false;
856
+ const child = spawn(process.execPath, [...process.execArgv, entrypoint, "--drain-finalize-queue"], {
857
+ detached: true,
858
+ stdio: "ignore",
859
+ env: process.env
860
+ });
861
+ child.unref();
862
+ return true;
863
+ }
822
864
  async function getStatus(params) {
823
865
  const api = params.includeRemote ? await createCollabApiClient() : null;
824
866
  const status = await coreCollabStatus({
@@ -849,7 +891,11 @@ async function initCollab(params) {
849
891
  return {
850
892
  data: result,
851
893
  warnings: collectResultWarnings(result),
852
- recommendedNextActions: ["Run remix_collab_status to inspect sync, reconcile, and merge-request readiness before mutating bound-repo state."],
894
+ recommendedNextActions: result.baselineStatus === "requires_re_anchor" ? [
895
+ "Run remix_collab_re_anchor_preview, then remix_collab_re_anchor_apply to anchor this checkout before recording turns."
896
+ ] : result.baselineStatus === "requires_sync" ? [
897
+ "Run remix_collab_sync_preview, then remix_collab_sync_apply to pull the server delta and create the first local baseline for this checkout."
898
+ ] : ["Run remix_collab_status to inspect sync, reconcile, and merge-request readiness before mutating bound-repo state."],
853
899
  logContext: {
854
900
  repoRoot: result.repoRoot,
855
901
  appId: result.appId
@@ -919,23 +965,44 @@ async function finalizeCollabTurn(params) {
919
965
  cwd: params.cwd,
920
966
  prompt: params.prompt,
921
967
  assistantResponse: params.assistantResponse,
922
- diff: params.externalDiff ?? null,
923
- diffSource: params.diffSource,
924
968
  sync: params.sync,
925
969
  allowBranchMismatch: params.allowBranchMismatch ?? false,
926
970
  idempotencyKey: params.idempotencyKey ?? null,
927
971
  actor: params.agent
928
972
  });
973
+ if (result.queued) {
974
+ if (!spawnFinalizeQueueDrainer()) {
975
+ await coreDrainPendingFinalizeQueue({ api });
976
+ }
977
+ }
929
978
  return {
930
979
  data: result,
931
980
  warnings: result.warnings,
932
- recommendedNextActions: [],
981
+ recommendedNextActions: result.queued ? ["Run remix_collab_drain_finalize_queue before merge-related flows if you need this queued turn recorded immediately."] : [],
933
982
  logContext: {
934
983
  repoRoot,
935
984
  appId: result.changeStep?.appId ?? result.collabTurn?.appId ?? null
936
985
  }
937
986
  };
938
987
  }
988
+ async function drainFinalizeQueue(params) {
989
+ const api = await createCollabApiClient();
990
+ const repoRoot = await findGitRoot(params.cwd);
991
+ const results = await coreDrainPendingFinalizeQueue({ api });
992
+ const warnings = results.flatMap((result) => collectResultWarnings(result));
993
+ return {
994
+ data: {
995
+ processed: results.length,
996
+ results
997
+ },
998
+ warnings,
999
+ recommendedNextActions: [],
1000
+ logContext: {
1001
+ repoRoot,
1002
+ appId: null
1003
+ }
1004
+ };
1005
+ }
939
1006
  async function syncCollab(params) {
940
1007
  const api = await createCollabApiClient();
941
1008
  const result = await coreCollabSync({
@@ -947,12 +1014,33 @@ async function syncCollab(params) {
947
1014
  return {
948
1015
  data: result,
949
1016
  warnings: collectResultWarnings(result),
950
- recommendedNextActions: params.dryRun ? ["Run remix_collab_sync_apply with confirm=true to apply this fast-forward update instead of using raw git pull or rebase."] : [],
1017
+ recommendedNextActions: params.dryRun ? result.status === "delta_ready" ? ["Run remix_collab_sync_apply with confirm=true to apply this server delta into the local working tree."] : result.status === "base_unknown" ? [
1018
+ "Direct pull is unavailable because Remix cannot diff from the last acknowledged server head.",
1019
+ "Run remix_collab_reconcile_preview next to inspect recovery options before applying any recovery flow."
1020
+ ] : [] : [],
951
1021
  logContext: {
952
1022
  repoRoot: result.repoRoot
953
1023
  }
954
1024
  };
955
1025
  }
1026
+ async function reAnchor(params) {
1027
+ const api = await createCollabApiClient();
1028
+ const result = await coreCollabReAnchor({
1029
+ api,
1030
+ cwd: params.cwd,
1031
+ dryRun: params.dryRun,
1032
+ allowBranchMismatch: params.allowBranchMismatch ?? false
1033
+ });
1034
+ return {
1035
+ data: result,
1036
+ warnings: collectWarnings(result.warnings),
1037
+ recommendedNextActions: params.dryRun ? ["Run remix_collab_re_anchor_apply with confirm=true to re-anchor this lane after manual Git/GitHub history movement."] : [],
1038
+ logContext: {
1039
+ repoRoot: result.repoRoot,
1040
+ appId: result.currentAppId
1041
+ }
1042
+ };
1043
+ }
956
1044
  async function requestMerge(params) {
957
1045
  const api = await createCollabApiClient();
958
1046
  const result = await coreCollabRequestMerge({
@@ -1126,8 +1214,8 @@ async function reconcile(params) {
1126
1214
  return {
1127
1215
  data: result,
1128
1216
  warnings: collectWarnings(result.warnings),
1129
- recommendedNextActions: params.dryRun ? ["Run remix_collab_reconcile_apply with confirm=true only if the preview is acceptable. Do not replace this with raw git history-rewrite commands."] : [],
1130
- risks: params.dryRun ? ["Reconcile apply rewrites local history and creates a backup branch."] : [],
1217
+ recommendedNextActions: params.dryRun ? ["Run remix_collab_reconcile_apply with confirm=true only if the preview is acceptable. This is the explicit Remix recovery flow for diverged state."] : [],
1218
+ risks: params.dryRun ? ["Reconcile may upload local history to re-anchor the server lane before future recording continues."] : [],
1131
1219
  logContext: {
1132
1220
  repoRoot: result.repoRoot ?? null
1133
1221
  }
@@ -1703,9 +1791,6 @@ function buildSuccessEnvelope(tool, requestId, result) {
1703
1791
  };
1704
1792
  }
1705
1793
  function deriveErrorRisks(tool, normalized) {
1706
- if (isFinalizeTurnLocalSyncFailure(tool, normalized)) {
1707
- return ["The change step succeeded remotely, but the local repository may need manual recovery or a follow-up sync."];
1708
- }
1709
1794
  if (normalized.code === "DESTRUCTIVE_OPERATION_BLOCKED") {
1710
1795
  return ["A policy guard blocked a potentially destructive or state-mutating operation."];
1711
1796
  }
@@ -1717,16 +1802,9 @@ function deriveErrorRisks(tool, normalized) {
1717
1802
  }
1718
1803
  return [];
1719
1804
  }
1720
- function isFinalizeTurnLocalSyncFailure(tool, normalized) {
1721
- return tool === "remix_collab_finalize_turn" && normalized.message === "Change step succeeded remotely, but automatic local sync failed.";
1722
- }
1723
1805
  function buildErrorEnvelope(tool, requestId, error) {
1724
1806
  const normalized = normalizeToolError(error);
1725
- const recommendedNextActions = isFinalizeTurnLocalSyncFailure(tool, normalized) ? [
1726
- "Run `remix_collab_status` to confirm the bound repo state before attempting recovery.",
1727
- "Run `remix_collab_sync_preview` next, then `remix_collab_sync_apply` with `confirm=true` if the preview looks correct.",
1728
- "Inspect `error.hint` for any preserved diff backup path before retrying local recovery, and do not rerun `remix_collab_finalize_turn` immediately."
1729
- ] : normalized.code === "AUTH_REQUIRED" ? ["Set COMERGE_ACCESS_TOKEN, then retry the tool call."] : normalized.code === "REPO_LOCK_TIMEOUT" ? ["Wait for the active Remix mutation to finish, then retry the tool call."] : normalized.code === "REPO_STATE_CHANGED_DURING_OPERATION" ? ["Review local repository changes, then rerun the tool once the worktree is stable."] : normalized.code === "PREFERRED_BRANCH_MISMATCH" ? ["Switch to the repository's preferred Remix branch, or rerun with allowBranchMismatch=true if intentional."] : [];
1807
+ const recommendedNextActions = normalized.code === "AUTH_REQUIRED" ? ["Set COMERGE_ACCESS_TOKEN, then retry the tool call."] : normalized.code === "REPO_LOCK_TIMEOUT" ? ["Wait for the active Remix mutation to finish, then retry the tool call."] : normalized.code === "REPO_STATE_CHANGED_DURING_OPERATION" ? ["Review local repository changes, then rerun the tool once the worktree is stable."] : normalized.code === "PREFERRED_BRANCH_MISMATCH" ? ["Switch to the repository's preferred Remix branch, or rerun with allowBranchMismatch=true if intentional."] : [];
1730
1808
  return {
1731
1809
  schemaVersion: SCHEMA_VERSION,
1732
1810
  ok: false,
@@ -1872,7 +1950,7 @@ function registerCollabTools(server, context) {
1872
1950
  });
1873
1951
  registerTool(server, context, {
1874
1952
  name: "remix_collab_finalize_turn",
1875
- description: "Primary turn recorder for the current bound repository. Call this exactly once before the final response; it records a changed turn when the worktree has a diff, records a no-diff turn when it does not, and can accept an explicit external diff when needed.",
1953
+ description: "Primary turn recorder for the current bound repository. Call this exactly once before the final response; it captures the current boundary locally and queues remote processing. Queued only: no remote change step exists yet until the finalize queue is drained.",
1876
1954
  access: "local_write",
1877
1955
  inputSchema: finalizeTurnInputSchema,
1878
1956
  outputSchema: finalizeTurnSuccessSchema,
@@ -1880,15 +1958,10 @@ function registerCollabTools(server, context) {
1880
1958
  run: async (args) => {
1881
1959
  const input = z3.object(finalizeTurnInputSchema).parse(args);
1882
1960
  const cwd = resolvePolicyCwd(context.policy, input.cwd);
1883
- if ((input.diffSource ?? "worktree") === "external" || typeof input.externalDiff === "string") {
1884
- assertDiffWithinLimit(context.policy, input.externalDiff ?? "");
1885
- }
1886
1961
  return finalizeCollabTurn({
1887
1962
  cwd,
1888
1963
  prompt: input.prompt,
1889
1964
  assistantResponse: input.assistantResponse,
1890
- diffSource: input.diffSource,
1891
- externalDiff: input.externalDiff,
1892
1965
  sync: input.sync,
1893
1966
  allowBranchMismatch: input.allowBranchMismatch ?? false,
1894
1967
  idempotencyKey: input.idempotencyKey,
@@ -1896,9 +1969,22 @@ function registerCollabTools(server, context) {
1896
1969
  });
1897
1970
  }
1898
1971
  });
1972
+ registerTool(server, context, {
1973
+ name: "remix_collab_drain_finalize_queue",
1974
+ description: "Drain the local finalize queue and record queued finalize_turn jobs immediately. Use this before request-merge, reconcile, or other flows that require the remote change step to exist already.",
1975
+ access: "local_write",
1976
+ inputSchema: drainFinalizeQueueInputSchema,
1977
+ outputSchema: drainFinalizeQueueSuccessSchema,
1978
+ annotations: getAnnotations("local_write", { idempotent: true }),
1979
+ run: async (args) => {
1980
+ const input = z3.object(drainFinalizeQueueInputSchema).parse(args);
1981
+ const cwd = resolvePolicyCwd(context.policy, input.cwd);
1982
+ return drainFinalizeQueue({ cwd });
1983
+ }
1984
+ });
1899
1985
  registerTool(server, context, {
1900
1986
  name: "remix_collab_sync_preview",
1901
- description: "Preview whether the current bound repository can be fast-forward synced to the Remix app state. Use this instead of raw git pull or rebase for bound-repo alignment.",
1987
+ description: "Preview whether the current bound repository can pull the server delta into the working tree. Use this instead of raw git pull or rebase for bound-repo alignment.",
1902
1988
  access: "read",
1903
1989
  inputSchema: previewInputSchema,
1904
1990
  outputSchema: syncSuccessSchema,
@@ -1910,7 +1996,7 @@ function registerCollabTools(server, context) {
1910
1996
  });
1911
1997
  registerTool(server, context, {
1912
1998
  name: "remix_collab_sync_apply",
1913
- description: "Fast-forward sync the current bound repository to the Remix app state. This is the Remix-native replacement for raw git pull or rebase in a bound repo.",
1999
+ description: "Pull the server delta into the local working tree without moving local git history. This is the Remix-native replacement for raw git pull or rebase in a bound repo.",
1914
2000
  access: "local_write",
1915
2001
  inputSchema: applyInputSchema,
1916
2002
  outputSchema: syncSuccessSchema,
@@ -1921,6 +2007,31 @@ function registerCollabTools(server, context) {
1921
2007
  return syncCollab({ cwd, dryRun: false, allowBranchMismatch: input.allowBranchMismatch ?? false });
1922
2008
  }
1923
2009
  });
2010
+ registerTool(server, context, {
2011
+ name: "remix_collab_re_anchor_preview",
2012
+ description: "Preview whether the current checkout needs an explicit re-anchor to adopt or recover external Git/GitHub history.",
2013
+ access: "read",
2014
+ inputSchema: previewInputSchema,
2015
+ outputSchema: reAnchorSuccessSchema,
2016
+ run: async (args) => {
2017
+ const input = z3.object(previewInputSchema).parse(args);
2018
+ const cwd = resolvePolicyCwd(context.policy, input.cwd);
2019
+ return reAnchor({ cwd, dryRun: true });
2020
+ }
2021
+ });
2022
+ registerTool(server, context, {
2023
+ name: "remix_collab_re_anchor_apply",
2024
+ description: "Explicitly re-anchor the current lane to adopted or recovered external Git/GitHub history, without rewriting the local checkout afterward.",
2025
+ access: "local_write",
2026
+ inputSchema: reAnchorInputSchema,
2027
+ outputSchema: reAnchorSuccessSchema,
2028
+ run: async (args) => {
2029
+ const input = z3.object(reAnchorInputSchema).parse(args);
2030
+ assertConfirm(input.confirm, "remix_collab_re_anchor_apply");
2031
+ const cwd = resolvePolicyCwd(context.policy, input.cwd);
2032
+ return reAnchor({ cwd, dryRun: false, allowBranchMismatch: input.allowBranchMismatch ?? false });
2033
+ }
2034
+ });
1924
2035
  registerTool(server, context, {
1925
2036
  name: "remix_collab_request_merge",
1926
2037
  description: "Open a prompt-backed Remix merge request from the current bound repository to its upstream app instead of merging locally with raw git.",
@@ -2061,7 +2172,7 @@ function registerCollabTools(server, context) {
2061
2172
  });
2062
2173
  registerTool(server, context, {
2063
2174
  name: "remix_collab_reconcile_preview",
2064
- description: "Preview reconcile readiness when the local repository cannot be fast-forward synced. Use this before any explicit Remix history-repair workflow.",
2175
+ description: "Preview the explicit Remix recovery flow when local and server state diverged beyond a simple pull.",
2065
2176
  access: "read",
2066
2177
  inputSchema: previewInputSchema,
2067
2178
  outputSchema: reconcileSuccessSchema,
@@ -2073,7 +2184,7 @@ function registerCollabTools(server, context) {
2073
2184
  });
2074
2185
  registerTool(server, context, {
2075
2186
  name: "remix_collab_reconcile_apply",
2076
- description: "Reconcile divergent local history against the bound Remix app and update the local checkout. This is the explicit Remix recovery path for divergent history.",
2187
+ description: "Run the explicit Remix recovery flow for diverged local/server state.",
2077
2188
  access: "local_write",
2078
2189
  inputSchema: applyInputSchema,
2079
2190
  outputSchema: reconcileSuccessSchema,
@@ -3198,6 +3309,19 @@ import { z as z9 } from "zod";
3198
3309
  // src/contracts/ops.ts
3199
3310
  import { z as z8 } from "zod";
3200
3311
  var genericRecordSchema4 = z8.record(z8.string(), z8.unknown());
3312
+ var appJobKindSchema = z8.enum([
3313
+ "fork",
3314
+ "edit",
3315
+ "bundle",
3316
+ "import_github",
3317
+ "import_upload",
3318
+ "merge",
3319
+ "revert",
3320
+ "change_step",
3321
+ "reconcile",
3322
+ "change_step_replay"
3323
+ ]);
3324
+ var appJobStatusSchema = z8.enum(["pending", "enqueued", "processing", "succeeded", "failed", "cancelled"]);
3201
3325
  var appScopedInputSchema = {
3202
3326
  ...commonRequestFieldsSchema,
3203
3327
  appId: z8.string().trim().min(1).optional()
@@ -3207,6 +3331,13 @@ var editQueueInputSchema = {
3207
3331
  limit: z8.number().int().positive().max(100).optional(),
3208
3332
  offset: z8.number().int().nonnegative().optional()
3209
3333
  };
3334
+ var appJobQueueInputSchema = {
3335
+ ...appScopedInputSchema,
3336
+ limit: z8.number().int().positive().max(100).optional(),
3337
+ offset: z8.number().int().nonnegative().optional(),
3338
+ kind: z8.array(appJobKindSchema).min(1).optional(),
3339
+ status: z8.array(appJobStatusSchema).min(1).optional()
3340
+ };
3210
3341
  var bundleInputSchema = {
3211
3342
  ...appScopedInputSchema,
3212
3343
  bundleId: z8.string().trim().min(1)
@@ -3239,6 +3370,7 @@ var agentRunEventsInputSchema = {
3239
3370
  };
3240
3371
  var appOverviewSuccessSchema = makeSuccessSchema(genericRecordSchema4);
3241
3372
  var editQueueSuccessSchema = makeSuccessSchema(genericRecordSchema4);
3373
+ var appJobQueueSuccessSchema = makeSuccessSchema(genericRecordSchema4);
3242
3374
  var bundleSuccessSchema = makeSuccessSchema(genericRecordSchema4);
3243
3375
  var timelineSuccessSchema = makeSuccessSchema(genericRecordSchema4);
3244
3376
  var agentRunsSuccessSchema = makeSuccessSchema(genericRecordSchema4);
@@ -3272,7 +3404,7 @@ async function getAppOverview(params) {
3272
3404
  data,
3273
3405
  warnings: [],
3274
3406
  recommendedNextActions: [
3275
- "Use `remix_ops_list_timeline`, `remix_ops_list_agent_runs`, `remix_ops_get_edit_queue`, or `remix_ops_get_sandbox_status` for the next operational drill-down on this app."
3407
+ "Use `remix_ops_list_timeline`, `remix_ops_list_agent_runs`, `remix_ops_get_edit_queue`, `remix_ops_list_app_job_queue`, or `remix_ops_get_sandbox_status` for the next operational drill-down on this app."
3276
3408
  ],
3277
3409
  logContext: target
3278
3410
  };
@@ -3295,6 +3427,42 @@ async function getEditQueue(params) {
3295
3427
  logContext: target
3296
3428
  };
3297
3429
  }
3430
+ async function listAppJobQueue(params) {
3431
+ const api = await createApiClient();
3432
+ const target = await resolveAppTarget(api, params);
3433
+ const data = unwrapResponseObject(
3434
+ await api.listAppJobQueue(target.appId, {
3435
+ limit: params.limit,
3436
+ offset: params.offset,
3437
+ kind: params.kind,
3438
+ status: params.status
3439
+ }),
3440
+ "app job queue"
3441
+ );
3442
+ const pageInfo = typeof data.pageInfo === "object" && data.pageInfo ? data.pageInfo : null;
3443
+ const items = Array.isArray(data.items) ? data.items : [];
3444
+ const activeBlockingKinds = /* @__PURE__ */ new Set(["merge", "change_step", "change_step_replay", "reconcile", "revert"]);
3445
+ const hasBlockingJobs = items.some((item) => {
3446
+ if (!item || typeof item !== "object") return false;
3447
+ const record = item;
3448
+ return typeof record.kind === "string" && activeBlockingKinds.has(record.kind) && (typeof record.status !== "string" || ["pending", "enqueued", "processing"].includes(record.status));
3449
+ });
3450
+ const recommendedNextActions = [];
3451
+ if (hasBlockingJobs) {
3452
+ recommendedNextActions.push(
3453
+ "Inspect the returned active workflow jobs before concluding that merge, replay, reconcile, or revert work is stuck. This surface reflects backend app-scoped queue state, not local finalize state."
3454
+ );
3455
+ }
3456
+ if (pageInfo?.hasMore === true && typeof pageInfo.limit === "number" && typeof pageInfo.offset === "number") {
3457
+ recommendedNextActions.push(`Pass offset=${pageInfo.offset + pageInfo.limit} to load the next app job queue page.`);
3458
+ }
3459
+ return {
3460
+ data,
3461
+ warnings: [],
3462
+ recommendedNextActions,
3463
+ logContext: target
3464
+ };
3465
+ }
3298
3466
  async function getBundle(params) {
3299
3467
  const api = await createApiClient();
3300
3468
  const target = await resolveAppTarget(api, params);
@@ -3521,6 +3689,25 @@ function registerOpsTools(server, context) {
3521
3689
  });
3522
3690
  }
3523
3691
  });
3692
+ registerTool4(server, context, {
3693
+ name: "remix_ops_list_app_job_queue",
3694
+ description: "Inspect bounded app-scoped workflow queue jobs for one app, including active backend work such as merge, replay, reconcile, revert, bundle, or import processing.",
3695
+ access: "read",
3696
+ inputSchema: appJobQueueInputSchema,
3697
+ outputSchema: appJobQueueSuccessSchema,
3698
+ run: async (args) => {
3699
+ const input = z9.object(appJobQueueInputSchema).parse(args);
3700
+ const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
3701
+ return listAppJobQueue({
3702
+ appId: input.appId,
3703
+ cwd,
3704
+ limit: input.limit,
3705
+ offset: input.offset,
3706
+ kind: input.kind,
3707
+ status: input.status
3708
+ });
3709
+ }
3710
+ });
3524
3711
  registerTool4(server, context, {
3525
3712
  name: "remix_ops_get_bundle",
3526
3713
  description: "Inspect one app bundle by id without downloading the artifact payload.",