@myagentroam/protocol 0.9.62 → 0.9.65
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/LICENSE +202 -0
- package/NOTICE +4 -0
- package/dist/enterprise.d.ts +423 -0
- package/dist/enterprise.js +182 -2
- package/dist/git-identity.d.ts +505 -0
- package/dist/git-identity.js +331 -0
- package/dist/index.d.ts +21930 -0
- package/dist/index.js +2784 -2
- package/dist/mcp.d.ts +438 -0
- package/dist/mcp.js +141 -2
- package/dist/skill.d.ts +370 -0
- package/dist/skill.js +175 -2
- package/package.json +13 -5
- package/README.md +0 -9
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const PERSONAL_GIT_NODE_VERSION = '0.9.63';
|
|
3
|
+
/** The credential-bound commit identity payload is incompatible with the 0.9.63 runtime shape. */
|
|
4
|
+
export const PERSONAL_GIT_IDENTITY_NODE_VERSION = '0.9.64';
|
|
5
|
+
const cleanLine = z
|
|
6
|
+
.string()
|
|
7
|
+
.trim()
|
|
8
|
+
.max(256)
|
|
9
|
+
// eslint-disable-next-line no-control-regex -- Git fields must reject control characters.
|
|
10
|
+
.refine((value) => !/[\x00-\x1f\x7f]/u.test(value));
|
|
11
|
+
export const personalGitIdentitySchema = z
|
|
12
|
+
.object({
|
|
13
|
+
name: cleanLine.refine((value) => !/[<>]/u.test(value)),
|
|
14
|
+
email: cleanLine.refine((value) => value === '' || /^[^\s<>@]+@[^\s<>@]+$/u.test(value))
|
|
15
|
+
})
|
|
16
|
+
.strict()
|
|
17
|
+
.refine((value) => Boolean(value.name) === Boolean(value.email));
|
|
18
|
+
export function normalizeGitHost(value) {
|
|
19
|
+
const host = value.toLowerCase();
|
|
20
|
+
if (!host || /[\s/@\\?#*%]/u.test(host) || host.startsWith('-'))
|
|
21
|
+
throw new Error('GIT_CONFIGURATION_INVALID');
|
|
22
|
+
const parsed = new URL(`https://${host}`);
|
|
23
|
+
if (parsed.port || parsed.username || parsed.password || parsed.hostname !== host)
|
|
24
|
+
throw new Error('GIT_CONFIGURATION_INVALID');
|
|
25
|
+
return host;
|
|
26
|
+
}
|
|
27
|
+
export function normalizeGitPath(value) {
|
|
28
|
+
const path = value.replace(/^\/+|\/+$/gu, '');
|
|
29
|
+
if (
|
|
30
|
+
// eslint-disable-next-line no-control-regex -- Reject control characters in repository scopes.
|
|
31
|
+
/[\x00-\x1f\x7f\\]/u.test(path) ||
|
|
32
|
+
(path !== '' && path.split('/').some((part) => part === '' || part === '.' || part === '..')))
|
|
33
|
+
throw new Error('GIT_CONFIGURATION_INVALID');
|
|
34
|
+
return path;
|
|
35
|
+
}
|
|
36
|
+
/** Parses the complete HTTPS or SSH remote address accepted by the management form. */
|
|
37
|
+
export function parseGitRemoteUrl(value) {
|
|
38
|
+
const remote = value.trim();
|
|
39
|
+
if (remote.startsWith('https://') || remote.startsWith('ssh://')) {
|
|
40
|
+
let address;
|
|
41
|
+
try {
|
|
42
|
+
address = new URL(remote);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw new Error('GIT_CONFIGURATION_INVALID');
|
|
46
|
+
}
|
|
47
|
+
if (address.search || address.hash || address.password)
|
|
48
|
+
throw new Error('GIT_CONFIGURATION_INVALID');
|
|
49
|
+
const protocol = address.protocol === 'https:' ? 'HTTPS' : 'SSH';
|
|
50
|
+
if (address.protocol !== 'https:' && address.protocol !== 'ssh:')
|
|
51
|
+
throw new Error('GIT_CONFIGURATION_INVALID');
|
|
52
|
+
const pathPrefix = normalizeGitPath(decodeURIComponent(address.pathname));
|
|
53
|
+
if (protocol === 'HTTPS') {
|
|
54
|
+
if (!pathPrefix)
|
|
55
|
+
throw new Error('GIT_CONFIGURATION_INVALID');
|
|
56
|
+
if (address.username)
|
|
57
|
+
throw new Error('GIT_CONFIGURATION_INVALID');
|
|
58
|
+
return {
|
|
59
|
+
protocol,
|
|
60
|
+
host: normalizeGitHost(address.hostname),
|
|
61
|
+
port: Number(address.port || 443),
|
|
62
|
+
pathPrefix
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const username = decodeURIComponent(address.username);
|
|
66
|
+
if (!/^[A-Za-z0-9_][A-Za-z0-9_.@-]*$/u.test(username))
|
|
67
|
+
throw new Error('GIT_CONFIGURATION_INVALID');
|
|
68
|
+
return {
|
|
69
|
+
protocol,
|
|
70
|
+
host: normalizeGitHost(address.hostname),
|
|
71
|
+
port: Number(address.port || 22),
|
|
72
|
+
pathPrefix,
|
|
73
|
+
username
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const scp = /^([A-Za-z0-9_][A-Za-z0-9_.@-]*)@([^:/\s]+)(?::(.+))?$/u.exec(remote);
|
|
77
|
+
if (scp === null)
|
|
78
|
+
throw new Error('GIT_CONFIGURATION_INVALID');
|
|
79
|
+
const pathPrefix = scp[3] === undefined ? '' : normalizeGitPath(scp[3]);
|
|
80
|
+
if (scp[3] !== undefined && !pathPrefix)
|
|
81
|
+
throw new Error('GIT_CONFIGURATION_INVALID');
|
|
82
|
+
return {
|
|
83
|
+
protocol: 'SSH',
|
|
84
|
+
host: normalizeGitHost(scp[2]),
|
|
85
|
+
port: 22,
|
|
86
|
+
pathPrefix,
|
|
87
|
+
username: scp[1]
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Formats a canonical complete remote address without exposing secrets. */
|
|
91
|
+
export function formatGitRemoteUrl(credential) {
|
|
92
|
+
const path = credential.pathPrefix ? `/${credential.pathPrefix}` : '';
|
|
93
|
+
if (credential.protocol === 'HTTPS')
|
|
94
|
+
return `https://${credential.host}${credential.port === 443 ? '' : `:${credential.port}`}${path}`;
|
|
95
|
+
if (credential.port === 22)
|
|
96
|
+
return `${credential.username}@${credential.host}${credential.pathPrefix ? `:${credential.pathPrefix}` : ''}`;
|
|
97
|
+
return `ssh://${credential.username}@${credential.host}:${credential.port}${path}`;
|
|
98
|
+
}
|
|
99
|
+
const hostSchema = cleanLine
|
|
100
|
+
.refine((value) => value.length > 0)
|
|
101
|
+
.transform((value, context) => {
|
|
102
|
+
try {
|
|
103
|
+
return normalizeGitHost(value);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
context.addIssue({
|
|
107
|
+
code: 'custom',
|
|
108
|
+
message: 'Enter a host without a protocol, port or path.'
|
|
109
|
+
});
|
|
110
|
+
return z.NEVER;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
const pathSchema = z
|
|
114
|
+
.string()
|
|
115
|
+
.trim()
|
|
116
|
+
.max(2048)
|
|
117
|
+
.transform((value, context) => {
|
|
118
|
+
try {
|
|
119
|
+
return normalizeGitPath(value);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
context.addIssue({
|
|
123
|
+
code: 'custom',
|
|
124
|
+
message: 'Enter a repository path without traversal or empty segments.'
|
|
125
|
+
});
|
|
126
|
+
return z.NEVER;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
const secretSchema = z
|
|
130
|
+
.string()
|
|
131
|
+
.min(1)
|
|
132
|
+
.max(32_768)
|
|
133
|
+
.refine((value) => !value.includes('\0'));
|
|
134
|
+
const passphraseSchema = z
|
|
135
|
+
.string()
|
|
136
|
+
.max(4096)
|
|
137
|
+
.refine((value) => !value.includes('\0') && !/[\r\n]/u.test(value));
|
|
138
|
+
const credentialFields = {
|
|
139
|
+
name: cleanLine.refine((value) => value.length > 0),
|
|
140
|
+
protocol: z.enum(['HTTPS', 'SSH']),
|
|
141
|
+
host: hostSchema,
|
|
142
|
+
port: z.number().int().min(1).max(65_535),
|
|
143
|
+
pathPrefix: pathSchema,
|
|
144
|
+
username: cleanLine.refine((value) => value.length > 0),
|
|
145
|
+
commitName: cleanLine.refine((value) => !/[<>]/u.test(value)),
|
|
146
|
+
commitEmail: cleanLine.refine((value) => value === '' || /^[^\s<>@]+@[^\s<>@]+$/u.test(value))
|
|
147
|
+
};
|
|
148
|
+
function credentialIssues(value, context) {
|
|
149
|
+
if (value.protocol === 'HTTPS') {
|
|
150
|
+
if (value.passphrase !== undefined)
|
|
151
|
+
context.addIssue({
|
|
152
|
+
code: 'custom',
|
|
153
|
+
path: ['passphrase'],
|
|
154
|
+
message: 'HTTPS credentials cannot include an SSH passphrase.'
|
|
155
|
+
});
|
|
156
|
+
if (value.secret !== undefined && /[\r\n]/u.test(value.secret))
|
|
157
|
+
context.addIssue({
|
|
158
|
+
code: 'custom',
|
|
159
|
+
path: ['secret'],
|
|
160
|
+
message: 'Enter a single-line HTTPS password.'
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (value.protocol === 'SSH') {
|
|
164
|
+
if (!/^[A-Za-z0-9_][A-Za-z0-9_.@-]*$/u.test(value.username))
|
|
165
|
+
context.addIssue({
|
|
166
|
+
code: 'custom',
|
|
167
|
+
path: ['username'],
|
|
168
|
+
message: 'Enter a valid SSH username.'
|
|
169
|
+
});
|
|
170
|
+
if (value.secret !== undefined &&
|
|
171
|
+
!/^-----BEGIN (?:OPENSSH |RSA |EC |ENCRYPTED )?PRIVATE KEY-----\r?\n/u.test(value.secret))
|
|
172
|
+
context.addIssue({
|
|
173
|
+
code: 'custom',
|
|
174
|
+
path: ['secret'],
|
|
175
|
+
message: 'Enter a PEM or OpenSSH private key.'
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
if (value.commitName !== undefined &&
|
|
179
|
+
value.commitEmail !== undefined &&
|
|
180
|
+
Boolean(value.commitName) !== Boolean(value.commitEmail)) {
|
|
181
|
+
context.addIssue({
|
|
182
|
+
code: 'custom',
|
|
183
|
+
path: ['commitName'],
|
|
184
|
+
message: 'Set or clear the commit name and email together.'
|
|
185
|
+
});
|
|
186
|
+
context.addIssue({
|
|
187
|
+
code: 'custom',
|
|
188
|
+
path: ['commitEmail'],
|
|
189
|
+
message: 'Set or clear the commit name and email together.'
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
export const gitCredentialInputSchema = z
|
|
194
|
+
.object({
|
|
195
|
+
...credentialFields,
|
|
196
|
+
secret: secretSchema,
|
|
197
|
+
passphrase: passphraseSchema.optional()
|
|
198
|
+
})
|
|
199
|
+
.strict()
|
|
200
|
+
.superRefine(credentialIssues);
|
|
201
|
+
export const gitCredentialUpdateSchema = z
|
|
202
|
+
.object({
|
|
203
|
+
...credentialFields,
|
|
204
|
+
secret: secretSchema.optional(),
|
|
205
|
+
passphrase: passphraseSchema.nullable().optional()
|
|
206
|
+
})
|
|
207
|
+
.strict()
|
|
208
|
+
.superRefine(credentialIssues);
|
|
209
|
+
export const gitCredentialSchema = z
|
|
210
|
+
.object({
|
|
211
|
+
...credentialFields,
|
|
212
|
+
id: z.string().min(1).max(128),
|
|
213
|
+
secret: secretSchema,
|
|
214
|
+
passphrase: passphraseSchema.optional()
|
|
215
|
+
})
|
|
216
|
+
.strict()
|
|
217
|
+
.superRefine(credentialIssues);
|
|
218
|
+
export const gitCredentialSummarySchema = z
|
|
219
|
+
.object({
|
|
220
|
+
...credentialFields,
|
|
221
|
+
id: z.string().min(1).max(128),
|
|
222
|
+
hasSecret: z.boolean(),
|
|
223
|
+
hasPassphrase: z.boolean()
|
|
224
|
+
})
|
|
225
|
+
.strict()
|
|
226
|
+
.superRefine(credentialIssues);
|
|
227
|
+
export const personalGitRuntimeSchema = z
|
|
228
|
+
.object({
|
|
229
|
+
credentials: z.array(gitCredentialSchema).max(64)
|
|
230
|
+
})
|
|
231
|
+
.strict()
|
|
232
|
+
.superRefine((value, context) => {
|
|
233
|
+
const scopes = value.credentials.map(gitCredentialScopeKey);
|
|
234
|
+
if (new Set(scopes).size !== scopes.length ||
|
|
235
|
+
new Set(value.credentials.map((entry) => entry.id)).size !== value.credentials.length)
|
|
236
|
+
context.addIssue({
|
|
237
|
+
code: 'custom',
|
|
238
|
+
message: 'Credential IDs and matching scopes must be unique.'
|
|
239
|
+
});
|
|
240
|
+
if (new TextEncoder().encode(JSON.stringify(value)).byteLength > 1024 * 1024)
|
|
241
|
+
context.addIssue({ code: 'custom', message: 'Git configuration exceeds the size limit.' });
|
|
242
|
+
});
|
|
243
|
+
export const personalGitSummarySchema = z
|
|
244
|
+
.object({
|
|
245
|
+
credentials: z.array(gitCredentialSummarySchema).max(64)
|
|
246
|
+
})
|
|
247
|
+
.strict();
|
|
248
|
+
export const workspaceGitSettingsSchema = z.object({ preserveGitConfig: z.boolean() }).strict();
|
|
249
|
+
const legacyGitCredentialSchema = z
|
|
250
|
+
.object({
|
|
251
|
+
name: cleanLine.refine((value) => value.length > 0),
|
|
252
|
+
protocol: z.enum(['HTTPS', 'SSH']),
|
|
253
|
+
host: hostSchema,
|
|
254
|
+
port: z.number().int().min(1).max(65_535),
|
|
255
|
+
pathPrefix: pathSchema,
|
|
256
|
+
username: cleanLine.refine((value) => value.length > 0),
|
|
257
|
+
id: z.string().min(1).max(128),
|
|
258
|
+
secret: secretSchema,
|
|
259
|
+
passphrase: passphraseSchema.optional()
|
|
260
|
+
})
|
|
261
|
+
.strict()
|
|
262
|
+
.superRefine(credentialIssues);
|
|
263
|
+
const legacyPersonalGitRuntimeSchema = z
|
|
264
|
+
.object({
|
|
265
|
+
identity: personalGitIdentitySchema,
|
|
266
|
+
credentials: z.array(legacyGitCredentialSchema).max(64)
|
|
267
|
+
})
|
|
268
|
+
.strict();
|
|
269
|
+
/** Converts the previous account-level identity record to per-credential identities on read. */
|
|
270
|
+
export function migratePersonalGitRuntime(value) {
|
|
271
|
+
const current = personalGitRuntimeSchema.safeParse(value);
|
|
272
|
+
if (current.success)
|
|
273
|
+
return current.data;
|
|
274
|
+
const legacy = legacyPersonalGitRuntimeSchema.safeParse(value);
|
|
275
|
+
if (!legacy.success)
|
|
276
|
+
throw new Error('GIT_CONFIGURATION_INVALID');
|
|
277
|
+
return personalGitRuntimeSchema.parse({
|
|
278
|
+
credentials: legacy.data.credentials.map((credential) => ({
|
|
279
|
+
...credential,
|
|
280
|
+
commitName: legacy.data.identity.name,
|
|
281
|
+
commitEmail: legacy.data.identity.email
|
|
282
|
+
}))
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
export function gitCredentialScopeKey(credential) {
|
|
286
|
+
return JSON.stringify([
|
|
287
|
+
credential.protocol,
|
|
288
|
+
credential.host,
|
|
289
|
+
credential.port,
|
|
290
|
+
credential.pathPrefix
|
|
291
|
+
]);
|
|
292
|
+
}
|
|
293
|
+
export function selectGitCredential(credentials, target) {
|
|
294
|
+
let host;
|
|
295
|
+
let path;
|
|
296
|
+
try {
|
|
297
|
+
host = normalizeGitHost(target.host);
|
|
298
|
+
path = normalizeGitPath(target.path);
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
return credentials
|
|
304
|
+
.filter((entry) => entry.protocol === target.protocol &&
|
|
305
|
+
entry.host === host &&
|
|
306
|
+
entry.port === target.port &&
|
|
307
|
+
(entry.pathPrefix === '' || path === entry.pathPrefix))
|
|
308
|
+
.sort((left, right) => right.pathPrefix.length - left.pathPrefix.length)[0];
|
|
309
|
+
}
|
|
310
|
+
export function supportsPersonalGit(version) {
|
|
311
|
+
return supportsVersion(version, PERSONAL_GIT_NODE_VERSION);
|
|
312
|
+
}
|
|
313
|
+
/** Gates forwarding of the credential-bound commit identity runtime payload. */
|
|
314
|
+
export function supportsPersonalGitIdentity(version) {
|
|
315
|
+
return supportsVersion(version, PERSONAL_GIT_IDENTITY_NODE_VERSION);
|
|
316
|
+
}
|
|
317
|
+
function supportsVersion(version, minimum) {
|
|
318
|
+
if (version === undefined || !/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u.test(version))
|
|
319
|
+
return false;
|
|
320
|
+
const parts = version.split('.').map(Number);
|
|
321
|
+
if (!parts.every(Number.isSafeInteger))
|
|
322
|
+
return false;
|
|
323
|
+
const baseline = minimum.split('.').map(Number);
|
|
324
|
+
for (let i = 0; i < 3; i++) {
|
|
325
|
+
if (parts[i] > baseline[i])
|
|
326
|
+
return true;
|
|
327
|
+
if (parts[i] < baseline[i])
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
return true;
|
|
331
|
+
}
|