@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/cli.js +231 -54
- package/dist/cli.js.map +1 -1
- package/dist/index.js +221 -50
- package/dist/index.js.map +1 -1
- package/dist/server.js +221 -50
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
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 {
|
|
@@ -355,14 +356,6 @@ function assertConfirm(confirm, operation) {
|
|
|
355
356
|
if (confirm) return;
|
|
356
357
|
throw createPolicyError(`${operation} requires explicit confirmation.`, "Pass confirm=true to run this tool.");
|
|
357
358
|
}
|
|
358
|
-
function assertDiffWithinLimit(policy, diff) {
|
|
359
|
-
const sizeBytes = Buffer.byteLength(diff, "utf8");
|
|
360
|
-
if (sizeBytes <= policy.maxDiffBytes) return;
|
|
361
|
-
throw createPolicyError(
|
|
362
|
-
"Diff exceeds the configured maximum size for Remix MCP.",
|
|
363
|
-
`Configured limit=${policy.maxDiffBytes} bytes actual=${sizeBytes} bytes.`
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
359
|
|
|
367
360
|
// src/bootstrap/context.ts
|
|
368
361
|
function createServerContext(params) {
|
|
@@ -510,8 +503,6 @@ var finalizeTurnInputSchema = {
|
|
|
510
503
|
...commonRequestFieldsSchema,
|
|
511
504
|
prompt: z2.string().trim().min(1),
|
|
512
505
|
assistantResponse: z2.string().trim().min(1),
|
|
513
|
-
diffSource: z2.enum(["worktree", "external"]).optional(),
|
|
514
|
-
externalDiff: z2.string().optional(),
|
|
515
506
|
sync: z2.boolean().optional(),
|
|
516
507
|
allowBranchMismatch: z2.boolean().optional(),
|
|
517
508
|
idempotencyKey: z2.string().trim().min(1).optional()
|
|
@@ -519,11 +510,17 @@ var finalizeTurnInputSchema = {
|
|
|
519
510
|
var previewInputSchema = {
|
|
520
511
|
...commonRequestFieldsSchema
|
|
521
512
|
};
|
|
513
|
+
var drainFinalizeQueueInputSchema = {
|
|
514
|
+
...commonRequestFieldsSchema
|
|
515
|
+
};
|
|
522
516
|
var applyInputSchema = {
|
|
523
517
|
...commonRequestFieldsSchema,
|
|
524
518
|
confirm: z2.boolean(),
|
|
525
519
|
allowBranchMismatch: z2.boolean().optional()
|
|
526
520
|
};
|
|
521
|
+
var reAnchorInputSchema = {
|
|
522
|
+
...applyInputSchema
|
|
523
|
+
};
|
|
527
524
|
var requestMergeInputSchema = {
|
|
528
525
|
...commonRequestFieldsSchema
|
|
529
526
|
};
|
|
@@ -635,7 +632,8 @@ var initDataSchema = z2.object({
|
|
|
635
632
|
bindingPath: z2.string(),
|
|
636
633
|
repoRoot: z2.string(),
|
|
637
634
|
bindingMode: z2.enum(["legacy", "lane", "explicit_root"]).optional(),
|
|
638
|
-
createdCanonicalFamily: z2.boolean().optional()
|
|
635
|
+
createdCanonicalFamily: z2.boolean().optional(),
|
|
636
|
+
baselineStatus: z2.enum(["seeded", "existing", "requires_re_anchor", "requires_sync"]).optional()
|
|
639
637
|
});
|
|
640
638
|
var listDataSchema = z2.object({
|
|
641
639
|
apps: genericArraySchema,
|
|
@@ -658,19 +656,26 @@ var checkoutDataSchema = z2.object({
|
|
|
658
656
|
repoRoot: z2.string()
|
|
659
657
|
});
|
|
660
658
|
var addDataSchema = z2.object({
|
|
661
|
-
changeStep: genericRecordSchema
|
|
662
|
-
autoSync: genericRecordSchema
|
|
659
|
+
changeStep: genericRecordSchema
|
|
663
660
|
});
|
|
664
661
|
var recordTurnDataSchema = genericRecordSchema;
|
|
665
662
|
var finalizeTurnDataSchema = z2.object({
|
|
666
663
|
mode: z2.enum(["changed_turn", "no_diff_turn"]),
|
|
667
664
|
idempotencyKey: z2.string().min(1),
|
|
665
|
+
queued: z2.boolean(),
|
|
666
|
+
jobId: z2.string().nullable(),
|
|
667
|
+
repoState: z2.string().nullable(),
|
|
668
668
|
changeStep: genericRecordSchema.nullable(),
|
|
669
669
|
collabTurn: genericRecordSchema.nullable(),
|
|
670
670
|
autoSync: genericRecordSchema.nullable(),
|
|
671
671
|
warnings: z2.array(z2.string())
|
|
672
672
|
});
|
|
673
|
+
var drainFinalizeQueueDataSchema = z2.object({
|
|
674
|
+
processed: z2.number().int().nonnegative(),
|
|
675
|
+
results: z2.array(genericRecordSchema)
|
|
676
|
+
});
|
|
673
677
|
var syncDataSchema = genericRecordSchema;
|
|
678
|
+
var reAnchorDataSchema = genericRecordSchema;
|
|
674
679
|
var requestMergeDataSchema = genericRecordSchema;
|
|
675
680
|
var mergeRequestQueueDataSchema = z2.object({
|
|
676
681
|
queue: mergeRequestQueueSchema,
|
|
@@ -745,7 +750,9 @@ var checkoutSuccessSchema = makeSuccessSchema(checkoutDataSchema);
|
|
|
745
750
|
var addSuccessSchema = makeSuccessSchema(addDataSchema);
|
|
746
751
|
var recordTurnSuccessSchema = makeSuccessSchema(recordTurnDataSchema);
|
|
747
752
|
var finalizeTurnSuccessSchema = makeSuccessSchema(finalizeTurnDataSchema);
|
|
753
|
+
var drainFinalizeQueueSuccessSchema = makeSuccessSchema(drainFinalizeQueueDataSchema);
|
|
748
754
|
var syncSuccessSchema = makeSuccessSchema(syncDataSchema);
|
|
755
|
+
var reAnchorSuccessSchema = makeSuccessSchema(reAnchorDataSchema);
|
|
749
756
|
var requestMergeSuccessSchema = makeSuccessSchema(requestMergeDataSchema);
|
|
750
757
|
var mergeRequestQueueSuccessSchema = makeSuccessSchema(mergeRequestQueueDataSchema);
|
|
751
758
|
var viewMergeRequestSuccessSchema = makeSuccessSchema(viewMergeRequestDataSchema);
|
|
@@ -763,17 +770,18 @@ var accessDebugSuccessSchema = makeSuccessSchema(accessDebugDataSchema);
|
|
|
763
770
|
var updateMemberRoleSuccessSchema = makeSuccessSchema(updateMemberRoleDataSchema);
|
|
764
771
|
|
|
765
772
|
// src/domain/coreAdapter.ts
|
|
773
|
+
import { spawn } from "child_process";
|
|
766
774
|
import {
|
|
767
775
|
collabList as coreCollabList,
|
|
768
776
|
collabListMembers as coreCollabListMembers,
|
|
769
777
|
collabUpdateMemberRole as coreCollabUpdateMemberRole,
|
|
770
|
-
|
|
778
|
+
drainPendingFinalizeQueue as coreDrainPendingFinalizeQueue,
|
|
771
779
|
collabFinalizeTurn as coreCollabFinalizeTurn,
|
|
772
|
-
collabRecordTurn as coreCollabRecordTurn,
|
|
773
780
|
collabApprove as coreCollabApprove,
|
|
774
781
|
collabCheckout as coreCollabCheckout,
|
|
775
782
|
collabListMergeRequests as coreCollabListMergeRequests,
|
|
776
783
|
collabInit as coreCollabInit,
|
|
784
|
+
collabReAnchor as coreCollabReAnchor,
|
|
777
785
|
collabInvite as coreCollabInvite,
|
|
778
786
|
collabReconcile as coreCollabReconcile,
|
|
779
787
|
collabReject as coreCollabReject,
|
|
@@ -784,11 +792,13 @@ import {
|
|
|
784
792
|
collabSyncUpstream as coreCollabSyncUpstream,
|
|
785
793
|
collabView as coreCollabView
|
|
786
794
|
} from "@remixhq/core/collab";
|
|
787
|
-
import { findGitRoot
|
|
795
|
+
import { findGitRoot } from "@remixhq/core/repo";
|
|
788
796
|
function getRiskLevel(status) {
|
|
789
797
|
if (status.recommendedAction === "reconcile") return "high";
|
|
790
|
-
if (status.recommendedAction === "choose_family") return "medium";
|
|
791
|
-
if (status.recommendedAction === "
|
|
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
|
+
}
|
|
792
802
|
if (status.repo.branchMismatch || !status.repo.isGitRepo || !status.binding.isBound || !status.repo.worktree.isClean) return "medium";
|
|
793
803
|
return "low";
|
|
794
804
|
}
|
|
@@ -801,10 +811,16 @@ function getRecommendedNextActions(status) {
|
|
|
801
811
|
switch (status.recommendedAction) {
|
|
802
812
|
case "init":
|
|
803
813
|
return ["Run remix_collab_init to bind the repository to Remix before using any Remix collaboration mutation flow."];
|
|
804
|
-
case "
|
|
805
|
-
return ["Run remix_collab_sync_preview, then remix_collab_sync_apply if the preview is acceptable.
|
|
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."];
|
|
806
818
|
case "reconcile":
|
|
807
|
-
return ["Run remix_collab_reconcile_preview before attempting remix_collab_reconcile_apply. Reconcile
|
|
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
|
+
];
|
|
808
824
|
case "review_queue":
|
|
809
825
|
return ["Run remix_collab_review_queue to inspect reviewable merge requests instead of using local git merge flows."];
|
|
810
826
|
case "choose_family":
|
|
@@ -838,6 +854,17 @@ function truncateText(value, maxChars) {
|
|
|
838
854
|
originalChars: value.length
|
|
839
855
|
};
|
|
840
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
|
+
}
|
|
841
868
|
async function getStatus(params) {
|
|
842
869
|
const api = params.includeRemote ? await createCollabApiClient() : null;
|
|
843
870
|
const status = await coreCollabStatus({
|
|
@@ -868,7 +895,11 @@ async function initCollab(params) {
|
|
|
868
895
|
return {
|
|
869
896
|
data: result,
|
|
870
897
|
warnings: collectResultWarnings(result),
|
|
871
|
-
recommendedNextActions:
|
|
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."],
|
|
872
903
|
logContext: {
|
|
873
904
|
repoRoot: result.repoRoot,
|
|
874
905
|
appId: result.appId
|
|
@@ -938,23 +969,44 @@ async function finalizeCollabTurn(params) {
|
|
|
938
969
|
cwd: params.cwd,
|
|
939
970
|
prompt: params.prompt,
|
|
940
971
|
assistantResponse: params.assistantResponse,
|
|
941
|
-
diff: params.externalDiff ?? null,
|
|
942
|
-
diffSource: params.diffSource,
|
|
943
972
|
sync: params.sync,
|
|
944
973
|
allowBranchMismatch: params.allowBranchMismatch ?? false,
|
|
945
974
|
idempotencyKey: params.idempotencyKey ?? null,
|
|
946
975
|
actor: params.agent
|
|
947
976
|
});
|
|
977
|
+
if (result.queued) {
|
|
978
|
+
if (!spawnFinalizeQueueDrainer()) {
|
|
979
|
+
await coreDrainPendingFinalizeQueue({ api });
|
|
980
|
+
}
|
|
981
|
+
}
|
|
948
982
|
return {
|
|
949
983
|
data: result,
|
|
950
984
|
warnings: result.warnings,
|
|
951
|
-
recommendedNextActions: [],
|
|
985
|
+
recommendedNextActions: result.queued ? ["Run remix_collab_drain_finalize_queue before merge-related flows if you need this queued turn recorded immediately."] : [],
|
|
952
986
|
logContext: {
|
|
953
987
|
repoRoot,
|
|
954
988
|
appId: result.changeStep?.appId ?? result.collabTurn?.appId ?? null
|
|
955
989
|
}
|
|
956
990
|
};
|
|
957
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
|
+
}
|
|
958
1010
|
async function syncCollab(params) {
|
|
959
1011
|
const api = await createCollabApiClient();
|
|
960
1012
|
const result = await coreCollabSync({
|
|
@@ -966,12 +1018,33 @@ async function syncCollab(params) {
|
|
|
966
1018
|
return {
|
|
967
1019
|
data: result,
|
|
968
1020
|
warnings: collectResultWarnings(result),
|
|
969
|
-
recommendedNextActions: params.dryRun ? ["Run remix_collab_sync_apply with confirm=true to apply this
|
|
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
|
+
] : [] : [],
|
|
970
1025
|
logContext: {
|
|
971
1026
|
repoRoot: result.repoRoot
|
|
972
1027
|
}
|
|
973
1028
|
};
|
|
974
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
|
+
}
|
|
975
1048
|
async function requestMerge(params) {
|
|
976
1049
|
const api = await createCollabApiClient();
|
|
977
1050
|
const result = await coreCollabRequestMerge({
|
|
@@ -1145,8 +1218,8 @@ async function reconcile(params) {
|
|
|
1145
1218
|
return {
|
|
1146
1219
|
data: result,
|
|
1147
1220
|
warnings: collectWarnings(result.warnings),
|
|
1148
|
-
recommendedNextActions: params.dryRun ? ["Run remix_collab_reconcile_apply with confirm=true only if the preview is acceptable.
|
|
1149
|
-
risks: params.dryRun ? ["Reconcile
|
|
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."] : [],
|
|
1150
1223
|
logContext: {
|
|
1151
1224
|
repoRoot: result.repoRoot ?? null
|
|
1152
1225
|
}
|
|
@@ -1722,9 +1795,6 @@ function buildSuccessEnvelope(tool, requestId, result) {
|
|
|
1722
1795
|
};
|
|
1723
1796
|
}
|
|
1724
1797
|
function deriveErrorRisks(tool, normalized) {
|
|
1725
|
-
if (isFinalizeTurnLocalSyncFailure(tool, normalized)) {
|
|
1726
|
-
return ["The change step succeeded remotely, but the local repository may need manual recovery or a follow-up sync."];
|
|
1727
|
-
}
|
|
1728
1798
|
if (normalized.code === "DESTRUCTIVE_OPERATION_BLOCKED") {
|
|
1729
1799
|
return ["A policy guard blocked a potentially destructive or state-mutating operation."];
|
|
1730
1800
|
}
|
|
@@ -1736,16 +1806,9 @@ function deriveErrorRisks(tool, normalized) {
|
|
|
1736
1806
|
}
|
|
1737
1807
|
return [];
|
|
1738
1808
|
}
|
|
1739
|
-
function isFinalizeTurnLocalSyncFailure(tool, normalized) {
|
|
1740
|
-
return tool === "remix_collab_finalize_turn" && normalized.message === "Change step succeeded remotely, but automatic local sync failed.";
|
|
1741
|
-
}
|
|
1742
1809
|
function buildErrorEnvelope(tool, requestId, error) {
|
|
1743
1810
|
const normalized = normalizeToolError(error);
|
|
1744
|
-
const recommendedNextActions =
|
|
1745
|
-
"Run `remix_collab_status` to confirm the bound repo state before attempting recovery.",
|
|
1746
|
-
"Run `remix_collab_sync_preview` next, then `remix_collab_sync_apply` with `confirm=true` if the preview looks correct.",
|
|
1747
|
-
"Inspect `error.hint` for any preserved diff backup path before retrying local recovery, and do not rerun `remix_collab_finalize_turn` immediately."
|
|
1748
|
-
] : 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."] : [];
|
|
1749
1812
|
return {
|
|
1750
1813
|
schemaVersion: SCHEMA_VERSION,
|
|
1751
1814
|
ok: false,
|
|
@@ -1891,7 +1954,7 @@ function registerCollabTools(server, context) {
|
|
|
1891
1954
|
});
|
|
1892
1955
|
registerTool(server, context, {
|
|
1893
1956
|
name: "remix_collab_finalize_turn",
|
|
1894
|
-
description: "Primary turn recorder for the current bound repository. Call this exactly once before the final response; it
|
|
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.",
|
|
1895
1958
|
access: "local_write",
|
|
1896
1959
|
inputSchema: finalizeTurnInputSchema,
|
|
1897
1960
|
outputSchema: finalizeTurnSuccessSchema,
|
|
@@ -1899,15 +1962,10 @@ function registerCollabTools(server, context) {
|
|
|
1899
1962
|
run: async (args) => {
|
|
1900
1963
|
const input = z3.object(finalizeTurnInputSchema).parse(args);
|
|
1901
1964
|
const cwd = resolvePolicyCwd(context.policy, input.cwd);
|
|
1902
|
-
if ((input.diffSource ?? "worktree") === "external" || typeof input.externalDiff === "string") {
|
|
1903
|
-
assertDiffWithinLimit(context.policy, input.externalDiff ?? "");
|
|
1904
|
-
}
|
|
1905
1965
|
return finalizeCollabTurn({
|
|
1906
1966
|
cwd,
|
|
1907
1967
|
prompt: input.prompt,
|
|
1908
1968
|
assistantResponse: input.assistantResponse,
|
|
1909
|
-
diffSource: input.diffSource,
|
|
1910
|
-
externalDiff: input.externalDiff,
|
|
1911
1969
|
sync: input.sync,
|
|
1912
1970
|
allowBranchMismatch: input.allowBranchMismatch ?? false,
|
|
1913
1971
|
idempotencyKey: input.idempotencyKey,
|
|
@@ -1915,9 +1973,22 @@ function registerCollabTools(server, context) {
|
|
|
1915
1973
|
});
|
|
1916
1974
|
}
|
|
1917
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
|
+
});
|
|
1918
1989
|
registerTool(server, context, {
|
|
1919
1990
|
name: "remix_collab_sync_preview",
|
|
1920
|
-
description: "Preview whether the current bound repository can
|
|
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.",
|
|
1921
1992
|
access: "read",
|
|
1922
1993
|
inputSchema: previewInputSchema,
|
|
1923
1994
|
outputSchema: syncSuccessSchema,
|
|
@@ -1929,7 +2000,7 @@ function registerCollabTools(server, context) {
|
|
|
1929
2000
|
});
|
|
1930
2001
|
registerTool(server, context, {
|
|
1931
2002
|
name: "remix_collab_sync_apply",
|
|
1932
|
-
description: "
|
|
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.",
|
|
1933
2004
|
access: "local_write",
|
|
1934
2005
|
inputSchema: applyInputSchema,
|
|
1935
2006
|
outputSchema: syncSuccessSchema,
|
|
@@ -1940,6 +2011,31 @@ function registerCollabTools(server, context) {
|
|
|
1940
2011
|
return syncCollab({ cwd, dryRun: false, allowBranchMismatch: input.allowBranchMismatch ?? false });
|
|
1941
2012
|
}
|
|
1942
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
|
+
});
|
|
1943
2039
|
registerTool(server, context, {
|
|
1944
2040
|
name: "remix_collab_request_merge",
|
|
1945
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.",
|
|
@@ -2080,7 +2176,7 @@ function registerCollabTools(server, context) {
|
|
|
2080
2176
|
});
|
|
2081
2177
|
registerTool(server, context, {
|
|
2082
2178
|
name: "remix_collab_reconcile_preview",
|
|
2083
|
-
description: "Preview
|
|
2179
|
+
description: "Preview the explicit Remix recovery flow when local and server state diverged beyond a simple pull.",
|
|
2084
2180
|
access: "read",
|
|
2085
2181
|
inputSchema: previewInputSchema,
|
|
2086
2182
|
outputSchema: reconcileSuccessSchema,
|
|
@@ -2092,7 +2188,7 @@ function registerCollabTools(server, context) {
|
|
|
2092
2188
|
});
|
|
2093
2189
|
registerTool(server, context, {
|
|
2094
2190
|
name: "remix_collab_reconcile_apply",
|
|
2095
|
-
description: "
|
|
2191
|
+
description: "Run the explicit Remix recovery flow for diverged local/server state.",
|
|
2096
2192
|
access: "local_write",
|
|
2097
2193
|
inputSchema: applyInputSchema,
|
|
2098
2194
|
outputSchema: reconcileSuccessSchema,
|
|
@@ -3217,6 +3313,19 @@ import { z as z9 } from "zod";
|
|
|
3217
3313
|
// src/contracts/ops.ts
|
|
3218
3314
|
import { z as z8 } from "zod";
|
|
3219
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"]);
|
|
3220
3329
|
var appScopedInputSchema = {
|
|
3221
3330
|
...commonRequestFieldsSchema,
|
|
3222
3331
|
appId: z8.string().trim().min(1).optional()
|
|
@@ -3226,6 +3335,13 @@ var editQueueInputSchema = {
|
|
|
3226
3335
|
limit: z8.number().int().positive().max(100).optional(),
|
|
3227
3336
|
offset: z8.number().int().nonnegative().optional()
|
|
3228
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
|
+
};
|
|
3229
3345
|
var bundleInputSchema = {
|
|
3230
3346
|
...appScopedInputSchema,
|
|
3231
3347
|
bundleId: z8.string().trim().min(1)
|
|
@@ -3258,6 +3374,7 @@ var agentRunEventsInputSchema = {
|
|
|
3258
3374
|
};
|
|
3259
3375
|
var appOverviewSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3260
3376
|
var editQueueSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3377
|
+
var appJobQueueSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3261
3378
|
var bundleSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3262
3379
|
var timelineSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3263
3380
|
var agentRunsSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
@@ -3291,7 +3408,7 @@ async function getAppOverview(params) {
|
|
|
3291
3408
|
data,
|
|
3292
3409
|
warnings: [],
|
|
3293
3410
|
recommendedNextActions: [
|
|
3294
|
-
"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."
|
|
3295
3412
|
],
|
|
3296
3413
|
logContext: target
|
|
3297
3414
|
};
|
|
@@ -3314,6 +3431,42 @@ async function getEditQueue(params) {
|
|
|
3314
3431
|
logContext: target
|
|
3315
3432
|
};
|
|
3316
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
|
+
}
|
|
3317
3470
|
async function getBundle(params) {
|
|
3318
3471
|
const api = await createApiClient();
|
|
3319
3472
|
const target = await resolveAppTarget(api, params);
|
|
@@ -3540,6 +3693,25 @@ function registerOpsTools(server, context) {
|
|
|
3540
3693
|
});
|
|
3541
3694
|
}
|
|
3542
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
|
+
});
|
|
3543
3715
|
registerTool4(server, context, {
|
|
3544
3716
|
name: "remix_ops_get_bundle",
|
|
3545
3717
|
description: "Inspect one app bundle by id without downloading the artifact payload.",
|
|
@@ -3670,6 +3842,11 @@ async function startStdioServer(params) {
|
|
|
3670
3842
|
var require2 = createRequire(import.meta.url);
|
|
3671
3843
|
var { version } = require2("../package.json");
|
|
3672
3844
|
async function main() {
|
|
3845
|
+
if (process.argv.includes("--drain-finalize-queue")) {
|
|
3846
|
+
const api = await createCollabApiClient();
|
|
3847
|
+
await drainPendingFinalizeQueue({ api });
|
|
3848
|
+
return;
|
|
3849
|
+
}
|
|
3673
3850
|
await startStdioServer({ version });
|
|
3674
3851
|
}
|
|
3675
3852
|
main().catch((error) => {
|