@remixhq/mcp 0.1.7 → 0.1.9
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 +1939 -302
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1939 -302
- package/dist/index.js.map +1 -1
- package/dist/server.js +1939 -302
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -86,6 +86,8 @@ var ERROR_CODES = {
|
|
|
86
86
|
METADATA_CONFLICT: "METADATA_CONFLICT",
|
|
87
87
|
DESTRUCTIVE_OPERATION_BLOCKED: "DESTRUCTIVE_OPERATION_BLOCKED",
|
|
88
88
|
CONFIG_INVALID: "CONFIG_INVALID",
|
|
89
|
+
ACCESS_DENIED: "ACCESS_DENIED",
|
|
90
|
+
RESOURCE_NOT_FOUND: "RESOURCE_NOT_FOUND",
|
|
89
91
|
INTERNAL_ERROR: "INTERNAL_ERROR"
|
|
90
92
|
};
|
|
91
93
|
var RemixMcpError = class extends Error {
|
|
@@ -103,6 +105,15 @@ function toStringOrNull(value) {
|
|
|
103
105
|
const trimmed = value.trim();
|
|
104
106
|
return trimmed.length > 0 ? trimmed : null;
|
|
105
107
|
}
|
|
108
|
+
function parseJsonObject(value) {
|
|
109
|
+
if (!value) return null;
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(value);
|
|
112
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
106
117
|
function makeNormalized(params) {
|
|
107
118
|
return {
|
|
108
119
|
code: params.code,
|
|
@@ -116,6 +127,8 @@ function normalizeByMessage(err) {
|
|
|
116
127
|
const code = toStringOrNull(err.code);
|
|
117
128
|
const message = toStringOrNull(err.message) ?? "Unexpected error.";
|
|
118
129
|
const hint = toStringOrNull(err.hint);
|
|
130
|
+
const hintBody = parseJsonObject(hint);
|
|
131
|
+
const statusCode = typeof hintBody?.statusCode === "number" ? hintBody.statusCode : typeof hintBody?.statusCode === "string" ? Number(hintBody.statusCode) : null;
|
|
119
132
|
if (code === ERROR_CODES.REPO_LOCK_HELD) {
|
|
120
133
|
return makeNormalized({
|
|
121
134
|
code: ERROR_CODES.REPO_LOCK_HELD,
|
|
@@ -216,6 +229,22 @@ function normalizeByMessage(err) {
|
|
|
216
229
|
category: "remote_state"
|
|
217
230
|
});
|
|
218
231
|
}
|
|
232
|
+
if (statusCode === 403) {
|
|
233
|
+
return makeNormalized({
|
|
234
|
+
code: ERROR_CODES.ACCESS_DENIED,
|
|
235
|
+
message,
|
|
236
|
+
hint,
|
|
237
|
+
category: "remote_state"
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
if (statusCode === 404) {
|
|
241
|
+
return makeNormalized({
|
|
242
|
+
code: ERROR_CODES.RESOURCE_NOT_FOUND,
|
|
243
|
+
message,
|
|
244
|
+
hint,
|
|
245
|
+
category: "remote_state"
|
|
246
|
+
});
|
|
247
|
+
}
|
|
219
248
|
if (message.includes("Timed out") || message.includes("failed") || message.includes("error state")) {
|
|
220
249
|
return makeNormalized({
|
|
221
250
|
code: ERROR_CODES.REMOTE_ERROR,
|
|
@@ -408,6 +437,11 @@ function makeErrorResult(envelope) {
|
|
|
408
437
|
// src/contracts/collab.ts
|
|
409
438
|
var genericRecordSchema = z2.record(z2.string(), z2.unknown());
|
|
410
439
|
var genericArraySchema = z2.array(genericRecordSchema);
|
|
440
|
+
var paginationSchema = z2.object({
|
|
441
|
+
limit: z2.number().int().positive(),
|
|
442
|
+
offset: z2.number().int().nonnegative(),
|
|
443
|
+
hasMore: z2.boolean()
|
|
444
|
+
});
|
|
411
445
|
var mergeRequestQueueSchema = z2.enum([
|
|
412
446
|
"reviewable",
|
|
413
447
|
"created_by_me",
|
|
@@ -429,7 +463,9 @@ var initInputSchema = {
|
|
|
429
463
|
var listInputSchema = {
|
|
430
464
|
requestId: z2.string().trim().min(1).optional(),
|
|
431
465
|
outputMode: z2.enum(["summary", "full"]).optional(),
|
|
432
|
-
forked: z2.enum(["only", "exclude", "all"]).optional()
|
|
466
|
+
forked: z2.enum(["only", "exclude", "all"]).optional(),
|
|
467
|
+
limit: z2.number().int().positive().max(50).optional(),
|
|
468
|
+
offset: z2.number().int().nonnegative().optional()
|
|
433
469
|
};
|
|
434
470
|
var remixInputSchema = {
|
|
435
471
|
...commonRequestFieldsSchema,
|
|
@@ -458,6 +494,16 @@ var recordTurnInputSchema = {
|
|
|
458
494
|
allowBranchMismatch: z2.boolean().optional(),
|
|
459
495
|
idempotencyKey: z2.string().trim().min(1).optional()
|
|
460
496
|
};
|
|
497
|
+
var finalizeTurnInputSchema = {
|
|
498
|
+
...commonRequestFieldsSchema,
|
|
499
|
+
prompt: z2.string().trim().min(1),
|
|
500
|
+
assistantResponse: z2.string().trim().min(1),
|
|
501
|
+
diffSource: z2.enum(["worktree", "external"]).optional(),
|
|
502
|
+
externalDiff: z2.string().optional(),
|
|
503
|
+
sync: z2.boolean().optional(),
|
|
504
|
+
allowBranchMismatch: z2.boolean().optional(),
|
|
505
|
+
idempotencyKey: z2.string().trim().min(1).optional()
|
|
506
|
+
};
|
|
461
507
|
var previewInputSchema = {
|
|
462
508
|
...commonRequestFieldsSchema
|
|
463
509
|
};
|
|
@@ -473,20 +519,26 @@ var reviewQueueInputSchema = {
|
|
|
473
519
|
requestId: z2.string().trim().min(1).optional(),
|
|
474
520
|
outputMode: z2.enum(["summary", "full"]).optional(),
|
|
475
521
|
status: z2.string().trim().min(1).optional(),
|
|
476
|
-
kind: z2.enum(["merge", "sync", "all"]).optional()
|
|
522
|
+
kind: z2.enum(["merge", "sync", "all"]).optional(),
|
|
523
|
+
limit: z2.number().int().positive().max(50).optional(),
|
|
524
|
+
offset: z2.number().int().nonnegative().optional()
|
|
477
525
|
};
|
|
478
526
|
var myMergeRequestsInputSchema = {
|
|
479
527
|
requestId: z2.string().trim().min(1).optional(),
|
|
480
528
|
outputMode: z2.enum(["summary", "full"]).optional(),
|
|
481
529
|
status: z2.string().trim().min(1).optional(),
|
|
482
|
-
kind: z2.enum(["merge", "sync", "all"]).optional()
|
|
530
|
+
kind: z2.enum(["merge", "sync", "all"]).optional(),
|
|
531
|
+
limit: z2.number().int().positive().max(50).optional(),
|
|
532
|
+
offset: z2.number().int().nonnegative().optional()
|
|
483
533
|
};
|
|
484
534
|
var appMergeRequestsInputSchema = {
|
|
485
535
|
...commonRequestFieldsSchema,
|
|
486
536
|
queue: appScopedMergeRequestQueueSchema,
|
|
487
537
|
appId: z2.string().trim().min(1).optional(),
|
|
488
538
|
status: z2.string().trim().min(1).optional(),
|
|
489
|
-
kind: z2.enum(["merge", "sync", "all"]).optional()
|
|
539
|
+
kind: z2.enum(["merge", "sync", "all"]).optional(),
|
|
540
|
+
limit: z2.number().int().positive().max(50).optional(),
|
|
541
|
+
offset: z2.number().int().nonnegative().optional()
|
|
490
542
|
};
|
|
491
543
|
var viewMergeRequestInputSchema = {
|
|
492
544
|
requestId: z2.string().trim().min(1).optional(),
|
|
@@ -517,6 +569,38 @@ var inviteInputSchema = {
|
|
|
517
569
|
var listMembersInputSchema = {
|
|
518
570
|
...commonRequestFieldsSchema,
|
|
519
571
|
scope: memberScopeSchema,
|
|
572
|
+
targetId: z2.string().trim().min(1).optional(),
|
|
573
|
+
limit: z2.number().int().positive().max(50).optional(),
|
|
574
|
+
offset: z2.number().int().nonnegative().optional()
|
|
575
|
+
};
|
|
576
|
+
var listInvitesInputSchema = {
|
|
577
|
+
...commonRequestFieldsSchema,
|
|
578
|
+
scope: memberScopeSchema.optional(),
|
|
579
|
+
targetId: z2.string().trim().min(1).optional(),
|
|
580
|
+
limit: z2.number().int().positive().max(50).optional(),
|
|
581
|
+
offset: z2.number().int().nonnegative().optional()
|
|
582
|
+
};
|
|
583
|
+
var resendInviteInputSchema = {
|
|
584
|
+
...commonRequestFieldsSchema,
|
|
585
|
+
scope: memberScopeSchema.optional(),
|
|
586
|
+
targetId: z2.string().trim().min(1).optional(),
|
|
587
|
+
inviteId: z2.string().trim().min(1),
|
|
588
|
+
ttlDays: z2.number().int().positive().max(30).optional()
|
|
589
|
+
};
|
|
590
|
+
var revokeInviteInputSchema = {
|
|
591
|
+
...commonRequestFieldsSchema,
|
|
592
|
+
scope: memberScopeSchema.optional(),
|
|
593
|
+
targetId: z2.string().trim().min(1).optional(),
|
|
594
|
+
inviteId: z2.string().trim().min(1),
|
|
595
|
+
confirm: z2.boolean()
|
|
596
|
+
};
|
|
597
|
+
var acceptInvitationInputSchema = {
|
|
598
|
+
requestId: z2.string().trim().min(1).optional(),
|
|
599
|
+
token: z2.string().trim().min(20)
|
|
600
|
+
};
|
|
601
|
+
var accessDebugInputSchema = {
|
|
602
|
+
...commonRequestFieldsSchema,
|
|
603
|
+
scope: memberScopeSchema.optional(),
|
|
520
604
|
targetId: z2.string().trim().min(1).optional()
|
|
521
605
|
};
|
|
522
606
|
var updateMemberRoleInputSchema = {
|
|
@@ -540,7 +624,8 @@ var initDataSchema = z2.object({
|
|
|
540
624
|
repoRoot: z2.string()
|
|
541
625
|
});
|
|
542
626
|
var listDataSchema = z2.object({
|
|
543
|
-
apps: genericArraySchema
|
|
627
|
+
apps: genericArraySchema,
|
|
628
|
+
pagination: paginationSchema
|
|
544
629
|
});
|
|
545
630
|
var remixDataSchema = z2.object({
|
|
546
631
|
appId: z2.string(),
|
|
@@ -563,12 +648,21 @@ var addDataSchema = z2.object({
|
|
|
563
648
|
autoSync: genericRecordSchema
|
|
564
649
|
});
|
|
565
650
|
var recordTurnDataSchema = genericRecordSchema;
|
|
651
|
+
var finalizeTurnDataSchema = z2.object({
|
|
652
|
+
mode: z2.enum(["changed_turn", "no_diff_turn"]),
|
|
653
|
+
idempotencyKey: z2.string().min(1),
|
|
654
|
+
changeStep: genericRecordSchema.nullable(),
|
|
655
|
+
collabTurn: genericRecordSchema.nullable(),
|
|
656
|
+
autoSync: genericRecordSchema.nullable(),
|
|
657
|
+
warnings: z2.array(z2.string())
|
|
658
|
+
});
|
|
566
659
|
var syncDataSchema = genericRecordSchema;
|
|
567
660
|
var requestMergeDataSchema = genericRecordSchema;
|
|
568
661
|
var mergeRequestQueueDataSchema = z2.object({
|
|
569
662
|
queue: mergeRequestQueueSchema,
|
|
570
663
|
appId: z2.string().nullable(),
|
|
571
|
-
mergeRequests: z2.array(genericRecordSchema)
|
|
664
|
+
mergeRequests: z2.array(genericRecordSchema),
|
|
665
|
+
pagination: paginationSchema
|
|
572
666
|
});
|
|
573
667
|
var viewMergeRequestDataSchema = genericRecordSchema;
|
|
574
668
|
var approveDataSchema = genericRecordSchema;
|
|
@@ -580,7 +674,49 @@ var memberRecordSchema = genericRecordSchema;
|
|
|
580
674
|
var listMembersDataSchema = z2.object({
|
|
581
675
|
scopeType: memberScopeSchema,
|
|
582
676
|
targetId: z2.string(),
|
|
583
|
-
members: z2.array(memberRecordSchema)
|
|
677
|
+
members: z2.array(memberRecordSchema),
|
|
678
|
+
pagination: paginationSchema
|
|
679
|
+
});
|
|
680
|
+
var listInvitesDataSchema = z2.object({
|
|
681
|
+
scopeType: memberScopeSchema,
|
|
682
|
+
targetId: z2.string(),
|
|
683
|
+
invites: z2.array(genericRecordSchema),
|
|
684
|
+
pagination: paginationSchema
|
|
685
|
+
});
|
|
686
|
+
var resendInviteDataSchema = genericRecordSchema;
|
|
687
|
+
var revokeInviteDataSchema = z2.object({
|
|
688
|
+
scopeType: memberScopeSchema,
|
|
689
|
+
targetId: z2.string(),
|
|
690
|
+
inviteId: z2.string(),
|
|
691
|
+
revoked: z2.boolean()
|
|
692
|
+
});
|
|
693
|
+
var acceptInvitationDataSchema = genericRecordSchema;
|
|
694
|
+
var accessDebugDataSchema = z2.object({
|
|
695
|
+
viewer: genericRecordSchema,
|
|
696
|
+
scope: z2.object({
|
|
697
|
+
scopeType: memberScopeSchema,
|
|
698
|
+
targetId: z2.string()
|
|
699
|
+
}),
|
|
700
|
+
entities: z2.object({
|
|
701
|
+
organization: genericRecordSchema.nullable(),
|
|
702
|
+
project: genericRecordSchema.nullable(),
|
|
703
|
+
app: genericRecordSchema.nullable()
|
|
704
|
+
}),
|
|
705
|
+
binding: z2.object({
|
|
706
|
+
repoRoot: z2.string().nullable(),
|
|
707
|
+
binding: genericRecordSchema.nullable()
|
|
708
|
+
}),
|
|
709
|
+
access: z2.object({
|
|
710
|
+
appContext: genericRecordSchema.nullable(),
|
|
711
|
+
viewerMember: genericRecordSchema.nullable(),
|
|
712
|
+
viewerProjectMember: genericRecordSchema.nullable(),
|
|
713
|
+
inviteForViewerEmail: genericRecordSchema.nullable(),
|
|
714
|
+
effectiveAppAccess: genericRecordSchema.nullable()
|
|
715
|
+
}),
|
|
716
|
+
members: z2.array(genericRecordSchema),
|
|
717
|
+
membersPageInfo: paginationSchema,
|
|
718
|
+
invites: z2.array(genericRecordSchema),
|
|
719
|
+
invitesPageInfo: paginationSchema
|
|
584
720
|
});
|
|
585
721
|
var updateMemberRoleDataSchema = z2.object({
|
|
586
722
|
scopeType: memberScopeSchema,
|
|
@@ -594,6 +730,7 @@ var remixSuccessSchema = makeSuccessSchema(remixDataSchema);
|
|
|
594
730
|
var checkoutSuccessSchema = makeSuccessSchema(checkoutDataSchema);
|
|
595
731
|
var addSuccessSchema = makeSuccessSchema(addDataSchema);
|
|
596
732
|
var recordTurnSuccessSchema = makeSuccessSchema(recordTurnDataSchema);
|
|
733
|
+
var finalizeTurnSuccessSchema = makeSuccessSchema(finalizeTurnDataSchema);
|
|
597
734
|
var syncSuccessSchema = makeSuccessSchema(syncDataSchema);
|
|
598
735
|
var requestMergeSuccessSchema = makeSuccessSchema(requestMergeDataSchema);
|
|
599
736
|
var mergeRequestQueueSuccessSchema = makeSuccessSchema(mergeRequestQueueDataSchema);
|
|
@@ -604,13 +741,20 @@ var syncUpstreamSuccessSchema = makeSuccessSchema(syncUpstreamDataSchema);
|
|
|
604
741
|
var reconcileSuccessSchema = makeSuccessSchema(reconcileDataSchema);
|
|
605
742
|
var inviteSuccessSchema = makeSuccessSchema(inviteDataSchema);
|
|
606
743
|
var listMembersSuccessSchema = makeSuccessSchema(listMembersDataSchema);
|
|
744
|
+
var listInvitesSuccessSchema = makeSuccessSchema(listInvitesDataSchema);
|
|
745
|
+
var resendInviteSuccessSchema = makeSuccessSchema(resendInviteDataSchema);
|
|
746
|
+
var revokeInviteSuccessSchema = makeSuccessSchema(revokeInviteDataSchema);
|
|
747
|
+
var acceptInvitationSuccessSchema = makeSuccessSchema(acceptInvitationDataSchema);
|
|
748
|
+
var accessDebugSuccessSchema = makeSuccessSchema(accessDebugDataSchema);
|
|
607
749
|
var updateMemberRoleSuccessSchema = makeSuccessSchema(updateMemberRoleDataSchema);
|
|
608
750
|
|
|
609
751
|
// src/domain/coreAdapter.ts
|
|
610
752
|
import {
|
|
753
|
+
collabList as coreCollabList,
|
|
611
754
|
collabListMembers as coreCollabListMembers,
|
|
612
755
|
collabUpdateMemberRole as coreCollabUpdateMemberRole,
|
|
613
756
|
collabAdd as coreCollabAdd,
|
|
757
|
+
collabFinalizeTurn as coreCollabFinalizeTurn,
|
|
614
758
|
collabRecordTurn as coreCollabRecordTurn,
|
|
615
759
|
collabApprove as coreCollabApprove,
|
|
616
760
|
collabCheckout as coreCollabCheckout,
|
|
@@ -627,13 +771,6 @@ import {
|
|
|
627
771
|
collabView as coreCollabView
|
|
628
772
|
} from "@remixhq/core/collab";
|
|
629
773
|
import { findGitRoot, getHeadCommitHash, listUntrackedFiles } from "@remixhq/core/repo";
|
|
630
|
-
function unwrapResponseObject(resp, label) {
|
|
631
|
-
const obj = resp?.responseObject;
|
|
632
|
-
if (obj === void 0 || obj === null) {
|
|
633
|
-
throw new Error(typeof resp?.message === "string" && resp.message.trim() ? resp.message : `Missing ${label} response`);
|
|
634
|
-
}
|
|
635
|
-
return obj;
|
|
636
|
-
}
|
|
637
774
|
function getRiskLevel(status) {
|
|
638
775
|
if (status.recommendedAction === "reconcile") return "high";
|
|
639
776
|
if (status.recommendedAction === "sync" || status.remote.incomingOpenMergeRequestCount) return "medium";
|
|
@@ -720,13 +857,17 @@ async function initCollab(params) {
|
|
|
720
857
|
};
|
|
721
858
|
}
|
|
722
859
|
async function listApps(params) {
|
|
723
|
-
const api = await
|
|
724
|
-
const
|
|
725
|
-
|
|
860
|
+
const api = await createCollabApiClient();
|
|
861
|
+
const result = await coreCollabList({
|
|
862
|
+
api,
|
|
863
|
+
forked: params.forked,
|
|
864
|
+
limit: params.limit,
|
|
865
|
+
offset: params.offset
|
|
866
|
+
});
|
|
726
867
|
return {
|
|
727
|
-
data:
|
|
868
|
+
data: result,
|
|
728
869
|
warnings: [],
|
|
729
|
-
recommendedNextActions: [],
|
|
870
|
+
recommendedNextActions: result.pagination.hasMore ? [`Pass offset=${result.pagination.offset + result.pagination.limit} to load the next page.`] : [],
|
|
730
871
|
logContext: {}
|
|
731
872
|
};
|
|
732
873
|
}
|
|
@@ -767,70 +908,28 @@ async function checkoutCollab(params) {
|
|
|
767
908
|
}
|
|
768
909
|
};
|
|
769
910
|
}
|
|
770
|
-
async function
|
|
911
|
+
async function finalizeCollabTurn(params) {
|
|
771
912
|
const api = await createCollabApiClient();
|
|
772
913
|
const repoRoot = await findGitRoot(params.cwd);
|
|
773
|
-
const
|
|
774
|
-
const untrackedBefore = params.diffSource === "worktree" ? await listUntrackedFiles(repoRoot) : [];
|
|
775
|
-
const changeStep = await coreCollabAdd({
|
|
914
|
+
const result = await coreCollabFinalizeTurn({
|
|
776
915
|
api,
|
|
777
916
|
cwd: params.cwd,
|
|
778
917
|
prompt: params.prompt,
|
|
779
|
-
assistantResponse: params.assistantResponse
|
|
918
|
+
assistantResponse: params.assistantResponse,
|
|
780
919
|
diff: params.externalDiff ?? null,
|
|
781
920
|
diffSource: params.diffSource,
|
|
782
|
-
|
|
783
|
-
idempotencyKey: params.idempotencyKey ?? null,
|
|
784
|
-
actor: params.agent
|
|
785
|
-
});
|
|
786
|
-
const postHead = await getHeadCommitHash(repoRoot);
|
|
787
|
-
const autoSyncEligible = params.diffSource === "worktree";
|
|
788
|
-
const localRepoMutated = autoSyncEligible && preHead !== postHead;
|
|
789
|
-
return {
|
|
790
|
-
data: {
|
|
791
|
-
changeStep,
|
|
792
|
-
autoSync: {
|
|
793
|
-
requested: true,
|
|
794
|
-
eligible: autoSyncEligible,
|
|
795
|
-
attempted: autoSyncEligible,
|
|
796
|
-
applied: autoSyncEligible,
|
|
797
|
-
trackedChangesDiscarded: autoSyncEligible,
|
|
798
|
-
capturedUntrackedPathsCandidate: untrackedBefore,
|
|
799
|
-
localHeadBefore: preHead,
|
|
800
|
-
localHeadAfter: postHead,
|
|
801
|
-
localRepoMutated
|
|
802
|
-
}
|
|
803
|
-
},
|
|
804
|
-
warnings: [
|
|
805
|
-
...collectResultWarnings(changeStep),
|
|
806
|
-
...params.diffSource === "external" ? [
|
|
807
|
-
"Automatic local discard+sync was skipped because the diff came from an external source and may not match the current worktree."
|
|
808
|
-
] : []
|
|
809
|
-
],
|
|
810
|
-
recommendedNextActions: [],
|
|
811
|
-
logContext: {
|
|
812
|
-
repoRoot
|
|
813
|
-
}
|
|
814
|
-
};
|
|
815
|
-
}
|
|
816
|
-
async function recordCollabTurn(params) {
|
|
817
|
-
const api = await createCollabApiClient();
|
|
818
|
-
const result = await coreCollabRecordTurn({
|
|
819
|
-
api,
|
|
820
|
-
cwd: params.cwd,
|
|
821
|
-
prompt: params.prompt,
|
|
822
|
-
assistantResponse: params.assistantResponse,
|
|
921
|
+
sync: params.sync,
|
|
823
922
|
allowBranchMismatch: params.allowBranchMismatch ?? false,
|
|
824
923
|
idempotencyKey: params.idempotencyKey ?? null,
|
|
825
924
|
actor: params.agent
|
|
826
925
|
});
|
|
827
926
|
return {
|
|
828
927
|
data: result,
|
|
829
|
-
warnings:
|
|
928
|
+
warnings: result.warnings,
|
|
830
929
|
recommendedNextActions: [],
|
|
831
930
|
logContext: {
|
|
832
|
-
repoRoot
|
|
833
|
-
appId: result.appId
|
|
931
|
+
repoRoot,
|
|
932
|
+
appId: result.changeStep?.appId ?? result.collabTurn?.appId ?? null
|
|
834
933
|
}
|
|
835
934
|
};
|
|
836
935
|
}
|
|
@@ -872,16 +971,19 @@ async function reviewQueue(params) {
|
|
|
872
971
|
api,
|
|
873
972
|
queue: "reviewable",
|
|
874
973
|
status: params.status ?? "open",
|
|
875
|
-
kind: params.kind ?? "merge"
|
|
974
|
+
kind: params.kind ?? "merge",
|
|
975
|
+
limit: params.limit,
|
|
976
|
+
offset: params.offset
|
|
876
977
|
});
|
|
877
978
|
return {
|
|
878
979
|
data: {
|
|
879
980
|
queue: result.queue,
|
|
880
981
|
appId: result.appId,
|
|
881
|
-
mergeRequests: result.mergeRequests
|
|
982
|
+
mergeRequests: result.mergeRequests,
|
|
983
|
+
pagination: result.pagination
|
|
882
984
|
},
|
|
883
985
|
warnings: [],
|
|
884
|
-
recommendedNextActions: [],
|
|
986
|
+
recommendedNextActions: result.pagination.hasMore ? [`Pass offset=${result.pagination.offset + result.pagination.limit} to load the next page.`] : [],
|
|
885
987
|
logContext: {}
|
|
886
988
|
};
|
|
887
989
|
}
|
|
@@ -891,16 +993,19 @@ async function myMergeRequests(params) {
|
|
|
891
993
|
api,
|
|
892
994
|
queue: "created_by_me",
|
|
893
995
|
status: params.status ?? "open",
|
|
894
|
-
kind: params.kind ?? "merge"
|
|
996
|
+
kind: params.kind ?? "merge",
|
|
997
|
+
limit: params.limit,
|
|
998
|
+
offset: params.offset
|
|
895
999
|
});
|
|
896
1000
|
return {
|
|
897
1001
|
data: {
|
|
898
1002
|
queue: result.queue,
|
|
899
1003
|
appId: result.appId,
|
|
900
|
-
mergeRequests: result.mergeRequests
|
|
1004
|
+
mergeRequests: result.mergeRequests,
|
|
1005
|
+
pagination: result.pagination
|
|
901
1006
|
},
|
|
902
1007
|
warnings: [],
|
|
903
|
-
recommendedNextActions: [],
|
|
1008
|
+
recommendedNextActions: result.pagination.hasMore ? [`Pass offset=${result.pagination.offset + result.pagination.limit} to load the next page.`] : [],
|
|
904
1009
|
logContext: {}
|
|
905
1010
|
};
|
|
906
1011
|
}
|
|
@@ -912,16 +1017,19 @@ async function listAppMergeRequests(params) {
|
|
|
912
1017
|
appId: params.appId,
|
|
913
1018
|
queue: params.queue,
|
|
914
1019
|
status: params.status ?? "open",
|
|
915
|
-
kind: params.kind ?? "merge"
|
|
1020
|
+
kind: params.kind ?? "merge",
|
|
1021
|
+
limit: params.limit,
|
|
1022
|
+
offset: params.offset
|
|
916
1023
|
});
|
|
917
1024
|
return {
|
|
918
1025
|
data: {
|
|
919
1026
|
queue: result.queue,
|
|
920
1027
|
appId: result.appId,
|
|
921
|
-
mergeRequests: result.mergeRequests
|
|
1028
|
+
mergeRequests: result.mergeRequests,
|
|
1029
|
+
pagination: result.pagination
|
|
922
1030
|
},
|
|
923
1031
|
warnings: [],
|
|
924
|
-
recommendedNextActions: [],
|
|
1032
|
+
recommendedNextActions: result.pagination.hasMore ? [`Pass offset=${result.pagination.offset + result.pagination.limit} to load the next page.`] : [],
|
|
925
1033
|
logContext: {
|
|
926
1034
|
appId: result.appId
|
|
927
1035
|
}
|
|
@@ -1046,12 +1154,14 @@ async function listMembers(params) {
|
|
|
1046
1154
|
api,
|
|
1047
1155
|
cwd: params.cwd,
|
|
1048
1156
|
scope: params.scope,
|
|
1049
|
-
targetId: params.targetId ?? null
|
|
1157
|
+
targetId: params.targetId ?? null,
|
|
1158
|
+
limit: params.limit,
|
|
1159
|
+
offset: params.offset
|
|
1050
1160
|
});
|
|
1051
1161
|
return {
|
|
1052
1162
|
data: result,
|
|
1053
1163
|
warnings: [],
|
|
1054
|
-
recommendedNextActions: [],
|
|
1164
|
+
recommendedNextActions: result.pagination.hasMore ? [`Pass offset=${result.pagination.offset + result.pagination.limit} to load the next page.`] : [],
|
|
1055
1165
|
logContext: {}
|
|
1056
1166
|
};
|
|
1057
1167
|
}
|
|
@@ -1073,83 +1183,578 @@ async function updateMemberRole(params) {
|
|
|
1073
1183
|
};
|
|
1074
1184
|
}
|
|
1075
1185
|
|
|
1076
|
-
// src/
|
|
1077
|
-
|
|
1078
|
-
|
|
1186
|
+
// src/domain/shared.ts
|
|
1187
|
+
import { readCollabBinding } from "@remixhq/core/binding";
|
|
1188
|
+
import { findGitRoot as findGitRoot2 } from "@remixhq/core/repo";
|
|
1189
|
+
function unwrapResponseObject(resp, label) {
|
|
1190
|
+
const obj = resp?.responseObject;
|
|
1191
|
+
if (obj === void 0 || obj === null) {
|
|
1192
|
+
throw new Error(typeof resp?.message === "string" && resp.message.trim() ? resp.message : `Missing ${label} response`);
|
|
1193
|
+
}
|
|
1194
|
+
return obj;
|
|
1195
|
+
}
|
|
1196
|
+
async function maybeFindGitRoot(cwd) {
|
|
1197
|
+
try {
|
|
1198
|
+
return await findGitRoot2(cwd);
|
|
1199
|
+
} catch {
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
async function loadBindingContext(cwd) {
|
|
1204
|
+
if (!cwd) return { repoRoot: null, binding: null };
|
|
1205
|
+
const repoRoot = await maybeFindGitRoot(cwd);
|
|
1206
|
+
if (!repoRoot) return { repoRoot: null, binding: null };
|
|
1207
|
+
const binding = await readCollabBinding(repoRoot);
|
|
1208
|
+
return {
|
|
1209
|
+
repoRoot,
|
|
1210
|
+
binding
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
function makeNotBoundError(message = "Repository is not bound to Remix.", hint) {
|
|
1214
|
+
const error = new Error(message);
|
|
1215
|
+
error.hint = hint ?? "Run `remix_collab_init` in this repository, or pass the explicit id for a direct read.";
|
|
1216
|
+
return error;
|
|
1217
|
+
}
|
|
1218
|
+
function parseJsonObject2(value) {
|
|
1219
|
+
if (!value) return null;
|
|
1220
|
+
try {
|
|
1221
|
+
const parsed = JSON.parse(value);
|
|
1222
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
1223
|
+
} catch {
|
|
1224
|
+
return null;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
function getBackendStatusCode(error) {
|
|
1228
|
+
if (!error || typeof error !== "object") return null;
|
|
1229
|
+
const hint = "hint" in error && typeof error.hint === "string" ? error.hint : null;
|
|
1230
|
+
const body = parseJsonObject2(hint);
|
|
1231
|
+
if (typeof body?.statusCode === "number") return body.statusCode;
|
|
1232
|
+
if (typeof body?.statusCode === "string") {
|
|
1233
|
+
const parsed = Number(body.statusCode);
|
|
1234
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1235
|
+
}
|
|
1236
|
+
return null;
|
|
1237
|
+
}
|
|
1238
|
+
function isBackendForbidden(error) {
|
|
1239
|
+
return getBackendStatusCode(error) === 403;
|
|
1240
|
+
}
|
|
1241
|
+
function isBackendNotFound(error) {
|
|
1242
|
+
return getBackendStatusCode(error) === 404;
|
|
1243
|
+
}
|
|
1244
|
+
function createAccessDeniedError(message, hint) {
|
|
1245
|
+
return new RemixMcpError({
|
|
1246
|
+
code: ERROR_CODES.ACCESS_DENIED,
|
|
1247
|
+
message,
|
|
1248
|
+
hint: hint ?? null,
|
|
1249
|
+
retryable: false,
|
|
1250
|
+
category: "remote_state"
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// src/domain/collabAdminAdapter.ts
|
|
1255
|
+
var MEMBERSHIP_PAGE_SIZE = 100;
|
|
1256
|
+
function normalizePagination(params) {
|
|
1257
|
+
const rawLimit = typeof params?.limit === "number" ? Math.trunc(params.limit) : 25;
|
|
1258
|
+
const rawOffset = typeof params?.offset === "number" ? Math.trunc(params.offset) : 0;
|
|
1259
|
+
return {
|
|
1260
|
+
limit: Math.max(1, Math.min(50, rawLimit)),
|
|
1261
|
+
offset: Math.max(0, rawOffset)
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
async function resolveScopeTarget(api, params) {
|
|
1265
|
+
const explicitTargetId = params.targetId?.trim();
|
|
1266
|
+
const bindingContext = await loadBindingContext(params.cwd);
|
|
1267
|
+
if (explicitTargetId) {
|
|
1079
1268
|
return {
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1269
|
+
scopeType: params.scope,
|
|
1270
|
+
targetId: explicitTargetId,
|
|
1271
|
+
repoRoot: bindingContext.repoRoot
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
if (!bindingContext.binding) {
|
|
1275
|
+
throw makeNotBoundError("Scope target was not provided and the current repository is not bound to Remix.");
|
|
1276
|
+
}
|
|
1277
|
+
if (params.scope === "app") {
|
|
1278
|
+
return {
|
|
1279
|
+
scopeType: "app",
|
|
1280
|
+
targetId: bindingContext.binding.currentAppId,
|
|
1281
|
+
repoRoot: bindingContext.repoRoot
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
const appContext = unwrapResponseObject(
|
|
1285
|
+
await api.getAppContext(bindingContext.binding.currentAppId),
|
|
1286
|
+
"bound app context"
|
|
1287
|
+
);
|
|
1288
|
+
if (params.scope === "project") {
|
|
1289
|
+
if (!appContext.readableScopes.project) {
|
|
1290
|
+
throw createAccessDeniedError(
|
|
1291
|
+
"The bound app's project is not readable to the current user.",
|
|
1292
|
+
"Use `scope=app` for app-level diagnostics, or pass an explicit readable project id."
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
return {
|
|
1296
|
+
scopeType: "project",
|
|
1297
|
+
targetId: appContext.projectId,
|
|
1298
|
+
repoRoot: bindingContext.repoRoot
|
|
1083
1299
|
};
|
|
1084
1300
|
}
|
|
1301
|
+
if (!appContext.readableScopes.organization) {
|
|
1302
|
+
throw createAccessDeniedError(
|
|
1303
|
+
"The bound app's organization is not readable to the current user.",
|
|
1304
|
+
"Use `scope=app` or `scope=project` for narrower diagnostics, or pass an explicit readable organization id."
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1085
1307
|
return {
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
openWorldHint: false
|
|
1308
|
+
scopeType: "organization",
|
|
1309
|
+
targetId: appContext.organizationId,
|
|
1310
|
+
repoRoot: bindingContext.repoRoot
|
|
1090
1311
|
};
|
|
1091
1312
|
}
|
|
1092
|
-
function
|
|
1313
|
+
async function getScopeEntity(api, scopeType, targetId) {
|
|
1314
|
+
if (scopeType === "organization") {
|
|
1315
|
+
return {
|
|
1316
|
+
organization: unwrapResponseObject(await api.getOrganization(targetId), "organization"),
|
|
1317
|
+
project: null,
|
|
1318
|
+
app: null
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
if (scopeType === "project") {
|
|
1322
|
+
const project2 = unwrapResponseObject(await api.getProject(targetId), "project");
|
|
1323
|
+
const organizationId2 = typeof project2.organizationId === "string" ? project2.organizationId : null;
|
|
1324
|
+
return {
|
|
1325
|
+
organization: organizationId2 == null ? null : unwrapResponseObject(await api.getOrganization(organizationId2), "organization"),
|
|
1326
|
+
project: project2,
|
|
1327
|
+
app: null
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
const app = unwrapResponseObject(await api.getApp(targetId), "app");
|
|
1331
|
+
const projectId = typeof app.projectId === "string" ? app.projectId : null;
|
|
1332
|
+
const project = projectId == null ? null : unwrapResponseObject(await api.getProject(projectId), "project");
|
|
1333
|
+
const organizationId = typeof project?.organizationId === "string" ? project.organizationId : null;
|
|
1093
1334
|
return {
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
requestId: requestId ?? null,
|
|
1098
|
-
data: result.data,
|
|
1099
|
-
warnings: result.warnings ?? [],
|
|
1100
|
-
risks: result.risks ?? [],
|
|
1101
|
-
recommendedNextActions: result.recommendedNextActions ?? []
|
|
1335
|
+
organization: organizationId == null ? null : unwrapResponseObject(await api.getOrganization(organizationId), "organization"),
|
|
1336
|
+
project,
|
|
1337
|
+
app
|
|
1102
1338
|
};
|
|
1103
1339
|
}
|
|
1104
|
-
function
|
|
1105
|
-
|
|
1106
|
-
|
|
1340
|
+
function mapProjectRoleToAppRole(role) {
|
|
1341
|
+
switch (role) {
|
|
1342
|
+
case "owner":
|
|
1343
|
+
case "maintainer":
|
|
1344
|
+
return "maintainer";
|
|
1345
|
+
case "editor":
|
|
1346
|
+
return "editor";
|
|
1347
|
+
case "viewer":
|
|
1348
|
+
return "viewer";
|
|
1349
|
+
default:
|
|
1350
|
+
return null;
|
|
1107
1351
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1352
|
+
}
|
|
1353
|
+
function strongestRole(...roles) {
|
|
1354
|
+
if (roles.includes("owner")) return "owner";
|
|
1355
|
+
if (roles.includes("maintainer")) return "maintainer";
|
|
1356
|
+
if (roles.includes("editor")) return "editor";
|
|
1357
|
+
if (roles.includes("viewer")) return "viewer";
|
|
1358
|
+
return null;
|
|
1359
|
+
}
|
|
1360
|
+
function resolveAppAccessSource(params) {
|
|
1361
|
+
if (params.directAppRole && params.inheritedProjectRole) return "both";
|
|
1362
|
+
if (params.directAppRole) return "direct_app_membership";
|
|
1363
|
+
if (params.inheritedProjectRole) return "project_membership";
|
|
1364
|
+
return "none";
|
|
1365
|
+
}
|
|
1366
|
+
function buildEffectiveAppAccess(params) {
|
|
1367
|
+
const directAppRole = params.directAppRole ?? null;
|
|
1368
|
+
const inheritedProjectRole = mapProjectRoleToAppRole(params.projectRole);
|
|
1369
|
+
const effectiveRole = strongestRole(directAppRole, inheritedProjectRole);
|
|
1370
|
+
return {
|
|
1371
|
+
effectiveRole,
|
|
1372
|
+
directAppRole,
|
|
1373
|
+
projectRole: params.projectRole,
|
|
1374
|
+
inheritedProjectRole,
|
|
1375
|
+
accessSource: resolveAppAccessSource({ directAppRole, inheritedProjectRole }),
|
|
1376
|
+
canReadWorkflow: effectiveRole !== null,
|
|
1377
|
+
canEdit: effectiveRole === "owner" || effectiveRole === "maintainer" || effectiveRole === "editor",
|
|
1378
|
+
canManage: effectiveRole === "owner" || effectiveRole === "maintainer",
|
|
1379
|
+
isOwner: directAppRole === "owner"
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
async function listMembersForScope(api, scopeType, targetId, params) {
|
|
1383
|
+
if (scopeType === "organization") {
|
|
1384
|
+
return unwrapResponseObject(await api.listOrganizationMembers(targetId, params), "organization members");
|
|
1110
1385
|
}
|
|
1111
|
-
if (
|
|
1112
|
-
return
|
|
1386
|
+
if (scopeType === "project") {
|
|
1387
|
+
return unwrapResponseObject(await api.listProjectMembers(targetId, params), "project members");
|
|
1113
1388
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1389
|
+
return unwrapResponseObject(await api.listAppMembers(targetId, params), "app members");
|
|
1390
|
+
}
|
|
1391
|
+
async function loadFirstPageSample(fetchPage) {
|
|
1392
|
+
const items = await fetchPage(MEMBERSHIP_PAGE_SIZE, 0);
|
|
1393
|
+
const hasMore = items.length < MEMBERSHIP_PAGE_SIZE ? false : (await fetchPage(1, MEMBERSHIP_PAGE_SIZE)).length > 0;
|
|
1394
|
+
return {
|
|
1395
|
+
items,
|
|
1396
|
+
pageInfo: {
|
|
1397
|
+
limit: MEMBERSHIP_PAGE_SIZE,
|
|
1398
|
+
offset: 0,
|
|
1399
|
+
hasMore
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
function emptyFirstPageSample() {
|
|
1404
|
+
return {
|
|
1405
|
+
items: [],
|
|
1406
|
+
pageInfo: {
|
|
1407
|
+
limit: MEMBERSHIP_PAGE_SIZE,
|
|
1408
|
+
offset: 0,
|
|
1409
|
+
hasMore: false
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
async function listInvitesForScope(api, scopeType, targetId, params) {
|
|
1414
|
+
if (scopeType === "organization") {
|
|
1415
|
+
return unwrapResponseObject(
|
|
1416
|
+
await api.listOrganizationInvites(targetId, params),
|
|
1417
|
+
"organization invites"
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
if (scopeType === "project") {
|
|
1421
|
+
return unwrapResponseObject(
|
|
1422
|
+
await api.listProjectInvites(targetId, params),
|
|
1423
|
+
"project invites"
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1426
|
+
return unwrapResponseObject(await api.listAppInvites(targetId, params), "app invites");
|
|
1427
|
+
}
|
|
1428
|
+
function getSelfMember(meId, members) {
|
|
1429
|
+
return meId == null ? null : members.find((member) => member.userId === meId || member.user_id === meId) ?? null;
|
|
1430
|
+
}
|
|
1431
|
+
async function findPendingInviteForEmailForScope(api, scopeType, targetId, email) {
|
|
1432
|
+
if (email == null) return null;
|
|
1433
|
+
let offset = 0;
|
|
1434
|
+
for (; ; ) {
|
|
1435
|
+
const invites = await listInvitesForScope(api, scopeType, targetId, {
|
|
1436
|
+
limit: MEMBERSHIP_PAGE_SIZE,
|
|
1437
|
+
offset
|
|
1438
|
+
});
|
|
1439
|
+
const match = invites.find((invite) => invite.email.toLowerCase() === email && invite.state === "pending") ?? null;
|
|
1440
|
+
if (match) return match;
|
|
1441
|
+
if (invites.length < MEMBERSHIP_PAGE_SIZE) return null;
|
|
1442
|
+
offset += MEMBERSHIP_PAGE_SIZE;
|
|
1116
1443
|
}
|
|
1117
|
-
return [];
|
|
1118
1444
|
}
|
|
1119
|
-
function
|
|
1120
|
-
|
|
1121
|
-
|
|
1445
|
+
async function findSelfMemberForScope(api, scopeType, targetId, meId) {
|
|
1446
|
+
if (meId == null) return null;
|
|
1447
|
+
let offset = 0;
|
|
1448
|
+
for (; ; ) {
|
|
1449
|
+
const members = scopeType === "organization" ? unwrapResponseObject(
|
|
1450
|
+
await api.listOrganizationMembers(targetId, { limit: MEMBERSHIP_PAGE_SIZE, offset }),
|
|
1451
|
+
"organization members"
|
|
1452
|
+
) : scopeType === "project" ? unwrapResponseObject(
|
|
1453
|
+
await api.listProjectMembers(targetId, { limit: MEMBERSHIP_PAGE_SIZE, offset }),
|
|
1454
|
+
"project members"
|
|
1455
|
+
) : unwrapResponseObject(
|
|
1456
|
+
await api.listAppMembers(targetId, { limit: MEMBERSHIP_PAGE_SIZE, offset }),
|
|
1457
|
+
"app members"
|
|
1458
|
+
);
|
|
1459
|
+
const match = getSelfMember(meId, members);
|
|
1460
|
+
if (match) return match;
|
|
1461
|
+
if (members.length < MEMBERSHIP_PAGE_SIZE) return null;
|
|
1462
|
+
offset += MEMBERSHIP_PAGE_SIZE;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
async function listInvites(params) {
|
|
1466
|
+
const api = await createApiClient();
|
|
1467
|
+
const target = await resolveScopeTarget(api, {
|
|
1468
|
+
scope: params.scope ?? "project",
|
|
1469
|
+
targetId: params.targetId,
|
|
1470
|
+
cwd: params.cwd
|
|
1471
|
+
});
|
|
1472
|
+
const pagination = normalizePagination(params);
|
|
1473
|
+
const invites = await listInvitesForScope(api, target.scopeType, target.targetId, {
|
|
1474
|
+
limit: pagination.limit + 1,
|
|
1475
|
+
offset: pagination.offset
|
|
1476
|
+
});
|
|
1122
1477
|
return {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1478
|
+
data: {
|
|
1479
|
+
scopeType: target.scopeType,
|
|
1480
|
+
targetId: target.targetId,
|
|
1481
|
+
invites: invites.slice(0, pagination.limit),
|
|
1482
|
+
pagination: {
|
|
1483
|
+
...pagination,
|
|
1484
|
+
hasMore: invites.length > pagination.limit
|
|
1485
|
+
}
|
|
1486
|
+
},
|
|
1128
1487
|
warnings: [],
|
|
1129
|
-
|
|
1130
|
-
|
|
1488
|
+
recommendedNextActions: invites.length > pagination.limit ? [`Pass offset=${pagination.offset + pagination.limit} to load the next page.`] : invites.length > 0 ? ["Use `remix_collab_resend_invite` or `remix_collab_revoke_invite` for lifecycle actions on a selected invite id."] : ["Use `remix_collab_invite` if you need to create a new invitation for this scope."],
|
|
1489
|
+
logContext: {
|
|
1490
|
+
repoRoot: target.repoRoot
|
|
1491
|
+
}
|
|
1131
1492
|
};
|
|
1132
1493
|
}
|
|
1133
|
-
function
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1136
|
-
params.
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1494
|
+
async function resendInvite(params) {
|
|
1495
|
+
const api = await createApiClient();
|
|
1496
|
+
const target = await resolveScopeTarget(api, {
|
|
1497
|
+
scope: params.scope ?? "project",
|
|
1498
|
+
targetId: params.targetId,
|
|
1499
|
+
cwd: params.cwd
|
|
1500
|
+
});
|
|
1501
|
+
const response = target.scopeType === "organization" ? await api.resendOrganizationInvite(target.targetId, params.inviteId, { ttlDays: params.ttlDays }) : target.scopeType === "project" ? await api.resendProjectInvite(target.targetId, params.inviteId, { ttlDays: params.ttlDays }) : await api.resendAppInvite(target.targetId, params.inviteId, { ttlDays: params.ttlDays });
|
|
1502
|
+
return {
|
|
1503
|
+
data: unwrapResponseObject(response, "resent invite"),
|
|
1504
|
+
warnings: [],
|
|
1505
|
+
recommendedNextActions: ["Use `remix_collab_list_invites` to confirm the refreshed expiry and delivery state."],
|
|
1506
|
+
logContext: {
|
|
1507
|
+
repoRoot: target.repoRoot
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
async function revokeInvite(params) {
|
|
1512
|
+
const api = await createApiClient();
|
|
1513
|
+
const target = await resolveScopeTarget(api, {
|
|
1514
|
+
scope: params.scope ?? "project",
|
|
1515
|
+
targetId: params.targetId,
|
|
1516
|
+
cwd: params.cwd
|
|
1517
|
+
});
|
|
1518
|
+
if (target.scopeType === "organization") {
|
|
1519
|
+
await api.revokeOrganizationInvite(target.targetId, params.inviteId);
|
|
1520
|
+
} else if (target.scopeType === "project") {
|
|
1521
|
+
await api.revokeProjectInvite(target.targetId, params.inviteId);
|
|
1522
|
+
} else {
|
|
1523
|
+
await api.revokeAppInvite(target.targetId, params.inviteId);
|
|
1524
|
+
}
|
|
1525
|
+
return {
|
|
1526
|
+
data: {
|
|
1527
|
+
scopeType: target.scopeType,
|
|
1528
|
+
targetId: target.targetId,
|
|
1529
|
+
inviteId: params.inviteId,
|
|
1530
|
+
revoked: true
|
|
1143
1531
|
},
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1532
|
+
warnings: [],
|
|
1533
|
+
recommendedNextActions: ["Use `remix_collab_list_invites` to confirm the invite state is now `revoked`."],
|
|
1534
|
+
logContext: {
|
|
1535
|
+
repoRoot: target.repoRoot
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
async function acceptInvitation(params) {
|
|
1540
|
+
const api = await createApiClient();
|
|
1541
|
+
const result = unwrapResponseObject(await api.acceptInvitation({ token: params.token }), "accepted invitation");
|
|
1542
|
+
return {
|
|
1543
|
+
data: result,
|
|
1544
|
+
warnings: [],
|
|
1545
|
+
recommendedNextActions: result.redirectPath ? [`The invitation is accepted. Use the returned redirect metadata to continue with the newly granted Remix scope.`] : [],
|
|
1546
|
+
logContext: {}
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
async function accessDebug(params) {
|
|
1550
|
+
const api = await createApiClient();
|
|
1551
|
+
const target = await resolveScopeTarget(api, {
|
|
1552
|
+
scope: params.scope ?? "project",
|
|
1553
|
+
targetId: params.targetId,
|
|
1554
|
+
cwd: params.cwd
|
|
1555
|
+
});
|
|
1556
|
+
const bindingContext = await loadBindingContext(params.cwd);
|
|
1557
|
+
const viewer = unwrapResponseObject(await api.getMe(), "current user");
|
|
1558
|
+
const viewerId = typeof viewer.id === "string" ? viewer.id : null;
|
|
1559
|
+
const viewerEmail = typeof viewer.email === "string" ? viewer.email.toLowerCase() : null;
|
|
1560
|
+
const warnings = [...bindingContext.binding ? [] : ["No local Remix binding was detected for the provided cwd."]];
|
|
1561
|
+
let entities;
|
|
1562
|
+
let appContext = null;
|
|
1563
|
+
let membersPage = emptyFirstPageSample();
|
|
1564
|
+
let invitesPage = emptyFirstPageSample();
|
|
1565
|
+
let viewerMember = null;
|
|
1566
|
+
let viewerProjectMember = null;
|
|
1567
|
+
let inviteForViewerEmail = null;
|
|
1568
|
+
let effectiveAppAccess = null;
|
|
1569
|
+
if (target.scopeType !== "app") {
|
|
1570
|
+
[entities, membersPage, invitesPage] = await Promise.all([
|
|
1571
|
+
getScopeEntity(api, target.scopeType, target.targetId),
|
|
1572
|
+
loadFirstPageSample((limit, offset) => listMembersForScope(api, target.scopeType, target.targetId, { limit, offset })),
|
|
1573
|
+
loadFirstPageSample((limit, offset) => listInvitesForScope(api, target.scopeType, target.targetId, { limit, offset }))
|
|
1574
|
+
]);
|
|
1575
|
+
viewerMember = await findSelfMemberForScope(api, target.scopeType, target.targetId, viewerId);
|
|
1576
|
+
inviteForViewerEmail = await findPendingInviteForEmailForScope(api, target.scopeType, target.targetId, viewerEmail);
|
|
1577
|
+
} else {
|
|
1578
|
+
const [app, loadedAppContext] = await Promise.all([
|
|
1579
|
+
unwrapResponseObject(await api.getApp(target.targetId), "app"),
|
|
1580
|
+
unwrapResponseObject(await api.getAppContext(target.targetId), "app context")
|
|
1581
|
+
]);
|
|
1582
|
+
appContext = loadedAppContext;
|
|
1583
|
+
entities = {
|
|
1584
|
+
organization: null,
|
|
1585
|
+
project: null,
|
|
1586
|
+
app
|
|
1587
|
+
};
|
|
1588
|
+
if (appContext.readableScopes.project) {
|
|
1589
|
+
try {
|
|
1590
|
+
entities.project = unwrapResponseObject(await api.getProject(appContext.projectId), "project");
|
|
1591
|
+
} catch (error) {
|
|
1592
|
+
if (!isBackendForbidden(error) && !isBackendNotFound(error)) throw error;
|
|
1593
|
+
warnings.push("The app is readable, but its project/workspace metadata is not currently readable to the current user.");
|
|
1594
|
+
}
|
|
1595
|
+
} else {
|
|
1596
|
+
warnings.push("The app is readable, but its project/workspace is not readable to the current user.");
|
|
1597
|
+
}
|
|
1598
|
+
if (appContext.readableScopes.organization) {
|
|
1599
|
+
try {
|
|
1600
|
+
entities.organization = unwrapResponseObject(await api.getOrganization(appContext.organizationId), "organization");
|
|
1601
|
+
} catch (error) {
|
|
1602
|
+
if (!isBackendForbidden(error) && !isBackendNotFound(error)) throw error;
|
|
1603
|
+
warnings.push("The app is readable, but its organization metadata is not currently readable to the current user.");
|
|
1604
|
+
}
|
|
1605
|
+
} else {
|
|
1606
|
+
warnings.push("The app's organization is not readable to the current user.");
|
|
1607
|
+
}
|
|
1608
|
+
effectiveAppAccess = {
|
|
1609
|
+
...buildEffectiveAppAccess({
|
|
1610
|
+
directAppRole: appContext.roles.appRole,
|
|
1611
|
+
projectRole: appContext.roles.projectRole
|
|
1612
|
+
}),
|
|
1613
|
+
accessPath: appContext.accessPath,
|
|
1614
|
+
readableScopes: appContext.readableScopes,
|
|
1615
|
+
visibility: appContext.visibility,
|
|
1616
|
+
organizationRole: appContext.roles.organizationRole ?? null
|
|
1617
|
+
};
|
|
1618
|
+
if (appContext.capabilities.canReadWorkflow) {
|
|
1619
|
+
membersPage = await loadFirstPageSample((limit, offset) => listMembersForScope(api, "app", target.targetId, { limit, offset }));
|
|
1620
|
+
viewerMember = await findSelfMemberForScope(api, "app", target.targetId, viewerId);
|
|
1621
|
+
if (appContext.readableScopes.project) {
|
|
1622
|
+
viewerProjectMember = await findSelfMemberForScope(api, "project", appContext.projectId, viewerId);
|
|
1623
|
+
}
|
|
1624
|
+
try {
|
|
1625
|
+
invitesPage = await loadFirstPageSample((limit, offset) => listInvitesForScope(api, "app", target.targetId, { limit, offset }));
|
|
1626
|
+
inviteForViewerEmail = await findPendingInviteForEmailForScope(api, "app", target.targetId, viewerEmail);
|
|
1627
|
+
} catch (error) {
|
|
1628
|
+
if (!isBackendForbidden(error) && !isBackendNotFound(error)) throw error;
|
|
1629
|
+
warnings.push("App invite data is not readable to the current user, so invite diagnostics are partial.");
|
|
1630
|
+
}
|
|
1631
|
+
} else {
|
|
1632
|
+
warnings.push("The current viewer can read the app, but cannot read workflow-scoped app membership data.");
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
if (membersPage.pageInfo.hasMore) {
|
|
1636
|
+
warnings.push("The `members` array is a first-page sample only; use the scope member list tool to inspect the remaining entries.");
|
|
1637
|
+
}
|
|
1638
|
+
if (invitesPage.pageInfo.hasMore) {
|
|
1639
|
+
warnings.push("The `invites` array is a first-page sample only; use the invite list tool with pagination to inspect the remaining entries.");
|
|
1640
|
+
}
|
|
1641
|
+
return {
|
|
1642
|
+
data: {
|
|
1643
|
+
viewer,
|
|
1644
|
+
scope: {
|
|
1645
|
+
scopeType: target.scopeType,
|
|
1646
|
+
targetId: target.targetId
|
|
1647
|
+
},
|
|
1648
|
+
entities,
|
|
1649
|
+
binding: {
|
|
1650
|
+
repoRoot: bindingContext.repoRoot,
|
|
1651
|
+
binding: bindingContext.binding
|
|
1652
|
+
},
|
|
1653
|
+
access: {
|
|
1654
|
+
appContext,
|
|
1655
|
+
viewerMember,
|
|
1656
|
+
viewerProjectMember,
|
|
1657
|
+
inviteForViewerEmail,
|
|
1658
|
+
effectiveAppAccess: effectiveAppAccess ?? null
|
|
1659
|
+
},
|
|
1660
|
+
members: membersPage.items,
|
|
1661
|
+
membersPageInfo: membersPage.pageInfo,
|
|
1662
|
+
invites: invitesPage.items,
|
|
1663
|
+
invitesPageInfo: invitesPage.pageInfo
|
|
1664
|
+
},
|
|
1665
|
+
warnings,
|
|
1666
|
+
recommendedNextActions: inviteForViewerEmail ? ["A pending invite exists for the current viewer email. Use `remix_collab_accept_invitation` if the viewer should accept it."] : target.scopeType === "app" ? ["Use `access.appContext` and `access.effectiveAppAccess` together to explain app-level visibility versus workspace-level visibility."] : ["Use the member and invite lists above to explain missing access, role mismatches, or stale invite state."],
|
|
1667
|
+
logContext: {
|
|
1668
|
+
repoRoot: target.repoRoot,
|
|
1669
|
+
appId: entities.app?.id ?? bindingContext.binding?.currentAppId ?? null
|
|
1670
|
+
}
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
// src/tools/collab/register.ts
|
|
1675
|
+
function getAnnotations(access, options) {
|
|
1676
|
+
if (access === "read") {
|
|
1677
|
+
return {
|
|
1678
|
+
readOnlyHint: true,
|
|
1679
|
+
idempotentHint: true,
|
|
1680
|
+
openWorldHint: false
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
return {
|
|
1684
|
+
readOnlyHint: false,
|
|
1685
|
+
destructiveHint: access === "local_write",
|
|
1686
|
+
idempotentHint: options?.idempotent ?? false,
|
|
1687
|
+
openWorldHint: false
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
function buildSuccessEnvelope(tool, requestId, result) {
|
|
1691
|
+
return {
|
|
1692
|
+
schemaVersion: SCHEMA_VERSION,
|
|
1693
|
+
ok: true,
|
|
1694
|
+
tool,
|
|
1695
|
+
requestId: requestId ?? null,
|
|
1696
|
+
data: result.data,
|
|
1697
|
+
warnings: result.warnings ?? [],
|
|
1698
|
+
risks: result.risks ?? [],
|
|
1699
|
+
recommendedNextActions: result.recommendedNextActions ?? []
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
function deriveErrorRisks(tool, normalized) {
|
|
1703
|
+
if (isFinalizeTurnLocalSyncFailure(tool, normalized)) {
|
|
1704
|
+
return ["The change step succeeded remotely, but the local repository may need manual recovery or a follow-up sync."];
|
|
1705
|
+
}
|
|
1706
|
+
if (normalized.code === "DESTRUCTIVE_OPERATION_BLOCKED") {
|
|
1707
|
+
return ["A policy guard blocked a potentially destructive or state-mutating operation."];
|
|
1708
|
+
}
|
|
1709
|
+
if (normalized.code === "REPO_LOCK_TIMEOUT") {
|
|
1710
|
+
return ["Another Remix mutation was already in progress for this repository, so the local mutation did not run."];
|
|
1711
|
+
}
|
|
1712
|
+
if (normalized.code === "REPO_STATE_CHANGED_DURING_OPERATION") {
|
|
1713
|
+
return ["The repository changed during the operation, so Remix aborted before applying a destructive local mutation."];
|
|
1714
|
+
}
|
|
1715
|
+
return [];
|
|
1716
|
+
}
|
|
1717
|
+
function isFinalizeTurnLocalSyncFailure(tool, normalized) {
|
|
1718
|
+
return tool === "remix_collab_finalize_turn" && normalized.message === "Change step succeeded remotely, but automatic local sync failed.";
|
|
1719
|
+
}
|
|
1720
|
+
function buildErrorEnvelope(tool, requestId, error) {
|
|
1721
|
+
const normalized = normalizeToolError(error);
|
|
1722
|
+
const recommendedNextActions = isFinalizeTurnLocalSyncFailure(tool, normalized) ? [
|
|
1723
|
+
"Run `remix_collab_status` to confirm the bound repo state before attempting recovery.",
|
|
1724
|
+
"Run `remix_collab_sync_preview` next, then `remix_collab_sync_apply` with `confirm=true` if the preview looks correct.",
|
|
1725
|
+
"Inspect `error.hint` for any preserved diff backup path before retrying local recovery, and do not rerun `remix_collab_finalize_turn` immediately."
|
|
1726
|
+
] : 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."] : [];
|
|
1727
|
+
return {
|
|
1728
|
+
schemaVersion: SCHEMA_VERSION,
|
|
1729
|
+
ok: false,
|
|
1730
|
+
tool,
|
|
1731
|
+
requestId: requestId ?? null,
|
|
1732
|
+
error: normalized,
|
|
1733
|
+
warnings: [],
|
|
1734
|
+
risks: deriveErrorRisks(tool, normalized),
|
|
1735
|
+
recommendedNextActions
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
function registerTool(server, context, params) {
|
|
1739
|
+
const errorSchema = makeErrorSchema();
|
|
1740
|
+
server.registerTool(
|
|
1741
|
+
params.name,
|
|
1742
|
+
{
|
|
1743
|
+
title: params.name,
|
|
1744
|
+
description: params.description,
|
|
1745
|
+
inputSchema: params.inputSchema,
|
|
1746
|
+
outputSchema: params.outputSchema,
|
|
1747
|
+
annotations: params.annotations ?? getAnnotations(params.access)
|
|
1748
|
+
},
|
|
1749
|
+
async (rawArgs) => {
|
|
1750
|
+
const requestId = typeof rawArgs.requestId === "string" ? rawArgs.requestId : void 0;
|
|
1751
|
+
const startedAt = Date.now();
|
|
1752
|
+
try {
|
|
1753
|
+
assertToolAccess(context.policy, params.access);
|
|
1754
|
+
const result = await params.run(rawArgs);
|
|
1755
|
+
const envelope = buildSuccessEnvelope(params.name, requestId, result);
|
|
1756
|
+
params.outputSchema.parse(envelope);
|
|
1757
|
+
context.logger.log({
|
|
1153
1758
|
level: "info",
|
|
1154
1759
|
message: "tool_completed",
|
|
1155
1760
|
tool: params.name,
|
|
@@ -1219,7 +1824,11 @@ function registerCollabTools(server, context) {
|
|
|
1219
1824
|
outputSchema: listSuccessSchema,
|
|
1220
1825
|
run: async (args) => {
|
|
1221
1826
|
const input = z3.object(listInputSchema).parse(args);
|
|
1222
|
-
return listApps({
|
|
1827
|
+
return listApps({
|
|
1828
|
+
forked: input.forked,
|
|
1829
|
+
limit: input.limit,
|
|
1830
|
+
offset: input.offset
|
|
1831
|
+
});
|
|
1223
1832
|
}
|
|
1224
1833
|
});
|
|
1225
1834
|
registerTool(server, context, {
|
|
@@ -1256,89 +1865,25 @@ function registerCollabTools(server, context) {
|
|
|
1256
1865
|
}
|
|
1257
1866
|
});
|
|
1258
1867
|
registerTool(server, context, {
|
|
1259
|
-
name: "
|
|
1260
|
-
description: "
|
|
1261
|
-
access: "local_write",
|
|
1262
|
-
inputSchema: addInputSchema,
|
|
1263
|
-
outputSchema: addSuccessSchema,
|
|
1264
|
-
run: async (args) => {
|
|
1265
|
-
const input = z3.object(addInputSchema).parse(args);
|
|
1266
|
-
const cwd = resolvePolicyCwd(context.policy, input.cwd);
|
|
1267
|
-
const diffSource = input.diffSource ?? "worktree";
|
|
1268
|
-
if (diffSource === "external") {
|
|
1269
|
-
const externalDiff = input.externalDiff ?? "";
|
|
1270
|
-
assertDiffWithinLimit(context.policy, externalDiff);
|
|
1271
|
-
}
|
|
1272
|
-
return addCollabStep({
|
|
1273
|
-
cwd,
|
|
1274
|
-
prompt: input.prompt,
|
|
1275
|
-
assistantResponse: input.assistantResponse,
|
|
1276
|
-
diffSource,
|
|
1277
|
-
externalDiff: input.externalDiff,
|
|
1278
|
-
allowBranchMismatch: input.allowBranchMismatch ?? false,
|
|
1279
|
-
idempotencyKey: input.idempotencyKey,
|
|
1280
|
-
agent: context.agentMetadata
|
|
1281
|
-
});
|
|
1282
|
-
}
|
|
1283
|
-
});
|
|
1284
|
-
registerTool(server, context, {
|
|
1285
|
-
name: "remix_collab_add_change_step",
|
|
1286
|
-
description: "Alias of remix_collab_add with a more explicit name: record a code-diff change step for the current bound repository.",
|
|
1868
|
+
name: "remix_collab_finalize_turn",
|
|
1869
|
+
description: "Primary turn recorder for the current bound repository. Call this exactly once before the final response; it records a changed turn when the worktree has a diff, records a no-diff turn when it does not, and can accept an explicit external diff when needed.",
|
|
1287
1870
|
access: "local_write",
|
|
1288
|
-
inputSchema:
|
|
1289
|
-
outputSchema:
|
|
1871
|
+
inputSchema: finalizeTurnInputSchema,
|
|
1872
|
+
outputSchema: finalizeTurnSuccessSchema,
|
|
1873
|
+
annotations: getAnnotations("local_write", { idempotent: true }),
|
|
1290
1874
|
run: async (args) => {
|
|
1291
|
-
const input = z3.object(
|
|
1875
|
+
const input = z3.object(finalizeTurnInputSchema).parse(args);
|
|
1292
1876
|
const cwd = resolvePolicyCwd(context.policy, input.cwd);
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
const externalDiff = input.externalDiff ?? "";
|
|
1296
|
-
assertDiffWithinLimit(context.policy, externalDiff);
|
|
1877
|
+
if ((input.diffSource ?? "worktree") === "external" || typeof input.externalDiff === "string") {
|
|
1878
|
+
assertDiffWithinLimit(context.policy, input.externalDiff ?? "");
|
|
1297
1879
|
}
|
|
1298
|
-
return
|
|
1880
|
+
return finalizeCollabTurn({
|
|
1299
1881
|
cwd,
|
|
1300
1882
|
prompt: input.prompt,
|
|
1301
1883
|
assistantResponse: input.assistantResponse,
|
|
1302
|
-
diffSource,
|
|
1884
|
+
diffSource: input.diffSource,
|
|
1303
1885
|
externalDiff: input.externalDiff,
|
|
1304
|
-
|
|
1305
|
-
idempotencyKey: input.idempotencyKey,
|
|
1306
|
-
agent: context.agentMetadata
|
|
1307
|
-
});
|
|
1308
|
-
}
|
|
1309
|
-
});
|
|
1310
|
-
registerTool(server, context, {
|
|
1311
|
-
name: "remix_collab_record_turn",
|
|
1312
|
-
description: "Record one no-diff collaboration turn for the current bound repository after a completed assistant response. This is for prompt/response history only and will fail if the worktree has code changes.",
|
|
1313
|
-
access: "remote_write",
|
|
1314
|
-
inputSchema: recordTurnInputSchema,
|
|
1315
|
-
outputSchema: recordTurnSuccessSchema,
|
|
1316
|
-
run: async (args) => {
|
|
1317
|
-
const input = z3.object(recordTurnInputSchema).parse(args);
|
|
1318
|
-
const cwd = resolvePolicyCwd(context.policy, input.cwd);
|
|
1319
|
-
return recordCollabTurn({
|
|
1320
|
-
cwd,
|
|
1321
|
-
prompt: input.prompt,
|
|
1322
|
-
assistantResponse: input.assistantResponse,
|
|
1323
|
-
allowBranchMismatch: input.allowBranchMismatch ?? false,
|
|
1324
|
-
idempotencyKey: input.idempotencyKey,
|
|
1325
|
-
agent: context.agentMetadata
|
|
1326
|
-
});
|
|
1327
|
-
}
|
|
1328
|
-
});
|
|
1329
|
-
registerTool(server, context, {
|
|
1330
|
-
name: "remix_collab_record_no_diff_turn",
|
|
1331
|
-
description: "Alias of remix_collab_record_turn with a more explicit name: record a prompt/response turn only when the worktree has no code diff.",
|
|
1332
|
-
access: "remote_write",
|
|
1333
|
-
inputSchema: recordTurnInputSchema,
|
|
1334
|
-
outputSchema: recordTurnSuccessSchema,
|
|
1335
|
-
run: async (args) => {
|
|
1336
|
-
const input = z3.object(recordTurnInputSchema).parse(args);
|
|
1337
|
-
const cwd = resolvePolicyCwd(context.policy, input.cwd);
|
|
1338
|
-
return recordCollabTurn({
|
|
1339
|
-
cwd,
|
|
1340
|
-
prompt: input.prompt,
|
|
1341
|
-
assistantResponse: input.assistantResponse,
|
|
1886
|
+
sync: input.sync,
|
|
1342
1887
|
allowBranchMismatch: input.allowBranchMismatch ?? false,
|
|
1343
1888
|
idempotencyKey: input.idempotencyKey,
|
|
1344
1889
|
agent: context.agentMetadata
|
|
@@ -1390,7 +1935,12 @@ function registerCollabTools(server, context) {
|
|
|
1390
1935
|
outputSchema: mergeRequestQueueSuccessSchema,
|
|
1391
1936
|
run: async (args) => {
|
|
1392
1937
|
const input = z3.object(reviewQueueInputSchema).parse(args);
|
|
1393
|
-
return reviewQueue({
|
|
1938
|
+
return reviewQueue({
|
|
1939
|
+
status: input.status,
|
|
1940
|
+
kind: input.kind,
|
|
1941
|
+
limit: input.limit,
|
|
1942
|
+
offset: input.offset
|
|
1943
|
+
});
|
|
1394
1944
|
}
|
|
1395
1945
|
});
|
|
1396
1946
|
registerTool(server, context, {
|
|
@@ -1401,7 +1951,12 @@ function registerCollabTools(server, context) {
|
|
|
1401
1951
|
outputSchema: mergeRequestQueueSuccessSchema,
|
|
1402
1952
|
run: async (args) => {
|
|
1403
1953
|
const input = z3.object(myMergeRequestsInputSchema).parse(args);
|
|
1404
|
-
return myMergeRequests({
|
|
1954
|
+
return myMergeRequests({
|
|
1955
|
+
status: input.status,
|
|
1956
|
+
kind: input.kind,
|
|
1957
|
+
limit: input.limit,
|
|
1958
|
+
offset: input.offset
|
|
1959
|
+
});
|
|
1405
1960
|
}
|
|
1406
1961
|
});
|
|
1407
1962
|
registerTool(server, context, {
|
|
@@ -1418,7 +1973,9 @@ function registerCollabTools(server, context) {
|
|
|
1418
1973
|
appId: input.appId,
|
|
1419
1974
|
queue: input.queue,
|
|
1420
1975
|
status: input.status,
|
|
1421
|
-
kind: input.kind
|
|
1976
|
+
kind: input.kind,
|
|
1977
|
+
limit: input.limit,
|
|
1978
|
+
offset: input.offset
|
|
1422
1979
|
});
|
|
1423
1980
|
}
|
|
1424
1981
|
});
|
|
@@ -1552,6 +2109,92 @@ function registerCollabTools(server, context) {
|
|
|
1552
2109
|
return listMembers({
|
|
1553
2110
|
cwd,
|
|
1554
2111
|
scope: input.scope,
|
|
2112
|
+
targetId: input.targetId,
|
|
2113
|
+
limit: input.limit,
|
|
2114
|
+
offset: input.offset
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
});
|
|
2118
|
+
registerTool(server, context, {
|
|
2119
|
+
name: "remix_collab_list_invites",
|
|
2120
|
+
description: "List invitations for an organization, project, or app, using the current repository binding unless targetId is provided.",
|
|
2121
|
+
access: "read",
|
|
2122
|
+
inputSchema: listInvitesInputSchema,
|
|
2123
|
+
outputSchema: listInvitesSuccessSchema,
|
|
2124
|
+
run: async (args) => {
|
|
2125
|
+
const input = z3.object(listInvitesInputSchema).parse(args);
|
|
2126
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
2127
|
+
return listInvites({
|
|
2128
|
+
cwd,
|
|
2129
|
+
scope: input.scope ?? "project",
|
|
2130
|
+
targetId: input.targetId,
|
|
2131
|
+
limit: input.limit,
|
|
2132
|
+
offset: input.offset
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
registerTool(server, context, {
|
|
2137
|
+
name: "remix_collab_resend_invite",
|
|
2138
|
+
description: "Resend an existing invitation for an organization, project, or app, using the current repository binding unless targetId is provided.",
|
|
2139
|
+
access: "remote_write",
|
|
2140
|
+
inputSchema: resendInviteInputSchema,
|
|
2141
|
+
outputSchema: resendInviteSuccessSchema,
|
|
2142
|
+
run: async (args) => {
|
|
2143
|
+
const input = z3.object(resendInviteInputSchema).parse(args);
|
|
2144
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
2145
|
+
return resendInvite({
|
|
2146
|
+
cwd,
|
|
2147
|
+
scope: input.scope ?? "project",
|
|
2148
|
+
targetId: input.targetId,
|
|
2149
|
+
inviteId: input.inviteId,
|
|
2150
|
+
ttlDays: input.ttlDays
|
|
2151
|
+
});
|
|
2152
|
+
}
|
|
2153
|
+
});
|
|
2154
|
+
registerTool(server, context, {
|
|
2155
|
+
name: "remix_collab_revoke_invite",
|
|
2156
|
+
description: "Revoke an existing invitation for an organization, project, or app, using the current repository binding unless targetId is provided.",
|
|
2157
|
+
access: "remote_write",
|
|
2158
|
+
inputSchema: revokeInviteInputSchema,
|
|
2159
|
+
outputSchema: revokeInviteSuccessSchema,
|
|
2160
|
+
run: async (args) => {
|
|
2161
|
+
const input = z3.object(revokeInviteInputSchema).parse(args);
|
|
2162
|
+
assertConfirm(input.confirm, "remix_collab_revoke_invite");
|
|
2163
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
2164
|
+
return revokeInvite({
|
|
2165
|
+
cwd,
|
|
2166
|
+
scope: input.scope ?? "project",
|
|
2167
|
+
targetId: input.targetId,
|
|
2168
|
+
inviteId: input.inviteId
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
});
|
|
2172
|
+
registerTool(server, context, {
|
|
2173
|
+
name: "remix_collab_accept_invitation",
|
|
2174
|
+
description: "Accept an invitation token for the currently authenticated Remix user.",
|
|
2175
|
+
access: "remote_write",
|
|
2176
|
+
inputSchema: acceptInvitationInputSchema,
|
|
2177
|
+
outputSchema: acceptInvitationSuccessSchema,
|
|
2178
|
+
annotations: getAnnotations("remote_write", { idempotent: true }),
|
|
2179
|
+
run: async (args) => {
|
|
2180
|
+
const input = z3.object(acceptInvitationInputSchema).parse(args);
|
|
2181
|
+
return acceptInvitation({
|
|
2182
|
+
token: input.token
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
2185
|
+
});
|
|
2186
|
+
registerTool(server, context, {
|
|
2187
|
+
name: "remix_access_debug",
|
|
2188
|
+
description: "Explain why the current user does or does not have access to an organization, project, or app by composing binding, membership, invite, and scope context.",
|
|
2189
|
+
access: "read",
|
|
2190
|
+
inputSchema: accessDebugInputSchema,
|
|
2191
|
+
outputSchema: accessDebugSuccessSchema,
|
|
2192
|
+
run: async (args) => {
|
|
2193
|
+
const input = z3.object(accessDebugInputSchema).parse(args);
|
|
2194
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
2195
|
+
return accessDebug({
|
|
2196
|
+
cwd,
|
|
2197
|
+
scope: input.scope ?? "project",
|
|
1555
2198
|
targetId: input.targetId
|
|
1556
2199
|
});
|
|
1557
2200
|
}
|
|
@@ -1576,92 +2219,648 @@ function registerCollabTools(server, context) {
|
|
|
1576
2219
|
});
|
|
1577
2220
|
}
|
|
1578
2221
|
|
|
1579
|
-
// src/tools/
|
|
2222
|
+
// src/tools/identity/register.ts
|
|
1580
2223
|
import { z as z5 } from "zod";
|
|
1581
2224
|
|
|
1582
|
-
// src/contracts/
|
|
2225
|
+
// src/contracts/identity.ts
|
|
1583
2226
|
import { z as z4 } from "zod";
|
|
1584
2227
|
var genericRecordSchema2 = z4.record(z4.string(), z4.unknown());
|
|
1585
2228
|
var genericArraySchema2 = z4.array(genericRecordSchema2);
|
|
1586
|
-
var
|
|
1587
|
-
|
|
1588
|
-
limit: z4.number().int().nonnegative(),
|
|
2229
|
+
var paginationSchema2 = z4.object({
|
|
2230
|
+
limit: z4.number().int().positive(),
|
|
1589
2231
|
offset: z4.number().int().nonnegative(),
|
|
1590
2232
|
hasMore: z4.boolean()
|
|
1591
2233
|
});
|
|
1592
|
-
var
|
|
2234
|
+
var whoamiInputSchema = {
|
|
2235
|
+
...commonRequestFieldsSchema
|
|
2236
|
+
};
|
|
2237
|
+
var directoryOrganizationInputSchema = {
|
|
1593
2238
|
...commonRequestFieldsSchema,
|
|
1594
|
-
|
|
2239
|
+
organizationId: z4.string().trim().min(1).optional()
|
|
1595
2240
|
};
|
|
1596
|
-
var
|
|
2241
|
+
var directoryListProjectsInputSchema = {
|
|
1597
2242
|
...commonRequestFieldsSchema,
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
kinds: z4.array(memoryKindSchema).max(4).optional(),
|
|
1601
|
-
limit: z4.number().int().positive().max(50).optional(),
|
|
1602
|
-
offset: z4.number().int().nonnegative().optional(),
|
|
1603
|
-
createdAfter: z4.string().trim().min(1).optional(),
|
|
1604
|
-
createdBefore: z4.string().trim().min(1).optional()
|
|
2243
|
+
organizationId: z4.string().trim().min(1).optional(),
|
|
2244
|
+
clientAppId: z4.string().trim().min(1).optional()
|
|
1605
2245
|
};
|
|
1606
|
-
var
|
|
2246
|
+
var directoryProjectInputSchema = {
|
|
2247
|
+
...commonRequestFieldsSchema,
|
|
2248
|
+
projectId: z4.string().trim().min(1).optional()
|
|
2249
|
+
};
|
|
2250
|
+
var directoryListAppsInputSchema = {
|
|
1607
2251
|
...commonRequestFieldsSchema,
|
|
1608
|
-
|
|
1609
|
-
|
|
2252
|
+
projectId: z4.string().trim().min(1).optional(),
|
|
2253
|
+
organizationId: z4.string().trim().min(1).optional(),
|
|
2254
|
+
forked: z4.enum(["only", "exclude", "all"]).optional(),
|
|
1610
2255
|
limit: z4.number().int().positive().max(50).optional(),
|
|
1611
|
-
offset: z4.number().int().nonnegative().optional()
|
|
1612
|
-
createdAfter: z4.string().trim().min(1).optional(),
|
|
1613
|
-
createdBefore: z4.string().trim().min(1).optional()
|
|
2256
|
+
offset: z4.number().int().nonnegative().optional()
|
|
1614
2257
|
};
|
|
1615
|
-
var
|
|
2258
|
+
var directoryAppInputSchema = {
|
|
1616
2259
|
...commonRequestFieldsSchema,
|
|
1617
|
-
appId: z4.string().trim().min(1).optional()
|
|
1618
|
-
changeStepId: z4.string().trim().min(1)
|
|
2260
|
+
appId: z4.string().trim().min(1).optional()
|
|
1619
2261
|
};
|
|
1620
|
-
var
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
2262
|
+
var whoamiDataSchema = z4.object({
|
|
2263
|
+
id: z4.string().nullable(),
|
|
2264
|
+
name: z4.string().nullable(),
|
|
2265
|
+
email: z4.string().nullable(),
|
|
2266
|
+
organizationId: z4.string().nullable(),
|
|
2267
|
+
roles: genericRecordSchema2,
|
|
2268
|
+
binding: genericRecordSchema2.nullable()
|
|
1624
2269
|
});
|
|
1625
|
-
var
|
|
1626
|
-
|
|
1627
|
-
pagination: paginationSchema
|
|
2270
|
+
var listOrganizationsDataSchema = z4.object({
|
|
2271
|
+
organizations: genericArraySchema2
|
|
1628
2272
|
});
|
|
1629
|
-
var
|
|
1630
|
-
|
|
1631
|
-
appId: z4.string(),
|
|
1632
|
-
diff: z4.string(),
|
|
1633
|
-
diffSha256: z4.string(),
|
|
1634
|
-
contentType: z4.string(),
|
|
1635
|
-
encoding: z4.string(),
|
|
1636
|
-
expiresIn: z4.number().int().positive()
|
|
2273
|
+
var getOrganizationDataSchema = z4.object({
|
|
2274
|
+
organization: genericRecordSchema2
|
|
1637
2275
|
});
|
|
1638
|
-
var
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
2276
|
+
var listProjectsDataSchema = z4.object({
|
|
2277
|
+
organizationId: z4.string().nullable(),
|
|
2278
|
+
projects: genericArraySchema2
|
|
2279
|
+
});
|
|
2280
|
+
var getProjectDataSchema = z4.object({
|
|
2281
|
+
project: genericRecordSchema2
|
|
2282
|
+
});
|
|
2283
|
+
var listAppsDataSchema = z4.object({
|
|
2284
|
+
apps: genericArraySchema2,
|
|
2285
|
+
pagination: paginationSchema2,
|
|
2286
|
+
filters: z4.object({
|
|
2287
|
+
projectId: z4.string().nullable(),
|
|
2288
|
+
organizationId: z4.string().nullable(),
|
|
2289
|
+
forked: z4.enum(["only", "exclude", "all"])
|
|
2290
|
+
})
|
|
2291
|
+
});
|
|
2292
|
+
var getAppDataSchema = z4.object({
|
|
2293
|
+
app: genericRecordSchema2
|
|
2294
|
+
});
|
|
2295
|
+
var whoamiSuccessSchema = makeSuccessSchema(whoamiDataSchema);
|
|
2296
|
+
var listOrganizationsSuccessSchema = makeSuccessSchema(listOrganizationsDataSchema);
|
|
2297
|
+
var getOrganizationSuccessSchema = makeSuccessSchema(getOrganizationDataSchema);
|
|
2298
|
+
var listProjectsSuccessSchema = makeSuccessSchema(listProjectsDataSchema);
|
|
2299
|
+
var getProjectSuccessSchema = makeSuccessSchema(getProjectDataSchema);
|
|
2300
|
+
var listAppsSuccessSchema = makeSuccessSchema(listAppsDataSchema);
|
|
2301
|
+
var getAppSuccessSchema = makeSuccessSchema(getAppDataSchema);
|
|
1642
2302
|
|
|
1643
|
-
// src/domain/
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
2303
|
+
// src/domain/directoryAdapter.ts
|
|
2304
|
+
function normalizePagination2(params) {
|
|
2305
|
+
const rawLimit = typeof params?.limit === "number" ? Math.trunc(params.limit) : 25;
|
|
2306
|
+
const rawOffset = typeof params?.offset === "number" ? Math.trunc(params.offset) : 0;
|
|
2307
|
+
return {
|
|
2308
|
+
limit: Math.max(1, Math.min(50, rawLimit)),
|
|
2309
|
+
offset: Math.max(0, rawOffset)
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
async function resolveOrganizationId(api, params) {
|
|
2313
|
+
const explicitId = params.organizationId?.trim();
|
|
2314
|
+
if (explicitId) return explicitId;
|
|
2315
|
+
const bindingContext = await loadBindingContext(params.cwd);
|
|
2316
|
+
if (!bindingContext.binding) {
|
|
2317
|
+
throw makeNotBoundError("Organization id was not provided and the current repository is not bound to Remix.");
|
|
1654
2318
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
2319
|
+
const appContext = unwrapResponseObject(
|
|
2320
|
+
await api.getAppContext(bindingContext.binding.currentAppId),
|
|
2321
|
+
"bound app context"
|
|
2322
|
+
);
|
|
2323
|
+
if (!appContext.readableScopes.organization) {
|
|
2324
|
+
throw createAccessDeniedError(
|
|
2325
|
+
"The bound app's organization is not readable to the current user.",
|
|
2326
|
+
"Use an explicit organization id you can access, or inspect the bound app with `remix_directory_get_app` / `remix_access_debug` for app-level context."
|
|
1658
2327
|
);
|
|
1659
2328
|
}
|
|
1660
|
-
return
|
|
1661
|
-
}
|
|
1662
|
-
function
|
|
1663
|
-
const
|
|
1664
|
-
|
|
2329
|
+
return appContext.organizationId;
|
|
2330
|
+
}
|
|
2331
|
+
async function resolveProjectId(api, params) {
|
|
2332
|
+
const explicitId = params.projectId?.trim();
|
|
2333
|
+
if (explicitId) {
|
|
2334
|
+
const bindingContext2 = await loadBindingContext(params.cwd);
|
|
2335
|
+
return { projectId: explicitId, repoRoot: bindingContext2.repoRoot };
|
|
2336
|
+
}
|
|
2337
|
+
const bindingContext = await loadBindingContext(params.cwd);
|
|
2338
|
+
if (!bindingContext.binding) {
|
|
2339
|
+
throw makeNotBoundError("Project id was not provided and the current repository is not bound to Remix.");
|
|
2340
|
+
}
|
|
2341
|
+
const appContext = unwrapResponseObject(
|
|
2342
|
+
await api.getAppContext(bindingContext.binding.currentAppId),
|
|
2343
|
+
"bound app context"
|
|
2344
|
+
);
|
|
2345
|
+
if (!appContext.readableScopes.project) {
|
|
2346
|
+
throw createAccessDeniedError(
|
|
2347
|
+
"The bound app's project is not readable to the current user.",
|
|
2348
|
+
"Use `remix_directory_get_app` or `remix_access_debug` for app-level diagnostics, or pass an explicit project id you can access."
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2351
|
+
return {
|
|
2352
|
+
projectId: appContext.projectId,
|
|
2353
|
+
repoRoot: bindingContext.repoRoot
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
async function resolveAppId(params) {
|
|
2357
|
+
const explicitId = params.appId?.trim();
|
|
2358
|
+
if (explicitId) {
|
|
2359
|
+
const bindingContext2 = await loadBindingContext(params.cwd);
|
|
2360
|
+
return { appId: explicitId, repoRoot: bindingContext2.repoRoot };
|
|
2361
|
+
}
|
|
2362
|
+
const bindingContext = await loadBindingContext(params.cwd);
|
|
2363
|
+
if (!bindingContext.binding) {
|
|
2364
|
+
throw makeNotBoundError("App id was not provided and the current repository is not bound to Remix.");
|
|
2365
|
+
}
|
|
2366
|
+
return {
|
|
2367
|
+
appId: bindingContext.binding.currentAppId,
|
|
2368
|
+
repoRoot: bindingContext.repoRoot
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
function toEffectiveAppRole(role) {
|
|
2372
|
+
switch (role) {
|
|
2373
|
+
case "owner":
|
|
2374
|
+
case "maintainer":
|
|
2375
|
+
case "editor":
|
|
2376
|
+
case "viewer":
|
|
2377
|
+
return role;
|
|
2378
|
+
default:
|
|
2379
|
+
return null;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
function resolveAppAccessSource2(params) {
|
|
2383
|
+
if (params.directAppRole && params.inheritedProjectRole) return "both";
|
|
2384
|
+
if (params.directAppRole) return "direct_app_membership";
|
|
2385
|
+
if (params.inheritedProjectRole) return "project_membership";
|
|
2386
|
+
return "none";
|
|
2387
|
+
}
|
|
2388
|
+
function emptySelfRoles() {
|
|
2389
|
+
return {
|
|
2390
|
+
organizationRole: null,
|
|
2391
|
+
projectRole: null,
|
|
2392
|
+
appRole: null,
|
|
2393
|
+
directAppRole: null,
|
|
2394
|
+
inheritedProjectRole: null,
|
|
2395
|
+
appAccessSource: "none"
|
|
2396
|
+
};
|
|
2397
|
+
}
|
|
2398
|
+
function resolveSelfRolesFromAppContext(appContext) {
|
|
2399
|
+
if (!appContext) return emptySelfRoles();
|
|
2400
|
+
const directAppRole = toEffectiveAppRole(appContext.roles.appRole);
|
|
2401
|
+
const inheritedProjectRole = toEffectiveAppRole(appContext.roles.inheritedProjectRole);
|
|
2402
|
+
return {
|
|
2403
|
+
organizationRole: appContext.roles.organizationRole ?? null,
|
|
2404
|
+
projectRole: appContext.roles.projectRole ?? null,
|
|
2405
|
+
appRole: toEffectiveAppRole(appContext.roles.effectiveAppRole),
|
|
2406
|
+
directAppRole,
|
|
2407
|
+
inheritedProjectRole,
|
|
2408
|
+
appAccessSource: resolveAppAccessSource2({ directAppRole, inheritedProjectRole })
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
async function whoAmI(params) {
|
|
2412
|
+
const api = await createApiClient();
|
|
2413
|
+
const bindingContext = await loadBindingContext(params.cwd);
|
|
2414
|
+
const me = unwrapResponseObject(await api.getMe(), "current user");
|
|
2415
|
+
const warnings = bindingContext.binding ? [] : ["No local Remix binding was detected for the provided cwd."];
|
|
2416
|
+
let boundAppContext = null;
|
|
2417
|
+
let boundProject = null;
|
|
2418
|
+
let boundApp = null;
|
|
2419
|
+
if (bindingContext.binding) {
|
|
2420
|
+
try {
|
|
2421
|
+
boundAppContext = unwrapResponseObject(
|
|
2422
|
+
await api.getAppContext(bindingContext.binding.currentAppId),
|
|
2423
|
+
"bound app context"
|
|
2424
|
+
);
|
|
2425
|
+
} catch (error) {
|
|
2426
|
+
if (!isBackendForbidden(error) && !isBackendNotFound(error)) throw error;
|
|
2427
|
+
warnings.push(
|
|
2428
|
+
"The bound app context could not be loaded. The local binding may be stale, or the current user may no longer be able to read that app."
|
|
2429
|
+
);
|
|
2430
|
+
}
|
|
2431
|
+
try {
|
|
2432
|
+
boundApp = unwrapResponseObject(await api.getApp(bindingContext.binding.currentAppId), "app");
|
|
2433
|
+
} catch (error) {
|
|
2434
|
+
if (!isBackendForbidden(error) && !isBackendNotFound(error)) throw error;
|
|
2435
|
+
warnings.push("The bound app metadata could not be loaded for the current user.");
|
|
2436
|
+
}
|
|
2437
|
+
if (boundAppContext?.readableScopes.project) {
|
|
2438
|
+
try {
|
|
2439
|
+
boundProject = unwrapResponseObject(await api.getProject(boundAppContext.projectId), "project");
|
|
2440
|
+
} catch (error) {
|
|
2441
|
+
if (!isBackendForbidden(error) && !isBackendNotFound(error)) throw error;
|
|
2442
|
+
warnings.push("The bound app is readable, but its project metadata is not currently readable to the current user.");
|
|
2443
|
+
}
|
|
2444
|
+
} else if (boundAppContext) {
|
|
2445
|
+
warnings.push("The bound app is readable, but its project/workspace is not readable to the current user.");
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
const roles = resolveSelfRolesFromAppContext(boundAppContext);
|
|
2449
|
+
return {
|
|
2450
|
+
data: {
|
|
2451
|
+
id: me.id ?? null,
|
|
2452
|
+
name: me.name ?? null,
|
|
2453
|
+
email: me.email ?? null,
|
|
2454
|
+
organizationId: me.organizationId ?? null,
|
|
2455
|
+
roles,
|
|
2456
|
+
binding: bindingContext.binding == null ? null : {
|
|
2457
|
+
repoRoot: bindingContext.repoRoot,
|
|
2458
|
+
...bindingContext.binding,
|
|
2459
|
+
projectId: boundAppContext?.projectId ?? bindingContext.binding.projectId,
|
|
2460
|
+
organizationId: boundAppContext?.organizationId ?? null,
|
|
2461
|
+
visibility: boundAppContext?.visibility ?? null,
|
|
2462
|
+
accessPath: boundAppContext?.accessPath ?? null,
|
|
2463
|
+
readableScopes: boundAppContext?.readableScopes ?? null,
|
|
2464
|
+
projectName: boundProject?.name ?? null,
|
|
2465
|
+
appName: boundApp?.name ?? null
|
|
2466
|
+
}
|
|
2467
|
+
},
|
|
2468
|
+
warnings,
|
|
2469
|
+
recommendedNextActions: bindingContext.binding ? ["Use `remix_access_debug` next when you need to explain repository binding or membership issues in this workspace."] : ["Use `remix_directory_list_organizations` to inspect visible tenancy context, or run `remix_collab_init` in a repository to create a local binding."],
|
|
2470
|
+
logContext: {
|
|
2471
|
+
repoRoot: bindingContext.repoRoot,
|
|
2472
|
+
appId: bindingContext.binding?.currentAppId ?? null
|
|
2473
|
+
}
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
async function listOrganizations() {
|
|
2477
|
+
const api = await createApiClient();
|
|
2478
|
+
const organizations = unwrapResponseObject(await api.listOrganizations(), "organizations");
|
|
2479
|
+
return {
|
|
2480
|
+
data: { organizations },
|
|
2481
|
+
warnings: [],
|
|
2482
|
+
recommendedNextActions: organizations.length ? ["Use `remix_directory_get_organization` for one organization, or `remix_directory_list_projects` to inspect projects under a chosen organization."] : [],
|
|
2483
|
+
logContext: {}
|
|
2484
|
+
};
|
|
2485
|
+
}
|
|
2486
|
+
async function getOrganization(params) {
|
|
2487
|
+
const api = await createApiClient();
|
|
2488
|
+
const organizationId = await resolveOrganizationId(api, params);
|
|
2489
|
+
const bindingContext = await loadBindingContext(params.cwd);
|
|
2490
|
+
const organization = unwrapResponseObject(await api.getOrganization(organizationId), "organization");
|
|
2491
|
+
return {
|
|
2492
|
+
data: { organization },
|
|
2493
|
+
warnings: [],
|
|
2494
|
+
recommendedNextActions: ["Use `remix_directory_list_projects` with this organization id to inspect its project directory."],
|
|
2495
|
+
logContext: {
|
|
2496
|
+
repoRoot: bindingContext.repoRoot
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
async function listProjects(params) {
|
|
2501
|
+
const api = await createApiClient();
|
|
2502
|
+
const bindingContext = await loadBindingContext(params.cwd);
|
|
2503
|
+
const organizationId = params.organizationId !== void 0 ? await resolveOrganizationId(api, { organizationId: params.organizationId, cwd: params.cwd }) : null;
|
|
2504
|
+
const projects = unwrapResponseObject(
|
|
2505
|
+
await api.listProjects({
|
|
2506
|
+
organizationId: organizationId ?? void 0,
|
|
2507
|
+
clientAppId: params.clientAppId
|
|
2508
|
+
}),
|
|
2509
|
+
"projects"
|
|
2510
|
+
);
|
|
2511
|
+
return {
|
|
2512
|
+
data: {
|
|
2513
|
+
projects,
|
|
2514
|
+
organizationId
|
|
2515
|
+
},
|
|
2516
|
+
warnings: [],
|
|
2517
|
+
recommendedNextActions: projects.length ? ["Use `remix_directory_get_project` for one project, or `remix_directory_list_apps` to inspect apps under a chosen project or organization."] : [],
|
|
2518
|
+
logContext: {
|
|
2519
|
+
repoRoot: bindingContext.repoRoot,
|
|
2520
|
+
appId: bindingContext.binding?.currentAppId ?? null
|
|
2521
|
+
}
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
async function getProject(params) {
|
|
2525
|
+
const api = await createApiClient();
|
|
2526
|
+
const target = await resolveProjectId(api, params);
|
|
2527
|
+
const project = unwrapResponseObject(await api.getProject(target.projectId), "project");
|
|
2528
|
+
return {
|
|
2529
|
+
data: { project },
|
|
2530
|
+
warnings: [],
|
|
2531
|
+
recommendedNextActions: ["Use `remix_directory_list_apps` with this project id to inspect apps under the project."],
|
|
2532
|
+
logContext: {
|
|
2533
|
+
repoRoot: target.repoRoot
|
|
2534
|
+
}
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
async function listApps2(params) {
|
|
2538
|
+
const api = await createApiClient();
|
|
2539
|
+
const bindingContext = await loadBindingContext(params.cwd);
|
|
2540
|
+
const pagination = normalizePagination2(params);
|
|
2541
|
+
const apps = unwrapResponseObject(
|
|
2542
|
+
await api.listApps({
|
|
2543
|
+
projectId: params.projectId,
|
|
2544
|
+
organizationId: params.organizationId,
|
|
2545
|
+
forked: params.forked,
|
|
2546
|
+
limit: pagination.limit + 1,
|
|
2547
|
+
offset: pagination.offset
|
|
2548
|
+
}),
|
|
2549
|
+
"apps"
|
|
2550
|
+
);
|
|
2551
|
+
return {
|
|
2552
|
+
data: {
|
|
2553
|
+
apps: apps.slice(0, pagination.limit),
|
|
2554
|
+
pagination: {
|
|
2555
|
+
...pagination,
|
|
2556
|
+
hasMore: apps.length > pagination.limit
|
|
2557
|
+
},
|
|
2558
|
+
filters: {
|
|
2559
|
+
projectId: params.projectId ?? null,
|
|
2560
|
+
organizationId: params.organizationId ?? null,
|
|
2561
|
+
forked: params.forked ?? "all"
|
|
2562
|
+
}
|
|
2563
|
+
},
|
|
2564
|
+
warnings: [],
|
|
2565
|
+
recommendedNextActions: apps.length > pagination.limit ? [`Pass offset=${pagination.offset + pagination.limit} to load the next page.`] : ["Use `remix_directory_get_app` for one app, or `remix_context_get_app_overview` for operational context on a chosen app."],
|
|
2566
|
+
logContext: {
|
|
2567
|
+
repoRoot: bindingContext.repoRoot,
|
|
2568
|
+
appId: bindingContext.binding?.currentAppId ?? null
|
|
2569
|
+
}
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
async function getApp(params) {
|
|
2573
|
+
const api = await createApiClient();
|
|
2574
|
+
const target = await resolveAppId(params);
|
|
2575
|
+
const app = unwrapResponseObject(await api.getApp(target.appId), "app");
|
|
2576
|
+
return {
|
|
2577
|
+
data: { app },
|
|
2578
|
+
warnings: [],
|
|
2579
|
+
recommendedNextActions: ["Use `remix_context_get_app_overview` for status and capability context, or `remix_ops_list_timeline` for bounded historical activity."],
|
|
2580
|
+
logContext: {
|
|
2581
|
+
repoRoot: target.repoRoot,
|
|
2582
|
+
appId: target.appId
|
|
2583
|
+
}
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
// src/tools/identity/register.ts
|
|
2588
|
+
function getAnnotations2(access) {
|
|
2589
|
+
return {
|
|
2590
|
+
readOnlyHint: access === "read",
|
|
2591
|
+
destructiveHint: false,
|
|
2592
|
+
idempotentHint: true,
|
|
2593
|
+
openWorldHint: false
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
function buildSuccessEnvelope2(tool, requestId, result) {
|
|
2597
|
+
return {
|
|
2598
|
+
schemaVersion: SCHEMA_VERSION,
|
|
2599
|
+
ok: true,
|
|
2600
|
+
tool,
|
|
2601
|
+
requestId: requestId ?? null,
|
|
2602
|
+
data: result.data,
|
|
2603
|
+
warnings: result.warnings ?? [],
|
|
2604
|
+
risks: result.risks ?? [],
|
|
2605
|
+
recommendedNextActions: result.recommendedNextActions ?? []
|
|
2606
|
+
};
|
|
2607
|
+
}
|
|
2608
|
+
function deriveErrorRisks2(normalized) {
|
|
2609
|
+
if (normalized.code === "DESTRUCTIVE_OPERATION_BLOCKED") {
|
|
2610
|
+
return ["A policy guard blocked a disallowed operation."];
|
|
2611
|
+
}
|
|
2612
|
+
return [];
|
|
2613
|
+
}
|
|
2614
|
+
function buildErrorEnvelope2(tool, requestId, error) {
|
|
2615
|
+
const normalized = normalizeToolError(error);
|
|
2616
|
+
return {
|
|
2617
|
+
schemaVersion: SCHEMA_VERSION,
|
|
2618
|
+
ok: false,
|
|
2619
|
+
tool,
|
|
2620
|
+
requestId: requestId ?? null,
|
|
2621
|
+
error: normalized,
|
|
2622
|
+
warnings: [],
|
|
2623
|
+
risks: deriveErrorRisks2(normalized),
|
|
2624
|
+
recommendedNextActions: normalized.code === "AUTH_REQUIRED" ? ["Run `remix login` or set COMERGE_ACCESS_TOKEN, then retry."] : []
|
|
2625
|
+
};
|
|
2626
|
+
}
|
|
2627
|
+
function registerTool2(server, context, params) {
|
|
2628
|
+
const errorSchema = makeErrorSchema();
|
|
2629
|
+
server.registerTool(
|
|
2630
|
+
params.name,
|
|
2631
|
+
{
|
|
2632
|
+
title: params.name,
|
|
2633
|
+
description: params.description,
|
|
2634
|
+
inputSchema: params.inputSchema,
|
|
2635
|
+
outputSchema: params.outputSchema,
|
|
2636
|
+
annotations: getAnnotations2(params.access)
|
|
2637
|
+
},
|
|
2638
|
+
async (rawArgs) => {
|
|
2639
|
+
const requestId = typeof rawArgs.requestId === "string" ? rawArgs.requestId : void 0;
|
|
2640
|
+
const startedAt = Date.now();
|
|
2641
|
+
try {
|
|
2642
|
+
assertToolAccess(context.policy, params.access);
|
|
2643
|
+
const result = await params.run(rawArgs);
|
|
2644
|
+
const envelope = buildSuccessEnvelope2(params.name, requestId, result);
|
|
2645
|
+
params.outputSchema.parse(envelope);
|
|
2646
|
+
context.logger.log({
|
|
2647
|
+
level: "info",
|
|
2648
|
+
message: "tool_completed",
|
|
2649
|
+
tool: params.name,
|
|
2650
|
+
requestId: envelope.requestId,
|
|
2651
|
+
durationMs: Date.now() - startedAt,
|
|
2652
|
+
result: "success",
|
|
2653
|
+
repoRoot: result.logContext?.repoRoot ?? null,
|
|
2654
|
+
appId: result.logContext?.appId ?? null,
|
|
2655
|
+
mrId: result.logContext?.mrId ?? null
|
|
2656
|
+
});
|
|
2657
|
+
return makeSuccessResult(envelope);
|
|
2658
|
+
} catch (error) {
|
|
2659
|
+
const envelope = buildErrorEnvelope2(params.name, requestId, error);
|
|
2660
|
+
errorSchema.parse(envelope);
|
|
2661
|
+
context.logger.log({
|
|
2662
|
+
level: "error",
|
|
2663
|
+
message: "tool_failed",
|
|
2664
|
+
tool: params.name,
|
|
2665
|
+
requestId: envelope.requestId,
|
|
2666
|
+
durationMs: Date.now() - startedAt,
|
|
2667
|
+
result: "error",
|
|
2668
|
+
errorCode: envelope.error.code
|
|
2669
|
+
});
|
|
2670
|
+
return makeErrorResult(envelope);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
);
|
|
2674
|
+
}
|
|
2675
|
+
function registerIdentityTools(server, context) {
|
|
2676
|
+
registerTool2(server, context, {
|
|
2677
|
+
name: "remix_identity_whoami",
|
|
2678
|
+
description: "Show the authenticated Remix user and, when cwd is provided, the current local binding and any immediately derivable roles for that bound workspace.",
|
|
2679
|
+
access: "read",
|
|
2680
|
+
inputSchema: whoamiInputSchema,
|
|
2681
|
+
outputSchema: whoamiSuccessSchema,
|
|
2682
|
+
run: async (args) => {
|
|
2683
|
+
const input = z5.object(whoamiInputSchema).parse(args);
|
|
2684
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
2685
|
+
return whoAmI({ cwd });
|
|
2686
|
+
}
|
|
2687
|
+
});
|
|
2688
|
+
registerTool2(server, context, {
|
|
2689
|
+
name: "remix_directory_list_organizations",
|
|
2690
|
+
description: "List organizations visible to the authenticated Remix user.",
|
|
2691
|
+
access: "read",
|
|
2692
|
+
inputSchema: whoamiInputSchema,
|
|
2693
|
+
outputSchema: listOrganizationsSuccessSchema,
|
|
2694
|
+
run: async () => listOrganizations()
|
|
2695
|
+
});
|
|
2696
|
+
registerTool2(server, context, {
|
|
2697
|
+
name: "remix_directory_get_organization",
|
|
2698
|
+
description: "Fetch one organization by id, or infer the bound repository's organization from cwd when possible.",
|
|
2699
|
+
access: "read",
|
|
2700
|
+
inputSchema: directoryOrganizationInputSchema,
|
|
2701
|
+
outputSchema: getOrganizationSuccessSchema,
|
|
2702
|
+
run: async (args) => {
|
|
2703
|
+
const input = z5.object(directoryOrganizationInputSchema).parse(args);
|
|
2704
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
2705
|
+
return getOrganization({
|
|
2706
|
+
organizationId: input.organizationId,
|
|
2707
|
+
cwd
|
|
2708
|
+
});
|
|
2709
|
+
}
|
|
2710
|
+
});
|
|
2711
|
+
registerTool2(server, context, {
|
|
2712
|
+
name: "remix_directory_list_projects",
|
|
2713
|
+
description: "List projects visible to the authenticated user, optionally narrowed to one organization or client app.",
|
|
2714
|
+
access: "read",
|
|
2715
|
+
inputSchema: directoryListProjectsInputSchema,
|
|
2716
|
+
outputSchema: listProjectsSuccessSchema,
|
|
2717
|
+
run: async (args) => {
|
|
2718
|
+
const input = z5.object(directoryListProjectsInputSchema).parse(args);
|
|
2719
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
2720
|
+
return listProjects({
|
|
2721
|
+
organizationId: input.organizationId,
|
|
2722
|
+
clientAppId: input.clientAppId,
|
|
2723
|
+
cwd
|
|
2724
|
+
});
|
|
2725
|
+
}
|
|
2726
|
+
});
|
|
2727
|
+
registerTool2(server, context, {
|
|
2728
|
+
name: "remix_directory_get_project",
|
|
2729
|
+
description: "Fetch one project by id, or infer the bound repository's project from cwd when possible.",
|
|
2730
|
+
access: "read",
|
|
2731
|
+
inputSchema: directoryProjectInputSchema,
|
|
2732
|
+
outputSchema: getProjectSuccessSchema,
|
|
2733
|
+
run: async (args) => {
|
|
2734
|
+
const input = z5.object(directoryProjectInputSchema).parse(args);
|
|
2735
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
2736
|
+
return getProject({
|
|
2737
|
+
projectId: input.projectId,
|
|
2738
|
+
cwd
|
|
2739
|
+
});
|
|
2740
|
+
}
|
|
2741
|
+
});
|
|
2742
|
+
registerTool2(server, context, {
|
|
2743
|
+
name: "remix_directory_list_apps",
|
|
2744
|
+
description: "List apps visible to the authenticated user, with optional organization or project filters and built-in pagination.",
|
|
2745
|
+
access: "read",
|
|
2746
|
+
inputSchema: directoryListAppsInputSchema,
|
|
2747
|
+
outputSchema: listAppsSuccessSchema,
|
|
2748
|
+
run: async (args) => {
|
|
2749
|
+
const input = z5.object(directoryListAppsInputSchema).parse(args);
|
|
2750
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
2751
|
+
return listApps2({
|
|
2752
|
+
projectId: input.projectId,
|
|
2753
|
+
organizationId: input.organizationId,
|
|
2754
|
+
forked: input.forked,
|
|
2755
|
+
limit: input.limit,
|
|
2756
|
+
offset: input.offset,
|
|
2757
|
+
cwd
|
|
2758
|
+
});
|
|
2759
|
+
}
|
|
2760
|
+
});
|
|
2761
|
+
registerTool2(server, context, {
|
|
2762
|
+
name: "remix_directory_get_app",
|
|
2763
|
+
description: "Fetch one app by id, or infer the bound repository's current app from cwd when possible.",
|
|
2764
|
+
access: "read",
|
|
2765
|
+
inputSchema: directoryAppInputSchema,
|
|
2766
|
+
outputSchema: getAppSuccessSchema,
|
|
2767
|
+
run: async (args) => {
|
|
2768
|
+
const input = z5.object(directoryAppInputSchema).parse(args);
|
|
2769
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
2770
|
+
return getApp({
|
|
2771
|
+
appId: input.appId,
|
|
2772
|
+
cwd
|
|
2773
|
+
});
|
|
2774
|
+
}
|
|
2775
|
+
});
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
// src/tools/memory/register.ts
|
|
2779
|
+
import { z as z7 } from "zod";
|
|
2780
|
+
|
|
2781
|
+
// src/contracts/memory.ts
|
|
2782
|
+
import { z as z6 } from "zod";
|
|
2783
|
+
var genericRecordSchema3 = z6.record(z6.string(), z6.unknown());
|
|
2784
|
+
var genericArraySchema3 = z6.array(genericRecordSchema3);
|
|
2785
|
+
var memoryKindSchema = z6.enum(["collab_turn", "change_step", "merge_request", "reconcile"]);
|
|
2786
|
+
var paginationSchema3 = z6.object({
|
|
2787
|
+
limit: z6.number().int().nonnegative(),
|
|
2788
|
+
offset: z6.number().int().nonnegative(),
|
|
2789
|
+
hasMore: z6.boolean()
|
|
2790
|
+
});
|
|
2791
|
+
var memorySummaryInputSchema = {
|
|
2792
|
+
...commonRequestFieldsSchema,
|
|
2793
|
+
appId: z6.string().trim().min(1).optional()
|
|
2794
|
+
};
|
|
2795
|
+
var memorySearchInputSchema = {
|
|
2796
|
+
...commonRequestFieldsSchema,
|
|
2797
|
+
appId: z6.string().trim().min(1).optional(),
|
|
2798
|
+
query: z6.string().trim().min(1),
|
|
2799
|
+
kinds: z6.array(memoryKindSchema).max(4).optional(),
|
|
2800
|
+
limit: z6.number().int().positive().max(50).optional(),
|
|
2801
|
+
offset: z6.number().int().nonnegative().optional(),
|
|
2802
|
+
createdAfter: z6.string().trim().min(1).optional(),
|
|
2803
|
+
createdBefore: z6.string().trim().min(1).optional()
|
|
2804
|
+
};
|
|
2805
|
+
var memoryTimelineInputSchema = {
|
|
2806
|
+
...commonRequestFieldsSchema,
|
|
2807
|
+
appId: z6.string().trim().min(1).optional(),
|
|
2808
|
+
kinds: z6.array(memoryKindSchema).max(4).optional(),
|
|
2809
|
+
limit: z6.number().int().positive().max(50).optional(),
|
|
2810
|
+
offset: z6.number().int().nonnegative().optional(),
|
|
2811
|
+
createdAfter: z6.string().trim().min(1).optional(),
|
|
2812
|
+
createdBefore: z6.string().trim().min(1).optional()
|
|
2813
|
+
};
|
|
2814
|
+
var changeStepDiffInputSchema = {
|
|
2815
|
+
...commonRequestFieldsSchema,
|
|
2816
|
+
appId: z6.string().trim().min(1).optional(),
|
|
2817
|
+
changeStepId: z6.string().trim().min(1)
|
|
2818
|
+
};
|
|
2819
|
+
var memorySummaryDataSchema = genericRecordSchema3;
|
|
2820
|
+
var memorySearchDataSchema = z6.object({
|
|
2821
|
+
items: genericArraySchema3,
|
|
2822
|
+
pagination: paginationSchema3
|
|
2823
|
+
});
|
|
2824
|
+
var memoryTimelineDataSchema = z6.object({
|
|
2825
|
+
items: genericArraySchema3,
|
|
2826
|
+
pagination: paginationSchema3
|
|
2827
|
+
});
|
|
2828
|
+
var changeStepDiffDataSchema = z6.object({
|
|
2829
|
+
changeStepId: z6.string(),
|
|
2830
|
+
appId: z6.string(),
|
|
2831
|
+
diff: z6.string(),
|
|
2832
|
+
diffSha256: z6.string(),
|
|
2833
|
+
contentType: z6.string(),
|
|
2834
|
+
encoding: z6.string(),
|
|
2835
|
+
expiresIn: z6.number().int().positive()
|
|
2836
|
+
});
|
|
2837
|
+
var memorySummarySuccessSchema = makeSuccessSchema(memorySummaryDataSchema);
|
|
2838
|
+
var memorySearchSuccessSchema = makeSuccessSchema(memorySearchDataSchema);
|
|
2839
|
+
var memoryTimelineSuccessSchema = makeSuccessSchema(memoryTimelineDataSchema);
|
|
2840
|
+
var changeStepDiffSuccessSchema = makeSuccessSchema(changeStepDiffDataSchema);
|
|
2841
|
+
|
|
2842
|
+
// src/domain/memoryAdapter.ts
|
|
2843
|
+
import { readCollabBinding as readCollabBinding2 } from "@remixhq/core/binding";
|
|
2844
|
+
import { findGitRoot as findGitRoot3 } from "@remixhq/core/repo";
|
|
2845
|
+
function buildSummaryNextActions(summary) {
|
|
2846
|
+
const actions = [];
|
|
2847
|
+
actions.push(
|
|
2848
|
+
"Use `remix_collab_memory_search` next for focused why/history/failed-attempt questions about this app."
|
|
2849
|
+
);
|
|
2850
|
+
const recentItemCount = summary.recent.collabTurns.length + summary.recent.changeSteps.length + summary.recent.mergeRequests.length + summary.recent.reconciles.length;
|
|
2851
|
+
if (recentItemCount > 0) {
|
|
2852
|
+
actions.push("Use `remix_collab_memory_timeline` next if you need the chronological sequence of recent activity.");
|
|
2853
|
+
}
|
|
2854
|
+
if (summary.counts.failedChangeStepCount > 0 || summary.counts.reconcileCount > 0) {
|
|
2855
|
+
actions.push(
|
|
2856
|
+
"For prior failures or recovery history, run `remix_collab_memory_search` with a focused query and `kinds` narrowed to `change_step` and `reconcile` when appropriate."
|
|
2857
|
+
);
|
|
2858
|
+
}
|
|
2859
|
+
return actions;
|
|
2860
|
+
}
|
|
2861
|
+
function getFirstChangeStepId(items) {
|
|
2862
|
+
const changeStep = items.find((item) => item.kind === "change_step");
|
|
2863
|
+
return changeStep?.id ?? null;
|
|
1665
2864
|
}
|
|
1666
2865
|
function buildSearchNextActions(result) {
|
|
1667
2866
|
if (result.items.length === 0) {
|
|
@@ -1716,14 +2915,14 @@ function unwrapResponseObject2(resp, label) {
|
|
|
1716
2915
|
}
|
|
1717
2916
|
return obj;
|
|
1718
2917
|
}
|
|
1719
|
-
function
|
|
2918
|
+
function makeNotBoundError2() {
|
|
1720
2919
|
const error = new Error("Repository is not bound to Remix.");
|
|
1721
2920
|
error.hint = "Run `remix_collab_init` in this repository, or pass `appId` explicitly for a direct memory read.";
|
|
1722
2921
|
return error;
|
|
1723
2922
|
}
|
|
1724
|
-
async function
|
|
2923
|
+
async function maybeFindGitRoot2(cwd) {
|
|
1725
2924
|
try {
|
|
1726
|
-
return await
|
|
2925
|
+
return await findGitRoot3(cwd);
|
|
1727
2926
|
} catch {
|
|
1728
2927
|
return null;
|
|
1729
2928
|
}
|
|
@@ -1733,13 +2932,13 @@ async function resolveMemoryTarget(params) {
|
|
|
1733
2932
|
if (explicitAppId) {
|
|
1734
2933
|
return {
|
|
1735
2934
|
appId: explicitAppId,
|
|
1736
|
-
repoRoot: await
|
|
2935
|
+
repoRoot: await maybeFindGitRoot2(params.cwd)
|
|
1737
2936
|
};
|
|
1738
2937
|
}
|
|
1739
|
-
const repoRoot = await
|
|
1740
|
-
const binding = await
|
|
2938
|
+
const repoRoot = await findGitRoot3(params.cwd);
|
|
2939
|
+
const binding = await readCollabBinding2(repoRoot);
|
|
1741
2940
|
if (!binding) {
|
|
1742
|
-
throw
|
|
2941
|
+
throw makeNotBoundError2();
|
|
1743
2942
|
}
|
|
1744
2943
|
return {
|
|
1745
2944
|
appId: binding.currentAppId,
|
|
@@ -1809,7 +3008,7 @@ async function getChangeStepDiff(params) {
|
|
|
1809
3008
|
}
|
|
1810
3009
|
|
|
1811
3010
|
// src/tools/memory/register.ts
|
|
1812
|
-
function
|
|
3011
|
+
function getAnnotations3(access) {
|
|
1813
3012
|
return {
|
|
1814
3013
|
readOnlyHint: access === "read",
|
|
1815
3014
|
destructiveHint: false,
|
|
@@ -1817,7 +3016,7 @@ function getAnnotations2(access) {
|
|
|
1817
3016
|
openWorldHint: false
|
|
1818
3017
|
};
|
|
1819
3018
|
}
|
|
1820
|
-
function
|
|
3019
|
+
function buildSuccessEnvelope3(tool, requestId, result) {
|
|
1821
3020
|
return {
|
|
1822
3021
|
schemaVersion: SCHEMA_VERSION,
|
|
1823
3022
|
ok: true,
|
|
@@ -1829,7 +3028,7 @@ function buildSuccessEnvelope2(tool, requestId, result) {
|
|
|
1829
3028
|
recommendedNextActions: result.recommendedNextActions ?? []
|
|
1830
3029
|
};
|
|
1831
3030
|
}
|
|
1832
|
-
function
|
|
3031
|
+
function buildErrorEnvelope3(tool, requestId, error) {
|
|
1833
3032
|
const normalized = normalizeToolError(error);
|
|
1834
3033
|
return {
|
|
1835
3034
|
schemaVersion: SCHEMA_VERSION,
|
|
@@ -1838,17 +3037,17 @@ function buildErrorEnvelope2(tool, requestId, error) {
|
|
|
1838
3037
|
requestId: requestId ?? null,
|
|
1839
3038
|
error: normalized,
|
|
1840
3039
|
warnings: [],
|
|
1841
|
-
risks:
|
|
3040
|
+
risks: deriveErrorRisks3(normalized),
|
|
1842
3041
|
recommendedNextActions: normalized.code === "AUTH_REQUIRED" ? ["Run `remix login` or set COMERGE_ACCESS_TOKEN, then retry."] : []
|
|
1843
3042
|
};
|
|
1844
3043
|
}
|
|
1845
|
-
function
|
|
3044
|
+
function deriveErrorRisks3(normalized) {
|
|
1846
3045
|
if (normalized.code === "DESTRUCTIVE_OPERATION_BLOCKED") {
|
|
1847
3046
|
return ["A policy guard blocked a disallowed operation."];
|
|
1848
3047
|
}
|
|
1849
3048
|
return [];
|
|
1850
3049
|
}
|
|
1851
|
-
function
|
|
3050
|
+
function registerTool3(server, context, params) {
|
|
1852
3051
|
const errorSchema = makeErrorSchema();
|
|
1853
3052
|
server.registerTool(
|
|
1854
3053
|
params.name,
|
|
@@ -1857,7 +3056,7 @@ function registerTool2(server, context, params) {
|
|
|
1857
3056
|
description: params.description,
|
|
1858
3057
|
inputSchema: params.inputSchema,
|
|
1859
3058
|
outputSchema: params.outputSchema,
|
|
1860
|
-
annotations:
|
|
3059
|
+
annotations: getAnnotations3(params.access)
|
|
1861
3060
|
},
|
|
1862
3061
|
async (rawArgs) => {
|
|
1863
3062
|
const requestId = typeof rawArgs.requestId === "string" ? rawArgs.requestId : void 0;
|
|
@@ -1865,7 +3064,7 @@ function registerTool2(server, context, params) {
|
|
|
1865
3064
|
try {
|
|
1866
3065
|
assertToolAccess(context.policy, params.access);
|
|
1867
3066
|
const result = await params.run(rawArgs);
|
|
1868
|
-
const envelope =
|
|
3067
|
+
const envelope = buildSuccessEnvelope3(params.name, requestId, result);
|
|
1869
3068
|
params.outputSchema.parse(envelope);
|
|
1870
3069
|
context.logger.log({
|
|
1871
3070
|
level: "info",
|
|
@@ -1881,7 +3080,7 @@ function registerTool2(server, context, params) {
|
|
|
1881
3080
|
});
|
|
1882
3081
|
return makeSuccessResult(envelope);
|
|
1883
3082
|
} catch (error) {
|
|
1884
|
-
const envelope =
|
|
3083
|
+
const envelope = buildErrorEnvelope3(params.name, requestId, error);
|
|
1885
3084
|
errorSchema.parse(envelope);
|
|
1886
3085
|
context.logger.log({
|
|
1887
3086
|
level: "error",
|
|
@@ -1898,14 +3097,14 @@ function registerTool2(server, context, params) {
|
|
|
1898
3097
|
);
|
|
1899
3098
|
}
|
|
1900
3099
|
function registerMemoryTools(server, context) {
|
|
1901
|
-
|
|
3100
|
+
registerTool3(server, context, {
|
|
1902
3101
|
name: "remix_collab_memory_summary",
|
|
1903
3102
|
description: "First read for a bound app's current collaboration state, recent reasoning context, and merge or reconcile history before deeper inspection or any raw git history lookup.",
|
|
1904
3103
|
access: "read",
|
|
1905
3104
|
inputSchema: memorySummaryInputSchema,
|
|
1906
3105
|
outputSchema: memorySummarySuccessSchema,
|
|
1907
3106
|
run: async (args) => {
|
|
1908
|
-
const input =
|
|
3107
|
+
const input = z7.object(memorySummaryInputSchema).parse(args);
|
|
1909
3108
|
const cwd = resolvePolicyCwd(context.policy, input.cwd);
|
|
1910
3109
|
return getMemorySummary({
|
|
1911
3110
|
cwd,
|
|
@@ -1913,14 +3112,14 @@ function registerMemoryTools(server, context) {
|
|
|
1913
3112
|
});
|
|
1914
3113
|
}
|
|
1915
3114
|
});
|
|
1916
|
-
|
|
3115
|
+
registerTool3(server, context, {
|
|
1917
3116
|
name: "remix_collab_memory_search",
|
|
1918
3117
|
description: "Default tool for why/history/failed-attempt/user-intent questions. Search prompts, diffs, merge activity, reconciles, and other historical context before using raw git for exact repository facts.",
|
|
1919
3118
|
access: "read",
|
|
1920
3119
|
inputSchema: memorySearchInputSchema,
|
|
1921
3120
|
outputSchema: memorySearchSuccessSchema,
|
|
1922
3121
|
run: async (args) => {
|
|
1923
|
-
const input =
|
|
3122
|
+
const input = z7.object(memorySearchInputSchema).parse(args);
|
|
1924
3123
|
const cwd = resolvePolicyCwd(context.policy, input.cwd);
|
|
1925
3124
|
return searchMemory({
|
|
1926
3125
|
cwd,
|
|
@@ -1934,14 +3133,14 @@ function registerMemoryTools(server, context) {
|
|
|
1934
3133
|
});
|
|
1935
3134
|
}
|
|
1936
3135
|
});
|
|
1937
|
-
|
|
3136
|
+
registerTool3(server, context, {
|
|
1938
3137
|
name: "remix_collab_memory_timeline",
|
|
1939
3138
|
description: "Chronological view of collaboration memory for understanding what happened and in what order, with optional filters for bounded historical inspection before any exact-facts raw git follow-up.",
|
|
1940
3139
|
access: "read",
|
|
1941
3140
|
inputSchema: memoryTimelineInputSchema,
|
|
1942
3141
|
outputSchema: memoryTimelineSuccessSchema,
|
|
1943
3142
|
run: async (args) => {
|
|
1944
|
-
const input =
|
|
3143
|
+
const input = z7.object(memoryTimelineInputSchema).parse(args);
|
|
1945
3144
|
const cwd = resolvePolicyCwd(context.policy, input.cwd);
|
|
1946
3145
|
return getMemoryTimeline({
|
|
1947
3146
|
cwd,
|
|
@@ -1954,14 +3153,14 @@ function registerMemoryTools(server, context) {
|
|
|
1954
3153
|
});
|
|
1955
3154
|
}
|
|
1956
3155
|
});
|
|
1957
|
-
|
|
3156
|
+
registerTool3(server, context, {
|
|
1958
3157
|
name: "remix_collab_memory_change_step_diff",
|
|
1959
3158
|
description: "Second-hop expansion tool that fetches the full stored diff for a specific change step after memory search, timeline, or review work has identified the relevant `changeStepId`, keeping historical inspection inside Remix before raw git fallback.",
|
|
1960
3159
|
access: "read",
|
|
1961
3160
|
inputSchema: changeStepDiffInputSchema,
|
|
1962
3161
|
outputSchema: changeStepDiffSuccessSchema,
|
|
1963
3162
|
run: async (args) => {
|
|
1964
|
-
const input =
|
|
3163
|
+
const input = z7.object(changeStepDiffInputSchema).parse(args);
|
|
1965
3164
|
const cwd = resolvePolicyCwd(context.policy, input.cwd);
|
|
1966
3165
|
return getChangeStepDiff({
|
|
1967
3166
|
cwd,
|
|
@@ -1972,6 +3171,442 @@ function registerMemoryTools(server, context) {
|
|
|
1972
3171
|
});
|
|
1973
3172
|
}
|
|
1974
3173
|
|
|
3174
|
+
// src/tools/ops/register.ts
|
|
3175
|
+
import { z as z9 } from "zod";
|
|
3176
|
+
|
|
3177
|
+
// src/contracts/ops.ts
|
|
3178
|
+
import { z as z8 } from "zod";
|
|
3179
|
+
var genericRecordSchema4 = z8.record(z8.string(), z8.unknown());
|
|
3180
|
+
var appScopedInputSchema = {
|
|
3181
|
+
...commonRequestFieldsSchema,
|
|
3182
|
+
appId: z8.string().trim().min(1).optional()
|
|
3183
|
+
};
|
|
3184
|
+
var editQueueInputSchema = {
|
|
3185
|
+
...appScopedInputSchema,
|
|
3186
|
+
limit: z8.number().int().positive().max(100).optional(),
|
|
3187
|
+
offset: z8.number().int().nonnegative().optional()
|
|
3188
|
+
};
|
|
3189
|
+
var bundleInputSchema = {
|
|
3190
|
+
...appScopedInputSchema,
|
|
3191
|
+
bundleId: z8.string().trim().min(1)
|
|
3192
|
+
};
|
|
3193
|
+
var timelineInputSchema = {
|
|
3194
|
+
...appScopedInputSchema,
|
|
3195
|
+
limit: z8.number().int().positive().max(100).optional(),
|
|
3196
|
+
cursor: z8.string().trim().min(1).optional()
|
|
3197
|
+
};
|
|
3198
|
+
var agentRunsInputSchema = {
|
|
3199
|
+
...appScopedInputSchema,
|
|
3200
|
+
limit: z8.number().int().positive().max(100).optional(),
|
|
3201
|
+
offset: z8.number().int().nonnegative().optional(),
|
|
3202
|
+
status: z8.string().trim().min(1).optional(),
|
|
3203
|
+
currentPhase: z8.string().trim().min(1).optional(),
|
|
3204
|
+
createdAfter: z8.string().datetime().optional(),
|
|
3205
|
+
createdBefore: z8.string().datetime().optional()
|
|
3206
|
+
};
|
|
3207
|
+
var agentRunInputSchema = {
|
|
3208
|
+
...appScopedInputSchema,
|
|
3209
|
+
runId: z8.string().trim().min(1)
|
|
3210
|
+
};
|
|
3211
|
+
var agentRunEventsInputSchema = {
|
|
3212
|
+
...appScopedInputSchema,
|
|
3213
|
+
runId: z8.string().trim().min(1),
|
|
3214
|
+
limit: z8.number().int().positive().max(100).optional(),
|
|
3215
|
+
offset: z8.number().int().nonnegative().optional(),
|
|
3216
|
+
createdAfter: z8.string().datetime().optional(),
|
|
3217
|
+
createdBefore: z8.string().datetime().optional()
|
|
3218
|
+
};
|
|
3219
|
+
var appOverviewSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3220
|
+
var editQueueSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3221
|
+
var bundleSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3222
|
+
var timelineSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3223
|
+
var agentRunsSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3224
|
+
var agentRunSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3225
|
+
var agentRunEventsSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3226
|
+
var sandboxStatusSuccessSchema = makeSuccessSchema(genericRecordSchema4);
|
|
3227
|
+
|
|
3228
|
+
// src/domain/opsAdapter.ts
|
|
3229
|
+
async function resolveAppTarget(_api, params) {
|
|
3230
|
+
const explicitAppId = params.appId?.trim();
|
|
3231
|
+
const bindingContext = await loadBindingContext(params.cwd);
|
|
3232
|
+
if (explicitAppId) {
|
|
3233
|
+
return {
|
|
3234
|
+
appId: explicitAppId,
|
|
3235
|
+
repoRoot: bindingContext.repoRoot
|
|
3236
|
+
};
|
|
3237
|
+
}
|
|
3238
|
+
if (!bindingContext.binding) {
|
|
3239
|
+
throw makeNotBoundError("App id was not provided and the current repository is not bound to Remix.");
|
|
3240
|
+
}
|
|
3241
|
+
return {
|
|
3242
|
+
appId: bindingContext.binding.currentAppId,
|
|
3243
|
+
repoRoot: bindingContext.repoRoot
|
|
3244
|
+
};
|
|
3245
|
+
}
|
|
3246
|
+
async function getAppOverview(params) {
|
|
3247
|
+
const api = await createApiClient();
|
|
3248
|
+
const target = await resolveAppTarget(api, params);
|
|
3249
|
+
const data = unwrapResponseObject(await api.getAppOverview(target.appId), "app overview");
|
|
3250
|
+
return {
|
|
3251
|
+
data,
|
|
3252
|
+
warnings: [],
|
|
3253
|
+
recommendedNextActions: [
|
|
3254
|
+
"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."
|
|
3255
|
+
],
|
|
3256
|
+
logContext: target
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
3259
|
+
async function getEditQueue(params) {
|
|
3260
|
+
const api = await createApiClient();
|
|
3261
|
+
const target = await resolveAppTarget(api, params);
|
|
3262
|
+
const data = unwrapResponseObject(
|
|
3263
|
+
await api.listAppEditQueue(target.appId, {
|
|
3264
|
+
limit: params.limit,
|
|
3265
|
+
offset: params.offset
|
|
3266
|
+
}),
|
|
3267
|
+
"edit queue"
|
|
3268
|
+
);
|
|
3269
|
+
const pageInfo = typeof data.pageInfo === "object" && data.pageInfo ? data.pageInfo : null;
|
|
3270
|
+
return {
|
|
3271
|
+
data,
|
|
3272
|
+
warnings: [],
|
|
3273
|
+
recommendedNextActions: pageInfo?.hasMore === true && typeof pageInfo.limit === "number" && typeof pageInfo.offset === "number" ? [`Pass offset=${pageInfo.offset + pageInfo.limit} to load the next edit queue page.`] : [],
|
|
3274
|
+
logContext: target
|
|
3275
|
+
};
|
|
3276
|
+
}
|
|
3277
|
+
async function getBundle(params) {
|
|
3278
|
+
const api = await createApiClient();
|
|
3279
|
+
const target = await resolveAppTarget(api, params);
|
|
3280
|
+
const data = unwrapResponseObject(await api.getBundle(target.appId, params.bundleId), "bundle");
|
|
3281
|
+
return {
|
|
3282
|
+
data,
|
|
3283
|
+
warnings: [],
|
|
3284
|
+
recommendedNextActions: ["Use the bundle download-url client methods only when you need the actual artifact, not just metadata inspection."],
|
|
3285
|
+
logContext: target
|
|
3286
|
+
};
|
|
3287
|
+
}
|
|
3288
|
+
async function listTimeline(params) {
|
|
3289
|
+
const api = await createApiClient();
|
|
3290
|
+
const target = await resolveAppTarget(api, params);
|
|
3291
|
+
const data = unwrapResponseObject(
|
|
3292
|
+
await api.listAppTimeline(target.appId, {
|
|
3293
|
+
limit: params.limit,
|
|
3294
|
+
cursor: params.cursor
|
|
3295
|
+
}),
|
|
3296
|
+
"app timeline"
|
|
3297
|
+
);
|
|
3298
|
+
const pageInfo = typeof data.pageInfo === "object" && data.pageInfo ? data.pageInfo : null;
|
|
3299
|
+
return {
|
|
3300
|
+
data,
|
|
3301
|
+
warnings: [],
|
|
3302
|
+
recommendedNextActions: pageInfo?.hasMore === true && typeof pageInfo.nextCursor === "string" ? [`Pass cursor=${pageInfo.nextCursor} to load the next timeline page.`] : [],
|
|
3303
|
+
logContext: target
|
|
3304
|
+
};
|
|
3305
|
+
}
|
|
3306
|
+
async function listAgentRuns(params) {
|
|
3307
|
+
const api = await createApiClient();
|
|
3308
|
+
const target = await resolveAppTarget(api, params);
|
|
3309
|
+
const data = unwrapResponseObject(
|
|
3310
|
+
await api.listAgentRuns(target.appId, {
|
|
3311
|
+
limit: params.limit,
|
|
3312
|
+
offset: params.offset,
|
|
3313
|
+
status: params.status,
|
|
3314
|
+
currentPhase: params.currentPhase,
|
|
3315
|
+
createdAfter: params.createdAfter,
|
|
3316
|
+
createdBefore: params.createdBefore
|
|
3317
|
+
}),
|
|
3318
|
+
"agent runs"
|
|
3319
|
+
);
|
|
3320
|
+
const pageInfo = typeof data.pageInfo === "object" && data.pageInfo ? data.pageInfo : null;
|
|
3321
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
3322
|
+
const firstRunId = items.length > 0 && items[0] && typeof items[0] === "object" && typeof items[0].id === "string" ? items[0].id : null;
|
|
3323
|
+
const recommendedNextActions = [];
|
|
3324
|
+
if (firstRunId) {
|
|
3325
|
+
recommendedNextActions.push(
|
|
3326
|
+
`Use \`remix_ops_get_agent_run\` with \`runId=${firstRunId}\` for the top listed run, then \`remix_ops_list_agent_run_events\` if you need its event stream.`
|
|
3327
|
+
);
|
|
3328
|
+
}
|
|
3329
|
+
if (pageInfo?.hasMore === true && typeof pageInfo.limit === "number" && typeof pageInfo.offset === "number") {
|
|
3330
|
+
recommendedNextActions.push(`Pass offset=${pageInfo.offset + pageInfo.limit} to load the next agent-runs page.`);
|
|
3331
|
+
}
|
|
3332
|
+
return {
|
|
3333
|
+
data,
|
|
3334
|
+
warnings: [],
|
|
3335
|
+
recommendedNextActions,
|
|
3336
|
+
logContext: target
|
|
3337
|
+
};
|
|
3338
|
+
}
|
|
3339
|
+
async function getAgentRun(params) {
|
|
3340
|
+
const api = await createApiClient();
|
|
3341
|
+
const target = await resolveAppTarget(api, params);
|
|
3342
|
+
const data = unwrapResponseObject(await api.getAgentRun(target.appId, params.runId), "agent run");
|
|
3343
|
+
return {
|
|
3344
|
+
data,
|
|
3345
|
+
warnings: [],
|
|
3346
|
+
recommendedNextActions: [`Use \`remix_ops_list_agent_run_events\` with \`runId=${params.runId}\` for the event stream behind this run.`],
|
|
3347
|
+
logContext: target
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
3350
|
+
async function listAgentRunEvents(params) {
|
|
3351
|
+
const api = await createApiClient();
|
|
3352
|
+
const target = await resolveAppTarget(api, params);
|
|
3353
|
+
const data = unwrapResponseObject(
|
|
3354
|
+
await api.listAgentRunEvents(target.appId, params.runId, {
|
|
3355
|
+
limit: params.limit,
|
|
3356
|
+
offset: params.offset,
|
|
3357
|
+
createdAfter: params.createdAfter,
|
|
3358
|
+
createdBefore: params.createdBefore
|
|
3359
|
+
}),
|
|
3360
|
+
"agent run events"
|
|
3361
|
+
);
|
|
3362
|
+
const pageInfo = typeof data.pageInfo === "object" && data.pageInfo ? data.pageInfo : null;
|
|
3363
|
+
return {
|
|
3364
|
+
data,
|
|
3365
|
+
warnings: [],
|
|
3366
|
+
recommendedNextActions: pageInfo?.hasMore === true && typeof pageInfo.limit === "number" && typeof pageInfo.offset === "number" ? [`Pass offset=${pageInfo.offset + pageInfo.limit} to load the next event page.`] : [],
|
|
3367
|
+
logContext: target
|
|
3368
|
+
};
|
|
3369
|
+
}
|
|
3370
|
+
async function getSandboxStatus(params) {
|
|
3371
|
+
const api = await createApiClient();
|
|
3372
|
+
const target = await resolveAppTarget(api, params);
|
|
3373
|
+
const data = unwrapResponseObject(await api.getSandboxStatus(target.appId), "sandbox status");
|
|
3374
|
+
return {
|
|
3375
|
+
data,
|
|
3376
|
+
warnings: [],
|
|
3377
|
+
recommendedNextActions: ["Use the sandbox metadata here to decide whether a resume is plausible before attempting any write-side sandbox action."],
|
|
3378
|
+
logContext: target
|
|
3379
|
+
};
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
// src/tools/ops/register.ts
|
|
3383
|
+
function getAnnotations4(access) {
|
|
3384
|
+
return {
|
|
3385
|
+
readOnlyHint: access === "read",
|
|
3386
|
+
destructiveHint: false,
|
|
3387
|
+
idempotentHint: true,
|
|
3388
|
+
openWorldHint: false
|
|
3389
|
+
};
|
|
3390
|
+
}
|
|
3391
|
+
function buildSuccessEnvelope4(tool, requestId, result) {
|
|
3392
|
+
return {
|
|
3393
|
+
schemaVersion: SCHEMA_VERSION,
|
|
3394
|
+
ok: true,
|
|
3395
|
+
tool,
|
|
3396
|
+
requestId: requestId ?? null,
|
|
3397
|
+
data: result.data,
|
|
3398
|
+
warnings: result.warnings ?? [],
|
|
3399
|
+
risks: result.risks ?? [],
|
|
3400
|
+
recommendedNextActions: result.recommendedNextActions ?? []
|
|
3401
|
+
};
|
|
3402
|
+
}
|
|
3403
|
+
function deriveErrorRisks4(normalized) {
|
|
3404
|
+
if (normalized.code === "DESTRUCTIVE_OPERATION_BLOCKED") {
|
|
3405
|
+
return ["A policy guard blocked a disallowed operation."];
|
|
3406
|
+
}
|
|
3407
|
+
return [];
|
|
3408
|
+
}
|
|
3409
|
+
function buildErrorEnvelope4(tool, requestId, error) {
|
|
3410
|
+
const normalized = normalizeToolError(error);
|
|
3411
|
+
return {
|
|
3412
|
+
schemaVersion: SCHEMA_VERSION,
|
|
3413
|
+
ok: false,
|
|
3414
|
+
tool,
|
|
3415
|
+
requestId: requestId ?? null,
|
|
3416
|
+
error: normalized,
|
|
3417
|
+
warnings: [],
|
|
3418
|
+
risks: deriveErrorRisks4(normalized),
|
|
3419
|
+
recommendedNextActions: normalized.code === "AUTH_REQUIRED" ? ["Run `remix login` or set COMERGE_ACCESS_TOKEN, then retry."] : []
|
|
3420
|
+
};
|
|
3421
|
+
}
|
|
3422
|
+
function registerTool4(server, context, params) {
|
|
3423
|
+
const errorSchema = makeErrorSchema();
|
|
3424
|
+
server.registerTool(
|
|
3425
|
+
params.name,
|
|
3426
|
+
{
|
|
3427
|
+
title: params.name,
|
|
3428
|
+
description: params.description,
|
|
3429
|
+
inputSchema: params.inputSchema,
|
|
3430
|
+
outputSchema: params.outputSchema,
|
|
3431
|
+
annotations: getAnnotations4(params.access)
|
|
3432
|
+
},
|
|
3433
|
+
async (rawArgs) => {
|
|
3434
|
+
const requestId = typeof rawArgs.requestId === "string" ? rawArgs.requestId : void 0;
|
|
3435
|
+
const startedAt = Date.now();
|
|
3436
|
+
try {
|
|
3437
|
+
assertToolAccess(context.policy, params.access);
|
|
3438
|
+
const result = await params.run(rawArgs);
|
|
3439
|
+
const envelope = buildSuccessEnvelope4(params.name, requestId, result);
|
|
3440
|
+
params.outputSchema.parse(envelope);
|
|
3441
|
+
context.logger.log({
|
|
3442
|
+
level: "info",
|
|
3443
|
+
message: "tool_completed",
|
|
3444
|
+
tool: params.name,
|
|
3445
|
+
requestId: envelope.requestId,
|
|
3446
|
+
durationMs: Date.now() - startedAt,
|
|
3447
|
+
result: "success",
|
|
3448
|
+
repoRoot: result.logContext?.repoRoot ?? null,
|
|
3449
|
+
appId: result.logContext?.appId ?? null,
|
|
3450
|
+
mrId: result.logContext?.mrId ?? null
|
|
3451
|
+
});
|
|
3452
|
+
return makeSuccessResult(envelope);
|
|
3453
|
+
} catch (error) {
|
|
3454
|
+
const envelope = buildErrorEnvelope4(params.name, requestId, error);
|
|
3455
|
+
errorSchema.parse(envelope);
|
|
3456
|
+
context.logger.log({
|
|
3457
|
+
level: "error",
|
|
3458
|
+
message: "tool_failed",
|
|
3459
|
+
tool: params.name,
|
|
3460
|
+
requestId: envelope.requestId,
|
|
3461
|
+
durationMs: Date.now() - startedAt,
|
|
3462
|
+
result: "error",
|
|
3463
|
+
errorCode: envelope.error.code
|
|
3464
|
+
});
|
|
3465
|
+
return makeErrorResult(envelope);
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
);
|
|
3469
|
+
}
|
|
3470
|
+
function registerOpsTools(server, context) {
|
|
3471
|
+
registerTool4(server, context, {
|
|
3472
|
+
name: "remix_context_get_app_overview",
|
|
3473
|
+
description: "Read the current app's overview, capabilities, and workflow readiness without mutating state.",
|
|
3474
|
+
access: "read",
|
|
3475
|
+
inputSchema: appScopedInputSchema,
|
|
3476
|
+
outputSchema: appOverviewSuccessSchema,
|
|
3477
|
+
run: async (args) => {
|
|
3478
|
+
const input = z9.object(appScopedInputSchema).parse(args);
|
|
3479
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
3480
|
+
return getAppOverview({
|
|
3481
|
+
appId: input.appId,
|
|
3482
|
+
cwd
|
|
3483
|
+
});
|
|
3484
|
+
}
|
|
3485
|
+
});
|
|
3486
|
+
registerTool4(server, context, {
|
|
3487
|
+
name: "remix_ops_get_edit_queue",
|
|
3488
|
+
description: "Inspect the pending edit queue for one app with bounded pagination.",
|
|
3489
|
+
access: "read",
|
|
3490
|
+
inputSchema: editQueueInputSchema,
|
|
3491
|
+
outputSchema: editQueueSuccessSchema,
|
|
3492
|
+
run: async (args) => {
|
|
3493
|
+
const input = z9.object(editQueueInputSchema).parse(args);
|
|
3494
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
3495
|
+
return getEditQueue({
|
|
3496
|
+
appId: input.appId,
|
|
3497
|
+
cwd,
|
|
3498
|
+
limit: input.limit,
|
|
3499
|
+
offset: input.offset
|
|
3500
|
+
});
|
|
3501
|
+
}
|
|
3502
|
+
});
|
|
3503
|
+
registerTool4(server, context, {
|
|
3504
|
+
name: "remix_ops_get_bundle",
|
|
3505
|
+
description: "Inspect one app bundle by id without downloading the artifact payload.",
|
|
3506
|
+
access: "read",
|
|
3507
|
+
inputSchema: bundleInputSchema,
|
|
3508
|
+
outputSchema: bundleSuccessSchema,
|
|
3509
|
+
run: async (args) => {
|
|
3510
|
+
const input = z9.object(bundleInputSchema).parse(args);
|
|
3511
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
3512
|
+
return getBundle({
|
|
3513
|
+
appId: input.appId,
|
|
3514
|
+
cwd,
|
|
3515
|
+
bundleId: input.bundleId
|
|
3516
|
+
});
|
|
3517
|
+
}
|
|
3518
|
+
});
|
|
3519
|
+
registerTool4(server, context, {
|
|
3520
|
+
name: "remix_ops_list_timeline",
|
|
3521
|
+
description: "List bounded timeline events for one app using the backend cursor pagination model.",
|
|
3522
|
+
access: "read",
|
|
3523
|
+
inputSchema: timelineInputSchema,
|
|
3524
|
+
outputSchema: timelineSuccessSchema,
|
|
3525
|
+
run: async (args) => {
|
|
3526
|
+
const input = z9.object(timelineInputSchema).parse(args);
|
|
3527
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
3528
|
+
return listTimeline({
|
|
3529
|
+
appId: input.appId,
|
|
3530
|
+
cwd,
|
|
3531
|
+
limit: input.limit,
|
|
3532
|
+
cursor: input.cursor
|
|
3533
|
+
});
|
|
3534
|
+
}
|
|
3535
|
+
});
|
|
3536
|
+
registerTool4(server, context, {
|
|
3537
|
+
name: "remix_ops_get_agent_run",
|
|
3538
|
+
description: "Fetch one stored agent run for an app, including status, phase, and summary metadata.",
|
|
3539
|
+
access: "read",
|
|
3540
|
+
inputSchema: agentRunInputSchema,
|
|
3541
|
+
outputSchema: agentRunSuccessSchema,
|
|
3542
|
+
run: async (args) => {
|
|
3543
|
+
const input = z9.object(agentRunInputSchema).parse(args);
|
|
3544
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
3545
|
+
return getAgentRun({
|
|
3546
|
+
appId: input.appId,
|
|
3547
|
+
cwd,
|
|
3548
|
+
runId: input.runId
|
|
3549
|
+
});
|
|
3550
|
+
}
|
|
3551
|
+
});
|
|
3552
|
+
registerTool4(server, context, {
|
|
3553
|
+
name: "remix_ops_list_agent_runs",
|
|
3554
|
+
description: "List paginated agent runs for one app so run ids can be discovered before deeper inspection.",
|
|
3555
|
+
access: "read",
|
|
3556
|
+
inputSchema: agentRunsInputSchema,
|
|
3557
|
+
outputSchema: agentRunsSuccessSchema,
|
|
3558
|
+
run: async (args) => {
|
|
3559
|
+
const input = z9.object(agentRunsInputSchema).parse(args);
|
|
3560
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
3561
|
+
return listAgentRuns({
|
|
3562
|
+
appId: input.appId,
|
|
3563
|
+
cwd,
|
|
3564
|
+
limit: input.limit,
|
|
3565
|
+
offset: input.offset,
|
|
3566
|
+
status: input.status,
|
|
3567
|
+
currentPhase: input.currentPhase,
|
|
3568
|
+
createdAfter: input.createdAfter,
|
|
3569
|
+
createdBefore: input.createdBefore
|
|
3570
|
+
});
|
|
3571
|
+
}
|
|
3572
|
+
});
|
|
3573
|
+
registerTool4(server, context, {
|
|
3574
|
+
name: "remix_ops_list_agent_run_events",
|
|
3575
|
+
description: "List paginated agent-run events for one stored run, with optional time bounds.",
|
|
3576
|
+
access: "read",
|
|
3577
|
+
inputSchema: agentRunEventsInputSchema,
|
|
3578
|
+
outputSchema: agentRunEventsSuccessSchema,
|
|
3579
|
+
run: async (args) => {
|
|
3580
|
+
const input = z9.object(agentRunEventsInputSchema).parse(args);
|
|
3581
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
3582
|
+
return listAgentRunEvents({
|
|
3583
|
+
appId: input.appId,
|
|
3584
|
+
cwd,
|
|
3585
|
+
runId: input.runId,
|
|
3586
|
+
limit: input.limit,
|
|
3587
|
+
offset: input.offset,
|
|
3588
|
+
createdAfter: input.createdAfter,
|
|
3589
|
+
createdBefore: input.createdBefore
|
|
3590
|
+
});
|
|
3591
|
+
}
|
|
3592
|
+
});
|
|
3593
|
+
registerTool4(server, context, {
|
|
3594
|
+
name: "remix_ops_get_sandbox_status",
|
|
3595
|
+
description: "Read safe sandbox metadata and the latest migration status for one app without exposing execution handles or secrets.",
|
|
3596
|
+
access: "read",
|
|
3597
|
+
inputSchema: appScopedInputSchema,
|
|
3598
|
+
outputSchema: sandboxStatusSuccessSchema,
|
|
3599
|
+
run: async (args) => {
|
|
3600
|
+
const input = z9.object(appScopedInputSchema).parse(args);
|
|
3601
|
+
const cwd = input.cwd ? resolvePolicyCwd(context.policy, input.cwd) : void 0;
|
|
3602
|
+
return getSandboxStatus({
|
|
3603
|
+
appId: input.appId,
|
|
3604
|
+
cwd
|
|
3605
|
+
});
|
|
3606
|
+
}
|
|
3607
|
+
});
|
|
3608
|
+
}
|
|
3609
|
+
|
|
1975
3610
|
// src/server.ts
|
|
1976
3611
|
function createRemixMcpServer(params) {
|
|
1977
3612
|
const context = createServerContext({ version: params.version });
|
|
@@ -1979,7 +3614,9 @@ function createRemixMcpServer(params) {
|
|
|
1979
3614
|
name: context.serverName,
|
|
1980
3615
|
version: context.version
|
|
1981
3616
|
});
|
|
3617
|
+
registerIdentityTools(server, context);
|
|
1982
3618
|
registerCollabTools(server, context);
|
|
3619
|
+
registerOpsTools(server, context);
|
|
1983
3620
|
registerMemoryTools(server, context);
|
|
1984
3621
|
return { server, context };
|
|
1985
3622
|
}
|