@mrrlin-dev/mcp 0.4.85 → 0.4.88

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.
Files changed (2) hide show
  1. package/dist/bin.cjs +674 -534
  2. package/package.json +1 -1
package/dist/bin.cjs CHANGED
@@ -36673,7 +36673,7 @@ var ARTIFACT_CHECKS = {
36673
36673
  file_set: null,
36674
36674
  code_change: { verifier: "build" },
36675
36675
  structured_data: null,
36676
- message: null
36676
+ message: { verifier: "status" }
36677
36677
  };
36678
36678
  var ARTIFACT_DESTINATIONS = {
36679
36679
  document: "wiki",
@@ -36681,7 +36681,7 @@ var ARTIFACT_DESTINATIONS = {
36681
36681
  file_set: "artifacts",
36682
36682
  code_change: "repo_host",
36683
36683
  structured_data: null,
36684
- message: "artifacts"
36684
+ message: "send"
36685
36685
  };
36686
36686
  function resolveKindCapability(kind) {
36687
36687
  return ARTIFACT_DESTINATIONS[kind];
@@ -36737,7 +36737,6 @@ function describeCapabilityRefusal(input) {
36737
36737
  var openApiOperationIds = {
36738
36738
  appendExecutionArtifact: "appendExecutionArtifact",
36739
36739
  archivePlan: "archivePlan",
36740
- bindProjectGithubRepo: "bindProjectGithubRepo",
36741
36740
  createExecutionRun: "createExecutionRun",
36742
36741
  createExecutionRunHandoff: "createExecutionRunHandoff",
36743
36742
  createAsyncJob: "createAsyncJob",
@@ -36749,10 +36748,6 @@ var openApiOperationIds = {
36749
36748
  beginAsyncJobAttempt: "beginAsyncJobAttempt",
36750
36749
  terminalizeAsyncJob: "terminalizeAsyncJob",
36751
36750
  requeueAsyncJob: "requeueAsyncJob",
36752
- createGithubPullRequest: "createGithubPullRequest",
36753
- getGithubPullRequest: "getGithubPullRequest",
36754
- getGithubWorkflowRunLatest: "getGithubWorkflowRunLatest",
36755
- getGithubLatestDeployment: "getGithubLatestDeployment",
36756
36751
  createTaskDependency: "createTaskDependency",
36757
36752
  createPlan: "createPlan",
36758
36753
  upsertProject: "upsertProject",
@@ -36775,7 +36770,6 @@ var openApiOperationIds = {
36775
36770
  getTaskDeployTarget: "getTaskDeployTarget",
36776
36771
  getSchedulerPendingTasks: "getSchedulerPendingTasks",
36777
36772
  wakeDirector: "wakeDirector",
36778
- getGithubInstallationRepositories: "getGithubInstallationRepositories",
36779
36773
  getProject: "getProject",
36780
36774
  getProjectSnapshot: "getProjectSnapshot",
36781
36775
  getTask: "getTask",
@@ -36784,11 +36778,8 @@ var openApiOperationIds = {
36784
36778
  takeOverTask: "takeOverTask",
36785
36779
  closeTaskAsWontDo: "closeTaskAsWontDo",
36786
36780
  getWikiPage: "getWikiPage",
36787
- handleGithubInstallCallback: "handleGithubInstallCallback",
36788
36781
  listExecutionArtifacts: "listExecutionArtifacts",
36789
36782
  listExecutionRuns: "listExecutionRuns",
36790
- listGithubInstallations: "listGithubInstallations",
36791
- listGithubWorkflows: "listGithubWorkflows",
36792
36783
  listPlans: "listPlans",
36793
36784
  listProjects: "listProjects",
36794
36785
  listTaskDependencies: "listTaskDependencies",
@@ -36797,8 +36788,6 @@ var openApiOperationIds = {
36797
36788
  listTaxonomy: "listTaxonomy",
36798
36789
  listWikiPages: "listWikiPages",
36799
36790
  searchWikiPages: "searchWikiPages",
36800
- dispatchGithubWorkflow: "dispatchGithubWorkflow",
36801
- dispatchTaskDeploy: "dispatchTaskDeploy",
36802
36791
  createInboxItem: "createInboxItem",
36803
36792
  listInboxItems: "listInboxItems",
36804
36793
  decideInboxItem: "decideInboxItem",
@@ -37510,16 +37499,12 @@ var mrrlinTaskEventAutonomyLevelSchema = external_exports.enum([
37510
37499
  "human_review"
37511
37500
  ]);
37512
37501
  var mrrlinAutoDeploySchema = external_exports.enum(["off", "dev", "prod"]);
37513
- var mrrlinTaskDomainSchema = external_exports.enum(["code", "seo", "marketing", "content"]);
37514
- function normalizeTaskDomain(value) {
37515
- if (typeof value !== "string")
37516
- return "code";
37517
- const v = value.trim().toLowerCase();
37518
- return mrrlinTaskDomainSchema.options.includes(v) ? v : "code";
37519
- }
37502
+ var mrrlinTaskDomainsSchema = external_exports.array(external_exports.string()).min(1);
37520
37503
  function domainScopedReviewHash(rawHash, domain2) {
37521
- const d = normalizeTaskDomain(domain2);
37522
- return d === "code" ? rawHash : `${rawHash}@${d}`;
37504
+ const d = Array.isArray(domain2) ? domain2.map(String) : [String(domain2 || "code")];
37505
+ const normalized = d.map((v) => v.trim().toLowerCase()).filter(Boolean);
37506
+ normalized.sort();
37507
+ return normalized.length === 1 && normalized[0] === "code" ? rawHash : `${rawHash}@${normalized.join(",")}`;
37523
37508
  }
37524
37509
  var mrrlinDeclaredOutcomeShapeSchema = external_exports.object({
37525
37510
  sentence: external_exports.string().trim().min(1),
@@ -37816,7 +37801,7 @@ var mrrlinTaskSchema = external_exports.object({
37816
37801
  // ADR 0020. OUTPUT-optional (not defaulted-required) so hardcoded task fixtures/mocks across the
37817
37802
  // codebase need no churn; the DB column is NOT NULL default 'code', so real rows always carry it,
37818
37803
  // and resolveHeads() treats an absent domain as `code`. Persisted create/update use their own forms.
37819
- domain: mrrlinTaskDomainSchema.optional(),
37804
+ domains: mrrlinTaskDomainsSchema.optional(),
37820
37805
  autonomyLevel: mrrlinAutonomyLevelSchema,
37821
37806
  autonomyReason: external_exports.string().min(1).max(240),
37822
37807
  blockedBy: external_exports.array(mrrlinTaskIdSchema),
@@ -37894,9 +37879,9 @@ var mrrlinTaskSummarySchema = mrrlinTaskSchema.omit({
37894
37879
  var mrrlinPlanSchema = external_exports.object({
37895
37880
  closedTaskCount: external_exports.number().int().nonnegative(),
37896
37881
  // ADR 0020 Phase 2 (decision 7): plan-scoped specs resolve their consensus domain through
37897
- // plan.domain → project.defaultDomain → 'code'. Nullable (null = inherit project default);
37882
+ // plan.domain → project.defaultDomains → 'code'. Nullable (null = inherit project default);
37898
37883
  // output-optional so existing plan fixtures need no churn.
37899
- domain: mrrlinTaskDomainSchema.nullable().optional(),
37884
+ domains: mrrlinTaskDomainsSchema.nullable().optional(),
37900
37885
  dueDate: external_exports.string().date().nullable(),
37901
37886
  goal: external_exports.string().min(1),
37902
37887
  id: mrrlinPlanIdSchema,
@@ -37949,7 +37934,7 @@ var mrrlinCloseTaskAsWontDoRequestSchema = external_exports.object({
37949
37934
  reason: external_exports.string().min(1)
37950
37935
  });
37951
37936
  var mrrlinGithubBindingSchema = external_exports.object({
37952
- appInstallationState: external_exports.enum(["installed", "missing"]),
37937
+ appInstallationState: external_exports.enum(["installed", "missing"]).default("missing"),
37953
37938
  installationId: external_exports.number().int().positive().nullable().default(null),
37954
37939
  operator: external_exports.string().startsWith("@"),
37955
37940
  repo: external_exports.string().regex(/^[^/]+\/[^/]+$/).nullable().default(null)
@@ -38011,7 +37996,7 @@ var mrrlinProjectSummarySchema = external_exports.object({
38011
37996
  slug: mrrlinProjectSlugSchema,
38012
37997
  // ADR 0020: default consensus domain for this project's plan/project-scoped specs. Output-optional
38013
37998
  // (DB column is NOT NULL default 'code') so existing project fixtures/upsert callers need no churn.
38014
- defaultDomain: mrrlinTaskDomainSchema.optional(),
37999
+ defaultDomains: mrrlinTaskDomainsSchema.optional(),
38015
38000
  // model-tier-planning-and-arbiter D9: per-project judge switch. NULL/true means the judge runs
38016
38001
  // as today; false means intent-check.ts skips it entirely. Output-optional for the same reason
38017
38002
  // defaultDomain is: existing project fixtures/upsert callers need no churn.
@@ -38065,7 +38050,7 @@ var mrrlinProjectSnapshotSchema = external_exports.object({
38065
38050
  project: external_exports.object({
38066
38051
  // ADR 0020 Phase 2: default consensus domain for plan/project-scoped specs. Output-optional
38067
38052
  // (DB column is NOT NULL default 'code') so existing snapshot fixtures need no churn.
38068
- defaultDomain: mrrlinTaskDomainSchema.optional(),
38053
+ defaultDomains: mrrlinTaskDomainsSchema.optional(),
38069
38054
  displayName: external_exports.string().min(1),
38070
38055
  emoji: mrrlinProjectEmojiSchema,
38071
38056
  slug: mrrlinProjectSlugSchema
@@ -38082,7 +38067,7 @@ var mrrlinTaskCreateShapeSchema = external_exports.object({
38082
38067
  // the preview-only constant "dev"; the tasks column itself still defaults to 'off' at the
38083
38068
  // SQL level for any direct-insert path that bypasses createTask entirely.
38084
38069
  autoDeploy: mrrlinAutoDeploySchema.optional(),
38085
- domain: mrrlinTaskDomainSchema.optional(),
38070
+ domains: mrrlinTaskDomainsSchema.optional(),
38086
38071
  autonomyLevel: mrrlinAutonomyLevelSchema.default("human_review"),
38087
38072
  autonomyReason: external_exports.string().min(1).max(240).default("Needs human review before automation."),
38088
38073
  category: taxonomyCategorySchema,
@@ -38111,11 +38096,11 @@ var mrrlinTaskCreateShapeSchema = external_exports.object({
38111
38096
  // `apps/api/src/index.ts` for why the refusal cannot live in this schema.
38112
38097
  });
38113
38098
  function refineTaskCreateDomain(data, ctx) {
38114
- if (data.domain === void 0 && data.parentTaskId == null) {
38099
+ if (data.domains === void 0 && data.parentTaskId == null) {
38115
38100
  ctx.addIssue({
38116
38101
  code: external_exports.ZodIssueCode.custom,
38117
- path: ["domain"],
38118
- message: "domain is required unless parentTaskId is set (a follow-up inherits its parent's domain)"
38102
+ path: ["domains"],
38103
+ message: "domains is required unless parentTaskId is set (a follow-up inherits its parent's domains)"
38119
38104
  });
38120
38105
  }
38121
38106
  }
@@ -38181,7 +38166,7 @@ var mrrlinTaskUpdateSchema = external_exports.object({
38181
38166
  actor: external_exports.string().min(1).optional(),
38182
38167
  assignee: mrrlinTaskAssigneeSchema.optional(),
38183
38168
  autoDeploy: mrrlinAutoDeploySchema.optional(),
38184
- domain: mrrlinTaskDomainSchema.optional(),
38169
+ domains: mrrlinTaskDomainsSchema.optional(),
38185
38170
  autonomyLevel: mrrlinAutonomyLevelSchema.optional(),
38186
38171
  autonomyReason: external_exports.string().min(1).max(240).optional(),
38187
38172
  category: taxonomyCategorySchema.optional(),
@@ -38207,7 +38192,7 @@ var mrrlinTaskUpdateSchema = external_exports.object({
38207
38192
  // fingerprint moved but the topology still maps the label to the same workflow. Never sent
38208
38193
  // by any other caller.
38209
38194
  reverifyRequested: external_exports.boolean().optional()
38210
- }).refine((value) => value.assignee !== void 0 || value.autoDeploy !== void 0 || value.domain !== void 0 || value.autonomyLevel !== void 0 || value.autonomyReason !== void 0 || value.category !== void 0 || value.directorThreadId !== void 0 || value.directorThreadProvider !== void 0 || value.dueDate !== void 0 || value.followUpAt !== void 0 || value.impact !== void 0 || value.notes !== void 0 || value.planId !== void 0 || value.specApproved !== void 0 || value.specWikiPageId !== void 0 || value.status !== void 0 || value.subcategory !== void 0 || value.tags !== void 0 || value.title !== void 0 || value.type !== void 0 || value.verificationDueAt !== void 0 || value.deployTargets !== void 0 || value.reverifyRequested !== void 0, { message: "TaskUpdate requires at least one field" }).superRefine((value, context) => {
38195
+ }).refine((value) => value.assignee !== void 0 || value.autoDeploy !== void 0 || value.domains !== void 0 || value.autonomyLevel !== void 0 || value.autonomyReason !== void 0 || value.category !== void 0 || value.directorThreadId !== void 0 || value.directorThreadProvider !== void 0 || value.dueDate !== void 0 || value.followUpAt !== void 0 || value.impact !== void 0 || value.notes !== void 0 || value.planId !== void 0 || value.specApproved !== void 0 || value.specWikiPageId !== void 0 || value.status !== void 0 || value.subcategory !== void 0 || value.tags !== void 0 || value.title !== void 0 || value.type !== void 0 || value.verificationDueAt !== void 0 || value.deployTargets !== void 0 || value.reverifyRequested !== void 0, { message: "TaskUpdate requires at least one field" }).superRefine((value, context) => {
38211
38196
  if (value.autonomyLevel !== void 0 && value.autonomyReason === void 0) {
38212
38197
  context.addIssue({
38213
38198
  code: external_exports.ZodIssueCode.custom,
@@ -38246,7 +38231,7 @@ var apiViolationsErrorSchema = external_exports.object({
38246
38231
  violations: external_exports.array(apiViolationSchema).min(1)
38247
38232
  });
38248
38233
  var mrrlinPlanCreateSchema = external_exports.object({
38249
- domain: mrrlinTaskDomainSchema.nullable().optional(),
38234
+ domains: mrrlinTaskDomainsSchema.nullable().optional(),
38250
38235
  dueDate: external_exports.string().date().nullable().optional(),
38251
38236
  goal: external_exports.string().min(1),
38252
38237
  id: mrrlinPlanIdSchema,
@@ -38255,7 +38240,7 @@ var mrrlinPlanCreateSchema = external_exports.object({
38255
38240
  status: mrrlinPlanStatusSchema.default("active")
38256
38241
  });
38257
38242
  var mrrlinPlanUpdateSchema = external_exports.object({
38258
- domain: mrrlinTaskDomainSchema.nullable().optional(),
38243
+ domains: mrrlinTaskDomainsSchema.nullable().optional(),
38259
38244
  dueDate: external_exports.string().date().nullable().optional(),
38260
38245
  goal: external_exports.string().min(1).optional(),
38261
38246
  name: external_exports.string().min(1).optional(),
@@ -39317,7 +39302,8 @@ var CapabilityRouteSchema = external_exports.object({
39317
39302
  var CAPABILITY_OPERATIONS = {
39318
39303
  wiki: { create: "required", update: "required" },
39319
39304
  repo_host: { pull_request: "required", pull_request_state: "required" },
39320
- publish: { release: "required", observe: "required", preview: "optional", rollback: "optional" }
39305
+ publish: { release: "required", observe: "required", preview: "optional", rollback: "optional" },
39306
+ send: { dispatch: "required", cancel: "optional" }
39321
39307
  };
39322
39308
  var CapabilityToolClassSchema = external_exports.object({
39323
39309
  projectSlug: mrrlinProjectSlugSchema,
@@ -39564,41 +39550,6 @@ function createMrrlinClient(config2) {
39564
39550
  return response.text();
39565
39551
  }
39566
39552
  return {
39567
- async listGithubInstallations() {
39568
- const body = await request(`/github/installations`);
39569
- return githubInstallationListResponseSchema.parse(body).data;
39570
- },
39571
- async handleGithubInstallCallback(input) {
39572
- const setupAction = input.setupAction ?? "install";
39573
- const url2 = appendQuery(`/github/install/callback`, {
39574
- installation_id: String(input.installationId),
39575
- setup_action: setupAction,
39576
- state: input.state
39577
- });
39578
- const body = await request(url2);
39579
- return githubInstallCallbackResponseSchema.parse(body).data;
39580
- },
39581
- async getGithubInstallationRepositories(installationId) {
39582
- const body = await request(`/github/installations/${encodeURIComponent(String(installationId))}/repositories`);
39583
- return githubInstallationRepositoriesResponseSchema.parse(body).data;
39584
- },
39585
- async bindProjectGithubRepo(projectSlug, input) {
39586
- const payload = githubBindProjectRepoRequestSchema.parse(input);
39587
- const body = await request(`${projectPath(projectSlug)}/github/repo`, {
39588
- body: JSON.stringify(payload),
39589
- headers: { "content-type": "application/json" },
39590
- method: "PUT"
39591
- });
39592
- return githubBindProjectRepoResponseSchema.parse(body).data;
39593
- },
39594
- async createGithubPullRequest(projectSlug, input) {
39595
- const body = await request(`${projectPath(projectSlug)}/github/pull-requests`, {
39596
- body: JSON.stringify(input),
39597
- headers: { "content-type": "application/json" },
39598
- method: "POST"
39599
- });
39600
- return githubCreatePullRequestResponseSchema.parse(body).data;
39601
- },
39602
39553
  async createTask(projectSlug, input) {
39603
39554
  const body = await request(`${projectPath(projectSlug)}/tasks`, {
39604
39555
  body: JSON.stringify(input),
@@ -39736,44 +39687,6 @@ function createMrrlinClient(config2) {
39736
39687
  const body = await request(`${projectPath(projectSlug)}/wiki/${encodeURIComponent(pageId)}`);
39737
39688
  return wikiPageResponseSchema.parse(body).data;
39738
39689
  },
39739
- async dispatchGithubWorkflow(projectSlug, workflowId, input) {
39740
- const body = await request(`${projectPath(projectSlug)}/github/workflows/${encodeURIComponent(workflowId)}/dispatches`, {
39741
- body: JSON.stringify(input),
39742
- headers: { "content-type": "application/json" },
39743
- method: "POST"
39744
- });
39745
- return githubWorkflowDispatchResponseSchema.parse(body).data;
39746
- },
39747
- async dispatchTaskDeploy(projectSlug, taskId, input) {
39748
- const body = await request(`${projectPath(projectSlug)}/tasks/${encodeURIComponent(taskId)}/deploy`, {
39749
- body: JSON.stringify(input),
39750
- headers: { "content-type": "application/json" },
39751
- method: "POST"
39752
- });
39753
- return githubWorkflowDispatchResponseSchema.parse(body).data;
39754
- },
39755
- async getGithubWorkflowRunLatest(projectSlug, query) {
39756
- const params = new URLSearchParams({
39757
- workflowId: query.workflowId,
39758
- branch: query.branch,
39759
- dispatchedAfter: query.dispatchedAfter
39760
- });
39761
- const body = await request(`${projectPath(projectSlug)}/github/workflow-runs/latest?${params.toString()}`);
39762
- return githubWorkflowRunResponseSchema.parse(body).data;
39763
- },
39764
- async getGithubPullRequest(projectSlug, pullNumber) {
39765
- const body = await request(`${projectPath(projectSlug)}/github/pull-requests/${encodeURIComponent(String(pullNumber))}`);
39766
- return githubPullRequestResponseSchema.parse(body).data;
39767
- },
39768
- async getGithubLatestDeployment(projectSlug, query) {
39769
- const params = new URLSearchParams({ environment: query.environment });
39770
- const body = await request(`${projectPath(projectSlug)}/github/deployments/latest?${params.toString()}`);
39771
- return githubLatestDeploymentResponseSchema.parse(body).data;
39772
- },
39773
- async listGithubWorkflows(projectSlug) {
39774
- const body = await request(`${projectPath(projectSlug)}/github/workflows`);
39775
- return githubWorkflowListResponseSchema.parse(body).data;
39776
- },
39777
39690
  async listProjects() {
39778
39691
  const body = await request("/projects");
39779
39692
  return projectSummaryListResponseSchema.parse(body).data;
@@ -40384,17 +40297,6 @@ function createMrrlinClient(config2) {
40384
40297
  });
40385
40298
  return projectSchedulerPolicyResponseSchema.parse(body).data;
40386
40299
  },
40387
- async getProjectDeployWorkflow(projectSlug, env) {
40388
- try {
40389
- const body = await request(`${projectPath(projectSlug)}/deploy-workflows/${env}`);
40390
- return projectDeployWorkflowResponseSchema.parse(body).data;
40391
- } catch (error51) {
40392
- if (error51 instanceof MrrlinApiError && error51.status === 404 && error51.code === "DEPLOY_WORKFLOW_UNSET") {
40393
- return null;
40394
- }
40395
- throw error51;
40396
- }
40397
- },
40398
40300
  async getDeployTopology(projectSlug) {
40399
40301
  const body = await request(`${projectPath(projectSlug)}/deploy-topology`);
40400
40302
  return deployTopologyResponseSchema.parse(body).data;
@@ -40418,14 +40320,6 @@ function createMrrlinClient(config2) {
40418
40320
  throw error51;
40419
40321
  }
40420
40322
  },
40421
- async upsertProjectDeployWorkflow(projectSlug, env, input) {
40422
- const body = await request(`${projectPath(projectSlug)}/deploy-workflows/${env}`, {
40423
- body: JSON.stringify(input),
40424
- headers: { "content-type": "application/json" },
40425
- method: "PUT"
40426
- });
40427
- return projectDeployWorkflowResponseSchema.parse(body).data;
40428
- },
40429
40323
  async getSchedulerPendingTasks(projectSlug) {
40430
40324
  const body = await request(`${projectPath(projectSlug)}/scheduler/pending`);
40431
40325
  return schedulerPendingTasksResponseSchema.parse(body).data;
@@ -41207,14 +41101,23 @@ var CODE_HEADS_BY_IMPACT = {
41207
41101
  medium: ["plan-reviewer", "security-analyst", "scope-analyst"],
41208
41102
  low: ["plan-reviewer", "scope-analyst"]
41209
41103
  };
41210
- var MAX_DOMAIN_HEADS = Math.max(
41211
- ...Object.values(HEADS_BY_DOMAIN).map((h) => h.length),
41212
- ...Object.values(CODE_HEADS_BY_IMPACT).map((h) => h.length)
41213
- );
41104
+ var MAX_DOMAIN_HEADS = 7;
41105
+ function normalizeDomains(value) {
41106
+ if (value == null) return [DEFAULT_DOMAIN];
41107
+ const items = Array.isArray(value) ? value : [value];
41108
+ const normalized = [];
41109
+ for (const item of items) {
41110
+ if (typeof item === "string") {
41111
+ const v = item.trim().toLowerCase();
41112
+ if (DOMAINS.includes(v) && !normalized.includes(v)) {
41113
+ normalized.push(v);
41114
+ }
41115
+ }
41116
+ }
41117
+ return normalized.length > 0 ? normalized.sort() : [DEFAULT_DOMAIN];
41118
+ }
41214
41119
  function normalizeDomain(value) {
41215
- if (typeof value !== "string") return DEFAULT_DOMAIN;
41216
- const v = value.trim().toLowerCase();
41217
- return DOMAINS.includes(v) ? v : DEFAULT_DOMAIN;
41120
+ return normalizeDomains(value)[0];
41218
41121
  }
41219
41122
  function normalizeImpact(value) {
41220
41123
  if (typeof value !== "string") return null;
@@ -41222,10 +41125,19 @@ function normalizeImpact(value) {
41222
41125
  return v === "low" || v === "medium" || v === "high" ? v : null;
41223
41126
  }
41224
41127
  function resolveHeads(domain2, impact) {
41225
- const d = normalizeDomain(domain2);
41226
- if (d !== "code") return [...HEADS_BY_DOMAIN[d]];
41227
- const i = normalizeImpact(impact) ?? "high";
41228
- return [...CODE_HEADS_BY_IMPACT[i]];
41128
+ const domains = normalizeDomains(domain2);
41129
+ const result = [];
41130
+ const seen = /* @__PURE__ */ new Set();
41131
+ for (const d of domains) {
41132
+ const headsForDomain = d === "code" ? CODE_HEADS_BY_IMPACT[normalizeImpact(impact) ?? "high"] : HEADS_BY_DOMAIN[d];
41133
+ for (const h of headsForDomain) {
41134
+ if (!seen.has(h)) {
41135
+ seen.add(h);
41136
+ result.push(h);
41137
+ }
41138
+ }
41139
+ }
41140
+ return result;
41229
41141
  }
41230
41142
  var PERSONAS_BY_DOMAIN = {
41231
41143
  seo: "seo-writer",
@@ -41233,9 +41145,13 @@ var PERSONAS_BY_DOMAIN = {
41233
41145
  marketing: "content-author"
41234
41146
  };
41235
41147
  function resolveProducerPersona(domain2) {
41236
- const d = normalizeDomain(domain2);
41237
- if (d === "code") return null;
41238
- return PERSONAS_BY_DOMAIN[d] ?? null;
41148
+ const domains = normalizeDomains(domain2);
41149
+ for (const d of domains) {
41150
+ if (d !== "code" && PERSONAS_BY_DOMAIN[d]) {
41151
+ return PERSONAS_BY_DOMAIN[d];
41152
+ }
41153
+ }
41154
+ return null;
41239
41155
  }
41240
41156
 
41241
41157
  // src/consensus/acceptance-criteria.ts
@@ -41358,7 +41274,7 @@ async function runSpecGate(_target, deps, opts) {
41358
41274
  ]
41359
41275
  };
41360
41276
  }
41361
- const baseHeads = resolveHeads(intent.domain, intent.impact);
41277
+ const baseHeads = resolveHeads(intent.domains, intent.impact);
41362
41278
  const extraHeads = opts?.extraHeads ?? [];
41363
41279
  const heads = [...baseHeads, ...extraHeads.filter((h) => !baseHeads.includes(h))];
41364
41280
  const spec = await deps.readSpec();
@@ -42685,7 +42601,8 @@ var ClaudeProcess = class {
42685
42601
  "stream-json",
42686
42602
  "--output-format",
42687
42603
  "stream-json",
42688
- "--include-partial-messages"
42604
+ "--include-partial-messages",
42605
+ "--verbose"
42689
42606
  ];
42690
42607
  if (options.mode.kind === "new") {
42691
42608
  args.push("--session-id", this.sessionId);
@@ -44024,13 +43941,14 @@ async function resolveSpecDomain(client, projectSlug, page) {
44024
43941
  const taskId = await resolveGatedTaskId(client, projectSlug, page);
44025
43942
  if (taskId !== null) {
44026
43943
  const task = await client.getTask(projectSlug, taskId);
44027
- return normalizeDomain(task?.domain);
43944
+ return normalizeDomain(task?.domains ?? task?.domain);
44028
43945
  }
44029
43946
  const plans = await client.listPlans(projectSlug);
44030
43947
  const plan = plans.find((p) => p.specWikiPageId === page.id);
44031
- if (plan?.domain != null) return normalizeDomain(plan.domain);
43948
+ const planDomains = plan?.domains ?? plan?.domain;
43949
+ if (planDomains != null) return normalizeDomain(planDomains);
44032
43950
  const project = await client.getProject(projectSlug);
44033
- return normalizeDomain(project?.defaultDomain);
43951
+ return normalizeDomain(project?.defaultDomains ?? project?.defaultDomain);
44034
43952
  } catch {
44035
43953
  return "code";
44036
43954
  }
@@ -44433,7 +44351,7 @@ function buildSpecGateDeps(args) {
44433
44351
  const taskId = await resolveGatedTaskId(client, projectSlug, { id: page.id, taskId: page.taskId ?? null });
44434
44352
  if (taskId === null) {
44435
44353
  const project = await client.getProject(projectSlug);
44436
- return { taskId: null, seed: null, title: null, domain: project.defaultDomain, notes: null };
44354
+ return { taskId: null, seed: null, title: null, domains: project.defaultDomains, notes: null };
44437
44355
  }
44438
44356
  const task = await client.getTask(projectSlug, taskId);
44439
44357
  if (!task) {
@@ -44461,7 +44379,7 @@ function buildSpecGateDeps(args) {
44461
44379
  taskId,
44462
44380
  seed: resolvedSeed,
44463
44381
  title: task.title ?? "",
44464
- domain: task.domain,
44382
+ domains: task.domains,
44465
44383
  notes,
44466
44384
  impact: task.impact,
44467
44385
  adjudicated,
@@ -44620,10 +44538,10 @@ ${artifact}`,
44620
44538
  timeoutMs,
44621
44539
  codexExecutable
44622
44540
  });
44623
- const { domain: domain2 } = await loadIntent();
44541
+ const { domains } = await loadIntent();
44624
44542
  try {
44625
44543
  await client.recordSpecConsensusReview(projectSlug, specPageId, {
44626
- contentHash: domainScopedReviewHash2(contentHash, domain2),
44544
+ contentHash: domainScopedReviewHash2(contentHash, domains),
44627
44545
  outcome: String(outcome),
44628
44546
  rounds: typeof rounds2 === "number" ? rounds2 : 0,
44629
44547
  ...history !== void 0 ? { history } : {},
@@ -44757,7 +44675,7 @@ async function buildDeliverableGateDeps(args) {
44757
44675
  let criteria = [];
44758
44676
  if (taskId) {
44759
44677
  const task = await client.getTask(projectSlug, taskId);
44760
- domain2 = normalizeDomain(task?.domain);
44678
+ domain2 = normalizeDomain(task?.domains ?? task?.domain);
44761
44679
  impact = task?.impact;
44762
44680
  taskTitle = task?.title ?? "";
44763
44681
  if (task?.specWikiPageId) {
@@ -45306,7 +45224,7 @@ async function maybeRunShadow(input, deps, logger) {
45306
45224
  const productionVerdict = productionVerdictFromOutcome(input.outcome);
45307
45225
  if (productionVerdict === null) return false;
45308
45226
  try {
45309
- const heads = input.heads ?? [...resolveHeads(input.domain, input.impact), ...input.extraHeads ?? []];
45227
+ const heads = input.heads ?? [...resolveHeads(input.domains, input.impact), ...input.extraHeads ?? []];
45310
45228
  const personaInstructions = {};
45311
45229
  for (const name of heads) {
45312
45230
  try {
@@ -45366,7 +45284,7 @@ function applyActivePanel(deps, panel) {
45366
45284
  async function resolveActivePanel(input, deps) {
45367
45285
  if (!multiLlmActive(input.env ?? process.env)) return null;
45368
45286
  try {
45369
- const heads = input.heads ?? [...resolveHeads(input.domain, input.impact), ...input.extraHeads ?? []];
45287
+ const heads = input.heads ?? [...resolveHeads(input.domains, input.impact), ...input.extraHeads ?? []];
45370
45288
  if (heads.length === 0) return null;
45371
45289
  const personaInstructions = {};
45372
45290
  for (const name of heads) {
@@ -50458,6 +50376,12 @@ async function deliverToArtifacts(client, projectSlug, payload, bareFetch) {
50458
50376
  }
50459
50377
  }
50460
50378
 
50379
+ // src/delivery/send.ts
50380
+ async function deliverToSend(server, toolName, payload) {
50381
+ const { operation: _operation, ...args } = payload;
50382
+ return invokeCapability(server, toolName, args);
50383
+ }
50384
+
50461
50385
  // src/delivery/publish.ts
50462
50386
  async function deliverToPublish(server, toolName, payload) {
50463
50387
  const { operation: _operation, ...args } = payload;
@@ -50805,6 +50729,14 @@ async function callDestination(client, projectSlug, route, resolvedToolName, pay
50805
50729
  }
50806
50730
  return deliverToArtifacts(client, projectSlug, payload, bareFetch);
50807
50731
  }
50732
+ if (route.capability === "send") {
50733
+ if (typeof operation !== "string" || !(operation in CAPABILITY_OPERATIONS.send)) {
50734
+ throw new Error(`callDestination: payload.operation "${String(operation)}" is not valid for send`);
50735
+ }
50736
+ const server = readMcpServerById(mcpConfigPath ?? defaultMcpConfigPath(), route.serverId);
50737
+ if (server === null) throw new NoGrantForServerError(`no local MCP config for server "${route.serverId}"`);
50738
+ return deliverToSend(server, resolvedToolName, payload);
50739
+ }
50808
50740
  if (route.capability === "publish") {
50809
50741
  if (typeof operation !== "string" || !(operation in CAPABILITY_OPERATIONS.publish)) {
50810
50742
  throw new Error(`callDestination: payload.operation "${String(operation)}" is not valid for publish`);
@@ -51483,7 +51415,7 @@ async function run(ctx, input, builders = defaultNativeSpecGateBuilders) {
51483
51415
  await maybeRunShadow({
51484
51416
  outcome: result.outcome,
51485
51417
  artifact: specPage.markdownBody,
51486
- domain: shadowDomain,
51418
+ domains: [shadowDomain],
51487
51419
  extraHeads: input.extraHeads,
51488
51420
  correlationId: `Specs/${input.pageId}`,
51489
51421
  // task 1.21: ctx's own minted seat, not an ambient read — ctx already carries it by
@@ -52011,7 +51943,7 @@ function buildDraftSpecPrompt(task, constitution, deployTarget = null) {
52011
51943
  `id: ${task.id}`,
52012
51944
  `title: ${task.title}`,
52013
51945
  `type: ${task.type ?? "task"}`,
52014
- `domain: ${task.domain ?? "code"}`,
51946
+ `domains: ${(task.domains ?? ["code"]).join(", ")}`,
52015
51947
  `impact: ${task.impact ?? ""}`,
52016
51948
  `category: ${task.category ?? ""} / ${task.subcategory ?? ""}`,
52017
51949
  `tags: ${(task.tags ?? []).join(", ")}`,
@@ -52715,9 +52647,6 @@ var OUTWARD_TOOL_NAMES = /* @__PURE__ */ new Set([
52715
52647
  "browser_press_key",
52716
52648
  "browser_file_upload",
52717
52649
  "browser_select_option",
52718
- "create_github_pull_request",
52719
- "dispatch_github_workflow",
52720
- "dispatch_task_deploy",
52721
52650
  "deliver_file",
52722
52651
  "record_produced_url"
52723
52652
  ]);
@@ -52725,7 +52654,6 @@ var READ_TOOL_NAMES = /* @__PURE__ */ new Set([
52725
52654
  "browser_navigate",
52726
52655
  "browser_snapshot",
52727
52656
  "get_execution_run",
52728
- "get_github_installation_repositories",
52729
52657
  "get_project",
52730
52658
  "get_project_scheduler_policy",
52731
52659
  "get_project_snapshot",
@@ -52735,8 +52663,6 @@ var READ_TOOL_NAMES = /* @__PURE__ */ new Set([
52735
52663
  "list_artifact_files",
52736
52664
  "list_execution_artifacts",
52737
52665
  "list_execution_runs",
52738
- "list_github_installations",
52739
- "list_github_workflows",
52740
52666
  "list_inbox_items",
52741
52667
  "list_plans",
52742
52668
  "list_project_members",
@@ -53497,9 +53423,7 @@ function readOrCreateWorkerToken() {
53497
53423
  var mcpToolNames = {
53498
53424
  appendExecutionArtifact: "append_execution_artifact",
53499
53425
  archivePlan: "archive_plan",
53500
- bindProjectGithubRepo: "bind_project_github_repo",
53501
53426
  createExecutionRun: "create_execution_run",
53502
- createGithubPullRequest: "create_github_pull_request",
53503
53427
  createPlan: "create_plan",
53504
53428
  createTaskDependency: "create_task_dependency",
53505
53429
  createTaxonomyEntry: "create_taxonomy_entry",
@@ -53522,14 +53446,9 @@ var mcpToolNames = {
53522
53446
  getProjectSnapshot: "get_project_snapshot",
53523
53447
  getTask: "get_task",
53524
53448
  getWikiPage: "get_wiki_page",
53525
- dispatchGithubWorkflow: "dispatch_github_workflow",
53526
- dispatchTaskDeploy: "dispatch_task_deploy",
53527
- getGithubInstallationRepositories: "get_github_installation_repositories",
53528
53449
  listExecutionArtifacts: "list_execution_artifacts",
53529
53450
  listExecutionRuns: "list_execution_runs",
53530
53451
  listInboxItems: "list_inbox_items",
53531
- listGithubInstallations: "list_github_installations",
53532
- listGithubWorkflows: "list_github_workflows",
53533
53452
  listPlans: "list_plans",
53534
53453
  listProjects: "list_projects",
53535
53454
  listTaskDependencies: "list_task_dependencies",
@@ -53541,7 +53460,6 @@ var mcpToolNames = {
53541
53460
  searchKnowledge: "search_knowledge",
53542
53461
  updateExecutionRun: "update_execution_run",
53543
53462
  upsertProjectSchedulerPolicy: "upsert_project_scheduler_policy",
53544
- recordDeployWorkflow: "record_deploy_workflow",
53545
53463
  updatePlan: "update_plan",
53546
53464
  updateTask: "update_task",
53547
53465
  resetDirectorThread: "reset_director_thread",
@@ -54192,17 +54110,6 @@ var runDecisionConsensusInputSchema = external_exports.object({
54192
54110
  )
54193
54111
  });
54194
54112
  var searchKnowledgeInputSchema = wikiKnowledgeSearchInputSchema;
54195
- var createGithubPullRequestInputSchema = projectScopedSchema.merge(githubCreatePullRequestRequestSchema);
54196
- var listGithubWorkflowsInputSchema = projectScopedSchema;
54197
- var dispatchGithubWorkflowInputSchema = projectScopedSchema.extend({ workflowId: external_exports.string().min(1) }).merge(githubWorkflowDispatchRequestSchema);
54198
- var dispatchTaskDeployInputSchema = taskScopedSchema.extend({
54199
- env: external_exports.enum(["dev", "prod"]),
54200
- ref: external_exports.string().min(1).default("main"),
54201
- workflowId: external_exports.string().min(1)
54202
- });
54203
- var listGithubInstallationsInputSchema = external_exports.object({});
54204
- var getGithubInstallationRepositoriesInputSchema = githubInstallationIdParamSchema;
54205
- var bindProjectGithubRepoInputSchema = projectScopedSchema.merge(githubBindProjectRepoRequestSchema);
54206
54113
  var runScopedSchema = projectScopedSchema.merge(executionRunIdParamSchema);
54207
54114
  var listExecutionRunsInputSchema = projectScopedSchema.merge(executionRunListFiltersSchema);
54208
54115
  var createExecutionRunInputSchema = projectScopedSchema.merge(mrrlinExecutionRunCreateSchema);
@@ -54214,13 +54121,6 @@ var recordProducedUrlInputSchema = runScopedSchema.merge(mrrlinProducedUrlPayloa
54214
54121
  var updateTaskStateInputSchema = runScopedSchema.merge(mrrlinTaskStateReportSchema);
54215
54122
  var getProjectSchedulerPolicyInputSchema = projectScopedSchema;
54216
54123
  var upsertProjectSchedulerPolicyInputSchema = projectScopedSchema.merge(mrrlinProjectSchedulerPolicyUpsertSchema);
54217
- var recordDeployWorkflowInputSchema = projectScopedSchema.extend({
54218
- env: mrrlinDeployWorkflowEnvSchema,
54219
- workflowId: external_exports.number().int().positive().nullable(),
54220
- workflowPath: external_exports.string().nullable().optional(),
54221
- reason: external_exports.string().nullable().optional(),
54222
- state: mrrlinDeployWorkflowStateSchema.optional()
54223
- });
54224
54124
  var getSchedulerPendingTasksInputSchema = projectScopedSchema;
54225
54125
  var wakeDirectorInputSchema = projectScopedSchema.merge(wakeDirectorRequestSchema.partial());
54226
54126
  var uploadArtifactInputSchema = projectScopedSchema.merge(
@@ -54278,9 +54178,7 @@ var retrieveContextInputSchema = external_exports.object({
54278
54178
  var mcpToolInputSchemas = {
54279
54179
  [mcpToolNames.appendExecutionArtifact]: appendExecutionArtifactInputSchema,
54280
54180
  [mcpToolNames.archivePlan]: archivePlanInputSchema,
54281
- [mcpToolNames.bindProjectGithubRepo]: bindProjectGithubRepoInputSchema,
54282
54181
  [mcpToolNames.createExecutionRun]: createExecutionRunInputSchema,
54283
- [mcpToolNames.createGithubPullRequest]: createGithubPullRequestInputSchema,
54284
54182
  [mcpToolNames.createPlan]: createPlanInputSchema,
54285
54183
  [mcpToolNames.createTaskDependency]: createTaskDependencyInputSchema,
54286
54184
  [mcpToolNames.createTaxonomyEntry]: createTaxonomyEntryInputSchema,
@@ -54301,13 +54199,8 @@ var mcpToolInputSchemas = {
54301
54199
  [mcpToolNames.getProjectSnapshot]: getProjectSnapshotInputSchema,
54302
54200
  [mcpToolNames.getTask]: getTaskInputSchema,
54303
54201
  [mcpToolNames.getWikiPage]: getWikiPageInputSchema,
54304
- [mcpToolNames.dispatchGithubWorkflow]: dispatchGithubWorkflowInputSchema,
54305
- [mcpToolNames.dispatchTaskDeploy]: dispatchTaskDeployInputSchema,
54306
- [mcpToolNames.getGithubInstallationRepositories]: getGithubInstallationRepositoriesInputSchema,
54307
54202
  [mcpToolNames.listExecutionArtifacts]: listExecutionArtifactsInputSchema,
54308
54203
  [mcpToolNames.listExecutionRuns]: listExecutionRunsInputSchema,
54309
- [mcpToolNames.listGithubInstallations]: listGithubInstallationsInputSchema,
54310
- [mcpToolNames.listGithubWorkflows]: listGithubWorkflowsInputSchema,
54311
54204
  [mcpToolNames.listPlans]: listPlansInputSchema,
54312
54205
  [mcpToolNames.listProjects]: listProjectsInputSchema,
54313
54206
  [mcpToolNames.listTaskDependencies]: listTaskDependenciesInputSchema,
@@ -54319,7 +54212,6 @@ var mcpToolInputSchemas = {
54319
54212
  [mcpToolNames.searchKnowledge]: searchKnowledgeInputSchema,
54320
54213
  [mcpToolNames.updateExecutionRun]: updateExecutionRunInputSchema,
54321
54214
  [mcpToolNames.upsertProjectSchedulerPolicy]: upsertProjectSchedulerPolicyInputSchema,
54322
- [mcpToolNames.recordDeployWorkflow]: recordDeployWorkflowInputSchema,
54323
54215
  [mcpToolNames.updatePlan]: updatePlanInputSchema,
54324
54216
  [mcpToolNames.updateTask]: updateTaskInputSchema,
54325
54217
  [mcpToolNames.resetDirectorThread]: resetDirectorThreadInputSchema,
@@ -54350,9 +54242,7 @@ var mcpToolInputSchemas = {
54350
54242
  var mcpToolOperationIds = {
54351
54243
  [mcpToolNames.appendExecutionArtifact]: openApiOperationIds.appendExecutionArtifact,
54352
54244
  [mcpToolNames.archivePlan]: openApiOperationIds.archivePlan,
54353
- [mcpToolNames.bindProjectGithubRepo]: openApiOperationIds.bindProjectGithubRepo,
54354
54245
  [mcpToolNames.createExecutionRun]: openApiOperationIds.createExecutionRun,
54355
- [mcpToolNames.createGithubPullRequest]: openApiOperationIds.createGithubPullRequest,
54356
54246
  [mcpToolNames.createPlan]: openApiOperationIds.createPlan,
54357
54247
  [mcpToolNames.createTaskDependency]: openApiOperationIds.createTaskDependency,
54358
54248
  [mcpToolNames.createTaxonomyEntry]: openApiOperationIds.createTaxonomyEntry,
@@ -54373,13 +54263,8 @@ var mcpToolOperationIds = {
54373
54263
  [mcpToolNames.getProjectSnapshot]: openApiOperationIds.getProjectSnapshot,
54374
54264
  [mcpToolNames.getTask]: openApiOperationIds.getTask,
54375
54265
  [mcpToolNames.getWikiPage]: openApiOperationIds.getWikiPage,
54376
- [mcpToolNames.dispatchGithubWorkflow]: openApiOperationIds.dispatchGithubWorkflow,
54377
- [mcpToolNames.dispatchTaskDeploy]: openApiOperationIds.dispatchTaskDeploy,
54378
- [mcpToolNames.getGithubInstallationRepositories]: openApiOperationIds.getGithubInstallationRepositories,
54379
54266
  [mcpToolNames.listExecutionArtifacts]: openApiOperationIds.listExecutionArtifacts,
54380
54267
  [mcpToolNames.listExecutionRuns]: openApiOperationIds.listExecutionRuns,
54381
- [mcpToolNames.listGithubInstallations]: openApiOperationIds.listGithubInstallations,
54382
- [mcpToolNames.listGithubWorkflows]: openApiOperationIds.listGithubWorkflows,
54383
54268
  [mcpToolNames.listPlans]: openApiOperationIds.listPlans,
54384
54269
  [mcpToolNames.listProjects]: openApiOperationIds.listProjects,
54385
54270
  [mcpToolNames.listTaskDependencies]: openApiOperationIds.listTaskDependencies,
@@ -54391,7 +54276,6 @@ var mcpToolOperationIds = {
54391
54276
  [mcpToolNames.searchKnowledge]: openApiOperationIds.searchKnowledge,
54392
54277
  [mcpToolNames.updateExecutionRun]: openApiOperationIds.updateExecutionRun,
54393
54278
  [mcpToolNames.upsertProjectSchedulerPolicy]: openApiOperationIds.upsertProjectSchedulerPolicy,
54394
- [mcpToolNames.recordDeployWorkflow]: openApiOperationIds.upsertProjectDeployWorkflow,
54395
54279
  [mcpToolNames.updatePlan]: openApiOperationIds.updatePlan,
54396
54280
  [mcpToolNames.updateTask]: openApiOperationIds.updateTask,
54397
54281
  [mcpToolNames.updateTaskState]: openApiOperationIds.reportTaskState,
@@ -54414,8 +54298,6 @@ var mcpToolOperationIds = {
54414
54298
  };
54415
54299
  var mcpToolDescriptions = {
54416
54300
  [mcpToolNames.archivePlan]: "Archive a plan/epic and clear task plan links.",
54417
- [mcpToolNames.bindProjectGithubRepo]: "Bind the project to a GitHub repository + installation id.",
54418
- [mcpToolNames.createGithubPullRequest]: "Create a GitHub pull request for the project's linked repository installation.",
54419
54301
  [mcpToolNames.createPlan]: "Create a plan/epic for grouping project tasks.",
54420
54302
  [mcpToolNames.createTaskDependency]: "Create a blocked-by relationship between two tasks.",
54421
54303
  [mcpToolNames.createTaxonomyEntry]: "Create an allowed category/subcategory pair.",
@@ -54432,11 +54314,6 @@ var mcpToolDescriptions = {
54432
54314
  [mcpToolNames.getProjectSnapshot]: "Fetch the full project snapshot: GitHub binding, tasks, plans, and wiki pages.",
54433
54315
  [mcpToolNames.getTask]: "Fetch a single task by project slug and task id.",
54434
54316
  [mcpToolNames.getWikiPage]: "Fetch a single markdown Wiki page.",
54435
- [mcpToolNames.dispatchGithubWorkflow]: "Dispatch a GitHub Actions workflow by workflow file/id against a ref visible to the linked installation.",
54436
- [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.",
54437
- [mcpToolNames.getGithubInstallationRepositories]: "List repositories visible to a GitHub App installation id.",
54438
- [mcpToolNames.listGithubInstallations]: "List persisted GitHub App installations.",
54439
- [mcpToolNames.listGithubWorkflows]: "List the bound repository's GitHub Actions workflows live through the linked installation. No Mrrlin-side cache.",
54440
54317
  [mcpToolNames.listProjects]: "List every project visible to the operator.",
54441
54318
  [mcpToolNames.listPlans]: "List project plans/epics with progress counts.",
54442
54319
  [mcpToolNames.listTaskDependencies]: "List task blocked-by dependencies.",
@@ -54468,7 +54345,6 @@ var mcpToolDescriptions = {
54468
54345
  [mcpToolNames.updateExecutionRun]: "Update an execution run (status, branch, SHA, PR url, dev/prod workflow ids, checkpoint cursor). Transitioning to handoff_queued requires an approved handoff_judgement artifact.",
54469
54346
  [mcpToolNames.getProjectSchedulerPolicy]: "Fetch the project scheduler policy: cadence floor, eligible autonomy levels, and optional wake_floor. Returns defaults if unset.",
54470
54347
  [mcpToolNames.upsertProjectSchedulerPolicy]: "Upsert the project scheduler policy. Partial: only provided fields overwrite existing values.",
54471
- [mcpToolNames.recordDeployWorkflow]: "Record which GitHub Actions workflow deploys this project for one environment (dev or prod), after confirming it via list_github_workflows + reading its file for a workflow_dispatch trigger. Call this AT MOST ONCE per environment per project, only when the deploy gate has parked on a missing/stale workflow id \u2014 this choice becomes durable project state, not a per-run decision. Pass workflowId=null and state='ambiguous' when zero or more than one candidate is confirmed dispatchable; otherwise pass the single confirmed workflowId with state='resolved' (the default). `source` is derived from your identity server-side \u2014 you cannot set it, and this call is always recorded as 'agent'.",
54472
54348
  [mcpToolNames.getSchedulerPendingTasks]: "Return tasks the scheduler should wake now: ones with follow_up_at or verification_due_at passed, or the policy wake_floor reached, filtered by eligible autonomy levels.",
54473
54349
  [mcpToolNames.wakeDirector]: "Wake Director once for a project: optionally dry-run scheduler evaluation or enqueue execution runs best-effort.",
54474
54350
  [mcpToolNames.listInboxItems]: "List operator-inbox items for a project. Optional filters: kind, status.",
@@ -54682,40 +54558,6 @@ function createMrrlinTools(options) {
54682
54558
  "Unable to archive plan.",
54683
54559
  async (c, { projectSlug, planId }) => await c.updatePlan(projectSlug, planId, { status: "archived" })
54684
54560
  ),
54685
- [mcpToolNames.listGithubInstallations]: makeTool(
54686
- client,
54687
- mcpToolNames.listGithubInstallations,
54688
- "GITHUB_INSTALLATION_LIST_FAILED",
54689
- "Unable to list GitHub installations.",
54690
- async (c) => await c.listGithubInstallations()
54691
- ),
54692
- [mcpToolNames.getGithubInstallationRepositories]: makeTool(
54693
- client,
54694
- mcpToolNames.getGithubInstallationRepositories,
54695
- "GITHUB_INSTALLATION_REPOSITORIES_FAILED",
54696
- "Unable to list installation repositories.",
54697
- async (c, { installationId }) => await c.getGithubInstallationRepositories(installationId)
54698
- ),
54699
- [mcpToolNames.bindProjectGithubRepo]: makeTool(
54700
- client,
54701
- mcpToolNames.bindProjectGithubRepo,
54702
- "GITHUB_REPO_BIND_FAILED",
54703
- "Unable to bind project repository.",
54704
- async (c, { projectSlug, ...input }) => await c.bindProjectGithubRepo(projectSlug, input)
54705
- ),
54706
- [mcpToolNames.createGithubPullRequest]: makeTool(
54707
- client,
54708
- mcpToolNames.createGithubPullRequest,
54709
- "GITHUB_PULL_REQUEST_CREATE_FAILED",
54710
- "Unable to create GitHub pull request.",
54711
- async (c, { projectSlug, ...input }) => await deliverBuiltIn(
54712
- c,
54713
- projectSlug,
54714
- "code_change",
54715
- { operation: "pull_request", ...input },
54716
- { mcpConfigPath: options.mcpConfigPath }
54717
- )
54718
- ),
54719
54561
  [mcpToolNames.createPlan]: makeTool(
54720
54562
  client,
54721
54563
  mcpToolNames.createPlan,
@@ -54836,27 +54678,6 @@ function createMrrlinTools(options) {
54836
54678
  "Unable to read wiki page.",
54837
54679
  async (c, { projectSlug, pageId }) => await c.getWikiPage(projectSlug, pageId)
54838
54680
  ),
54839
- [mcpToolNames.dispatchGithubWorkflow]: makeTool(
54840
- client,
54841
- mcpToolNames.dispatchGithubWorkflow,
54842
- "GITHUB_WORKFLOW_DISPATCH_FAILED",
54843
- "Unable to dispatch GitHub workflow.",
54844
- async (c, { projectSlug, workflowId, ...input }) => await c.dispatchGithubWorkflow(projectSlug, workflowId, input)
54845
- ),
54846
- [mcpToolNames.dispatchTaskDeploy]: makeTool(
54847
- client,
54848
- mcpToolNames.dispatchTaskDeploy,
54849
- "TASK_DEPLOY_DISPATCH_FAILED",
54850
- "Unable to dispatch the task deploy workflow.",
54851
- async (c, { projectSlug, taskId, workflowId, ...rest }) => await c.dispatchTaskDeploy(projectSlug, taskId, { ...rest, workflowId })
54852
- ),
54853
- [mcpToolNames.listGithubWorkflows]: makeTool(
54854
- client,
54855
- mcpToolNames.listGithubWorkflows,
54856
- "GITHUB_WORKFLOW_LIST_FAILED",
54857
- "Unable to list GitHub workflows.",
54858
- async (c, { projectSlug }) => await c.listGithubWorkflows(projectSlug)
54859
- ),
54860
54681
  [mcpToolNames.listProjects]: makeTool(
54861
54682
  client,
54862
54683
  mcpToolNames.listProjects,
@@ -55203,13 +55024,6 @@ function createMrrlinTools(options) {
55203
55024
  "Unable to upsert project scheduler policy.",
55204
55025
  async (c, { projectSlug, ...input }) => await c.upsertProjectSchedulerPolicy(projectSlug, input)
55205
55026
  ),
55206
- [mcpToolNames.recordDeployWorkflow]: makeTool(
55207
- client,
55208
- mcpToolNames.recordDeployWorkflow,
55209
- "DEPLOY_WORKFLOW_RECORD_FAILED",
55210
- "Unable to record the project's deploy workflow choice.",
55211
- async (c, { projectSlug, env, ...input }) => await c.upsertProjectDeployWorkflow(projectSlug, env, input)
55212
- ),
55213
55027
  [mcpToolNames.getSchedulerPendingTasks]: makeTool(
55214
55028
  client,
55215
55029
  mcpToolNames.getSchedulerPendingTasks,
@@ -55764,31 +55578,31 @@ async function verifyProviderReadiness(provider, run3, opts = {}) {
55764
55578
  if (cliResult2.status === 0) {
55765
55579
  return {
55766
55580
  ready: true,
55767
- checks: { cli: true, version: true, auth: false, mcpReachable: false },
55581
+ checks: { cli: true, version: true, auth: false, mcpReachable: false, smoke: true },
55768
55582
  details: []
55769
55583
  };
55770
55584
  } else if (cliResult2.status === null) {
55771
55585
  return {
55772
55586
  ready: false,
55773
- checks: { cli: false, version: false, auth: false, mcpReachable: false },
55587
+ checks: { cli: false, version: false, auth: false, mcpReachable: false, smoke: false },
55774
55588
  details: ["cli: `agy` binary not found on PATH"]
55775
55589
  };
55776
55590
  } else {
55777
55591
  return {
55778
55592
  ready: false,
55779
- checks: { cli: true, version: false, auth: false, mcpReachable: false },
55593
+ checks: { cli: true, version: false, auth: false, mcpReachable: false, smoke: false },
55780
55594
  details: [`cli: \`agy --version\` exited with code ${cliResult2.status}`]
55781
55595
  };
55782
55596
  }
55783
55597
  } catch (_e) {
55784
55598
  return {
55785
55599
  ready: false,
55786
- checks: { cli: false, version: false, auth: false, mcpReachable: false },
55600
+ checks: { cli: false, version: false, auth: false, mcpReachable: false, smoke: false },
55787
55601
  details: ["cli: `agy` binary not found on PATH"]
55788
55602
  };
55789
55603
  }
55790
55604
  }
55791
- const checks = { cli: false, version: false, auth: false, mcpReachable: false };
55605
+ const checks = { cli: false, version: false, auth: false, mcpReachable: false, smoke: false };
55792
55606
  const details = [];
55793
55607
  let cliResult;
55794
55608
  let cliUnreachable = false;
@@ -55851,7 +55665,34 @@ async function verifyProviderReadiness(provider, run3, opts = {}) {
55851
55665
  }
55852
55666
  if (!checks.mcpReachable) details.push("mcpReachable: external-agents server entry missing or invalid");
55853
55667
  }
55854
- const ready = checks.cli && checks.version && checks.auth && checks.mcpReachable;
55668
+ if (provider === "codex") {
55669
+ checks.smoke = true;
55670
+ } else if (!checks.cli || !checks.version || !checks.auth || !checks.mcpReachable) {
55671
+ checks.smoke = false;
55672
+ } else {
55673
+ try {
55674
+ const result = run3([
55675
+ "--print",
55676
+ "--input-format",
55677
+ "stream-json",
55678
+ "--output-format",
55679
+ "stream-json",
55680
+ "--include-partial-messages",
55681
+ "--verbose",
55682
+ "--permission-mode",
55683
+ "bypassPermissions",
55684
+ "--model",
55685
+ "haiku",
55686
+ "-p",
55687
+ "hi"
55688
+ ]);
55689
+ checks.smoke = result.status === 0;
55690
+ } catch {
55691
+ checks.smoke = false;
55692
+ }
55693
+ if (!checks.smoke) details.push("smoke: single-turn `claude --print ... -p hi` failed");
55694
+ }
55695
+ const ready = checks.cli && checks.version && checks.auth && checks.mcpReachable && checks.smoke;
55855
55696
  return { ready, checks, details };
55856
55697
  }
55857
55698
 
@@ -56990,7 +56831,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
56990
56831
  }
56991
56832
 
56992
56833
  // src/_generated/version.ts
56993
- var PKG_VERSION = "0.4.85";
56834
+ var PKG_VERSION = "0.4.88";
56994
56835
 
56995
56836
  // src/api-base-url-migrate.ts
56996
56837
  var import_node_fs13 = __toESM(require("node:fs"), 1);
@@ -57015,28 +56856,45 @@ function resolveServiceCwd(opts) {
57015
56856
  }
57016
56857
  var BRIDGE_APP_NAME = "mrrlin-bridge";
57017
56858
  function renderEcosystemConfig(opts) {
57018
- const env = {
57019
- HOME: opts.home,
57020
- PATH: opts.pathEnv,
57021
- MRRLIN_DIRECTOR_BRIDGE_LOG: "1"
57022
- };
57023
- if (opts.user) {
57024
- env.USER = opts.user;
57025
- if (process.platform === "win32") {
57026
- env.USERNAME = opts.user;
56859
+ let envLines;
56860
+ if ("env" in opts && opts.env !== void 0) {
56861
+ const safeEnv = /* @__PURE__ */ Object.create(null);
56862
+ for (const key of Object.keys(opts.env)) {
56863
+ const val = opts.env[key];
56864
+ if (val !== void 0) {
56865
+ safeEnv[key] = val;
56866
+ }
56867
+ }
56868
+ const keys = Object.keys(safeEnv).sort();
56869
+ envLines = keys.map((k) => {
56870
+ const keyProp = k === "__proto__" ? '["__proto__"]' : k;
56871
+ return ` ${keyProp}: ${JSON.stringify(safeEnv[k])},`;
56872
+ }).join("\n");
56873
+ } else {
56874
+ const legacyOpts = opts;
56875
+ const env = {
56876
+ HOME: legacyOpts.home,
56877
+ PATH: legacyOpts.pathEnv,
56878
+ MRRLIN_DIRECTOR_BRIDGE_LOG: "1"
56879
+ };
56880
+ if (legacyOpts.user) {
56881
+ env.USER = legacyOpts.user;
56882
+ if (process.platform === "win32") {
56883
+ env.USERNAME = legacyOpts.user;
56884
+ }
57027
56885
  }
57028
- }
57029
- if (opts.codexHome) env.CODEX_HOME = opts.codexHome;
57030
- const claudeConfigDir = opts.claudeConfigDir?.trim();
57031
- if (claudeConfigDir) env.CLAUDE_CONFIG_DIR = claudeConfigDir;
57032
- const apiBaseUrl = opts.apiBaseUrl ? retargetRetiredApiBaseUrl(opts.apiBaseUrl).baseUrl : void 0;
57033
- if (apiBaseUrl && !isProductionApiBaseUrl(apiBaseUrl)) {
57034
- env.MRRLIN_API_BASE_URL = apiBaseUrl;
57035
- if (opts.apiAllowRemote && !isStagingApiBaseUrl(apiBaseUrl) && !isLoopbackUrl(apiBaseUrl)) {
57036
- env.MRRLIN_API_ALLOW_REMOTE = "1";
56886
+ if (legacyOpts.codexHome) env.CODEX_HOME = legacyOpts.codexHome;
56887
+ const claudeConfigDir = legacyOpts.claudeConfigDir?.trim();
56888
+ if (claudeConfigDir) env.CLAUDE_CONFIG_DIR = claudeConfigDir;
56889
+ const apiBaseUrl = legacyOpts.apiBaseUrl ? retargetRetiredApiBaseUrl(legacyOpts.apiBaseUrl).baseUrl : void 0;
56890
+ if (apiBaseUrl && !isProductionApiBaseUrl(apiBaseUrl)) {
56891
+ env.MRRLIN_API_BASE_URL = apiBaseUrl;
56892
+ if (legacyOpts.apiAllowRemote && !isStagingApiBaseUrl(apiBaseUrl) && !isLoopbackUrl(apiBaseUrl)) {
56893
+ env.MRRLIN_API_ALLOW_REMOTE = "1";
56894
+ }
57037
56895
  }
56896
+ envLines = Object.entries(env).map(([k, v]) => ` ${k}: ${JSON.stringify(v)},`).join("\n");
57038
56897
  }
57039
- const envLines = Object.entries(env).map(([k, v]) => ` ${k}: ${JSON.stringify(v)},`).join("\n");
57040
56898
  return `module.exports = {
57041
56899
  apps: [{
57042
56900
  name: ${JSON.stringify(BRIDGE_APP_NAME)},
@@ -59723,7 +59581,7 @@ async function notifyPinRetirement(info, opts) {
59723
59581
  type: "bug",
59724
59582
  category: "bug",
59725
59583
  subcategory: "reported",
59726
- domain: "code",
59584
+ domains: ["code"],
59727
59585
  impact: "medium",
59728
59586
  notes: `The pinned strong model "${info.pinnedModel}" for provider "${info.providerId}" was not found in the provider's live model list (retired by provider). Falling back to live default model "${info.fallbackModel}". Please update the operator pin in ~/.mrrlin/ecosystem.config.cjs or the corresponding environment variable (MRRLIN_${info.providerId.toUpperCase()}_STRONG_MODEL).`,
59729
59587
  operatorSeedRequest: `The pinned strong model "${info.pinnedModel}" for provider "${info.providerId}" has been retired. Fallback in use: "${info.fallbackModel}". Update the strong model pin.`,
@@ -60038,7 +59896,7 @@ var BASE_INSTRUCTIONS_PREFIX = [
60038
59896
  "",
60039
59897
  "Publish policy and task split:",
60040
59898
  "- When creating tasks for work that produces a published web page, author the work as a `[code_change]` task and a `[page]` task linked by `blocked_by` (the `[page]` task is blocked by the `[code_change]` task).",
60041
- '- The `[page]` task must carry domain: "content" so it routes to non-code execution and closes on a live url receipt.',
59899
+ '- The `[page]` task must carry domains: ["content"] so it routes to non-code execution and closes on a live url receipt.',
60042
59900
  "- Set autoDeploy on the code change task following the three-way heuristic:",
60043
59901
  " - the result is only meaningful once live and there is nothing subjective to look at (`robots.txt`, `llms.txt`, sitemap, metatags) \u2192 release-to-live (`prod`);",
60044
59902
  " - the operator would plausibly want to see it rendered first (a landing page, copy, a layout) \u2192 stop at preview (`dev`);",
@@ -60233,9 +60091,10 @@ async function gatherContext(client, projectSlug, task, openRunId) {
60233
60091
  async function deployWorkflowReverifyNotice(client, projectSlug) {
60234
60092
  const envs = ["dev", "prod"];
60235
60093
  const flagged = [];
60094
+ const clientWithDeploy = client;
60236
60095
  for (const env of envs) {
60237
60096
  try {
60238
- const row = await client.getProjectDeployWorkflow(projectSlug, env);
60097
+ const row = await clientWithDeploy.getProjectDeployWorkflow?.(projectSlug, env);
60239
60098
  if (row?.reverifyRequested) flagged.push(env);
60240
60099
  } catch {
60241
60100
  }
@@ -60558,8 +60417,10 @@ async function driveRun(deps, projectSlug, runId) {
60558
60417
  }
60559
60418
  }
60560
60419
  }
60561
- const runDomain = normalizeDomain(task.domain);
60562
- if (runDomain !== "code") {
60420
+ const runDomains = Array.isArray(task.domains) ? task.domains : [task.domains ?? "code"];
60421
+ const isPureCode = runDomains.length === 1 && runDomains[0] === "code";
60422
+ if (!isPureCode) {
60423
+ const runDomain = runDomains[0] ?? "code";
60563
60424
  await driveNonCodeRun(deps, projectSlug, runId, leaseId, task, runDomain);
60564
60425
  return;
60565
60426
  }
@@ -61240,7 +61101,7 @@ function buildDeliverableAuthorPrompt(task, specMarkdown) {
61240
61101
  `--- TASK ---`,
61241
61102
  `id: ${task.id}`,
61242
61103
  `title: ${task.title}`,
61243
- `domain: ${task.domain ?? "code"}`,
61104
+ `domains: ${(task.domains ?? ["code"]).join(", ")}`,
61244
61105
  ``,
61245
61106
  `notes:`,
61246
61107
  task.notes ?? "(none)",
@@ -61906,7 +61767,7 @@ async function runBackstopSweep(deps, drive = (slug, id) => driveRun(deps, slug,
61906
61767
  }
61907
61768
  function isRunRecoverableByThisBridge(run3, worktreeRoot) {
61908
61769
  if (!worktreeRoot) return true;
61909
- if (!run3.worktreeId) return false;
61770
+ if (!run3.worktreeId) return true;
61910
61771
  const relative = import_node_path30.default.relative(import_node_path30.default.resolve(worktreeRoot), import_node_path30.default.resolve(run3.worktreeId));
61911
61772
  return relative === "" || relative.length > 0 && !relative.startsWith("..") && !import_node_path30.default.isAbsolute(relative);
61912
61773
  }
@@ -66417,97 +66278,6 @@ async function startExecutorServer(opts) {
66417
66278
  }
66418
66279
  }
66419
66280
 
66420
- // src/verification-turn.ts
66421
- function buildVerificationPrompt(input) {
66422
- return [
66423
- `You are verifying a deployment. The application is live at: ${input.environmentUrl}`,
66424
- `Run ONLY read-only HTTP checks against that URL to evaluate the verification steps below.`,
66425
- `Behave as if you had no repository, filesystem, or credential access: do not read or write`,
66426
- `files, do not use any credentials, do not contact any other host, and do not attempt to`,
66427
- `mutate anything \u2014 only observe the deployed app over HTTP. (This is a rule you must follow,`,
66428
- `not something the environment enforces.)`,
66429
- ``,
66430
- `Verification steps for environment "${input.env}":`,
66431
- input.verificationSection,
66432
- ``,
66433
- `When finished, end your final message with EXACTLY one line, nothing after it:`,
66434
- `VERDICT: PASS - <one-line summary> (if every step passed)`,
66435
- `VERDICT: FAIL - <one-line summary> (if any step failed or could not be confirmed)`
66436
- ].join("\n");
66437
- }
66438
- function parseVerdict2(text) {
66439
- const re = /^[^\S\n]*VERDICT:\s*(PASS|FAIL)\b[^\S\n]*[-—:]?[^\S\n]*([^\n]*)/gim;
66440
- let match;
66441
- let last = null;
66442
- while ((match = re.exec(text)) !== null) last = match;
66443
- if (!last) {
66444
- return {
66445
- verdict: "fail",
66446
- summary: "no parseable VERDICT line in verification output",
66447
- turnRef: null,
66448
- fullOutput: text
66449
- };
66450
- }
66451
- const verdict = (last[1] ?? "").toLowerCase() === "pass" ? "pass" : "fail";
66452
- const summary = (last[2] ?? "").trim() || `verification ${verdict}`;
66453
- return { verdict, summary, turnRef: null, fullOutput: text };
66454
- }
66455
- function isBlockedVerificationHost(hostname3) {
66456
- const h = hostname3.toLowerCase().replace(/^\[|\]$/g, "");
66457
- if (h === "localhost" || h.endsWith(".localhost") || h.endsWith(".local") || h.endsWith(".internal")) return true;
66458
- const isIpv6 = h.includes(":");
66459
- if (isIpv6) {
66460
- if (h === "::1" || h === "::" || h.startsWith("fe80:") || h.startsWith("fc") || h.startsWith("fd")) return true;
66461
- if (h.startsWith("::ffff:")) return true;
66462
- }
66463
- const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(h);
66464
- if (m) {
66465
- const a = Number(m[1]);
66466
- const b = Number(m[2]);
66467
- if (a === 0 || a === 127 || a === 10) return true;
66468
- if (a === 169 && b === 254) return true;
66469
- if (a === 172 && b >= 16 && b <= 31) return true;
66470
- if (a === 192 && b === 168) return true;
66471
- }
66472
- return false;
66473
- }
66474
- function createRunVerificationTurn(deps) {
66475
- return async (input) => {
66476
- let url2;
66477
- try {
66478
- url2 = new URL(input.environmentUrl);
66479
- } catch {
66480
- return { verdict: "fail", summary: `invalid environmentUrl: ${input.environmentUrl}`, turnRef: null };
66481
- }
66482
- if (url2.protocol !== "http:" && url2.protocol !== "https:") {
66483
- return { verdict: "fail", summary: `unsupported scheme in environmentUrl: ${url2.protocol}`, turnRef: null };
66484
- }
66485
- const hostname3 = url2.hostname;
66486
- if (!hostname3) {
66487
- return { verdict: "fail", summary: `environmentUrl has no host: ${input.environmentUrl}`, turnRef: null };
66488
- }
66489
- if (isBlockedVerificationHost(hostname3)) {
66490
- return { verdict: "fail", summary: `refusing to verify against non-public host: ${hostname3}`, turnRef: null };
66491
- }
66492
- const providerId = input.run.agentSeat;
66493
- if (providerId == null) {
66494
- return { verdict: "fail", summary: "run has no resolved agent_seat", turnRef: null };
66495
- }
66496
- const { handle, dispose, cwd } = await deps.startSandbox([hostname3], providerId);
66497
- try {
66498
- let text;
66499
- try {
66500
- text = await deps.runTurn(handle, buildVerificationPrompt(input), cwd);
66501
- } catch (error51) {
66502
- return { verdict: "fail", summary: `verification turn errored: ${String(error51).slice(0, 160)}`, turnRef: null };
66503
- }
66504
- return parseVerdict2(text);
66505
- } finally {
66506
- dispose();
66507
- }
66508
- };
66509
- }
66510
-
66511
66281
  // src/self-review.ts
66512
66282
  function buildSelfReviewPrompt(task) {
66513
66283
  return [
@@ -66577,33 +66347,6 @@ function createSelfReviewTurn(deps) {
66577
66347
  };
66578
66348
  }
66579
66349
 
66580
- // src/verification-section.ts
66581
- function extractVerificationSection(markdown) {
66582
- const lines2 = markdown.split("\n");
66583
- const headingRe = /^(#{1,6})\s+(.*)$/;
66584
- let start = -1;
66585
- let level = 0;
66586
- for (let i = 0; i < lines2.length; i += 1) {
66587
- const m = headingRe.exec(lines2[i] ?? "");
66588
- if (m && /^verification\b/i.test((m[2] ?? "").trim())) {
66589
- start = i + 1;
66590
- level = (m[1] ?? "").length;
66591
- break;
66592
- }
66593
- }
66594
- if (start === -1) return null;
66595
- let end = lines2.length;
66596
- for (let i = start; i < lines2.length; i += 1) {
66597
- const m = headingRe.exec(lines2[i] ?? "");
66598
- if (m && (m[1] ?? "").length <= level) {
66599
- end = i;
66600
- break;
66601
- }
66602
- }
66603
- const body = lines2.slice(start, end).join("\n").trim();
66604
- return body.length > 0 ? body : null;
66605
- }
66606
-
66607
66350
  // src/director-reseed.ts
66608
66351
  var import_node_fs21 = require("node:fs");
66609
66352
  var import_node_readline = require("node:readline");
@@ -66668,6 +66411,23 @@ function extractItems(lines2, sessionIds) {
66668
66411
  items.sort((a, b) => a.ts < b.ts ? -1 : a.ts > b.ts ? 1 : 0);
66669
66412
  return { items, skipped: skipped2 };
66670
66413
  }
66414
+ function contextMessagesToReseedItems(contextMessages, currentPromptText) {
66415
+ if (!contextMessages || contextMessages.length === 0) return [];
66416
+ const prior = contextMessages.filter((msg, idx) => {
66417
+ if (idx === 0 && currentPromptText && msg.role === "user" && msg.content.trim() === currentPromptText.trim()) {
66418
+ return false;
66419
+ }
66420
+ return true;
66421
+ });
66422
+ if (prior.length === 0) return [];
66423
+ const chronological = [...prior].reverse();
66424
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
66425
+ return chronological.map((msg, idx) => ({
66426
+ ts: `${nowIso.slice(0, 10)}T00:00:${(idx + 1).toString().padStart(2, "0")}.000Z`,
66427
+ kind: msg.role === "assistant" ? "assistant" : "user",
66428
+ text: msg.content
66429
+ }));
66430
+ }
66671
66431
  function renderReseedBlock(items, opts) {
66672
66432
  const buildText = (kept2, droppedCount) => {
66673
66433
  const header = [
@@ -69182,7 +68942,9 @@ function createBridgeMessageHandler(deps) {
69182
68942
  // Client-supplied, so the elements are validated and not just the shape:
69183
68943
  // this array selects which sessions' log rows get replayed into a prompt.
69184
68944
  priorSessionIds: Array.isArray(msg.priorSessionIds) ? msg.priorSessionIds.filter((id) => typeof id === "string" && id.length > 0) : [],
69185
- otherBytes
68945
+ otherBytes,
68946
+ contextMessages: msg.context?.contextMessages,
68947
+ currentText: effectiveText
69186
68948
  });
69187
68949
  } catch {
69188
68950
  reseedPrefix = null;
@@ -69591,48 +69353,6 @@ async function startDirectorBridge() {
69591
69353
  selfReviewTurn: createSelfReviewTurn({
69592
69354
  runTurn: (handle, cwd, prompt) => wrappedTurnLock.run("chat", () => runCollectedAgentTurn(handle, prompt, cwd, SELF_REVIEW_BASE_INSTRUCTIONS))
69593
69355
  }),
69594
- // ADR 0015 deploy gate: after a pushed run reaches dev_deploy/prod_deploy, the backstop sweep
69595
- // dispatches the deploy + polls + runs a verification turn against the deployed env URL in a
69596
- // throwaway cwd. Per the owner no-sandbox decision the verification turn runs from the real
69597
- // operator config like every other executor turn; its observe-only base instructions bound
69598
- // behaviour, provider-generic since task 4.1.
69599
- deployGate: {
69600
- loadVerificationSection: async (projectSlug, task) => {
69601
- if (!task.specWikiPageId) return null;
69602
- const page = await client.getWikiPage(projectSlug, task.specWikiPageId).catch(() => null);
69603
- return page?.markdownBody ? extractVerificationSection(page.markdownBody) : null;
69604
- },
69605
- runVerificationTurn: createRunVerificationTurn({
69606
- // egressDomains is retained in the seam signature (verification-turn.ts still computes the
69607
- // env host) but no longer constrains anything — the executor is unrestricted.
69608
- startSandbox: async (_egressDomains, providerId) => {
69609
- const cwd = import_node_fs23.default.mkdtempSync(import_node_path37.default.join(import_node_os17.default.tmpdir(), "mrrlin-verify-"));
69610
- let executor;
69611
- try {
69612
- executor = await startExecutorServer({ worktree: cwd, provider: providerId });
69613
- } catch (error51) {
69614
- try {
69615
- import_node_fs23.default.rmSync(cwd, { recursive: true, force: true });
69616
- } catch {
69617
- }
69618
- throw error51;
69619
- }
69620
- return {
69621
- handle: executor.handle,
69622
- cwd,
69623
- dispose: () => {
69624
- executor.dispose();
69625
- try {
69626
- import_node_fs23.default.rmSync(cwd, { recursive: true, force: true });
69627
- } catch {
69628
- }
69629
- }
69630
- };
69631
- },
69632
- runTurn: (handle, prompt, cwd) => wrappedTurnLock.run("chat", () => runCollectedAgentTurn(handle, prompt, cwd, VERIFICATION_BASE_INSTRUCTIONS))
69633
- }),
69634
- budgetSeconds: deployPollBudgetSeconds()
69635
- },
69636
69356
  // ADR 0020 Phase 2: run the deliverable-gate over a non-code run's authored Deliverables page.
69637
69357
  // Builds the gate deps (resolving domain/heads/criteria from the page's task + spec) and runs the
69638
69358
  // same convergence loop the spec-gate uses. runId is threaded for verdict provenance (decision 10).
@@ -69818,27 +69538,32 @@ async function startDirectorBridge() {
69818
69538
  });
69819
69539
  return NO_HISTORY_BLOCK;
69820
69540
  };
69821
- if (!resolveBridgeLogEnabled()) return refuse2("log-disabled");
69822
- if (!resolvePromptsEnabled()) return refuse2("prompts-not-logged");
69823
- const retentionWindowMs = readEnvInt("MRRLIN_LOG_RETENTION_AGE_DAYS", 30) * 864e5;
69824
- const reseedWindowMs = Math.min(config2.idleThreadTtlMs, retentionWindowMs);
69825
- if (reseedWindowMs < config2.idleThreadTtlMs) {
69826
- bridgeLogger.logOut({
69827
- spanId: "reseed",
69828
- sessionId: args.directorSessionId,
69829
- type: "<reseed-window-clamped>",
69830
- payload: {
69831
- idleThreadTtlMs: config2.idleThreadTtlMs,
69832
- retentionWindowMs,
69833
- effectiveWindowMs: reseedWindowMs
69834
- }
69835
- });
69541
+ let items = [];
69542
+ let filesCount = 0;
69543
+ let missingFilesCount = 0;
69544
+ let skippedCount = 0;
69545
+ const logEnabled = resolveBridgeLogEnabled();
69546
+ const promptsEnabled = resolvePromptsEnabled();
69547
+ if (logEnabled && promptsEnabled) {
69548
+ const retentionWindowMs = readEnvInt("MRRLIN_LOG_RETENTION_AGE_DAYS", 30) * 864e5;
69549
+ const reseedWindowMs = retentionWindowMs;
69550
+ const files = dayFilesInWindow(/* @__PURE__ */ new Date(), reseedWindowMs);
69551
+ filesCount = files.length;
69552
+ const sessionIds = [args.directorSessionId, ...args.priorSessionIds];
69553
+ const { lines: lines2, missingFiles } = await readSessionRows(import_node_path37.default.join(config2.stateDir, "logs"), files, sessionIds);
69554
+ missingFilesCount = missingFiles;
69555
+ const extracted = extractItems(lines2, sessionIds);
69556
+ items = extracted.items;
69557
+ skippedCount = extracted.skipped;
69558
+ }
69559
+ if (items.length === 0 && args.contextMessages && args.contextMessages.length > 0) {
69560
+ items = contextMessagesToReseedItems(args.contextMessages, args.currentText);
69561
+ }
69562
+ if (items.length === 0) {
69563
+ if (!logEnabled) return refuse2("log-disabled");
69564
+ if (!promptsEnabled) return refuse2("prompts-not-logged");
69565
+ return refuse2("no-rows");
69836
69566
  }
69837
- const files = dayFilesInWindow(/* @__PURE__ */ new Date(), reseedWindowMs);
69838
- const sessionIds = [args.directorSessionId, ...args.priorSessionIds];
69839
- const { lines: lines2, missingFiles } = await readSessionRows(import_node_path37.default.join(config2.stateDir, "logs"), files, sessionIds);
69840
- const { items, skipped: skipped2 } = extractItems(lines2, sessionIds);
69841
- if (items.length === 0) return refuse2("no-rows");
69842
69567
  const budget = Math.max(0, Math.min(config2.reseedMaxTokens, config2.turnMaxTokens - args.otherBytes));
69843
69568
  const block = renderReseedBlock(items, {
69844
69569
  maxTokens: budget,
@@ -69852,9 +69577,9 @@ async function startDirectorBridge() {
69852
69577
  payload: {
69853
69578
  sessionId: args.directorSessionId,
69854
69579
  priorSessionIds: args.priorSessionIds.length,
69855
- dayFiles: files.length,
69856
- missingFiles,
69857
- parseSkips: skipped2,
69580
+ dayFiles: filesCount,
69581
+ missingFiles: missingFilesCount,
69582
+ parseSkips: skippedCount,
69858
69583
  itemCount: block.itemCount,
69859
69584
  droppedCount: block.droppedCount,
69860
69585
  estimatedTokens: Buffer.byteLength(block.text, "utf8"),
@@ -71321,49 +71046,440 @@ function runSetCredential(args) {
71321
71046
  var import_node_os18 = __toESM(require("node:os"), 1);
71322
71047
  var import_node_fs24 = require("node:fs");
71323
71048
  var import_node_path38 = __toESM(require("node:path"), 1);
71324
- function isOwnedEcosystemConfig(text) {
71325
- return /name:\s*"mrrlin-bridge"/.test(text) && /script:\s*"mrrlin-mcp"/.test(text) && /args:\s*"director-bridge"/.test(text);
71326
- }
71327
- function readEnvString(text, key) {
71328
- const match = new RegExp(`\\b${key}:\\s*("(?:[^"\\\\]|\\\\.)*")`).exec(text);
71329
- if (!match?.[1]) return void 0;
71049
+ function parseEnvLine(line, env) {
71050
+ const trimmed = line.trim();
71051
+ if (!trimmed || trimmed === "}" || trimmed === "},") return;
71052
+ const match = /^\s*(?:([a-zA-Z_$][a-zA-Z0-9_$]*)|"((?:[^"\\]|\\.)*)"|(\["__proto__"\]))\s*:\s*("(?:[^"\\]|\\.)*")\s*,?\s*$/.exec(line);
71053
+ if (!match) return;
71054
+ const identKey = match[1];
71055
+ const quotedKey = match[2];
71056
+ const computedProto = match[3];
71057
+ const rawValue = match[4];
71058
+ if (!rawValue) return;
71059
+ let key = null;
71060
+ if (computedProto) {
71061
+ key = "__proto__";
71062
+ } else if (identKey) {
71063
+ if (identKey === "__proto__") {
71064
+ return;
71065
+ }
71066
+ key = identKey;
71067
+ } else if (quotedKey !== void 0) {
71068
+ try {
71069
+ const parsedKey = JSON.parse(`"${quotedKey}"`);
71070
+ if (typeof parsedKey !== "string" || parsedKey === "__proto__") {
71071
+ return;
71072
+ }
71073
+ key = parsedKey;
71074
+ } catch {
71075
+ return;
71076
+ }
71077
+ }
71078
+ if (key === null) return;
71330
71079
  try {
71331
- const value = JSON.parse(match[1]);
71332
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
71080
+ const parsedValue = JSON.parse(rawValue);
71081
+ if (typeof parsedValue === "string") {
71082
+ env[key] = parsedValue;
71083
+ }
71333
71084
  } catch {
71334
- return void 0;
71085
+ return;
71335
71086
  }
71336
71087
  }
71337
- function normalizeServiceApiTarget(apiBaseUrl, allowRemote) {
71338
- const normalized = retargetRetiredApiBaseUrl(apiBaseUrl).baseUrl;
71339
- if (isProductionApiBaseUrl(normalized)) return {};
71340
- const target = { apiBaseUrl: normalized };
71341
- if (allowRemote && !isStagingApiBaseUrl(normalized) && !isLoopbackUrl(normalized)) target.apiAllowRemote = true;
71342
- return target;
71088
+ function parseExistingEcosystemConfig(text) {
71089
+ if (text === null || text === void 0) {
71090
+ return { kind: "missing" };
71091
+ }
71092
+ let mode = "code";
71093
+ const templateCurlyStack = [];
71094
+ let curlyDepth = 0;
71095
+ let squareDepth = 0;
71096
+ let parenDepth = 0;
71097
+ let moduleExportsOpened = false;
71098
+ let moduleExportsClosed = false;
71099
+ let appsArrayOpened = false;
71100
+ let appsArrayClosed = false;
71101
+ let appCandidateActive = false;
71102
+ let candidateHasName = false;
71103
+ let candidateHasScript = false;
71104
+ let candidateHasArgs = false;
71105
+ let candidateHasEnv = false;
71106
+ let candidateEnvOpenedOnLine = false;
71107
+ let candidateEnvClosed = false;
71108
+ let candidateEnv = null;
71109
+ let matchedAppEnv = null;
71110
+ let matchedAppCount = 0;
71111
+ let recentCode = "";
71112
+ let lineCode = "";
71113
+ const len = text.length;
71114
+ let i = 0;
71115
+ while (i < len) {
71116
+ const ch = text[i] ?? "";
71117
+ if (mode === "line_comment") {
71118
+ if (ch === "\n") {
71119
+ mode = "code";
71120
+ lineCode = "";
71121
+ candidateEnvOpenedOnLine = false;
71122
+ }
71123
+ i++;
71124
+ continue;
71125
+ }
71126
+ if (mode === "block_comment") {
71127
+ if (ch === "*" && i + 1 < len && text[i + 1] === "/") {
71128
+ mode = "code";
71129
+ i += 2;
71130
+ continue;
71131
+ }
71132
+ if (ch === "\n") {
71133
+ lineCode = "";
71134
+ candidateEnvOpenedOnLine = false;
71135
+ }
71136
+ i++;
71137
+ continue;
71138
+ }
71139
+ if (mode === "single_quote") {
71140
+ if (ch === "\\") {
71141
+ i += 2;
71142
+ continue;
71143
+ }
71144
+ if (ch === "'") {
71145
+ mode = "code";
71146
+ i++;
71147
+ continue;
71148
+ }
71149
+ if (ch === "\n" || ch === "\r") {
71150
+ return { kind: "rejected", text };
71151
+ }
71152
+ i++;
71153
+ continue;
71154
+ }
71155
+ if (mode === "double_quote") {
71156
+ if (ch === "\\") {
71157
+ lineCode += ch + (text[i + 1] ?? "");
71158
+ i += 2;
71159
+ continue;
71160
+ }
71161
+ lineCode += ch;
71162
+ if (ch === '"') {
71163
+ mode = "code";
71164
+ if (appCandidateActive && curlyDepth === 2 && squareDepth === 1) {
71165
+ if (/name\s*:\s*"mrrlin-bridge"/.test(lineCode)) candidateHasName = true;
71166
+ if (/script\s*:\s*"mrrlin-mcp"/.test(lineCode)) candidateHasScript = true;
71167
+ if (/args\s*:\s*"director-bridge"/.test(lineCode)) candidateHasArgs = true;
71168
+ }
71169
+ i++;
71170
+ continue;
71171
+ }
71172
+ if (ch === "\n" || ch === "\r") {
71173
+ return { kind: "rejected", text };
71174
+ }
71175
+ i++;
71176
+ continue;
71177
+ }
71178
+ if (mode === "template") {
71179
+ if (ch === "\\") {
71180
+ i += 2;
71181
+ continue;
71182
+ }
71183
+ if (ch === "`") {
71184
+ mode = "code";
71185
+ i++;
71186
+ continue;
71187
+ }
71188
+ if (ch === "$" && i + 1 < len && text[i + 1] === "{") {
71189
+ mode = "code";
71190
+ templateCurlyStack.push(curlyDepth);
71191
+ curlyDepth++;
71192
+ i += 2;
71193
+ continue;
71194
+ }
71195
+ i++;
71196
+ continue;
71197
+ }
71198
+ if (ch === "/" && i + 1 < len) {
71199
+ if (text[i + 1] === "/") {
71200
+ mode = "line_comment";
71201
+ i += 2;
71202
+ continue;
71203
+ }
71204
+ if (text[i + 1] === "*") {
71205
+ mode = "block_comment";
71206
+ i += 2;
71207
+ continue;
71208
+ }
71209
+ return { kind: "rejected", text };
71210
+ }
71211
+ if (ch === "/") {
71212
+ return { kind: "rejected", text };
71213
+ }
71214
+ if (ch === "'") {
71215
+ mode = "single_quote";
71216
+ i++;
71217
+ continue;
71218
+ }
71219
+ if (ch === '"') {
71220
+ mode = "double_quote";
71221
+ lineCode += ch;
71222
+ i++;
71223
+ continue;
71224
+ }
71225
+ if (ch === "`") {
71226
+ mode = "template";
71227
+ i++;
71228
+ continue;
71229
+ }
71230
+ if (ch === "\n") {
71231
+ if (appCandidateActive && candidateHasEnv && !candidateEnvClosed && curlyDepth === 3 && candidateEnv) {
71232
+ parseEnvLine(lineCode, candidateEnv);
71233
+ }
71234
+ lineCode = "";
71235
+ candidateEnvOpenedOnLine = false;
71236
+ recentCode += " ";
71237
+ i++;
71238
+ continue;
71239
+ }
71240
+ lineCode += ch;
71241
+ recentCode += ch;
71242
+ if (ch === "{") {
71243
+ if (curlyDepth === 0 && squareDepth === 0) {
71244
+ if (/module\.exports\s*=\s*\{$/.test(recentCode.trim())) {
71245
+ if (moduleExportsOpened) return { kind: "rejected", text };
71246
+ moduleExportsOpened = true;
71247
+ }
71248
+ } else if (moduleExportsOpened && !moduleExportsClosed && appsArrayOpened && !appsArrayClosed && squareDepth === 1 && curlyDepth === 1) {
71249
+ appCandidateActive = true;
71250
+ candidateHasName = false;
71251
+ candidateHasScript = false;
71252
+ candidateHasArgs = false;
71253
+ candidateHasEnv = false;
71254
+ candidateEnvOpenedOnLine = false;
71255
+ candidateEnvClosed = false;
71256
+ candidateEnv = /* @__PURE__ */ Object.create(null);
71257
+ } else if (appCandidateActive && squareDepth === 1 && curlyDepth === 2) {
71258
+ if (/\benv\s*:\s*\{$/.test(recentCode.trim())) {
71259
+ candidateHasEnv = true;
71260
+ candidateEnvOpenedOnLine = true;
71261
+ }
71262
+ }
71263
+ curlyDepth++;
71264
+ recentCode = "";
71265
+ i++;
71266
+ continue;
71267
+ }
71268
+ if (ch === "}") {
71269
+ if (curlyDepth === 0) return { kind: "rejected", text };
71270
+ if (templateCurlyStack.length > 0 && templateCurlyStack[templateCurlyStack.length - 1] === curlyDepth - 1) {
71271
+ templateCurlyStack.pop();
71272
+ mode = "template";
71273
+ curlyDepth--;
71274
+ recentCode = "";
71275
+ i++;
71276
+ continue;
71277
+ }
71278
+ if (appCandidateActive && candidateHasEnv && !candidateEnvClosed && curlyDepth === 3) {
71279
+ if (candidateEnvOpenedOnLine) {
71280
+ candidateHasEnv = false;
71281
+ }
71282
+ const beforeBrace = lineCode.slice(0, -1);
71283
+ if (beforeBrace.trim() && candidateEnv) {
71284
+ parseEnvLine(beforeBrace, candidateEnv);
71285
+ }
71286
+ candidateEnvClosed = true;
71287
+ } else if (appCandidateActive && curlyDepth === 2 && squareDepth === 1) {
71288
+ if (candidateHasName && candidateHasScript && candidateHasArgs && candidateHasEnv && candidateEnvClosed) {
71289
+ matchedAppCount++;
71290
+ if (matchedAppCount > 1) return { kind: "rejected", text };
71291
+ matchedAppEnv = candidateEnv;
71292
+ }
71293
+ appCandidateActive = false;
71294
+ candidateEnv = null;
71295
+ } else if (moduleExportsOpened && !moduleExportsClosed && curlyDepth === 1 && squareDepth === 0) {
71296
+ moduleExportsClosed = true;
71297
+ }
71298
+ curlyDepth--;
71299
+ recentCode = "";
71300
+ i++;
71301
+ continue;
71302
+ }
71303
+ if (ch === "[") {
71304
+ if (moduleExportsOpened && !moduleExportsClosed && curlyDepth === 1 && squareDepth === 0) {
71305
+ if (/\bapps\s*:\s*\[$/.test(recentCode.trim())) {
71306
+ if (appsArrayOpened) return { kind: "rejected", text };
71307
+ appsArrayOpened = true;
71308
+ }
71309
+ }
71310
+ squareDepth++;
71311
+ recentCode = "";
71312
+ i++;
71313
+ continue;
71314
+ }
71315
+ if (ch === "]") {
71316
+ if (squareDepth === 0) return { kind: "rejected", text };
71317
+ if (moduleExportsOpened && !moduleExportsClosed && appsArrayOpened && squareDepth === 1 && curlyDepth === 1) {
71318
+ appsArrayClosed = true;
71319
+ }
71320
+ squareDepth--;
71321
+ recentCode = "";
71322
+ i++;
71323
+ continue;
71324
+ }
71325
+ if (ch === "(") {
71326
+ parenDepth++;
71327
+ i++;
71328
+ continue;
71329
+ }
71330
+ if (ch === ")") {
71331
+ if (parenDepth === 0) return { kind: "rejected", text };
71332
+ parenDepth--;
71333
+ i++;
71334
+ continue;
71335
+ }
71336
+ i++;
71337
+ }
71338
+ if (mode !== "code") return { kind: "rejected", text };
71339
+ if (curlyDepth !== 0 || squareDepth !== 0 || parenDepth !== 0) return { kind: "rejected", text };
71340
+ if (!moduleExportsOpened || !moduleExportsClosed) return { kind: "rejected", text };
71341
+ if (!appsArrayOpened || !appsArrayClosed) return { kind: "rejected", text };
71342
+ if (matchedAppCount !== 1 || !matchedAppEnv) return { kind: "rejected", text };
71343
+ return { kind: "accepted", env: matchedAppEnv, text };
71344
+ }
71345
+ var CURATED_KEYS = [
71346
+ "CODEX_HOME",
71347
+ "CLAUDE_CONFIG_DIR",
71348
+ "MRRLIN_DIRECTOR_BRIDGE_LOG",
71349
+ "MRRLIN_API_BASE_URL",
71350
+ "MRRLIN_API_ALLOW_REMOTE",
71351
+ "MRRLIN_STAGING"
71352
+ ];
71353
+ function mergeConfigEnv(depsEnv, carriedEnv) {
71354
+ const isWin = process.platform === "win32";
71355
+ const DENIED_CARRIED_KEYS = new Set(isWin ? ["HOME", "PATH", "USER", "USERNAME"] : ["HOME", "PATH", "USER"]);
71356
+ const mergedEnv = /* @__PURE__ */ Object.create(null);
71357
+ const preservedNames = [];
71358
+ const droppedNames = [];
71359
+ const carriedKeys = Object.keys(carriedEnv);
71360
+ for (const key of carriedKeys) {
71361
+ if (DENIED_CARRIED_KEYS.has(key)) {
71362
+ droppedNames.push(key);
71363
+ continue;
71364
+ }
71365
+ const envVal = depsEnv[key];
71366
+ if (Object.hasOwn(depsEnv, key) && envVal !== void 0) {
71367
+ let installerVal = envVal;
71368
+ if (key === "CODEX_HOME" || key === "CLAUDE_CONFIG_DIR" || key === "MRRLIN_API_BASE_URL") {
71369
+ installerVal = installerVal.trim();
71370
+ }
71371
+ if (installerVal === "") {
71372
+ droppedNames.push(key);
71373
+ } else {
71374
+ preservedNames.push(key);
71375
+ mergedEnv[key] = installerVal;
71376
+ }
71377
+ } else {
71378
+ const carriedVal = carriedEnv[key];
71379
+ if (carriedVal !== void 0) {
71380
+ preservedNames.push(key);
71381
+ mergedEnv[key] = carriedVal;
71382
+ }
71383
+ }
71384
+ }
71385
+ for (const key of CURATED_KEYS) {
71386
+ if (Object.hasOwn(carriedEnv, key)) continue;
71387
+ const envVal = depsEnv[key];
71388
+ if (Object.hasOwn(depsEnv, key) && envVal !== void 0) {
71389
+ let val = envVal;
71390
+ if (key === "CODEX_HOME" || key === "CLAUDE_CONFIG_DIR" || key === "MRRLIN_API_BASE_URL") {
71391
+ val = val.trim();
71392
+ }
71393
+ if (val !== "") {
71394
+ mergedEnv[key] = val;
71395
+ }
71396
+ } else {
71397
+ if (key === "MRRLIN_DIRECTOR_BRIDGE_LOG") {
71398
+ mergedEnv[key] = "1";
71399
+ }
71400
+ }
71401
+ }
71402
+ preservedNames.sort();
71403
+ droppedNames.sort();
71404
+ return { mergedEnv, preservedNames, droppedNames };
71343
71405
  }
71344
- function readExistingServiceApiTarget(deps, ecoPath) {
71345
- try {
71346
- const existing = deps.readFile?.(ecoPath);
71347
- if (!existing || !isOwnedEcosystemConfig(existing)) return void 0;
71348
- const apiBaseUrl = readEnvString(existing, "MRRLIN_API_BASE_URL");
71349
- if (apiBaseUrl) {
71350
- return normalizeServiceApiTarget(apiBaseUrl, readEnvString(existing, "MRRLIN_API_ALLOW_REMOTE") === "1");
71406
+ function deriveMergedApiRouting(mergedEnv, invokingBridgeUrl) {
71407
+ const result = /* @__PURE__ */ Object.create(null);
71408
+ for (const k of Object.keys(mergedEnv)) {
71409
+ const v = mergedEnv[k];
71410
+ if (v !== void 0) {
71411
+ result[k] = v;
71351
71412
  }
71352
- if (readEnvString(existing, "MRRLIN_STAGING") === "1") {
71353
- return { apiBaseUrl: STAGING_API_BASE_URL };
71413
+ }
71414
+ if (Object.hasOwn(result, "MRRLIN_API_BASE_URL")) {
71415
+ const rawApi = result["MRRLIN_API_BASE_URL"];
71416
+ if (rawApi === "") {
71417
+ result["MRRLIN_API_BASE_URL"] = "";
71418
+ } else if (rawApi !== void 0) {
71419
+ const retargeted = retargetRetiredApiBaseUrl(rawApi).baseUrl;
71420
+ result["MRRLIN_API_BASE_URL"] = retargeted;
71421
+ }
71422
+ } else {
71423
+ if (result["MRRLIN_STAGING"] === "1") {
71424
+ result["MRRLIN_API_BASE_URL"] = STAGING_API_BASE_URL;
71425
+ } else if (bridgeUrlImpliesStaging(invokingBridgeUrl)) {
71426
+ result["MRRLIN_API_BASE_URL"] = STAGING_API_BASE_URL;
71354
71427
  }
71355
- return void 0;
71356
- } catch {
71357
- return void 0;
71358
71428
  }
71429
+ return result;
71430
+ }
71431
+ function escapeRegex2(s) {
71432
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
71359
71433
  }
71360
- function deriveServiceApiTarget(deps, ecoPath) {
71361
- const apiBaseUrl = deps.env.MRRLIN_API_BASE_URL?.trim();
71362
- if (apiBaseUrl) return normalizeServiceApiTarget(apiBaseUrl, deps.env.MRRLIN_API_ALLOW_REMOTE === "1");
71363
- if (deps.env.MRRLIN_STAGING === "1" || bridgeUrlImpliesStaging(deps.env.MRRLIN_BRIDGE_URL)) {
71364
- return { apiBaseUrl: STAGING_API_BASE_URL };
71434
+ function performConfigBackup(deps, ecoPath, existingText) {
71435
+ try {
71436
+ if (!deps.listDir || !deps.writeExclusiveFile) {
71437
+ deps.log("Service config backup failed.");
71438
+ return;
71439
+ }
71440
+ const dir = import_node_path38.default.dirname(ecoPath);
71441
+ let files;
71442
+ try {
71443
+ files = deps.listDir(dir);
71444
+ } catch {
71445
+ deps.log("Service config backup failed.");
71446
+ return;
71447
+ }
71448
+ if (!files || !Array.isArray(files)) {
71449
+ deps.log("Service config backup failed.");
71450
+ return;
71451
+ }
71452
+ const base = import_node_path38.default.basename(ecoPath);
71453
+ const backupRegex = new RegExp(`^${escapeRegex2(base)}\\.bak-\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}-\\d{3}Z$`);
71454
+ const existingBackups = files.filter((f) => backupRegex.test(f));
71455
+ if (existingBackups.length >= 10) {
71456
+ deps.log("Service config backup failed.");
71457
+ return;
71458
+ }
71459
+ let now;
71460
+ try {
71461
+ now = deps.clock ? deps.clock() : /* @__PURE__ */ new Date();
71462
+ } catch {
71463
+ deps.log("Service config backup failed.");
71464
+ return;
71465
+ }
71466
+ let iso;
71467
+ try {
71468
+ iso = now.toISOString();
71469
+ } catch {
71470
+ deps.log("Service config backup failed.");
71471
+ return;
71472
+ }
71473
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(iso)) {
71474
+ deps.log("Service config backup failed.");
71475
+ return;
71476
+ }
71477
+ const ts = iso.replace(/:/g, "-").replace(/\./g, "-");
71478
+ const backupPath = `${ecoPath}.bak-${ts}`;
71479
+ deps.writeExclusiveFile(backupPath, existingText);
71480
+ } catch {
71481
+ deps.log("Service config backup failed.");
71365
71482
  }
71366
- return readExistingServiceApiTarget(deps, ecoPath) ?? {};
71367
71483
  }
71368
71484
  var SYSTEM_PATH_DIRS = ["/usr/bin", "/bin", "/usr/sbin", "/sbin"];
71369
71485
  function defaultHomeOwnerUid(home) {
@@ -71439,19 +71555,34 @@ function installService(deps) {
71439
71555
  /* @__PURE__ */ new Set([...resolvedBins.map((b) => import_node_path38.default.dirname(b)), ...systemDirs])
71440
71556
  ).join(import_node_path38.default.delimiter);
71441
71557
  const ecoPath = import_node_path38.default.join(home, ".mrrlin", "ecosystem.config.cjs");
71442
- const apiTarget = deriveServiceApiTarget(deps, ecoPath);
71558
+ const existingText = deps.readFile ? deps.readFile(ecoPath) : null;
71559
+ const parseResult = parseExistingEcosystemConfig(existingText);
71560
+ if (parseResult.kind === "rejected") {
71561
+ deps.log("Service config env carry skipped.");
71562
+ }
71563
+ if (existingText !== null && existingText !== void 0) {
71564
+ performConfigBackup(deps, ecoPath, existingText);
71565
+ }
71566
+ const carriedEnv = parseResult.kind === "accepted" ? parseResult.env : /* @__PURE__ */ Object.create(null);
71567
+ const { mergedEnv, preservedNames, droppedNames } = mergeConfigEnv(deps.env, carriedEnv);
71568
+ const routedEnv = deriveMergedApiRouting(mergedEnv, deps.env.MRRLIN_BRIDGE_URL);
71569
+ routedEnv.HOME = home;
71570
+ routedEnv.PATH = pathEnv;
71571
+ if (user !== void 0) {
71572
+ routedEnv.USER = user;
71573
+ }
71574
+ if (preservedNames.length > 0) {
71575
+ deps.log(`Service env preserved: ${preservedNames.join(", ")}.`);
71576
+ }
71577
+ if (droppedNames.length > 0) {
71578
+ deps.log(`Service env dropped: ${droppedNames.join(", ")}.`);
71579
+ }
71443
71580
  const text = renderEcosystemConfig({
71444
71581
  cwd: resolved.cwd,
71445
- home,
71446
- user,
71447
- codexHome: deps.env.CODEX_HOME?.trim() || void 0,
71448
- claudeConfigDir: deps.env.CLAUDE_CONFIG_DIR?.trim() || void 0,
71449
- pathEnv,
71450
- apiBaseUrl: apiTarget.apiBaseUrl,
71451
- apiAllowRemote: apiTarget.apiAllowRemote
71582
+ env: routedEnv
71452
71583
  });
71453
71584
  deps.writeFile(ecoPath, text);
71454
- const start = deps.runPm2(["startOrReload", ecoPath]);
71585
+ const start = deps.runPm2(["startOrReload", ecoPath, "--update-env"]);
71455
71586
  if (start.code !== 0) {
71456
71587
  deps.log(`pm2 startOrReload failed (exit ${start.code}).`);
71457
71588
  return { ok: false };
@@ -72074,6 +72205,15 @@ async function main() {
72074
72205
  (0, import_node_fs25.mkdirSync)(import_node_path42.default.dirname(p), { recursive: true, mode: 448 });
72075
72206
  (0, import_node_fs25.writeFileSync)(p, c, { mode: 384 });
72076
72207
  },
72208
+ writeExclusiveFile: (p, c) => (0, import_node_fs25.writeFileSync)(p, c, { flag: "wx", mode: 384 }),
72209
+ listDir: (p) => {
72210
+ try {
72211
+ return (0, import_node_fs25.readdirSync)(p);
72212
+ } catch {
72213
+ return null;
72214
+ }
72215
+ },
72216
+ clock: () => /* @__PURE__ */ new Date(),
72077
72217
  // Swallows every error on purpose — the only caller uses this to recover settings from the
72078
72218
  // config it is about to overwrite, and a missing or unreadable file means "no previous
72079
72219
  // value", never a failed install. See readExistingTelemetryDefaultProject.