@sanurb/ringi 0.2.0 → 0.2.1
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.mjs +1741 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/mcp.mjs +1061 -0
- package/dist/mcp.mjs.map +1 -0
- package/dist/runtime.mjs +2975 -0
- package/dist/runtime.mjs.map +1 -0
- package/package.json +9 -9
- package/dist/chunk-KMYSGMD3.js +0 -3526
- package/dist/chunk-KMYSGMD3.js.map +0 -1
- package/dist/cli.js +0 -1839
- package/dist/cli.js.map +0 -1
- package/dist/mcp.js +0 -1228
- package/dist/mcp.js.map +0 -1
package/dist/mcp.js
DELETED
|
@@ -1,1228 +0,0 @@
|
|
|
1
|
-
import { ReviewSourceType, ReviewId, TodoId, CoreLive, TodoService, ReviewService, CommentService, ExportService, parseDiff, getDiffSummary, GitService } from './chunk-KMYSGMD3.js';
|
|
2
|
-
import { stdin, stdout } from 'node:process';
|
|
3
|
-
import { execFileSync } from 'node:child_process';
|
|
4
|
-
import * as Context from 'effect/Context';
|
|
5
|
-
import * as Layer2 from 'effect/Layer';
|
|
6
|
-
import * as vm from 'node:vm';
|
|
7
|
-
import * as Duration from 'effect/Duration';
|
|
8
|
-
import * as Effect from 'effect/Effect';
|
|
9
|
-
import * as ParseResult from 'effect/ParseResult';
|
|
10
|
-
import * as Schema2 from 'effect/Schema';
|
|
11
|
-
import * as ConfigProvider from 'effect/ConfigProvider';
|
|
12
|
-
import * as ManagedRuntime from 'effect/ManagedRuntime';
|
|
13
|
-
|
|
14
|
-
var DEFAULT_DB_PATH = ".ringi/reviews.db";
|
|
15
|
-
var DEFAULT_MAX_OUTPUT_BYTES = 100 * 1024;
|
|
16
|
-
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
17
|
-
var MAX_TIMEOUT_MS = 12e4;
|
|
18
|
-
var McpConfig = class extends Context.Tag("McpConfig")() {
|
|
19
|
-
};
|
|
20
|
-
var McpConfigLive = (config) => Layer2.succeed(McpConfig, config);
|
|
21
|
-
var resolveRepositoryRoot = (repoOverride) => {
|
|
22
|
-
const cwd = repoOverride ?? process.cwd();
|
|
23
|
-
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
24
|
-
cwd,
|
|
25
|
-
encoding: "utf8"
|
|
26
|
-
}).trim();
|
|
27
|
-
};
|
|
28
|
-
var resolveDbPath = (repoRoot, dbPathOverride) => dbPathOverride ?? `${repoRoot}/${DEFAULT_DB_PATH}`;
|
|
29
|
-
var parseNumberFlag = (flagValue, fallback, name) => {
|
|
30
|
-
if (flagValue === void 0) {
|
|
31
|
-
return fallback;
|
|
32
|
-
}
|
|
33
|
-
const parsed = Number.parseInt(flagValue, 10);
|
|
34
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
35
|
-
throw new Error(
|
|
36
|
-
`Invalid ${name}: expected a positive integer, received ${flagValue}`
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
return parsed;
|
|
40
|
-
};
|
|
41
|
-
var resolveMcpConfig = (argv) => {
|
|
42
|
-
const args = [...argv];
|
|
43
|
-
const readonly = args.includes("--readonly");
|
|
44
|
-
const dbIndex = args.indexOf("--db-path");
|
|
45
|
-
const repoIndex = args.indexOf("--repo");
|
|
46
|
-
const timeoutIndex = args.indexOf("--timeout-ms");
|
|
47
|
-
const maxOutputIndex = args.indexOf("--max-output-bytes");
|
|
48
|
-
const repoRoot = resolveRepositoryRoot(
|
|
49
|
-
repoIndex === -1 ? void 0 : args[repoIndex + 1]
|
|
50
|
-
);
|
|
51
|
-
return {
|
|
52
|
-
cwd: process.cwd(),
|
|
53
|
-
dbPath: resolveDbPath(
|
|
54
|
-
repoRoot,
|
|
55
|
-
dbIndex === -1 ? void 0 : args[dbIndex + 1]
|
|
56
|
-
),
|
|
57
|
-
defaultTimeoutMs: parseNumberFlag(
|
|
58
|
-
timeoutIndex === -1 ? void 0 : args[timeoutIndex + 1],
|
|
59
|
-
DEFAULT_TIMEOUT_MS,
|
|
60
|
-
"timeout"
|
|
61
|
-
),
|
|
62
|
-
maxOutputBytes: parseNumberFlag(
|
|
63
|
-
maxOutputIndex === -1 ? void 0 : args[maxOutputIndex + 1],
|
|
64
|
-
DEFAULT_MAX_OUTPUT_BYTES,
|
|
65
|
-
"max output bytes"
|
|
66
|
-
),
|
|
67
|
-
maxTimeoutMs: MAX_TIMEOUT_MS,
|
|
68
|
-
readonly,
|
|
69
|
-
repoRoot
|
|
70
|
-
};
|
|
71
|
-
};
|
|
72
|
-
var InvalidCodeError = class extends Schema2.TaggedError()(
|
|
73
|
-
"InvalidCodeError",
|
|
74
|
-
{ message: Schema2.String }
|
|
75
|
-
) {
|
|
76
|
-
};
|
|
77
|
-
var InvalidTimeoutError = class extends Schema2.TaggedError()(
|
|
78
|
-
"InvalidTimeoutError",
|
|
79
|
-
{ message: Schema2.String, received: Schema2.Unknown }
|
|
80
|
-
) {
|
|
81
|
-
};
|
|
82
|
-
(class extends Schema2.TaggedError()(
|
|
83
|
-
"InputDecodeError",
|
|
84
|
-
{ message: Schema2.String, operation: Schema2.String }
|
|
85
|
-
) {
|
|
86
|
-
});
|
|
87
|
-
(class extends Schema2.TaggedError()(
|
|
88
|
-
"ReadonlyViolationError",
|
|
89
|
-
{ message: Schema2.String }
|
|
90
|
-
) {
|
|
91
|
-
});
|
|
92
|
-
var ExecutionTimeoutError = class extends Schema2.TaggedError()(
|
|
93
|
-
"ExecutionTimeoutError",
|
|
94
|
-
{ message: Schema2.String, timeoutMs: Schema2.Number }
|
|
95
|
-
) {
|
|
96
|
-
};
|
|
97
|
-
var SandboxExecutionError = class extends Schema2.TaggedError()(
|
|
98
|
-
"SandboxExecutionError",
|
|
99
|
-
{ error: Schema2.Defect, message: Schema2.String }
|
|
100
|
-
) {
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
// src/mcp/namespaces.ts
|
|
104
|
-
var PHASE_UNAVAILABLE_MESSAGE = "This capability is not available in the current server phase. Intelligence features require Phase 2.";
|
|
105
|
-
var createIntelligenceNamespace = () => Object.freeze({
|
|
106
|
-
getConfidence: (_reviewId) => Promise.reject(new Error(PHASE_UNAVAILABLE_MESSAGE)),
|
|
107
|
-
getImpacts: (_reviewId) => Promise.reject(new Error(PHASE_UNAVAILABLE_MESSAGE)),
|
|
108
|
-
getRelationships: (_reviewId) => Promise.reject(new Error(PHASE_UNAVAILABLE_MESSAGE)),
|
|
109
|
-
validate: (_options) => Promise.reject(new Error(PHASE_UNAVAILABLE_MESSAGE))
|
|
110
|
-
});
|
|
111
|
-
var createSessionNamespace = (deps) => Object.freeze({
|
|
112
|
-
context: async () => {
|
|
113
|
-
const repo = await deps.getRepositoryInfo();
|
|
114
|
-
const activeReviewId = await deps.getLatestReviewId();
|
|
115
|
-
return {
|
|
116
|
-
activeReviewId,
|
|
117
|
-
activeSnapshotId: null,
|
|
118
|
-
readonly: deps.readonly,
|
|
119
|
-
repository: repo,
|
|
120
|
-
serverMode: "stdio"
|
|
121
|
-
};
|
|
122
|
-
},
|
|
123
|
-
status: async () => ({
|
|
124
|
-
activeSubscriptions: 0,
|
|
125
|
-
currentPhase: "phase1",
|
|
126
|
-
ok: true,
|
|
127
|
-
readonly: deps.readonly
|
|
128
|
-
})
|
|
129
|
-
});
|
|
130
|
-
var buildPreview = (diffText, source) => {
|
|
131
|
-
const files = parseDiff(diffText);
|
|
132
|
-
const summary = getDiffSummary(files);
|
|
133
|
-
return {
|
|
134
|
-
files: files.map((f) => ({
|
|
135
|
-
additions: f.additions,
|
|
136
|
-
deletions: f.deletions,
|
|
137
|
-
path: f.newPath,
|
|
138
|
-
status: f.status
|
|
139
|
-
})),
|
|
140
|
-
source,
|
|
141
|
-
summary: {
|
|
142
|
-
totalAdditions: summary.totalAdditions,
|
|
143
|
-
totalDeletions: summary.totalDeletions,
|
|
144
|
-
totalFiles: summary.totalFiles
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
};
|
|
148
|
-
var createSourcesNamespace = (deps) => Object.freeze({
|
|
149
|
-
list: async () => {
|
|
150
|
-
const [stagedFiles, branches, commits] = await Promise.all([
|
|
151
|
-
deps.getStagedFiles(),
|
|
152
|
-
deps.getBranches(),
|
|
153
|
-
deps.getRecentCommits()
|
|
154
|
-
]);
|
|
155
|
-
return {
|
|
156
|
-
branches: branches.map((b) => ({ current: b.current, name: b.name })),
|
|
157
|
-
recentCommits: commits.map((c) => ({
|
|
158
|
-
author: c.author,
|
|
159
|
-
date: c.date,
|
|
160
|
-
hash: c.hash,
|
|
161
|
-
message: c.message
|
|
162
|
-
})),
|
|
163
|
-
staged: { available: stagedFiles.length > 0 }
|
|
164
|
-
};
|
|
165
|
-
},
|
|
166
|
-
previewDiff: async (source) => {
|
|
167
|
-
let diffText;
|
|
168
|
-
switch (source.type) {
|
|
169
|
-
case "staged": {
|
|
170
|
-
diffText = await deps.getStagedDiff();
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
case "branch": {
|
|
174
|
-
diffText = await deps.getBranchDiff(source.baseRef);
|
|
175
|
-
break;
|
|
176
|
-
}
|
|
177
|
-
case "commits": {
|
|
178
|
-
diffText = await deps.getCommitDiff(source.commits);
|
|
179
|
-
break;
|
|
180
|
-
}
|
|
181
|
-
default: {
|
|
182
|
-
throw new Error(
|
|
183
|
-
`Unsupported source type: ${source.type}`
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return buildPreview(diffText, source);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
var createEventsNamespace = () => {
|
|
191
|
-
let subscriptionCounter = 0;
|
|
192
|
-
return Object.freeze({
|
|
193
|
-
listRecent: async (_filter) => [],
|
|
194
|
-
subscribe: async (filter) => {
|
|
195
|
-
subscriptionCounter += 1;
|
|
196
|
-
return {
|
|
197
|
-
eventTypes: filter?.eventTypes ?? [
|
|
198
|
-
"reviews.updated",
|
|
199
|
-
"comments.updated",
|
|
200
|
-
"todos.updated",
|
|
201
|
-
"files.changed"
|
|
202
|
-
],
|
|
203
|
-
id: `sub_${subscriptionCounter}`,
|
|
204
|
-
reviewId: filter?.reviewId
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
// src/mcp/sandbox.ts
|
|
211
|
-
var parseId = (value, fieldName) => {
|
|
212
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
213
|
-
throw new Error(`Invalid ${fieldName}: expected a non-empty string`);
|
|
214
|
-
}
|
|
215
|
-
return value;
|
|
216
|
-
};
|
|
217
|
-
var createSandboxGlobals = (deps) => {
|
|
218
|
-
const reviews = Object.freeze({
|
|
219
|
-
create: async (_input) => {
|
|
220
|
-
deps.requireWritable();
|
|
221
|
-
return deps.call("reviews.create", async () => {
|
|
222
|
-
throw new Error("reviews.create: not wired to runtime");
|
|
223
|
-
});
|
|
224
|
-
},
|
|
225
|
-
export: (_options) => deps.call("reviews.export", async () => {
|
|
226
|
-
throw new Error("reviews.export: not wired to runtime");
|
|
227
|
-
}),
|
|
228
|
-
get: (reviewIdValue) => {
|
|
229
|
-
parseId(reviewIdValue, "reviewId");
|
|
230
|
-
return deps.call("reviews.get", async () => {
|
|
231
|
-
throw new Error("reviews.get: not wired to runtime");
|
|
232
|
-
});
|
|
233
|
-
},
|
|
234
|
-
getComments: (reviewIdValue, _filePath) => {
|
|
235
|
-
parseId(reviewIdValue, "reviewId");
|
|
236
|
-
return deps.call("reviews.getComments", async () => {
|
|
237
|
-
throw new Error("reviews.getComments: not wired to runtime");
|
|
238
|
-
});
|
|
239
|
-
},
|
|
240
|
-
getDiff: (_query) => deps.call("reviews.getDiff", async () => {
|
|
241
|
-
throw new Error("reviews.getDiff: not wired to runtime");
|
|
242
|
-
}),
|
|
243
|
-
getFiles: (reviewIdValue) => {
|
|
244
|
-
parseId(reviewIdValue, "reviewId");
|
|
245
|
-
return deps.call("reviews.getFiles", async () => {
|
|
246
|
-
throw new Error("reviews.getFiles: not wired to runtime");
|
|
247
|
-
});
|
|
248
|
-
},
|
|
249
|
-
getStatus: (reviewIdValue) => {
|
|
250
|
-
parseId(reviewIdValue, "reviewId");
|
|
251
|
-
return deps.call("reviews.getStatus", async () => {
|
|
252
|
-
throw new Error("reviews.getStatus: not wired to runtime");
|
|
253
|
-
});
|
|
254
|
-
},
|
|
255
|
-
getSuggestions: (reviewIdValue) => {
|
|
256
|
-
parseId(reviewIdValue, "reviewId");
|
|
257
|
-
return deps.call("reviews.getSuggestions", async () => {
|
|
258
|
-
throw new Error("reviews.getSuggestions: not wired to runtime");
|
|
259
|
-
});
|
|
260
|
-
},
|
|
261
|
-
list: (_filters) => deps.call("reviews.list", async () => {
|
|
262
|
-
throw new Error("reviews.list: not wired to runtime");
|
|
263
|
-
})
|
|
264
|
-
});
|
|
265
|
-
const todos = Object.freeze({
|
|
266
|
-
add: async (_input) => {
|
|
267
|
-
deps.requireWritable();
|
|
268
|
-
return deps.call("todos.add", async () => {
|
|
269
|
-
throw new Error("todos.add: not wired to runtime");
|
|
270
|
-
});
|
|
271
|
-
},
|
|
272
|
-
clear: async (_reviewIdValue) => {
|
|
273
|
-
deps.requireWritable();
|
|
274
|
-
return deps.call("todos.clear", async () => {
|
|
275
|
-
throw new Error("todos.clear: not wired to runtime");
|
|
276
|
-
});
|
|
277
|
-
},
|
|
278
|
-
done: async (todoIdValue) => {
|
|
279
|
-
deps.requireWritable();
|
|
280
|
-
parseId(todoIdValue, "todoId");
|
|
281
|
-
return deps.call("todos.done", async () => {
|
|
282
|
-
throw new Error("todos.done: not wired to runtime");
|
|
283
|
-
});
|
|
284
|
-
},
|
|
285
|
-
list: (_filter) => deps.call("todos.list", async () => {
|
|
286
|
-
throw new Error("todos.list: not wired to runtime");
|
|
287
|
-
}),
|
|
288
|
-
move: async (todoIdValue, _positionValue) => {
|
|
289
|
-
deps.requireWritable();
|
|
290
|
-
parseId(todoIdValue, "todoId");
|
|
291
|
-
return deps.call("todos.move", async () => {
|
|
292
|
-
throw new Error("todos.move: not wired to runtime");
|
|
293
|
-
});
|
|
294
|
-
},
|
|
295
|
-
remove: async (todoIdValue) => {
|
|
296
|
-
deps.requireWritable();
|
|
297
|
-
parseId(todoIdValue, "todoId");
|
|
298
|
-
return deps.call("todos.remove", async () => {
|
|
299
|
-
throw new Error("todos.remove: not wired to runtime");
|
|
300
|
-
});
|
|
301
|
-
},
|
|
302
|
-
undone: async (todoIdValue) => {
|
|
303
|
-
deps.requireWritable();
|
|
304
|
-
parseId(todoIdValue, "todoId");
|
|
305
|
-
return deps.call("todos.undone", async () => {
|
|
306
|
-
throw new Error("todos.undone: not wired to runtime");
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
const sources = createSourcesNamespace({
|
|
311
|
-
getBranchDiff: deps.getBranchDiff,
|
|
312
|
-
getBranches: deps.getBranches,
|
|
313
|
-
getCommitDiff: deps.getCommitDiff,
|
|
314
|
-
getRecentCommits: deps.getRecentCommits,
|
|
315
|
-
getRepositoryInfo: deps.getRepositoryInfo,
|
|
316
|
-
getStagedDiff: deps.getStagedDiff,
|
|
317
|
-
getStagedFiles: deps.getStagedFiles
|
|
318
|
-
});
|
|
319
|
-
const intelligence = createIntelligenceNamespace();
|
|
320
|
-
const events = createEventsNamespace();
|
|
321
|
-
const session = createSessionNamespace({
|
|
322
|
-
getLatestReviewId: deps.getLatestReviewId,
|
|
323
|
-
getRepositoryInfo: deps.getRepositoryInfo,
|
|
324
|
-
readonly: deps.readonly
|
|
325
|
-
});
|
|
326
|
-
return { events, intelligence, reviews, session, sources, todos };
|
|
327
|
-
};
|
|
328
|
-
var NonEmptyString = Schema2.String.pipe(Schema2.minLength(1));
|
|
329
|
-
var ReviewCreateFromSpec = Schema2.Struct({
|
|
330
|
-
source: Schema2.Struct({
|
|
331
|
-
baseRef: Schema2.optionalWith(Schema2.NullOr(Schema2.String), {
|
|
332
|
-
default: () => null
|
|
333
|
-
}),
|
|
334
|
-
type: Schema2.optionalWith(ReviewSourceType, {
|
|
335
|
-
default: () => "staged"
|
|
336
|
-
})
|
|
337
|
-
})
|
|
338
|
-
});
|
|
339
|
-
var ReviewCreateFromLegacy = Schema2.Struct({
|
|
340
|
-
sourceRef: Schema2.optionalWith(Schema2.NullOr(Schema2.String), {
|
|
341
|
-
default: () => null
|
|
342
|
-
}),
|
|
343
|
-
sourceType: Schema2.optionalWith(ReviewSourceType, {
|
|
344
|
-
default: () => "staged"
|
|
345
|
-
})
|
|
346
|
-
});
|
|
347
|
-
var decodeReviewCreateInput = (input) => {
|
|
348
|
-
if (typeof input === "object" && input !== null && "source" in input) {
|
|
349
|
-
const parsed2 = Schema2.decodeUnknownSync(ReviewCreateFromSpec)(input);
|
|
350
|
-
return {
|
|
351
|
-
sourceRef: parsed2.source.baseRef,
|
|
352
|
-
sourceType: parsed2.source.type
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
const parsed = Schema2.decodeUnknownSync(ReviewCreateFromLegacy)(input);
|
|
356
|
-
return {
|
|
357
|
-
sourceRef: parsed.sourceRef,
|
|
358
|
-
sourceType: parsed.sourceType
|
|
359
|
-
};
|
|
360
|
-
};
|
|
361
|
-
var ReviewExportInput = Schema2.Struct({
|
|
362
|
-
reviewId: ReviewId
|
|
363
|
-
});
|
|
364
|
-
var ReviewDiffQuery = Schema2.Struct({
|
|
365
|
-
filePath: NonEmptyString,
|
|
366
|
-
reviewId: ReviewId
|
|
367
|
-
});
|
|
368
|
-
var ReviewListFilters = Schema2.Struct({
|
|
369
|
-
limit: Schema2.optionalWith(Schema2.Number, { default: () => 20 }),
|
|
370
|
-
page: Schema2.optionalWith(Schema2.Number, { default: () => 1 }),
|
|
371
|
-
pageSize: Schema2.optionalWith(Schema2.Number, { default: () => 20 }),
|
|
372
|
-
sourceType: Schema2.optional(Schema2.String),
|
|
373
|
-
status: Schema2.optional(Schema2.String)
|
|
374
|
-
});
|
|
375
|
-
var TodoInputFromSpec = Schema2.Struct({
|
|
376
|
-
reviewId: Schema2.optionalWith(Schema2.NullOr(ReviewId), {
|
|
377
|
-
default: () => null
|
|
378
|
-
}),
|
|
379
|
-
text: NonEmptyString
|
|
380
|
-
});
|
|
381
|
-
var TodoInputFromLegacy = Schema2.Struct({
|
|
382
|
-
content: NonEmptyString,
|
|
383
|
-
reviewId: Schema2.optionalWith(Schema2.NullOr(ReviewId), {
|
|
384
|
-
default: () => null
|
|
385
|
-
})
|
|
386
|
-
});
|
|
387
|
-
var decodeCreateTodoInput = (input) => {
|
|
388
|
-
if (typeof input === "object" && input !== null && "text" in input) {
|
|
389
|
-
const parsed2 = Schema2.decodeUnknownSync(TodoInputFromSpec)(input);
|
|
390
|
-
return { content: parsed2.text, reviewId: parsed2.reviewId };
|
|
391
|
-
}
|
|
392
|
-
const parsed = Schema2.decodeUnknownSync(TodoInputFromLegacy)(input);
|
|
393
|
-
return { content: parsed.content, reviewId: parsed.reviewId };
|
|
394
|
-
};
|
|
395
|
-
var TodoListFilter = Schema2.Struct({
|
|
396
|
-
reviewId: Schema2.optional(ReviewId)
|
|
397
|
-
});
|
|
398
|
-
var TodoMoveInput = Schema2.Struct({
|
|
399
|
-
position: Schema2.Number,
|
|
400
|
-
todoId: TodoId
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
// src/mcp/execute.ts
|
|
404
|
-
var MAX_CODE_LENGTH = 5e4;
|
|
405
|
-
var MIN_PREVIEW_BYTES = 256;
|
|
406
|
-
var clampTimeout = (requestedTimeout, config) => {
|
|
407
|
-
if (requestedTimeout === void 0) {
|
|
408
|
-
return config.defaultTimeoutMs;
|
|
409
|
-
}
|
|
410
|
-
if (!Number.isFinite(requestedTimeout) || requestedTimeout <= 0) {
|
|
411
|
-
throw new Error(
|
|
412
|
-
`Invalid timeout: expected a positive integer, received ${requestedTimeout}`
|
|
413
|
-
);
|
|
414
|
-
}
|
|
415
|
-
return Math.min(Math.trunc(requestedTimeout), config.maxTimeoutMs);
|
|
416
|
-
};
|
|
417
|
-
var formatError = (error) => error instanceof Error ? error.message : String(error);
|
|
418
|
-
var truncateUtf8 = (text, maxBytes) => {
|
|
419
|
-
const buffer = Buffer.from(text, "utf8");
|
|
420
|
-
if (buffer.byteLength <= maxBytes) {
|
|
421
|
-
return text;
|
|
422
|
-
}
|
|
423
|
-
return buffer.subarray(0, maxBytes).toString("utf8");
|
|
424
|
-
};
|
|
425
|
-
var summarizeForJournal = (value) => {
|
|
426
|
-
if (typeof value === "string") {
|
|
427
|
-
return value.length > 200 ? `${value.slice(0, 200)}\u2026` : value;
|
|
428
|
-
}
|
|
429
|
-
if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) {
|
|
430
|
-
return value;
|
|
431
|
-
}
|
|
432
|
-
if (Array.isArray(value)) {
|
|
433
|
-
return { kind: "array", length: value.length };
|
|
434
|
-
}
|
|
435
|
-
if (typeof value === "object") {
|
|
436
|
-
return {
|
|
437
|
-
keys: Object.keys(value).slice(0, 10),
|
|
438
|
-
kind: "object"
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
return typeof value;
|
|
442
|
-
};
|
|
443
|
-
var finalizeOutput = (output, maxOutputBytes) => {
|
|
444
|
-
const serialized = JSON.stringify(output);
|
|
445
|
-
if (Buffer.byteLength(serialized, "utf8") <= maxOutputBytes) {
|
|
446
|
-
return output;
|
|
447
|
-
}
|
|
448
|
-
const previewBudget = Math.max(
|
|
449
|
-
MIN_PREVIEW_BYTES,
|
|
450
|
-
maxOutputBytes - Math.min(1024, Math.floor(maxOutputBytes / 4))
|
|
451
|
-
);
|
|
452
|
-
return {
|
|
453
|
-
...output,
|
|
454
|
-
result: {
|
|
455
|
-
note: "Result truncated to fit MCP output budget",
|
|
456
|
-
preview: truncateUtf8(JSON.stringify(output.result), previewBudget)
|
|
457
|
-
},
|
|
458
|
-
truncated: true
|
|
459
|
-
};
|
|
460
|
-
};
|
|
461
|
-
var ensureCode = (code) => {
|
|
462
|
-
if (typeof code !== "string") {
|
|
463
|
-
throw new TypeError("Invalid code: expected a string");
|
|
464
|
-
}
|
|
465
|
-
const trimmed = code.trim();
|
|
466
|
-
if (trimmed.length === 0) {
|
|
467
|
-
throw new Error("Invalid code: expected a non-empty string");
|
|
468
|
-
}
|
|
469
|
-
if (trimmed.length > MAX_CODE_LENGTH) {
|
|
470
|
-
throw new Error(
|
|
471
|
-
`Invalid code: maximum length is ${MAX_CODE_LENGTH} characters`
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
return trimmed;
|
|
475
|
-
};
|
|
476
|
-
var decodeInputSync = (schema, input, operation) => {
|
|
477
|
-
try {
|
|
478
|
-
return Schema2.decodeUnknownSync(schema)(input);
|
|
479
|
-
} catch (error) {
|
|
480
|
-
if (error instanceof ParseResult.ParseError) {
|
|
481
|
-
throw new TypeError(
|
|
482
|
-
`${operation}: ${ParseResult.TreeFormatter.formatErrorSync(error)}`,
|
|
483
|
-
{ cause: error }
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
throw error;
|
|
487
|
-
}
|
|
488
|
-
};
|
|
489
|
-
var validateCode = (code) => Effect.try({
|
|
490
|
-
catch: (e) => new InvalidCodeError({ message: formatError(e) }),
|
|
491
|
-
try: () => ensureCode(code)
|
|
492
|
-
});
|
|
493
|
-
var validateTimeout = (requested, config) => Effect.try({
|
|
494
|
-
catch: () => new InvalidTimeoutError({
|
|
495
|
-
message: `Invalid timeout: expected a positive integer, received ${requested}`,
|
|
496
|
-
received: requested
|
|
497
|
-
}),
|
|
498
|
-
try: () => clampTimeout(requested, config)
|
|
499
|
-
});
|
|
500
|
-
var writeSandboxLog = (level, args) => {
|
|
501
|
-
const line = args.map((arg) => {
|
|
502
|
-
if (typeof arg === "string") {
|
|
503
|
-
return arg;
|
|
504
|
-
}
|
|
505
|
-
try {
|
|
506
|
-
return JSON.stringify(arg);
|
|
507
|
-
} catch {
|
|
508
|
-
return String(arg);
|
|
509
|
-
}
|
|
510
|
-
}).join(" ");
|
|
511
|
-
process.stderr.write(`[ringi:mcp:${level}] ${line}
|
|
512
|
-
`);
|
|
513
|
-
};
|
|
514
|
-
var createSandboxConsole = () => Object.freeze({
|
|
515
|
-
error: (...args) => writeSandboxLog("error", args),
|
|
516
|
-
info: (...args) => writeSandboxLog("info", args),
|
|
517
|
-
log: (...args) => writeSandboxLog("log", args),
|
|
518
|
-
warn: (...args) => writeSandboxLog("warn", args)
|
|
519
|
-
});
|
|
520
|
-
var runSandbox = (globals, code, timeoutMs) => {
|
|
521
|
-
const execute = Effect.tryPromise({
|
|
522
|
-
catch: (error) => new SandboxExecutionError({
|
|
523
|
-
error,
|
|
524
|
-
message: formatError(error)
|
|
525
|
-
}),
|
|
526
|
-
try: () => {
|
|
527
|
-
const context = vm.createContext({
|
|
528
|
-
...globals,
|
|
529
|
-
Buffer: void 0,
|
|
530
|
-
clearImmediate: void 0,
|
|
531
|
-
clearInterval: void 0,
|
|
532
|
-
clearTimeout: void 0,
|
|
533
|
-
console: createSandboxConsole(),
|
|
534
|
-
fetch: void 0,
|
|
535
|
-
process: void 0,
|
|
536
|
-
queueMicrotask,
|
|
537
|
-
require: void 0,
|
|
538
|
-
setImmediate: void 0,
|
|
539
|
-
setInterval: void 0,
|
|
540
|
-
setTimeout: void 0
|
|
541
|
-
});
|
|
542
|
-
const script = new vm.Script(
|
|
543
|
-
`"use strict"; (async () => {
|
|
544
|
-
${code}
|
|
545
|
-
})()`,
|
|
546
|
-
{ filename: "ringi-mcp-execute.js" }
|
|
547
|
-
);
|
|
548
|
-
return Promise.resolve(
|
|
549
|
-
script.runInContext(context, { timeout: timeoutMs })
|
|
550
|
-
);
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
return execute.pipe(
|
|
554
|
-
Effect.timeoutFail({
|
|
555
|
-
duration: Duration.millis(timeoutMs),
|
|
556
|
-
onTimeout: () => new ExecutionTimeoutError({
|
|
557
|
-
message: `Execution timed out after ${timeoutMs}ms`,
|
|
558
|
-
timeoutMs
|
|
559
|
-
})
|
|
560
|
-
})
|
|
561
|
-
);
|
|
562
|
-
};
|
|
563
|
-
var createJournal = () => {
|
|
564
|
-
const entries = [];
|
|
565
|
-
const recordSuccess = (name, result) => {
|
|
566
|
-
entries.push({ name, ok: true, result: summarizeForJournal(result) });
|
|
567
|
-
};
|
|
568
|
-
const recordFailure = (name, error) => {
|
|
569
|
-
entries.push({ error: formatError(error), name, ok: false });
|
|
570
|
-
};
|
|
571
|
-
const tracked = (runtime, name, effect) => runtime.runPromise(
|
|
572
|
-
effect
|
|
573
|
-
).then(
|
|
574
|
-
(result) => {
|
|
575
|
-
recordSuccess(name, result);
|
|
576
|
-
return result;
|
|
577
|
-
},
|
|
578
|
-
(error) => {
|
|
579
|
-
recordFailure(name, error);
|
|
580
|
-
throw error;
|
|
581
|
-
}
|
|
582
|
-
);
|
|
583
|
-
const trackedAsync = async (name, fn) => {
|
|
584
|
-
try {
|
|
585
|
-
const result = await fn();
|
|
586
|
-
recordSuccess(name, result);
|
|
587
|
-
return result;
|
|
588
|
-
} catch (error) {
|
|
589
|
-
recordFailure(name, error);
|
|
590
|
-
throw error;
|
|
591
|
-
}
|
|
592
|
-
};
|
|
593
|
-
return { entries, recordFailure, recordSuccess, tracked, trackedAsync };
|
|
594
|
-
};
|
|
595
|
-
var buildSandboxGlobals = (runtime, config, journal) => {
|
|
596
|
-
const { tracked, trackedAsync } = journal;
|
|
597
|
-
const throwIfReadonly = () => {
|
|
598
|
-
if (config.readonly) {
|
|
599
|
-
throw new Error(
|
|
600
|
-
"Mutation rejected: MCP server is running in readonly mode"
|
|
601
|
-
);
|
|
602
|
-
}
|
|
603
|
-
};
|
|
604
|
-
const run = (name, effect) => tracked(runtime, name, effect);
|
|
605
|
-
const deps = {
|
|
606
|
-
call: trackedAsync,
|
|
607
|
-
getBranchDiff: (branch) => run(
|
|
608
|
-
"git.getBranchDiff",
|
|
609
|
-
Effect.gen(function* getBranchDiff() {
|
|
610
|
-
const git = yield* GitService;
|
|
611
|
-
return yield* git.getBranchDiff(branch);
|
|
612
|
-
})
|
|
613
|
-
),
|
|
614
|
-
getBranches: () => run(
|
|
615
|
-
"git.getBranches",
|
|
616
|
-
Effect.gen(function* getBranches() {
|
|
617
|
-
const git = yield* GitService;
|
|
618
|
-
return yield* git.getBranches;
|
|
619
|
-
})
|
|
620
|
-
),
|
|
621
|
-
getCommitDiff: (shas) => run(
|
|
622
|
-
"git.getCommitDiff",
|
|
623
|
-
Effect.gen(function* getCommitDiff() {
|
|
624
|
-
const git = yield* GitService;
|
|
625
|
-
return yield* git.getCommitDiff(shas);
|
|
626
|
-
})
|
|
627
|
-
),
|
|
628
|
-
getLatestReviewId: async () => {
|
|
629
|
-
const result = await run(
|
|
630
|
-
"reviews.latestId",
|
|
631
|
-
Effect.gen(function* result2() {
|
|
632
|
-
const svc = yield* ReviewService;
|
|
633
|
-
return yield* svc.list({
|
|
634
|
-
page: 1,
|
|
635
|
-
pageSize: 1,
|
|
636
|
-
repositoryPath: config.repoRoot
|
|
637
|
-
});
|
|
638
|
-
})
|
|
639
|
-
);
|
|
640
|
-
return result.reviews[0]?.id ?? null;
|
|
641
|
-
},
|
|
642
|
-
getRecentCommits: async () => {
|
|
643
|
-
const result = await run(
|
|
644
|
-
"git.getCommits",
|
|
645
|
-
Effect.gen(function* result2() {
|
|
646
|
-
const git = yield* GitService;
|
|
647
|
-
return yield* git.getCommits({ limit: 10, offset: 0 });
|
|
648
|
-
})
|
|
649
|
-
);
|
|
650
|
-
return result.commits;
|
|
651
|
-
},
|
|
652
|
-
getRepositoryInfo: () => run(
|
|
653
|
-
"git.getRepositoryInfo",
|
|
654
|
-
Effect.gen(function* getRepositoryInfo() {
|
|
655
|
-
const git = yield* GitService;
|
|
656
|
-
return yield* git.getRepositoryInfo;
|
|
657
|
-
})
|
|
658
|
-
),
|
|
659
|
-
getStagedDiff: () => run(
|
|
660
|
-
"git.getStagedDiff",
|
|
661
|
-
Effect.gen(function* getStagedDiff() {
|
|
662
|
-
const git = yield* GitService;
|
|
663
|
-
return yield* git.getStagedDiff;
|
|
664
|
-
})
|
|
665
|
-
),
|
|
666
|
-
getStagedFiles: () => run(
|
|
667
|
-
"git.getStagedFiles",
|
|
668
|
-
Effect.gen(function* getStagedFiles() {
|
|
669
|
-
const git = yield* GitService;
|
|
670
|
-
return yield* git.getStagedFiles;
|
|
671
|
-
})
|
|
672
|
-
),
|
|
673
|
-
readonly: config.readonly,
|
|
674
|
-
repoRoot: config.repoRoot,
|
|
675
|
-
requireWritable: throwIfReadonly
|
|
676
|
-
};
|
|
677
|
-
const baseGlobals = createSandboxGlobals(deps);
|
|
678
|
-
const reviews = Object.freeze({
|
|
679
|
-
create: async (inputValue) => {
|
|
680
|
-
throwIfReadonly();
|
|
681
|
-
const parsed = decodeReviewCreateInput(inputValue);
|
|
682
|
-
return run(
|
|
683
|
-
"reviews.create",
|
|
684
|
-
Effect.gen(function* create() {
|
|
685
|
-
const svc = yield* ReviewService;
|
|
686
|
-
return yield* svc.create(parsed);
|
|
687
|
-
})
|
|
688
|
-
);
|
|
689
|
-
},
|
|
690
|
-
export: async (options) => {
|
|
691
|
-
const opts = decodeInputSync(
|
|
692
|
-
ReviewExportInput,
|
|
693
|
-
options,
|
|
694
|
-
"reviews.export"
|
|
695
|
-
);
|
|
696
|
-
const markdown = await run(
|
|
697
|
-
"reviews.export",
|
|
698
|
-
Effect.gen(function* markdown2() {
|
|
699
|
-
const svc = yield* ExportService;
|
|
700
|
-
return yield* svc.exportReview(opts.reviewId);
|
|
701
|
-
})
|
|
702
|
-
);
|
|
703
|
-
return { markdown, reviewId: opts.reviewId };
|
|
704
|
-
},
|
|
705
|
-
get: (reviewIdValue) => {
|
|
706
|
-
const reviewId = reviewIdValue;
|
|
707
|
-
return run(
|
|
708
|
-
"reviews.get",
|
|
709
|
-
Effect.gen(function* get() {
|
|
710
|
-
const svc = yield* ReviewService;
|
|
711
|
-
return yield* svc.getById(reviewId);
|
|
712
|
-
})
|
|
713
|
-
);
|
|
714
|
-
},
|
|
715
|
-
getComments: (reviewIdValue, filePath) => {
|
|
716
|
-
const reviewId = reviewIdValue;
|
|
717
|
-
if (filePath !== null && filePath !== void 0 && typeof filePath !== "string") {
|
|
718
|
-
return Promise.reject(new Error("Invalid filePath: expected a string"));
|
|
719
|
-
}
|
|
720
|
-
return run(
|
|
721
|
-
"reviews.getComments",
|
|
722
|
-
Effect.gen(function* getComments() {
|
|
723
|
-
const svc = yield* CommentService;
|
|
724
|
-
return filePath ? yield* svc.getByFile(reviewId, filePath) : yield* svc.getByReview(reviewId);
|
|
725
|
-
})
|
|
726
|
-
);
|
|
727
|
-
},
|
|
728
|
-
getDiff: async (query) => {
|
|
729
|
-
const q = decodeInputSync(ReviewDiffQuery, query, "reviews.getDiff");
|
|
730
|
-
const hunks = await run(
|
|
731
|
-
`reviews.getDiff:${q.filePath}`,
|
|
732
|
-
Effect.gen(function* hunks2() {
|
|
733
|
-
const svc = yield* ReviewService;
|
|
734
|
-
return yield* svc.getFileHunks(q.reviewId, q.filePath);
|
|
735
|
-
})
|
|
736
|
-
);
|
|
737
|
-
return { filePath: q.filePath, hunks, reviewId: q.reviewId };
|
|
738
|
-
},
|
|
739
|
-
getFiles: async (reviewIdValue) => {
|
|
740
|
-
const reviewId = reviewIdValue;
|
|
741
|
-
const review = await run(
|
|
742
|
-
"reviews.getFiles",
|
|
743
|
-
Effect.gen(function* review2() {
|
|
744
|
-
const svc = yield* ReviewService;
|
|
745
|
-
return yield* svc.getById(reviewId);
|
|
746
|
-
})
|
|
747
|
-
);
|
|
748
|
-
return review.files;
|
|
749
|
-
},
|
|
750
|
-
getStatus: async (reviewIdValue) => {
|
|
751
|
-
const reviewId = reviewIdValue;
|
|
752
|
-
const [review, stats] = await Promise.all([
|
|
753
|
-
run(
|
|
754
|
-
"reviews.getStatus.review",
|
|
755
|
-
Effect.gen(function* () {
|
|
756
|
-
const svc = yield* ReviewService;
|
|
757
|
-
return yield* svc.getById(reviewId);
|
|
758
|
-
})
|
|
759
|
-
),
|
|
760
|
-
run(
|
|
761
|
-
"reviews.getStatus.comments",
|
|
762
|
-
Effect.gen(function* () {
|
|
763
|
-
const svc = yield* CommentService;
|
|
764
|
-
return yield* svc.getStats(reviewId);
|
|
765
|
-
})
|
|
766
|
-
)
|
|
767
|
-
]);
|
|
768
|
-
return {
|
|
769
|
-
resolvedComments: stats.resolved,
|
|
770
|
-
reviewId,
|
|
771
|
-
status: review.status,
|
|
772
|
-
totalComments: stats.total,
|
|
773
|
-
unresolvedComments: stats.unresolved,
|
|
774
|
-
withSuggestions: 0
|
|
775
|
-
};
|
|
776
|
-
},
|
|
777
|
-
getSuggestions: async (reviewIdValue) => {
|
|
778
|
-
const reviewId = reviewIdValue;
|
|
779
|
-
const comments = await run(
|
|
780
|
-
"reviews.getSuggestions",
|
|
781
|
-
Effect.gen(function* comments2() {
|
|
782
|
-
const svc = yield* CommentService;
|
|
783
|
-
return yield* svc.getByReview(reviewId);
|
|
784
|
-
})
|
|
785
|
-
);
|
|
786
|
-
return comments.filter((c) => c.suggestion != null).map((c) => ({
|
|
787
|
-
commentId: c.id,
|
|
788
|
-
id: c.id,
|
|
789
|
-
originalCode: "",
|
|
790
|
-
suggestedCode: c.suggestion ?? ""
|
|
791
|
-
}));
|
|
792
|
-
},
|
|
793
|
-
list: async (filters) => {
|
|
794
|
-
const parsed = decodeInputSync(
|
|
795
|
-
ReviewListFilters,
|
|
796
|
-
filters ?? {},
|
|
797
|
-
"reviews.list"
|
|
798
|
-
);
|
|
799
|
-
return run(
|
|
800
|
-
"reviews.list",
|
|
801
|
-
Effect.gen(function* list() {
|
|
802
|
-
const svc = yield* ReviewService;
|
|
803
|
-
return yield* svc.list({
|
|
804
|
-
page: parsed.page,
|
|
805
|
-
pageSize: parsed.limit,
|
|
806
|
-
repositoryPath: config.repoRoot,
|
|
807
|
-
sourceType: parsed.sourceType,
|
|
808
|
-
status: parsed.status
|
|
809
|
-
});
|
|
810
|
-
})
|
|
811
|
-
);
|
|
812
|
-
}
|
|
813
|
-
});
|
|
814
|
-
const todos = Object.freeze({
|
|
815
|
-
add: async (inputValue) => {
|
|
816
|
-
throwIfReadonly();
|
|
817
|
-
const parsed = decodeCreateTodoInput(inputValue);
|
|
818
|
-
return run(
|
|
819
|
-
"todos.add",
|
|
820
|
-
Effect.gen(function* add() {
|
|
821
|
-
const svc = yield* TodoService;
|
|
822
|
-
return yield* svc.create(parsed);
|
|
823
|
-
})
|
|
824
|
-
);
|
|
825
|
-
},
|
|
826
|
-
clear: async (_reviewIdValue) => {
|
|
827
|
-
throwIfReadonly();
|
|
828
|
-
return run(
|
|
829
|
-
"todos.clear",
|
|
830
|
-
Effect.gen(function* clear() {
|
|
831
|
-
const svc = yield* TodoService;
|
|
832
|
-
const result = yield* svc.removeCompleted();
|
|
833
|
-
return { removed: result.deleted, success: true };
|
|
834
|
-
})
|
|
835
|
-
);
|
|
836
|
-
},
|
|
837
|
-
done: async (todoIdValue) => {
|
|
838
|
-
throwIfReadonly();
|
|
839
|
-
const todoId = todoIdValue;
|
|
840
|
-
return run(
|
|
841
|
-
"todos.done",
|
|
842
|
-
Effect.gen(function* done() {
|
|
843
|
-
const svc = yield* TodoService;
|
|
844
|
-
const todo = yield* svc.getById(todoId);
|
|
845
|
-
if (todo.completed) {
|
|
846
|
-
return todo;
|
|
847
|
-
}
|
|
848
|
-
return yield* svc.toggle(todoId);
|
|
849
|
-
})
|
|
850
|
-
);
|
|
851
|
-
},
|
|
852
|
-
list: async (filter) => {
|
|
853
|
-
const parsed = decodeInputSync(
|
|
854
|
-
TodoListFilter,
|
|
855
|
-
filter ?? {},
|
|
856
|
-
"todos.list"
|
|
857
|
-
);
|
|
858
|
-
const result = await run(
|
|
859
|
-
"todos.list",
|
|
860
|
-
Effect.gen(function* result2() {
|
|
861
|
-
const svc = yield* TodoService;
|
|
862
|
-
return yield* svc.list({ reviewId: parsed.reviewId });
|
|
863
|
-
})
|
|
864
|
-
);
|
|
865
|
-
return result.data;
|
|
866
|
-
},
|
|
867
|
-
move: async (todoIdValue, positionValue) => {
|
|
868
|
-
throwIfReadonly();
|
|
869
|
-
const parsed = decodeInputSync(
|
|
870
|
-
TodoMoveInput,
|
|
871
|
-
{ position: positionValue, todoId: todoIdValue },
|
|
872
|
-
"todos.move"
|
|
873
|
-
);
|
|
874
|
-
return run(
|
|
875
|
-
"todos.move",
|
|
876
|
-
Effect.gen(function* move() {
|
|
877
|
-
const svc = yield* TodoService;
|
|
878
|
-
return yield* svc.move(parsed.todoId, parsed.position);
|
|
879
|
-
})
|
|
880
|
-
);
|
|
881
|
-
},
|
|
882
|
-
remove: async (todoIdValue) => {
|
|
883
|
-
throwIfReadonly();
|
|
884
|
-
const todoId = todoIdValue;
|
|
885
|
-
return run(
|
|
886
|
-
"todos.remove",
|
|
887
|
-
Effect.gen(function* remove() {
|
|
888
|
-
const svc = yield* TodoService;
|
|
889
|
-
return yield* svc.remove(todoId);
|
|
890
|
-
})
|
|
891
|
-
);
|
|
892
|
-
},
|
|
893
|
-
undone: async (todoIdValue) => {
|
|
894
|
-
throwIfReadonly();
|
|
895
|
-
const todoId = todoIdValue;
|
|
896
|
-
return run(
|
|
897
|
-
"todos.undone",
|
|
898
|
-
Effect.gen(function* undone() {
|
|
899
|
-
const svc = yield* TodoService;
|
|
900
|
-
const todo = yield* svc.getById(todoId);
|
|
901
|
-
if (!todo.completed) {
|
|
902
|
-
return todo;
|
|
903
|
-
}
|
|
904
|
-
return yield* svc.toggle(todoId);
|
|
905
|
-
})
|
|
906
|
-
);
|
|
907
|
-
}
|
|
908
|
-
});
|
|
909
|
-
return {
|
|
910
|
-
events: baseGlobals.events,
|
|
911
|
-
intelligence: baseGlobals.intelligence,
|
|
912
|
-
reviews,
|
|
913
|
-
session: baseGlobals.session,
|
|
914
|
-
sources: baseGlobals.sources,
|
|
915
|
-
todos
|
|
916
|
-
};
|
|
917
|
-
};
|
|
918
|
-
var executeCode = (runtime, config, input) => Effect.gen(function* executeCode2() {
|
|
919
|
-
const timeoutMs = yield* validateTimeout(input.timeout, config);
|
|
920
|
-
const code = yield* validateCode(input.code);
|
|
921
|
-
const journal = createJournal();
|
|
922
|
-
const globals = buildSandboxGlobals(runtime, config, journal);
|
|
923
|
-
const sandboxResult = yield* runSandbox(globals, code, timeoutMs).pipe(
|
|
924
|
-
Effect.catchTags({
|
|
925
|
-
ExecutionTimeoutError: (e) => Effect.succeed({
|
|
926
|
-
error: e.message,
|
|
927
|
-
ok: false,
|
|
928
|
-
result: journal.entries.length === 0 ? null : { operations: journal.entries }
|
|
929
|
-
}),
|
|
930
|
-
SandboxExecutionError: (e) => Effect.succeed({
|
|
931
|
-
error: e.message,
|
|
932
|
-
ok: false,
|
|
933
|
-
result: journal.entries.length === 0 ? null : { operations: journal.entries }
|
|
934
|
-
})
|
|
935
|
-
}),
|
|
936
|
-
Effect.map((result) => {
|
|
937
|
-
if (typeof result === "object" && result !== null && "ok" in result && result.ok === false) {
|
|
938
|
-
return result;
|
|
939
|
-
}
|
|
940
|
-
return { ok: true, result };
|
|
941
|
-
})
|
|
942
|
-
);
|
|
943
|
-
return finalizeOutput(sandboxResult, config.maxOutputBytes);
|
|
944
|
-
});
|
|
945
|
-
var executeCodeToPromise = async (runtime, config, input) => {
|
|
946
|
-
const program = executeCode(runtime, config, input).pipe(
|
|
947
|
-
Effect.catchTags({
|
|
948
|
-
InvalidCodeError: (e) => Effect.succeed({
|
|
949
|
-
error: e.message,
|
|
950
|
-
ok: false,
|
|
951
|
-
result: null
|
|
952
|
-
}),
|
|
953
|
-
InvalidTimeoutError: (e) => Effect.succeed({
|
|
954
|
-
error: e.message,
|
|
955
|
-
ok: false,
|
|
956
|
-
result: null
|
|
957
|
-
})
|
|
958
|
-
})
|
|
959
|
-
);
|
|
960
|
-
try {
|
|
961
|
-
return await Effect.runPromise(program);
|
|
962
|
-
} catch (error) {
|
|
963
|
-
return finalizeOutput(
|
|
964
|
-
{
|
|
965
|
-
error: formatError(error),
|
|
966
|
-
ok: false,
|
|
967
|
-
result: null
|
|
968
|
-
},
|
|
969
|
-
config.maxOutputBytes
|
|
970
|
-
);
|
|
971
|
-
}
|
|
972
|
-
};
|
|
973
|
-
var makeConfigLayer = (config) => Layer2.setConfigProvider(
|
|
974
|
-
ConfigProvider.fromMap(
|
|
975
|
-
/* @__PURE__ */ new Map([
|
|
976
|
-
["DB_PATH", config.dbPath],
|
|
977
|
-
["REPOSITORY_PATH", config.repoRoot]
|
|
978
|
-
])
|
|
979
|
-
)
|
|
980
|
-
);
|
|
981
|
-
var makeMcpLayer = (config) => Layer2.mergeAll(CoreLive, McpConfigLive(config)).pipe(
|
|
982
|
-
Layer2.provideMerge(makeConfigLayer(config))
|
|
983
|
-
);
|
|
984
|
-
var createMcpRuntime = (config) => ManagedRuntime.make(makeMcpLayer(config));
|
|
985
|
-
|
|
986
|
-
// src/mcp/server.ts
|
|
987
|
-
var JSON_RPC_VERSION = "2.0";
|
|
988
|
-
var LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
989
|
-
var MCP_SERVER_NAME = "ringi";
|
|
990
|
-
var MCP_SERVER_VERSION = process.env.npm_package_version ?? "0.0.0-dev";
|
|
991
|
-
var EXECUTE_TOOL = {
|
|
992
|
-
description: "Run constrained JavaScript against Ringi review namespaces: review, todo, comment, diff, export, and session.",
|
|
993
|
-
inputSchema: {
|
|
994
|
-
additionalProperties: false,
|
|
995
|
-
properties: {
|
|
996
|
-
code: {
|
|
997
|
-
description: "JavaScript snippet to evaluate inside the Ringi MCP sandbox.",
|
|
998
|
-
type: "string"
|
|
999
|
-
},
|
|
1000
|
-
timeout: {
|
|
1001
|
-
description: "Optional timeout in milliseconds. Defaults to 30000 and clamps at 120000.",
|
|
1002
|
-
type: "number"
|
|
1003
|
-
}
|
|
1004
|
-
},
|
|
1005
|
-
required: ["code"],
|
|
1006
|
-
type: "object"
|
|
1007
|
-
},
|
|
1008
|
-
name: "execute",
|
|
1009
|
-
outputSchema: {
|
|
1010
|
-
additionalProperties: false,
|
|
1011
|
-
properties: {
|
|
1012
|
-
error: { type: "string" },
|
|
1013
|
-
ok: { type: "boolean" },
|
|
1014
|
-
result: {},
|
|
1015
|
-
truncated: { type: "boolean" }
|
|
1016
|
-
},
|
|
1017
|
-
required: ["ok", "result"],
|
|
1018
|
-
type: "object"
|
|
1019
|
-
}
|
|
1020
|
-
};
|
|
1021
|
-
var writeStderr = (message) => {
|
|
1022
|
-
process.stderr.write(`[ringi:mcp] ${message}
|
|
1023
|
-
`);
|
|
1024
|
-
};
|
|
1025
|
-
var writeMessage = (message) => {
|
|
1026
|
-
const body = JSON.stringify(message);
|
|
1027
|
-
const payload = `Content-Length: ${Buffer.byteLength(body, "utf8")}\r
|
|
1028
|
-
\r
|
|
1029
|
-
${body}`;
|
|
1030
|
-
stdout.write(payload);
|
|
1031
|
-
};
|
|
1032
|
-
var parseContentLength = (headerText) => {
|
|
1033
|
-
const headerLine = headerText.split("\r\n").find((line) => line.toLowerCase().startsWith("content-length:"));
|
|
1034
|
-
if (!headerLine) {
|
|
1035
|
-
throw new Error("Missing Content-Length header");
|
|
1036
|
-
}
|
|
1037
|
-
const rawValue = headerLine.slice(headerLine.indexOf(":") + 1).trim();
|
|
1038
|
-
const parsed = Number.parseInt(rawValue, 10);
|
|
1039
|
-
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
1040
|
-
throw new Error(`Invalid Content-Length header: ${rawValue}`);
|
|
1041
|
-
}
|
|
1042
|
-
return parsed;
|
|
1043
|
-
};
|
|
1044
|
-
var formatError2 = (error) => {
|
|
1045
|
-
if (error instanceof Error) {
|
|
1046
|
-
return error.message;
|
|
1047
|
-
}
|
|
1048
|
-
return String(error);
|
|
1049
|
-
};
|
|
1050
|
-
var sendError = (id, code, message) => {
|
|
1051
|
-
writeMessage({
|
|
1052
|
-
error: {
|
|
1053
|
-
code,
|
|
1054
|
-
message
|
|
1055
|
-
},
|
|
1056
|
-
id,
|
|
1057
|
-
jsonrpc: JSON_RPC_VERSION
|
|
1058
|
-
});
|
|
1059
|
-
};
|
|
1060
|
-
var sendResult = (id, result) => {
|
|
1061
|
-
writeMessage({
|
|
1062
|
-
id,
|
|
1063
|
-
jsonrpc: JSON_RPC_VERSION,
|
|
1064
|
-
result
|
|
1065
|
-
});
|
|
1066
|
-
};
|
|
1067
|
-
var createInitializeResult = () => ({
|
|
1068
|
-
capabilities: {
|
|
1069
|
-
tools: {
|
|
1070
|
-
listChanged: false
|
|
1071
|
-
}
|
|
1072
|
-
},
|
|
1073
|
-
protocolVersion: LATEST_PROTOCOL_VERSION,
|
|
1074
|
-
serverInfo: {
|
|
1075
|
-
description: "Local-first MCP codemode adapter over the Ringi core runtime.",
|
|
1076
|
-
name: MCP_SERVER_NAME,
|
|
1077
|
-
version: MCP_SERVER_VERSION
|
|
1078
|
-
}
|
|
1079
|
-
});
|
|
1080
|
-
var StdioJsonRpcServer = class {
|
|
1081
|
-
config = resolveMcpConfig(process.argv.slice(2));
|
|
1082
|
-
runtime = createMcpRuntime(this.config);
|
|
1083
|
-
buffer = Buffer.alloc(0);
|
|
1084
|
-
initialized = false;
|
|
1085
|
-
shuttingDown = false;
|
|
1086
|
-
start() {
|
|
1087
|
-
stdin.on("data", async (chunk) => {
|
|
1088
|
-
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
1089
|
-
try {
|
|
1090
|
-
await this.drainBuffer();
|
|
1091
|
-
} catch (error) {
|
|
1092
|
-
writeStderr(`fatal buffer drain error: ${formatError2(error)}`);
|
|
1093
|
-
}
|
|
1094
|
-
});
|
|
1095
|
-
stdin.on("end", async () => {
|
|
1096
|
-
await this.runtime.dispose();
|
|
1097
|
-
});
|
|
1098
|
-
process.on("SIGINT", async () => {
|
|
1099
|
-
await this.close(0);
|
|
1100
|
-
});
|
|
1101
|
-
process.on("SIGTERM", async () => {
|
|
1102
|
-
await this.close(0);
|
|
1103
|
-
});
|
|
1104
|
-
writeStderr(
|
|
1105
|
-
`server started readonly=${String(this.config.readonly)} repo=${this.config.repoRoot}`
|
|
1106
|
-
);
|
|
1107
|
-
}
|
|
1108
|
-
async close(code) {
|
|
1109
|
-
await this.runtime.dispose();
|
|
1110
|
-
process.exit(code);
|
|
1111
|
-
}
|
|
1112
|
-
async drainBuffer() {
|
|
1113
|
-
while (true) {
|
|
1114
|
-
const headerEnd = this.buffer.indexOf("\r\n\r\n");
|
|
1115
|
-
if (headerEnd === -1) {
|
|
1116
|
-
return;
|
|
1117
|
-
}
|
|
1118
|
-
const headerText = this.buffer.subarray(0, headerEnd).toString("utf8");
|
|
1119
|
-
const contentLength = parseContentLength(headerText);
|
|
1120
|
-
const messageEnd = headerEnd + 4 + contentLength;
|
|
1121
|
-
if (this.buffer.byteLength < messageEnd) {
|
|
1122
|
-
return;
|
|
1123
|
-
}
|
|
1124
|
-
const payload = this.buffer.subarray(headerEnd + 4, messageEnd).toString("utf8");
|
|
1125
|
-
this.buffer = this.buffer.subarray(messageEnd);
|
|
1126
|
-
let message;
|
|
1127
|
-
try {
|
|
1128
|
-
message = JSON.parse(payload);
|
|
1129
|
-
} catch (error) {
|
|
1130
|
-
sendError(null, -32700, `Parse error: ${formatError2(error)}`);
|
|
1131
|
-
continue;
|
|
1132
|
-
}
|
|
1133
|
-
await this.handleMessage(message);
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
async handleMessage(message) {
|
|
1137
|
-
if (typeof message.method !== "string") {
|
|
1138
|
-
sendError(message.id ?? null, -32600, "Invalid JSON-RPC request");
|
|
1139
|
-
return;
|
|
1140
|
-
}
|
|
1141
|
-
try {
|
|
1142
|
-
if (await this.handleLifecycleMessage(message)) {
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
this.assertInitialized();
|
|
1146
|
-
if (message.method === "tools/list") {
|
|
1147
|
-
sendResult(message.id ?? null, { tools: [EXECUTE_TOOL] });
|
|
1148
|
-
return;
|
|
1149
|
-
}
|
|
1150
|
-
if (message.method === "tools/call") {
|
|
1151
|
-
await this.handleToolCall(message.id ?? null, message.params);
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
sendError(
|
|
1155
|
-
message.id ?? null,
|
|
1156
|
-
-32601,
|
|
1157
|
-
`Method not found: ${message.method}`
|
|
1158
|
-
);
|
|
1159
|
-
} catch (error) {
|
|
1160
|
-
sendError(message.id ?? null, -32603, formatError2(error));
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
async handleLifecycleMessage(message) {
|
|
1164
|
-
if (message.method === "initialize") {
|
|
1165
|
-
this.initialized = true;
|
|
1166
|
-
sendResult(message.id ?? null, createInitializeResult());
|
|
1167
|
-
return true;
|
|
1168
|
-
}
|
|
1169
|
-
if (message.method === "notifications/initialized") {
|
|
1170
|
-
return true;
|
|
1171
|
-
}
|
|
1172
|
-
if (message.method === "ping") {
|
|
1173
|
-
sendResult(message.id ?? null, {});
|
|
1174
|
-
return true;
|
|
1175
|
-
}
|
|
1176
|
-
if (message.method === "shutdown") {
|
|
1177
|
-
this.shuttingDown = true;
|
|
1178
|
-
sendResult(message.id ?? null, {});
|
|
1179
|
-
return true;
|
|
1180
|
-
}
|
|
1181
|
-
if (message.method === "exit") {
|
|
1182
|
-
await this.close(this.shuttingDown ? 0 : 1);
|
|
1183
|
-
return true;
|
|
1184
|
-
}
|
|
1185
|
-
return false;
|
|
1186
|
-
}
|
|
1187
|
-
assertInitialized() {
|
|
1188
|
-
if (!this.initialized) {
|
|
1189
|
-
throw new Error("Server is not initialized");
|
|
1190
|
-
}
|
|
1191
|
-
if (this.shuttingDown) {
|
|
1192
|
-
throw new Error("Server is shutting down");
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
async handleToolCall(id, params) {
|
|
1196
|
-
if (!params || typeof params.name !== "string") {
|
|
1197
|
-
sendError(id, -32602, "Invalid tools/call params");
|
|
1198
|
-
return;
|
|
1199
|
-
}
|
|
1200
|
-
if (params.name !== EXECUTE_TOOL.name) {
|
|
1201
|
-
sendError(id, -32602, `Tool not found: ${String(params.name)}`);
|
|
1202
|
-
return;
|
|
1203
|
-
}
|
|
1204
|
-
const argumentsValue = params.arguments;
|
|
1205
|
-
if (typeof argumentsValue !== "object" || argumentsValue === null) {
|
|
1206
|
-
sendError(id, -32602, "Invalid tools/call arguments");
|
|
1207
|
-
return;
|
|
1208
|
-
}
|
|
1209
|
-
const executeResult = await executeCodeToPromise(
|
|
1210
|
-
this.runtime,
|
|
1211
|
-
this.config,
|
|
1212
|
-
argumentsValue
|
|
1213
|
-
);
|
|
1214
|
-
sendResult(id, {
|
|
1215
|
-
content: [
|
|
1216
|
-
{
|
|
1217
|
-
text: JSON.stringify(executeResult),
|
|
1218
|
-
type: "text"
|
|
1219
|
-
}
|
|
1220
|
-
],
|
|
1221
|
-
isError: !executeResult.ok,
|
|
1222
|
-
structuredContent: executeResult
|
|
1223
|
-
});
|
|
1224
|
-
}
|
|
1225
|
-
};
|
|
1226
|
-
new StdioJsonRpcServer().start();
|
|
1227
|
-
//# sourceMappingURL=mcp.js.map
|
|
1228
|
-
//# sourceMappingURL=mcp.js.map
|