@remixhq/mcp 0.1.12 → 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 +351 -65
- package/dist/cli.js.map +1 -1
- package/dist/index.js +336 -61
- package/dist/index.js.map +1 -1
- package/dist/server.js +336 -61
- 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 { drainAsyncJobs, 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
|
};
|
|
@@ -626,7 +623,7 @@ var statusDataSchema = z2.object({
|
|
|
626
623
|
status: genericRecordSchema,
|
|
627
624
|
riskLevel: z2.enum(["low", "medium", "high"])
|
|
628
625
|
});
|
|
629
|
-
var
|
|
626
|
+
var initSyncDataSchema = z2.object({
|
|
630
627
|
reused: z2.boolean(),
|
|
631
628
|
projectId: z2.string(),
|
|
632
629
|
appId: z2.string(),
|
|
@@ -635,8 +632,26 @@ 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
|
});
|
|
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;
|
|
640
655
|
var listDataSchema = z2.object({
|
|
641
656
|
apps: genericArraySchema,
|
|
642
657
|
pagination: paginationSchema
|
|
@@ -658,19 +673,26 @@ var checkoutDataSchema = z2.object({
|
|
|
658
673
|
repoRoot: z2.string()
|
|
659
674
|
});
|
|
660
675
|
var addDataSchema = z2.object({
|
|
661
|
-
changeStep: genericRecordSchema
|
|
662
|
-
autoSync: genericRecordSchema
|
|
676
|
+
changeStep: genericRecordSchema
|
|
663
677
|
});
|
|
664
678
|
var recordTurnDataSchema = genericRecordSchema;
|
|
665
679
|
var finalizeTurnDataSchema = z2.object({
|
|
666
680
|
mode: z2.enum(["changed_turn", "no_diff_turn"]),
|
|
667
681
|
idempotencyKey: z2.string().min(1),
|
|
682
|
+
queued: z2.boolean(),
|
|
683
|
+
jobId: z2.string().nullable(),
|
|
684
|
+
repoState: z2.string().nullable(),
|
|
668
685
|
changeStep: genericRecordSchema.nullable(),
|
|
669
686
|
collabTurn: genericRecordSchema.nullable(),
|
|
670
687
|
autoSync: genericRecordSchema.nullable(),
|
|
671
688
|
warnings: z2.array(z2.string())
|
|
672
689
|
});
|
|
690
|
+
var drainFinalizeQueueDataSchema = z2.object({
|
|
691
|
+
processed: z2.number().int().nonnegative(),
|
|
692
|
+
results: z2.array(genericRecordSchema)
|
|
693
|
+
});
|
|
673
694
|
var syncDataSchema = genericRecordSchema;
|
|
695
|
+
var reAnchorDataSchema = genericRecordSchema;
|
|
674
696
|
var requestMergeDataSchema = genericRecordSchema;
|
|
675
697
|
var mergeRequestQueueDataSchema = z2.object({
|
|
676
698
|
queue: mergeRequestQueueSchema,
|
|
@@ -745,7 +767,9 @@ var checkoutSuccessSchema = makeSuccessSchema(checkoutDataSchema);
|
|
|
745
767
|
var addSuccessSchema = makeSuccessSchema(addDataSchema);
|
|
746
768
|
var recordTurnSuccessSchema = makeSuccessSchema(recordTurnDataSchema);
|
|
747
769
|
var finalizeTurnSuccessSchema = makeSuccessSchema(finalizeTurnDataSchema);
|
|
770
|
+
var drainFinalizeQueueSuccessSchema = makeSuccessSchema(drainFinalizeQueueDataSchema);
|
|
748
771
|
var syncSuccessSchema = makeSuccessSchema(syncDataSchema);
|
|
772
|
+
var reAnchorSuccessSchema = makeSuccessSchema(reAnchorDataSchema);
|
|
749
773
|
var requestMergeSuccessSchema = makeSuccessSchema(requestMergeDataSchema);
|
|
750
774
|
var mergeRequestQueueSuccessSchema = makeSuccessSchema(mergeRequestQueueDataSchema);
|
|
751
775
|
var viewMergeRequestSuccessSchema = makeSuccessSchema(viewMergeRequestDataSchema);
|
|
@@ -763,17 +787,18 @@ var accessDebugSuccessSchema = makeSuccessSchema(accessDebugDataSchema);
|
|
|
763
787
|
var updateMemberRoleSuccessSchema = makeSuccessSchema(updateMemberRoleDataSchema);
|
|
764
788
|
|
|
765
789
|
// src/domain/coreAdapter.ts
|
|
790
|
+
import { spawn } from "child_process";
|
|
766
791
|
import {
|
|
767
792
|
collabList as coreCollabList,
|
|
768
793
|
collabListMembers as coreCollabListMembers,
|
|
769
794
|
collabUpdateMemberRole as coreCollabUpdateMemberRole,
|
|
770
|
-
|
|
795
|
+
drainPendingFinalizeQueue as coreDrainPendingFinalizeQueue,
|
|
771
796
|
collabFinalizeTurn as coreCollabFinalizeTurn,
|
|
772
|
-
collabRecordTurn as coreCollabRecordTurn,
|
|
773
797
|
collabApprove as coreCollabApprove,
|
|
774
798
|
collabCheckout as coreCollabCheckout,
|
|
775
799
|
collabListMergeRequests as coreCollabListMergeRequests,
|
|
776
800
|
collabInit as coreCollabInit,
|
|
801
|
+
collabReAnchor as coreCollabReAnchor,
|
|
777
802
|
collabInvite as coreCollabInvite,
|
|
778
803
|
collabReconcile as coreCollabReconcile,
|
|
779
804
|
collabReject as coreCollabReject,
|
|
@@ -784,11 +809,13 @@ import {
|
|
|
784
809
|
collabSyncUpstream as coreCollabSyncUpstream,
|
|
785
810
|
collabView as coreCollabView
|
|
786
811
|
} from "@remixhq/core/collab";
|
|
787
|
-
import { findGitRoot
|
|
812
|
+
import { findGitRoot } from "@remixhq/core/repo";
|
|
788
813
|
function getRiskLevel(status) {
|
|
789
814
|
if (status.recommendedAction === "reconcile") return "high";
|
|
790
|
-
if (status.recommendedAction === "choose_family") return "medium";
|
|
791
|
-
if (status.recommendedAction === "
|
|
815
|
+
if (status.recommendedAction === "choose_family" || status.recommendedAction === "await_finalize") return "medium";
|
|
816
|
+
if (status.recommendedAction === "pull" || status.recommendedAction === "re_anchor" || status.remote.incomingOpenMergeRequestCount) {
|
|
817
|
+
return "medium";
|
|
818
|
+
}
|
|
792
819
|
if (status.repo.branchMismatch || !status.repo.isGitRepo || !status.binding.isBound || !status.repo.worktree.isClean) return "medium";
|
|
793
820
|
return "low";
|
|
794
821
|
}
|
|
@@ -801,10 +828,22 @@ function getRecommendedNextActions(status) {
|
|
|
801
828
|
switch (status.recommendedAction) {
|
|
802
829
|
case "init":
|
|
803
830
|
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.
|
|
831
|
+
case "pull":
|
|
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."];
|
|
833
|
+
case "re_anchor":
|
|
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
|
+
];
|
|
806
841
|
case "reconcile":
|
|
807
|
-
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."];
|
|
843
|
+
case "await_finalize":
|
|
844
|
+
return [
|
|
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."
|
|
846
|
+
];
|
|
808
847
|
case "review_queue":
|
|
809
848
|
return ["Run remix_collab_review_queue to inspect reviewable merge requests instead of using local git merge flows."];
|
|
810
849
|
case "choose_family":
|
|
@@ -838,6 +877,21 @@ function truncateText(value, maxChars) {
|
|
|
838
877
|
originalChars: value.length
|
|
839
878
|
};
|
|
840
879
|
}
|
|
880
|
+
function spawnFinalizeQueueDrainer() {
|
|
881
|
+
const entrypoint = process.argv[1];
|
|
882
|
+
if (!entrypoint) return false;
|
|
883
|
+
const child = spawn(process.execPath, [...process.execArgv, entrypoint, "--drain-finalize-queue"], {
|
|
884
|
+
detached: true,
|
|
885
|
+
stdio: "ignore",
|
|
886
|
+
env: process.env
|
|
887
|
+
});
|
|
888
|
+
child.unref();
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
async function drainBeforeMutation(api) {
|
|
892
|
+
const results = await coreDrainPendingFinalizeQueue({ api });
|
|
893
|
+
return results.flatMap((result) => collectResultWarnings(result));
|
|
894
|
+
}
|
|
841
895
|
async function getStatus(params) {
|
|
842
896
|
const api = params.includeRemote ? await createCollabApiClient() : null;
|
|
843
897
|
const status = await coreCollabStatus({
|
|
@@ -863,15 +917,26 @@ async function initCollab(params) {
|
|
|
863
917
|
api,
|
|
864
918
|
cwd: params.cwd,
|
|
865
919
|
appName: params.appName ?? null,
|
|
866
|
-
forceNew: params.forceNew ?? false
|
|
920
|
+
forceNew: params.forceNew ?? false,
|
|
921
|
+
asyncSubmit: false
|
|
867
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;
|
|
868
929
|
return {
|
|
869
|
-
data:
|
|
930
|
+
data: syncResult,
|
|
870
931
|
warnings: collectResultWarnings(result),
|
|
871
|
-
recommendedNextActions:
|
|
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" ? [
|
|
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."
|
|
936
|
+
] : ["Run remix_collab_status to inspect sync, reconcile, and merge-request readiness before mutating bound-repo state."],
|
|
872
937
|
logContext: {
|
|
873
|
-
repoRoot:
|
|
874
|
-
appId:
|
|
938
|
+
repoRoot: syncResult.repoRoot,
|
|
939
|
+
appId: syncResult.appId
|
|
875
940
|
}
|
|
876
941
|
};
|
|
877
942
|
}
|
|
@@ -938,23 +1003,46 @@ async function finalizeCollabTurn(params) {
|
|
|
938
1003
|
cwd: params.cwd,
|
|
939
1004
|
prompt: params.prompt,
|
|
940
1005
|
assistantResponse: params.assistantResponse,
|
|
941
|
-
diff: params.externalDiff ?? null,
|
|
942
|
-
diffSource: params.diffSource,
|
|
943
1006
|
sync: params.sync,
|
|
944
1007
|
allowBranchMismatch: params.allowBranchMismatch ?? false,
|
|
945
1008
|
idempotencyKey: params.idempotencyKey ?? null,
|
|
946
|
-
actor: params.agent
|
|
1009
|
+
actor: params.agent,
|
|
1010
|
+
awaitingUsageDeadlineMs: params.awaitingUsageDeadlineMs ?? null
|
|
947
1011
|
});
|
|
1012
|
+
const hasAwaitingDeadline = typeof params.awaitingUsageDeadlineMs === "number" && params.awaitingUsageDeadlineMs > 0;
|
|
1013
|
+
if (result.queued && !hasAwaitingDeadline) {
|
|
1014
|
+
if (!spawnFinalizeQueueDrainer()) {
|
|
1015
|
+
await coreDrainPendingFinalizeQueue({ api });
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
948
1018
|
return {
|
|
949
1019
|
data: result,
|
|
950
1020
|
warnings: result.warnings,
|
|
951
|
-
recommendedNextActions: [],
|
|
1021
|
+
recommendedNextActions: result.queued ? ["Run remix_collab_drain_finalize_queue before merge-related flows if you need this queued turn recorded immediately."] : [],
|
|
952
1022
|
logContext: {
|
|
953
1023
|
repoRoot,
|
|
954
1024
|
appId: result.changeStep?.appId ?? result.collabTurn?.appId ?? null
|
|
955
1025
|
}
|
|
956
1026
|
};
|
|
957
1027
|
}
|
|
1028
|
+
async function drainFinalizeQueue(params) {
|
|
1029
|
+
const api = await createCollabApiClient();
|
|
1030
|
+
const repoRoot = await findGitRoot(params.cwd);
|
|
1031
|
+
const results = await coreDrainPendingFinalizeQueue({ api });
|
|
1032
|
+
const warnings = results.flatMap((result) => collectResultWarnings(result));
|
|
1033
|
+
return {
|
|
1034
|
+
data: {
|
|
1035
|
+
processed: results.length,
|
|
1036
|
+
results
|
|
1037
|
+
},
|
|
1038
|
+
warnings,
|
|
1039
|
+
recommendedNextActions: [],
|
|
1040
|
+
logContext: {
|
|
1041
|
+
repoRoot,
|
|
1042
|
+
appId: null
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
958
1046
|
async function syncCollab(params) {
|
|
959
1047
|
const api = await createCollabApiClient();
|
|
960
1048
|
const result = await coreCollabSync({
|
|
@@ -966,21 +1054,45 @@ async function syncCollab(params) {
|
|
|
966
1054
|
return {
|
|
967
1055
|
data: result,
|
|
968
1056
|
warnings: collectResultWarnings(result),
|
|
969
|
-
recommendedNextActions: params.dryRun ? ["Run remix_collab_sync_apply with confirm=true to apply this
|
|
1057
|
+
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" ? [
|
|
1058
|
+
"Direct pull is unavailable because Remix cannot diff from the last acknowledged server head.",
|
|
1059
|
+
"Run remix_collab_reconcile_preview next to inspect recovery options before applying any recovery flow."
|
|
1060
|
+
] : [] : [],
|
|
970
1061
|
logContext: {
|
|
971
1062
|
repoRoot: result.repoRoot
|
|
972
1063
|
}
|
|
973
1064
|
};
|
|
974
1065
|
}
|
|
1066
|
+
async function reAnchor(params) {
|
|
1067
|
+
const api = await createCollabApiClient();
|
|
1068
|
+
const result = await coreCollabReAnchor({
|
|
1069
|
+
api,
|
|
1070
|
+
cwd: params.cwd,
|
|
1071
|
+
dryRun: params.dryRun,
|
|
1072
|
+
allowBranchMismatch: params.allowBranchMismatch ?? false
|
|
1073
|
+
});
|
|
1074
|
+
return {
|
|
1075
|
+
data: result,
|
|
1076
|
+
warnings: collectWarnings(result.warnings),
|
|
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
|
+
] : [],
|
|
1080
|
+
logContext: {
|
|
1081
|
+
repoRoot: result.repoRoot,
|
|
1082
|
+
appId: result.currentAppId
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
975
1086
|
async function requestMerge(params) {
|
|
976
1087
|
const api = await createCollabApiClient();
|
|
1088
|
+
const drainWarnings = await drainBeforeMutation(api);
|
|
977
1089
|
const result = await coreCollabRequestMerge({
|
|
978
1090
|
api,
|
|
979
1091
|
cwd: params.cwd
|
|
980
1092
|
});
|
|
981
1093
|
return {
|
|
982
1094
|
data: result,
|
|
983
|
-
warnings:
|
|
1095
|
+
warnings: drainWarnings,
|
|
984
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.`] : [],
|
|
985
1097
|
logContext: {
|
|
986
1098
|
mrId: typeof result.id === "string" ? result.id : null
|
|
@@ -1136,6 +1248,7 @@ async function syncUpstream(params) {
|
|
|
1136
1248
|
}
|
|
1137
1249
|
async function reconcile(params) {
|
|
1138
1250
|
const api = await createCollabApiClient();
|
|
1251
|
+
const drainWarnings = params.dryRun ? [] : await drainBeforeMutation(api);
|
|
1139
1252
|
const result = await coreCollabReconcile({
|
|
1140
1253
|
api,
|
|
1141
1254
|
cwd: params.cwd,
|
|
@@ -1144,9 +1257,9 @@ async function reconcile(params) {
|
|
|
1144
1257
|
});
|
|
1145
1258
|
return {
|
|
1146
1259
|
data: result,
|
|
1147
|
-
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
|
|
1260
|
+
warnings: [...drainWarnings, ...collectWarnings(result.warnings)],
|
|
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."] : [],
|
|
1262
|
+
risks: params.dryRun ? ["Reconcile may upload local history to recover the server lane onto the latest agreed state before future recording continues."] : [],
|
|
1150
1263
|
logContext: {
|
|
1151
1264
|
repoRoot: result.repoRoot ?? null
|
|
1152
1265
|
}
|
|
@@ -1693,6 +1806,50 @@ async function accessDebug(params) {
|
|
|
1693
1806
|
};
|
|
1694
1807
|
}
|
|
1695
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
|
+
|
|
1696
1853
|
// src/tools/collab/register.ts
|
|
1697
1854
|
function getAnnotations(access, options) {
|
|
1698
1855
|
if (access === "read") {
|
|
@@ -1722,9 +1879,6 @@ function buildSuccessEnvelope(tool, requestId, result) {
|
|
|
1722
1879
|
};
|
|
1723
1880
|
}
|
|
1724
1881
|
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
1882
|
if (normalized.code === "DESTRUCTIVE_OPERATION_BLOCKED") {
|
|
1729
1883
|
return ["A policy guard blocked a potentially destructive or state-mutating operation."];
|
|
1730
1884
|
}
|
|
@@ -1736,16 +1890,9 @@ function deriveErrorRisks(tool, normalized) {
|
|
|
1736
1890
|
}
|
|
1737
1891
|
return [];
|
|
1738
1892
|
}
|
|
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
1893
|
function buildErrorEnvelope(tool, requestId, error) {
|
|
1743
1894
|
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."] : [];
|
|
1895
|
+
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
1896
|
return {
|
|
1750
1897
|
schemaVersion: SCHEMA_VERSION,
|
|
1751
1898
|
ok: false,
|
|
@@ -1824,18 +1971,37 @@ function registerCollabTools(server, context) {
|
|
|
1824
1971
|
});
|
|
1825
1972
|
registerTool(server, context, {
|
|
1826
1973
|
name: "remix_collab_init",
|
|
1827
|
-
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.",
|
|
1828
1975
|
access: "remote_write",
|
|
1829
1976
|
inputSchema: initInputSchema,
|
|
1830
1977
|
outputSchema: initSuccessSchema,
|
|
1831
1978
|
run: async (args) => {
|
|
1832
1979
|
const input = z3.object(initInputSchema).parse(args);
|
|
1833
1980
|
const cwd = resolvePolicyCwd(context.policy, input.cwd);
|
|
1834
|
-
|
|
1981
|
+
const result = await initCollab({
|
|
1835
1982
|
cwd,
|
|
1836
1983
|
appName: input.appName,
|
|
1837
1984
|
forceNew: input.forceNew
|
|
1838
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;
|
|
1839
2005
|
}
|
|
1840
2006
|
});
|
|
1841
2007
|
registerTool(server, context, {
|
|
@@ -1891,7 +2057,7 @@ function registerCollabTools(server, context) {
|
|
|
1891
2057
|
});
|
|
1892
2058
|
registerTool(server, context, {
|
|
1893
2059
|
name: "remix_collab_finalize_turn",
|
|
1894
|
-
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.",
|
|
1895
2061
|
access: "local_write",
|
|
1896
2062
|
inputSchema: finalizeTurnInputSchema,
|
|
1897
2063
|
outputSchema: finalizeTurnSuccessSchema,
|
|
@@ -1899,25 +2065,34 @@ function registerCollabTools(server, context) {
|
|
|
1899
2065
|
run: async (args) => {
|
|
1900
2066
|
const input = z3.object(finalizeTurnInputSchema).parse(args);
|
|
1901
2067
|
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
2068
|
return finalizeCollabTurn({
|
|
1906
2069
|
cwd,
|
|
1907
2070
|
prompt: input.prompt,
|
|
1908
2071
|
assistantResponse: input.assistantResponse,
|
|
1909
|
-
diffSource: input.diffSource,
|
|
1910
|
-
externalDiff: input.externalDiff,
|
|
1911
2072
|
sync: input.sync,
|
|
1912
2073
|
allowBranchMismatch: input.allowBranchMismatch ?? false,
|
|
1913
2074
|
idempotencyKey: input.idempotencyKey,
|
|
1914
|
-
agent: context.agentMetadata
|
|
2075
|
+
agent: context.agentMetadata,
|
|
2076
|
+
awaitingUsageDeadlineMs: 3e4
|
|
1915
2077
|
});
|
|
1916
2078
|
}
|
|
1917
2079
|
});
|
|
2080
|
+
registerTool(server, context, {
|
|
2081
|
+
name: "remix_collab_drain_finalize_queue",
|
|
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.",
|
|
2083
|
+
access: "local_write",
|
|
2084
|
+
inputSchema: drainFinalizeQueueInputSchema,
|
|
2085
|
+
outputSchema: drainFinalizeQueueSuccessSchema,
|
|
2086
|
+
annotations: getAnnotations("local_write", { idempotent: true }),
|
|
2087
|
+
run: async (args) => {
|
|
2088
|
+
const input = z3.object(drainFinalizeQueueInputSchema).parse(args);
|
|
2089
|
+
const cwd = resolvePolicyCwd(context.policy, input.cwd);
|
|
2090
|
+
return drainFinalizeQueue({ cwd });
|
|
2091
|
+
}
|
|
2092
|
+
});
|
|
1918
2093
|
registerTool(server, context, {
|
|
1919
2094
|
name: "remix_collab_sync_preview",
|
|
1920
|
-
description: "Preview whether the current bound repository can
|
|
2095
|
+
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
2096
|
access: "read",
|
|
1922
2097
|
inputSchema: previewInputSchema,
|
|
1923
2098
|
outputSchema: syncSuccessSchema,
|
|
@@ -1929,7 +2104,7 @@ function registerCollabTools(server, context) {
|
|
|
1929
2104
|
});
|
|
1930
2105
|
registerTool(server, context, {
|
|
1931
2106
|
name: "remix_collab_sync_apply",
|
|
1932
|
-
description: "
|
|
2107
|
+
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
2108
|
access: "local_write",
|
|
1934
2109
|
inputSchema: applyInputSchema,
|
|
1935
2110
|
outputSchema: syncSuccessSchema,
|
|
@@ -1940,6 +2115,31 @@ function registerCollabTools(server, context) {
|
|
|
1940
2115
|
return syncCollab({ cwd, dryRun: false, allowBranchMismatch: input.allowBranchMismatch ?? false });
|
|
1941
2116
|
}
|
|
1942
2117
|
});
|
|
2118
|
+
registerTool(server, context, {
|
|
2119
|
+
name: "remix_collab_re_anchor_preview",
|
|
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.",
|
|
2121
|
+
access: "read",
|
|
2122
|
+
inputSchema: previewInputSchema,
|
|
2123
|
+
outputSchema: reAnchorSuccessSchema,
|
|
2124
|
+
run: async (args) => {
|
|
2125
|
+
const input = z3.object(previewInputSchema).parse(args);
|
|
2126
|
+
const cwd = resolvePolicyCwd(context.policy, input.cwd);
|
|
2127
|
+
return reAnchor({ cwd, dryRun: true });
|
|
2128
|
+
}
|
|
2129
|
+
});
|
|
2130
|
+
registerTool(server, context, {
|
|
2131
|
+
name: "remix_collab_re_anchor_apply",
|
|
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`.",
|
|
2133
|
+
access: "local_write",
|
|
2134
|
+
inputSchema: reAnchorInputSchema,
|
|
2135
|
+
outputSchema: reAnchorSuccessSchema,
|
|
2136
|
+
run: async (args) => {
|
|
2137
|
+
const input = z3.object(reAnchorInputSchema).parse(args);
|
|
2138
|
+
assertConfirm(input.confirm, "remix_collab_re_anchor_apply");
|
|
2139
|
+
const cwd = resolvePolicyCwd(context.policy, input.cwd);
|
|
2140
|
+
return reAnchor({ cwd, dryRun: false, allowBranchMismatch: input.allowBranchMismatch ?? false });
|
|
2141
|
+
}
|
|
2142
|
+
});
|
|
1943
2143
|
registerTool(server, context, {
|
|
1944
2144
|
name: "remix_collab_request_merge",
|
|
1945
2145
|
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 +2280,7 @@ function registerCollabTools(server, context) {
|
|
|
2080
2280
|
});
|
|
2081
2281
|
registerTool(server, context, {
|
|
2082
2282
|
name: "remix_collab_reconcile_preview",
|
|
2083
|
-
description: "Preview
|
|
2283
|
+
description: "Preview the explicit Remix recovery flow when local and server state diverged beyond a simple pull.",
|
|
2084
2284
|
access: "read",
|
|
2085
2285
|
inputSchema: previewInputSchema,
|
|
2086
2286
|
outputSchema: reconcileSuccessSchema,
|
|
@@ -2092,7 +2292,7 @@ function registerCollabTools(server, context) {
|
|
|
2092
2292
|
});
|
|
2093
2293
|
registerTool(server, context, {
|
|
2094
2294
|
name: "remix_collab_reconcile_apply",
|
|
2095
|
-
description: "
|
|
2295
|
+
description: "Run the explicit Remix recovery flow for diverged local/server state.",
|
|
2096
2296
|
access: "local_write",
|
|
2097
2297
|
inputSchema: applyInputSchema,
|
|
2098
2298
|
outputSchema: reconcileSuccessSchema,
|
|
@@ -3217,6 +3417,19 @@ import { z as z9 } from "zod";
|
|
|
3217
3417
|
// src/contracts/ops.ts
|
|
3218
3418
|
import { z as z8 } from "zod";
|
|
3219
3419
|
var genericRecordSchema4 = z8.record(z8.string(), z8.unknown());
|
|
3420
|
+
var appJobKindSchema = z8.enum([
|
|
3421
|
+
"fork",
|
|
3422
|
+
"edit",
|
|
3423
|
+
"bundle",
|
|
3424
|
+
"import_github",
|
|
3425
|
+
"import_upload",
|
|
3426
|
+
"merge",
|
|
3427
|
+
"revert",
|
|
3428
|
+
"change_step",
|
|
3429
|
+
"reconcile",
|
|
3430
|
+
"change_step_replay"
|
|
3431
|
+
]);
|
|
3432
|
+
var appJobStatusSchema = z8.enum(["pending", "enqueued", "processing", "succeeded", "failed", "cancelled"]);
|
|
3220
3433
|
var appScopedInputSchema = {
|
|
3221
3434
|
...commonRequestFieldsSchema,
|
|
3222
3435
|
appId: z8.string().trim().min(1).optional()
|
|
@@ -3226,6 +3439,13 @@ var editQueueInputSchema = {
|
|
|
3226
3439
|
limit: z8.number().int().positive().max(100).optional(),
|
|
3227
3440
|
offset: z8.number().int().nonnegative().optional()
|
|
3228
3441
|
};
|
|
3442
|
+
var appJobQueueInputSchema = {
|
|
3443
|
+
...appScopedInputSchema,
|
|
3444
|
+
limit: z8.number().int().positive().max(100).optional(),
|
|
3445
|
+
offset: z8.number().int().nonnegative().optional(),
|
|
3446
|
+
kind: z8.array(appJobKindSchema).min(1).optional(),
|
|
3447
|
+
status: z8.array(appJobStatusSchema).min(1).optional()
|
|
3448
|
+
};
|
|
3229
3449
|
var bundleInputSchema = {
|
|
3230
3450
|
...appScopedInputSchema,
|
|
3231
3451
|
bundleId: z8.string().trim().min(1)
|
|
@@ -3258,6 +3478,7 @@ var agentRunEventsInputSchema = {
|
|
|
3258
3478
|
};
|
|
3259
3479
|
var appOverviewSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3260
3480
|
var editQueueSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3481
|
+
var appJobQueueSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3261
3482
|
var bundleSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3262
3483
|
var timelineSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3263
3484
|
var agentRunsSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
@@ -3291,7 +3512,7 @@ async function getAppOverview(params) {
|
|
|
3291
3512
|
data,
|
|
3292
3513
|
warnings: [],
|
|
3293
3514
|
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."
|
|
3515
|
+
"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
3516
|
],
|
|
3296
3517
|
logContext: target
|
|
3297
3518
|
};
|
|
@@ -3314,6 +3535,42 @@ async function getEditQueue(params) {
|
|
|
3314
3535
|
logContext: target
|
|
3315
3536
|
};
|
|
3316
3537
|
}
|
|
3538
|
+
async function listAppJobQueue(params) {
|
|
3539
|
+
const api = await createApiClient();
|
|
3540
|
+
const target = await resolveAppTarget(api, params);
|
|
3541
|
+
const data = unwrapResponseObject(
|
|
3542
|
+
await api.listAppJobQueue(target.appId, {
|
|
3543
|
+
limit: params.limit,
|
|
3544
|
+
offset: params.offset,
|
|
3545
|
+
kind: params.kind,
|
|
3546
|
+
status: params.status
|
|
3547
|
+
}),
|
|
3548
|
+
"app job queue"
|
|
3549
|
+
);
|
|
3550
|
+
const pageInfo = typeof data.pageInfo === "object" && data.pageInfo ? data.pageInfo : null;
|
|
3551
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
3552
|
+
const activeBlockingKinds = /* @__PURE__ */ new Set(["merge", "change_step", "change_step_replay", "reconcile", "revert"]);
|
|
3553
|
+
const hasBlockingJobs = items.some((item) => {
|
|
3554
|
+
if (!item || typeof item !== "object") return false;
|
|
3555
|
+
const record = item;
|
|
3556
|
+
return typeof record.kind === "string" && activeBlockingKinds.has(record.kind) && (typeof record.status !== "string" || ["pending", "enqueued", "processing"].includes(record.status));
|
|
3557
|
+
});
|
|
3558
|
+
const recommendedNextActions = [];
|
|
3559
|
+
if (hasBlockingJobs) {
|
|
3560
|
+
recommendedNextActions.push(
|
|
3561
|
+
"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."
|
|
3562
|
+
);
|
|
3563
|
+
}
|
|
3564
|
+
if (pageInfo?.hasMore === true && typeof pageInfo.limit === "number" && typeof pageInfo.offset === "number") {
|
|
3565
|
+
recommendedNextActions.push(`Pass offset=${pageInfo.offset + pageInfo.limit} to load the next app job queue page.`);
|
|
3566
|
+
}
|
|
3567
|
+
return {
|
|
3568
|
+
data,
|
|
3569
|
+
warnings: [],
|
|
3570
|
+
recommendedNextActions,
|
|
3571
|
+
logContext: target
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3317
3574
|
async function getBundle(params) {
|
|
3318
3575
|
const api = await createApiClient();
|
|
3319
3576
|
const target = await resolveAppTarget(api, params);
|
|
@@ -3540,6 +3797,25 @@ function registerOpsTools(server, context) {
|
|
|
3540
3797
|
});
|
|
3541
3798
|
}
|
|
3542
3799
|
});
|
|
3800
|
+
registerTool4(server, context, {
|
|
3801
|
+
name: "remix_ops_list_app_job_queue",
|
|
3802
|
+
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.",
|
|
3803
|
+
access: "read",
|
|
3804
|
+
inputSchema: appJobQueueInputSchema,
|
|
3805
|
+
outputSchema: appJobQueueSuccessSchema,
|
|
3806
|
+
run: async (args) => {
|
|
3807
|
+
const input = z9.object(appJobQueueInputSchema).parse(args);
|
|
3808
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
3809
|
+
return listAppJobQueue({
|
|
3810
|
+
appId: input.appId,
|
|
3811
|
+
cwd,
|
|
3812
|
+
limit: input.limit,
|
|
3813
|
+
offset: input.offset,
|
|
3814
|
+
kind: input.kind,
|
|
3815
|
+
status: input.status
|
|
3816
|
+
});
|
|
3817
|
+
}
|
|
3818
|
+
});
|
|
3543
3819
|
registerTool4(server, context, {
|
|
3544
3820
|
name: "remix_ops_get_bundle",
|
|
3545
3821
|
description: "Inspect one app bundle by id without downloading the artifact payload.",
|
|
@@ -3670,6 +3946,16 @@ async function startStdioServer(params) {
|
|
|
3670
3946
|
var require2 = createRequire(import.meta.url);
|
|
3671
3947
|
var { version } = require2("../package.json");
|
|
3672
3948
|
async function main() {
|
|
3949
|
+
if (process.argv.includes("--drain-finalize-queue")) {
|
|
3950
|
+
const api = await createCollabApiClient();
|
|
3951
|
+
await drainPendingFinalizeQueue({ api });
|
|
3952
|
+
return;
|
|
3953
|
+
}
|
|
3954
|
+
if (process.argv.includes("--drain-async-jobs")) {
|
|
3955
|
+
const api = await createCollabApiClient();
|
|
3956
|
+
await drainAsyncJobs({ api });
|
|
3957
|
+
return;
|
|
3958
|
+
}
|
|
3673
3959
|
await startStdioServer({ version });
|
|
3674
3960
|
}
|
|
3675
3961
|
main().catch((error) => {
|