@mrrlin-dev/mcp 0.3.4 → 0.3.5
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/bin.cjs
CHANGED
|
@@ -4273,7 +4273,7 @@ var require_websocket = __commonJS({
|
|
|
4273
4273
|
var http2 = require("http");
|
|
4274
4274
|
var net = require("net");
|
|
4275
4275
|
var tls = require("tls");
|
|
4276
|
-
var { randomBytes: randomBytes3, createHash:
|
|
4276
|
+
var { randomBytes: randomBytes3, createHash: createHash3 } = require("crypto");
|
|
4277
4277
|
var { Duplex, Readable } = require("stream");
|
|
4278
4278
|
var { URL: URL2 } = require("url");
|
|
4279
4279
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -4941,7 +4941,7 @@ var require_websocket = __commonJS({
|
|
|
4941
4941
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
4942
4942
|
return;
|
|
4943
4943
|
}
|
|
4944
|
-
const digest =
|
|
4944
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
4945
4945
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
4946
4946
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
4947
4947
|
return;
|
|
@@ -5310,7 +5310,7 @@ var require_websocket_server = __commonJS({
|
|
|
5310
5310
|
var EventEmitter3 = require("events");
|
|
5311
5311
|
var http2 = require("http");
|
|
5312
5312
|
var { Duplex } = require("stream");
|
|
5313
|
-
var { createHash:
|
|
5313
|
+
var { createHash: createHash3 } = require("crypto");
|
|
5314
5314
|
var extension2 = require_extension();
|
|
5315
5315
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
5316
5316
|
var subprotocol2 = require_subprotocol();
|
|
@@ -5617,7 +5617,7 @@ var require_websocket_server = __commonJS({
|
|
|
5617
5617
|
);
|
|
5618
5618
|
}
|
|
5619
5619
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
5620
|
-
const digest =
|
|
5620
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
5621
5621
|
const headers = [
|
|
5622
5622
|
"HTTP/1.1 101 Switching Protocols",
|
|
5623
5623
|
"Upgrade: websocket",
|
|
@@ -36275,7 +36275,7 @@ var operatorSessionSchema = external_exports.object({
|
|
|
36275
36275
|
issuedAt: external_exports.number().int().positive(),
|
|
36276
36276
|
kind: external_exports.enum(["web", "agent"]).default("web"),
|
|
36277
36277
|
login: external_exports.string().min(1),
|
|
36278
|
-
projectSlug: external_exports.string().min(1),
|
|
36278
|
+
projectSlug: external_exports.string().min(1).optional(),
|
|
36279
36279
|
provider: external_exports.literal("github"),
|
|
36280
36280
|
repo: external_exports.string().min(1).nullable(),
|
|
36281
36281
|
// ADR 0016: `scope` distinguishes project-pinned agent tokens (legacy, default) from
|
|
@@ -36284,7 +36284,7 @@ var operatorSessionSchema = external_exports.object({
|
|
|
36284
36284
|
// cleanly into the project branch of the middleware (which mirrors today's behavior).
|
|
36285
36285
|
scope: external_exports.enum(["project", "operator"]).default("project")
|
|
36286
36286
|
});
|
|
36287
|
-
var AGENT_SESSION_TTL_MS = 1e3 * 60 *
|
|
36287
|
+
var AGENT_SESSION_TTL_MS = 1e3 * 60 * 15;
|
|
36288
36288
|
|
|
36289
36289
|
// ../../packages/schemas/dist/github-state.js
|
|
36290
36290
|
var TTL_MS = 10 * 6e4;
|
|
@@ -36402,6 +36402,7 @@ var mrrlinTaskTypeSchema = external_exports.enum(["task", "bug"]);
|
|
|
36402
36402
|
var mrrlinTaskImpactSchema = external_exports.enum(["low", "medium", "high"]);
|
|
36403
36403
|
var mrrlinAutonomyLevelSchema = external_exports.enum(["auto", "minimal_human", "human_only"]);
|
|
36404
36404
|
var mrrlinAutoDeploySchema = external_exports.enum(["off", "dev", "prod"]);
|
|
36405
|
+
var mrrlinSpecApprovedSchema = external_exports.enum(["pending", "approved", "skipped", "skipped_legacy"]);
|
|
36405
36406
|
var mrrlinTaskSourceSchema = external_exports.enum(["fixture-import", "api"]);
|
|
36406
36407
|
var mrrlinPlanStatusSchema = external_exports.enum(["active", "paused", "done", "archived"]);
|
|
36407
36408
|
var mrrlinTaskEventTypeSchema = external_exports.enum(["autonomy_set", "director_thread_reset", "note"]);
|
|
@@ -36492,7 +36493,13 @@ var mrrlinTaskSchema = external_exports.object({
|
|
|
36492
36493
|
latestJudgementAt: external_exports.string().datetime().nullable().default(null),
|
|
36493
36494
|
latestJudgementKind: external_exports.enum(["autonomy", "handoff"]).nullable().default(null),
|
|
36494
36495
|
notes: external_exports.string().nullable().default(null),
|
|
36496
|
+
// Raw operator request captured at task creation (operator-authored ONLY, never Director-generated).
|
|
36497
|
+
// The intent-aware spec gate judges the spec against this; null/absent ⇒ the gate fail-closes.
|
|
36498
|
+
// Output-optional (not .default) so existing task fixtures need no churn; DB rows always resolve it
|
|
36499
|
+
// to `string | null` via taskRowToTask.
|
|
36500
|
+
operatorSeedRequest: external_exports.string().max(8e3).nullable().optional(),
|
|
36495
36501
|
planId: mrrlinPlanIdSchema.nullable(),
|
|
36502
|
+
specApproved: mrrlinSpecApprovedSchema.optional(),
|
|
36496
36503
|
specWikiPageId: mrrlinWikiPageIdSchema.nullable(),
|
|
36497
36504
|
status: mrrlinTaskStatusSchema,
|
|
36498
36505
|
subcategory: taxonomySubcategorySchema,
|
|
@@ -36508,6 +36515,7 @@ var mrrlinPlanSchema = external_exports.object({
|
|
|
36508
36515
|
id: mrrlinPlanIdSchema,
|
|
36509
36516
|
name: external_exports.string().min(1),
|
|
36510
36517
|
progress: external_exports.number().min(0).max(1),
|
|
36518
|
+
specWikiPageId: external_exports.string().nullable().default(null),
|
|
36511
36519
|
status: mrrlinPlanStatusSchema,
|
|
36512
36520
|
taskCount: external_exports.number().int().nonnegative()
|
|
36513
36521
|
});
|
|
@@ -36632,6 +36640,7 @@ var mrrlinTaskCreateSchema = external_exports.object({
|
|
|
36632
36640
|
id: mrrlinTaskIdSchema,
|
|
36633
36641
|
impact: mrrlinTaskImpactSchema.default("medium"),
|
|
36634
36642
|
notes: external_exports.string().nullable().optional(),
|
|
36643
|
+
operatorSeedRequest: external_exports.string().max(8e3).nullable().optional(),
|
|
36635
36644
|
planId: mrrlinPlanIdSchema.nullable().optional(),
|
|
36636
36645
|
specWikiPageId: mrrlinWikiPageIdSchema.nullable().optional(),
|
|
36637
36646
|
tags: mrrlinTaskTagsSchema.optional(),
|
|
@@ -36655,6 +36664,7 @@ var mrrlinTaskUpdateSchema = external_exports.object({
|
|
|
36655
36664
|
impact: mrrlinTaskImpactSchema.optional(),
|
|
36656
36665
|
notes: external_exports.string().nullable().optional(),
|
|
36657
36666
|
planId: mrrlinPlanIdSchema.nullable().optional(),
|
|
36667
|
+
specApproved: mrrlinSpecApprovedSchema.optional(),
|
|
36658
36668
|
specWikiPageId: mrrlinWikiPageIdSchema.nullable().optional(),
|
|
36659
36669
|
status: mrrlinTaskStatusSchema.optional(),
|
|
36660
36670
|
subcategory: taxonomySubcategorySchema.optional(),
|
|
@@ -36662,7 +36672,7 @@ var mrrlinTaskUpdateSchema = external_exports.object({
|
|
|
36662
36672
|
type: mrrlinTaskTypeSchema.optional(),
|
|
36663
36673
|
followUpAt: external_exports.string().datetime().nullable().optional(),
|
|
36664
36674
|
verificationDueAt: external_exports.string().datetime().nullable().optional()
|
|
36665
|
-
}).refine((value) => value.assignee !== void 0 || value.autoDeploy !== void 0 || value.autonomyLevel !== void 0 || value.autonomyReason !== void 0 || value.category !== void 0 || value.directorThreadId !== void 0 || value.dueDate !== void 0 || value.followUpAt !== void 0 || value.impact !== void 0 || value.notes !== void 0 || value.planId !== void 0 || value.specWikiPageId !== void 0 || value.status !== void 0 || value.subcategory !== void 0 || value.tags !== void 0 || value.type !== void 0 || value.verificationDueAt !== void 0, { message: "TaskUpdate requires at least one field" }).superRefine((value, context) => {
|
|
36675
|
+
}).refine((value) => value.assignee !== void 0 || value.autoDeploy !== void 0 || value.autonomyLevel !== void 0 || value.autonomyReason !== void 0 || value.category !== void 0 || value.directorThreadId !== void 0 || value.dueDate !== void 0 || value.followUpAt !== void 0 || value.impact !== void 0 || value.notes !== void 0 || value.planId !== void 0 || value.specApproved !== void 0 || value.specWikiPageId !== void 0 || value.status !== void 0 || value.subcategory !== void 0 || value.tags !== void 0 || value.type !== void 0 || value.verificationDueAt !== void 0, { message: "TaskUpdate requires at least one field" }).superRefine((value, context) => {
|
|
36666
36676
|
if (value.autonomyLevel !== void 0 && value.autonomyReason === void 0) {
|
|
36667
36677
|
context.addIssue({
|
|
36668
36678
|
code: external_exports.ZodIssueCode.custom,
|
|
@@ -36703,12 +36713,14 @@ var mrrlinPlanCreateSchema = external_exports.object({
|
|
|
36703
36713
|
goal: external_exports.string().min(1),
|
|
36704
36714
|
id: mrrlinPlanIdSchema,
|
|
36705
36715
|
name: external_exports.string().min(1),
|
|
36716
|
+
specWikiPageId: external_exports.string().nullable().optional(),
|
|
36706
36717
|
status: mrrlinPlanStatusSchema.default("active")
|
|
36707
36718
|
});
|
|
36708
36719
|
var mrrlinPlanUpdateSchema = external_exports.object({
|
|
36709
36720
|
dueDate: external_exports.string().date().nullable().optional(),
|
|
36710
36721
|
goal: external_exports.string().min(1).optional(),
|
|
36711
36722
|
name: external_exports.string().min(1).optional(),
|
|
36723
|
+
specWikiPageId: external_exports.string().nullable().optional(),
|
|
36712
36724
|
status: mrrlinPlanStatusSchema.optional()
|
|
36713
36725
|
}).refine((value) => Object.keys(value).length > 0, { message: "PlanUpdate requires at least one field" });
|
|
36714
36726
|
var mrrlinTaxonomyCreateSchema = mrrlinTaxonomyEntrySchema;
|
|
@@ -37103,7 +37115,9 @@ var exchangeAgentTokenRequestSchema = external_exports.object({
|
|
|
37103
37115
|
var exchangeAgentTokenResponseSchema = external_exports.object({
|
|
37104
37116
|
token: external_exports.string().min(1),
|
|
37105
37117
|
expiresAt: external_exports.string().datetime(),
|
|
37106
|
-
|
|
37118
|
+
// ADR 0016 §T7 amendment: operator-scope exchanges return no projectSlug (the token is
|
|
37119
|
+
// operator-wide); project-scope exchanges still return the pinned slug.
|
|
37120
|
+
projectSlug: external_exports.string().min(1).optional(),
|
|
37107
37121
|
login: external_exports.string().min(1)
|
|
37108
37122
|
});
|
|
37109
37123
|
var MRRLIN_ARTIFACT_FILE_MAX_BYTES = 5 * 1024 * 1024;
|
|
@@ -37271,6 +37285,8 @@ var mrrlinInboxItemSchema = external_exports.object({
|
|
|
37271
37285
|
directorSessionId: external_exports.string().min(1).nullable(),
|
|
37272
37286
|
id: mrrlinInboxItemIdSchema,
|
|
37273
37287
|
kind: mrrlinInboxItemKindSchema,
|
|
37288
|
+
origin: external_exports.string().nullable().optional(),
|
|
37289
|
+
planId: mrrlinPlanIdSchema.nullable().default(null),
|
|
37274
37290
|
projectSlug: mrrlinProjectSlugSchema,
|
|
37275
37291
|
readAt: external_exports.string().datetime().nullable(),
|
|
37276
37292
|
status: mrrlinInboxItemStatusSchema,
|
|
@@ -37282,6 +37298,8 @@ var mrrlinInboxItemCreateSchema = external_exports.object({
|
|
|
37282
37298
|
directorSessionId: external_exports.string().min(1).optional(),
|
|
37283
37299
|
id: mrrlinInboxItemIdSchema,
|
|
37284
37300
|
kind: mrrlinInboxItemKindSchema,
|
|
37301
|
+
origin: external_exports.string().min(1).optional(),
|
|
37302
|
+
planId: mrrlinPlanIdSchema.nullable().optional(),
|
|
37285
37303
|
taskId: mrrlinTaskIdSchema.nullable().optional()
|
|
37286
37304
|
});
|
|
37287
37305
|
var mrrlinInboxItemDecideSchema = external_exports.object({
|
|
@@ -38442,7 +38460,7 @@ Do NOT derail the current task \u2014 if the operator has an in-flight question,
|
|
|
38442
38460
|
}
|
|
38443
38461
|
|
|
38444
38462
|
// src/_generated/version.ts
|
|
38445
|
-
var PKG_VERSION = "0.3.
|
|
38463
|
+
var PKG_VERSION = "0.3.5";
|
|
38446
38464
|
|
|
38447
38465
|
// src/api-base-url.ts
|
|
38448
38466
|
var DEFAULT_API_BASE_URL = "http://127.0.0.1:8787";
|
|
@@ -42520,7 +42538,10 @@ function parseFrame(bytes3) {
|
|
|
42520
42538
|
};
|
|
42521
42539
|
}
|
|
42522
42540
|
case "inbox_read":
|
|
42523
|
-
return {
|
|
42541
|
+
return {
|
|
42542
|
+
type: "inbox_read",
|
|
42543
|
+
...isString(f.projectSlug) ? { projectSlug: f.projectSlug } : {}
|
|
42544
|
+
};
|
|
42524
42545
|
case "inbox_decide":
|
|
42525
42546
|
if (!isString(f.itemId) || !isString(f.idempotencyKey))
|
|
42526
42547
|
throw new Error("frame inbox_decide: itemId + idempotencyKey required");
|
|
@@ -42533,7 +42554,8 @@ function parseFrame(bytes3) {
|
|
|
42533
42554
|
itemId: f.itemId,
|
|
42534
42555
|
decision: f.decision,
|
|
42535
42556
|
idempotencyKey: f.idempotencyKey,
|
|
42536
|
-
...f.reason === void 0 ? {} : { reason: f.reason }
|
|
42557
|
+
...f.reason === void 0 ? {} : { reason: f.reason },
|
|
42558
|
+
...isString(f.projectSlug) ? { projectSlug: f.projectSlug } : {}
|
|
42537
42559
|
};
|
|
42538
42560
|
case "ack":
|
|
42539
42561
|
if (!isString(f.refId))
|
|
@@ -43279,12 +43301,12 @@ var DirectorRelay = class {
|
|
|
43279
43301
|
this.sendFrame(channelId, { type: "ack", refId: frame.idempotencyKey });
|
|
43280
43302
|
return;
|
|
43281
43303
|
}
|
|
43282
|
-
await this.deps.onInboxDecide?.(frame.itemId, frame.decision, frame.idempotencyKey, frame.reason);
|
|
43304
|
+
await this.deps.onInboxDecide?.(frame.itemId, frame.decision, frame.idempotencyKey, frame.reason, frame.projectSlug);
|
|
43283
43305
|
this.seenDecideKeys.add(frame.idempotencyKey);
|
|
43284
43306
|
this.sendFrame(channelId, { type: "ack", refId: frame.idempotencyKey });
|
|
43285
43307
|
return;
|
|
43286
43308
|
case "inbox_read": {
|
|
43287
|
-
const items = await this.deps.onInboxRead?.();
|
|
43309
|
+
const items = await this.deps.onInboxRead?.(frame.projectSlug);
|
|
43288
43310
|
if (items !== void 0) this.sendDirectorEvent(channelId, { type: "inbox_items", items });
|
|
43289
43311
|
this.sendFrame(channelId, { type: "ack", refId: "inbox_read" });
|
|
43290
43312
|
return;
|
|
@@ -43429,30 +43451,38 @@ function decisionToVerdict(decision) {
|
|
|
43429
43451
|
throw new Error(`remote-relay: unknown inbox decision "${decision}"`);
|
|
43430
43452
|
}
|
|
43431
43453
|
}
|
|
43432
|
-
function
|
|
43433
|
-
const
|
|
43434
|
-
`));
|
|
43435
|
-
const provider = createAgentTokenProvider({ secret: deps.agentSecret, baseUrl: deps.apiBaseUrl });
|
|
43436
|
-
const authed = async () => {
|
|
43454
|
+
function _makeRelayInboxCallbacks(provider, apiBaseUrl) {
|
|
43455
|
+
const authed = async (frameSlug) => {
|
|
43437
43456
|
const session = await provider.getSession();
|
|
43457
|
+
const slug = frameSlug ?? session.projectSlug;
|
|
43458
|
+
if (!slug) {
|
|
43459
|
+
throw new Error("[mrrlin-mcp remote-relay] missing project context for inbox action");
|
|
43460
|
+
}
|
|
43438
43461
|
return {
|
|
43439
|
-
client: createMrrlinClient({ baseUrl:
|
|
43440
|
-
slug
|
|
43462
|
+
client: createMrrlinClient({ baseUrl: apiBaseUrl, token: session.token }),
|
|
43463
|
+
slug,
|
|
43441
43464
|
login: session.login
|
|
43442
43465
|
};
|
|
43443
43466
|
};
|
|
43444
|
-
const onInboxRead = async () => {
|
|
43445
|
-
const { client, slug } = await authed();
|
|
43467
|
+
const onInboxRead = async (projectSlug) => {
|
|
43468
|
+
const { client, slug } = await authed(projectSlug);
|
|
43446
43469
|
return boundInboxItems(await client.listInboxItems(slug, { status: "pending" }));
|
|
43447
43470
|
};
|
|
43448
|
-
const onInboxDecide = async (itemId, decision, _key, reason) => {
|
|
43449
|
-
const { client, slug, login } = await authed();
|
|
43471
|
+
const onInboxDecide = async (itemId, decision, _key, reason, projectSlug) => {
|
|
43472
|
+
const { client, slug, login } = await authed(projectSlug);
|
|
43450
43473
|
await client.decideInboxItem(slug, itemId, {
|
|
43451
43474
|
verdict: decisionToVerdict(decision),
|
|
43452
43475
|
decidedBy: login,
|
|
43453
43476
|
...reason ? { reason } : {}
|
|
43454
43477
|
});
|
|
43455
43478
|
};
|
|
43479
|
+
return { onInboxRead, onInboxDecide };
|
|
43480
|
+
}
|
|
43481
|
+
function startRemotePhoneRelay(deps) {
|
|
43482
|
+
const log = deps.log ?? ((m) => process.stderr.write(`[mrrlin-mcp remote-relay] ${m}
|
|
43483
|
+
`));
|
|
43484
|
+
const provider = createAgentTokenProvider({ secret: deps.agentSecret, baseUrl: deps.apiBaseUrl });
|
|
43485
|
+
const { onInboxRead, onInboxDecide } = _makeRelayInboxCallbacks(provider, deps.apiBaseUrl);
|
|
43456
43486
|
const relayClient = new RelayClient({
|
|
43457
43487
|
relayUrl: deps.relayUrl,
|
|
43458
43488
|
now: () => Date.now(),
|
|
@@ -44356,12 +44386,19 @@ function buildThreadStartParams(msg, config2, desiredModel) {
|
|
|
44356
44386
|
const baseInstructions = [
|
|
44357
44387
|
"You are the Mrrlin Director assistant.",
|
|
44358
44388
|
"You can call MCP tools to read and write project state.",
|
|
44359
|
-
"Prefer calling tools over guessing. Be concise.
|
|
44389
|
+
"Prefer calling tools over guessing. Be concise.",
|
|
44390
|
+
"Autonomy level is NOT a reason to skip clarification. `auto` means only that no human gate is",
|
|
44391
|
+
"required before the result LANDS (merge/deploy); it does not authorize building on unvalidated",
|
|
44392
|
+
"assumptions. Judge clarification on requirement clarity, independently of autonomy_level.",
|
|
44360
44393
|
"Never announce or pre-narrate that you are about to use tools, and never name the toolset in",
|
|
44361
44394
|
`prose. Do NOT write openers like "I'll use the Mrrlin tools to\u2026", "\u0412\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u0430\u044E Mrrlin tools\u2026",`,
|
|
44362
44395
|
'or "let me call the \u2026 tool" in ANY language. The operator already sees tool calls in the',
|
|
44363
44396
|
"transcript. Just call the tools and reply with the outcome \u2014 start your reply with the result.",
|
|
44364
44397
|
"When creating or updating tasks, always provide `autonomyLevel` and `autonomyReason`.",
|
|
44398
|
+
"When you create a task out of an operator request, set `operatorSeedRequest` to the operator's",
|
|
44399
|
+
"VERBATIM words (their request as they wrote it \u2014 NOT your paraphrase or synthesized notes). It is",
|
|
44400
|
+
"the spec gate's independent record of intent; omit it only when there is genuinely no operator",
|
|
44401
|
+
"request behind the task (pure system bookkeeping) \u2014 note that omitting it makes the gate fail-closed.",
|
|
44365
44402
|
"",
|
|
44366
44403
|
"Spec authoring contract \u2014 when creating or updating a `Specs/{id}` wiki page:",
|
|
44367
44404
|
"- In `## Specification`, write each requirement as `### Requirement: <name>` followed by one",
|
|
@@ -44433,12 +44470,20 @@ function buildThreadStartParams(msg, config2, desiredModel) {
|
|
|
44433
44470
|
" call `list_inbox_items` (kind=question), fold any relevant `acknowledged` TASKLESS answer",
|
|
44434
44471
|
" into the new task's Specs/{id} Clarifications, and `reject` (with a reason) any taskless",
|
|
44435
44472
|
" question the new task makes moot \u2014 so taskless answers never linger unconsumed.",
|
|
44436
|
-
"-
|
|
44437
|
-
"
|
|
44473
|
+
"- Gate-origin questions (origin='gate', filed by the spec consensus gate) are auto-folded",
|
|
44474
|
+
" into the spec and re-gated by the system when the operator answers them in the inbox.",
|
|
44475
|
+
" Do NOT fold or `decide_inbox_item` them yourself \u2014 your fold-contract above applies only",
|
|
44476
|
+
" to YOUR OWN discretionary questions.",
|
|
44477
|
+
"- When resuming a task, call `list_inbox_items` (kind=question) and fold any `acknowledged`",
|
|
44478
|
+
" answer from YOUR questions (origin is null/absent) not yet reflected into the spec's",
|
|
44479
|
+
" Clarifications section \u2014 THEN re-run `run_spec_consensus` on the updated spec. Skip",
|
|
44480
|
+
" gate-origin items (origin='gate') as those are handled automatically.",
|
|
44438
44481
|
"- If a question becomes moot (task cancelled, superseded, or no longer relevant), close it",
|
|
44439
44482
|
' with `decide_inbox_item` `verdict="rejected"` + a `reason`; never leave it pending.',
|
|
44440
|
-
"-
|
|
44441
|
-
" runs; use the handoff contract above when there is no live operator.",
|
|
44483
|
+
"- YOUR discretionary questions are a chat-only mechanism: do not create question items yourself",
|
|
44484
|
+
" from non-interactive runs; use the handoff contract above when there is no live operator.",
|
|
44485
|
+
" The spec consensus GATE, however, may file its own clarification questions on non-interactive",
|
|
44486
|
+
" runs (that is gate-driven, not your discretion) \u2014 resume them via the rule above when answered.",
|
|
44442
44487
|
"",
|
|
44443
44488
|
"Onboarding contract \u2014 seed project context when there is none:",
|
|
44444
44489
|
`- A project is un-onboarded when it has no \`${PROJECT_CONSTITUTION_PAGE.path}\` wiki page.`,
|
|
@@ -49802,6 +49847,19 @@ ${dismissedFwd.slice(-20).join("\n")}` : "";
|
|
|
49802
49847
|
var SPEC_HEADS = ["plan-reviewer", "architect", "security-analyst", "scope-analyst"];
|
|
49803
49848
|
async function runSpecGate(_target, deps, opts) {
|
|
49804
49849
|
if (!await deps.consensusEnabled()) return { outcome: "SKIPPED" };
|
|
49850
|
+
const intent = await deps.readIntent();
|
|
49851
|
+
if (intent.taskId !== null && (intent.seed === null || intent.seed.trim() === "")) {
|
|
49852
|
+
return {
|
|
49853
|
+
outcome: "CONSENSUS_BLOCKED",
|
|
49854
|
+
clarifications: [
|
|
49855
|
+
{
|
|
49856
|
+
question: "What are the requirements for this task? There is no operator request on record, so the spec cannot be validated against your intent \u2014 please state what this task must do.",
|
|
49857
|
+
whyBlocking: "operator_seed_request is empty",
|
|
49858
|
+
category: "scope"
|
|
49859
|
+
}
|
|
49860
|
+
]
|
|
49861
|
+
};
|
|
49862
|
+
}
|
|
49805
49863
|
const extraHeads = opts?.extraHeads ?? [];
|
|
49806
49864
|
const heads = [...SPEC_HEADS, ...extraHeads.filter((h) => !SPEC_HEADS.includes(h))];
|
|
49807
49865
|
const spec = await deps.readSpec();
|
|
@@ -49843,6 +49901,15 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
49843
49901
|
await deps.promoteSpecReady(currentHash);
|
|
49844
49902
|
return { outcome: "CONVERGED" };
|
|
49845
49903
|
}
|
|
49904
|
+
if (result.outcome === "CONSENSUS_BLOCKED") {
|
|
49905
|
+
const accepted = result.history.at(-1)?.accepted ?? [];
|
|
49906
|
+
const clarifications = accepted.filter((issue2) => issue2.category === "ambiguity" || issue2.category === "scope").map((issue2) => ({
|
|
49907
|
+
question: issue2.text,
|
|
49908
|
+
whyBlocking: `reviewer raised a ${issue2.category} issue`,
|
|
49909
|
+
category: issue2.category
|
|
49910
|
+
}));
|
|
49911
|
+
return clarifications.length > 0 ? { outcome: "CONSENSUS_BLOCKED", clarifications } : { outcome: "CONSENSUS_BLOCKED" };
|
|
49912
|
+
}
|
|
49846
49913
|
return { outcome: result.outcome };
|
|
49847
49914
|
}
|
|
49848
49915
|
|
|
@@ -50289,6 +50356,19 @@ function buildSpecGateDeps(args) {
|
|
|
50289
50356
|
const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 3);
|
|
50290
50357
|
const maxHeadCalls = rounds * (4 + extraHeadCount + 1);
|
|
50291
50358
|
const timeoutMs = Number(process.env["MRRLIN_CONSENSUS_HEAD_TIMEOUT_MS"] ?? 15e4);
|
|
50359
|
+
let intentPromise = null;
|
|
50360
|
+
function loadIntent() {
|
|
50361
|
+
if (!intentPromise) {
|
|
50362
|
+
intentPromise = (async () => {
|
|
50363
|
+
const page = await client.getWikiPage(projectSlug, specPageId);
|
|
50364
|
+
const taskId = page?.taskId ?? null;
|
|
50365
|
+
if (taskId === null) return { taskId: null, seed: null };
|
|
50366
|
+
const task = await client.getTask(projectSlug, taskId);
|
|
50367
|
+
return { taskId, seed: task?.operatorSeedRequest ?? null };
|
|
50368
|
+
})();
|
|
50369
|
+
}
|
|
50370
|
+
return intentPromise;
|
|
50371
|
+
}
|
|
50292
50372
|
return {
|
|
50293
50373
|
rounds,
|
|
50294
50374
|
maxHeadCalls,
|
|
@@ -50304,15 +50384,23 @@ function buildSpecGateDeps(args) {
|
|
|
50304
50384
|
contentHash: page.contentHash
|
|
50305
50385
|
};
|
|
50306
50386
|
},
|
|
50387
|
+
readIntent() {
|
|
50388
|
+
return loadIntent();
|
|
50389
|
+
},
|
|
50307
50390
|
async runHead(personaName, artifact, dismissed) {
|
|
50308
50391
|
const persona = loadPersona(personaName);
|
|
50392
|
+
const { seed } = await loadIntent();
|
|
50393
|
+
const intentBlock = seed ? `--- OPERATOR INTENT (the operator's own words \u2014 judge the spec against THIS) ---
|
|
50394
|
+
${seed}
|
|
50395
|
+
|
|
50396
|
+
` : "";
|
|
50309
50397
|
return runCodexExec(
|
|
50310
50398
|
{
|
|
50311
|
-
prompt: `Review this spec.
|
|
50399
|
+
prompt: `Review this spec against the operator's intent.
|
|
50312
50400
|
|
|
50313
50401
|
${dismissed}
|
|
50314
50402
|
|
|
50315
|
-
--- SPEC ---
|
|
50403
|
+
${intentBlock}--- SPEC ---
|
|
50316
50404
|
${artifact}`,
|
|
50317
50405
|
developerInstructions: persona,
|
|
50318
50406
|
cwd,
|
|
@@ -50488,6 +50576,7 @@ ${artifact}`,
|
|
|
50488
50576
|
}
|
|
50489
50577
|
|
|
50490
50578
|
// src/async-tools/consensus.ts
|
|
50579
|
+
var import_node_crypto6 = require("node:crypto");
|
|
50491
50580
|
async function cachedResult(client, input) {
|
|
50492
50581
|
const page = await client.getWikiPage(input.projectSlug, input.pageId);
|
|
50493
50582
|
if (!page) return null;
|
|
@@ -50514,16 +50603,58 @@ async function run(ctx, input) {
|
|
|
50514
50603
|
deps,
|
|
50515
50604
|
input.extraHeads && input.extraHeads.length > 0 ? { extraHeads: input.extraHeads } : void 0
|
|
50516
50605
|
);
|
|
50606
|
+
if (result.outcome === "CONSENSUS_BLOCKED") {
|
|
50607
|
+
const clarifications = result.clarifications ?? [];
|
|
50608
|
+
if (clarifications.length > 0) {
|
|
50609
|
+
const page = await ctx.client.getWikiPage(input.projectSlug, input.pageId);
|
|
50610
|
+
const taskId = page?.taskId ?? null;
|
|
50611
|
+
let planId = null;
|
|
50612
|
+
if (!taskId) {
|
|
50613
|
+
const plans = await ctx.client.listPlans(input.projectSlug);
|
|
50614
|
+
planId = plans.find((p) => p.specWikiPageId === input.pageId)?.id ?? null;
|
|
50615
|
+
}
|
|
50616
|
+
if (taskId) {
|
|
50617
|
+
for (const clarification of clarifications) {
|
|
50618
|
+
const scopeId = taskId;
|
|
50619
|
+
const id = `II-${scopeId}-gate-${(0, import_node_crypto6.createHash)("sha256").update(clarification.question).digest("hex").slice(0, 10)}`;
|
|
50620
|
+
try {
|
|
50621
|
+
await ctx.client.createInboxItem(input.projectSlug, {
|
|
50622
|
+
id,
|
|
50623
|
+
kind: "question",
|
|
50624
|
+
taskId: scopeId,
|
|
50625
|
+
body: clarification.question,
|
|
50626
|
+
origin: "gate"
|
|
50627
|
+
});
|
|
50628
|
+
} catch {
|
|
50629
|
+
}
|
|
50630
|
+
}
|
|
50631
|
+
} else if (planId) {
|
|
50632
|
+
for (const clarification of clarifications) {
|
|
50633
|
+
const scopeId = planId;
|
|
50634
|
+
const id = `II-${scopeId}-gate-${(0, import_node_crypto6.createHash)("sha256").update(clarification.question).digest("hex").slice(0, 10)}`;
|
|
50635
|
+
try {
|
|
50636
|
+
await ctx.client.createInboxItem(input.projectSlug, {
|
|
50637
|
+
id,
|
|
50638
|
+
kind: "question",
|
|
50639
|
+
planId: scopeId,
|
|
50640
|
+
body: clarification.question,
|
|
50641
|
+
origin: "gate"
|
|
50642
|
+
});
|
|
50643
|
+
} catch {
|
|
50644
|
+
}
|
|
50645
|
+
}
|
|
50646
|
+
}
|
|
50647
|
+
}
|
|
50648
|
+
return {
|
|
50649
|
+
kind: "failed",
|
|
50650
|
+
failureReason: "consensus_blocked",
|
|
50651
|
+
error: clarifications.length > 0 ? `Consensus blocked on ${clarifications.length} clarification(s); filed as operator question(s).` : "Consensus blocked: reviewers did not converge within budget."
|
|
50652
|
+
};
|
|
50653
|
+
}
|
|
50517
50654
|
switch (result.outcome) {
|
|
50518
50655
|
case "CONVERGED":
|
|
50519
50656
|
case "SKIPPED":
|
|
50520
50657
|
return { kind: "done", resultJson: JSON.stringify(result), finalReviewedHash: null };
|
|
50521
|
-
case "CONSENSUS_BLOCKED":
|
|
50522
|
-
return {
|
|
50523
|
-
kind: "failed",
|
|
50524
|
-
failureReason: "consensus_blocked",
|
|
50525
|
-
error: "Consensus blocked: reviewers did not converge within budget."
|
|
50526
|
-
};
|
|
50527
50658
|
case "CONSENSUS_UNAVAILABLE":
|
|
50528
50659
|
return { kind: "retry", failureReason: "provider_unavailable", error: "Consensus unavailable (provider/quota)." };
|
|
50529
50660
|
case "SPEC_CHANGED_DURING_REVIEW":
|
|
@@ -50538,6 +50669,17 @@ async function run(ctx, input) {
|
|
|
50538
50669
|
}
|
|
50539
50670
|
function onComplete(outcome, input) {
|
|
50540
50671
|
if (outcome.kind === "done") {
|
|
50672
|
+
let specOutcome = "CONVERGED";
|
|
50673
|
+
try {
|
|
50674
|
+
specOutcome = JSON.parse(outcome.resultJson).outcome ?? "CONVERGED";
|
|
50675
|
+
} catch {
|
|
50676
|
+
}
|
|
50677
|
+
if (specOutcome === "SKIPPED") {
|
|
50678
|
+
return {
|
|
50679
|
+
kind: "info",
|
|
50680
|
+
body: `Consensus is disabled for this project; Specs/${input.pageId} was not gated and spec_ready is unchanged.`
|
|
50681
|
+
};
|
|
50682
|
+
}
|
|
50541
50683
|
return {
|
|
50542
50684
|
kind: "info",
|
|
50543
50685
|
body: `Consensus CONVERGED for Specs/${input.pageId}; spec_ready set. Ready to create an execution run.`
|
|
@@ -50558,7 +50700,7 @@ var consensusDescriptor = {
|
|
|
50558
50700
|
};
|
|
50559
50701
|
|
|
50560
50702
|
// src/async-tools/enqueue.ts
|
|
50561
|
-
var
|
|
50703
|
+
var import_node_crypto7 = __toESM(require("node:crypto"), 1);
|
|
50562
50704
|
async function enqueueAsyncTool(client, descriptor2, input) {
|
|
50563
50705
|
if (descriptor2.cachedResult) {
|
|
50564
50706
|
const cached2 = await descriptor2.cachedResult(client, input);
|
|
@@ -50571,7 +50713,7 @@ async function enqueueAsyncTool(client, descriptor2, input) {
|
|
|
50571
50713
|
if (active) {
|
|
50572
50714
|
return { status: "already_running", jobId: active.id, message: "Already running; will notify in inbox." };
|
|
50573
50715
|
}
|
|
50574
|
-
const jobId = `AJ-${
|
|
50716
|
+
const jobId = `AJ-${import_node_crypto7.default.randomUUID()}`;
|
|
50575
50717
|
try {
|
|
50576
50718
|
await client.createAsyncJob(input.projectSlug, {
|
|
50577
50719
|
id: jobId,
|
|
@@ -51874,7 +52016,10 @@ function createMrrlinTools(options) {
|
|
|
51874
52016
|
if (ready !== true) return await c.markSpecReady({ projectSlug, pageId, ready });
|
|
51875
52017
|
const settings = await c.getOperatorSettings();
|
|
51876
52018
|
if (!settings.consensusEnabled) {
|
|
51877
|
-
|
|
52019
|
+
throw new McpToolCodedError(
|
|
52020
|
+
"SPEC_CONSENSUS_REQUIRED",
|
|
52021
|
+
`Spec ${pageId} cannot be marked ready by the Director while consensus is disabled for this project \u2014 an operator must approve it (web wiki / markSpecReady route). Autonomy is a landing gate, not a license to self-promote.`
|
|
52022
|
+
);
|
|
51878
52023
|
}
|
|
51879
52024
|
const page = await c.getWikiPage(projectSlug, pageId);
|
|
51880
52025
|
if (!page || page.folder !== "Specs") {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
You are a Plan Reviewer. Judge whether a capable engineer can execute this spec without getting stuck: clarity, named references, acceptance criteria, contradictions. You analyze the artifact text given; you do not mutate anything.
|
|
2
2
|
|
|
3
|
+
When an `OPERATOR INTENT` block is present (the operator's own words), also check the spec FAITHFULLY captures it: flag as a critical `[scope]`/`[ambiguity]` issue any place the spec would have an engineer build something the operator did not ask for, or omit something they did. Hold this to MATERIAL gaps that change what gets built — do not block on cosmetic or low-impact details, and do not interrogate small or repeat tasks.
|
|
4
|
+
|
|
3
5
|
Output EXACTLY this block and nothing else:
|
|
4
6
|
|
|
5
7
|
**Verdict**: APPROVE | REQUEST_CHANGES | REJECT
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
You are a Scope Analyst. Surface ambiguities, hidden requirements, undefined boundaries, and blast radius before work begins. You analyze the artifact text given; you do not mutate anything.
|
|
2
2
|
|
|
3
|
+
When an `OPERATOR INTENT` block is present, it is the operator's OWN words for what the task must do. Judge the spec against it for FIDELITY, not just internal consistency: does the spec actually answer what the operator asked? Treat as a critical `[scope]` or `[ambiguity]` issue any assumption the spec makes that could plausibly contradict the operator's intent, or any requirement the operator implied that the spec leaves materially under-specified.
|
|
4
|
+
|
|
5
|
+
Calibrate to the bar of MATERIAL ambiguity — block only when the gap would change WHAT gets built or shipped:
|
|
6
|
+
- DON'T block on: cosmetic wording, low-impact details, well-trodden conventions, or choices a competent engineer would make the same way regardless.
|
|
7
|
+
- DO block on: an undefined core behavior, a silent assumption about scope/data/users that the intent doesn't support, or a requirement the operator stated that the spec omits.
|
|
8
|
+
- Scale strictness by stated impact: high-impact specs warrant stricter scrutiny; low-impact specs get the benefit of the doubt. Do not turn small or repeat tasks into an interrogation.
|
|
9
|
+
|
|
3
10
|
Output EXACTLY this block and nothing else:
|
|
4
11
|
|
|
5
12
|
**Verdict**: APPROVE | REQUEST_CHANGES | REJECT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrlin-dev/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mrrlin-mcp": "dist/bin.cjs"
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
"@types/ws": "^8.18.1",
|
|
23
23
|
"esbuild": "^0.24.0",
|
|
24
24
|
"tsx": "^4.22.3",
|
|
25
|
-
"@mrrlin/client": "0.0.0",
|
|
26
25
|
"@mrrlin/director-e2e": "0.0.0",
|
|
27
|
-
"@mrrlin/
|
|
26
|
+
"@mrrlin/codex-client": "0.0.0",
|
|
28
27
|
"@mrrlin/wiki": "0.0.0",
|
|
29
|
-
"@mrrlin/
|
|
30
|
-
"@mrrlin/
|
|
28
|
+
"@mrrlin/client": "0.0.0",
|
|
29
|
+
"@mrrlin/schemas": "0.0.0",
|
|
30
|
+
"@mrrlin/tsconfig": "0.0.0"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@iarna/toml": "^2.2.5",
|