@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/cli.js +133 -24
- package/dist/cli.js.map +1 -1
- package/dist/index.js +127 -23
- package/dist/index.js.map +1 -1
- package/dist/server.js +127 -23
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { createRequire } from "module";
|
|
5
|
-
import { drainPendingFinalizeQueue } from "@remixhq/core/collab";
|
|
5
|
+
import { drainAsyncJobs, drainPendingFinalizeQueue } from "@remixhq/core/collab";
|
|
6
6
|
|
|
7
7
|
// src/domain/apiClient.ts
|
|
8
8
|
import { createApiClient as createCoreApiClient, resolveConfig as resolveConfig2 } from "@remixhq/core";
|
|
@@ -623,7 +623,7 @@ var statusDataSchema = z2.object({
|
|
|
623
623
|
status: genericRecordSchema,
|
|
624
624
|
riskLevel: z2.enum(["low", "medium", "high"])
|
|
625
625
|
});
|
|
626
|
-
var
|
|
626
|
+
var initSyncDataSchema = z2.object({
|
|
627
627
|
reused: z2.boolean(),
|
|
628
628
|
projectId: z2.string(),
|
|
629
629
|
appId: z2.string(),
|
|
@@ -635,6 +635,23 @@ var initDataSchema = z2.object({
|
|
|
635
635
|
createdCanonicalFamily: z2.boolean().optional(),
|
|
636
636
|
baselineStatus: z2.enum(["seeded", "existing", "requires_re_anchor", "requires_sync"]).optional()
|
|
637
637
|
});
|
|
638
|
+
var initQueuedDataSchema = z2.object({
|
|
639
|
+
queued: z2.literal(true),
|
|
640
|
+
status: z2.literal("ready_to_record"),
|
|
641
|
+
doNotRetry: z2.literal(true),
|
|
642
|
+
jobId: z2.string(),
|
|
643
|
+
projectId: z2.string(),
|
|
644
|
+
appId: z2.string(),
|
|
645
|
+
dashboardUrl: z2.string(),
|
|
646
|
+
upstreamAppId: z2.string(),
|
|
647
|
+
bindingPath: z2.string(),
|
|
648
|
+
repoRoot: z2.string(),
|
|
649
|
+
bindingMode: z2.enum(["legacy", "lane", "explicit_root"]),
|
|
650
|
+
createdCanonicalFamily: z2.boolean(),
|
|
651
|
+
remoteUrl: z2.string().nullable(),
|
|
652
|
+
defaultBranch: z2.string().nullable()
|
|
653
|
+
});
|
|
654
|
+
var initDataSchema = initSyncDataSchema;
|
|
638
655
|
var listDataSchema = z2.object({
|
|
639
656
|
apps: genericArraySchema,
|
|
640
657
|
pagination: paginationSchema
|
|
@@ -814,9 +831,15 @@ function getRecommendedNextActions(status) {
|
|
|
814
831
|
case "pull":
|
|
815
832
|
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
833
|
case "re_anchor":
|
|
817
|
-
return [
|
|
834
|
+
return [
|
|
835
|
+
"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."
|
|
836
|
+
];
|
|
837
|
+
case "record":
|
|
838
|
+
return [
|
|
839
|
+
"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."
|
|
840
|
+
];
|
|
818
841
|
case "reconcile":
|
|
819
|
-
return ["Run remix_collab_reconcile_preview before attempting remix_collab_reconcile_apply. Reconcile
|
|
842
|
+
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."];
|
|
820
843
|
case "await_finalize":
|
|
821
844
|
return [
|
|
822
845
|
"Run remix_collab_drain_finalize_queue before merge-related or recovery flows. finalize_turn is queued only until the local finalize queue is drained."
|
|
@@ -865,6 +888,10 @@ function spawnFinalizeQueueDrainer() {
|
|
|
865
888
|
child.unref();
|
|
866
889
|
return true;
|
|
867
890
|
}
|
|
891
|
+
async function drainBeforeMutation(api) {
|
|
892
|
+
const results = await coreDrainPendingFinalizeQueue({ api });
|
|
893
|
+
return results.flatMap((result) => collectResultWarnings(result));
|
|
894
|
+
}
|
|
868
895
|
async function getStatus(params) {
|
|
869
896
|
const api = params.includeRemote ? await createCollabApiClient() : null;
|
|
870
897
|
const status = await coreCollabStatus({
|
|
@@ -890,19 +917,26 @@ async function initCollab(params) {
|
|
|
890
917
|
api,
|
|
891
918
|
cwd: params.cwd,
|
|
892
919
|
appName: params.appName ?? null,
|
|
893
|
-
forceNew: params.forceNew ?? false
|
|
920
|
+
forceNew: params.forceNew ?? false,
|
|
921
|
+
asyncSubmit: false
|
|
894
922
|
});
|
|
923
|
+
if ("queued" in result && result.queued) {
|
|
924
|
+
throw new Error(
|
|
925
|
+
"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)."
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
const syncResult = result;
|
|
895
929
|
return {
|
|
896
|
-
data:
|
|
930
|
+
data: syncResult,
|
|
897
931
|
warnings: collectResultWarnings(result),
|
|
898
|
-
recommendedNextActions:
|
|
899
|
-
"Run remix_collab_re_anchor_preview, then remix_collab_re_anchor_apply to
|
|
900
|
-
] :
|
|
932
|
+
recommendedNextActions: syncResult.baselineStatus === "requires_re_anchor" ? [
|
|
933
|
+
"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."
|
|
934
|
+
] : syncResult.baselineStatus === "requires_sync" ? [
|
|
901
935
|
"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
936
|
] : ["Run remix_collab_status to inspect sync, reconcile, and merge-request readiness before mutating bound-repo state."],
|
|
903
937
|
logContext: {
|
|
904
|
-
repoRoot:
|
|
905
|
-
appId:
|
|
938
|
+
repoRoot: syncResult.repoRoot,
|
|
939
|
+
appId: syncResult.appId
|
|
906
940
|
}
|
|
907
941
|
};
|
|
908
942
|
}
|
|
@@ -972,9 +1006,11 @@ async function finalizeCollabTurn(params) {
|
|
|
972
1006
|
sync: params.sync,
|
|
973
1007
|
allowBranchMismatch: params.allowBranchMismatch ?? false,
|
|
974
1008
|
idempotencyKey: params.idempotencyKey ?? null,
|
|
975
|
-
actor: params.agent
|
|
1009
|
+
actor: params.agent,
|
|
1010
|
+
awaitingUsageDeadlineMs: params.awaitingUsageDeadlineMs ?? null
|
|
976
1011
|
});
|
|
977
|
-
|
|
1012
|
+
const hasAwaitingDeadline = typeof params.awaitingUsageDeadlineMs === "number" && params.awaitingUsageDeadlineMs > 0;
|
|
1013
|
+
if (result.queued && !hasAwaitingDeadline) {
|
|
978
1014
|
if (!spawnFinalizeQueueDrainer()) {
|
|
979
1015
|
await coreDrainPendingFinalizeQueue({ api });
|
|
980
1016
|
}
|
|
@@ -1038,7 +1074,9 @@ async function reAnchor(params) {
|
|
|
1038
1074
|
return {
|
|
1039
1075
|
data: result,
|
|
1040
1076
|
warnings: collectWarnings(result.warnings),
|
|
1041
|
-
recommendedNextActions: params.dryRun ? [
|
|
1077
|
+
recommendedNextActions: params.dryRun ? [
|
|
1078
|
+
"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."
|
|
1079
|
+
] : [],
|
|
1042
1080
|
logContext: {
|
|
1043
1081
|
repoRoot: result.repoRoot,
|
|
1044
1082
|
appId: result.currentAppId
|
|
@@ -1047,13 +1085,14 @@ async function reAnchor(params) {
|
|
|
1047
1085
|
}
|
|
1048
1086
|
async function requestMerge(params) {
|
|
1049
1087
|
const api = await createCollabApiClient();
|
|
1088
|
+
const drainWarnings = await drainBeforeMutation(api);
|
|
1050
1089
|
const result = await coreCollabRequestMerge({
|
|
1051
1090
|
api,
|
|
1052
1091
|
cwd: params.cwd
|
|
1053
1092
|
});
|
|
1054
1093
|
return {
|
|
1055
1094
|
data: result,
|
|
1056
|
-
warnings:
|
|
1095
|
+
warnings: drainWarnings,
|
|
1057
1096
|
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.`] : [],
|
|
1058
1097
|
logContext: {
|
|
1059
1098
|
mrId: typeof result.id === "string" ? result.id : null
|
|
@@ -1209,6 +1248,7 @@ async function syncUpstream(params) {
|
|
|
1209
1248
|
}
|
|
1210
1249
|
async function reconcile(params) {
|
|
1211
1250
|
const api = await createCollabApiClient();
|
|
1251
|
+
const drainWarnings = params.dryRun ? [] : await drainBeforeMutation(api);
|
|
1212
1252
|
const result = await coreCollabReconcile({
|
|
1213
1253
|
api,
|
|
1214
1254
|
cwd: params.cwd,
|
|
@@ -1217,9 +1257,9 @@ async function reconcile(params) {
|
|
|
1217
1257
|
});
|
|
1218
1258
|
return {
|
|
1219
1259
|
data: result,
|
|
1220
|
-
warnings: collectWarnings(result.warnings),
|
|
1260
|
+
warnings: [...drainWarnings, ...collectWarnings(result.warnings)],
|
|
1221
1261
|
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
|
|
1262
|
+
risks: params.dryRun ? ["Reconcile may upload local history to recover the server lane onto the latest agreed state before future recording continues."] : [],
|
|
1223
1263
|
logContext: {
|
|
1224
1264
|
repoRoot: result.repoRoot ?? null
|
|
1225
1265
|
}
|
|
@@ -1766,6 +1806,50 @@ async function accessDebug(params) {
|
|
|
1766
1806
|
};
|
|
1767
1807
|
}
|
|
1768
1808
|
|
|
1809
|
+
// src/tools/collab/autoSpawnHistoryImport.ts
|
|
1810
|
+
import { spawn as spawn2 } from "child_process";
|
|
1811
|
+
import { existsSync, mkdirSync, openSync } from "fs";
|
|
1812
|
+
import path2 from "path";
|
|
1813
|
+
var MARKER_REL_PATH = path2.join(".remix", ".history-imported");
|
|
1814
|
+
var LOG_REL_PATH = path2.join(".remix", "history-import.log");
|
|
1815
|
+
function shouldAutoSpawnHistoryImport(repoRoot) {
|
|
1816
|
+
try {
|
|
1817
|
+
return !existsSync(path2.join(repoRoot, MARKER_REL_PATH));
|
|
1818
|
+
} catch {
|
|
1819
|
+
return false;
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
function spawnHistoryImportDetached(repoRoot) {
|
|
1823
|
+
const remixDir = path2.join(repoRoot, ".remix");
|
|
1824
|
+
try {
|
|
1825
|
+
mkdirSync(remixDir, { recursive: true });
|
|
1826
|
+
} catch {
|
|
1827
|
+
}
|
|
1828
|
+
const logPath = path2.join(repoRoot, LOG_REL_PATH);
|
|
1829
|
+
const out = openSync(logPath, "a");
|
|
1830
|
+
const err = openSync(logPath, "a");
|
|
1831
|
+
const child = spawn2(
|
|
1832
|
+
"remix",
|
|
1833
|
+
[
|
|
1834
|
+
"history",
|
|
1835
|
+
"import",
|
|
1836
|
+
"--repo",
|
|
1837
|
+
repoRoot,
|
|
1838
|
+
// Include prompt text for parity with the CLI auto-spawn path:
|
|
1839
|
+
// first-time UX is a lot worse if the dashboard renders every
|
|
1840
|
+
// historical row as "(prompt not uploaded)".
|
|
1841
|
+
"--include-prompt-text"
|
|
1842
|
+
],
|
|
1843
|
+
{
|
|
1844
|
+
detached: true,
|
|
1845
|
+
stdio: ["ignore", out, err],
|
|
1846
|
+
env: { ...process.env, REMIX_HISTORY_AUTO_SPAWN: "1" }
|
|
1847
|
+
}
|
|
1848
|
+
);
|
|
1849
|
+
child.unref();
|
|
1850
|
+
return { pid: child.pid, logPath };
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1769
1853
|
// src/tools/collab/register.ts
|
|
1770
1854
|
function getAnnotations(access, options) {
|
|
1771
1855
|
if (access === "read") {
|
|
@@ -1887,18 +1971,37 @@ function registerCollabTools(server, context) {
|
|
|
1887
1971
|
});
|
|
1888
1972
|
registerTool(server, context, {
|
|
1889
1973
|
name: "remix_collab_init",
|
|
1890
|
-
description: "Import the current repository into Remix and write the local binding file.",
|
|
1974
|
+
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.",
|
|
1891
1975
|
access: "remote_write",
|
|
1892
1976
|
inputSchema: initInputSchema,
|
|
1893
1977
|
outputSchema: initSuccessSchema,
|
|
1894
1978
|
run: async (args) => {
|
|
1895
1979
|
const input = z3.object(initInputSchema).parse(args);
|
|
1896
1980
|
const cwd = resolvePolicyCwd(context.policy, input.cwd);
|
|
1897
|
-
|
|
1981
|
+
const result = await initCollab({
|
|
1898
1982
|
cwd,
|
|
1899
1983
|
appName: input.appName,
|
|
1900
1984
|
forceNew: input.forceNew
|
|
1901
1985
|
});
|
|
1986
|
+
try {
|
|
1987
|
+
const repoRoot = result && typeof result === "object" && "data" in result && result.data && typeof result.data.repoRoot === "string" ? result.data.repoRoot : null;
|
|
1988
|
+
if (repoRoot && shouldAutoSpawnHistoryImport(repoRoot)) {
|
|
1989
|
+
const spawned = spawnHistoryImportDetached(repoRoot);
|
|
1990
|
+
context.logger.log({
|
|
1991
|
+
level: "info",
|
|
1992
|
+
message: `history_import_auto_spawned pid=${spawned.pid ?? "?"} log=${spawned.logPath}`,
|
|
1993
|
+
tool: "remix_collab_init",
|
|
1994
|
+
repoRoot
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
} catch (spawnError) {
|
|
1998
|
+
context.logger.log({
|
|
1999
|
+
level: "error",
|
|
2000
|
+
message: `history_import_auto_spawn_failed: ${spawnError instanceof Error ? spawnError.message : String(spawnError)}`,
|
|
2001
|
+
tool: "remix_collab_init"
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
return result;
|
|
1902
2005
|
}
|
|
1903
2006
|
});
|
|
1904
2007
|
registerTool(server, context, {
|
|
@@ -1954,7 +2057,7 @@ function registerCollabTools(server, context) {
|
|
|
1954
2057
|
});
|
|
1955
2058
|
registerTool(server, context, {
|
|
1956
2059
|
name: "remix_collab_finalize_turn",
|
|
1957
|
-
description: "Primary turn recorder for the current bound repository.
|
|
2060
|
+
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.",
|
|
1958
2061
|
access: "local_write",
|
|
1959
2062
|
inputSchema: finalizeTurnInputSchema,
|
|
1960
2063
|
outputSchema: finalizeTurnSuccessSchema,
|
|
@@ -1969,13 +2072,14 @@ function registerCollabTools(server, context) {
|
|
|
1969
2072
|
sync: input.sync,
|
|
1970
2073
|
allowBranchMismatch: input.allowBranchMismatch ?? false,
|
|
1971
2074
|
idempotencyKey: input.idempotencyKey,
|
|
1972
|
-
agent: context.agentMetadata
|
|
2075
|
+
agent: context.agentMetadata,
|
|
2076
|
+
awaitingUsageDeadlineMs: 3e4
|
|
1973
2077
|
});
|
|
1974
2078
|
}
|
|
1975
2079
|
});
|
|
1976
2080
|
registerTool(server, context, {
|
|
1977
2081
|
name: "remix_collab_drain_finalize_queue",
|
|
1978
|
-
description: "Drain the local finalize queue and record queued finalize_turn jobs immediately.
|
|
2082
|
+
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.",
|
|
1979
2083
|
access: "local_write",
|
|
1980
2084
|
inputSchema: drainFinalizeQueueInputSchema,
|
|
1981
2085
|
outputSchema: drainFinalizeQueueSuccessSchema,
|
|
@@ -2013,7 +2117,7 @@ function registerCollabTools(server, context) {
|
|
|
2013
2117
|
});
|
|
2014
2118
|
registerTool(server, context, {
|
|
2015
2119
|
name: "remix_collab_re_anchor_preview",
|
|
2016
|
-
description: "Preview whether
|
|
2120
|
+
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.",
|
|
2017
2121
|
access: "read",
|
|
2018
2122
|
inputSchema: previewInputSchema,
|
|
2019
2123
|
outputSchema: reAnchorSuccessSchema,
|
|
@@ -2025,7 +2129,7 @@ function registerCollabTools(server, context) {
|
|
|
2025
2129
|
});
|
|
2026
2130
|
registerTool(server, context, {
|
|
2027
2131
|
name: "remix_collab_re_anchor_apply",
|
|
2028
|
-
description: "
|
|
2132
|
+
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`.",
|
|
2029
2133
|
access: "local_write",
|
|
2030
2134
|
inputSchema: reAnchorInputSchema,
|
|
2031
2135
|
outputSchema: reAnchorSuccessSchema,
|
|
@@ -3847,6 +3951,11 @@ async function main() {
|
|
|
3847
3951
|
await drainPendingFinalizeQueue({ api });
|
|
3848
3952
|
return;
|
|
3849
3953
|
}
|
|
3954
|
+
if (process.argv.includes("--drain-async-jobs")) {
|
|
3955
|
+
const api = await createCollabApiClient();
|
|
3956
|
+
await drainAsyncJobs({ api });
|
|
3957
|
+
return;
|
|
3958
|
+
}
|
|
3850
3959
|
await startStdioServer({ version });
|
|
3851
3960
|
}
|
|
3852
3961
|
main().catch((error) => {
|