@remixhq/mcp 0.1.13 → 0.1.14

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/server.js CHANGED
@@ -619,7 +619,7 @@ var statusDataSchema = z2.object({
619
619
  status: genericRecordSchema,
620
620
  riskLevel: z2.enum(["low", "medium", "high"])
621
621
  });
622
- var initDataSchema = z2.object({
622
+ var initSyncDataSchema = z2.object({
623
623
  reused: z2.boolean(),
624
624
  projectId: z2.string(),
625
625
  appId: z2.string(),
@@ -631,6 +631,23 @@ var initDataSchema = z2.object({
631
631
  createdCanonicalFamily: z2.boolean().optional(),
632
632
  baselineStatus: z2.enum(["seeded", "existing", "requires_re_anchor", "requires_sync"]).optional()
633
633
  });
634
+ var initQueuedDataSchema = z2.object({
635
+ queued: z2.literal(true),
636
+ status: z2.literal("ready_to_record"),
637
+ doNotRetry: z2.literal(true),
638
+ jobId: z2.string(),
639
+ projectId: z2.string(),
640
+ appId: z2.string(),
641
+ dashboardUrl: z2.string(),
642
+ upstreamAppId: z2.string(),
643
+ bindingPath: z2.string(),
644
+ repoRoot: z2.string(),
645
+ bindingMode: z2.enum(["legacy", "lane", "explicit_root"]),
646
+ createdCanonicalFamily: z2.boolean(),
647
+ remoteUrl: z2.string().nullable(),
648
+ defaultBranch: z2.string().nullable()
649
+ });
650
+ var initDataSchema = initSyncDataSchema;
634
651
  var listDataSchema = z2.object({
635
652
  apps: genericArraySchema,
636
653
  pagination: paginationSchema
@@ -810,9 +827,15 @@ function getRecommendedNextActions(status) {
810
827
  case "pull":
811
828
  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
829
  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."];
830
+ return [
831
+ "Run remix_collab_re_anchor_preview, then remix_collab_re_anchor_apply. This seeds a local Remix baseline. It is required because no local baseline exists for this lane yet (fresh clone, deleted .remix/ state, or first init didn't seed) \u2014 not because of any specific git operation. After it succeeds, normal recording (remix_collab_finalize_turn) becomes available."
832
+ ];
833
+ case "record":
834
+ return [
835
+ "Run remix_collab_finalize_turn to capture the local boundary delta. This is the catch-all for any local content change since the last recorded turn, regardless of whether the change came from agent edits, manual user edits, git commit, git pull, git merge, git rebase, or git reset."
836
+ ];
814
837
  case "reconcile":
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."];
838
+ return ["Run remix_collab_reconcile_preview before attempting remix_collab_reconcile_apply. Reconcile applies only when both the local workspace and the server lane changed since the last agreed baseline."];
816
839
  case "await_finalize":
817
840
  return [
818
841
  "Run remix_collab_drain_finalize_queue before merge-related or recovery flows. finalize_turn is queued only until the local finalize queue is drained."
@@ -861,6 +884,10 @@ function spawnFinalizeQueueDrainer() {
861
884
  child.unref();
862
885
  return true;
863
886
  }
887
+ async function drainBeforeMutation(api) {
888
+ const results = await coreDrainPendingFinalizeQueue({ api });
889
+ return results.flatMap((result) => collectResultWarnings(result));
890
+ }
864
891
  async function getStatus(params) {
865
892
  const api = params.includeRemote ? await createCollabApiClient() : null;
866
893
  const status = await coreCollabStatus({
@@ -886,19 +913,26 @@ async function initCollab(params) {
886
913
  api,
887
914
  cwd: params.cwd,
888
915
  appName: params.appName ?? null,
889
- forceNew: params.forceNew ?? false
916
+ forceNew: params.forceNew ?? false,
917
+ asyncSubmit: false
890
918
  });
919
+ if ("queued" in result && result.queued) {
920
+ throw new Error(
921
+ "Unexpected queued init result on the MCP path. Async init is currently disabled for agent callers; if you intended to re-enable it, also restore the queued shape in initDataSchema (see remix-mcp/src/contracts/collab.ts)."
922
+ );
923
+ }
924
+ const syncResult = result;
891
925
  return {
892
- data: result,
926
+ data: syncResult,
893
927
  warnings: collectResultWarnings(result),
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" ? [
928
+ recommendedNextActions: syncResult.baselineStatus === "requires_re_anchor" ? [
929
+ "This checkout has no local Remix baseline yet. Run remix_collab_re_anchor_preview, then remix_collab_re_anchor_apply to seed one. After it succeeds, normal recording (remix_collab_finalize_turn) becomes available."
930
+ ] : syncResult.baselineStatus === "requires_sync" ? [
897
931
  "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
932
  ] : ["Run remix_collab_status to inspect sync, reconcile, and merge-request readiness before mutating bound-repo state."],
899
933
  logContext: {
900
- repoRoot: result.repoRoot,
901
- appId: result.appId
934
+ repoRoot: syncResult.repoRoot,
935
+ appId: syncResult.appId
902
936
  }
903
937
  };
904
938
  }
@@ -968,9 +1002,11 @@ async function finalizeCollabTurn(params) {
968
1002
  sync: params.sync,
969
1003
  allowBranchMismatch: params.allowBranchMismatch ?? false,
970
1004
  idempotencyKey: params.idempotencyKey ?? null,
971
- actor: params.agent
1005
+ actor: params.agent,
1006
+ awaitingUsageDeadlineMs: params.awaitingUsageDeadlineMs ?? null
972
1007
  });
973
- if (result.queued) {
1008
+ const hasAwaitingDeadline = typeof params.awaitingUsageDeadlineMs === "number" && params.awaitingUsageDeadlineMs > 0;
1009
+ if (result.queued && !hasAwaitingDeadline) {
974
1010
  if (!spawnFinalizeQueueDrainer()) {
975
1011
  await coreDrainPendingFinalizeQueue({ api });
976
1012
  }
@@ -1034,7 +1070,9 @@ async function reAnchor(params) {
1034
1070
  return {
1035
1071
  data: result,
1036
1072
  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."] : [],
1073
+ recommendedNextActions: params.dryRun ? [
1074
+ "Run remix_collab_re_anchor_apply with confirm=true to seed a local Remix baseline for this checkout. Re-anchor is for missing-baseline cases only and does not replace remix_collab_finalize_turn for ordinary local content changes."
1075
+ ] : [],
1038
1076
  logContext: {
1039
1077
  repoRoot: result.repoRoot,
1040
1078
  appId: result.currentAppId
@@ -1043,13 +1081,14 @@ async function reAnchor(params) {
1043
1081
  }
1044
1082
  async function requestMerge(params) {
1045
1083
  const api = await createCollabApiClient();
1084
+ const drainWarnings = await drainBeforeMutation(api);
1046
1085
  const result = await coreCollabRequestMerge({
1047
1086
  api,
1048
1087
  cwd: params.cwd
1049
1088
  });
1050
1089
  return {
1051
1090
  data: result,
1052
- warnings: [],
1091
+ warnings: drainWarnings,
1053
1092
  recommendedNextActions: result.id ? [`Run remix_collab_view_merge_request with mrId=${String(result.id)} to inspect the request before deciding whether to approve or reject it.`] : [],
1054
1093
  logContext: {
1055
1094
  mrId: typeof result.id === "string" ? result.id : null
@@ -1205,6 +1244,7 @@ async function syncUpstream(params) {
1205
1244
  }
1206
1245
  async function reconcile(params) {
1207
1246
  const api = await createCollabApiClient();
1247
+ const drainWarnings = params.dryRun ? [] : await drainBeforeMutation(api);
1208
1248
  const result = await coreCollabReconcile({
1209
1249
  api,
1210
1250
  cwd: params.cwd,
@@ -1213,9 +1253,9 @@ async function reconcile(params) {
1213
1253
  });
1214
1254
  return {
1215
1255
  data: result,
1216
- warnings: collectWarnings(result.warnings),
1256
+ warnings: [...drainWarnings, ...collectWarnings(result.warnings)],
1217
1257
  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."] : [],
1258
+ risks: params.dryRun ? ["Reconcile may upload local history to recover the server lane onto the latest agreed state before future recording continues."] : [],
1219
1259
  logContext: {
1220
1260
  repoRoot: result.repoRoot ?? null
1221
1261
  }
@@ -1762,6 +1802,50 @@ async function accessDebug(params) {
1762
1802
  };
1763
1803
  }
1764
1804
 
1805
+ // src/tools/collab/autoSpawnHistoryImport.ts
1806
+ import { spawn as spawn2 } from "child_process";
1807
+ import { existsSync, mkdirSync, openSync } from "fs";
1808
+ import path2 from "path";
1809
+ var MARKER_REL_PATH = path2.join(".remix", ".history-imported");
1810
+ var LOG_REL_PATH = path2.join(".remix", "history-import.log");
1811
+ function shouldAutoSpawnHistoryImport(repoRoot) {
1812
+ try {
1813
+ return !existsSync(path2.join(repoRoot, MARKER_REL_PATH));
1814
+ } catch {
1815
+ return false;
1816
+ }
1817
+ }
1818
+ function spawnHistoryImportDetached(repoRoot) {
1819
+ const remixDir = path2.join(repoRoot, ".remix");
1820
+ try {
1821
+ mkdirSync(remixDir, { recursive: true });
1822
+ } catch {
1823
+ }
1824
+ const logPath = path2.join(repoRoot, LOG_REL_PATH);
1825
+ const out = openSync(logPath, "a");
1826
+ const err = openSync(logPath, "a");
1827
+ const child = spawn2(
1828
+ "remix",
1829
+ [
1830
+ "history",
1831
+ "import",
1832
+ "--repo",
1833
+ repoRoot,
1834
+ // Include prompt text for parity with the CLI auto-spawn path:
1835
+ // first-time UX is a lot worse if the dashboard renders every
1836
+ // historical row as "(prompt not uploaded)".
1837
+ "--include-prompt-text"
1838
+ ],
1839
+ {
1840
+ detached: true,
1841
+ stdio: ["ignore", out, err],
1842
+ env: { ...process.env, REMIX_HISTORY_AUTO_SPAWN: "1" }
1843
+ }
1844
+ );
1845
+ child.unref();
1846
+ return { pid: child.pid, logPath };
1847
+ }
1848
+
1765
1849
  // src/tools/collab/register.ts
1766
1850
  function getAnnotations(access, options) {
1767
1851
  if (access === "read") {
@@ -1883,18 +1967,37 @@ function registerCollabTools(server, context) {
1883
1967
  });
1884
1968
  registerTool(server, context, {
1885
1969
  name: "remix_collab_init",
1886
- description: "Import the current repository into Remix and write the local binding file.",
1970
+ description: "Import the current repository into Remix and write the local binding file. Synchronous: by the time this tool resolves, the local binding file AND the local Remix baseline are both on disk, so the very next call to remix_collab_finalize_turn will succeed. Brand-new init on the default branch typically takes ~10s; non-default-branch init can take 30-90s while the server provisions a feature lane. The result includes `reused: boolean` (false for a brand-new app, true if a binding already existed) plus the canonical app/project identifiers and the dashboard URL. Use forceNew=true only when intentionally creating a new canonical family from scratch in a previously-bound repo; do NOT use forceNew as a retry mechanism for a failed init \u2014 it creates orphan backend apps and triggers canonical-family ambiguity errors on subsequent inits in this directory.",
1887
1971
  access: "remote_write",
1888
1972
  inputSchema: initInputSchema,
1889
1973
  outputSchema: initSuccessSchema,
1890
1974
  run: async (args) => {
1891
1975
  const input = z3.object(initInputSchema).parse(args);
1892
1976
  const cwd = resolvePolicyCwd(context.policy, input.cwd);
1893
- return initCollab({
1977
+ const result = await initCollab({
1894
1978
  cwd,
1895
1979
  appName: input.appName,
1896
1980
  forceNew: input.forceNew
1897
1981
  });
1982
+ try {
1983
+ const repoRoot = result && typeof result === "object" && "data" in result && result.data && typeof result.data.repoRoot === "string" ? result.data.repoRoot : null;
1984
+ if (repoRoot && shouldAutoSpawnHistoryImport(repoRoot)) {
1985
+ const spawned = spawnHistoryImportDetached(repoRoot);
1986
+ context.logger.log({
1987
+ level: "info",
1988
+ message: `history_import_auto_spawned pid=${spawned.pid ?? "?"} log=${spawned.logPath}`,
1989
+ tool: "remix_collab_init",
1990
+ repoRoot
1991
+ });
1992
+ }
1993
+ } catch (spawnError) {
1994
+ context.logger.log({
1995
+ level: "error",
1996
+ message: `history_import_auto_spawn_failed: ${spawnError instanceof Error ? spawnError.message : String(spawnError)}`,
1997
+ tool: "remix_collab_init"
1998
+ });
1999
+ }
2000
+ return result;
1898
2001
  }
1899
2002
  });
1900
2003
  registerTool(server, context, {
@@ -1950,7 +2053,7 @@ function registerCollabTools(server, context) {
1950
2053
  });
1951
2054
  registerTool(server, context, {
1952
2055
  name: "remix_collab_finalize_turn",
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.",
2056
+ description: "Primary turn recorder for the current bound repository. Required: call this exactly once before the final response on every turn that touched a Remix-bound repo. Captures the current boundary locally and queues remote processing. Queued only: no remote change step exists yet until the finalize queue drains. Runtime exception: if this turn's context contains a [Remix runtime status] system note from the Remix UserPromptSubmit hook stating that the Stop hook will record this turn automatically, do NOT call this tool \u2014 the runtime is recording it on your behalf and a manual call would create a duplicate record. The runtime note is the only sanctioned override; trivial prompts, error states, and ambiguity all still require this call.",
1954
2057
  access: "local_write",
1955
2058
  inputSchema: finalizeTurnInputSchema,
1956
2059
  outputSchema: finalizeTurnSuccessSchema,
@@ -1965,13 +2068,14 @@ function registerCollabTools(server, context) {
1965
2068
  sync: input.sync,
1966
2069
  allowBranchMismatch: input.allowBranchMismatch ?? false,
1967
2070
  idempotencyKey: input.idempotencyKey,
1968
- agent: context.agentMetadata
2071
+ agent: context.agentMetadata,
2072
+ awaitingUsageDeadlineMs: 3e4
1969
2073
  });
1970
2074
  }
1971
2075
  });
1972
2076
  registerTool(server, context, {
1973
2077
  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.",
2078
+ description: "Drain the local finalize queue and record queued finalize_turn jobs immediately. NOT required as a precondition for `remix_collab_request_merge` or `remix_collab_reconcile_apply` \u2014 those tools drain the queue internally before they run. Useful only for explicit recovery flows (e.g. status reports `await_finalize` and you want to flush before re-checking). Runtime exception: if this turn's context contains a [Remix runtime status] system note from the Remix UserPromptSubmit hook, the runtime drains the queue automatically in the background; do NOT call this tool unless an explicit recovery flow requires it.",
1975
2079
  access: "local_write",
1976
2080
  inputSchema: drainFinalizeQueueInputSchema,
1977
2081
  outputSchema: drainFinalizeQueueSuccessSchema,
@@ -2009,7 +2113,7 @@ function registerCollabTools(server, context) {
2009
2113
  });
2010
2114
  registerTool(server, context, {
2011
2115
  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.",
2116
+ description: "Preview whether this checkout needs a fresh local Remix baseline. Use only when status reports `re_anchor` (no local baseline exists for this lane yet \u2014 fresh clone, deleted `.remix/` state, or first init didn't seed). Re-anchor does not replace `remix_collab_finalize_turn`; ordinary local content changes (including merges, pulls, and rebases) are recorded by `finalize-turn`, not by re-anchor.",
2013
2117
  access: "read",
2014
2118
  inputSchema: previewInputSchema,
2015
2119
  outputSchema: reAnchorSuccessSchema,
@@ -2021,7 +2125,7 @@ function registerCollabTools(server, context) {
2021
2125
  });
2022
2126
  registerTool(server, context, {
2023
2127
  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.",
2128
+ description: "Establish a local Remix baseline for the current checkout against the existing app head, without rewriting the local checkout afterward. Required only when status reports `re_anchor` (missing local baseline). It does not replace `remix_collab_finalize_turn` \u2014 local commits, pulls, merges, and rebases must still be recorded with `finalize-turn`.",
2025
2129
  access: "local_write",
2026
2130
  inputSchema: reAnchorInputSchema,
2027
2131
  outputSchema: reAnchorSuccessSchema,