severally-mcp 2.0.0 → 2.1.0
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/CONFIG.md +4 -2
- package/README.ja.md +28 -15
- package/README.md +29 -15
- package/docs/how-it-works.ja.md +7 -1
- package/docs/how-it-works.md +7 -1
- package/docs/install.ja.md +13 -1
- package/docs/install.md +16 -2
- package/package.json +2 -2
- package/plugins/severally/.claude-plugin/plugin.json +1 -1
- package/plugins/severally/.codex-plugin/plugin.json +4 -2
- package/plugins/severally/.mcp.json +2 -1
- package/plugins/severally/dist/severally-mcp.mjs +327 -94
- package/plugins/severally/skills/_template/SKILL.md.tmpl +5 -3
- package/plugins/severally/skills/_template/reference.md.tmpl +12 -6
- package/plugins/severally/skills/antigravity/severally/SKILL.md +5 -3
- package/plugins/severally/skills/antigravity/severally/reference.md +12 -6
- package/plugins/severally/skills/claude/severally/SKILL.md +5 -3
- package/plugins/severally/skills/claude/severally/reference.md +12 -6
- package/plugins/severally/skills/codex/severally/SKILL.md +5 -3
- package/plugins/severally/skills/codex/severally/reference.md +12 -6
- package/plugins/severally/skills/opencode/severally/SKILL.md +5 -3
- package/plugins/severally/skills/opencode/severally/reference.md +12 -6
- package/scripts/init-config.mjs +1 -1
- package/scripts/install.mjs +35 -5
- package/src/adapters/antigravity-sandbox.mjs +5 -1
- package/src/adapters/codex.mjs +17 -6
- package/src/brief.mjs +38 -16
- package/src/expose-paths.mjs +68 -5
- package/src/jobs.mjs +171 -45
- package/src/policy.mjs +5 -4
- package/src/redact.mjs +20 -1
- package/src/schema.mjs +5 -4
- package/src/server.mjs +44 -16
package/src/server.mjs
CHANGED
|
@@ -9,12 +9,17 @@ import { requestSchema, RequestError } from './schema.mjs';
|
|
|
9
9
|
import { JobManager, pointsOf } from './jobs.mjs';
|
|
10
10
|
import { exportChain, ExportError } from './export.mjs';
|
|
11
11
|
|
|
12
|
-
export const SEVERALLY_VERSION = '2.
|
|
12
|
+
export const SEVERALLY_VERSION = '2.1.0';
|
|
13
13
|
|
|
14
14
|
// consult_get waits this long when no wait_ms is given: a call that forgot it
|
|
15
|
-
// would otherwise return at once and be repeated.
|
|
16
|
-
//
|
|
17
|
-
|
|
15
|
+
// would otherwise return at once and be repeated. Up to 45 s, under the 60 s
|
|
16
|
+
// some clients allow a tool call -- unless this client's own registration
|
|
17
|
+
// raised the limit (SEVERALLY_MAX_WAIT_MS), which says the client waits that
|
|
18
|
+
// long. The config file is shared by every client, so it cannot say that.
|
|
19
|
+
const DEFAULT_WAIT_MS = process.env.SEVERALLY_MAX_WAIT_MS ? POLICY.maxWaitMs : Math.min(POLICY.maxWaitMs, 45_000);
|
|
20
|
+
// While consult_get waits, the client hears from it this often: a client that
|
|
21
|
+
// counts silence against the call keeps it open.
|
|
22
|
+
const PROGRESS_EVERY_MS = 15_000;
|
|
18
23
|
|
|
19
24
|
const semver = (v) => String(v).split('.').map(Number);
|
|
20
25
|
const older = (a, b) => { const [x, y] = [semver(a), semver(b)]; for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] < y[i]; return false; };
|
|
@@ -79,8 +84,12 @@ ${availableConsultants().map((c) => ` ${c.name}: ${c.cli}, ${c.model}`)
|
|
|
79
84
|
drift -- and the result is marked as same lineage.
|
|
80
85
|
targets: ask several consultants the same question at once (mutually exclusive with target, no duplicates).
|
|
81
86
|
targets: ["all"] asks every consultant listed above; "all" cannot be mixed with names.
|
|
82
|
-
Every member gets the byte-identical brief
|
|
83
|
-
|
|
87
|
+
Every member gets the byte-identical brief (on a follow-up, plus its own earlier rounds) and one
|
|
88
|
+
group_id; poll it with consult_get({ group_id }). Each member takes one slot of the concurrency cap,
|
|
89
|
+
and a fan-out that would go over it is refused before any member starts.
|
|
90
|
+
A follow-up (followup_to) to a job names that one consultant. followup_to a group_id, with no target or
|
|
91
|
+
targets, asks every member of that fan-out the same new question, each on its own chain from its latest
|
|
92
|
+
round; a member that is still running, never answered or has used its rounds is skipped and listed.
|
|
84
93
|
caller: optional -- the CLI you are running in ("codex" / "claude-code" / "antigravity" / "opencode"), so the
|
|
85
94
|
server can annotate a same-vendor consultation.
|
|
86
95
|
caller_model: optional -- the model you are running on (e.g. "claude-opus-5"), self-declared and never checked.
|
|
@@ -104,7 +113,9 @@ code only if you hand it over in this call. When the question is about code in a
|
|
|
104
113
|
name the files it is about by absolute path in context.expose_paths: those files and directories are copied
|
|
105
114
|
read-only into its working directory (under ./workspace) and are then the only part of your repository it has.
|
|
106
115
|
At most 20 entries, 500 files and 5 MB in total; symlinks and protected files are skipped rather than copied,
|
|
107
|
-
and text files are credential-masked exactly as the brief is. A
|
|
116
|
+
and text files are credential-masked exactly as the brief is. A directory inside a git work tree copies only the
|
|
117
|
+
files git lists (tracked, or untracked and not ignored), so a repository root fits; outside git, .git and
|
|
118
|
+
node_modules are skipped. A follow-up starts in a fresh working directory
|
|
108
119
|
with no files: pass context.expose_from_parent: true to copy again, as they are now, the paths the round it follows
|
|
109
120
|
exposed (a changed or vanished entry is marked; an earlier expose_diff brings back the files it touched, not the
|
|
110
121
|
diff), and add expose_paths for anything new. It is never implied: leave it off when the follow-up does not need them. Put the facts it needs into context.facts, and keep
|
|
@@ -182,9 +193,11 @@ export function createServer(manager = new JobManager()) {
|
|
|
182
193
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
183
194
|
description:
|
|
184
195
|
`Fetch the state of a consultation. It waits up to ${DEFAULT_WAIT_MS} ms for it to finish before ` +
|
|
185
|
-
'answering,
|
|
186
|
-
'wait_ms: 0 answers at once.
|
|
187
|
-
'
|
|
196
|
+
'answering, a limit set to stay under this client\'s tool-call timeout; wait_ms sets another wait, and ' +
|
|
197
|
+
'wait_ms: 0 answers at once. While it waits it sends a progress notification every 15 s if you asked ' +
|
|
198
|
+
'for them, and a call you cancel stops waiting. A typical consultation takes one to ten minutes; call ' +
|
|
199
|
+
'this again while it runs, and pass a shorter wait_ms when you have independent work to do in between. ' +
|
|
200
|
+
'A completed job carries the structured ' +
|
|
188
201
|
'answer; a failed one carries failure.kind (timeout, auth, usage_limit, rate_limited, model_unavailable, ' +
|
|
189
202
|
'invalid_output, cli_error, spawn_error, interrupted) — that is "no advice was obtained", which is different from advice that arrived ' +
|
|
190
203
|
'with thin evidence (see quality.evidence_basis). ' +
|
|
@@ -206,16 +219,30 @@ export function createServer(manager = new JobManager()) {
|
|
|
206
219
|
.describe('with job_id: return only these points of the finished answer (f1, u1, c1 ...), each whole with its grounds and, under `recorded`, the verdict you wrote'),
|
|
207
220
|
}),
|
|
208
221
|
},
|
|
209
|
-
async ({ job_id, group_id, wait_ms, detail, ids }) => {
|
|
222
|
+
async ({ job_id, group_id, wait_ms, detail, ids }, ctx) => {
|
|
210
223
|
if (Boolean(job_id) === Boolean(group_id)) {
|
|
211
224
|
return fail({ error: 'invalid_request', message: 'pass exactly one of job_id or group_id' });
|
|
212
225
|
}
|
|
213
226
|
if (ids && (group_id || detail)) {
|
|
214
227
|
return fail({ error: 'invalid_request', message: 'ids goes with job_id alone: a point id belongs to one consultant\'s answer' });
|
|
215
228
|
}
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
229
|
+
const req = ctx?.mcpReq;
|
|
230
|
+
const token = req?._meta?.progressToken;
|
|
231
|
+
let beats = 0;
|
|
232
|
+
const timer = token === undefined ? null : setInterval(() => {
|
|
233
|
+
beats += 1;
|
|
234
|
+
req.notify({ method: 'notifications/progress', params: { progressToken: token, progress: beats, message: 'the consultation is still running' } })
|
|
235
|
+
.catch(() => {});
|
|
236
|
+
}, PROGRESS_EVERY_MS);
|
|
237
|
+
let view;
|
|
238
|
+
try {
|
|
239
|
+
// A request the client cancels stops the wait with it.
|
|
240
|
+
view = group_id
|
|
241
|
+
? await manager.waitGroup(group_id, wait_ms ?? DEFAULT_WAIT_MS, { detail }, req?.signal)
|
|
242
|
+
: await manager.wait(job_id, wait_ms ?? DEFAULT_WAIT_MS, { detail }, req?.signal);
|
|
243
|
+
} finally {
|
|
244
|
+
clearInterval(timer);
|
|
245
|
+
}
|
|
219
246
|
if (!view) {
|
|
220
247
|
return fail({ error: 'unknown_job', message: `no consultation with ${group_id ? 'group_id' : 'job_id'} "${group_id ?? job_id}"` });
|
|
221
248
|
}
|
|
@@ -267,13 +294,14 @@ export function createServer(manager = new JobManager()) {
|
|
|
267
294
|
+ 'not for this codebase), "unverifiable" (cannot be settled with what you can reach), "unverified" (not '
|
|
268
295
|
+ 'checked yet, and say in effect why not). It does not say whether you adopted the point. effect is what '
|
|
269
296
|
+ 'it changed about your decision; note is the evidence you used. A follow-up to this job shows the '
|
|
270
|
-
+ 'consultant each
|
|
297
|
+
+ 'consultant each point\'s verdict and effect (credential-masked, like the brief), never the note. '
|
|
271
298
|
+ 'Recording the same id again replaces '
|
|
272
299
|
+ 'that entry. This server stores what you write and counts the verdicts; it never infers one, and never '
|
|
273
300
|
+ 'decides a consultation was worth it. The entry is saved beside the answer and the brief in '
|
|
274
301
|
+ '~/.severally/history, which is what makes the decision readable a month from now; the job is '
|
|
275
302
|
+ 'read back from that history, so a consultation from an earlier session can still be recorded against. '
|
|
276
|
-
+ '
|
|
303
|
+
+ 'It answers with counts (the verdicts tallied, and how many points now carry one); consult_get reads '
|
|
304
|
+
+ 'the entries back. Pass `reflection` to record what the answer added over what you already expected, when the '
|
|
277
305
|
+ 'consultation was started with a `prediction`. A prediction itself cannot be written here: it goes '
|
|
278
306
|
+ 'in consult_start, before the consultant runs, which is the only thing that makes it a prediction. '
|
|
279
307
|
+ 'For a fan-out, record every member in one call: records: [{ job_id, entries, reflection? }, ...] in '
|