@remixhq/mcp 0.1.12 → 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
@@ -352,14 +352,6 @@ function assertConfirm(confirm, operation) {
352
352
  if (confirm) return;
353
353
  throw createPolicyError(`${operation} requires explicit confirmation.`, "Pass confirm=true to run this tool.");
354
354
  }
355
- function assertDiffWithinLimit(policy, diff) {
356
- const sizeBytes = Buffer.byteLength(diff, "utf8");
357
- if (sizeBytes <= policy.maxDiffBytes) return;
358
- throw createPolicyError(
359
- "Diff exceeds the configured maximum size for Remix MCP.",
360
- `Configured limit=${policy.maxDiffBytes} bytes actual=${sizeBytes} bytes.`
361
- );
362
- }
363
355
 
364
356
  // src/bootstrap/context.ts
365
357
  function createServerContext(params) {
@@ -507,8 +499,6 @@ var finalizeTurnInputSchema = {
507
499
  ...commonRequestFieldsSchema,
508
500
  prompt: z2.string().trim().min(1),
509
501
  assistantResponse: z2.string().trim().min(1),
510
- diffSource: z2.enum(["worktree", "external"]).optional(),
511
- externalDiff: z2.string().optional(),
512
502
  sync: z2.boolean().optional(),
513
503
  allowBranchMismatch: z2.boolean().optional(),
514
504
  idempotencyKey: z2.string().trim().min(1).optional()
@@ -516,11 +506,17 @@ var finalizeTurnInputSchema = {
516
506
  var previewInputSchema = {
517
507
  ...commonRequestFieldsSchema
518
508
  };
509
+ var drainFinalizeQueueInputSchema = {
510
+ ...commonRequestFieldsSchema
511
+ };
519
512
  var applyInputSchema = {
520
513
  ...commonRequestFieldsSchema,
521
514
  confirm: z2.boolean(),
522
515
  allowBranchMismatch: z2.boolean().optional()
523
516
  };
517
+ var reAnchorInputSchema = {
518
+ ...applyInputSchema
519
+ };
524
520
  var requestMergeInputSchema = {
525
521
  ...commonRequestFieldsSchema
526
522
  };
@@ -632,7 +628,8 @@ var initDataSchema = z2.object({
632
628
  bindingPath: z2.string(),
633
629
  repoRoot: z2.string(),
634
630
  bindingMode: z2.enum(["legacy", "lane", "explicit_root"]).optional(),
635
- createdCanonicalFamily: z2.boolean().optional()
631
+ createdCanonicalFamily: z2.boolean().optional(),
632
+ baselineStatus: z2.enum(["seeded", "existing", "requires_re_anchor", "requires_sync"]).optional()
636
633
  });
637
634
  var listDataSchema = z2.object({
638
635
  apps: genericArraySchema,
@@ -655,19 +652,26 @@ var checkoutDataSchema = z2.object({
655
652
  repoRoot: z2.string()
656
653
  });
657
654
  var addDataSchema = z2.object({
658
- changeStep: genericRecordSchema,
659
- autoSync: genericRecordSchema
655
+ changeStep: genericRecordSchema
660
656
  });
661
657
  var recordTurnDataSchema = genericRecordSchema;
662
658
  var finalizeTurnDataSchema = z2.object({
663
659
  mode: z2.enum(["changed_turn", "no_diff_turn"]),
664
660
  idempotencyKey: z2.string().min(1),
661
+ queued: z2.boolean(),
662
+ jobId: z2.string().nullable(),
663
+ repoState: z2.string().nullable(),
665
664
  changeStep: genericRecordSchema.nullable(),
666
665
  collabTurn: genericRecordSchema.nullable(),
667
666
  autoSync: genericRecordSchema.nullable(),
668
667
  warnings: z2.array(z2.string())
669
668
  });
669
+ var drainFinalizeQueueDataSchema = z2.object({
670
+ processed: z2.number().int().nonnegative(),
671
+ results: z2.array(genericRecordSchema)
672
+ });
670
673
  var syncDataSchema = genericRecordSchema;
674
+ var reAnchorDataSchema = genericRecordSchema;
671
675
  var requestMergeDataSchema = genericRecordSchema;
672
676
  var mergeRequestQueueDataSchema = z2.object({
673
677
  queue: mergeRequestQueueSchema,
@@ -742,7 +746,9 @@ var checkoutSuccessSchema = makeSuccessSchema(checkoutDataSchema);
742
746
  var addSuccessSchema = makeSuccessSchema(addDataSchema);
743
747
  var recordTurnSuccessSchema = makeSuccessSchema(recordTurnDataSchema);
744
748
  var finalizeTurnSuccessSchema = makeSuccessSchema(finalizeTurnDataSchema);
749
+ var drainFinalizeQueueSuccessSchema = makeSuccessSchema(drainFinalizeQueueDataSchema);
745
750
  var syncSuccessSchema = makeSuccessSchema(syncDataSchema);
751
+ var reAnchorSuccessSchema = makeSuccessSchema(reAnchorDataSchema);
746
752
  var requestMergeSuccessSchema = makeSuccessSchema(requestMergeDataSchema);
747
753
  var mergeRequestQueueSuccessSchema = makeSuccessSchema(mergeRequestQueueDataSchema);
748
754
  var viewMergeRequestSuccessSchema = makeSuccessSchema(viewMergeRequestDataSchema);
@@ -760,17 +766,18 @@ var accessDebugSuccessSchema = makeSuccessSchema(accessDebugDataSchema);
760
766
  var updateMemberRoleSuccessSchema = makeSuccessSchema(updateMemberRoleDataSchema);
761
767
 
762
768
  // src/domain/coreAdapter.ts
769
+ import { spawn } from "child_process";
763
770
  import {
764
771
  collabList as coreCollabList,
765
772
  collabListMembers as coreCollabListMembers,
766
773
  collabUpdateMemberRole as coreCollabUpdateMemberRole,
767
- collabAdd as coreCollabAdd,
774
+ drainPendingFinalizeQueue as coreDrainPendingFinalizeQueue,
768
775
  collabFinalizeTurn as coreCollabFinalizeTurn,
769
- collabRecordTurn as coreCollabRecordTurn,
770
776
  collabApprove as coreCollabApprove,
771
777
  collabCheckout as coreCollabCheckout,
772
778
  collabListMergeRequests as coreCollabListMergeRequests,
773
779
  collabInit as coreCollabInit,
780
+ collabReAnchor as coreCollabReAnchor,
774
781
  collabInvite as coreCollabInvite,
775
782
  collabReconcile as coreCollabReconcile,
776
783
  collabReject as coreCollabReject,
@@ -781,11 +788,13 @@ import {
781
788
  collabSyncUpstream as coreCollabSyncUpstream,
782
789
  collabView as coreCollabView
783
790
  } from "@remixhq/core/collab";
784
- import { findGitRoot, getHeadCommitHash, listUntrackedFiles } from "@remixhq/core/repo";
791
+ import { findGitRoot } from "@remixhq/core/repo";
785
792
  function getRiskLevel(status) {
786
793
  if (status.recommendedAction === "reconcile") return "high";
787
- if (status.recommendedAction === "choose_family") return "medium";
788
- 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
+ }
789
798
  if (status.repo.branchMismatch || !status.repo.isGitRepo || !status.binding.isBound || !status.repo.worktree.isClean) return "medium";
790
799
  return "low";
791
800
  }
@@ -798,10 +807,16 @@ function getRecommendedNextActions(status) {
798
807
  switch (status.recommendedAction) {
799
808
  case "init":
800
809
  return ["Run remix_collab_init to bind the repository to Remix before using any Remix collaboration mutation flow."];
801
- case "sync":
802
- 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."];
803
814
  case "reconcile":
804
- 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
+ ];
805
820
  case "review_queue":
806
821
  return ["Run remix_collab_review_queue to inspect reviewable merge requests instead of using local git merge flows."];
807
822
  case "choose_family":
@@ -835,6 +850,17 @@ function truncateText(value, maxChars) {
835
850
  originalChars: value.length
836
851
  };
837
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
+ }
838
864
  async function getStatus(params) {
839
865
  const api = params.includeRemote ? await createCollabApiClient() : null;
840
866
  const status = await coreCollabStatus({
@@ -865,7 +891,11 @@ async function initCollab(params) {
865
891
  return {
866
892
  data: result,
867
893
  warnings: collectResultWarnings(result),
868
- 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."],
869
899
  logContext: {
870
900
  repoRoot: result.repoRoot,
871
901
  appId: result.appId
@@ -935,23 +965,44 @@ async function finalizeCollabTurn(params) {
935
965
  cwd: params.cwd,
936
966
  prompt: params.prompt,
937
967
  assistantResponse: params.assistantResponse,
938
- diff: params.externalDiff ?? null,
939
- diffSource: params.diffSource,
940
968
  sync: params.sync,
941
969
  allowBranchMismatch: params.allowBranchMismatch ?? false,
942
970
  idempotencyKey: params.idempotencyKey ?? null,
943
971
  actor: params.agent
944
972
  });
973
+ if (result.queued) {
974
+ if (!spawnFinalizeQueueDrainer()) {
975
+ await coreDrainPendingFinalizeQueue({ api });
976
+ }
977
+ }
945
978
  return {
946
979
  data: result,
947
980
  warnings: result.warnings,
948
- recommendedNextActions: [],
981
+ recommendedNextActions: result.queued ? ["Run remix_collab_drain_finalize_queue before merge-related flows if you need this queued turn recorded immediately."] : [],
949
982
  logContext: {
950
983
  repoRoot,
951
984
  appId: result.changeStep?.appId ?? result.collabTurn?.appId ?? null
952
985
  }
953
986
  };
954
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
+ }
955
1006
  async function syncCollab(params) {
956
1007
  const api = await createCollabApiClient();
957
1008
  const result = await coreCollabSync({
@@ -963,12 +1014,33 @@ async function syncCollab(params) {
963
1014
  return {
964
1015
  data: result,
965
1016
  warnings: collectResultWarnings(result),
966
- 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
+ ] : [] : [],
967
1021
  logContext: {
968
1022
  repoRoot: result.repoRoot
969
1023
  }
970
1024
  };
971
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
+ }
972
1044
  async function requestMerge(params) {
973
1045
  const api = await createCollabApiClient();
974
1046
  const result = await coreCollabRequestMerge({
@@ -1142,8 +1214,8 @@ async function reconcile(params) {
1142
1214
  return {
1143
1215
  data: result,
1144
1216
  warnings: collectWarnings(result.warnings),
1145
- 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."] : [],
1146
- 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."] : [],
1147
1219
  logContext: {
1148
1220
  repoRoot: result.repoRoot ?? null
1149
1221
  }
@@ -1719,9 +1791,6 @@ function buildSuccessEnvelope(tool, requestId, result) {
1719
1791
  };
1720
1792
  }
1721
1793
  function deriveErrorRisks(tool, normalized) {
1722
- if (isFinalizeTurnLocalSyncFailure(tool, normalized)) {
1723
- return ["The change step succeeded remotely, but the local repository may need manual recovery or a follow-up sync."];
1724
- }
1725
1794
  if (normalized.code === "DESTRUCTIVE_OPERATION_BLOCKED") {
1726
1795
  return ["A policy guard blocked a potentially destructive or state-mutating operation."];
1727
1796
  }
@@ -1733,16 +1802,9 @@ function deriveErrorRisks(tool, normalized) {
1733
1802
  }
1734
1803
  return [];
1735
1804
  }
1736
- function isFinalizeTurnLocalSyncFailure(tool, normalized) {
1737
- return tool === "remix_collab_finalize_turn" && normalized.message === "Change step succeeded remotely, but automatic local sync failed.";
1738
- }
1739
1805
  function buildErrorEnvelope(tool, requestId, error) {
1740
1806
  const normalized = normalizeToolError(error);
1741
- const recommendedNextActions = isFinalizeTurnLocalSyncFailure(tool, normalized) ? [
1742
- "Run `remix_collab_status` to confirm the bound repo state before attempting recovery.",
1743
- "Run `remix_collab_sync_preview` next, then `remix_collab_sync_apply` with `confirm=true` if the preview looks correct.",
1744
- "Inspect `error.hint` for any preserved diff backup path before retrying local recovery, and do not rerun `remix_collab_finalize_turn` immediately."
1745
- ] : 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."] : [];
1746
1808
  return {
1747
1809
  schemaVersion: SCHEMA_VERSION,
1748
1810
  ok: false,
@@ -1888,7 +1950,7 @@ function registerCollabTools(server, context) {
1888
1950
  });
1889
1951
  registerTool(server, context, {
1890
1952
  name: "remix_collab_finalize_turn",
1891
- 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.",
1892
1954
  access: "local_write",
1893
1955
  inputSchema: finalizeTurnInputSchema,
1894
1956
  outputSchema: finalizeTurnSuccessSchema,
@@ -1896,15 +1958,10 @@ function registerCollabTools(server, context) {
1896
1958
  run: async (args) => {
1897
1959
  const input = z3.object(finalizeTurnInputSchema).parse(args);
1898
1960
  const cwd = resolvePolicyCwd(context.policy, input.cwd);
1899
- if ((input.diffSource ?? "worktree") === "external" || typeof input.externalDiff === "string") {
1900
- assertDiffWithinLimit(context.policy, input.externalDiff ?? "");
1901
- }
1902
1961
  return finalizeCollabTurn({
1903
1962
  cwd,
1904
1963
  prompt: input.prompt,
1905
1964
  assistantResponse: input.assistantResponse,
1906
- diffSource: input.diffSource,
1907
- externalDiff: input.externalDiff,
1908
1965
  sync: input.sync,
1909
1966
  allowBranchMismatch: input.allowBranchMismatch ?? false,
1910
1967
  idempotencyKey: input.idempotencyKey,
@@ -1912,9 +1969,22 @@ function registerCollabTools(server, context) {
1912
1969
  });
1913
1970
  }
1914
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
+ });
1915
1985
  registerTool(server, context, {
1916
1986
  name: "remix_collab_sync_preview",
1917
- 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.",
1918
1988
  access: "read",
1919
1989
  inputSchema: previewInputSchema,
1920
1990
  outputSchema: syncSuccessSchema,
@@ -1926,7 +1996,7 @@ function registerCollabTools(server, context) {
1926
1996
  });
1927
1997
  registerTool(server, context, {
1928
1998
  name: "remix_collab_sync_apply",
1929
- 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.",
1930
2000
  access: "local_write",
1931
2001
  inputSchema: applyInputSchema,
1932
2002
  outputSchema: syncSuccessSchema,
@@ -1937,6 +2007,31 @@ function registerCollabTools(server, context) {
1937
2007
  return syncCollab({ cwd, dryRun: false, allowBranchMismatch: input.allowBranchMismatch ?? false });
1938
2008
  }
1939
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
+ });
1940
2035
  registerTool(server, context, {
1941
2036
  name: "remix_collab_request_merge",
1942
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.",
@@ -2077,7 +2172,7 @@ function registerCollabTools(server, context) {
2077
2172
  });
2078
2173
  registerTool(server, context, {
2079
2174
  name: "remix_collab_reconcile_preview",
2080
- 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.",
2081
2176
  access: "read",
2082
2177
  inputSchema: previewInputSchema,
2083
2178
  outputSchema: reconcileSuccessSchema,
@@ -2089,7 +2184,7 @@ function registerCollabTools(server, context) {
2089
2184
  });
2090
2185
  registerTool(server, context, {
2091
2186
  name: "remix_collab_reconcile_apply",
2092
- 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.",
2093
2188
  access: "local_write",
2094
2189
  inputSchema: applyInputSchema,
2095
2190
  outputSchema: reconcileSuccessSchema,
@@ -3214,6 +3309,19 @@ import { z as z9 } from "zod";
3214
3309
  // src/contracts/ops.ts
3215
3310
  import { z as z8 } from "zod";
3216
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"]);
3217
3325
  var appScopedInputSchema = {
3218
3326
  ...commonRequestFieldsSchema,
3219
3327
  appId: z8.string().trim().min(1).optional()
@@ -3223,6 +3331,13 @@ var editQueueInputSchema = {
3223
3331
  limit: z8.number().int().positive().max(100).optional(),
3224
3332
  offset: z8.number().int().nonnegative().optional()
3225
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
+ };
3226
3341
  var bundleInputSchema = {
3227
3342
  ...appScopedInputSchema,
3228
3343
  bundleId: z8.string().trim().min(1)
@@ -3255,6 +3370,7 @@ var agentRunEventsInputSchema = {
3255
3370
  };
3256
3371
  var appOverviewSuccessSchema = makeSuccessSchema(genericRecordSchema4);
3257
3372
  var editQueueSuccessSchema = makeSuccessSchema(genericRecordSchema4);
3373
+ var appJobQueueSuccessSchema = makeSuccessSchema(genericRecordSchema4);
3258
3374
  var bundleSuccessSchema = makeSuccessSchema(genericRecordSchema4);
3259
3375
  var timelineSuccessSchema = makeSuccessSchema(genericRecordSchema4);
3260
3376
  var agentRunsSuccessSchema = makeSuccessSchema(genericRecordSchema4);
@@ -3288,7 +3404,7 @@ async function getAppOverview(params) {
3288
3404
  data,
3289
3405
  warnings: [],
3290
3406
  recommendedNextActions: [
3291
- "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."
3292
3408
  ],
3293
3409
  logContext: target
3294
3410
  };
@@ -3311,6 +3427,42 @@ async function getEditQueue(params) {
3311
3427
  logContext: target
3312
3428
  };
3313
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
+ }
3314
3466
  async function getBundle(params) {
3315
3467
  const api = await createApiClient();
3316
3468
  const target = await resolveAppTarget(api, params);
@@ -3537,6 +3689,25 @@ function registerOpsTools(server, context) {
3537
3689
  });
3538
3690
  }
3539
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
+ });
3540
3711
  registerTool4(server, context, {
3541
3712
  name: "remix_ops_get_bundle",
3542
3713
  description: "Inspect one app bundle by id without downloading the artifact payload.",