@mrrlin-dev/mcp 0.2.5 → 0.3.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/dist/bin.cjs CHANGED
@@ -18671,11 +18671,11 @@ var require_dist = __commonJS({
18671
18671
  });
18672
18672
 
18673
18673
  // src/bin.ts
18674
- var import_node_path20 = __toESM(require("node:path"), 1);
18674
+ var import_node_path19 = __toESM(require("node:path"), 1);
18675
18675
  var import_node_child_process7 = require("node:child_process");
18676
18676
  var import_node_fs14 = require("node:fs");
18677
- var import_node_os10 = require("node:os");
18678
- var import_node_path21 = __toESM(require("node:path"), 1);
18677
+ var import_node_os11 = require("node:os");
18678
+ var import_node_path20 = __toESM(require("node:path"), 1);
18679
18679
 
18680
18680
  // src/install-codex.ts
18681
18681
  var import_promises = __toESM(require("node:fs/promises"), 1);
@@ -18759,7 +18759,7 @@ async function installCodex(options) {
18759
18759
  const configPath = import_node_path.default.join(codexHome, "config.toml");
18760
18760
  const distribution = await detectInstallDistribution(options);
18761
18761
  await import_promises.default.mkdir(codexHome, { recursive: true, mode: 448 });
18762
- const writePrompts = async () => installPrompts(codexHome, options.prompts ?? []);
18762
+ const writePrompts = async () => installPrompts(codexHome, options.prompts ?? [], options.forcePrompts === true);
18763
18763
  if (!await pathExists(configPath)) {
18764
18764
  await import_promises.default.writeFile(configPath, renderBlock(options.binPath, distribution).replace(/^\n/, ""), { mode: 384 });
18765
18765
  return { action: "created", configPath, prompts: await writePrompts() };
@@ -18799,7 +18799,7 @@ async function installCodex(options) {
18799
18799
  await import_promises.default.writeFile(realPath, appended, { mode: 384 });
18800
18800
  return { action: "appended", configPath: realPath, prompts: await writePrompts() };
18801
18801
  }
18802
- async function installPrompts(codexHome, prompts) {
18802
+ async function installPrompts(codexHome, prompts, forcePrompts) {
18803
18803
  if (prompts.length === 0) return [];
18804
18804
  const promptsDir2 = import_node_path.default.join(codexHome, "prompts");
18805
18805
  await import_promises.default.mkdir(promptsDir2, { recursive: true, mode: 448 });
@@ -18812,12 +18812,21 @@ async function installPrompts(codexHome, prompts) {
18812
18812
  } catch {
18813
18813
  existing = null;
18814
18814
  }
18815
+ if (existing === null) {
18816
+ await import_promises.default.writeFile(promptPath, prompt.content, { mode: 384 });
18817
+ out.push({ name: prompt.name, action: "created", promptPath });
18818
+ continue;
18819
+ }
18815
18820
  if (existing === prompt.content) {
18816
18821
  out.push({ name: prompt.name, action: "noop", promptPath });
18817
18822
  continue;
18818
18823
  }
18824
+ if (!forcePrompts) {
18825
+ out.push({ name: prompt.name, action: "skipped-modified", promptPath });
18826
+ continue;
18827
+ }
18819
18828
  await import_promises.default.writeFile(promptPath, prompt.content, { mode: 384 });
18820
- out.push({ name: prompt.name, action: existing === null ? "created" : "updated", promptPath });
18829
+ out.push({ name: prompt.name, action: "updated", promptPath });
18821
18830
  }
18822
18831
  return out;
18823
18832
  }
@@ -18838,11 +18847,11 @@ ${block}`;
18838
18847
  }
18839
18848
 
18840
18849
  // src/director-bridge.ts
18841
- var import_node_fs11 = __toESM(require("node:fs"), 1);
18850
+ var import_node_fs10 = __toESM(require("node:fs"), 1);
18842
18851
  var import_node_http = __toESM(require("node:http"), 1);
18843
18852
  var import_node_os7 = __toESM(require("node:os"), 1);
18844
- var import_node_path12 = __toESM(require("node:path"), 1);
18845
- var import_node_child_process4 = require("node:child_process");
18853
+ var import_node_path11 = __toESM(require("node:path"), 1);
18854
+ var import_node_child_process3 = require("node:child_process");
18846
18855
  var import_node_crypto4 = __toESM(require("node:crypto"), 1);
18847
18856
 
18848
18857
  // ../../node_modules/.pnpm/ws@8.21.0/node_modules/ws/wrapper.mjs
@@ -19257,13 +19266,15 @@ async function acquireThread(codex, opts) {
19257
19266
  }
19258
19267
 
19259
19268
  // ../../packages/codex-client/dist/render-context-bundle.js
19269
+ var DEFAULT_SPEC_BUDGET_CHARS = 6e3;
19270
+ var DEFAULT_ARTIFACT_PAYLOAD_BUDGET_CHARS = 400;
19260
19271
  function renderContextBundle(input) {
19261
19272
  const sections = [];
19262
19273
  sections.push(renderTaskSummary(input.task));
19263
- sections.push(renderSpec(input.specMarkdown));
19274
+ sections.push(renderSpec(input.specMarkdown, input.compact));
19264
19275
  sections.push(renderRunHistory(input.runs, input.openRunId));
19265
19276
  sections.push(renderOpenRun(input.openRunId));
19266
- sections.push(renderArtifacts(input.artifacts, input.omittedArtifactCount ?? 0));
19277
+ sections.push(renderArtifacts(input.artifacts, input.omittedArtifactCount ?? 0, input.compact));
19267
19278
  sections.push(renderAutonomyEvents(input.autonomyEvents));
19268
19279
  return sections.join("\n\n");
19269
19280
  }
@@ -19281,11 +19292,55 @@ function renderTaskSummary(task) {
19281
19292
  }
19282
19293
  return lines2.join("\n");
19283
19294
  }
19284
- function renderSpec(specMarkdown) {
19295
+ function renderSpec(specMarkdown, compact) {
19285
19296
  if (!specMarkdown)
19286
19297
  return "## Spec\n(none)";
19287
- return `## Spec
19298
+ if (!compact)
19299
+ return `## Spec
19288
19300
  ${specMarkdown}`;
19301
+ const budget = compact.specBudgetChars ?? DEFAULT_SPEC_BUDGET_CHARS;
19302
+ if (specMarkdown.length <= budget)
19303
+ return `## Spec
19304
+ ${specMarkdown}`;
19305
+ return `## Spec
19306
+ ${compactSpecMarkdown(specMarkdown, budget, compact.projectSlug ?? null, compact.specWikiPageId ?? null)}`;
19307
+ }
19308
+ function compactSpecMarkdown(markdown, totalBudget, projectSlug, specWikiPageId) {
19309
+ const lines2 = markdown.split(/\r?\n/);
19310
+ const sections = [{ heading: "", body: [] }];
19311
+ for (const line of lines2) {
19312
+ if (/^#{1,6}\s/.test(line)) {
19313
+ sections.push({ heading: line, body: [] });
19314
+ } else {
19315
+ sections[sections.length - 1].body.push(line);
19316
+ }
19317
+ }
19318
+ const sectionCount = Math.max(1, sections.length);
19319
+ const perSectionBudget = Math.max(120, Math.floor(totalBudget / sectionCount));
19320
+ const out = [];
19321
+ for (const section of sections) {
19322
+ const bodyText = section.body.join("\n").replace(/^\s+|\s+$/g, "");
19323
+ if (section.heading)
19324
+ out.push(section.heading);
19325
+ if (!bodyText)
19326
+ continue;
19327
+ if (bodyText.length <= perSectionBudget) {
19328
+ out.push(bodyText);
19329
+ } else {
19330
+ const kept = bodyText.slice(0, perSectionBudget);
19331
+ const omitted = bodyText.length - perSectionBudget;
19332
+ out.push(`${kept}
19333
+ \u2026(${omitted} chars omitted; retrieve full spec for the rest)`);
19334
+ }
19335
+ }
19336
+ if (projectSlug && specWikiPageId) {
19337
+ out.push("");
19338
+ out.push(`> Compact spec. Source: mrrlin://wiki/${projectSlug}/${specWikiPageId} \u2014 call retrieve_context with this handle before any irreversible decision.`);
19339
+ } else {
19340
+ out.push("");
19341
+ out.push("> Compact spec. Retrieve the Specs/{id} wiki page via get_wiki_page before any irreversible decision.");
19342
+ }
19343
+ return out.join("\n");
19289
19344
  }
19290
19345
  function renderRunHistory(runs, openRunId) {
19291
19346
  if (runs.length === 0)
@@ -19301,13 +19356,14 @@ function renderOpenRun(openRunId) {
19301
19356
  return `## Open run
19302
19357
  ${openRunId ?? "(none)"}`;
19303
19358
  }
19304
- function renderArtifacts(artifacts, omittedCount) {
19359
+ function renderArtifacts(artifacts, omittedCount, compact) {
19305
19360
  if (artifacts.length === 0 && omittedCount === 0)
19306
19361
  return "## Artifacts\n(none)";
19307
19362
  const lines2 = ["## Artifacts"];
19308
19363
  if (omittedCount > 0) {
19309
19364
  lines2.push(`(+${omittedCount} older artifact${omittedCount === 1 ? "" : "s"} omitted)`);
19310
19365
  }
19366
+ let truncatedAny = false;
19311
19367
  for (const artifact of artifacts) {
19312
19368
  if (artifact.kind === "file") {
19313
19369
  const p = artifact.payload;
@@ -19316,7 +19372,16 @@ function renderArtifacts(artifacts, omittedCount) {
19316
19372
  continue;
19317
19373
  }
19318
19374
  }
19319
- lines2.push(`- ${artifact.createdAt} [${artifact.kind}] run=${artifact.runId} ${JSON.stringify(artifact.payload)}`);
19375
+ const payloadText = JSON.stringify(artifact.payload);
19376
+ const budget = compact?.artifactPayloadBudgetChars ?? DEFAULT_ARTIFACT_PAYLOAD_BUDGET_CHARS;
19377
+ const willTruncate = !!compact && payloadText.length > budget;
19378
+ const rendered = willTruncate ? `${payloadText.slice(0, budget)}\u2026(${payloadText.length - budget} chars omitted)` : payloadText;
19379
+ if (willTruncate)
19380
+ truncatedAny = true;
19381
+ lines2.push(`- ${artifact.createdAt} [${artifact.kind}] run=${artifact.runId} ${rendered}`);
19382
+ }
19383
+ if (compact && (omittedCount > 0 || truncatedAny)) {
19384
+ lines2.push("> Compact artifacts. Use retrieve_context with a `mrrlin://run/{projectSlug}/{runId}/artifacts` handle (substitute the slug and run id from the lines above) to load a run's full artifact history.");
19320
19385
  }
19321
19386
  return lines2.join("\n");
19322
19387
  }
@@ -33892,7 +33957,6 @@ var openApiOperationIds = {
33892
33957
  listExecutionArtifacts: "listExecutionArtifacts",
33893
33958
  listExecutionRuns: "listExecutionRuns",
33894
33959
  listGithubInstallations: "listGithubInstallations",
33895
- listGithubWebhookDeliveries: "listGithubWebhookDeliveries",
33896
33960
  listGithubWorkflows: "listGithubWorkflows",
33897
33961
  listPlans: "listPlans",
33898
33962
  listProjects: "listProjects",
@@ -33909,7 +33973,6 @@ var openApiOperationIds = {
33909
33973
  decideInboxItem: "decideInboxItem",
33910
33974
  markInboxItemRead: "markInboxItemRead",
33911
33975
  markAllInboxItemsRead: "markAllInboxItemsRead",
33912
- receiveGithubWebhook: "receiveGithubWebhook",
33913
33976
  searchKnowledge: "searchKnowledge",
33914
33977
  updateExecutionRun: "updateExecutionRun",
33915
33978
  upsertProjectSchedulerPolicy: "upsertProjectSchedulerPolicy",
@@ -33926,6 +33989,10 @@ var openApiOperationIds = {
33926
33989
  listAgentCredentials: "listAgentCredentials",
33927
33990
  revokeAgentCredential: "revokeAgentCredential",
33928
33991
  exchangeAgentToken: "exchangeAgentToken",
33992
+ // ADR 0016 — operator-scoped credential routes.
33993
+ issueOperatorAgentCredential: "issueOperatorAgentCredential",
33994
+ listOperatorAgentCredentials: "listOperatorAgentCredentials",
33995
+ revokeOperatorAgentCredential: "revokeOperatorAgentCredential",
33929
33996
  createArtifactFile: "createArtifactFile",
33930
33997
  completeArtifactFile: "completeArtifactFile",
33931
33998
  getArtifactFile: "getArtifactFile",
@@ -33949,7 +34016,12 @@ var operatorSessionSchema = external_exports.object({
33949
34016
  login: external_exports.string().min(1),
33950
34017
  projectSlug: external_exports.string().min(1),
33951
34018
  provider: external_exports.literal("github"),
33952
- repo: external_exports.string().min(1).nullable()
34019
+ repo: external_exports.string().min(1).nullable(),
34020
+ // ADR 0016: `scope` distinguishes project-pinned agent tokens (legacy, default) from
34021
+ // operator-scoped agent tokens. Only relevant for kind:"agent". `web` sessions ignore it.
34022
+ // Default 'project' for backward-compat: tokens minted before this field existed parse
34023
+ // cleanly into the project branch of the middleware (which mirrors today's behavior).
34024
+ scope: external_exports.enum(["project", "operator"]).default("project")
33953
34025
  });
33954
34026
  var AGENT_SESSION_TTL_MS = 1e3 * 60 * 60;
33955
34027
 
@@ -34251,15 +34323,6 @@ var mrrlinGithubWorkflowDispatchSchema = external_exports.object({
34251
34323
  ref: external_exports.string().min(1),
34252
34324
  workflowId: external_exports.string().min(1)
34253
34325
  });
34254
- var mrrlinGithubWebhookDeliverySchema = external_exports.object({
34255
- action: external_exports.string().min(1).nullable(),
34256
- deliveryId: external_exports.string().min(1),
34257
- eventType: external_exports.string().min(1),
34258
- installationId: external_exports.number().int().positive().nullable(),
34259
- projectSlug: mrrlinProjectSlugSchema.nullable(),
34260
- receivedAt: external_exports.string().datetime(),
34261
- repositoryFullName: external_exports.string().regex(/^[^/]+\/[^/]+$/).nullable()
34262
- });
34263
34326
  var mrrlinProjectSummarySchema = external_exports.object({
34264
34327
  displayName: external_exports.string().min(1),
34265
34328
  emoji: mrrlinProjectEmojiSchema,
@@ -34485,14 +34548,6 @@ var githubBindProjectRepoRequestSchema = external_exports.object({
34485
34548
  var githubBindProjectRepoResponseSchema = external_exports.object({
34486
34549
  data: mrrlinProjectSummarySchema
34487
34550
  });
34488
- var githubWebhookReceiptResponseSchema = external_exports.object({
34489
- data: external_exports.object({
34490
- deliveryId: external_exports.string().min(1),
34491
- eventType: external_exports.string().min(1),
34492
- linkedProjectSlugs: external_exports.array(mrrlinProjectSlugSchema),
34493
- stored: external_exports.boolean()
34494
- })
34495
- });
34496
34551
  var githubCreateBranchRequestSchema = external_exports.object({
34497
34552
  baseBranch: external_exports.string().min(1).default("main"),
34498
34553
  branchName: external_exports.string().min(1)
@@ -34542,9 +34597,6 @@ var githubLatestDeploymentQuerySchema = external_exports.object({
34542
34597
  var githubLatestDeploymentResponseSchema = external_exports.object({
34543
34598
  data: external_exports.object({ deploymentId: external_exports.number().int(), environmentUrl: external_exports.string() }).nullable()
34544
34599
  });
34545
- var githubWebhookDeliveryListResponseSchema = external_exports.object({
34546
- data: external_exports.array(mrrlinGithubWebhookDeliverySchema)
34547
- });
34548
34600
  var githubMintPushTokenRequestSchema = external_exports.object({
34549
34601
  runId: mrrlinExecutionRunIdSchema,
34550
34602
  leaseId: external_exports.string().min(1)
@@ -34757,13 +34809,24 @@ var agentCredentialSummarySchema = external_exports.object({
34757
34809
  createdAt: external_exports.string().datetime(),
34758
34810
  lastUsedAt: external_exports.string().datetime().nullable(),
34759
34811
  revokedAt: external_exports.string().datetime().nullable(),
34760
- expiresAt: external_exports.string().datetime().nullable()
34812
+ expiresAt: external_exports.string().datetime().nullable(),
34813
+ // ADR 0016: surface scope so UI can label rows; seedProjectSlug is audit-only ("issued from
34814
+ // project X"). Both default to 'project'/null for back-compat with legacy rows.
34815
+ scope: external_exports.enum(["project", "operator"]).default("project"),
34816
+ seedProjectSlug: external_exports.string().nullable().default(null)
34761
34817
  });
34762
34818
  var agentCredentialListResponseSchema = external_exports.object({
34763
34819
  data: external_exports.array(agentCredentialSummarySchema)
34764
34820
  });
34765
34821
  var exchangeAgentTokenRequestSchema = external_exports.object({
34766
- secret: external_exports.string().min(1)
34822
+ // ADR 0016 §3 no-oracle: the exchange schema MUST accept any string shape for `secret` and
34823
+ // `projectSlug`. Empty/whitespace/missing inputs are handled by the handler's constant-work
34824
+ // 5-step loop and flow into the same uniform 401 path as wrong-secret. A schema-level 400
34825
+ // would leak (attacker can distinguish "malformed input" from "wrong secret" before the
34826
+ // handler runs). Validation of structural content (e.g. selector parseability for `secret`)
34827
+ // happens inside the handler against the dummy hash.
34828
+ secret: external_exports.string(),
34829
+ projectSlug: external_exports.string().optional()
34767
34830
  });
34768
34831
  var exchangeAgentTokenResponseSchema = external_exports.object({
34769
34832
  token: external_exports.string().min(1),
@@ -34978,10 +35041,10 @@ var AgentTokenProviderError = class extends Error {
34978
35041
  this.code = code;
34979
35042
  }
34980
35043
  };
34981
- async function defaultExchange(secret, baseUrl) {
35044
+ async function defaultExchange(secret, baseUrl, projectSlug) {
34982
35045
  const url2 = `${baseUrl.replace(/\/$/, "")}/auth/agent/token`;
34983
35046
  const response = await fetch(url2, {
34984
- body: JSON.stringify({ secret }),
35047
+ body: JSON.stringify({ secret, ...projectSlug ? { projectSlug } : {} }),
34985
35048
  headers: { "content-type": "application/json" },
34986
35049
  method: "POST"
34987
35050
  });
@@ -35006,22 +35069,28 @@ function createAgentTokenProvider(opts) {
35006
35069
  const { baseUrl, secret } = opts;
35007
35070
  const exchange = opts.exchange ?? defaultExchange;
35008
35071
  const now = opts.now ?? (() => Date.now());
35009
- let cached2 = null;
35010
- function isValid() {
35011
- if (!cached2)
35072
+ const LEGACY_KEY = "__mrrlin_agent_no_slug__";
35073
+ const cacheByKey = /* @__PURE__ */ new Map();
35074
+ function cacheKey(projectSlug) {
35075
+ return projectSlug ?? LEGACY_KEY;
35076
+ }
35077
+ function isValid(entry) {
35078
+ if (!entry)
35012
35079
  return false;
35013
- const expiresAtMs = new Date(cached2.expiresAt).getTime();
35080
+ const expiresAtMs = new Date(entry.expiresAt).getTime();
35014
35081
  return now() < expiresAtMs - REFRESH_BUFFER_MS;
35015
35082
  }
35016
- async function getSession() {
35017
- if (!isValid()) {
35018
- cached2 = await exchange(secret, baseUrl);
35083
+ async function getSession(projectSlug) {
35084
+ const key = cacheKey(projectSlug);
35085
+ let entry = cacheByKey.get(key);
35086
+ if (!isValid(entry)) {
35087
+ entry = await exchange(secret, baseUrl, projectSlug);
35088
+ cacheByKey.set(key, entry);
35019
35089
  }
35020
- const c = cached2;
35021
- return { token: c.token, projectSlug: c.projectSlug, login: c.login };
35090
+ return { token: entry.token, projectSlug: entry.projectSlug, login: entry.login };
35022
35091
  }
35023
- async function getToken() {
35024
- return (await getSession()).token;
35092
+ async function getToken(projectSlug) {
35093
+ return (await getSession(projectSlug)).token;
35025
35094
  }
35026
35095
  return { getToken, getSession };
35027
35096
  }
@@ -35319,10 +35388,6 @@ function createMrrlinClient(config2) {
35319
35388
  const body = await request(`${projectPath(projectSlug)}/github/workflows`);
35320
35389
  return githubWorkflowListResponseSchema.parse(body).data;
35321
35390
  },
35322
- async listGithubWebhookDeliveries(projectSlug) {
35323
- const body = await request(`${projectPath(projectSlug)}/github/webhook-deliveries`);
35324
- return githubWebhookDeliveryListResponseSchema.parse(body).data;
35325
- },
35326
35391
  async listProjects() {
35327
35392
  const body = await request("/projects");
35328
35393
  return projectSummaryListResponseSchema.parse(body).data;
@@ -35754,6 +35819,24 @@ function createMrrlinClient(config2) {
35754
35819
  method: "POST"
35755
35820
  });
35756
35821
  return exchangeAgentTokenResponseSchema.parse(body);
35822
+ },
35823
+ // ── ADR 0016 — operator-scoped credential client methods ─────────────────
35824
+ async issueOperatorAgentCredential(input) {
35825
+ const body = await request("/operator/agent-credentials", {
35826
+ body: JSON.stringify(input),
35827
+ headers: { "content-type": "application/json" },
35828
+ method: "POST"
35829
+ });
35830
+ return agentCredentialResponseSchema.parse(body.data);
35831
+ },
35832
+ async listOperatorAgentCredentials() {
35833
+ const body = await request("/operator/agent-credentials");
35834
+ return agentCredentialListResponseSchema.parse(body).data;
35835
+ },
35836
+ async revokeOperatorAgentCredential(credentialId) {
35837
+ await request(`/operator/agent-credentials/${encodeURIComponent(credentialId)}`, {
35838
+ method: "DELETE"
35839
+ });
35757
35840
  }
35758
35841
  };
35759
35842
  }
@@ -36427,7 +36510,17 @@ async function driveRun(deps, projectSlug, runId) {
36427
36510
  }
36428
36511
  const leaseId = claimed.leaseId;
36429
36512
  const task = await deps.client.getTask(projectSlug, claimed.taskId);
36430
- const bundle = renderContextBundle(await gatherContext(deps.client, projectSlug, task, runId));
36513
+ const compactEnabled = (process.env.MRRLIN_MCP_COMPACT_DISABLE ?? "").trim() !== "1";
36514
+ const context = await gatherContext(deps.client, projectSlug, task, runId);
36515
+ const bundle = renderContextBundle(
36516
+ compactEnabled ? {
36517
+ ...context,
36518
+ compact: {
36519
+ projectSlug,
36520
+ specWikiPageId: task.specWikiPageId ?? null
36521
+ }
36522
+ } : context
36523
+ );
36431
36524
  const checkout = deps.checkoutRegistry?.get(projectSlug) ?? null;
36432
36525
  let sandbox = "read-only";
36433
36526
  let turnCwd = deps.codexCwd ?? null;
@@ -40918,75 +41011,10 @@ var CheckoutRegistry = class {
40918
41011
  }
40919
41012
  };
40920
41013
 
40921
- // src/checkout-scan.ts
40922
- var import_node_fs8 = require("node:fs");
40923
- var import_node_path9 = require("node:path");
40924
- var import_node_child_process3 = require("node:child_process");
40925
-
40926
- // src/git-remote-match.ts
40927
- function normalizeRemote(url2) {
40928
- const trimmed = url2.trim().replace(/\.git$/i, "");
40929
- const scp = /^[^@]+@[^:]+:(.+)$/.exec(trimmed);
40930
- const pathPart = scp ? scp[1] : (() => {
40931
- try {
40932
- return new URL(trimmed).pathname.replace(/^\/+/, "");
40933
- } catch {
40934
- return null;
40935
- }
40936
- })();
40937
- if (!pathPart) return null;
40938
- const segs = pathPart.split("/").filter(Boolean);
40939
- if (segs.length < 2) return null;
40940
- return `${segs[segs.length - 2]}/${segs[segs.length - 1]}`.toLowerCase();
40941
- }
40942
- function remoteMatchesRepo(remoteUrl, repoFullName) {
40943
- const a = normalizeRemote(remoteUrl);
40944
- return a !== null && a === repoFullName.trim().toLowerCase();
40945
- }
40946
-
40947
- // src/checkout-scan.ts
40948
- var SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", ".cache", "Library", ".Trash"]);
40949
- function originUrl(dir) {
40950
- try {
40951
- return (0, import_node_child_process3.execFileSync)("git", ["-C", dir, "remote", "get-url", "origin"], {
40952
- encoding: "utf8",
40953
- stdio: ["ignore", "pipe", "ignore"]
40954
- }).trim();
40955
- } catch {
40956
- return null;
40957
- }
40958
- }
40959
- function scanForCheckouts(roots, repoFullName, opts = {}) {
40960
- const maxDepth = opts.maxDepth ?? 3;
40961
- const out = [];
40962
- const walk = (dir, depth) => {
40963
- if (depth > maxDepth) return;
40964
- const url2 = originUrl(dir);
40965
- if (url2) {
40966
- if (remoteMatchesRepo(url2, repoFullName)) out.push(dir);
40967
- return;
40968
- }
40969
- let entries = [];
40970
- try {
40971
- entries = (0, import_node_fs8.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && !SKIP.has(e.name)).map((e) => e.name);
40972
- } catch {
40973
- return;
40974
- }
40975
- for (const name of entries) walk((0, import_node_path9.join)(dir, name), depth + 1);
40976
- };
40977
- for (const root of roots) walk(root, 0);
40978
- return out;
40979
- }
40980
- function defaultScanRoots(home) {
40981
- const fromEnv = (process.env.MRRLIN_DIRECTOR_BRIDGE_SCAN_ROOTS ?? "").trim();
40982
- if (fromEnv) return fromEnv.split(":").filter(Boolean);
40983
- return ["dev", "projects", "src", "work"].map((d) => (0, import_node_path9.join)(home, d));
40984
- }
40985
-
40986
41014
  // src/executor-server.ts
40987
- var import_node_fs9 = require("node:fs");
41015
+ var import_node_fs8 = require("node:fs");
40988
41016
  var import_node_os6 = require("node:os");
40989
- var import_node_path10 = require("node:path");
41017
+ var import_node_path9 = require("node:path");
40990
41018
  var DENY_READ_GLOBS = [
40991
41019
  "**/.ssh/**",
40992
41020
  "**/.aws/**",
@@ -41019,15 +41047,15 @@ function buildExecutorConfig(opts) {
41019
41047
  }
41020
41048
  function operatorSecretPaths(realHome = (0, import_node_os6.homedir)()) {
41021
41049
  return [
41022
- (0, import_node_path10.join)(realHome, ".ssh"),
41023
- (0, import_node_path10.join)(realHome, ".aws"),
41024
- (0, import_node_path10.join)(realHome, ".config", "gh"),
41025
- (0, import_node_path10.join)(realHome, ".npmrc"),
41026
- (0, import_node_path10.join)(realHome, ".netrc"),
41027
- (0, import_node_path10.join)(realHome, ".gnupg"),
41028
- (0, import_node_path10.join)(realHome, ".kube"),
41029
- (0, import_node_path10.join)(realHome, ".docker", "config.json"),
41030
- (0, import_node_path10.join)(realHome, ".config", "gcloud")
41050
+ (0, import_node_path9.join)(realHome, ".ssh"),
41051
+ (0, import_node_path9.join)(realHome, ".aws"),
41052
+ (0, import_node_path9.join)(realHome, ".config", "gh"),
41053
+ (0, import_node_path9.join)(realHome, ".npmrc"),
41054
+ (0, import_node_path9.join)(realHome, ".netrc"),
41055
+ (0, import_node_path9.join)(realHome, ".gnupg"),
41056
+ (0, import_node_path9.join)(realHome, ".kube"),
41057
+ (0, import_node_path9.join)(realHome, ".docker", "config.json"),
41058
+ (0, import_node_path9.join)(realHome, ".config", "gcloud")
41031
41059
  ];
41032
41060
  }
41033
41061
  var SECRET_DIRECTORY_BASENAMES = /* @__PURE__ */ new Set([".ssh", ".aws", "gh", ".gnupg", ".kube", "gcloud"]);
@@ -41036,8 +41064,8 @@ function isSecretDirectoryPath(p) {
41036
41064
  return SECRET_DIRECTORY_BASENAMES.has(base);
41037
41065
  }
41038
41066
  function writeExecutorCodexHome(scratchDir, opts) {
41039
- const codexHome = (0, import_node_path10.join)(scratchDir, ".codex");
41040
- (0, import_node_fs9.mkdirSync)(codexHome, { recursive: true });
41067
+ const codexHome = (0, import_node_path9.join)(scratchDir, ".codex");
41068
+ (0, import_node_fs8.mkdirSync)(codexHome, { recursive: true });
41041
41069
  const hasEgress = opts.egressDomains.length > 0;
41042
41070
  const networkSection = hasEgress ? [
41043
41071
  "[permissions.executor.network]",
@@ -41072,21 +41100,21 @@ function writeExecutorCodexHome(scratchDir, opts) {
41072
41100
  networkSection,
41073
41101
  ""
41074
41102
  ].join("\n");
41075
- (0, import_node_fs9.writeFileSync)((0, import_node_path10.join)(codexHome, "config.toml"), toml3, "utf8");
41103
+ (0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(codexHome, "config.toml"), toml3, "utf8");
41076
41104
  return codexHome;
41077
41105
  }
41078
41106
  async function startExecutorServer(opts) {
41079
41107
  const egressDomains = opts.egressDomains ?? DEFAULT_EGRESS_DOMAINS;
41080
41108
  const realHome = (0, import_node_os6.homedir)();
41081
41109
  const denySecretPaths = operatorSecretPaths(realHome);
41082
- const scratchDir = (0, import_node_fs9.mkdtempSync)((0, import_node_path10.join)((0, import_node_os6.tmpdir)(), "mrrlin-executor-"));
41110
+ const scratchDir = (0, import_node_fs8.mkdtempSync)((0, import_node_path9.join)((0, import_node_os6.tmpdir)(), "mrrlin-executor-"));
41083
41111
  const scratchHome = scratchDir;
41084
41112
  let scratchCleaned = false;
41085
41113
  const cleanScratch = () => {
41086
41114
  if (scratchCleaned) return;
41087
41115
  scratchCleaned = true;
41088
41116
  try {
41089
- (0, import_node_fs9.rmSync)(scratchDir, { recursive: true, force: true });
41117
+ (0, import_node_fs8.rmSync)(scratchDir, { recursive: true, force: true });
41090
41118
  } catch {
41091
41119
  }
41092
41120
  };
@@ -41277,17 +41305,10 @@ function extractVerificationSection(markdown) {
41277
41305
  }
41278
41306
 
41279
41307
  // src/bridge-log.ts
41280
- var import_node_fs10 = require("node:fs");
41281
- var import_node_path11 = require("node:path");
41282
- var NOOP_LOGGER = {
41283
- logIn() {
41284
- },
41285
- logOut() {
41286
- },
41287
- close() {
41288
- }
41289
- };
41290
- var REDACT_DEPTH_CAP = 8;
41308
+ var import_node_fs9 = require("node:fs");
41309
+ var import_node_path10 = require("node:path");
41310
+
41311
+ // src/redact.ts
41291
41312
  var REDACTED = "[REDACTED]";
41292
41313
  var SECRET_PATTERNS = [
41293
41314
  /ghp_[A-Za-z0-9]{20,}/g,
@@ -41303,17 +41324,36 @@ var SECRET_PATTERNS = [
41303
41324
  /\b[A-Za-z0-9+/]{40,}={0,2}\b/g
41304
41325
  // long base64-ish run (opaque tokens)
41305
41326
  ];
41306
- function scrubString(value) {
41307
- let out = value;
41327
+ function redact(input) {
41328
+ let out = input;
41308
41329
  for (const re of SECRET_PATTERNS) out = out.replace(re, REDACTED);
41309
41330
  return out;
41310
41331
  }
41332
+
41333
+ // src/bridge-log.ts
41334
+ var NOOP_LOGGER = {
41335
+ logIn() {
41336
+ },
41337
+ logOut() {
41338
+ },
41339
+ close() {
41340
+ }
41341
+ };
41342
+ var REDACT_DEPTH_CAP = 8;
41343
+ var IMAGE_DATA_URI_RE = /^(data:image\/[a-zA-Z0-9.+-]+;base64,)([A-Za-z0-9+/=]+)$/;
41344
+ function redactImageDataUri(value) {
41345
+ const m = value.match(IMAGE_DATA_URI_RE);
41346
+ if (!m) return value;
41347
+ const prefix = m[1];
41348
+ const body = m[2];
41349
+ return `${prefix}\u2026(${body.length} bytes)`;
41350
+ }
41311
41351
  function redactValue(value, depth) {
41312
- if (typeof value === "string") return scrubString(value);
41352
+ if (typeof value === "string") return redact(redactImageDataUri(value));
41313
41353
  if (value === null || typeof value !== "object") return value;
41314
41354
  if (depth >= REDACT_DEPTH_CAP) {
41315
41355
  try {
41316
- return scrubString(JSON.stringify(value));
41356
+ return redact(JSON.stringify(value));
41317
41357
  } catch {
41318
41358
  return REDACTED;
41319
41359
  }
@@ -41351,14 +41391,14 @@ function resolvePromptsEnabled(env) {
41351
41391
  }
41352
41392
  function createBridgeLogger(stateDir, env = process.env) {
41353
41393
  if (!resolveBridgeLogEnabled(env)) return NOOP_LOGGER;
41354
- const logsDir = (0, import_node_path11.join)(stateDir, "logs");
41394
+ const logsDir = (0, import_node_path10.join)(stateDir, "logs");
41355
41395
  try {
41356
- (0, import_node_fs10.mkdirSync)(logsDir, { recursive: true });
41396
+ (0, import_node_fs9.mkdirSync)(logsDir, { recursive: true });
41357
41397
  } catch {
41358
41398
  return NOOP_LOGGER;
41359
41399
  }
41360
41400
  const includePrompts = resolvePromptsEnabled(env);
41361
- const fileFor = () => (0, import_node_path11.join)(logsDir, `bridge-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.jsonl`);
41401
+ const fileFor = () => (0, import_node_path10.join)(logsDir, `bridge-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.jsonl`);
41362
41402
  const write = (dir, rec) => {
41363
41403
  try {
41364
41404
  const line = JSON.stringify({
@@ -41370,7 +41410,7 @@ function createBridgeLogger(stateDir, env = process.env) {
41370
41410
  ...rec.ms !== void 0 ? { ms: rec.ms } : {},
41371
41411
  payload: redactMessage(rec.payload, { includePrompts })
41372
41412
  }) + "\n";
41373
- (0, import_node_fs10.appendFileSync)(fileFor(), line);
41413
+ (0, import_node_fs9.appendFileSync)(fileFor(), line);
41374
41414
  } catch {
41375
41415
  }
41376
41416
  };
@@ -41404,9 +41444,9 @@ function normalizeModel(value) {
41404
41444
  return trimmed ? trimmed : null;
41405
41445
  }
41406
41446
  function neutralCheckoutCwd(stateDir) {
41407
- const dir = import_node_path12.default.join(stateDir, "no-checkout");
41447
+ const dir = import_node_path11.default.join(stateDir, "no-checkout");
41408
41448
  try {
41409
- import_node_fs11.default.mkdirSync(dir, { recursive: true });
41449
+ import_node_fs10.default.mkdirSync(dir, { recursive: true });
41410
41450
  } catch {
41411
41451
  }
41412
41452
  return dir;
@@ -41597,7 +41637,7 @@ function resolveDefaultBranch(checkoutPath) {
41597
41637
  if (!checkoutPath) return "main";
41598
41638
  const runGit = (args) => {
41599
41639
  try {
41600
- return (0, import_node_child_process4.execFileSync)("git", ["-C", checkoutPath, ...args], {
41640
+ return (0, import_node_child_process3.execFileSync)("git", ["-C", checkoutPath, ...args], {
41601
41641
  encoding: "utf8",
41602
41642
  stdio: ["ignore", "pipe", "ignore"],
41603
41643
  timeout: 15e3,
@@ -41629,7 +41669,7 @@ async function runOrphanWorktreeSweep(client, opts) {
41629
41669
  const cutoff = Date.now() - worktreeRetentionMs();
41630
41670
  const isReapable = (worktreePath) => {
41631
41671
  try {
41632
- return import_node_fs11.default.statSync(worktreePath).mtimeMs < cutoff;
41672
+ return import_node_fs10.default.statSync(worktreePath).mtimeMs < cutoff;
41633
41673
  } catch {
41634
41674
  return false;
41635
41675
  }
@@ -41666,8 +41706,8 @@ async function runOrphanWorktreeSweep(client, opts) {
41666
41706
  }
41667
41707
  }
41668
41708
  function readStateDir() {
41669
- if (process.env.CODEX_HOME) return import_node_path12.default.join(process.env.CODEX_HOME, "mrrlin", "director-bridge");
41670
- return import_node_path12.default.join(import_node_os7.default.homedir(), ".mrrlin", "director-bridge");
41709
+ if (process.env.CODEX_HOME) return import_node_path11.default.join(process.env.CODEX_HOME, "mrrlin", "director-bridge");
41710
+ return import_node_path11.default.join(import_node_os7.default.homedir(), ".mrrlin", "director-bridge");
41671
41711
  }
41672
41712
  function readAllowedOrigins() {
41673
41713
  const raw = (process.env.MRRLIN_DIRECTOR_BRIDGE_ALLOWED_ORIGINS ?? "").trim();
@@ -41931,53 +41971,26 @@ function createBridgeMessageHandler(deps) {
41931
41971
  sendForSpan({ type: "reconfigured", directorSessionId: directorSessionId2 });
41932
41972
  return;
41933
41973
  }
41934
- if (msg.type === "discover-checkouts") {
41935
- const projectSlug = typeof msg.projectSlug === "string" ? msg.projectSlug.trim() : "";
41936
- const repo = typeof msg.repo === "string" ? msg.repo.trim() : "";
41937
- if (!projectSlug || !repo) {
41938
- sendForSpan({ type: "error", error: "discover-checkouts requires projectSlug and repo." });
41939
- return;
41940
- }
41941
- const roots = deps.scanRoots ?? defaultScanRoots(import_node_os7.default.homedir());
41942
- const candidates = scanForCheckouts(roots, repo);
41943
- sendForSpan({ type: "checkout-candidates", projectSlug, candidates });
41974
+ if (msg.type !== "turn" || typeof msg.directorSessionId !== "string" || typeof msg.message !== "string") {
41975
+ sendForSpan({ type: "error", error: "Invalid message schema." });
41944
41976
  return;
41945
41977
  }
41946
- if (msg.type === "confirm-checkout") {
41947
- const projectSlug = typeof msg.projectSlug === "string" ? msg.projectSlug.trim() : "";
41948
- const checkoutPath = typeof msg.path === "string" ? msg.path.trim() : "";
41949
- const repo = typeof msg.repo === "string" ? msg.repo.trim() : "";
41950
- if (!projectSlug || !checkoutPath) {
41951
- sendForSpan({ type: "checkout-error", projectSlug, error: "confirm-checkout requires projectSlug and path." });
41952
- return;
41953
- }
41954
- let remoteUrl = null;
41955
- try {
41956
- remoteUrl = (0, import_node_child_process4.execFileSync)("git", ["-C", checkoutPath, "remote", "get-url", "origin"], {
41957
- encoding: "utf8",
41958
- stdio: ["ignore", "pipe", "ignore"],
41959
- timeout: 15e3,
41960
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
41961
- }).trim();
41962
- } catch {
41963
- sendForSpan({ type: "checkout-error", projectSlug, error: `Path is not a git repository or has no origin remote: ${checkoutPath}` });
41978
+ const rawImage = msg.imageDataUri;
41979
+ let imageDataUri = null;
41980
+ if (rawImage !== void 0 && rawImage !== null) {
41981
+ if (typeof rawImage !== "string") {
41982
+ sendForSpan({ type: "error", error: "imageDataUri must be a string." });
41964
41983
  return;
41965
41984
  }
41966
- if (repo && !remoteMatchesRepo(remoteUrl, repo)) {
41967
- sendForSpan({ type: "checkout-error", projectSlug, error: `Origin remote ${remoteUrl} does not match repo ${repo}.` });
41985
+ if (!rawImage.startsWith("data:image/")) {
41986
+ sendForSpan({ type: "error", error: "imageDataUri must be a data URI for an image." });
41968
41987
  return;
41969
41988
  }
41970
- if (!deps.checkoutRegistry) {
41971
- sendForSpan({ type: "checkout-error", projectSlug, error: "No checkout registry configured." });
41989
+ if (rawImage.length > 3 * 1024 * 1024) {
41990
+ sendForSpan({ type: "error", error: "Image too large." });
41972
41991
  return;
41973
41992
  }
41974
- deps.checkoutRegistry.confirm(projectSlug, checkoutPath, (/* @__PURE__ */ new Date()).toISOString());
41975
- sendForSpan({ type: "checkout-confirmed", projectSlug, path: checkoutPath });
41976
- return;
41977
- }
41978
- if (msg.type !== "turn" || typeof msg.directorSessionId !== "string" || typeof msg.message !== "string") {
41979
- sendForSpan({ type: "error", error: "Invalid message schema." });
41980
- return;
41993
+ imageDataUri = rawImage;
41981
41994
  }
41982
41995
  const directorSessionId = msg.directorSessionId.trim();
41983
41996
  if (!directorSessionId) {
@@ -41985,7 +41998,7 @@ function createBridgeMessageHandler(deps) {
41985
41998
  return;
41986
41999
  }
41987
42000
  const inputText = msg.message.trim();
41988
- if (!inputText) {
42001
+ if (!inputText && !imageDataUri) {
41989
42002
  sendForSpan({ type: "error", directorSessionId, error: "message is required." });
41990
42003
  return;
41991
42004
  }
@@ -42131,11 +42144,19 @@ function createBridgeMessageHandler(deps) {
42131
42144
  socket.once("close", onSocketClose);
42132
42145
  pokeActivity = () => watchdog.poke();
42133
42146
  const runSequence = (async () => {
42147
+ const turnInput = [];
42148
+ if (inputText.length > 0) {
42149
+ const composed = contextPrefix ? `${contextPrefix}
42150
+
42151
+ ${inputText}` : inputText;
42152
+ turnInput.push({ type: "text", text: composed, text_elements: [] });
42153
+ }
42154
+ if (imageDataUri) {
42155
+ turnInput.push({ type: "image", url: imageDataUri });
42156
+ }
42134
42157
  await codexClient.turn.start({
42135
42158
  threadId: thread.threadId,
42136
- input: [{ type: "text", text: contextPrefix ? `${contextPrefix}
42137
-
42138
- ${inputText}` : inputText }],
42159
+ input: turnInput,
42139
42160
  ...thread.reasoningEffort ? { effort: thread.reasoningEffort } : {}
42140
42161
  });
42141
42162
  await turnFinished;
@@ -42297,8 +42318,8 @@ async function startDirectorBridge() {
42297
42318
  codexCwd: config2.codexCwd,
42298
42319
  codexExecutable: config2.codexExecutable,
42299
42320
  checkoutRegistry,
42300
- worktreeRoot: import_node_path12.default.join(config2.stateDir, "worktrees"),
42301
- locksDir: import_node_path12.default.join(config2.stateDir, "locks"),
42321
+ worktreeRoot: import_node_path11.default.join(config2.stateDir, "worktrees"),
42322
+ locksDir: import_node_path11.default.join(config2.stateDir, "locks"),
42302
42323
  // Wire the profile-enforced executor server for code-execution runs. Each run gets its own
42303
42324
  // sandboxed Codex instance (scrubbed env, scratch CODEX_HOME, deny-read, egress allowlist).
42304
42325
  // The returned handle's dispose() shuts down the spawned app-server CHILD (manager.shutdown(),
@@ -42331,13 +42352,13 @@ async function startDirectorBridge() {
42331
42352
  },
42332
42353
  runVerificationTurn: createRunVerificationTurn({
42333
42354
  startSandbox: async (egressDomains) => {
42334
- const cwd = import_node_fs11.default.mkdtempSync(import_node_path12.default.join(import_node_os7.default.tmpdir(), "mrrlin-verify-"));
42355
+ const cwd = import_node_fs10.default.mkdtempSync(import_node_path11.default.join(import_node_os7.default.tmpdir(), "mrrlin-verify-"));
42335
42356
  let handle;
42336
42357
  try {
42337
42358
  handle = await startExecutorServer({ worktree: cwd, egressDomains });
42338
42359
  } catch (error51) {
42339
42360
  try {
42340
- import_node_fs11.default.rmSync(cwd, { recursive: true, force: true });
42361
+ import_node_fs10.default.rmSync(cwd, { recursive: true, force: true });
42341
42362
  } catch {
42342
42363
  }
42343
42364
  throw error51;
@@ -42348,7 +42369,7 @@ async function startDirectorBridge() {
42348
42369
  dispose: () => {
42349
42370
  handle.dispose();
42350
42371
  try {
42351
- import_node_fs11.default.rmSync(cwd, { recursive: true, force: true });
42372
+ import_node_fs10.default.rmSync(cwd, { recursive: true, force: true });
42352
42373
  } catch {
42353
42374
  }
42354
42375
  }
@@ -42581,8 +42602,8 @@ async function startDirectorBridge() {
42581
42602
  enqueueRun(
42582
42603
  () => runOrphanWorktreeSweep(client, {
42583
42604
  checkoutRegistry,
42584
- worktreeRoot: import_node_path12.default.join(config2.stateDir, "worktrees"),
42585
- locksDir: import_node_path12.default.join(config2.stateDir, "locks")
42605
+ worktreeRoot: import_node_path11.default.join(config2.stateDir, "worktrees"),
42606
+ locksDir: import_node_path11.default.join(config2.stateDir, "locks")
42586
42607
  })
42587
42608
  );
42588
42609
  scheduleClaimLoop();
@@ -42739,15 +42760,15 @@ function readDispatchBody(req) {
42739
42760
  function readOrCreateBridgeToken(stateDir) {
42740
42761
  const explicit = (process.env.MRRLIN_DIRECTOR_BRIDGE_TOKEN ?? "").trim();
42741
42762
  if (explicit) return explicit;
42742
- const tokenPath = import_node_path12.default.join(stateDir, "token.txt");
42763
+ const tokenPath = import_node_path11.default.join(stateDir, "token.txt");
42743
42764
  try {
42744
- const existing = import_node_fs11.default.readFileSync(tokenPath, "utf8").trim();
42765
+ const existing = import_node_fs10.default.readFileSync(tokenPath, "utf8").trim();
42745
42766
  if (existing) return existing;
42746
42767
  } catch {
42747
42768
  }
42748
- import_node_fs11.default.mkdirSync(stateDir, { recursive: true, mode: 448 });
42769
+ import_node_fs10.default.mkdirSync(stateDir, { recursive: true, mode: 448 });
42749
42770
  const token = import_node_crypto4.default.randomBytes(32).toString("base64url");
42750
- import_node_fs11.default.writeFileSync(tokenPath, `${token}
42771
+ import_node_fs10.default.writeFileSync(tokenPath, `${token}
42751
42772
  `, { mode: 384 });
42752
42773
  return token;
42753
42774
  }
@@ -46130,14 +46151,17 @@ var StdioServerTransport = class {
46130
46151
 
46131
46152
  // src/tools.ts
46132
46153
  var import_promises3 = require("node:fs/promises");
46133
- var import_node_path15 = __toESM(require("node:path"), 1);
46154
+ var import_node_fs12 = require("node:fs");
46155
+ var import_node_path14 = __toESM(require("node:path"), 1);
46156
+ var import_node_os8 = __toESM(require("node:os"), 1);
46157
+ var import_node_child_process6 = require("node:child_process");
46134
46158
 
46135
46159
  // ../../packages/wiki/dist/index.js
46136
46160
  var import_promises2 = __toESM(require("node:fs/promises"), 1);
46137
- var import_node_path13 = __toESM(require("node:path"), 1);
46161
+ var import_node_path12 = __toESM(require("node:path"), 1);
46138
46162
  var INCLUDED_DOC_FOLDERS = /* @__PURE__ */ new Set(["_meta", "explanation", "how-to", "reference", "sdd", "tutorials"]);
46139
46163
  function toPosixPath(input) {
46140
- return input.split(import_node_path13.default.sep).join("/");
46164
+ return input.split(import_node_path12.default.sep).join("/");
46141
46165
  }
46142
46166
  function slugifyWikiSegment(input) {
46143
46167
  return input.trim().toLowerCase().replace(/`/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -46151,12 +46175,12 @@ async function pathExists2(input) {
46151
46175
  }
46152
46176
  }
46153
46177
  async function findWorkspaceRoot(start = process.cwd()) {
46154
- let current = import_node_path13.default.resolve(start);
46178
+ let current = import_node_path12.default.resolve(start);
46155
46179
  while (true) {
46156
- if (await pathExists2(import_node_path13.default.join(current, "pnpm-workspace.yaml")) && await pathExists2(import_node_path13.default.join(current, "docs"))) {
46180
+ if (await pathExists2(import_node_path12.default.join(current, "pnpm-workspace.yaml")) && await pathExists2(import_node_path12.default.join(current, "docs"))) {
46157
46181
  return current;
46158
46182
  }
46159
- const parent = import_node_path13.default.dirname(current);
46183
+ const parent = import_node_path12.default.dirname(current);
46160
46184
  if (parent === current) {
46161
46185
  throw new Error(`Unable to find Mrrlin workspace root from ${start}`);
46162
46186
  }
@@ -46165,28 +46189,28 @@ async function findWorkspaceRoot(start = process.cwd()) {
46165
46189
  }
46166
46190
  async function resolveDocsRoot(input) {
46167
46191
  if (input) {
46168
- return import_node_path13.default.resolve(input);
46192
+ return import_node_path12.default.resolve(input);
46169
46193
  }
46170
46194
  if (process.env.MRRLIN_DOCS_ROOT) {
46171
- return import_node_path13.default.resolve(process.env.MRRLIN_DOCS_ROOT);
46195
+ return import_node_path12.default.resolve(process.env.MRRLIN_DOCS_ROOT);
46172
46196
  }
46173
- return import_node_path13.default.join(await findWorkspaceRoot(), "docs");
46197
+ return import_node_path12.default.join(await findWorkspaceRoot(), "docs");
46174
46198
  }
46175
46199
  async function resolveIndexPath(input) {
46176
46200
  if (input) {
46177
- return import_node_path13.default.resolve(input);
46201
+ return import_node_path12.default.resolve(input);
46178
46202
  }
46179
46203
  if (process.env.MRRLIN_WIKI_INDEX_PATH) {
46180
- return import_node_path13.default.resolve(process.env.MRRLIN_WIKI_INDEX_PATH);
46204
+ return import_node_path12.default.resolve(process.env.MRRLIN_WIKI_INDEX_PATH);
46181
46205
  }
46182
46206
  const workspaceRoot = await findWorkspaceRoot();
46183
- return import_node_path13.default.join(workspaceRoot, "apps", "web", "public", "wiki-index.json");
46207
+ return import_node_path12.default.join(workspaceRoot, "apps", "web", "public", "wiki-index.json");
46184
46208
  }
46185
46209
  async function walkMarkdownFiles(root, dir = root) {
46186
46210
  const entries = await import_promises2.default.readdir(dir, { withFileTypes: true });
46187
46211
  const files = [];
46188
46212
  for (const entry of entries) {
46189
- const absolute = import_node_path13.default.join(dir, entry.name);
46213
+ const absolute = import_node_path12.default.join(dir, entry.name);
46190
46214
  if (entry.isDirectory()) {
46191
46215
  files.push(...await walkMarkdownFiles(root, absolute));
46192
46216
  continue;
@@ -46205,7 +46229,7 @@ function parseTitle(markdown, sourcePath) {
46205
46229
  return title;
46206
46230
  }
46207
46231
  function documentFromFile(docsRoot, absolutePath, markdown) {
46208
- const relativePath = toPosixPath(import_node_path13.default.relative(docsRoot, absolutePath));
46232
+ const relativePath = toPosixPath(import_node_path12.default.relative(docsRoot, absolutePath));
46209
46233
  const [section, fileName] = relativePath.split("/");
46210
46234
  if (!section || !fileName || !INCLUDED_DOC_FOLDERS.has(section)) {
46211
46235
  return null;
@@ -46620,7 +46644,7 @@ async function runCodeGate(_target, deps) {
46620
46644
  }
46621
46645
 
46622
46646
  // src/consensus/codex-exec.ts
46623
- var import_node_child_process5 = require("node:child_process");
46647
+ var import_node_child_process4 = require("node:child_process");
46624
46648
  function extractFinalMessage(stdout) {
46625
46649
  let last = null;
46626
46650
  for (const rawLine of stdout.split(/\r?\n/)) {
@@ -46665,7 +46689,7 @@ function firstString(...values) {
46665
46689
  return null;
46666
46690
  }
46667
46691
  function runCodexExec(input, deps) {
46668
- const spawn2 = deps?.spawn ?? import_node_child_process5.spawn;
46692
+ const spawn2 = deps?.spawn ?? import_node_child_process4.spawn;
46669
46693
  const executable = input.codexExecutable ?? "codex";
46670
46694
  const sandbox = input.sandbox ?? "read-only";
46671
46695
  const fullPrompt = input.developerInstructions.trim() ? `${input.developerInstructions.trim()}
@@ -46737,12 +46761,12 @@ function errMessage(err) {
46737
46761
  }
46738
46762
 
46739
46763
  // src/consensus/wiring.ts
46740
- var import_node_fs12 = __toESM(require("node:fs"), 1);
46741
- var import_node_path14 = __toESM(require("node:path"), 1);
46764
+ var import_node_fs11 = __toESM(require("node:fs"), 1);
46765
+ var import_node_path13 = __toESM(require("node:path"), 1);
46742
46766
  var import_node_url = require("node:url");
46743
46767
 
46744
46768
  // src/consensus/code-gate-git.ts
46745
- var import_node_child_process6 = require("node:child_process");
46769
+ var import_node_child_process5 = require("node:child_process");
46746
46770
  var DIFF_MAX_BYTES = 2e5;
46747
46771
  var FASTGATE_OUTPUT_MAX_BYTES = 16e3;
46748
46772
  var GIT_TIMEOUT_MS = 6e4;
@@ -46758,7 +46782,7 @@ var defaultRunCmd = (cmd, args, opts) => new Promise((resolve) => {
46758
46782
  };
46759
46783
  let child;
46760
46784
  try {
46761
- child = (0, import_node_child_process6.spawn)(cmd, args, {
46785
+ child = (0, import_node_child_process5.spawn)(cmd, args, {
46762
46786
  cwd: opts.cwd,
46763
46787
  env: process.env,
46764
46788
  shell: false,
@@ -46973,15 +46997,15 @@ ${res.stdout}${res.stderr}`);
46973
46997
  var import_meta = {};
46974
46998
  function getPersonasDir() {
46975
46999
  if (typeof __dirname !== "undefined") {
46976
- return import_node_path14.default.join(__dirname, "consensus", "personas");
47000
+ return import_node_path13.default.join(__dirname, "consensus", "personas");
46977
47001
  }
46978
- return import_node_path14.default.join(import_node_path14.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url)), "personas");
47002
+ return import_node_path13.default.join(import_node_path13.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url)), "personas");
46979
47003
  }
46980
47004
  var personaCache = /* @__PURE__ */ new Map();
46981
47005
  function loadPersona(name) {
46982
47006
  const cached2 = personaCache.get(name);
46983
47007
  if (cached2 !== void 0) return cached2;
46984
- const content = import_node_fs12.default.readFileSync(import_node_path14.default.join(getPersonasDir(), `${name}.md`), "utf8");
47008
+ const content = import_node_fs11.default.readFileSync(import_node_path13.default.join(getPersonasDir(), `${name}.md`), "utf8");
46985
47009
  personaCache.set(name, content);
46986
47010
  return content;
46987
47011
  }
@@ -47290,8 +47314,28 @@ async function enqueueAsyncTool(client, descriptor2, input) {
47290
47314
  }
47291
47315
  }
47292
47316
 
47293
- // src/tools.ts
47294
- registerAsyncTool(consensusDescriptor);
47317
+ // src/git-remote-match.ts
47318
+ function normalizeRemote(url2) {
47319
+ const trimmed = url2.trim().replace(/\.git$/i, "");
47320
+ const scp = /^[^@]+@[^:]+:(.+)$/.exec(trimmed);
47321
+ const pathPart = scp ? scp[1] : (() => {
47322
+ try {
47323
+ return new URL(trimmed).pathname.replace(/^\/+/, "");
47324
+ } catch {
47325
+ return null;
47326
+ }
47327
+ })();
47328
+ if (!pathPart) return null;
47329
+ const segs = pathPart.split("/").filter(Boolean);
47330
+ if (segs.length < 2) return null;
47331
+ return `${segs[segs.length - 2]}/${segs[segs.length - 1]}`.toLowerCase();
47332
+ }
47333
+ function remoteMatchesRepo(remoteUrl, repoFullName) {
47334
+ const a = normalizeRemote(remoteUrl);
47335
+ return a !== null && a === repoFullName.trim().toLowerCase();
47336
+ }
47337
+
47338
+ // src/tool-names.ts
47295
47339
  var mcpToolNames = {
47296
47340
  appendExecutionArtifact: "append_execution_artifact",
47297
47341
  archivePlan: "archive_plan",
@@ -47327,7 +47371,6 @@ var mcpToolNames = {
47327
47371
  listExecutionArtifacts: "list_execution_artifacts",
47328
47372
  listExecutionRuns: "list_execution_runs",
47329
47373
  listInboxItems: "list_inbox_items",
47330
- listGithubWebhookDeliveries: "list_github_webhook_deliveries",
47331
47374
  listGithubInstallations: "list_github_installations",
47332
47375
  listGithubWorkflows: "list_github_workflows",
47333
47376
  listPlans: "list_plans",
@@ -47350,8 +47393,500 @@ var mcpToolNames = {
47350
47393
  runCodeReviewerGate: "run_code_reviewer_gate",
47351
47394
  uploadArtifact: "upload_artifact",
47352
47395
  listArtifactFiles: "list_artifact_files",
47353
- resolveHandoff: "resolve_handoff"
47396
+ resolveHandoff: "resolve_handoff",
47397
+ registerLocalCheckout: "register_local_checkout",
47398
+ /** Compact-context retrieval: rehydrate a mrrlin:// handle into the canonical full record. */
47399
+ retrieveContext: "retrieve_context"
47400
+ };
47401
+
47402
+ // src/context-compact/policy.ts
47403
+ var DEFAULT_THRESHOLD_CHARS = 12e3;
47404
+ var DEFAULT_MAX_ITEMS = 25;
47405
+ function readPositiveInt(value, fallback) {
47406
+ const raw = (value ?? "").trim();
47407
+ if (!raw) return fallback;
47408
+ const parsed = Number.parseInt(raw, 10);
47409
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
47410
+ }
47411
+ function readCompactPolicy(env = process.env) {
47412
+ const disabled = (env.MRRLIN_MCP_COMPACT_DISABLE ?? "").trim() === "1";
47413
+ return {
47414
+ enabled: !disabled,
47415
+ thresholdChars: readPositiveInt(env.MRRLIN_MCP_COMPACT_THRESHOLD_CHARS, DEFAULT_THRESHOLD_CHARS),
47416
+ maxItems: readPositiveInt(env.MRRLIN_MCP_COMPACT_MAX_ITEMS, DEFAULT_MAX_ITEMS),
47417
+ debug: (env.MRRLIN_MCP_COMPACT_DEBUG ?? "").trim() === "1"
47418
+ };
47419
+ }
47420
+
47421
+ // src/context-compact/handles.ts
47422
+ var MRRLIN_HANDLE_SCHEME = "mrrlin://";
47423
+ var SLUG_RE = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
47424
+ var TASK_ID_RE = /^GT-[A-Za-z0-9-]+$/;
47425
+ var WIKI_ID_RE = /^WK-[A-Za-z0-9-]+$/;
47426
+ var RUN_ID_RE = /^ER-[A-Za-z0-9-]+$/;
47427
+ function isProjectSlug(value) {
47428
+ return SLUG_RE.test(value);
47429
+ }
47430
+ function formatTaskHandle(projectSlug, taskId) {
47431
+ return `${MRRLIN_HANDLE_SCHEME}task/${projectSlug}/${taskId}`;
47432
+ }
47433
+ function formatTaskEventsHandle(projectSlug, taskId) {
47434
+ return `${MRRLIN_HANDLE_SCHEME}task/${projectSlug}/${taskId}/events`;
47435
+ }
47436
+ function formatWikiHandle(projectSlug, pageId) {
47437
+ return `${MRRLIN_HANDLE_SCHEME}wiki/${projectSlug}/${pageId}`;
47438
+ }
47439
+ function formatProjectSnapshotHandle(projectSlug) {
47440
+ return `${MRRLIN_HANDLE_SCHEME}project/${projectSlug}/snapshot`;
47441
+ }
47442
+ function formatRunArtifactsHandle(projectSlug, runId) {
47443
+ return `${MRRLIN_HANDLE_SCHEME}run/${projectSlug}/${runId}/artifacts`;
47444
+ }
47445
+ function parseMrrlinHandle(value) {
47446
+ if (typeof value !== "string") return null;
47447
+ if (!value.startsWith(MRRLIN_HANDLE_SCHEME)) return null;
47448
+ const rest = value.slice(MRRLIN_HANDLE_SCHEME.length);
47449
+ if (!rest) return null;
47450
+ if (rest.includes("?") || rest.includes("#")) return null;
47451
+ const parts = rest.split("/");
47452
+ if (parts.length < 2) return null;
47453
+ const [kind, slug, ...tail2] = parts;
47454
+ if (!kind || !slug) return null;
47455
+ if (!isProjectSlug(slug)) return null;
47456
+ switch (kind) {
47457
+ case "task": {
47458
+ if (tail2.length < 1 || tail2.length > 2) return null;
47459
+ const taskId = tail2[0] ?? "";
47460
+ if (!TASK_ID_RE.test(taskId)) return null;
47461
+ if (tail2.length === 1) return { kind: "task", projectSlug: slug, taskId };
47462
+ if (tail2[1] !== "events") return null;
47463
+ return { kind: "task-events", projectSlug: slug, taskId };
47464
+ }
47465
+ case "wiki": {
47466
+ if (tail2.length !== 1) return null;
47467
+ const pageId = tail2[0] ?? "";
47468
+ if (!WIKI_ID_RE.test(pageId)) return null;
47469
+ return { kind: "wiki", projectSlug: slug, pageId };
47470
+ }
47471
+ case "project": {
47472
+ if (tail2.length !== 1) return null;
47473
+ const sub = tail2[0];
47474
+ if (sub === "snapshot") return { kind: "project-snapshot", projectSlug: slug };
47475
+ if (sub === "tasks") return { kind: "project-tasks", projectSlug: slug };
47476
+ return null;
47477
+ }
47478
+ case "run": {
47479
+ if (tail2.length < 1 || tail2.length > 2) return null;
47480
+ const runId = tail2[0] ?? "";
47481
+ if (!RUN_ID_RE.test(runId)) return null;
47482
+ if (tail2.length === 1) return { kind: "run", projectSlug: slug, runId };
47483
+ if (tail2[1] !== "artifacts") return null;
47484
+ return { kind: "run-artifacts", projectSlug: slug, runId };
47485
+ }
47486
+ default:
47487
+ return null;
47488
+ }
47489
+ }
47490
+
47491
+ // src/context-compact/compactors.ts
47492
+ var NOTES_EXCERPT_CHARS = 200;
47493
+ var TITLE_TRUNCATE_CHARS = 160;
47494
+ var EXCERPT_TRUNCATE_CHARS = 240;
47495
+ function truncate(value, maxChars) {
47496
+ if (value.length <= maxChars) return value;
47497
+ return `${value.slice(0, Math.max(0, maxChars - 1))}\u2026`;
47498
+ }
47499
+ function firstHeadingLine(markdown) {
47500
+ for (const line of markdown.split(/\r?\n/, 32)) {
47501
+ if (line.startsWith("#")) return line.replace(/\s+$/u, "");
47502
+ }
47503
+ return null;
47504
+ }
47505
+ function compactTaskList(projectSlug, tasks, maxItems) {
47506
+ const total = tasks.length;
47507
+ const included = Math.min(total, maxItems);
47508
+ const slice = tasks.slice(0, included);
47509
+ const items = slice.map((t) => ({
47510
+ id: t.id,
47511
+ title: truncate(t.title, TITLE_TRUNCATE_CHARS),
47512
+ status: t.status,
47513
+ type: t.type,
47514
+ category: t.category,
47515
+ subcategory: t.subcategory,
47516
+ impact: t.impact,
47517
+ autonomy: t.autonomyLevel,
47518
+ tags: t.tags,
47519
+ assignee: t.assignee,
47520
+ planId: t.planId,
47521
+ specWikiPageId: t.specWikiPageId,
47522
+ notesExcerpt: t.notes ? truncate(t.notes, NOTES_EXCERPT_CHARS) : null,
47523
+ latestJudgementExcerpt: t.latestJudgement ? truncate(t.latestJudgement, NOTES_EXCERPT_CHARS) : null,
47524
+ handle: formatTaskHandle(projectSlug, t.id)
47525
+ }));
47526
+ const handles = slice.map((t) => ({
47527
+ handle: formatTaskHandle(projectSlug, t.id),
47528
+ retrieveTool: mcpToolNames.getTask,
47529
+ retrieveArgs: { projectSlug, taskId: t.id }
47530
+ }));
47531
+ return {
47532
+ summary: `${total} task${total === 1 ? "" : "s"} in project ${projectSlug}; ${included} included in this compact view.`,
47533
+ items,
47534
+ handles,
47535
+ counts: { total, included, omitted: total - included }
47536
+ };
47537
+ }
47538
+ function compactWikiPageList(projectSlug, pages, maxItems) {
47539
+ const total = pages.length;
47540
+ const included = Math.min(total, maxItems);
47541
+ const slice = pages.slice(0, included);
47542
+ const items = slice.map((p) => ({
47543
+ id: p.id,
47544
+ title: truncate(p.title, TITLE_TRUNCATE_CHARS),
47545
+ folder: p.folder,
47546
+ path: p.path,
47547
+ taskId: p.taskId,
47548
+ specReady: p.specReady,
47549
+ firstHeading: firstHeadingLine(p.markdownBody),
47550
+ handle: formatWikiHandle(projectSlug, p.id)
47551
+ }));
47552
+ const handles = slice.map((p) => ({
47553
+ handle: formatWikiHandle(projectSlug, p.id),
47554
+ retrieveTool: mcpToolNames.getWikiPage,
47555
+ retrieveArgs: { projectSlug, pageId: p.id }
47556
+ }));
47557
+ return {
47558
+ summary: `${total} wiki page${total === 1 ? "" : "s"} in project ${projectSlug}; ${included} included in this compact view.`,
47559
+ items,
47560
+ handles,
47561
+ counts: { total, included, omitted: total - included }
47562
+ };
47563
+ }
47564
+ function compactWikiSearch(projectSlug, results, maxItems) {
47565
+ const total = results.length;
47566
+ const included = Math.min(total, maxItems);
47567
+ const slice = results.slice(0, included);
47568
+ const items = slice.map((r) => ({
47569
+ pageId: r.id,
47570
+ title: truncate(r.title, TITLE_TRUNCATE_CHARS),
47571
+ folder: r.folder,
47572
+ path: r.path,
47573
+ rank: r.rank,
47574
+ snippet: truncate(r.snippet, EXCERPT_TRUNCATE_CHARS),
47575
+ handle: formatWikiHandle(projectSlug, r.id)
47576
+ }));
47577
+ const handles = slice.map((r) => ({
47578
+ handle: formatWikiHandle(projectSlug, r.id),
47579
+ retrieveTool: mcpToolNames.getWikiPage,
47580
+ retrieveArgs: { projectSlug, pageId: r.id }
47581
+ }));
47582
+ return {
47583
+ summary: `${total} wiki search hit${total === 1 ? "" : "s"} in project ${projectSlug}; ${included} included.`,
47584
+ items,
47585
+ handles,
47586
+ counts: { total, included, omitted: total - included }
47587
+ };
47588
+ }
47589
+ function compactProjectSnapshot(projectSlug, snapshot, maxItems) {
47590
+ const taskBody = compactTaskList(projectSlug, snapshot.tasks, maxItems);
47591
+ const wikiBody = compactWikiPageList(projectSlug, snapshot.wikiPages, maxItems);
47592
+ const statusCounts = {};
47593
+ for (const t of snapshot.tasks) {
47594
+ statusCounts[t.status] = (statusCounts[t.status] ?? 0) + 1;
47595
+ }
47596
+ const categoryCounts = {};
47597
+ for (const t of snapshot.tasks) {
47598
+ const key = `${t.category}/${t.subcategory}`;
47599
+ categoryCounts[key] = (categoryCounts[key] ?? 0) + 1;
47600
+ }
47601
+ const compactPlans = snapshot.plans.map((p) => ({
47602
+ id: p.id,
47603
+ name: truncate(p.name, TITLE_TRUNCATE_CHARS),
47604
+ status: p.status,
47605
+ taskCount: p.taskCount,
47606
+ closedTaskCount: p.closedTaskCount,
47607
+ progress: p.progress,
47608
+ dueDate: p.dueDate
47609
+ }));
47610
+ const items = [
47611
+ {
47612
+ project: snapshot.project,
47613
+ github: {
47614
+ installationState: snapshot.github.appInstallationState,
47615
+ installationId: snapshot.github.installationId,
47616
+ operator: snapshot.github.operator
47617
+ },
47618
+ taskCount: snapshot.tasks.length,
47619
+ wikiCount: snapshot.wikiPages.length,
47620
+ planCount: snapshot.plans.length,
47621
+ statusCounts,
47622
+ categoryCounts,
47623
+ plans: compactPlans,
47624
+ tasks: taskBody.items,
47625
+ wikiPages: wikiBody.items
47626
+ }
47627
+ ];
47628
+ const handles = [
47629
+ {
47630
+ handle: formatProjectSnapshotHandle(projectSlug),
47631
+ retrieveTool: mcpToolNames.getProjectSnapshot,
47632
+ retrieveArgs: { projectSlug }
47633
+ },
47634
+ ...taskBody.handles,
47635
+ ...wikiBody.handles
47636
+ ];
47637
+ const total = snapshot.tasks.length + snapshot.wikiPages.length;
47638
+ const included = taskBody.counts.included + wikiBody.counts.included;
47639
+ return {
47640
+ summary: `Snapshot for ${projectSlug}: ${snapshot.tasks.length} tasks, ${snapshot.wikiPages.length} wiki pages, ${snapshot.plans.length} plans.`,
47641
+ items,
47642
+ handles,
47643
+ counts: { total, included, omitted: total - included }
47644
+ };
47645
+ }
47646
+ var ARTIFACT_PAYLOAD_BUDGET = 400;
47647
+ var COMMAND_OUTPUT_TAIL_LINES = 10;
47648
+ function compactArtifactPayload(artifact) {
47649
+ const payload = artifact.payload ?? {};
47650
+ switch (artifact.kind) {
47651
+ case "file": {
47652
+ return {
47653
+ filename: payload.filename ?? null,
47654
+ contentType: payload.contentType ?? null,
47655
+ sizeBytes: payload.sizeBytes ?? null,
47656
+ class: payload.class ?? null,
47657
+ stableUrl: payload.stableUrl ?? null,
47658
+ description: payload.description ?? null
47659
+ };
47660
+ }
47661
+ case "command_output": {
47662
+ const event = payload.event ?? void 0;
47663
+ const command = payload.command ?? event?.tool?.input?.command ?? null;
47664
+ const cwd = payload.cwd ?? event?.tool?.input?.cwd ?? null;
47665
+ const exitCode = payload.exitCode ?? null;
47666
+ const rawOutput = payload.stdout ?? payload.output ?? event?.content ?? "";
47667
+ const lines2 = rawOutput.split(/\r?\n/);
47668
+ const tail2 = lines2.slice(-COMMAND_OUTPUT_TAIL_LINES).join("\n");
47669
+ const firstError = lines2.find((l) => /error|fail/i.test(l)) ?? null;
47670
+ return {
47671
+ command,
47672
+ cwd,
47673
+ exitCode,
47674
+ firstError,
47675
+ tail: truncate(tail2, ARTIFACT_PAYLOAD_BUDGET),
47676
+ totalLines: lines2.length
47677
+ };
47678
+ }
47679
+ case "test_result": {
47680
+ return {
47681
+ status: payload.status ?? null,
47682
+ command: payload.command ?? null,
47683
+ summary: typeof payload.summary === "string" ? truncate(payload.summary, ARTIFACT_PAYLOAD_BUDGET) : null,
47684
+ failed: Array.isArray(payload.failedTests) ? payload.failedTests.slice(0, 25) : null
47685
+ };
47686
+ }
47687
+ case "diff_summary": {
47688
+ return {
47689
+ files: Array.isArray(payload.files) ? payload.files.slice(0, 50) : null,
47690
+ added: payload.added ?? null,
47691
+ removed: payload.removed ?? null,
47692
+ renamed: payload.renamed ?? null
47693
+ };
47694
+ }
47695
+ case "handoff_judgement":
47696
+ case "self_review":
47697
+ case "error":
47698
+ case "checkpoint":
47699
+ default: {
47700
+ const flattened = JSON.stringify(payload);
47701
+ return { excerpt: truncate(flattened, ARTIFACT_PAYLOAD_BUDGET) };
47702
+ }
47703
+ }
47704
+ }
47705
+ function compactExecutionArtifacts(projectSlug, artifacts, maxItems) {
47706
+ const total = artifacts.length;
47707
+ const included = Math.min(total, maxItems);
47708
+ const slice = total > included ? artifacts.slice(total - included) : artifacts;
47709
+ const items = slice.map((a) => ({
47710
+ id: a.id,
47711
+ runId: a.runId,
47712
+ kind: a.kind,
47713
+ createdAt: a.createdAt,
47714
+ payload: compactArtifactPayload(a)
47715
+ }));
47716
+ const runIds = Array.from(new Set(slice.map((a) => a.runId)));
47717
+ const handles = runIds.map((runId) => ({
47718
+ handle: formatRunArtifactsHandle(projectSlug, runId),
47719
+ retrieveTool: mcpToolNames.listExecutionArtifacts,
47720
+ retrieveArgs: { projectSlug, runId }
47721
+ }));
47722
+ return {
47723
+ summary: `${total} execution artifact${total === 1 ? "" : "s"}; ${included} included (most recent first dropped older).`,
47724
+ items,
47725
+ handles,
47726
+ counts: { total, included, omitted: total - included }
47727
+ };
47728
+ }
47729
+ function compactTaskEvents(projectSlug, taskId, events, maxItems) {
47730
+ const total = events.length;
47731
+ const included = Math.min(total, maxItems);
47732
+ const slice = total > included ? events.slice(total - included) : events;
47733
+ const items = slice.map((e) => ({
47734
+ id: e.id,
47735
+ eventType: e.eventType,
47736
+ actor: e.actor,
47737
+ createdAt: e.createdAt,
47738
+ autonomyLevel: e.autonomyLevel,
47739
+ autonomyReason: e.autonomyReason,
47740
+ bodyExcerpt: e.body ? truncate(e.body, NOTES_EXCERPT_CHARS) : null
47741
+ }));
47742
+ const handles = [
47743
+ {
47744
+ handle: formatTaskEventsHandle(projectSlug, taskId),
47745
+ retrieveTool: mcpToolNames.listTaskEvents,
47746
+ retrieveArgs: { projectSlug, taskId }
47747
+ }
47748
+ ];
47749
+ return {
47750
+ summary: `${total} task event${total === 1 ? "" : "s"} for ${taskId}; ${included} most recent included.`,
47751
+ items,
47752
+ handles,
47753
+ counts: { total, included, omitted: total - included }
47754
+ };
47755
+ }
47756
+ function compactGeneric(value, maxItems, projectSlug) {
47757
+ if (Array.isArray(value)) {
47758
+ const total = value.length;
47759
+ const included = Math.min(total, maxItems);
47760
+ const slice = value.slice(0, included).map((entry) => {
47761
+ const text2 = typeof entry === "string" ? entry : JSON.stringify(entry);
47762
+ return truncate(text2, ARTIFACT_PAYLOAD_BUDGET);
47763
+ });
47764
+ return {
47765
+ summary: `Compact array view: ${total} entries, ${included} included.`,
47766
+ items: slice,
47767
+ handles: [],
47768
+ counts: { total, included, omitted: total - included }
47769
+ };
47770
+ }
47771
+ if (value && typeof value === "object") {
47772
+ const obj = value;
47773
+ const keys = Object.keys(obj);
47774
+ return {
47775
+ summary: `Compact object view (project=${projectSlug}): keys=[${keys.join(", ")}].`,
47776
+ items: [
47777
+ Object.fromEntries(
47778
+ keys.map((k) => {
47779
+ const entry = obj[k];
47780
+ if (Array.isArray(entry)) return [k, `[Array(${entry.length})]`];
47781
+ if (entry && typeof entry === "object") return [k, "[Object]"];
47782
+ return [k, entry];
47783
+ })
47784
+ )
47785
+ ],
47786
+ handles: [],
47787
+ counts: { total: 1, included: 1, omitted: 0 }
47788
+ };
47789
+ }
47790
+ const text = typeof value === "string" ? value : JSON.stringify(value);
47791
+ return {
47792
+ summary: "Compact scalar view.",
47793
+ items: [truncate(text, ARTIFACT_PAYLOAD_BUDGET)],
47794
+ handles: [],
47795
+ counts: { total: 1, included: 1, omitted: 0 }
47796
+ };
47797
+ }
47798
+
47799
+ // src/context-compact/index.ts
47800
+ var COMPACT_WARNING = "Compact result. Source of truth lives in the Mrrlin API; call `retrieve_context` with one of the embedded mrrlin:// handles to load the full record before any irreversible decision.";
47801
+ function serializedChars(value) {
47802
+ return JSON.stringify({ data: value }, null, 2).length;
47803
+ }
47804
+ function buildEnvelope(dispatch, body, originalChars) {
47805
+ const partial2 = {
47806
+ compact: true,
47807
+ kind: dispatch.kind,
47808
+ toolName: dispatch.toolName,
47809
+ projectSlug: dispatch.projectSlug,
47810
+ summary: body.summary,
47811
+ counts: body.counts,
47812
+ items: body.items,
47813
+ handles: body.handles,
47814
+ warning: COMPACT_WARNING
47815
+ };
47816
+ const compactNoStats = JSON.stringify({ data: partial2 }, null, 2).length;
47817
+ const statsOverhead = 220;
47818
+ const compactChars = compactNoStats + statsOverhead;
47819
+ const savedChars = originalChars - compactChars;
47820
+ const savedRatio = originalChars > 0 ? Math.max(0, savedChars / originalChars) : 0;
47821
+ const envelope = {
47822
+ ...partial2,
47823
+ stats: {
47824
+ originalChars,
47825
+ compactChars,
47826
+ savedChars,
47827
+ savedRatio: Number(savedRatio.toFixed(4))
47828
+ }
47829
+ };
47830
+ return { envelope, shrunk: compactChars < originalChars };
47831
+ }
47832
+ function runCompactor(dispatch, policy) {
47833
+ switch (dispatch.kind) {
47834
+ case "task-list":
47835
+ return compactTaskList(dispatch.projectSlug, dispatch.rawValue, policy.maxItems);
47836
+ case "wiki-list":
47837
+ return compactWikiPageList(dispatch.projectSlug, dispatch.rawValue, policy.maxItems);
47838
+ case "wiki-search":
47839
+ return compactWikiSearch(
47840
+ dispatch.projectSlug,
47841
+ dispatch.rawValue,
47842
+ policy.maxItems
47843
+ );
47844
+ case "project-snapshot":
47845
+ return compactProjectSnapshot(
47846
+ dispatch.projectSlug,
47847
+ dispatch.rawValue,
47848
+ policy.maxItems
47849
+ );
47850
+ case "execution-artifacts":
47851
+ return compactExecutionArtifacts(
47852
+ dispatch.projectSlug,
47853
+ dispatch.rawValue,
47854
+ policy.maxItems
47855
+ );
47856
+ case "task-events": {
47857
+ if (!dispatch.taskId) throw new Error("task-events compact requires taskId");
47858
+ return compactTaskEvents(dispatch.projectSlug, dispatch.taskId, dispatch.rawValue, policy.maxItems);
47859
+ }
47860
+ case "generic-json":
47861
+ return compactGeneric(dispatch.rawValue, policy.maxItems, dispatch.projectSlug);
47862
+ }
47863
+ }
47864
+ function maybeCompact(dispatch, policy = readCompactPolicy()) {
47865
+ if (!policy.enabled) return null;
47866
+ const originalChars = serializedChars(dispatch.rawValue);
47867
+ if (originalChars <= policy.thresholdChars) return null;
47868
+ const body = runCompactor(dispatch, policy);
47869
+ const result = buildEnvelope(dispatch, body, originalChars);
47870
+ if (policy.debug) {
47871
+ process.stderr.write(
47872
+ `[mrrlin-mcp compact] tool=${dispatch.toolName} kind=${dispatch.kind} originalChars=${originalChars} compactChars=${result.envelope.stats.compactChars} savedRatio=${result.envelope.stats.savedRatio}
47873
+ `
47874
+ );
47875
+ }
47876
+ if (!result.shrunk) return null;
47877
+ return result.envelope;
47878
+ }
47879
+ var COMPACT_OPT_IN = {
47880
+ [mcpToolNames.listTasks]: "task-list",
47881
+ [mcpToolNames.listWikiPages]: "wiki-list",
47882
+ [mcpToolNames.searchWikiPages]: "wiki-search",
47883
+ [mcpToolNames.getProjectSnapshot]: "project-snapshot",
47884
+ [mcpToolNames.listExecutionArtifacts]: "execution-artifacts",
47885
+ [mcpToolNames.listTaskEvents]: "task-events"
47354
47886
  };
47887
+
47888
+ // src/tools.ts
47889
+ registerAsyncTool(consensusDescriptor);
47355
47890
  var projectScopedSchema = external_exports.object({ projectSlug: mrrlinProjectSlugSchema });
47356
47891
  var taskScopedSchema = projectScopedSchema.extend({ taskId: mrrlinTaskIdSchema });
47357
47892
  var planScopedSchema = projectScopedSchema.extend({ planId: mrrlinPlanIdSchema });
@@ -47403,7 +47938,6 @@ var runCodeReviewerGateInputSchema = external_exports.object({
47403
47938
  var searchKnowledgeInputSchema = wikiKnowledgeSearchInputSchema;
47404
47939
  var createGithubBranchInputSchema = projectScopedSchema.merge(githubCreateBranchRequestSchema);
47405
47940
  var createGithubPullRequestInputSchema = projectScopedSchema.merge(githubCreatePullRequestRequestSchema);
47406
- var listGithubWebhookDeliveriesInputSchema = projectScopedSchema;
47407
47941
  var listGithubWorkflowsInputSchema = projectScopedSchema;
47408
47942
  var dispatchGithubWorkflowInputSchema = projectScopedSchema.extend({ workflowId: external_exports.string().min(1) }).merge(githubWorkflowDispatchRequestSchema);
47409
47943
  var dispatchTaskDeployInputSchema = taskScopedSchema.extend({
@@ -47448,6 +47982,18 @@ var listInboxItemsInputSchema = projectScopedSchema.merge(mrrlinInboxItemListFil
47448
47982
  var createInboxItemInputSchema = projectScopedSchema.merge(mrrlinInboxItemCreateSchema);
47449
47983
  var decideInboxItemInputSchema = projectScopedSchema.extend({ itemId: mrrlinInboxItemIdSchema }).merge(mrrlinInboxItemDecideSchema);
47450
47984
  var resolveHandoffInputSchema = projectScopedSchema.extend({ runId: mrrlinExecutionRunIdSchema }).merge(mrrlinResolveHandoffSchema);
47985
+ var registerLocalCheckoutInputSchema = projectScopedSchema.extend({
47986
+ path: external_exports.string().min(1).describe("Absolute path to the local git checkout (must be a git repo whose origin matches `repo`)."),
47987
+ repo: external_exports.string().min(1).describe("The project's bound GitHub repo full name in `owner/repo` form.")
47988
+ });
47989
+ var retrieveContextInputSchema = external_exports.object({
47990
+ handle: external_exports.string().min(1).describe(
47991
+ "A mrrlin:// handle emitted in a compact envelope. Examples: mrrlin://task/{slug}/GT-123, mrrlin://wiki/{slug}/WK-1, mrrlin://project/{slug}/snapshot, mrrlin://run/{slug}/ER-9, mrrlin://run/{slug}/ER-9/artifacts."
47992
+ ),
47993
+ mode: external_exports.enum(["full", "summary", "metadata"]).optional().describe(
47994
+ "Reserved. v1 always returns the full record regardless; future versions will use this to scope rehydration."
47995
+ )
47996
+ });
47451
47997
  var mcpToolInputSchemas = {
47452
47998
  [mcpToolNames.appendExecutionArtifact]: appendExecutionArtifactInputSchema,
47453
47999
  [mcpToolNames.archivePlan]: archivePlanInputSchema,
@@ -47480,7 +48026,6 @@ var mcpToolInputSchemas = {
47480
48026
  [mcpToolNames.getGithubInstallationRepositories]: getGithubInstallationRepositoriesInputSchema,
47481
48027
  [mcpToolNames.listExecutionArtifacts]: listExecutionArtifactsInputSchema,
47482
48028
  [mcpToolNames.listExecutionRuns]: listExecutionRunsInputSchema,
47483
- [mcpToolNames.listGithubWebhookDeliveries]: listGithubWebhookDeliveriesInputSchema,
47484
48029
  [mcpToolNames.listGithubInstallations]: listGithubInstallationsInputSchema,
47485
48030
  [mcpToolNames.listGithubWorkflows]: listGithubWorkflowsInputSchema,
47486
48031
  [mcpToolNames.listPlans]: listPlansInputSchema,
@@ -47506,7 +48051,9 @@ var mcpToolInputSchemas = {
47506
48051
  [mcpToolNames.decideInboxItem]: decideInboxItemInputSchema,
47507
48052
  [mcpToolNames.uploadArtifact]: uploadArtifactInputSchema,
47508
48053
  [mcpToolNames.listArtifactFiles]: listArtifactFilesInputSchema,
47509
- [mcpToolNames.resolveHandoff]: resolveHandoffInputSchema
48054
+ [mcpToolNames.resolveHandoff]: resolveHandoffInputSchema,
48055
+ [mcpToolNames.registerLocalCheckout]: registerLocalCheckoutInputSchema,
48056
+ [mcpToolNames.retrieveContext]: retrieveContextInputSchema
47510
48057
  };
47511
48058
  var mcpToolOperationIds = {
47512
48059
  [mcpToolNames.appendExecutionArtifact]: openApiOperationIds.appendExecutionArtifact,
@@ -47540,7 +48087,6 @@ var mcpToolOperationIds = {
47540
48087
  [mcpToolNames.getGithubInstallationRepositories]: openApiOperationIds.getGithubInstallationRepositories,
47541
48088
  [mcpToolNames.listExecutionArtifacts]: openApiOperationIds.listExecutionArtifacts,
47542
48089
  [mcpToolNames.listExecutionRuns]: openApiOperationIds.listExecutionRuns,
47543
- [mcpToolNames.listGithubWebhookDeliveries]: openApiOperationIds.listGithubWebhookDeliveries,
47544
48090
  [mcpToolNames.listGithubInstallations]: openApiOperationIds.listGithubInstallations,
47545
48091
  [mcpToolNames.listGithubWorkflows]: openApiOperationIds.listGithubWorkflows,
47546
48092
  [mcpToolNames.listPlans]: openApiOperationIds.listPlans,
@@ -47590,7 +48136,6 @@ var mcpToolDescriptions = {
47590
48136
  [mcpToolNames.dispatchGithubWorkflow]: "Dispatch a GitHub Actions workflow by workflow file/id against a ref visible to the linked installation.",
47591
48137
  [mcpToolNames.dispatchTaskDeploy]: "Dispatch a task's dev or prod deploy; caller supplies workflowId chosen for this run (ADR 0012). Subject to auto_deploy policy and prod dev-verification gate.",
47592
48138
  [mcpToolNames.getGithubInstallationRepositories]: "List repositories visible to a GitHub App installation id.",
47593
- [mcpToolNames.listGithubWebhookDeliveries]: "List recent persisted GitHub webhook deliveries for the project so install/webhook smoke can be verified without raw DB access.",
47594
48139
  [mcpToolNames.listGithubInstallations]: "List persisted GitHub App installations.",
47595
48140
  [mcpToolNames.listGithubWorkflows]: "List the bound repository's GitHub Actions workflows live through the linked installation. No Mrrlin-side cache.",
47596
48141
  [mcpToolNames.listProjects]: "List every project visible to the operator.",
@@ -47624,7 +48169,9 @@ var mcpToolDescriptions = {
47624
48169
  [mcpToolNames.decideInboxItem]: "Approve, reject, or acknowledge an operator-inbox item. Rejection requires a reason.",
47625
48170
  [mcpToolNames.uploadArtifact]: "Upload a local file (md/txt/html/csv/json/pdf/png/jpg/webp/gif, <=5MB) as a project artifact. Returns a stable markdown link to embed in chat/spec/handoff text. Use class=temp for one-off hand-offs, class=durable for anything referenced by specs or evidence. Pass runId to record the file in that run's artifact history; pass taskId to surface it on the task's Artifacts list.",
47626
48171
  [mcpToolNames.listArtifactFiles]: "List uploaded artifact files for the project (optionally filtered by taskId/runId), newest first.",
47627
- [mcpToolNames.resolveHandoff]: "Resolve a tool_assisted handoff for an execution run (outcome success|failure). Success returns the task to the board; failure opens a new handoff cycle. Idempotent."
48172
+ [mcpToolNames.resolveHandoff]: "Resolve a tool_assisted handoff for an execution run (outcome success|failure). Success returns the task to the board; failure opens a new handoff cycle. Idempotent.",
48173
+ [mcpToolNames.registerLocalCheckout]: "Register the operator-local git checkout path for `projectSlug` so execution-run workers run code on this machine instead of the cloud. Validates the path is a git repo whose `origin` remote matches `repo`, then writes the (slug -> absolute path) entry to the operator-local checkout registry. Call this after locating the checkout on disk (e.g. via filesystem search) and confirming the choice with the user in chat \u2014 they should NOT have to paste the path into Settings. Returns `{ projectSlug, path, confirmedAt }`. Idempotent: re-registering overwrites the prior entry.",
48174
+ [mcpToolNames.retrieveContext]: "Rehydrate a mrrlin:// handle emitted by the compact-context layer into the full canonical Mrrlin record (task/wiki page/project snapshot/execution run/artifacts). Use whenever you need the full source \u2014 irreversible decisions (closing tasks, dispatching deploys, writing specs) MUST call this first; never act on a compact envelope alone. Returns `{ kind, data }`. Invalid or unsupported handle \u2192 structured `HANDLE_INVALID` error."
47628
48175
  };
47629
48176
  var ARTIFACT_EXTENSION_TYPES = {
47630
48177
  ".md": "text/markdown",
@@ -47728,6 +48275,41 @@ function makeTool(client, name, fallbackCode, fallbackMessage, handler) {
47728
48275
  }
47729
48276
  };
47730
48277
  }
48278
+ function structuredOkCompactEnvelope(envelope) {
48279
+ return {
48280
+ content: [{ text: JSON.stringify({ data: envelope }, null, 2), type: "text" }],
48281
+ isError: false,
48282
+ structuredContent: { data: envelope }
48283
+ };
48284
+ }
48285
+ function structuredOkMaybeCompact(name, projectSlug, value, context = {}) {
48286
+ const kind = COMPACT_OPT_IN[name];
48287
+ if (!kind) return structuredOk(value);
48288
+ const envelope = maybeCompact(
48289
+ { toolName: name, projectSlug, kind, rawValue: value, taskId: context.taskId },
48290
+ readCompactPolicy()
48291
+ );
48292
+ if (!envelope) return structuredOk(value);
48293
+ return structuredOkCompactEnvelope(envelope);
48294
+ }
48295
+ function makeReadTool(client, name, fallbackCode, fallbackMessage, handler, projectSlugFrom, taskIdFrom) {
48296
+ return {
48297
+ description: mcpToolDescriptions[name],
48298
+ inputSchema: mcpToolInputSchemas[name],
48299
+ name,
48300
+ openApiOperationId: mcpToolOperationIds[name],
48301
+ async invoke(input) {
48302
+ try {
48303
+ const value = await handler(client, input);
48304
+ return structuredOkMaybeCompact(name, projectSlugFrom(input), value, {
48305
+ taskId: taskIdFrom ? taskIdFrom(input) : void 0
48306
+ });
48307
+ } catch (error51) {
48308
+ return structuredErr(classifyClientError(fallbackCode, fallbackMessage, error51));
48309
+ }
48310
+ }
48311
+ };
48312
+ }
47731
48313
  function createMrrlinTools(options) {
47732
48314
  const client = createMrrlinClient(withAuth(options));
47733
48315
  return {
@@ -47864,12 +48446,13 @@ function createMrrlinTools(options) {
47864
48446
  "Unable to read project.",
47865
48447
  async (c, { projectSlug }) => await c.getProject(projectSlug)
47866
48448
  ),
47867
- [mcpToolNames.getProjectSnapshot]: makeTool(
48449
+ [mcpToolNames.getProjectSnapshot]: makeReadTool(
47868
48450
  client,
47869
48451
  mcpToolNames.getProjectSnapshot,
47870
48452
  "PROJECT_SNAPSHOT_READ_FAILED",
47871
48453
  "Unable to read project snapshot.",
47872
- async (c, { projectSlug }) => await c.getProjectSnapshot(projectSlug)
48454
+ async (c, { projectSlug }) => await c.getProjectSnapshot(projectSlug),
48455
+ ({ projectSlug }) => projectSlug
47873
48456
  ),
47874
48457
  [mcpToolNames.getTask]: makeTool(
47875
48458
  client,
@@ -47899,13 +48482,6 @@ function createMrrlinTools(options) {
47899
48482
  "Unable to dispatch the task deploy workflow.",
47900
48483
  async (c, { projectSlug, taskId, workflowId, ...rest }) => await c.dispatchTaskDeploy(projectSlug, taskId, { ...rest, workflowId })
47901
48484
  ),
47902
- [mcpToolNames.listGithubWebhookDeliveries]: makeTool(
47903
- client,
47904
- mcpToolNames.listGithubWebhookDeliveries,
47905
- "GITHUB_WEBHOOK_DELIVERY_LIST_FAILED",
47906
- "Unable to list GitHub webhook deliveries.",
47907
- async (c, { projectSlug }) => await c.listGithubWebhookDeliveries(projectSlug)
47908
- ),
47909
48485
  [mcpToolNames.listGithubWorkflows]: makeTool(
47910
48486
  client,
47911
48487
  mcpToolNames.listGithubWorkflows,
@@ -47934,19 +48510,22 @@ function createMrrlinTools(options) {
47934
48510
  "Unable to list task dependencies.",
47935
48511
  async (c, { projectSlug }) => await c.listTaskDependencies(projectSlug)
47936
48512
  ),
47937
- [mcpToolNames.listTaskEvents]: makeTool(
48513
+ [mcpToolNames.listTaskEvents]: makeReadTool(
47938
48514
  client,
47939
48515
  mcpToolNames.listTaskEvents,
47940
48516
  "TASK_EVENT_LIST_FAILED",
47941
48517
  "Unable to list task events.",
47942
- async (c, { projectSlug, taskId }) => await c.listTaskEvents(projectSlug, taskId)
48518
+ async (c, { projectSlug, taskId }) => await c.listTaskEvents(projectSlug, taskId),
48519
+ ({ projectSlug }) => projectSlug,
48520
+ ({ taskId }) => taskId
47943
48521
  ),
47944
- [mcpToolNames.listTasks]: makeTool(
48522
+ [mcpToolNames.listTasks]: makeReadTool(
47945
48523
  client,
47946
48524
  mcpToolNames.listTasks,
47947
48525
  "TASK_LIST_FAILED",
47948
48526
  "Unable to list tasks.",
47949
- async (c, { projectSlug, ...filters }) => await c.listTasks(projectSlug, filters)
48527
+ async (c, { projectSlug, ...filters }) => await c.listTasks(projectSlug, filters),
48528
+ ({ projectSlug }) => projectSlug
47950
48529
  ),
47951
48530
  [mcpToolNames.listTaxonomy]: makeTool(
47952
48531
  client,
@@ -47955,19 +48534,21 @@ function createMrrlinTools(options) {
47955
48534
  "Unable to list taxonomy.",
47956
48535
  async (c, { projectSlug }) => await c.listTaxonomy(projectSlug)
47957
48536
  ),
47958
- [mcpToolNames.listWikiPages]: makeTool(
48537
+ [mcpToolNames.listWikiPages]: makeReadTool(
47959
48538
  client,
47960
48539
  mcpToolNames.listWikiPages,
47961
48540
  "WIKI_PAGE_LIST_FAILED",
47962
48541
  "Unable to list wiki pages.",
47963
- async (c, { projectSlug, ...filters }) => await c.listWikiPages(projectSlug, filters)
48542
+ async (c, { projectSlug, ...filters }) => await c.listWikiPages(projectSlug, filters),
48543
+ ({ projectSlug }) => projectSlug
47964
48544
  ),
47965
- [mcpToolNames.searchWikiPages]: makeTool(
48545
+ [mcpToolNames.searchWikiPages]: makeReadTool(
47966
48546
  client,
47967
48547
  mcpToolNames.searchWikiPages,
47968
48548
  "WIKI_PAGE_SEARCH_FAILED",
47969
48549
  "Unable to search wiki pages.",
47970
- async (c, { projectSlug, ...query }) => await c.searchWikiPages(projectSlug, query)
48550
+ async (c, { projectSlug, ...query }) => await c.searchWikiPages(projectSlug, query),
48551
+ ({ projectSlug }) => projectSlug
47971
48552
  ),
47972
48553
  [mcpToolNames.searchKnowledge]: makeTool(
47973
48554
  client,
@@ -48114,12 +48695,13 @@ function createMrrlinTools(options) {
48114
48695
  "Unable to update execution run.",
48115
48696
  async (c, { projectSlug, runId, ...patch }) => await c.updateExecutionRun(projectSlug, runId, patch)
48116
48697
  ),
48117
- [mcpToolNames.listExecutionArtifacts]: makeTool(
48698
+ [mcpToolNames.listExecutionArtifacts]: makeReadTool(
48118
48699
  client,
48119
48700
  mcpToolNames.listExecutionArtifacts,
48120
48701
  "EXECUTION_ARTIFACTS_LIST_FAILED",
48121
48702
  "Unable to list execution artifacts.",
48122
- async (c, { projectSlug, runId }) => await c.listExecutionArtifacts(projectSlug, runId)
48703
+ async (c, { projectSlug, runId }) => await c.listExecutionArtifacts(projectSlug, runId),
48704
+ ({ projectSlug }) => projectSlug
48123
48705
  ),
48124
48706
  [mcpToolNames.appendExecutionArtifact]: makeTool(
48125
48707
  client,
@@ -48186,7 +48768,7 @@ function createMrrlinTools(options) {
48186
48768
  "ARTIFACT_FILE_UPLOAD_FAILED",
48187
48769
  "Unable to upload artifact file.",
48188
48770
  async (c, { projectSlug, path: filePath, class: artifactClass, taskId, runId, description }) => {
48189
- const extension2 = import_node_path15.default.extname(filePath).toLowerCase();
48771
+ const extension2 = import_node_path14.default.extname(filePath).toLowerCase();
48190
48772
  const contentType = ARTIFACT_EXTENSION_TYPES[extension2];
48191
48773
  if (!contentType) {
48192
48774
  throw new McpToolCodedError(
@@ -48214,7 +48796,7 @@ function createMrrlinTools(options) {
48214
48796
  "Artifact exceeds 5MB; split or summarize instead."
48215
48797
  );
48216
48798
  }
48217
- const filename = import_node_path15.default.basename(filePath);
48799
+ const filename = import_node_path14.default.basename(filePath);
48218
48800
  const created = await c.createArtifactFile(projectSlug, {
48219
48801
  class: artifactClass,
48220
48802
  contentType,
@@ -48289,9 +48871,98 @@ function createMrrlinTools(options) {
48289
48871
  "HANDOFF_RESOLVE_FAILED",
48290
48872
  "Unable to resolve handoff.",
48291
48873
  async (c, { projectSlug, runId, ...input }) => await c.resolveHandoff(projectSlug, runId, input)
48874
+ ),
48875
+ [mcpToolNames.registerLocalCheckout]: makeTool(
48876
+ client,
48877
+ mcpToolNames.registerLocalCheckout,
48878
+ "LOCAL_CHECKOUT_REGISTER_FAILED",
48879
+ "Unable to register local checkout.",
48880
+ async (_c, { projectSlug, path: checkoutPath, repo }) => {
48881
+ const trimmedPath = checkoutPath.trim();
48882
+ if (!import_node_path14.default.isAbsolute(trimmedPath)) {
48883
+ throw new McpToolCodedError(
48884
+ "LOCAL_CHECKOUT_PATH_NOT_ABSOLUTE",
48885
+ `Path must be absolute, got: ${trimmedPath}`
48886
+ );
48887
+ }
48888
+ let resolvedPath;
48889
+ try {
48890
+ resolvedPath = (0, import_node_fs12.realpathSync)(trimmedPath);
48891
+ } catch {
48892
+ throw new McpToolCodedError(
48893
+ "LOCAL_CHECKOUT_PATH_NOT_FOUND",
48894
+ `Path does not exist or is not accessible: ${trimmedPath}`
48895
+ );
48896
+ }
48897
+ let remoteUrl;
48898
+ try {
48899
+ remoteUrl = (0, import_node_child_process6.execFileSync)("git", ["-C", resolvedPath, "remote", "get-url", "origin"], {
48900
+ encoding: "utf8",
48901
+ stdio: ["ignore", "pipe", "ignore"],
48902
+ timeout: 15e3,
48903
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
48904
+ }).trim();
48905
+ } catch {
48906
+ throw new McpToolCodedError(
48907
+ "LOCAL_CHECKOUT_NOT_A_GIT_REPO",
48908
+ `Path is not a git repository or has no origin remote: ${resolvedPath}`
48909
+ );
48910
+ }
48911
+ if (!remoteMatchesRepo(remoteUrl, repo)) {
48912
+ throw new McpToolCodedError(
48913
+ "LOCAL_CHECKOUT_REMOTE_MISMATCH",
48914
+ `Origin remote ${remoteUrl} does not match repo ${repo}.`
48915
+ );
48916
+ }
48917
+ const stateDir = process.env.CODEX_HOME ? import_node_path14.default.join(process.env.CODEX_HOME, "mrrlin", "director-bridge") : import_node_path14.default.join(import_node_os8.default.homedir(), ".mrrlin", "director-bridge");
48918
+ const registry3 = new CheckoutRegistry(stateDir);
48919
+ const confirmedAt = (/* @__PURE__ */ new Date()).toISOString();
48920
+ registry3.confirm(projectSlug, resolvedPath, confirmedAt);
48921
+ return { projectSlug, path: resolvedPath, confirmedAt };
48922
+ }
48923
+ ),
48924
+ [mcpToolNames.retrieveContext]: makeTool(
48925
+ client,
48926
+ mcpToolNames.retrieveContext,
48927
+ "HANDLE_INVALID",
48928
+ "Unable to retrieve handle.",
48929
+ async (c, { handle }) => {
48930
+ const parsed = parseMrrlinHandle(handle);
48931
+ if (!parsed) {
48932
+ throw new McpToolCodedError(
48933
+ "HANDLE_INVALID",
48934
+ `Unrecognized mrrlin:// handle: ${handle}. Valid kinds: task | wiki | project/{slug}/snapshot | project/{slug}/tasks | run/{slug}/{runId} | run/{slug}/{runId}/artifacts.`
48935
+ );
48936
+ }
48937
+ return await dispatchHandle(c, parsed);
48938
+ }
48292
48939
  )
48293
48940
  };
48294
48941
  }
48942
+ async function dispatchHandle(client, handle) {
48943
+ switch (handle.kind) {
48944
+ case "task":
48945
+ return { kind: handle.kind, data: await client.getTask(handle.projectSlug, handle.taskId) };
48946
+ case "task-events":
48947
+ return {
48948
+ kind: handle.kind,
48949
+ data: await client.listTaskEvents(handle.projectSlug, handle.taskId)
48950
+ };
48951
+ case "wiki":
48952
+ return { kind: handle.kind, data: await client.getWikiPage(handle.projectSlug, handle.pageId) };
48953
+ case "project-snapshot":
48954
+ return { kind: handle.kind, data: await client.getProjectSnapshot(handle.projectSlug) };
48955
+ case "project-tasks":
48956
+ return { kind: handle.kind, data: await client.listTasks(handle.projectSlug, {}) };
48957
+ case "run":
48958
+ return { kind: handle.kind, data: await client.getExecutionRun(handle.projectSlug, handle.runId) };
48959
+ case "run-artifacts":
48960
+ return {
48961
+ kind: handle.kind,
48962
+ data: await client.listExecutionArtifacts(handle.projectSlug, handle.runId)
48963
+ };
48964
+ }
48965
+ }
48295
48966
 
48296
48967
  // src/server.ts
48297
48968
  function redactUrlForLog(value) {
@@ -48450,13 +49121,13 @@ function runSetCredential(args) {
48450
49121
  }
48451
49122
 
48452
49123
  // src/install-service.ts
48453
- var import_node_os8 = __toESM(require("node:os"), 1);
48454
- var import_node_path17 = __toESM(require("node:path"), 1);
49124
+ var import_node_os9 = __toESM(require("node:os"), 1);
49125
+ var import_node_path16 = __toESM(require("node:path"), 1);
48455
49126
 
48456
49127
  // src/service-paths.ts
48457
- var import_node_path16 = __toESM(require("node:path"), 1);
49128
+ var import_node_path15 = __toESM(require("node:path"), 1);
48458
49129
  function isWorktreePath(p) {
48459
- const norm = import_node_path16.default.normalize(p).split(import_node_path16.default.sep).join("/");
49130
+ const norm = import_node_path15.default.normalize(p).split(import_node_path15.default.sep).join("/");
48460
49131
  return norm.includes("/.claude/worktrees/");
48461
49132
  }
48462
49133
  function resolveServiceCwd(opts) {
@@ -48509,7 +49180,7 @@ function installService(deps) {
48509
49180
  deps.log(resolved.reason);
48510
49181
  return { ok: false };
48511
49182
  }
48512
- const home = deps.env.HOME ?? import_node_os8.default.homedir();
49183
+ const home = deps.env.HOME ?? import_node_os9.default.homedir();
48513
49184
  const bins = ["node", "codex", "mrrlin-mcp"].map((b) => deps.which(b));
48514
49185
  if (bins.some((b) => !b)) {
48515
49186
  deps.log("Could not resolve absolute paths for node/codex/mrrlin-mcp on PATH. Install them or fix PATH.");
@@ -48520,7 +49191,7 @@ function installService(deps) {
48520
49191
  return { ok: false };
48521
49192
  }
48522
49193
  const resolvedBins = bins.filter((b) => b !== null);
48523
- const pathEnv = Array.from(new Set(resolvedBins.map((b) => import_node_path17.default.dirname(b)))).join(":");
49194
+ const pathEnv = Array.from(new Set(resolvedBins.map((b) => import_node_path16.default.dirname(b)))).join(":");
48524
49195
  const text = renderEcosystemConfig({
48525
49196
  cwd: resolved.cwd,
48526
49197
  home,
@@ -48529,7 +49200,7 @@ function installService(deps) {
48529
49200
  staging: deps.env.MRRLIN_STAGING === "1",
48530
49201
  apiBaseUrl: deps.env.MRRLIN_API_BASE_URL?.trim() || void 0
48531
49202
  });
48532
- const ecoPath = import_node_path17.default.join(home, ".mrrlin", "ecosystem.config.cjs");
49203
+ const ecoPath = import_node_path16.default.join(home, ".mrrlin", "ecosystem.config.cjs");
48533
49204
  deps.writeFile(ecoPath, text);
48534
49205
  const start = deps.runPm2(["startOrReload", ecoPath]);
48535
49206
  if (start.code !== 0) {
@@ -48560,13 +49231,13 @@ function uninstallService(deps) {
48560
49231
 
48561
49232
  // src/uninstall-codex.ts
48562
49233
  var import_promises4 = __toESM(require("node:fs/promises"), 1);
48563
- var import_node_os9 = __toESM(require("node:os"), 1);
48564
- var import_node_path18 = __toESM(require("node:path"), 1);
49234
+ var import_node_os10 = __toESM(require("node:os"), 1);
49235
+ var import_node_path17 = __toESM(require("node:path"), 1);
48565
49236
  var toml2 = __toESM(require_toml(), 1);
48566
49237
  function resolveCodexHome2(options) {
48567
49238
  if (options.codexHome) return options.codexHome;
48568
49239
  if (process.env.CODEX_HOME) return process.env.CODEX_HOME;
48569
- return import_node_path18.default.join(options.homeDir ?? import_node_os9.default.homedir(), ".codex");
49240
+ return import_node_path17.default.join(options.homeDir ?? import_node_os10.default.homedir(), ".codex");
48570
49241
  }
48571
49242
  async function pathExists3(target) {
48572
49243
  try {
@@ -48579,7 +49250,7 @@ async function pathExists3(target) {
48579
49250
  var MRRLIN_BLOCK_RE2 = /(^|\n)\[mcp_servers\.mrrlin(?:\]|\.[^\]\n]*\])[\s\S]*?(?=\n\[|$)/g;
48580
49251
  async function uninstallCodex(options = {}) {
48581
49252
  const codexHome = resolveCodexHome2(options);
48582
- const configPath = import_node_path18.default.join(codexHome, "config.toml");
49253
+ const configPath = import_node_path17.default.join(codexHome, "config.toml");
48583
49254
  const removePrompts = async () => uninstallPrompts(codexHome, options.promptNames ?? []);
48584
49255
  if (!await pathExists3(configPath)) {
48585
49256
  return { action: "missing", configPath, prompts: await removePrompts() };
@@ -48607,10 +49278,10 @@ async function uninstallCodex(options = {}) {
48607
49278
  }
48608
49279
  async function uninstallPrompts(codexHome, names) {
48609
49280
  if (names.length === 0) return [];
48610
- const promptsDir2 = import_node_path18.default.join(codexHome, "prompts");
49281
+ const promptsDir2 = import_node_path17.default.join(codexHome, "prompts");
48611
49282
  const out = [];
48612
49283
  for (const name of names) {
48613
- const promptPath = import_node_path18.default.join(promptsDir2, `${name}.md`);
49284
+ const promptPath = import_node_path17.default.join(promptsDir2, `${name}.md`);
48614
49285
  try {
48615
49286
  await import_promises4.default.unlink(promptPath);
48616
49287
  out.push({ name, action: "removed", promptPath });
@@ -48628,15 +49299,15 @@ async function uninstallPrompts(codexHome, names) {
48628
49299
 
48629
49300
  // src/report-issue-prompt.ts
48630
49301
  var import_node_fs13 = __toESM(require("node:fs"), 1);
48631
- var import_node_path19 = __toESM(require("node:path"), 1);
49302
+ var import_node_path18 = __toESM(require("node:path"), 1);
48632
49303
  var import_node_url2 = require("node:url");
48633
49304
  var import_meta2 = {};
48634
49305
  function promptsDir() {
48635
- if (typeof __dirname !== "undefined") return import_node_path19.default.join(__dirname, "prompts");
48636
- return import_node_path19.default.join(import_node_path19.default.dirname((0, import_node_url2.fileURLToPath)(import_meta2.url)), "prompts");
49306
+ if (typeof __dirname !== "undefined") return import_node_path18.default.join(__dirname, "prompts");
49307
+ return import_node_path18.default.join(import_node_path18.default.dirname((0, import_node_url2.fileURLToPath)(import_meta2.url)), "prompts");
48637
49308
  }
48638
49309
  function readReportIssuePrompt() {
48639
- return import_node_fs13.default.readFileSync(import_node_path19.default.join(promptsDir(), "report-issue.md"), "utf8");
49310
+ return import_node_fs13.default.readFileSync(import_node_path18.default.join(promptsDir(), "report-issue.md"), "utf8");
48640
49311
  }
48641
49312
 
48642
49313
  // src/bin.ts
@@ -48662,14 +49333,18 @@ Usage:
48662
49333
 
48663
49334
  mrrlin-mcp install-codex Idempotently register Mrrlin in
48664
49335
  [--force] ~/.codex/config.toml (or CODEX_HOME). Adds an
48665
- [mcp_servers.mrrlin] block AND drops the bundled
49336
+ [--force-prompts] [mcp_servers.mrrlin] block AND drops the bundled
48666
49337
  slash-command prompts (currently: /report-issue)
48667
49338
  into <CODEX_HOME>/prompts/. Development
48668
49339
  checkouts register the local dist/bin.cjs;
48669
49340
  published npm installs register
48670
49341
  \`mrrlin-mcp serve\`.
48671
- --force replaces
48672
- an existing conflicting block.
49342
+ --force replaces an existing conflicting block.
49343
+ --force-prompts overwrites prompt files that
49344
+ already exist with different content. WITHOUT it,
49345
+ local edits to <CODEX_HOME>/prompts/<name>.md are
49346
+ preserved across patch releases (reported as
49347
+ 'skipped-modified').
48673
49348
 
48674
49349
  mrrlin-mcp director-bridge Run a local ws://127.0.0.1 (plain HTTP +
48675
49350
  WebSocket) bridge that lets the web Director
@@ -48747,6 +49422,14 @@ Usage:
48747
49422
  binary) and the Settings credential revoke. Leaves
48748
49423
  global pm2 alone (it may be used elsewhere).
48749
49424
 
49425
+ mrrlin-mcp redact Scrub known secret shapes (GitHub PATs,
49426
+ Bearer/JWT tokens, long hex / base64-ish runs)
49427
+ from stdin and write the result to stdout. Same
49428
+ regex set as the bridge logger. Empty input ->
49429
+ empty output, exit 0. Used by the /report-issue
49430
+ prompt to ensure no raw text reaches Telegram:
49431
+ printf %s "$HINT" | mrrlin-mcp redact
49432
+
48750
49433
  mrrlin-mcp report-issue Print the bundled support-report prompt to
48751
49434
  stdout. Normal users don't need this \u2014 install-codex
48752
49435
  already drops it as a /report-issue slash command.
@@ -48785,10 +49468,12 @@ async function main() {
48785
49468
  }
48786
49469
  case "install-codex": {
48787
49470
  const force = rest.includes("--force");
49471
+ const forcePrompts = rest.includes("--force-prompts");
48788
49472
  const binPath = resolveSelfBinPath();
48789
49473
  const result = await installCodex({
48790
49474
  binPath,
48791
49475
  force,
49476
+ forcePrompts,
48792
49477
  prompts: [{ name: "report-issue", content: readReportIssuePrompt() }]
48793
49478
  });
48794
49479
  process.stderr.write(`[mrrlin-mcp install-codex] ${result.action} ${result.configPath}
@@ -48796,7 +49481,22 @@ async function main() {
48796
49481
  for (const p of result.prompts) {
48797
49482
  process.stderr.write(`[mrrlin-mcp install-codex] prompt ${p.name}: ${p.action} ${p.promptPath}
48798
49483
  `);
49484
+ if (p.action === "skipped-modified") {
49485
+ process.stderr.write(
49486
+ `[mrrlin-mcp install-codex] prompt ${p.name}: kept your local edits; run with --force-prompts to overwrite (or delete ${p.promptPath} to take the new bundled version)
49487
+ `
49488
+ );
49489
+ }
49490
+ }
49491
+ return;
49492
+ }
49493
+ case "redact": {
49494
+ const chunks = [];
49495
+ for await (const chunk of process.stdin) {
49496
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
48799
49497
  }
49498
+ const input = Buffer.concat(chunks).toString("utf8");
49499
+ process.stdout.write(redact(input));
48800
49500
  return;
48801
49501
  }
48802
49502
  case "report-issue": {
@@ -48819,7 +49519,7 @@ async function main() {
48819
49519
  env: process.env,
48820
49520
  cwd: process.cwd(),
48821
49521
  writeFile: (p, c) => {
48822
- (0, import_node_fs14.mkdirSync)(import_node_path21.default.dirname(p), { recursive: true, mode: 448 });
49522
+ (0, import_node_fs14.mkdirSync)(import_node_path20.default.dirname(p), { recursive: true, mode: 448 });
48823
49523
  (0, import_node_fs14.writeFileSync)(p, c, { mode: 384 });
48824
49524
  },
48825
49525
  runPm2: pm2Runner,
@@ -48844,14 +49544,14 @@ async function main() {
48844
49544
  purgeSecret: purge,
48845
49545
  secretPath: agentCredentialPath(),
48846
49546
  tokenPath: operatorTokenPath(),
48847
- ecoPath: import_node_path21.default.join(process.env.HOME ?? (0, import_node_os10.homedir)(), ".mrrlin", "ecosystem.config.cjs"),
49547
+ ecoPath: import_node_path20.default.join(process.env.HOME ?? (0, import_node_os11.homedir)(), ".mrrlin", "ecosystem.config.cjs"),
48848
49548
  log: (m) => process.stderr.write(`[mrrlin-mcp uninstall-service] ${m}
48849
49549
  `)
48850
49550
  });
48851
49551
  return;
48852
49552
  }
48853
49553
  case "uninstall": {
48854
- const home = process.env.HOME ?? (0, import_node_os10.homedir)();
49554
+ const home = process.env.HOME ?? (0, import_node_os11.homedir)();
48855
49555
  const log = (m) => process.stderr.write(`[mrrlin-mcp uninstall] ${m}
48856
49556
  `);
48857
49557
  uninstallService({
@@ -48865,7 +49565,7 @@ async function main() {
48865
49565
  purgeSecret: true,
48866
49566
  secretPath: agentCredentialPath(),
48867
49567
  tokenPath: operatorTokenPath(),
48868
- ecoPath: import_node_path21.default.join(home, ".mrrlin", "ecosystem.config.cjs"),
49568
+ ecoPath: import_node_path20.default.join(home, ".mrrlin", "ecosystem.config.cjs"),
48869
49569
  log
48870
49570
  });
48871
49571
  let codexOk = true;
@@ -48885,7 +49585,7 @@ async function main() {
48885
49585
  log(`codex config NOT modified: ${error51 instanceof Error ? error51.message : String(error51)}`);
48886
49586
  }
48887
49587
  try {
48888
- (0, import_node_fs14.rmSync)(import_node_path21.default.join(home, ".mrrlin"), { recursive: true, force: true });
49588
+ (0, import_node_fs14.rmSync)(import_node_path20.default.join(home, ".mrrlin"), { recursive: true, force: true });
48889
49589
  } catch {
48890
49590
  }
48891
49591
  log("removed ~/.mrrlin");
@@ -48926,7 +49626,7 @@ ${HELP_TEXT}`);
48926
49626
  }
48927
49627
  }
48928
49628
  function resolveSelfBinPath() {
48929
- return import_node_path20.default.resolve(process.argv[1] ?? process.execPath);
49629
+ return import_node_path19.default.resolve(process.argv[1] ?? process.execPath);
48930
49630
  }
48931
49631
  main().catch((error51) => {
48932
49632
  process.stderr.write(`mrrlin-mcp fatal error: ${error51 instanceof Error ? error51.message : String(error51)}