@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/cli.js CHANGED
@@ -2,10 +2,7 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { createRequire } from "module";
5
-
6
- // src/server.ts
7
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { drainPendingFinalizeQueue } from "@remixhq/core/collab";
9
6
 
10
7
  // src/domain/apiClient.ts
11
8
  import { createApiClient as createCoreApiClient, resolveConfig as resolveConfig2 } from "@remixhq/core";
@@ -42,6 +39,10 @@ async function createApiClient() {
42
39
  });
43
40
  }
44
41
 
42
+ // src/server.ts
43
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
44
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
45
+
45
46
  // src/observability/logger.ts
46
47
  function createLogger() {
47
48
  return {
@@ -77,6 +78,7 @@ var ERROR_CODES = {
77
78
  DIRTY_WORKTREE: "DIRTY_WORKTREE",
78
79
  DETACHED_HEAD: "DETACHED_HEAD",
79
80
  PREFERRED_BRANCH_MISMATCH: "PREFERRED_BRANCH_MISMATCH",
81
+ FAMILY_SELECTION_AMBIGUOUS: "FAMILY_SELECTION_AMBIGUOUS",
80
82
  MISSING_HEAD: "MISSING_HEAD",
81
83
  REPO_LOCK_HELD: "REPO_LOCK_HELD",
82
84
  REPO_LOCK_TIMEOUT: "REPO_LOCK_TIMEOUT",
@@ -213,6 +215,14 @@ function normalizeByMessage(err) {
213
215
  category: "local_state"
214
216
  });
215
217
  }
218
+ if (message.includes("Multiple canonical Remix families") || message.includes("family selection is ambiguous")) {
219
+ return makeNormalized({
220
+ code: ERROR_CODES.FAMILY_SELECTION_AMBIGUOUS,
221
+ message,
222
+ hint,
223
+ category: "local_state"
224
+ });
225
+ }
216
226
  if (message.includes("Failed to resolve local HEAD")) {
217
227
  return makeNormalized({
218
228
  code: ERROR_CODES.MISSING_HEAD,
@@ -346,14 +356,6 @@ function assertConfirm(confirm, operation) {
346
356
  if (confirm) return;
347
357
  throw createPolicyError(`${operation} requires explicit confirmation.`, "Pass confirm=true to run this tool.");
348
358
  }
349
- function assertDiffWithinLimit(policy, diff) {
350
- const sizeBytes = Buffer.byteLength(diff, "utf8");
351
- if (sizeBytes <= policy.maxDiffBytes) return;
352
- throw createPolicyError(
353
- "Diff exceeds the configured maximum size for Remix MCP.",
354
- `Configured limit=${policy.maxDiffBytes} bytes actual=${sizeBytes} bytes.`
355
- );
356
- }
357
359
 
358
360
  // src/bootstrap/context.ts
359
361
  function createServerContext(params) {
@@ -501,8 +503,6 @@ var finalizeTurnInputSchema = {
501
503
  ...commonRequestFieldsSchema,
502
504
  prompt: z2.string().trim().min(1),
503
505
  assistantResponse: z2.string().trim().min(1),
504
- diffSource: z2.enum(["worktree", "external"]).optional(),
505
- externalDiff: z2.string().optional(),
506
506
  sync: z2.boolean().optional(),
507
507
  allowBranchMismatch: z2.boolean().optional(),
508
508
  idempotencyKey: z2.string().trim().min(1).optional()
@@ -510,11 +510,17 @@ var finalizeTurnInputSchema = {
510
510
  var previewInputSchema = {
511
511
  ...commonRequestFieldsSchema
512
512
  };
513
+ var drainFinalizeQueueInputSchema = {
514
+ ...commonRequestFieldsSchema
515
+ };
513
516
  var applyInputSchema = {
514
517
  ...commonRequestFieldsSchema,
515
518
  confirm: z2.boolean(),
516
519
  allowBranchMismatch: z2.boolean().optional()
517
520
  };
521
+ var reAnchorInputSchema = {
522
+ ...applyInputSchema
523
+ };
518
524
  var requestMergeInputSchema = {
519
525
  ...commonRequestFieldsSchema
520
526
  };
@@ -624,7 +630,10 @@ var initDataSchema = z2.object({
624
630
  dashboardUrl: z2.string().url(),
625
631
  upstreamAppId: z2.string(),
626
632
  bindingPath: z2.string(),
627
- repoRoot: z2.string()
633
+ repoRoot: z2.string(),
634
+ bindingMode: z2.enum(["legacy", "lane", "explicit_root"]).optional(),
635
+ createdCanonicalFamily: z2.boolean().optional(),
636
+ baselineStatus: z2.enum(["seeded", "existing", "requires_re_anchor", "requires_sync"]).optional()
628
637
  });
629
638
  var listDataSchema = z2.object({
630
639
  apps: genericArraySchema,
@@ -647,19 +656,26 @@ var checkoutDataSchema = z2.object({
647
656
  repoRoot: z2.string()
648
657
  });
649
658
  var addDataSchema = z2.object({
650
- changeStep: genericRecordSchema,
651
- autoSync: genericRecordSchema
659
+ changeStep: genericRecordSchema
652
660
  });
653
661
  var recordTurnDataSchema = genericRecordSchema;
654
662
  var finalizeTurnDataSchema = z2.object({
655
663
  mode: z2.enum(["changed_turn", "no_diff_turn"]),
656
664
  idempotencyKey: z2.string().min(1),
665
+ queued: z2.boolean(),
666
+ jobId: z2.string().nullable(),
667
+ repoState: z2.string().nullable(),
657
668
  changeStep: genericRecordSchema.nullable(),
658
669
  collabTurn: genericRecordSchema.nullable(),
659
670
  autoSync: genericRecordSchema.nullable(),
660
671
  warnings: z2.array(z2.string())
661
672
  });
673
+ var drainFinalizeQueueDataSchema = z2.object({
674
+ processed: z2.number().int().nonnegative(),
675
+ results: z2.array(genericRecordSchema)
676
+ });
662
677
  var syncDataSchema = genericRecordSchema;
678
+ var reAnchorDataSchema = genericRecordSchema;
663
679
  var requestMergeDataSchema = genericRecordSchema;
664
680
  var mergeRequestQueueDataSchema = z2.object({
665
681
  queue: mergeRequestQueueSchema,
@@ -734,7 +750,9 @@ var checkoutSuccessSchema = makeSuccessSchema(checkoutDataSchema);
734
750
  var addSuccessSchema = makeSuccessSchema(addDataSchema);
735
751
  var recordTurnSuccessSchema = makeSuccessSchema(recordTurnDataSchema);
736
752
  var finalizeTurnSuccessSchema = makeSuccessSchema(finalizeTurnDataSchema);
753
+ var drainFinalizeQueueSuccessSchema = makeSuccessSchema(drainFinalizeQueueDataSchema);
737
754
  var syncSuccessSchema = makeSuccessSchema(syncDataSchema);
755
+ var reAnchorSuccessSchema = makeSuccessSchema(reAnchorDataSchema);
738
756
  var requestMergeSuccessSchema = makeSuccessSchema(requestMergeDataSchema);
739
757
  var mergeRequestQueueSuccessSchema = makeSuccessSchema(mergeRequestQueueDataSchema);
740
758
  var viewMergeRequestSuccessSchema = makeSuccessSchema(viewMergeRequestDataSchema);
@@ -752,17 +770,18 @@ var accessDebugSuccessSchema = makeSuccessSchema(accessDebugDataSchema);
752
770
  var updateMemberRoleSuccessSchema = makeSuccessSchema(updateMemberRoleDataSchema);
753
771
 
754
772
  // src/domain/coreAdapter.ts
773
+ import { spawn } from "child_process";
755
774
  import {
756
775
  collabList as coreCollabList,
757
776
  collabListMembers as coreCollabListMembers,
758
777
  collabUpdateMemberRole as coreCollabUpdateMemberRole,
759
- collabAdd as coreCollabAdd,
778
+ drainPendingFinalizeQueue as coreDrainPendingFinalizeQueue,
760
779
  collabFinalizeTurn as coreCollabFinalizeTurn,
761
- collabRecordTurn as coreCollabRecordTurn,
762
780
  collabApprove as coreCollabApprove,
763
781
  collabCheckout as coreCollabCheckout,
764
782
  collabListMergeRequests as coreCollabListMergeRequests,
765
783
  collabInit as coreCollabInit,
784
+ collabReAnchor as coreCollabReAnchor,
766
785
  collabInvite as coreCollabInvite,
767
786
  collabReconcile as coreCollabReconcile,
768
787
  collabReject as coreCollabReject,
@@ -773,10 +792,13 @@ import {
773
792
  collabSyncUpstream as coreCollabSyncUpstream,
774
793
  collabView as coreCollabView
775
794
  } from "@remixhq/core/collab";
776
- import { findGitRoot, getHeadCommitHash, listUntrackedFiles } from "@remixhq/core/repo";
795
+ import { findGitRoot } from "@remixhq/core/repo";
777
796
  function getRiskLevel(status) {
778
797
  if (status.recommendedAction === "reconcile") return "high";
779
- if (status.recommendedAction === "sync" || status.remote.incomingOpenMergeRequestCount) return "medium";
798
+ if (status.recommendedAction === "choose_family" || status.recommendedAction === "await_finalize") return "medium";
799
+ if (status.recommendedAction === "pull" || status.recommendedAction === "re_anchor" || status.remote.incomingOpenMergeRequestCount) {
800
+ return "medium";
801
+ }
780
802
  if (status.repo.branchMismatch || !status.repo.isGitRepo || !status.binding.isBound || !status.repo.worktree.isClean) return "medium";
781
803
  return "low";
782
804
  }
@@ -789,12 +811,22 @@ function getRecommendedNextActions(status) {
789
811
  switch (status.recommendedAction) {
790
812
  case "init":
791
813
  return ["Run remix_collab_init to bind the repository to Remix before using any Remix collaboration mutation flow."];
792
- case "sync":
793
- 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."];
814
+ case "pull":
815
+ 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."];
816
+ case "re_anchor":
817
+ return ["Run remix_collab_re_anchor_preview, then remix_collab_re_anchor_apply to re-anchor the lane after manual Git/GitHub history movement."];
794
818
  case "reconcile":
795
- 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."];
819
+ 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."];
820
+ case "await_finalize":
821
+ return [
822
+ "Run remix_collab_drain_finalize_queue before merge-related or recovery flows. finalize_turn is queued only until the local finalize queue is drained."
823
+ ];
796
824
  case "review_queue":
797
825
  return ["Run remix_collab_review_queue to inspect reviewable merge requests instead of using local git merge flows."];
826
+ case "choose_family":
827
+ return [
828
+ "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."
829
+ ];
798
830
  default:
799
831
  return [];
800
832
  }
@@ -822,6 +854,17 @@ function truncateText(value, maxChars) {
822
854
  originalChars: value.length
823
855
  };
824
856
  }
857
+ function spawnFinalizeQueueDrainer() {
858
+ const entrypoint = process.argv[1];
859
+ if (!entrypoint) return false;
860
+ const child = spawn(process.execPath, [...process.execArgv, entrypoint, "--drain-finalize-queue"], {
861
+ detached: true,
862
+ stdio: "ignore",
863
+ env: process.env
864
+ });
865
+ child.unref();
866
+ return true;
867
+ }
825
868
  async function getStatus(params) {
826
869
  const api = params.includeRemote ? await createCollabApiClient() : null;
827
870
  const status = await coreCollabStatus({
@@ -852,7 +895,11 @@ async function initCollab(params) {
852
895
  return {
853
896
  data: result,
854
897
  warnings: collectResultWarnings(result),
855
- recommendedNextActions: ["Run remix_collab_status to inspect sync, reconcile, and merge-request readiness before mutating bound-repo state."],
898
+ recommendedNextActions: result.baselineStatus === "requires_re_anchor" ? [
899
+ "Run remix_collab_re_anchor_preview, then remix_collab_re_anchor_apply to anchor this checkout before recording turns."
900
+ ] : result.baselineStatus === "requires_sync" ? [
901
+ "Run remix_collab_sync_preview, then remix_collab_sync_apply to pull the server delta and create the first local baseline for this checkout."
902
+ ] : ["Run remix_collab_status to inspect sync, reconcile, and merge-request readiness before mutating bound-repo state."],
856
903
  logContext: {
857
904
  repoRoot: result.repoRoot,
858
905
  appId: result.appId
@@ -922,23 +969,44 @@ async function finalizeCollabTurn(params) {
922
969
  cwd: params.cwd,
923
970
  prompt: params.prompt,
924
971
  assistantResponse: params.assistantResponse,
925
- diff: params.externalDiff ?? null,
926
- diffSource: params.diffSource,
927
972
  sync: params.sync,
928
973
  allowBranchMismatch: params.allowBranchMismatch ?? false,
929
974
  idempotencyKey: params.idempotencyKey ?? null,
930
975
  actor: params.agent
931
976
  });
977
+ if (result.queued) {
978
+ if (!spawnFinalizeQueueDrainer()) {
979
+ await coreDrainPendingFinalizeQueue({ api });
980
+ }
981
+ }
932
982
  return {
933
983
  data: result,
934
984
  warnings: result.warnings,
935
- recommendedNextActions: [],
985
+ recommendedNextActions: result.queued ? ["Run remix_collab_drain_finalize_queue before merge-related flows if you need this queued turn recorded immediately."] : [],
936
986
  logContext: {
937
987
  repoRoot,
938
988
  appId: result.changeStep?.appId ?? result.collabTurn?.appId ?? null
939
989
  }
940
990
  };
941
991
  }
992
+ async function drainFinalizeQueue(params) {
993
+ const api = await createCollabApiClient();
994
+ const repoRoot = await findGitRoot(params.cwd);
995
+ const results = await coreDrainPendingFinalizeQueue({ api });
996
+ const warnings = results.flatMap((result) => collectResultWarnings(result));
997
+ return {
998
+ data: {
999
+ processed: results.length,
1000
+ results
1001
+ },
1002
+ warnings,
1003
+ recommendedNextActions: [],
1004
+ logContext: {
1005
+ repoRoot,
1006
+ appId: null
1007
+ }
1008
+ };
1009
+ }
942
1010
  async function syncCollab(params) {
943
1011
  const api = await createCollabApiClient();
944
1012
  const result = await coreCollabSync({
@@ -950,12 +1018,33 @@ async function syncCollab(params) {
950
1018
  return {
951
1019
  data: result,
952
1020
  warnings: collectResultWarnings(result),
953
- 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."] : [],
1021
+ 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" ? [
1022
+ "Direct pull is unavailable because Remix cannot diff from the last acknowledged server head.",
1023
+ "Run remix_collab_reconcile_preview next to inspect recovery options before applying any recovery flow."
1024
+ ] : [] : [],
954
1025
  logContext: {
955
1026
  repoRoot: result.repoRoot
956
1027
  }
957
1028
  };
958
1029
  }
1030
+ async function reAnchor(params) {
1031
+ const api = await createCollabApiClient();
1032
+ const result = await coreCollabReAnchor({
1033
+ api,
1034
+ cwd: params.cwd,
1035
+ dryRun: params.dryRun,
1036
+ allowBranchMismatch: params.allowBranchMismatch ?? false
1037
+ });
1038
+ return {
1039
+ data: result,
1040
+ warnings: collectWarnings(result.warnings),
1041
+ recommendedNextActions: params.dryRun ? ["Run remix_collab_re_anchor_apply with confirm=true to re-anchor this lane after manual Git/GitHub history movement."] : [],
1042
+ logContext: {
1043
+ repoRoot: result.repoRoot,
1044
+ appId: result.currentAppId
1045
+ }
1046
+ };
1047
+ }
959
1048
  async function requestMerge(params) {
960
1049
  const api = await createCollabApiClient();
961
1050
  const result = await coreCollabRequestMerge({
@@ -1129,8 +1218,8 @@ async function reconcile(params) {
1129
1218
  return {
1130
1219
  data: result,
1131
1220
  warnings: collectWarnings(result.warnings),
1132
- 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."] : [],
1133
- risks: params.dryRun ? ["Reconcile apply rewrites local history and creates a backup branch."] : [],
1221
+ 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."] : [],
1222
+ risks: params.dryRun ? ["Reconcile may upload local history to re-anchor the server lane before future recording continues."] : [],
1134
1223
  logContext: {
1135
1224
  repoRoot: result.repoRoot ?? null
1136
1225
  }
@@ -1706,9 +1795,6 @@ function buildSuccessEnvelope(tool, requestId, result) {
1706
1795
  };
1707
1796
  }
1708
1797
  function deriveErrorRisks(tool, normalized) {
1709
- if (isFinalizeTurnLocalSyncFailure(tool, normalized)) {
1710
- return ["The change step succeeded remotely, but the local repository may need manual recovery or a follow-up sync."];
1711
- }
1712
1798
  if (normalized.code === "DESTRUCTIVE_OPERATION_BLOCKED") {
1713
1799
  return ["A policy guard blocked a potentially destructive or state-mutating operation."];
1714
1800
  }
@@ -1720,16 +1806,9 @@ function deriveErrorRisks(tool, normalized) {
1720
1806
  }
1721
1807
  return [];
1722
1808
  }
1723
- function isFinalizeTurnLocalSyncFailure(tool, normalized) {
1724
- return tool === "remix_collab_finalize_turn" && normalized.message === "Change step succeeded remotely, but automatic local sync failed.";
1725
- }
1726
1809
  function buildErrorEnvelope(tool, requestId, error) {
1727
1810
  const normalized = normalizeToolError(error);
1728
- const recommendedNextActions = isFinalizeTurnLocalSyncFailure(tool, normalized) ? [
1729
- "Run `remix_collab_status` to confirm the bound repo state before attempting recovery.",
1730
- "Run `remix_collab_sync_preview` next, then `remix_collab_sync_apply` with `confirm=true` if the preview looks correct.",
1731
- "Inspect `error.hint` for any preserved diff backup path before retrying local recovery, and do not rerun `remix_collab_finalize_turn` immediately."
1732
- ] : 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."] : [];
1811
+ 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."] : [];
1733
1812
  return {
1734
1813
  schemaVersion: SCHEMA_VERSION,
1735
1814
  ok: false,
@@ -1875,7 +1954,7 @@ function registerCollabTools(server, context) {
1875
1954
  });
1876
1955
  registerTool(server, context, {
1877
1956
  name: "remix_collab_finalize_turn",
1878
- 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.",
1957
+ 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.",
1879
1958
  access: "local_write",
1880
1959
  inputSchema: finalizeTurnInputSchema,
1881
1960
  outputSchema: finalizeTurnSuccessSchema,
@@ -1883,15 +1962,10 @@ function registerCollabTools(server, context) {
1883
1962
  run: async (args) => {
1884
1963
  const input = z3.object(finalizeTurnInputSchema).parse(args);
1885
1964
  const cwd = resolvePolicyCwd(context.policy, input.cwd);
1886
- if ((input.diffSource ?? "worktree") === "external" || typeof input.externalDiff === "string") {
1887
- assertDiffWithinLimit(context.policy, input.externalDiff ?? "");
1888
- }
1889
1965
  return finalizeCollabTurn({
1890
1966
  cwd,
1891
1967
  prompt: input.prompt,
1892
1968
  assistantResponse: input.assistantResponse,
1893
- diffSource: input.diffSource,
1894
- externalDiff: input.externalDiff,
1895
1969
  sync: input.sync,
1896
1970
  allowBranchMismatch: input.allowBranchMismatch ?? false,
1897
1971
  idempotencyKey: input.idempotencyKey,
@@ -1899,9 +1973,22 @@ function registerCollabTools(server, context) {
1899
1973
  });
1900
1974
  }
1901
1975
  });
1976
+ registerTool(server, context, {
1977
+ name: "remix_collab_drain_finalize_queue",
1978
+ 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.",
1979
+ access: "local_write",
1980
+ inputSchema: drainFinalizeQueueInputSchema,
1981
+ outputSchema: drainFinalizeQueueSuccessSchema,
1982
+ annotations: getAnnotations("local_write", { idempotent: true }),
1983
+ run: async (args) => {
1984
+ const input = z3.object(drainFinalizeQueueInputSchema).parse(args);
1985
+ const cwd = resolvePolicyCwd(context.policy, input.cwd);
1986
+ return drainFinalizeQueue({ cwd });
1987
+ }
1988
+ });
1902
1989
  registerTool(server, context, {
1903
1990
  name: "remix_collab_sync_preview",
1904
- 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.",
1991
+ 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.",
1905
1992
  access: "read",
1906
1993
  inputSchema: previewInputSchema,
1907
1994
  outputSchema: syncSuccessSchema,
@@ -1913,7 +2000,7 @@ function registerCollabTools(server, context) {
1913
2000
  });
1914
2001
  registerTool(server, context, {
1915
2002
  name: "remix_collab_sync_apply",
1916
- 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.",
2003
+ 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.",
1917
2004
  access: "local_write",
1918
2005
  inputSchema: applyInputSchema,
1919
2006
  outputSchema: syncSuccessSchema,
@@ -1924,6 +2011,31 @@ function registerCollabTools(server, context) {
1924
2011
  return syncCollab({ cwd, dryRun: false, allowBranchMismatch: input.allowBranchMismatch ?? false });
1925
2012
  }
1926
2013
  });
2014
+ registerTool(server, context, {
2015
+ name: "remix_collab_re_anchor_preview",
2016
+ description: "Preview whether the current checkout needs an explicit re-anchor to adopt or recover external Git/GitHub history.",
2017
+ access: "read",
2018
+ inputSchema: previewInputSchema,
2019
+ outputSchema: reAnchorSuccessSchema,
2020
+ run: async (args) => {
2021
+ const input = z3.object(previewInputSchema).parse(args);
2022
+ const cwd = resolvePolicyCwd(context.policy, input.cwd);
2023
+ return reAnchor({ cwd, dryRun: true });
2024
+ }
2025
+ });
2026
+ registerTool(server, context, {
2027
+ name: "remix_collab_re_anchor_apply",
2028
+ description: "Explicitly re-anchor the current lane to adopted or recovered external Git/GitHub history, without rewriting the local checkout afterward.",
2029
+ access: "local_write",
2030
+ inputSchema: reAnchorInputSchema,
2031
+ outputSchema: reAnchorSuccessSchema,
2032
+ run: async (args) => {
2033
+ const input = z3.object(reAnchorInputSchema).parse(args);
2034
+ assertConfirm(input.confirm, "remix_collab_re_anchor_apply");
2035
+ const cwd = resolvePolicyCwd(context.policy, input.cwd);
2036
+ return reAnchor({ cwd, dryRun: false, allowBranchMismatch: input.allowBranchMismatch ?? false });
2037
+ }
2038
+ });
1927
2039
  registerTool(server, context, {
1928
2040
  name: "remix_collab_request_merge",
1929
2041
  description: "Open a prompt-backed Remix merge request from the current bound repository to its upstream app instead of merging locally with raw git.",
@@ -2064,7 +2176,7 @@ function registerCollabTools(server, context) {
2064
2176
  });
2065
2177
  registerTool(server, context, {
2066
2178
  name: "remix_collab_reconcile_preview",
2067
- description: "Preview reconcile readiness when the local repository cannot be fast-forward synced. Use this before any explicit Remix history-repair workflow.",
2179
+ description: "Preview the explicit Remix recovery flow when local and server state diverged beyond a simple pull.",
2068
2180
  access: "read",
2069
2181
  inputSchema: previewInputSchema,
2070
2182
  outputSchema: reconcileSuccessSchema,
@@ -2076,7 +2188,7 @@ function registerCollabTools(server, context) {
2076
2188
  });
2077
2189
  registerTool(server, context, {
2078
2190
  name: "remix_collab_reconcile_apply",
2079
- 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.",
2191
+ description: "Run the explicit Remix recovery flow for diverged local/server state.",
2080
2192
  access: "local_write",
2081
2193
  inputSchema: applyInputSchema,
2082
2194
  outputSchema: reconcileSuccessSchema,
@@ -3201,6 +3313,19 @@ import { z as z9 } from "zod";
3201
3313
  // src/contracts/ops.ts
3202
3314
  import { z as z8 } from "zod";
3203
3315
  var genericRecordSchema4 = z8.record(z8.string(), z8.unknown());
3316
+ var appJobKindSchema = z8.enum([
3317
+ "fork",
3318
+ "edit",
3319
+ "bundle",
3320
+ "import_github",
3321
+ "import_upload",
3322
+ "merge",
3323
+ "revert",
3324
+ "change_step",
3325
+ "reconcile",
3326
+ "change_step_replay"
3327
+ ]);
3328
+ var appJobStatusSchema = z8.enum(["pending", "enqueued", "processing", "succeeded", "failed", "cancelled"]);
3204
3329
  var appScopedInputSchema = {
3205
3330
  ...commonRequestFieldsSchema,
3206
3331
  appId: z8.string().trim().min(1).optional()
@@ -3210,6 +3335,13 @@ var editQueueInputSchema = {
3210
3335
  limit: z8.number().int().positive().max(100).optional(),
3211
3336
  offset: z8.number().int().nonnegative().optional()
3212
3337
  };
3338
+ var appJobQueueInputSchema = {
3339
+ ...appScopedInputSchema,
3340
+ limit: z8.number().int().positive().max(100).optional(),
3341
+ offset: z8.number().int().nonnegative().optional(),
3342
+ kind: z8.array(appJobKindSchema).min(1).optional(),
3343
+ status: z8.array(appJobStatusSchema).min(1).optional()
3344
+ };
3213
3345
  var bundleInputSchema = {
3214
3346
  ...appScopedInputSchema,
3215
3347
  bundleId: z8.string().trim().min(1)
@@ -3242,6 +3374,7 @@ var agentRunEventsInputSchema = {
3242
3374
  };
3243
3375
  var appOverviewSuccessSchema = makeSuccessSchema(genericRecordSchema4);
3244
3376
  var editQueueSuccessSchema = makeSuccessSchema(genericRecordSchema4);
3377
+ var appJobQueueSuccessSchema = makeSuccessSchema(genericRecordSchema4);
3245
3378
  var bundleSuccessSchema = makeSuccessSchema(genericRecordSchema4);
3246
3379
  var timelineSuccessSchema = makeSuccessSchema(genericRecordSchema4);
3247
3380
  var agentRunsSuccessSchema = makeSuccessSchema(genericRecordSchema4);
@@ -3275,7 +3408,7 @@ async function getAppOverview(params) {
3275
3408
  data,
3276
3409
  warnings: [],
3277
3410
  recommendedNextActions: [
3278
- "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."
3411
+ "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."
3279
3412
  ],
3280
3413
  logContext: target
3281
3414
  };
@@ -3298,6 +3431,42 @@ async function getEditQueue(params) {
3298
3431
  logContext: target
3299
3432
  };
3300
3433
  }
3434
+ async function listAppJobQueue(params) {
3435
+ const api = await createApiClient();
3436
+ const target = await resolveAppTarget(api, params);
3437
+ const data = unwrapResponseObject(
3438
+ await api.listAppJobQueue(target.appId, {
3439
+ limit: params.limit,
3440
+ offset: params.offset,
3441
+ kind: params.kind,
3442
+ status: params.status
3443
+ }),
3444
+ "app job queue"
3445
+ );
3446
+ const pageInfo = typeof data.pageInfo === "object" && data.pageInfo ? data.pageInfo : null;
3447
+ const items = Array.isArray(data.items) ? data.items : [];
3448
+ const activeBlockingKinds = /* @__PURE__ */ new Set(["merge", "change_step", "change_step_replay", "reconcile", "revert"]);
3449
+ const hasBlockingJobs = items.some((item) => {
3450
+ if (!item || typeof item !== "object") return false;
3451
+ const record = item;
3452
+ return typeof record.kind === "string" && activeBlockingKinds.has(record.kind) && (typeof record.status !== "string" || ["pending", "enqueued", "processing"].includes(record.status));
3453
+ });
3454
+ const recommendedNextActions = [];
3455
+ if (hasBlockingJobs) {
3456
+ recommendedNextActions.push(
3457
+ "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."
3458
+ );
3459
+ }
3460
+ if (pageInfo?.hasMore === true && typeof pageInfo.limit === "number" && typeof pageInfo.offset === "number") {
3461
+ recommendedNextActions.push(`Pass offset=${pageInfo.offset + pageInfo.limit} to load the next app job queue page.`);
3462
+ }
3463
+ return {
3464
+ data,
3465
+ warnings: [],
3466
+ recommendedNextActions,
3467
+ logContext: target
3468
+ };
3469
+ }
3301
3470
  async function getBundle(params) {
3302
3471
  const api = await createApiClient();
3303
3472
  const target = await resolveAppTarget(api, params);
@@ -3524,6 +3693,25 @@ function registerOpsTools(server, context) {
3524
3693
  });
3525
3694
  }
3526
3695
  });
3696
+ registerTool4(server, context, {
3697
+ name: "remix_ops_list_app_job_queue",
3698
+ 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.",
3699
+ access: "read",
3700
+ inputSchema: appJobQueueInputSchema,
3701
+ outputSchema: appJobQueueSuccessSchema,
3702
+ run: async (args) => {
3703
+ const input = z9.object(appJobQueueInputSchema).parse(args);
3704
+ const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
3705
+ return listAppJobQueue({
3706
+ appId: input.appId,
3707
+ cwd,
3708
+ limit: input.limit,
3709
+ offset: input.offset,
3710
+ kind: input.kind,
3711
+ status: input.status
3712
+ });
3713
+ }
3714
+ });
3527
3715
  registerTool4(server, context, {
3528
3716
  name: "remix_ops_get_bundle",
3529
3717
  description: "Inspect one app bundle by id without downloading the artifact payload.",
@@ -3654,6 +3842,11 @@ async function startStdioServer(params) {
3654
3842
  var require2 = createRequire(import.meta.url);
3655
3843
  var { version } = require2("../package.json");
3656
3844
  async function main() {
3845
+ if (process.argv.includes("--drain-finalize-queue")) {
3846
+ const api = await createCollabApiClient();
3847
+ await drainPendingFinalizeQueue({ api });
3848
+ return;
3849
+ }
3657
3850
  await startStdioServer({ version });
3658
3851
  }
3659
3852
  main().catch((error) => {