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