@mrrlin-dev/mcp 0.4.95 → 0.4.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +1834 -256
- package/package.json +1 -1
package/dist/bin.cjs
CHANGED
|
@@ -32,6 +32,7 @@ var __toESM = (mod2, isNodeMode, target) => (target = mod2 != null ? __create(__
|
|
|
32
32
|
isNodeMode || !mod2 || !mod2.__esModule ? __defProp(target, "default", { value: mod2, enumerable: true }) : target,
|
|
33
33
|
mod2
|
|
34
34
|
));
|
|
35
|
+
var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: true }), mod2);
|
|
35
36
|
|
|
36
37
|
// ../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/lib/parser.js
|
|
37
38
|
var require_parser = __commonJS({
|
|
@@ -21655,6 +21656,11 @@ var init_install_public_mcp = __esm({
|
|
|
21655
21656
|
});
|
|
21656
21657
|
|
|
21657
21658
|
// src/bin.ts
|
|
21659
|
+
var bin_exports = {};
|
|
21660
|
+
__export(bin_exports, {
|
|
21661
|
+
augmentProcessPathWithCommonDirs: () => augmentProcessPathWithCommonDirs
|
|
21662
|
+
});
|
|
21663
|
+
module.exports = __toCommonJS(bin_exports);
|
|
21658
21664
|
var import_node_path42 = __toESM(require("node:path"), 1);
|
|
21659
21665
|
var import_node_child_process22 = require("node:child_process");
|
|
21660
21666
|
var import_node_fs25 = require("node:fs");
|
|
@@ -37372,6 +37378,7 @@ var openApiOperationIds = {
|
|
|
37372
37378
|
promoteSpecReadyCas: "promoteSpecReadyCas",
|
|
37373
37379
|
recordSpecConsensusReview: "recordSpecConsensusReview",
|
|
37374
37380
|
getSpecConsensusReview: "getSpecConsensusReview",
|
|
37381
|
+
finalizeSpecReviewCas: "finalizeSpecReviewCas",
|
|
37375
37382
|
onboardingKickoffTransition: "onboardingKickoffTransition",
|
|
37376
37383
|
ingestTelemetry: "ingestTelemetry",
|
|
37377
37384
|
ingestOperatorTelemetry: "ingestOperatorTelemetry",
|
|
@@ -37642,6 +37649,63 @@ var PERSONA_ROLE_MAP = {
|
|
|
37642
37649
|
"spec-author": "spec"
|
|
37643
37650
|
};
|
|
37644
37651
|
var UNNAMED_PERSONAS = ["plan-reviewer", "scope-analyst"];
|
|
37652
|
+
var mrrlinSpecOwnershipSnapshotSchema = external_exports.discriminatedUnion("kind", [
|
|
37653
|
+
external_exports.object({
|
|
37654
|
+
kind: external_exports.literal("task"),
|
|
37655
|
+
taskId: external_exports.string().min(1),
|
|
37656
|
+
domains: external_exports.array(external_exports.string()).default(["code"])
|
|
37657
|
+
}),
|
|
37658
|
+
external_exports.object({
|
|
37659
|
+
kind: external_exports.literal("plan"),
|
|
37660
|
+
planId: external_exports.string().min(1),
|
|
37661
|
+
domains: external_exports.array(external_exports.string()).default(["code"])
|
|
37662
|
+
}),
|
|
37663
|
+
external_exports.object({
|
|
37664
|
+
kind: external_exports.literal("project"),
|
|
37665
|
+
domains: external_exports.array(external_exports.string()).default(["code"])
|
|
37666
|
+
})
|
|
37667
|
+
]);
|
|
37668
|
+
var SPEC_CONSENSUS_OUTCOMES = [
|
|
37669
|
+
"CONVERGED",
|
|
37670
|
+
"approved",
|
|
37671
|
+
"CONSENSUS_BLOCKED",
|
|
37672
|
+
"CONSENSUS_BLOCKED_CLARIFICATION",
|
|
37673
|
+
"SKIPPED"
|
|
37674
|
+
];
|
|
37675
|
+
var specConsensusOutcomeSchema = external_exports.enum(SPEC_CONSENSUS_OUTCOMES);
|
|
37676
|
+
var mrrlinFinalizeSpecReviewCasBodySchema = external_exports.object({
|
|
37677
|
+
expectedContentHash: external_exports.string().min(1),
|
|
37678
|
+
reviewHash: external_exports.string().min(1).optional(),
|
|
37679
|
+
ownership: mrrlinSpecOwnershipSnapshotSchema,
|
|
37680
|
+
review: external_exports.object({
|
|
37681
|
+
outcome: specConsensusOutcomeSchema,
|
|
37682
|
+
rounds: external_exports.number().int().nonnegative().default(0),
|
|
37683
|
+
suppressionEvidence: mrrlinSpecConsensusSuppressionEvidenceSchema.nullish(),
|
|
37684
|
+
history: mrrlinSpecConsensusHistorySchema.nullish(),
|
|
37685
|
+
highlights: mrrlinSpecConsensusHighlightsSchema.nullish()
|
|
37686
|
+
}),
|
|
37687
|
+
specReady: external_exports.boolean().default(false)
|
|
37688
|
+
}).refine((data) => {
|
|
37689
|
+
if (data.specReady && (data.review.outcome === "CONSENSUS_BLOCKED" || data.review.outcome === "CONSENSUS_BLOCKED_CLARIFICATION")) {
|
|
37690
|
+
return false;
|
|
37691
|
+
}
|
|
37692
|
+
return true;
|
|
37693
|
+
}, {
|
|
37694
|
+
message: "specReady cannot be true when review outcome is blocked",
|
|
37695
|
+
path: ["specReady"]
|
|
37696
|
+
});
|
|
37697
|
+
var mrrlinFinalizeSpecReviewCasResponseSchema = external_exports.object({
|
|
37698
|
+
data: external_exports.discriminatedUnion("status", [
|
|
37699
|
+
external_exports.object({
|
|
37700
|
+
status: external_exports.literal("applied"),
|
|
37701
|
+
promoted: external_exports.boolean()
|
|
37702
|
+
}),
|
|
37703
|
+
external_exports.object({
|
|
37704
|
+
status: external_exports.literal("superseded"),
|
|
37705
|
+
reason: external_exports.string()
|
|
37706
|
+
})
|
|
37707
|
+
])
|
|
37708
|
+
});
|
|
37645
37709
|
|
|
37646
37710
|
// ../../packages/schemas/dist/asyncJob.js
|
|
37647
37711
|
var ASYNC_JOB_STATUSES = ["queued", "running", "done", "failed", "superseded"];
|
|
@@ -37713,7 +37777,7 @@ var mrrlinAsyncJobBeginAttemptSchema = external_exports.object({
|
|
|
37713
37777
|
});
|
|
37714
37778
|
var mrrlinAsyncJobTerminalizeSchema = external_exports.object({
|
|
37715
37779
|
leaseId: external_exports.string().min(1),
|
|
37716
|
-
status: external_exports.enum(["done", "failed"]),
|
|
37780
|
+
status: external_exports.enum(["done", "failed", "superseded"]),
|
|
37717
37781
|
resultJson: external_exports.string().nullable().optional(),
|
|
37718
37782
|
error: external_exports.string().nullable().optional(),
|
|
37719
37783
|
failureReason: mrrlinAsyncJobFailureReasonSchema.nullable().optional(),
|
|
@@ -38016,13 +38080,36 @@ var DEFAULT_INTEGRATION_ROUTES = {
|
|
|
38016
38080
|
operations: {
|
|
38017
38081
|
dispatch: "slack_post_message"
|
|
38018
38082
|
}
|
|
38083
|
+
},
|
|
38084
|
+
publish: {
|
|
38085
|
+
capability: "publish",
|
|
38086
|
+
artifactKind: "page",
|
|
38087
|
+
operations: {
|
|
38088
|
+
preview: "publish_preview",
|
|
38089
|
+
release: "publish_release",
|
|
38090
|
+
rollback: "publish_rollback",
|
|
38091
|
+
observe: "publish_observe"
|
|
38092
|
+
}
|
|
38093
|
+
},
|
|
38094
|
+
"local-preview": {
|
|
38095
|
+
capability: "publish",
|
|
38096
|
+
artifactKind: "page",
|
|
38097
|
+
operations: {
|
|
38098
|
+
preview: "publish_preview",
|
|
38099
|
+
release: "publish_release",
|
|
38100
|
+
rollback: "publish_rollback",
|
|
38101
|
+
observe: "publish_observe"
|
|
38102
|
+
}
|
|
38019
38103
|
}
|
|
38020
38104
|
};
|
|
38021
38105
|
var SERVER_ALIASES = {
|
|
38022
38106
|
"github-mcp": "github",
|
|
38023
38107
|
gh: "github",
|
|
38024
38108
|
"github-server": "github",
|
|
38025
|
-
"slack-mcp": "slack"
|
|
38109
|
+
"slack-mcp": "slack",
|
|
38110
|
+
"static-publisher": "publish",
|
|
38111
|
+
"local-publisher": "publish",
|
|
38112
|
+
preview: "local-preview"
|
|
38026
38113
|
};
|
|
38027
38114
|
function getDefaultCapabilityRouteForServer(serverId) {
|
|
38028
38115
|
const normalizedId = (SERVER_ALIASES[serverId.toLowerCase()] ?? serverId).toLowerCase();
|
|
@@ -38052,10 +38139,14 @@ var mrrlinTaskEventAutonomyLevelSchema = external_exports.enum([
|
|
|
38052
38139
|
]);
|
|
38053
38140
|
var mrrlinAutoDeploySchema = external_exports.enum(["off", "dev", "prod"]);
|
|
38054
38141
|
var mrrlinTaskDomainsSchema = external_exports.array(external_exports.string()).min(1);
|
|
38055
|
-
function
|
|
38056
|
-
const d = Array.isArray(
|
|
38142
|
+
function normalizeDomainArray(domains) {
|
|
38143
|
+
const d = Array.isArray(domains) ? domains.map(String) : [String(domains || "code")];
|
|
38057
38144
|
const normalized = d.map((v) => v.trim().toLowerCase()).filter(Boolean);
|
|
38058
38145
|
normalized.sort();
|
|
38146
|
+
return normalized.length === 0 ? ["code"] : Array.from(new Set(normalized));
|
|
38147
|
+
}
|
|
38148
|
+
function domainScopedReviewHash(rawHash, domain2) {
|
|
38149
|
+
const normalized = normalizeDomainArray(domain2);
|
|
38059
38150
|
return normalized.length === 1 && normalized[0] === "code" ? rawHash : `${rawHash}@${normalized.join(",")}`;
|
|
38060
38151
|
}
|
|
38061
38152
|
var mrrlinDeclaredOutcomeShapeSchema = external_exports.object({
|
|
@@ -38355,7 +38446,8 @@ var mrrlinTaskSchema = external_exports.object({
|
|
|
38355
38446
|
// ADR 0020. OUTPUT-optional (not defaulted-required) so hardcoded task fixtures/mocks across the
|
|
38356
38447
|
// codebase need no churn; the DB column is NOT NULL default 'code', so real rows always carry it,
|
|
38357
38448
|
// and resolveHeads() treats an absent domain as `code`. Persisted create/update use their own forms.
|
|
38358
|
-
domains
|
|
38449
|
+
// Preprocesses null to undefined so tasks with null domains deserialize gracefully without widening MrrlinTask['domains'] to null.
|
|
38450
|
+
domains: external_exports.preprocess((val) => val === null ? void 0 : val, mrrlinTaskDomainsSchema.optional()),
|
|
38359
38451
|
autonomyLevel: mrrlinAutonomyLevelSchema,
|
|
38360
38452
|
autonomyReason: external_exports.string().min(1).max(240),
|
|
38361
38453
|
blockedBy: external_exports.array(mrrlinTaskIdSchema),
|
|
@@ -38470,14 +38562,17 @@ var mrrlinWikiPageSchema = external_exports.object({
|
|
|
38470
38562
|
createdAt: external_exports.string().datetime().nullable().default(null),
|
|
38471
38563
|
folder: external_exports.string().min(1),
|
|
38472
38564
|
id: mrrlinWikiPageIdSchema,
|
|
38473
|
-
markdownBody: external_exports.string().
|
|
38565
|
+
markdownBody: external_exports.string().default(""),
|
|
38474
38566
|
path: mrrlinWikiPagePathSchema,
|
|
38475
38567
|
preview: external_exports.string(),
|
|
38476
38568
|
specReady: external_exports.boolean().default(false),
|
|
38477
38569
|
taskId: mrrlinTaskIdSchema.nullable().default(null),
|
|
38478
|
-
title: external_exports.string().
|
|
38570
|
+
title: external_exports.string().default("Untitled"),
|
|
38479
38571
|
updatedAt: external_exports.string().datetime().nullable().default(null)
|
|
38480
38572
|
});
|
|
38573
|
+
var mrrlinWikiPageSummarySchema = mrrlinWikiPageSchema.omit({
|
|
38574
|
+
markdownBody: true
|
|
38575
|
+
});
|
|
38481
38576
|
var mrrlinMarkSpecReadyRequestSchema = external_exports.object({
|
|
38482
38577
|
ready: external_exports.boolean()
|
|
38483
38578
|
});
|
|
@@ -38628,6 +38723,7 @@ var mrrlinTaskCreateShapeSchema = external_exports.object({
|
|
|
38628
38723
|
// "caller omitted this" apart from "caller explicitly chose off" -- omitted falls through to
|
|
38629
38724
|
// the preview-only constant "dev"; the tasks column itself still defaults to 'off' at the
|
|
38630
38725
|
// SQL level for any direct-insert path that bypasses createTask entirely.
|
|
38726
|
+
assignee: mrrlinTaskAssigneeSchema.optional(),
|
|
38631
38727
|
autoDeploy: mrrlinAutoDeploySchema.optional(),
|
|
38632
38728
|
domains: mrrlinTaskDomainsSchema.optional(),
|
|
38633
38729
|
autonomyLevel: mrrlinAutonomyLevelSchema.default("human_review"),
|
|
@@ -38772,6 +38868,7 @@ var mrrlinTaskListFiltersSchema = external_exports.object({
|
|
|
38772
38868
|
category: taxonomyCategorySchema.optional(),
|
|
38773
38869
|
dueDate: external_exports.string().date().optional(),
|
|
38774
38870
|
impact: mrrlinTaskImpactSchema.optional(),
|
|
38871
|
+
limit: external_exports.coerce.number().int().positive().optional(),
|
|
38775
38872
|
q: external_exports.string().trim().min(1).optional(),
|
|
38776
38873
|
status: mrrlinTaskStatusSchema.optional(),
|
|
38777
38874
|
subcategory: taxonomySubcategorySchema.optional(),
|
|
@@ -38837,11 +38934,14 @@ var mrrlinWikiPageUpdateSchema = external_exports.object({
|
|
|
38837
38934
|
taskId: mrrlinTaskIdSchema.nullable().optional(),
|
|
38838
38935
|
title: external_exports.string().min(1).optional()
|
|
38839
38936
|
}).refine((value) => Object.keys(value).length > 0, { message: "WikiPageUpdate requires at least one field" });
|
|
38937
|
+
var mrrlinWikiPageViewSchema = external_exports.enum(["summary"]);
|
|
38840
38938
|
var mrrlinWikiPageListFiltersSchema = external_exports.object({
|
|
38841
38939
|
folder: external_exports.string().min(1).optional(),
|
|
38842
38940
|
id: mrrlinWikiPageIdSchema.optional(),
|
|
38941
|
+
limit: external_exports.coerce.number().int().positive().optional(),
|
|
38843
38942
|
q: external_exports.string().min(1).optional(),
|
|
38844
|
-
taskId: mrrlinTaskIdSchema.optional()
|
|
38943
|
+
taskId: mrrlinTaskIdSchema.optional(),
|
|
38944
|
+
view: mrrlinWikiPageViewSchema.optional()
|
|
38845
38945
|
});
|
|
38846
38946
|
var mrrlinWikiPageSearchQuerySchema = external_exports.object({
|
|
38847
38947
|
folder: external_exports.string().min(1).optional(),
|
|
@@ -38858,10 +38958,10 @@ var mrrlinWikiPageSearchResultSchema = external_exports.object({
|
|
|
38858
38958
|
memoryClass: external_exports.enum(["normative", "historical"]),
|
|
38859
38959
|
path: external_exports.string().min(1),
|
|
38860
38960
|
precedence: external_exports.union([external_exports.literal(0), external_exports.literal(1), external_exports.literal(2), external_exports.literal(3)]).nullable(),
|
|
38861
|
-
preview: external_exports.string(),
|
|
38961
|
+
preview: external_exports.string().default(""),
|
|
38862
38962
|
rank: external_exports.number(),
|
|
38863
|
-
snippet: external_exports.string(),
|
|
38864
|
-
title: external_exports.string().
|
|
38963
|
+
snippet: external_exports.string().default(""),
|
|
38964
|
+
title: external_exports.string().default(""),
|
|
38865
38965
|
updatedAt: external_exports.string().datetime().nullable()
|
|
38866
38966
|
});
|
|
38867
38967
|
var wikiKnowledgeChunkSchema = external_exports.object({
|
|
@@ -39037,6 +39137,9 @@ var wikiPageResponseSchema = external_exports.object({
|
|
|
39037
39137
|
var wikiPageListResponseSchema = external_exports.object({
|
|
39038
39138
|
data: external_exports.array(mrrlinWikiPageSchema)
|
|
39039
39139
|
});
|
|
39140
|
+
var wikiPageSummaryListResponseSchema = external_exports.object({
|
|
39141
|
+
data: external_exports.array(mrrlinWikiPageSummarySchema)
|
|
39142
|
+
});
|
|
39040
39143
|
var wikiPageSearchResponseSchema = external_exports.object({
|
|
39041
39144
|
data: external_exports.array(mrrlinWikiPageSearchResultSchema)
|
|
39042
39145
|
});
|
|
@@ -39508,6 +39611,7 @@ var mrrlinSchedulerParkedReasonSchema = external_exports.enum([
|
|
|
39508
39611
|
"running",
|
|
39509
39612
|
"awaiting_handoff",
|
|
39510
39613
|
"awaiting_clarification",
|
|
39614
|
+
"consensus_blocked",
|
|
39511
39615
|
"spec_awaiting_approval",
|
|
39512
39616
|
"blocked_by_active",
|
|
39513
39617
|
// Derived, not stored (design.md, "Parking is derived"): an open `question` inbox item with
|
|
@@ -39541,7 +39645,8 @@ var mrrlinInFlightSchedulerEntrySchema = external_exports.discriminatedUnion("ki
|
|
|
39541
39645
|
toolName: external_exports.enum(["draft_task_spec", "run_spec_consensus"]),
|
|
39542
39646
|
// Additive: same resolution as the `run` variant above. null when `taskId` is null (a job
|
|
39543
39647
|
// genuinely untied to a visible task) or the task could not be found.
|
|
39544
|
-
taskTitle: external_exports.string().nullable()
|
|
39648
|
+
taskTitle: external_exports.string().nullable(),
|
|
39649
|
+
specPageId: external_exports.string().nullable().optional()
|
|
39545
39650
|
})
|
|
39546
39651
|
]);
|
|
39547
39652
|
var mrrlinIneligibleTaskSchema = external_exports.object({
|
|
@@ -39735,12 +39840,35 @@ var mrrlinInboxItemDecideSchema = external_exports.object({
|
|
|
39735
39840
|
});
|
|
39736
39841
|
}
|
|
39737
39842
|
});
|
|
39843
|
+
var mrrlinInboxItemSummarySchema = external_exports.object({
|
|
39844
|
+
answer: external_exports.string().nullable(),
|
|
39845
|
+
createdAt: external_exports.string().datetime(),
|
|
39846
|
+
createdBy: external_exports.string().min(1),
|
|
39847
|
+
decidedAt: external_exports.string().datetime().nullable(),
|
|
39848
|
+
decidedBy: external_exports.string().min(1).nullable(),
|
|
39849
|
+
dedupKey: external_exports.string().nullable(),
|
|
39850
|
+
directorSessionId: external_exports.string().min(1).nullable(),
|
|
39851
|
+
groupId: external_exports.string().nullable().default(null),
|
|
39852
|
+
id: mrrlinInboxItemIdSchema,
|
|
39853
|
+
kind: mrrlinInboxItemKindSchema,
|
|
39854
|
+
origin: external_exports.string().nullable().optional(),
|
|
39855
|
+
planId: mrrlinPlanIdSchema.nullable().default(null),
|
|
39856
|
+
projectSlug: mrrlinProjectSlugSchema,
|
|
39857
|
+
readAt: external_exports.string().datetime().nullable(),
|
|
39858
|
+
reason: external_exports.string().nullable().optional(),
|
|
39859
|
+
status: mrrlinInboxItemStatusSchema,
|
|
39860
|
+
taskId: mrrlinTaskIdSchema.nullable(),
|
|
39861
|
+
taskTitle: external_exports.string().nullable()
|
|
39862
|
+
});
|
|
39863
|
+
var mrrlinInboxItemViewSchema = external_exports.enum(["summary"]);
|
|
39738
39864
|
var mrrlinInboxItemListFiltersSchema = external_exports.object({
|
|
39739
39865
|
groupId: external_exports.string().optional(),
|
|
39740
39866
|
kind: mrrlinInboxItemKindSchema.optional(),
|
|
39867
|
+
limit: external_exports.coerce.number().int().positive().optional(),
|
|
39741
39868
|
readState: external_exports.enum(["read", "unread"]).optional(),
|
|
39742
39869
|
status: mrrlinInboxItemStatusSchema.optional(),
|
|
39743
|
-
taskId: mrrlinTaskIdSchema.optional()
|
|
39870
|
+
taskId: mrrlinTaskIdSchema.optional(),
|
|
39871
|
+
view: mrrlinInboxItemViewSchema.optional()
|
|
39744
39872
|
});
|
|
39745
39873
|
var mrrlinIntentVerdictSchema = external_exports.object({
|
|
39746
39874
|
verdict: external_exports.enum(["CLEAR", "UNCLEAR"]),
|
|
@@ -39784,6 +39912,7 @@ var inboxItemsMarkReadResponseSchema = external_exports.object({
|
|
|
39784
39912
|
});
|
|
39785
39913
|
var inboxItemResponseSchema = external_exports.object({ data: mrrlinInboxItemSchema });
|
|
39786
39914
|
var inboxItemListResponseSchema = external_exports.object({ data: external_exports.array(mrrlinInboxItemSchema) });
|
|
39915
|
+
var inboxItemSummaryListResponseSchema = external_exports.object({ data: external_exports.array(mrrlinInboxItemSummarySchema) });
|
|
39787
39916
|
var capabilityServerIdSchema = external_exports.string().trim().min(1).max(200);
|
|
39788
39917
|
var capabilityNameSchema = external_exports.string().trim().min(1).max(200);
|
|
39789
39918
|
var capabilityOperationNameSchema = external_exports.string().min(1).max(100).regex(/^[a-z][a-z0-9_]*$/, "an operation name must be lower_snake_case, starting with a letter");
|
|
@@ -39984,11 +40113,12 @@ function buildTaskListPath(projectSlug, filters, view) {
|
|
|
39984
40113
|
category: filters.category,
|
|
39985
40114
|
dueDate: filters.dueDate,
|
|
39986
40115
|
impact: filters.impact,
|
|
40116
|
+
limit: filters.limit === void 0 ? void 0 : String(filters.limit),
|
|
39987
40117
|
q: filters.q,
|
|
39988
40118
|
status: filters.status,
|
|
39989
40119
|
subcategory: filters.subcategory,
|
|
39990
40120
|
type: filters.type,
|
|
39991
|
-
view
|
|
40121
|
+
view: view ?? filters.view
|
|
39992
40122
|
});
|
|
39993
40123
|
}
|
|
39994
40124
|
var MrrlinApiError = class extends Error {
|
|
@@ -40253,28 +40383,35 @@ function createMrrlinClient(config2) {
|
|
|
40253
40383
|
// this one implementation signature; MrrlinClient's declared overloads keep callers precise.
|
|
40254
40384
|
async listTasks(projectSlug, filters = {}, options) {
|
|
40255
40385
|
const path29 = buildTaskListPath(projectSlug, filters, options?.view);
|
|
40256
|
-
const body = await request(path29);
|
|
40386
|
+
const body = await request(path29, options?.signal ? { signal: options.signal } : void 0);
|
|
40257
40387
|
if (options?.view === "summary") {
|
|
40258
40388
|
return taskSummaryListResponseSchema.parse(body).data;
|
|
40259
40389
|
}
|
|
40260
40390
|
return taskListResponseSchema.parse(body).data;
|
|
40261
40391
|
},
|
|
40262
|
-
async listTaskSummaries(projectSlug, filters = {}) {
|
|
40392
|
+
async listTaskSummaries(projectSlug, filters = {}, options) {
|
|
40263
40393
|
const path29 = buildTaskListPath(projectSlug, filters, "summary");
|
|
40264
|
-
const body = await request(path29);
|
|
40394
|
+
const body = await request(path29, options?.signal ? { signal: options.signal } : void 0);
|
|
40265
40395
|
return taskSummaryListResponseSchema.parse(body).data;
|
|
40266
40396
|
},
|
|
40267
40397
|
async listTaxonomy(projectSlug) {
|
|
40268
40398
|
const body = await request(`${projectPath(projectSlug)}/taxonomy`);
|
|
40269
40399
|
return taxonomyListResponseSchema.parse(body).data;
|
|
40270
40400
|
},
|
|
40271
|
-
async listWikiPages(projectSlug, filters = {}) {
|
|
40401
|
+
async listWikiPages(projectSlug, filters = {}, options) {
|
|
40402
|
+
const view = options?.view ?? filters.view;
|
|
40272
40403
|
const path29 = appendQuery(`${projectPath(projectSlug)}/wiki`, {
|
|
40273
40404
|
folder: filters.folder,
|
|
40405
|
+
id: filters.id,
|
|
40406
|
+
limit: filters.limit === void 0 ? void 0 : String(filters.limit),
|
|
40274
40407
|
q: filters.q,
|
|
40275
|
-
taskId: filters.taskId
|
|
40408
|
+
taskId: filters.taskId,
|
|
40409
|
+
view
|
|
40276
40410
|
});
|
|
40277
|
-
const body = await request(path29);
|
|
40411
|
+
const body = await request(path29, options?.signal ? { signal: options.signal } : void 0);
|
|
40412
|
+
if (view === "summary") {
|
|
40413
|
+
return wikiPageSummaryListResponseSchema.parse(body).data;
|
|
40414
|
+
}
|
|
40278
40415
|
return wikiPageListResponseSchema.parse(body).data;
|
|
40279
40416
|
},
|
|
40280
40417
|
async searchWikiPages(projectSlug, query) {
|
|
@@ -40561,15 +40698,21 @@ function createMrrlinClient(config2) {
|
|
|
40561
40698
|
});
|
|
40562
40699
|
return capabilityUseResponseSchema.parse(body).data;
|
|
40563
40700
|
},
|
|
40564
|
-
async listInboxItems(projectSlug, filters = {}) {
|
|
40701
|
+
async listInboxItems(projectSlug, filters = {}, options) {
|
|
40702
|
+
const view = options?.view ?? filters.view;
|
|
40565
40703
|
const url2 = appendQuery(`${projectPath(projectSlug)}/inbox`, {
|
|
40566
40704
|
groupId: filters.groupId,
|
|
40567
40705
|
kind: filters.kind,
|
|
40706
|
+
limit: filters.limit === void 0 ? void 0 : String(filters.limit),
|
|
40568
40707
|
readState: filters.readState,
|
|
40569
40708
|
status: filters.status,
|
|
40570
|
-
taskId: filters.taskId
|
|
40709
|
+
taskId: filters.taskId,
|
|
40710
|
+
view
|
|
40571
40711
|
});
|
|
40572
|
-
const body = await request(url2);
|
|
40712
|
+
const body = await request(url2, options?.signal ? { signal: options.signal } : void 0);
|
|
40713
|
+
if (view === "summary") {
|
|
40714
|
+
return inboxItemSummaryListResponseSchema.parse(body).data;
|
|
40715
|
+
}
|
|
40573
40716
|
return inboxItemListResponseSchema.parse(body).data;
|
|
40574
40717
|
},
|
|
40575
40718
|
async createInboxItem(projectSlug, input) {
|
|
@@ -40952,6 +41095,14 @@ function createMrrlinClient(config2) {
|
|
|
40952
41095
|
const body = await request(url2);
|
|
40953
41096
|
return specConsensusReviewMaybeResponseSchema.parse(body).data;
|
|
40954
41097
|
},
|
|
41098
|
+
async finalizeSpecReviewCas(projectSlug, pageId, input) {
|
|
41099
|
+
const body = await request(`${projectPath(projectSlug)}/wiki/${encodeURIComponent(pageId)}/consensus-finalize`, {
|
|
41100
|
+
body: JSON.stringify(input),
|
|
41101
|
+
headers: { "content-type": "application/json" },
|
|
41102
|
+
method: "POST"
|
|
41103
|
+
});
|
|
41104
|
+
return mrrlinFinalizeSpecReviewCasResponseSchema.parse(body).data;
|
|
41105
|
+
},
|
|
40955
41106
|
async issueAgentCredential(projectSlug, input) {
|
|
40956
41107
|
const body = await request(`${projectPath(projectSlug)}/agent-credentials`, {
|
|
40957
41108
|
body: JSON.stringify(input),
|
|
@@ -41154,19 +41305,19 @@ function searchKnowledgeIndex(index, query, limit = 5) {
|
|
|
41154
41305
|
if (queryTokens.length === 0)
|
|
41155
41306
|
return [];
|
|
41156
41307
|
const documents = index.map((chunk) => tokenize(`${chunk.heading_path.join(" ")} ${chunk.chunk_text}`));
|
|
41157
|
-
const averageLength = documents.reduce((sum,
|
|
41308
|
+
const averageLength = documents.reduce((sum, tokens2) => sum + tokens2.length, 0) / Math.max(documents.length, 1);
|
|
41158
41309
|
const documentFrequency = /* @__PURE__ */ new Map();
|
|
41159
|
-
for (const
|
|
41160
|
-
for (const token of new Set(
|
|
41310
|
+
for (const tokens2 of documents) {
|
|
41311
|
+
for (const token of new Set(tokens2)) {
|
|
41161
41312
|
documentFrequency.set(token, (documentFrequency.get(token) ?? 0) + 1);
|
|
41162
41313
|
}
|
|
41163
41314
|
}
|
|
41164
41315
|
const k1 = 1.2;
|
|
41165
41316
|
const b = 0.75;
|
|
41166
41317
|
const scored = index.map((chunk, chunkIndex) => {
|
|
41167
|
-
const
|
|
41318
|
+
const tokens2 = documents[chunkIndex] ?? [];
|
|
41168
41319
|
const frequencies = /* @__PURE__ */ new Map();
|
|
41169
|
-
for (const token of
|
|
41320
|
+
for (const token of tokens2) {
|
|
41170
41321
|
frequencies.set(token, (frequencies.get(token) ?? 0) + 1);
|
|
41171
41322
|
}
|
|
41172
41323
|
const score = queryTokens.reduce((sum, token) => {
|
|
@@ -41175,7 +41326,7 @@ function searchKnowledgeIndex(index, query, limit = 5) {
|
|
|
41175
41326
|
return sum;
|
|
41176
41327
|
const df = documentFrequency.get(token) ?? 0;
|
|
41177
41328
|
const idf = Math.log(1 + (documents.length - df + 0.5) / (df + 0.5));
|
|
41178
|
-
const denominator = tf + k1 * (1 - b + b * (
|
|
41329
|
+
const denominator = tf + k1 * (1 - b + b * (tokens2.length / Math.max(averageLength, 1)));
|
|
41179
41330
|
return sum + idf * (tf * (k1 + 1) / denominator);
|
|
41180
41331
|
}, 0);
|
|
41181
41332
|
return { ...chunk, score };
|
|
@@ -41723,18 +41874,87 @@ function resolveProducerPersona(domain2) {
|
|
|
41723
41874
|
}
|
|
41724
41875
|
|
|
41725
41876
|
// src/consensus/acceptance-criteria.ts
|
|
41726
|
-
var HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
41727
|
-
var
|
|
41728
|
-
|
|
41729
|
-
|
|
41877
|
+
var HEADING_RE = /^[ ]{0,3}(#{1,6})\s+(.+?)\s*$/;
|
|
41878
|
+
var TOP_LEVEL_CHECKLIST_RE = /^[ ]{0,1}[-*]\s+\[[ xX]\]\s+(.+\S)\s*$/;
|
|
41879
|
+
var TOP_LEVEL_ITEM_RE = /^[ ]{0,1}(?:\d+[.)]|[-*])\s+(.+?\S)\s*$/;
|
|
41880
|
+
var INDENTED_CODE_RE = /^[ ]{4,}\S/;
|
|
41881
|
+
function createCommonMarkFenceScanner() {
|
|
41882
|
+
let active = null;
|
|
41883
|
+
return {
|
|
41884
|
+
isFenced(line) {
|
|
41885
|
+
const trimmed = line.trimStart();
|
|
41886
|
+
const indent = line.length - trimmed.length;
|
|
41887
|
+
if (indent <= 3) {
|
|
41888
|
+
if (!active) {
|
|
41889
|
+
const m = /^(`{3,}|~{3,})/.exec(trimmed);
|
|
41890
|
+
if (m && m[1]) {
|
|
41891
|
+
const fenceStr = m[1];
|
|
41892
|
+
const char = fenceStr[0];
|
|
41893
|
+
const length = fenceStr.length;
|
|
41894
|
+
const remainder = trimmed.slice(length);
|
|
41895
|
+
if (char === "~" || !remainder.includes("`")) {
|
|
41896
|
+
active = { char, length };
|
|
41897
|
+
return true;
|
|
41898
|
+
}
|
|
41899
|
+
}
|
|
41900
|
+
} else {
|
|
41901
|
+
const m = /^(`{3,}|~{3,})\s*$/.exec(trimmed);
|
|
41902
|
+
if (m && m[1]) {
|
|
41903
|
+
const fenceStr = m[1];
|
|
41904
|
+
const char = fenceStr[0];
|
|
41905
|
+
const length = fenceStr.length;
|
|
41906
|
+
if (char === active.char && length >= active.length) {
|
|
41907
|
+
active = null;
|
|
41908
|
+
return true;
|
|
41909
|
+
}
|
|
41910
|
+
}
|
|
41911
|
+
}
|
|
41912
|
+
}
|
|
41913
|
+
return active !== null;
|
|
41914
|
+
},
|
|
41915
|
+
isInCodeBlock() {
|
|
41916
|
+
return active !== null;
|
|
41917
|
+
}
|
|
41918
|
+
};
|
|
41919
|
+
}
|
|
41920
|
+
var ACCEPTANCE_CRITERIA_ALIASES = /* @__PURE__ */ new Set([
|
|
41921
|
+
"acceptance criteria",
|
|
41922
|
+
"acceptance criteria (checklist)",
|
|
41923
|
+
"acceptance criteria (dod)",
|
|
41924
|
+
"acceptance criteria (definition of done)",
|
|
41925
|
+
"criteria for acceptance",
|
|
41926
|
+
"success criteria",
|
|
41927
|
+
"definition of done",
|
|
41928
|
+
"dod",
|
|
41929
|
+
"dod (definition of done)",
|
|
41930
|
+
"\u043A\u0440\u0438\u0442\u0435\u0440\u0456\u0457 \u043F\u0440\u0438\u0439\u043D\u044F\u0442\u043D\u043E\u0441\u0442\u0456",
|
|
41931
|
+
"\u043A\u0440\u0438\u0442\u0435\u0440\u0456\u0457 \u043F\u0440\u0438\u0439\u043E\u043C\u043A\u0438",
|
|
41932
|
+
"\u043A\u0440\u0438\u0442\u0435\u0440\u0456\u0457 \u043F\u0440\u0438\u0439\u043C\u0430\u043D\u043D\u044F",
|
|
41933
|
+
"\u043A\u0440\u0438\u0442\u0435\u0440\u0456\u0457 \u0443\u0441\u043F\u0456\u0445\u0443",
|
|
41934
|
+
"\u043A\u0440\u0438\u0442\u0435\u0440\u0456\u0457 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0456",
|
|
41935
|
+
"kryteria akceptacji",
|
|
41936
|
+
"kryteria sukcesu",
|
|
41937
|
+
"akzeptanzkriterien",
|
|
41938
|
+
"erfolgskriterien"
|
|
41939
|
+
]);
|
|
41940
|
+
function isAcceptanceCriteriaHeading(title) {
|
|
41941
|
+
const strippedNumber = title.trim().toLowerCase().replace(/^\d+[.)]\s*/, "");
|
|
41942
|
+
const cleaned = strippedNumber.replace(/[:\-?.]+$/, "").replace(/\s+/g, " ").trim();
|
|
41943
|
+
if (ACCEPTANCE_CRITERIA_ALIASES.has(cleaned)) return true;
|
|
41944
|
+
if (cleaned.startsWith("acceptance criteria") || cleaned.startsWith("\u043A\u0440\u0438\u0442\u0435\u0440\u0456\u0457 \u043F\u0440\u0438\u0439\u043D\u044F\u0442\u043D\u043E\u0441\u0442\u0456")) return true;
|
|
41945
|
+
return false;
|
|
41730
41946
|
}
|
|
41731
41947
|
function parseAcceptanceCriteria(markdown) {
|
|
41732
41948
|
const lines2 = markdown.split(/\r?\n/);
|
|
41949
|
+
const fenceScanner = createCommonMarkFenceScanner();
|
|
41733
41950
|
let sectionStart = -1;
|
|
41734
41951
|
let sectionLevel = 0;
|
|
41735
41952
|
for (let i = 0; i < lines2.length; i += 1) {
|
|
41736
|
-
const
|
|
41737
|
-
|
|
41953
|
+
const rawLine = lines2[i] ?? "";
|
|
41954
|
+
const isFenced = fenceScanner.isFenced(rawLine);
|
|
41955
|
+
if (isFenced) continue;
|
|
41956
|
+
const m = HEADING_RE.exec(rawLine);
|
|
41957
|
+
if (m && isAcceptanceCriteriaHeading(m[2] ?? "")) {
|
|
41738
41958
|
sectionStart = i + 1;
|
|
41739
41959
|
sectionLevel = (m[1] ?? "").length;
|
|
41740
41960
|
break;
|
|
@@ -41743,14 +41963,65 @@ function parseAcceptanceCriteria(markdown) {
|
|
|
41743
41963
|
if (sectionStart === -1) return null;
|
|
41744
41964
|
const items = [];
|
|
41745
41965
|
for (let i = sectionStart; i < lines2.length; i += 1) {
|
|
41746
|
-
const
|
|
41747
|
-
const
|
|
41966
|
+
const rawLine = lines2[i] ?? "";
|
|
41967
|
+
const isFenced = fenceScanner.isFenced(rawLine);
|
|
41968
|
+
if (isFenced) continue;
|
|
41969
|
+
if (INDENTED_CODE_RE.test(rawLine)) continue;
|
|
41970
|
+
const heading = HEADING_RE.exec(rawLine);
|
|
41748
41971
|
if (heading && (heading[1] ?? "").length <= sectionLevel) break;
|
|
41749
|
-
const item =
|
|
41972
|
+
const item = TOP_LEVEL_CHECKLIST_RE.exec(rawLine);
|
|
41750
41973
|
if (item && item[1]) items.push(item[1].trim());
|
|
41751
41974
|
}
|
|
41752
41975
|
return items.length > 0 ? { items } : null;
|
|
41753
41976
|
}
|
|
41977
|
+
function normalizeAcceptanceCriteria(markdown) {
|
|
41978
|
+
const isCrlf = markdown.includes("\r\n");
|
|
41979
|
+
const newline = isCrlf ? "\r\n" : "\n";
|
|
41980
|
+
const lines2 = markdown.split(/\r?\n/);
|
|
41981
|
+
const fenceScanner = createCommonMarkFenceScanner();
|
|
41982
|
+
let sectionStart = -1;
|
|
41983
|
+
let sectionLevel = 0;
|
|
41984
|
+
let headingIndex = -1;
|
|
41985
|
+
for (let i = 0; i < lines2.length; i += 1) {
|
|
41986
|
+
const rawLine = lines2[i] ?? "";
|
|
41987
|
+
const isFenced = fenceScanner.isFenced(rawLine);
|
|
41988
|
+
if (isFenced) continue;
|
|
41989
|
+
const m = HEADING_RE.exec(rawLine);
|
|
41990
|
+
if (m && isAcceptanceCriteriaHeading(m[2] ?? "")) {
|
|
41991
|
+
headingIndex = i;
|
|
41992
|
+
sectionStart = i + 1;
|
|
41993
|
+
sectionLevel = (m[1] ?? "").length;
|
|
41994
|
+
break;
|
|
41995
|
+
}
|
|
41996
|
+
}
|
|
41997
|
+
if (sectionStart === -1) return markdown;
|
|
41998
|
+
let changed = false;
|
|
41999
|
+
const newLines = [...lines2];
|
|
42000
|
+
const hashes = "#".repeat(sectionLevel || 2);
|
|
42001
|
+
if (newLines[headingIndex]?.trim() !== `${hashes} Acceptance criteria`) {
|
|
42002
|
+
newLines[headingIndex] = `${hashes} Acceptance criteria`;
|
|
42003
|
+
changed = true;
|
|
42004
|
+
}
|
|
42005
|
+
const bodyFenceScanner = createCommonMarkFenceScanner();
|
|
42006
|
+
for (let i = 0; i < sectionStart; i += 1) {
|
|
42007
|
+
bodyFenceScanner.isFenced(lines2[i] ?? "");
|
|
42008
|
+
}
|
|
42009
|
+
for (let i = sectionStart; i < newLines.length; i += 1) {
|
|
42010
|
+
const rawLine = newLines[i] ?? "";
|
|
42011
|
+
const isFenced = bodyFenceScanner.isFenced(rawLine);
|
|
42012
|
+
if (isFenced) continue;
|
|
42013
|
+
if (INDENTED_CODE_RE.test(rawLine)) continue;
|
|
42014
|
+
const heading = HEADING_RE.exec(rawLine);
|
|
42015
|
+
if (heading && (heading[1] ?? "").length <= sectionLevel) break;
|
|
42016
|
+
if (TOP_LEVEL_CHECKLIST_RE.test(rawLine)) continue;
|
|
42017
|
+
const match = TOP_LEVEL_ITEM_RE.exec(rawLine);
|
|
42018
|
+
if (match && match[1]) {
|
|
42019
|
+
newLines[i] = `- [ ] ${match[1]}`;
|
|
42020
|
+
changed = true;
|
|
42021
|
+
}
|
|
42022
|
+
}
|
|
42023
|
+
return changed ? newLines.join(newline) : markdown;
|
|
42024
|
+
}
|
|
41754
42025
|
function resolveSpecBudget(impact) {
|
|
41755
42026
|
switch (impact?.toLowerCase()) {
|
|
41756
42027
|
case "high":
|
|
@@ -41822,12 +42093,21 @@ function applyClarificationCap(issues, impact) {
|
|
|
41822
42093
|
};
|
|
41823
42094
|
}
|
|
41824
42095
|
async function runSpecGate(_target, deps, opts) {
|
|
41825
|
-
|
|
42096
|
+
const spec = await deps.readSpec();
|
|
42097
|
+
let currentHash = spec.contentHash;
|
|
42098
|
+
let currentMarkdown = spec.markdownBody;
|
|
42099
|
+
if (!await deps.consensusEnabled()) {
|
|
42100
|
+
return {
|
|
42101
|
+
outcome: "SKIPPED",
|
|
42102
|
+
evaluatedSnapshot: { contentHash: currentHash, markdownBody: currentMarkdown }
|
|
42103
|
+
};
|
|
42104
|
+
}
|
|
41826
42105
|
const intent = await deps.readIntent();
|
|
41827
42106
|
const legacyExempt = intent.specApproved === "skipped_legacy";
|
|
41828
42107
|
if (!legacyExempt && intent.taskId !== null && (intent.seed === null || intent.seed.trim() === "")) {
|
|
41829
42108
|
return {
|
|
41830
42109
|
outcome: "CONSENSUS_BLOCKED",
|
|
42110
|
+
evaluatedSnapshot: { contentHash: currentHash, markdownBody: currentMarkdown },
|
|
41831
42111
|
operatorClarifications: [
|
|
41832
42112
|
{
|
|
41833
42113
|
kind: "missing_requirement",
|
|
@@ -41845,10 +42125,17 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
41845
42125
|
const baseHeads = resolveHeads(intent.domains, intent.impact);
|
|
41846
42126
|
const extraHeads = opts?.extraHeads ?? [];
|
|
41847
42127
|
const heads = [...baseHeads, ...extraHeads.filter((h) => !baseHeads.includes(h))];
|
|
41848
|
-
const spec = await deps.readSpec();
|
|
41849
|
-
let currentHash = spec.contentHash;
|
|
41850
|
-
let currentMarkdown = spec.markdownBody;
|
|
41851
42128
|
let conflicted = false;
|
|
42129
|
+
if (parseAcceptanceCriteria(currentMarkdown) === null) {
|
|
42130
|
+
const normalized = normalizeAcceptanceCriteria(currentMarkdown);
|
|
42131
|
+
if (parseAcceptanceCriteria(normalized) !== null) {
|
|
42132
|
+
const cas = await deps.casUpdateSpec(normalized, currentHash);
|
|
42133
|
+
if (cas.ok) {
|
|
42134
|
+
currentHash = cas.contentHash;
|
|
42135
|
+
currentMarkdown = normalized;
|
|
42136
|
+
}
|
|
42137
|
+
}
|
|
42138
|
+
}
|
|
41852
42139
|
if (parseAcceptanceCriteria(currentMarkdown) === null) {
|
|
41853
42140
|
const bootstrapIssue = {
|
|
41854
42141
|
id: "acceptance-criteria-bootstrap",
|
|
@@ -41861,6 +42148,7 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
41861
42148
|
`);
|
|
41862
42149
|
return {
|
|
41863
42150
|
outcome: "CONSENSUS_BLOCKED",
|
|
42151
|
+
evaluatedSnapshot: { contentHash: currentHash, markdownBody: currentMarkdown },
|
|
41864
42152
|
diagnostics: [
|
|
41865
42153
|
{
|
|
41866
42154
|
kind: "acceptance_criteria_unauthorable",
|
|
@@ -41881,8 +42169,11 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
41881
42169
|
}
|
|
41882
42170
|
const cas = await deps.casUpdateSpec(revised.text, currentHash);
|
|
41883
42171
|
if (!cas.ok) {
|
|
41884
|
-
await deps.writeEvidence({ specPageId: spec.pageId, outcome: "SPEC_CHANGED_DURING_REVIEW" });
|
|
41885
|
-
return {
|
|
42172
|
+
await deps.writeEvidence?.({ specPageId: spec.pageId, outcome: "SPEC_CHANGED_DURING_REVIEW" });
|
|
42173
|
+
return {
|
|
42174
|
+
outcome: "SPEC_CHANGED_DURING_REVIEW",
|
|
42175
|
+
evaluatedSnapshot: { contentHash: currentHash, markdownBody: currentMarkdown }
|
|
42176
|
+
};
|
|
41886
42177
|
}
|
|
41887
42178
|
currentHash = cas.contentHash;
|
|
41888
42179
|
currentMarkdown = revised.text;
|
|
@@ -41918,8 +42209,11 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
41918
42209
|
// error and must propagate so it is not silently masked as a conflict.
|
|
41919
42210
|
}).catch((e) => conflicted ? null : Promise.reject(e));
|
|
41920
42211
|
if (conflicted || result === null) {
|
|
41921
|
-
await deps.writeEvidence({ specPageId: spec.pageId, outcome: "SPEC_CHANGED_DURING_REVIEW" });
|
|
41922
|
-
return {
|
|
42212
|
+
await deps.writeEvidence?.({ specPageId: spec.pageId, outcome: "SPEC_CHANGED_DURING_REVIEW" });
|
|
42213
|
+
return {
|
|
42214
|
+
outcome: "SPEC_CHANGED_DURING_REVIEW",
|
|
42215
|
+
evaluatedSnapshot: { contentHash: currentHash, markdownBody: currentMarkdown }
|
|
42216
|
+
};
|
|
41923
42217
|
}
|
|
41924
42218
|
if (result.outcome === "CONVERGED" && intent.taskId !== null && !validateSpecIdentity(currentMarkdown, intent.taskId)) {
|
|
41925
42219
|
process.stderr.write(
|
|
@@ -41928,6 +42222,7 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
41928
42222
|
);
|
|
41929
42223
|
return {
|
|
41930
42224
|
outcome: "CONSENSUS_BLOCKED",
|
|
42225
|
+
evaluatedSnapshot: { contentHash: currentHash, markdownBody: currentMarkdown },
|
|
41931
42226
|
diagnostics: [
|
|
41932
42227
|
{
|
|
41933
42228
|
kind: "spec_identity_mismatch",
|
|
@@ -41951,19 +42246,50 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
41951
42246
|
keptClarifications = capped.kept;
|
|
41952
42247
|
droppedByCap = capped.dropped;
|
|
41953
42248
|
}
|
|
41954
|
-
|
|
41955
|
-
|
|
41956
|
-
outcome
|
|
41957
|
-
|
|
41958
|
-
|
|
41959
|
-
|
|
41960
|
-
|
|
41961
|
-
|
|
42249
|
+
const ownership = intent.taskId !== null ? { kind: "task", taskId: intent.taskId, domains: intent.domains ?? ["code"] } : intent.planId != null ? { kind: "plan", planId: intent.planId, domains: intent.domains ?? ["code"] } : { kind: "project", domains: intent.domains ?? ["code"] };
|
|
42250
|
+
if (deps.finalizeSpecReviewCas) {
|
|
42251
|
+
const shouldFinalizeCas = result.outcome === "CONVERGED" || result.outcome === "CONSENSUS_BLOCKED" && keptClarifications.length === 0;
|
|
42252
|
+
if (shouldFinalizeCas) {
|
|
42253
|
+
const finalizeRes = await deps.finalizeSpecReviewCas({
|
|
42254
|
+
expectedContentHash: currentHash,
|
|
42255
|
+
ownership,
|
|
42256
|
+
review: {
|
|
42257
|
+
outcome: result.outcome,
|
|
42258
|
+
rounds: result.rounds,
|
|
42259
|
+
history: result.history,
|
|
42260
|
+
...droppedByCap.length > 0 ? { suppressionEvidence: { droppedByCap } } : {}
|
|
42261
|
+
},
|
|
42262
|
+
specReady: result.outcome === "CONVERGED"
|
|
42263
|
+
});
|
|
42264
|
+
if (finalizeRes.status === "superseded") {
|
|
42265
|
+
return {
|
|
42266
|
+
outcome: "SUPERSEDED",
|
|
42267
|
+
evaluatedSnapshot: { contentHash: currentHash, markdownBody: currentMarkdown },
|
|
42268
|
+
evaluatedOwnership: ownership
|
|
42269
|
+
};
|
|
42270
|
+
}
|
|
42271
|
+
}
|
|
42272
|
+
} else {
|
|
42273
|
+
await deps.writeEvidence?.({
|
|
42274
|
+
specPageId: spec.pageId,
|
|
42275
|
+
outcome: result.outcome,
|
|
42276
|
+
rounds: result.rounds,
|
|
42277
|
+
history: result.history,
|
|
42278
|
+
contentHash: currentHash,
|
|
42279
|
+
...droppedByCap.length > 0 ? { droppedByCap } : {}
|
|
42280
|
+
});
|
|
42281
|
+
if (result.outcome === "CONVERGED") {
|
|
42282
|
+
await deps.promoteSpecReady?.(currentHash);
|
|
42283
|
+
}
|
|
42284
|
+
}
|
|
41962
42285
|
if (result.outcome === "CONVERGED") {
|
|
41963
|
-
await deps.promoteSpecReady(currentHash);
|
|
41964
42286
|
return {
|
|
41965
42287
|
outcome: "CONVERGED",
|
|
42288
|
+
evaluatedSnapshot: { contentHash: currentHash, markdownBody: currentMarkdown },
|
|
42289
|
+
evaluatedOwnership: ownership,
|
|
41966
42290
|
headOutcomes: result.history.at(-1)?.headOutcomes ?? [],
|
|
42291
|
+
rounds: result.rounds,
|
|
42292
|
+
history: result.history,
|
|
41967
42293
|
arbiterInfo: result.arbiterInfo,
|
|
41968
42294
|
panelComposition: result.panelComposition,
|
|
41969
42295
|
sufficiencyVerdict: result.sufficiencyVerdict
|
|
@@ -41972,6 +42298,10 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
41972
42298
|
if (result.outcome === "CONSENSUS_BLOCKED") {
|
|
41973
42299
|
return {
|
|
41974
42300
|
outcome: "CONSENSUS_BLOCKED",
|
|
42301
|
+
evaluatedSnapshot: { contentHash: currentHash, markdownBody: currentMarkdown },
|
|
42302
|
+
evaluatedOwnership: ownership,
|
|
42303
|
+
rounds: result.rounds,
|
|
42304
|
+
history: result.history,
|
|
41975
42305
|
...keptClarifications.length > 0 ? { operatorClarifications: keptClarifications } : {},
|
|
41976
42306
|
...droppedByCap.length > 0 ? { droppedByCap } : {},
|
|
41977
42307
|
...result.escalation ? { escalation: result.escalation } : {},
|
|
@@ -41983,6 +42313,8 @@ async function runSpecGate(_target, deps, opts) {
|
|
|
41983
42313
|
}
|
|
41984
42314
|
return {
|
|
41985
42315
|
outcome: result.outcome,
|
|
42316
|
+
evaluatedSnapshot: { contentHash: currentHash, markdownBody: currentMarkdown },
|
|
42317
|
+
evaluatedOwnership: ownership,
|
|
41986
42318
|
arbiterInfo: result.arbiterInfo,
|
|
41987
42319
|
panelComposition: result.panelComposition,
|
|
41988
42320
|
sufficiencyVerdict: result.sufficiencyVerdict
|
|
@@ -42401,6 +42733,8 @@ var CodexServerManager = class {
|
|
|
42401
42733
|
rpcClient.respond(id, { decision: "accept" });
|
|
42402
42734
|
} else if (method === "mcpServer/elicitation/request") {
|
|
42403
42735
|
rpcClient.respond(id, { action: "accept" });
|
|
42736
|
+
} else if (method.includes("approval") || method.includes("Approval") || method.includes("permission") || method.includes("elicitation")) {
|
|
42737
|
+
rpcClient.respond(id, { action: "accept", decision: "accept", approved: true });
|
|
42404
42738
|
}
|
|
42405
42739
|
});
|
|
42406
42740
|
const notificationHandlers = /* @__PURE__ */ new Set();
|
|
@@ -43198,6 +43532,9 @@ var ClaudeProcess = class {
|
|
|
43198
43532
|
if (options.mcpConfig) {
|
|
43199
43533
|
args.push("--mcp-config", options.mcpConfig);
|
|
43200
43534
|
}
|
|
43535
|
+
if (options.appendSystemPrompt && options.appendSystemPrompt.trim().length > 0) {
|
|
43536
|
+
args.push("--append-system-prompt", options.appendSystemPrompt.trim());
|
|
43537
|
+
}
|
|
43201
43538
|
args.push("--permission-mode", options.permissionMode);
|
|
43202
43539
|
if (options.disallowedTools && options.disallowedTools.length > 0) {
|
|
43203
43540
|
args.push("--disallowedTools", options.disallowedTools.join(","));
|
|
@@ -43473,7 +43810,8 @@ var ClaudeAgentHandle = class {
|
|
|
43473
43810
|
disallowedTools,
|
|
43474
43811
|
model: params.model,
|
|
43475
43812
|
cwd: params.cwd,
|
|
43476
|
-
mcpConfig: params.mcpConfig
|
|
43813
|
+
mcpConfig: params.mcpConfig,
|
|
43814
|
+
appendSystemPrompt: params.baseInstructions
|
|
43477
43815
|
});
|
|
43478
43816
|
this.processes.set(child.sessionId, child);
|
|
43479
43817
|
this.mappers.set(child.sessionId, new ClaudeBridgeEventMapper());
|
|
@@ -43487,7 +43825,8 @@ var ClaudeAgentHandle = class {
|
|
|
43487
43825
|
disallowedTools,
|
|
43488
43826
|
model: params.model,
|
|
43489
43827
|
cwd: params.cwd,
|
|
43490
|
-
mcpConfig: params.mcpConfig
|
|
43828
|
+
mcpConfig: params.mcpConfig,
|
|
43829
|
+
appendSystemPrompt: params.baseInstructions
|
|
43491
43830
|
});
|
|
43492
43831
|
this.processes.set(threadId, child);
|
|
43493
43832
|
this.mappers.set(threadId, new ClaudeBridgeEventMapper());
|
|
@@ -43631,8 +43970,14 @@ var AgyProcess = class _AgyProcess {
|
|
|
43631
43970
|
args.push("--mode", "accept-edits");
|
|
43632
43971
|
break;
|
|
43633
43972
|
}
|
|
43634
|
-
|
|
43635
|
-
|
|
43973
|
+
let modelToUse = options.model;
|
|
43974
|
+
if (modelToUse === "gemini-3.1-pro-high" || modelToUse === "gemini-3.7-flash-high") {
|
|
43975
|
+
modelToUse = "gemini-3.8-flash-high";
|
|
43976
|
+
} else if (modelToUse === "gemini-3.1-pro-low" || modelToUse === "gemini-3.7-flash-low") {
|
|
43977
|
+
modelToUse = "gemini-3.8-flash-low";
|
|
43978
|
+
}
|
|
43979
|
+
if (modelToUse) {
|
|
43980
|
+
args.push("--model", modelToUse);
|
|
43636
43981
|
}
|
|
43637
43982
|
if (mode.kind === "resume") {
|
|
43638
43983
|
args.push("--conversation", mode.resumeId);
|
|
@@ -43861,7 +44206,7 @@ function agyModelCatalog() {
|
|
|
43861
44206
|
id: entry.id,
|
|
43862
44207
|
displayName: entry.displayName,
|
|
43863
44208
|
description: entry.description,
|
|
43864
|
-
isDefault: entry.id === "gemini-3.
|
|
44209
|
+
isDefault: entry.id === "gemini-3.8-flash-high"
|
|
43865
44210
|
}));
|
|
43866
44211
|
}
|
|
43867
44212
|
var MODEL_ID_REGEX2 = /^[a-zA-Z0-9][a-zA-Z0-9._-]{1,63}$/;
|
|
@@ -43886,7 +44231,7 @@ function parseAgyModelsOutput(output3) {
|
|
|
43886
44231
|
id,
|
|
43887
44232
|
displayName,
|
|
43888
44233
|
description,
|
|
43889
|
-
isDefault: id === "gemini-3.
|
|
44234
|
+
isDefault: id === "gemini-3.8-flash-high"
|
|
43890
44235
|
});
|
|
43891
44236
|
}
|
|
43892
44237
|
return models;
|
|
@@ -43955,6 +44300,7 @@ function permissionModeFor2(sandbox, approvalPolicy) {
|
|
|
43955
44300
|
var AgyAgentHandle = class {
|
|
43956
44301
|
processes = /* @__PURE__ */ new Map();
|
|
43957
44302
|
mappers = /* @__PURE__ */ new Map();
|
|
44303
|
+
baseInstructions = /* @__PURE__ */ new Map();
|
|
43958
44304
|
async startThread(params) {
|
|
43959
44305
|
const { permissionMode } = permissionModeFor2(params.sandbox, params.approvalPolicy);
|
|
43960
44306
|
const child = new AgyProcess({
|
|
@@ -43965,6 +44311,9 @@ var AgyAgentHandle = class {
|
|
|
43965
44311
|
});
|
|
43966
44312
|
this.processes.set(child.sessionId, child);
|
|
43967
44313
|
this.mappers.set(child.sessionId, new AgyBridgeEventMapper());
|
|
44314
|
+
if (params.baseInstructions && params.baseInstructions.trim().length > 0) {
|
|
44315
|
+
this.baseInstructions.set(child.sessionId, params.baseInstructions.trim());
|
|
44316
|
+
}
|
|
43968
44317
|
return { threadId: child.sessionId };
|
|
43969
44318
|
}
|
|
43970
44319
|
async resumeThread(threadId, params) {
|
|
@@ -43982,12 +44331,26 @@ var AgyAgentHandle = class {
|
|
|
43982
44331
|
});
|
|
43983
44332
|
this.processes.set(threadId, child);
|
|
43984
44333
|
this.mappers.set(threadId, new AgyBridgeEventMapper());
|
|
44334
|
+
if (params.baseInstructions && params.baseInstructions.trim().length > 0) {
|
|
44335
|
+
this.baseInstructions.set(threadId, params.baseInstructions.trim());
|
|
44336
|
+
}
|
|
43985
44337
|
return { threadId: child.sessionId };
|
|
43986
44338
|
}
|
|
43987
44339
|
async startTurn(threadId, input) {
|
|
43988
44340
|
const child = this.requireProcess(threadId);
|
|
43989
44341
|
const text = input.filter((block) => block.type === "text").map((block) => block.text).join("\n");
|
|
43990
|
-
|
|
44342
|
+
const inst = this.baseInstructions.get(threadId);
|
|
44343
|
+
if (inst) {
|
|
44344
|
+
const composed = `[SYSTEM INSTRUCTIONS - MRRLIN DIRECTOR]
|
|
44345
|
+
${inst}
|
|
44346
|
+
|
|
44347
|
+
---
|
|
44348
|
+
[OPERATOR REQUEST]
|
|
44349
|
+
${text}`;
|
|
44350
|
+
child.sendTurn(composed);
|
|
44351
|
+
} else {
|
|
44352
|
+
child.sendTurn(text);
|
|
44353
|
+
}
|
|
43991
44354
|
return { turnId: void 0 };
|
|
43992
44355
|
}
|
|
43993
44356
|
async interruptTurn(threadId) {
|
|
@@ -43997,6 +44360,7 @@ var AgyAgentHandle = class {
|
|
|
43997
44360
|
});
|
|
43998
44361
|
this.processes.delete(threadId);
|
|
43999
44362
|
this.mappers.delete(threadId);
|
|
44363
|
+
this.baseInstructions.delete(threadId);
|
|
44000
44364
|
}
|
|
44001
44365
|
}
|
|
44002
44366
|
onEvent(threadId, handler) {
|
|
@@ -44385,7 +44749,18 @@ function runAgyExec(input, deps) {
|
|
|
44385
44749
|
prompt = trimmedInstructions + "\n\n" + prompt;
|
|
44386
44750
|
}
|
|
44387
44751
|
if (input.model) {
|
|
44388
|
-
|
|
44752
|
+
let targetModel = input.model;
|
|
44753
|
+
if (targetModel === "gemini-3.1-pro-high" || targetModel === "gemini-3.7-flash-high") {
|
|
44754
|
+
targetModel = "gemini-3.8-flash-high";
|
|
44755
|
+
} else if (targetModel === "gemini-3.1-pro-low" || targetModel === "gemini-3.7-flash-low") {
|
|
44756
|
+
targetModel = "gemini-3.8-flash-low";
|
|
44757
|
+
}
|
|
44758
|
+
const known = AGY_MODEL_CATALOG.find((m) => m.id === targetModel || m.displayName.toLowerCase() === targetModel.toLowerCase());
|
|
44759
|
+
if (known && !known.id.startsWith("gemini-3.1")) {
|
|
44760
|
+
args.push("--model", known.id);
|
|
44761
|
+
} else {
|
|
44762
|
+
args.push("--model", "gemini-3.8-flash-high");
|
|
44763
|
+
}
|
|
44389
44764
|
}
|
|
44390
44765
|
args.push(`--print=${prompt}`);
|
|
44391
44766
|
return new Promise((resolve) => {
|
|
@@ -44468,7 +44843,9 @@ function extractOneShotResult2(stdout) {
|
|
|
44468
44843
|
|
|
44469
44844
|
// ../../packages/agent-client/dist/run-agent-exec.js
|
|
44470
44845
|
function runAgentExec(input, deps, persistedProvider) {
|
|
44471
|
-
const
|
|
44846
|
+
const explicitProvider = persistedProvider ?? process.env.MRRLIN_CONSENSUS_PROVIDER?.trim().toLowerCase();
|
|
44847
|
+
const envToUse = explicitProvider ? { ...process.env, MRRLIN_AGENT_PROVIDER: explicitProvider } : process.env;
|
|
44848
|
+
const provider = resolveProvider(envToUse, explicitProvider);
|
|
44472
44849
|
if (provider.id === "codex") {
|
|
44473
44850
|
return runCodexExec(input, deps);
|
|
44474
44851
|
}
|
|
@@ -44484,7 +44861,7 @@ function runAgentExec(input, deps, persistedProvider) {
|
|
|
44484
44861
|
// ../../packages/agent-client/dist/resolved-seat.js
|
|
44485
44862
|
var CODEX_DEFAULT_MODEL_CONSTANT = "gpt-5.6-terra";
|
|
44486
44863
|
var CLAUDE_DEFAULT_MODEL_CONSTANT = "sonnet";
|
|
44487
|
-
var AGY_DEFAULT_MODEL_CONSTANT = "gemini-3.
|
|
44864
|
+
var AGY_DEFAULT_MODEL_CONSTANT = "gemini-3.8-flash-high";
|
|
44488
44865
|
var CODEX_DEFAULT_MODEL_ENV = "MRRLIN_CODEX_DEFAULT_MODEL";
|
|
44489
44866
|
var CODEX_STRONG_MODEL_ENV = "MRRLIN_CODEX_STRONG_MODEL";
|
|
44490
44867
|
var CLAUDE_DEFAULT_MODEL_ENV = "MRRLIN_CLAUDE_DEFAULT_MODEL";
|
|
@@ -44635,23 +45012,32 @@ async function resolveGatedTaskId(client, projectSlug, page) {
|
|
|
44635
45012
|
function domainScopedReviewHash2(rawHash, domain2) {
|
|
44636
45013
|
return domainScopedReviewHash(rawHash, domain2);
|
|
44637
45014
|
}
|
|
44638
|
-
async function
|
|
45015
|
+
async function resolveSpecDomains(client, projectSlug, page) {
|
|
44639
45016
|
try {
|
|
44640
45017
|
const taskId = await resolveGatedTaskId(client, projectSlug, page);
|
|
44641
45018
|
if (taskId !== null) {
|
|
44642
45019
|
const task = await client.getTask(projectSlug, taskId);
|
|
44643
|
-
return
|
|
45020
|
+
return normalizeDomainArray(task?.domains ?? task?.domain);
|
|
44644
45021
|
}
|
|
44645
45022
|
const plans = await client.listPlans(projectSlug);
|
|
44646
45023
|
const plan = plans.find((p) => p.specWikiPageId === page.id);
|
|
44647
|
-
|
|
44648
|
-
|
|
45024
|
+
if (plan) {
|
|
45025
|
+
const rawPlanDomains = plan?.domains ?? plan?.domain;
|
|
45026
|
+
const substantivePlanDomains = (Array.isArray(rawPlanDomains) ? rawPlanDomains : rawPlanDomains != null ? [rawPlanDomains] : []).map(String).map((d) => d.trim().toLowerCase()).filter(Boolean);
|
|
45027
|
+
if (substantivePlanDomains.length > 0) {
|
|
45028
|
+
return normalizeDomainArray(substantivePlanDomains);
|
|
45029
|
+
}
|
|
45030
|
+
}
|
|
44649
45031
|
const project = await client.getProject(projectSlug);
|
|
44650
|
-
return
|
|
45032
|
+
return normalizeDomainArray(project?.defaultDomains ?? project?.defaultDomain);
|
|
44651
45033
|
} catch {
|
|
44652
|
-
return "code";
|
|
45034
|
+
return ["code"];
|
|
44653
45035
|
}
|
|
44654
45036
|
}
|
|
45037
|
+
async function resolveSpecDomain(client, projectSlug, page) {
|
|
45038
|
+
const domains = await resolveSpecDomains(client, projectSlug, page);
|
|
45039
|
+
return normalizeDomain(domains);
|
|
45040
|
+
}
|
|
44655
45041
|
|
|
44656
45042
|
// src/consensus/findings.ts
|
|
44657
45043
|
function shouldCurateFindings(history) {
|
|
@@ -45035,7 +45421,13 @@ function gateCallRecorder(args) {
|
|
|
45035
45421
|
}
|
|
45036
45422
|
function buildSpecGateDeps(args) {
|
|
45037
45423
|
const { client, projectSlug, specPageId, codexExecutable, extraHeadCount = 0, seat, stateDir } = args;
|
|
45038
|
-
const
|
|
45424
|
+
const consensusProvider = process.env.MRRLIN_CONSENSUS_PROVIDER?.trim().toLowerCase() || void 0;
|
|
45425
|
+
const recordCost = gateCallRecorder({
|
|
45426
|
+
stateDir,
|
|
45427
|
+
projectSlug,
|
|
45428
|
+
gate: "spec",
|
|
45429
|
+
provider: consensusProvider ?? seat?.id ?? null
|
|
45430
|
+
});
|
|
45039
45431
|
const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 5);
|
|
45040
45432
|
const maxHeadCalls = 2 * rounds * (MAX_DOMAIN_HEADS + extraHeadCount + 2);
|
|
45041
45433
|
const timeoutMs = Number(process.env["MRRLIN_CONSENSUS_HEAD_TIMEOUT_MS"] ?? 15e4);
|
|
@@ -45049,8 +45441,24 @@ function buildSpecGateDeps(args) {
|
|
|
45049
45441
|
}
|
|
45050
45442
|
const taskId = await resolveGatedTaskId(client, projectSlug, { id: page.id, taskId: page.taskId ?? null });
|
|
45051
45443
|
if (taskId === null) {
|
|
45444
|
+
const plans = await client.listPlans(projectSlug);
|
|
45445
|
+
const plan = plans.find((p) => p.specWikiPageId === specPageId);
|
|
45446
|
+
if (plan) {
|
|
45447
|
+
const project2 = await client.getProject(projectSlug);
|
|
45448
|
+
const rawPlanDomains = plan?.domains ?? plan?.domain;
|
|
45449
|
+
const substantivePlanDomains = (Array.isArray(rawPlanDomains) ? rawPlanDomains : rawPlanDomains != null ? [rawPlanDomains] : []).map(String).map((d) => d.trim().toLowerCase()).filter(Boolean);
|
|
45450
|
+
const planDomains = substantivePlanDomains.length > 0 ? substantivePlanDomains : project2?.defaultDomains ?? (project2?.defaultDomain ? [project2.defaultDomain] : void 0);
|
|
45451
|
+
return {
|
|
45452
|
+
taskId: null,
|
|
45453
|
+
planId: plan.id,
|
|
45454
|
+
seed: null,
|
|
45455
|
+
title: plan.title ?? plan.name ?? null,
|
|
45456
|
+
domains: planDomains,
|
|
45457
|
+
notes: null
|
|
45458
|
+
};
|
|
45459
|
+
}
|
|
45052
45460
|
const project = await client.getProject(projectSlug);
|
|
45053
|
-
return { taskId: null, seed: null, title: null, domains: project.defaultDomains, notes: null };
|
|
45461
|
+
return { taskId: null, planId: null, seed: null, title: null, domains: project.defaultDomains, notes: null };
|
|
45054
45462
|
}
|
|
45055
45463
|
const task = await client.getTask(projectSlug, taskId);
|
|
45056
45464
|
if (!task) {
|
|
@@ -45067,10 +45475,7 @@ function buildSpecGateDeps(args) {
|
|
|
45067
45475
|
if (answered && answered.answer) {
|
|
45068
45476
|
resolvedSeed = "(No operator statement was recorded when this task was created. Derived from the operator's answer to the gate's requirements question:)\n\n" + answered.answer;
|
|
45069
45477
|
} else {
|
|
45070
|
-
|
|
45071
|
-
if (parsed !== null && parsed.items.length > 0) {
|
|
45072
|
-
resolvedSeed = "(No operator statement on record for this task. Derived from the task's own notes \u2014 the following acceptance criteria were extracted verbatim:)\n\n" + parsed.items.map((c) => `- ${c}`).join("\n");
|
|
45073
|
-
}
|
|
45478
|
+
resolvedSeed = null;
|
|
45074
45479
|
}
|
|
45075
45480
|
}
|
|
45076
45481
|
const adjudicated = selectAdjudicated(taskInboxItems);
|
|
@@ -45132,7 +45537,9 @@ ${artifact}`,
|
|
|
45132
45537
|
timeoutMs,
|
|
45133
45538
|
sandbox: "read-only",
|
|
45134
45539
|
codexExecutable
|
|
45135
|
-
}
|
|
45540
|
+
},
|
|
45541
|
+
void 0,
|
|
45542
|
+
consensusProvider
|
|
45136
45543
|
);
|
|
45137
45544
|
recordCost("head", personaName, null, startedAt, result);
|
|
45138
45545
|
return result;
|
|
@@ -45149,8 +45556,10 @@ ${artifact}`,
|
|
|
45149
45556
|
codexExecutable,
|
|
45150
45557
|
// model-tier-planning-and-arbiter task 2.5 (D1): the arbiter is pinned to the seat's
|
|
45151
45558
|
// default model.
|
|
45152
|
-
model: seat?.model ?? seat?.models?.strong
|
|
45153
|
-
}
|
|
45559
|
+
model: consensusProvider ? void 0 : seat?.model ?? seat?.models?.strong
|
|
45560
|
+
},
|
|
45561
|
+
void 0,
|
|
45562
|
+
consensusProvider
|
|
45154
45563
|
);
|
|
45155
45564
|
recordCost("arbiter", "arbiter", seat?.model ?? seat?.models?.strong ?? null, startedAt, result);
|
|
45156
45565
|
return result;
|
|
@@ -45173,7 +45582,9 @@ ${artifact}`,
|
|
|
45173
45582
|
timeoutMs,
|
|
45174
45583
|
sandbox: "read-only",
|
|
45175
45584
|
codexExecutable
|
|
45176
|
-
}
|
|
45585
|
+
},
|
|
45586
|
+
void 0,
|
|
45587
|
+
consensusProvider
|
|
45177
45588
|
);
|
|
45178
45589
|
recordCost("reviser", null, null, startedAt, result);
|
|
45179
45590
|
return result;
|
|
@@ -45206,6 +45617,28 @@ ${artifact}`,
|
|
|
45206
45617
|
return false;
|
|
45207
45618
|
}
|
|
45208
45619
|
},
|
|
45620
|
+
async finalizeSpecReviewCas(input) {
|
|
45621
|
+
const history = input.review.history;
|
|
45622
|
+
const highlights = await curateFindings(Array.isArray(history) ? history : [], {
|
|
45623
|
+
runCodexExec: async (execInput) => {
|
|
45624
|
+
const startedAt = Date.now();
|
|
45625
|
+
const result = await runAgentExec(execInput, void 0, consensusProvider);
|
|
45626
|
+
recordCost("findings", "findings-writer", null, startedAt, result);
|
|
45627
|
+
return result;
|
|
45628
|
+
},
|
|
45629
|
+
loadPersona,
|
|
45630
|
+
cwd: sterileCwd(),
|
|
45631
|
+
timeoutMs,
|
|
45632
|
+
codexExecutable
|
|
45633
|
+
});
|
|
45634
|
+
return client.finalizeSpecReviewCas(projectSlug, specPageId, {
|
|
45635
|
+
...input,
|
|
45636
|
+
review: {
|
|
45637
|
+
...input.review,
|
|
45638
|
+
...highlights ? { highlights } : {}
|
|
45639
|
+
}
|
|
45640
|
+
});
|
|
45641
|
+
},
|
|
45209
45642
|
// Record the consensus-review outcome keyed by content hash via the D2b
|
|
45210
45643
|
// endpoint. `record` is typed `unknown` (it crosses the SpecGateDeps
|
|
45211
45644
|
// boundary) so we narrow it defensively:
|
|
@@ -45228,7 +45661,7 @@ ${artifact}`,
|
|
|
45228
45661
|
// call the cost record silently omits.
|
|
45229
45662
|
runCodexExec: async (input) => {
|
|
45230
45663
|
const startedAt = Date.now();
|
|
45231
|
-
const result = await runAgentExec(input);
|
|
45664
|
+
const result = await runAgentExec(input, void 0, consensusProvider);
|
|
45232
45665
|
recordCost("findings", "findings-writer", null, startedAt, result);
|
|
45233
45666
|
return result;
|
|
45234
45667
|
},
|
|
@@ -45257,7 +45690,13 @@ ${artifact}`,
|
|
|
45257
45690
|
}
|
|
45258
45691
|
function buildCodeGateDeps(args) {
|
|
45259
45692
|
const { client, projectSlug, baseRef, cwd, codexExecutable, extraHeads, seat, stateDir } = args;
|
|
45260
|
-
const
|
|
45693
|
+
const consensusProvider = process.env.MRRLIN_CONSENSUS_PROVIDER?.trim().toLowerCase() || void 0;
|
|
45694
|
+
const recordCost = gateCallRecorder({
|
|
45695
|
+
stateDir,
|
|
45696
|
+
projectSlug,
|
|
45697
|
+
gate: "code",
|
|
45698
|
+
provider: consensusProvider ?? seat?.id ?? null
|
|
45699
|
+
});
|
|
45261
45700
|
const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 5);
|
|
45262
45701
|
const heads = ["code-reviewer", ...extraHeads ?? []];
|
|
45263
45702
|
const maxHeadCalls = 2 * rounds * (heads.length + 2);
|
|
@@ -45285,60 +45724,72 @@ function buildCodeGateDeps(args) {
|
|
|
45285
45724
|
async runHead(personaName, artifact, dismissed) {
|
|
45286
45725
|
const persona = loadPersona(personaName);
|
|
45287
45726
|
const startedAt = Date.now();
|
|
45288
|
-
const result = await runAgentExec(
|
|
45289
|
-
|
|
45727
|
+
const result = await runAgentExec(
|
|
45728
|
+
{
|
|
45729
|
+
prompt: `Review this code diff.
|
|
45290
45730
|
|
|
45291
45731
|
${dismissed}
|
|
45292
45732
|
|
|
45293
45733
|
--- DIFF ---
|
|
45294
45734
|
${artifact}`,
|
|
45295
|
-
|
|
45296
|
-
|
|
45297
|
-
|
|
45298
|
-
|
|
45299
|
-
|
|
45300
|
-
|
|
45301
|
-
|
|
45302
|
-
|
|
45303
|
-
|
|
45735
|
+
developerInstructions: persona,
|
|
45736
|
+
// Read-only review of a diff that is entirely in the prompt — sterile, per sterile-cwd.ts.
|
|
45737
|
+
// This factory is the ONE that stays mixed: runReviser below keeps the real `cwd` because
|
|
45738
|
+
// it edits the branch, and the git helpers keep it because they are not provider calls.
|
|
45739
|
+
cwd: sterileCwd(),
|
|
45740
|
+
timeoutMs: headTimeoutMs,
|
|
45741
|
+
sandbox: "read-only",
|
|
45742
|
+
codexExecutable
|
|
45743
|
+
},
|
|
45744
|
+
void 0,
|
|
45745
|
+
consensusProvider
|
|
45746
|
+
);
|
|
45304
45747
|
recordCost("head", personaName, null, startedAt, result);
|
|
45305
45748
|
return result;
|
|
45306
45749
|
},
|
|
45307
45750
|
async runArbiter(prompt) {
|
|
45308
45751
|
const startedAt = Date.now();
|
|
45309
|
-
const result = await runAgentExec(
|
|
45310
|
-
|
|
45311
|
-
|
|
45312
|
-
|
|
45313
|
-
|
|
45314
|
-
|
|
45315
|
-
|
|
45316
|
-
|
|
45317
|
-
|
|
45318
|
-
|
|
45319
|
-
|
|
45752
|
+
const result = await runAgentExec(
|
|
45753
|
+
{
|
|
45754
|
+
prompt,
|
|
45755
|
+
developerInstructions: loadPersona("arbiter"),
|
|
45756
|
+
cwd: sterileCwd(),
|
|
45757
|
+
timeoutMs: headTimeoutMs,
|
|
45758
|
+
sandbox: "read-only",
|
|
45759
|
+
codexExecutable,
|
|
45760
|
+
// model-tier-planning-and-arbiter task 2.5 (D1): the arbiter is pinned to the seat's
|
|
45761
|
+
// default model.
|
|
45762
|
+
model: consensusProvider ? void 0 : seat?.model ?? seat?.models?.strong
|
|
45763
|
+
},
|
|
45764
|
+
void 0,
|
|
45765
|
+
consensusProvider
|
|
45766
|
+
);
|
|
45320
45767
|
recordCost("arbiter", "arbiter", seat?.model ?? seat?.models?.strong ?? null, startedAt, result);
|
|
45321
45768
|
return result;
|
|
45322
45769
|
},
|
|
45323
45770
|
async runReviser(artifact, accepted) {
|
|
45324
45771
|
const acceptedList = accepted.map((a) => `- (${a.category}) ${a.text}`).join("\n");
|
|
45325
45772
|
const startedAt = Date.now();
|
|
45326
|
-
const result = await runAgentExec(
|
|
45327
|
-
|
|
45773
|
+
const result = await runAgentExec(
|
|
45774
|
+
{
|
|
45775
|
+
prompt: `Accepted issues to fix:
|
|
45328
45776
|
${acceptedList}
|
|
45329
45777
|
|
|
45330
45778
|
--- DIFF ---
|
|
45331
45779
|
${artifact}`,
|
|
45332
|
-
|
|
45333
|
-
|
|
45334
|
-
|
|
45335
|
-
|
|
45336
|
-
|
|
45337
|
-
|
|
45338
|
-
|
|
45339
|
-
|
|
45340
|
-
|
|
45341
|
-
|
|
45780
|
+
developerInstructions: "You are fixing code review issues. Edit ONLY files already changed in this branch's diff; make the minimal change to resolve each accepted issue. Do not refactor unrelated code. Do not edit files outside the diff.",
|
|
45781
|
+
// The real repository root, NOT sterileCwd(): this is the one gate call that runs
|
|
45782
|
+
// `workspace-write` and edits the branch, so a sterile cwd would point the sandbox at an
|
|
45783
|
+
// empty directory and the revision would silently apply to nothing — the gate would keep
|
|
45784
|
+
// reporting rounds while fixing no code at all.
|
|
45785
|
+
cwd,
|
|
45786
|
+
timeoutMs: reviserTimeoutMs,
|
|
45787
|
+
sandbox: "workspace-write",
|
|
45788
|
+
codexExecutable
|
|
45789
|
+
},
|
|
45790
|
+
void 0,
|
|
45791
|
+
consensusProvider
|
|
45792
|
+
);
|
|
45342
45793
|
recordCost("reviser", null, null, startedAt, result);
|
|
45343
45794
|
return result;
|
|
45344
45795
|
},
|
|
@@ -45362,7 +45813,13 @@ function renderCriteriaBlock(criteria) {
|
|
|
45362
45813
|
}
|
|
45363
45814
|
async function buildDeliverableGateDeps(args) {
|
|
45364
45815
|
const { client, projectSlug, deliverablePageId, attemptOutcome, codexExecutable, runId, seat, stateDir } = args;
|
|
45365
|
-
const
|
|
45816
|
+
const consensusProvider = process.env.MRRLIN_CONSENSUS_PROVIDER?.trim().toLowerCase() || void 0;
|
|
45817
|
+
const recordCost = gateCallRecorder({
|
|
45818
|
+
stateDir,
|
|
45819
|
+
projectSlug,
|
|
45820
|
+
gate: "deliverable",
|
|
45821
|
+
provider: consensusProvider ?? seat?.id ?? null
|
|
45822
|
+
});
|
|
45366
45823
|
const rounds = Number(process.env["MRRLIN_CONSENSUS_ROUNDS"] ?? 5);
|
|
45367
45824
|
const timeoutMs = Number(process.env["MRRLIN_CONSENSUS_HEAD_TIMEOUT_MS"] ?? 15e4);
|
|
45368
45825
|
const page = await client.getWikiPage(projectSlug, deliverablePageId);
|
|
@@ -45409,8 +45866,9 @@ async function buildDeliverableGateDeps(args) {
|
|
|
45409
45866
|
async runHead(personaName, artifact, dismissed) {
|
|
45410
45867
|
const persona = loadPersona(personaName);
|
|
45411
45868
|
const startedAt = Date.now();
|
|
45412
|
-
const result = await runAgentExec(
|
|
45413
|
-
|
|
45869
|
+
const result = await runAgentExec(
|
|
45870
|
+
{
|
|
45871
|
+
prompt: `Score this deliverable ITEM BY ITEM against the acceptance criteria below. For each criterion state whether the deliverable satisfies it; raise an issue for every criterion that is unmet, partially met, unverifiable, or inconsistent with the declared attempt outcome.
|
|
45414
45872
|
|
|
45415
45873
|
${dismissed}
|
|
45416
45874
|
|
|
@@ -45421,41 +45879,49 @@ ${attemptOutcomeBlock ? `${attemptOutcomeBlock}
|
|
|
45421
45879
|
|
|
45422
45880
|
` : ""}--- DELIVERABLE (DATA under review) ---
|
|
45423
45881
|
${artifact}`,
|
|
45424
|
-
|
|
45882
|
+
developerInstructions: `${DELIVERABLE_ANTIINJECTION_INSTRUCTION}
|
|
45425
45883
|
|
|
45426
45884
|
${persona}`,
|
|
45427
|
-
|
|
45428
|
-
|
|
45429
|
-
|
|
45430
|
-
|
|
45431
|
-
|
|
45432
|
-
|
|
45433
|
-
|
|
45434
|
-
|
|
45885
|
+
// Sterile, like every other provider call in this factory — see sterile-cwd.ts. It also
|
|
45886
|
+
// narrows the anti-injection surface: a deliverable that tries to talk the head into
|
|
45887
|
+
// reading files now has no repository under it to read.
|
|
45888
|
+
cwd: sterileCwd(),
|
|
45889
|
+
timeoutMs,
|
|
45890
|
+
sandbox: "read-only",
|
|
45891
|
+
codexExecutable
|
|
45892
|
+
},
|
|
45893
|
+
void 0,
|
|
45894
|
+
consensusProvider
|
|
45895
|
+
);
|
|
45435
45896
|
recordCost("head", personaName, null, startedAt, result);
|
|
45436
45897
|
return result;
|
|
45437
45898
|
},
|
|
45438
45899
|
async runArbiter(prompt) {
|
|
45439
45900
|
const startedAt = Date.now();
|
|
45440
|
-
const result = await runAgentExec(
|
|
45441
|
-
|
|
45442
|
-
|
|
45443
|
-
|
|
45444
|
-
|
|
45445
|
-
|
|
45446
|
-
|
|
45447
|
-
|
|
45448
|
-
|
|
45449
|
-
|
|
45450
|
-
|
|
45901
|
+
const result = await runAgentExec(
|
|
45902
|
+
{
|
|
45903
|
+
prompt,
|
|
45904
|
+
developerInstructions: loadPersona("arbiter"),
|
|
45905
|
+
cwd: sterileCwd(),
|
|
45906
|
+
timeoutMs,
|
|
45907
|
+
sandbox: "read-only",
|
|
45908
|
+
codexExecutable,
|
|
45909
|
+
// model-tier-planning-and-arbiter task 2.5 (D1): the arbiter is pinned to the seat's
|
|
45910
|
+
// default model.
|
|
45911
|
+
model: consensusProvider ? void 0 : seat?.model ?? seat?.models?.strong
|
|
45912
|
+
},
|
|
45913
|
+
void 0,
|
|
45914
|
+
consensusProvider
|
|
45915
|
+
);
|
|
45451
45916
|
recordCost("arbiter", "arbiter", seat?.model ?? seat?.models?.strong ?? null, startedAt, result);
|
|
45452
45917
|
return result;
|
|
45453
45918
|
},
|
|
45454
45919
|
async runReviser(artifact, accepted) {
|
|
45455
45920
|
const acceptedList = accepted.map((a) => `- (${a.category}) ${a.text}`).join("\n");
|
|
45456
45921
|
const startedAt = Date.now();
|
|
45457
|
-
const result = await runAgentExec(
|
|
45458
|
-
|
|
45922
|
+
const result = await runAgentExec(
|
|
45923
|
+
{
|
|
45924
|
+
prompt: `Accepted issues to fix (each maps to an unmet acceptance criterion):
|
|
45459
45925
|
${acceptedList}
|
|
45460
45926
|
|
|
45461
45927
|
--- ACCEPTANCE CRITERIA ---
|
|
@@ -45463,14 +45929,17 @@ ${criteriaBlock}
|
|
|
45463
45929
|
|
|
45464
45930
|
--- DELIVERABLE ---
|
|
45465
45931
|
${artifact}`,
|
|
45466
|
-
|
|
45932
|
+
developerInstructions: `${DELIVERABLE_ANTIINJECTION_INSTRUCTION}
|
|
45467
45933
|
|
|
45468
45934
|
You revise a deliverable to address the accepted issues so it satisfies the acceptance criteria and stays consistent with the declared outcome${attemptOutcome ? ` (${attemptOutcome})` : ""}. Return ONLY the full revised markdown, no commentary. Do not invent facts; keep the deliverable's format and do not change the declared outcome.`,
|
|
45469
|
-
|
|
45470
|
-
|
|
45471
|
-
|
|
45472
|
-
|
|
45473
|
-
|
|
45935
|
+
cwd: sterileCwd(),
|
|
45936
|
+
timeoutMs,
|
|
45937
|
+
sandbox: "read-only",
|
|
45938
|
+
codexExecutable
|
|
45939
|
+
},
|
|
45940
|
+
void 0,
|
|
45941
|
+
consensusProvider
|
|
45942
|
+
);
|
|
45474
45943
|
recordCost("reviser", null, null, startedAt, result);
|
|
45475
45944
|
return result;
|
|
45476
45945
|
},
|
|
@@ -45501,7 +45970,7 @@ You revise a deliverable to address the accepted issues so it satisfies the acce
|
|
|
45501
45970
|
const rounds2 = typeof r["rounds"] === "number" ? r["rounds"] : 0;
|
|
45502
45971
|
const history = r["history"];
|
|
45503
45972
|
const highlights = await curateFindings(Array.isArray(history) ? history : [], {
|
|
45504
|
-
runCodexExec: runAgentExec,
|
|
45973
|
+
runCodexExec: (execInput) => runAgentExec(execInput, void 0, consensusProvider),
|
|
45505
45974
|
loadPersona,
|
|
45506
45975
|
cwd: sterileCwd(),
|
|
45507
45976
|
timeoutMs,
|
|
@@ -49891,22 +50360,22 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
|
|
|
49891
50360
|
}
|
|
49892
50361
|
const nonInteractiveFlow = !provider.redirectUrl;
|
|
49893
50362
|
if (authorizationCode !== void 0 || nonInteractiveFlow) {
|
|
49894
|
-
const
|
|
50363
|
+
const tokens3 = await fetchToken(provider, authorizationServerUrl, {
|
|
49895
50364
|
metadata,
|
|
49896
50365
|
resource,
|
|
49897
50366
|
authorizationCode,
|
|
49898
50367
|
fetchFn
|
|
49899
50368
|
});
|
|
49900
|
-
await provider.saveTokens(
|
|
50369
|
+
await provider.saveTokens(tokens3);
|
|
49901
50370
|
return "AUTHORIZED";
|
|
49902
50371
|
}
|
|
49903
|
-
const
|
|
49904
|
-
if (
|
|
50372
|
+
const tokens2 = await provider.tokens();
|
|
50373
|
+
if (tokens2?.refresh_token) {
|
|
49905
50374
|
try {
|
|
49906
50375
|
const newTokens = await refreshAuthorization(authorizationServerUrl, {
|
|
49907
50376
|
metadata,
|
|
49908
50377
|
clientInformation,
|
|
49909
|
-
refreshToken:
|
|
50378
|
+
refreshToken: tokens2.refresh_token,
|
|
49910
50379
|
resource,
|
|
49911
50380
|
addClientAuthentication: provider.addClientAuthentication,
|
|
49912
50381
|
fetchFn
|
|
@@ -50208,7 +50677,7 @@ async function refreshAuthorization(authorizationServerUrl, { metadata, clientIn
|
|
|
50208
50677
|
grant_type: "refresh_token",
|
|
50209
50678
|
refresh_token: refreshToken
|
|
50210
50679
|
});
|
|
50211
|
-
const
|
|
50680
|
+
const tokens2 = await executeTokenRequest(authorizationServerUrl, {
|
|
50212
50681
|
metadata,
|
|
50213
50682
|
tokenRequestParams,
|
|
50214
50683
|
clientInformation,
|
|
@@ -50216,7 +50685,7 @@ async function refreshAuthorization(authorizationServerUrl, { metadata, clientIn
|
|
|
50216
50685
|
resource,
|
|
50217
50686
|
fetchFn
|
|
50218
50687
|
});
|
|
50219
|
-
return { refresh_token: refreshToken, ...
|
|
50688
|
+
return { refresh_token: refreshToken, ...tokens2 };
|
|
50220
50689
|
}
|
|
50221
50690
|
async function fetchToken(provider, authorizationServerUrl, { metadata, resource, authorizationCode, fetchFn } = {}) {
|
|
50222
50691
|
const scope = provider.clientMetadata.scope;
|
|
@@ -50521,9 +50990,9 @@ var StreamableHTTPClientTransport = class {
|
|
|
50521
50990
|
async _commonHeaders() {
|
|
50522
50991
|
const headers = {};
|
|
50523
50992
|
if (this._authProvider) {
|
|
50524
|
-
const
|
|
50525
|
-
if (
|
|
50526
|
-
headers["Authorization"] = `Bearer ${
|
|
50993
|
+
const tokens2 = await this._authProvider.tokens();
|
|
50994
|
+
if (tokens2) {
|
|
50995
|
+
headers["Authorization"] = `Bearer ${tokens2.access_token}`;
|
|
50527
50996
|
}
|
|
50528
50997
|
}
|
|
50529
50998
|
if (this._sessionId) {
|
|
@@ -52284,13 +52753,87 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
52284
52753
|
suppressedAsAdjudicated.push({ rawQuestion: clarification.rawQuestion, coveredByAnswer: covering.answer });
|
|
52285
52754
|
return false;
|
|
52286
52755
|
});
|
|
52756
|
+
let planId = null;
|
|
52757
|
+
if (!taskId) {
|
|
52758
|
+
const plans = await ctx.client.listPlans(input.projectSlug);
|
|
52759
|
+
planId = plans.find((p) => p.specWikiPageId === input.pageId)?.id ?? null;
|
|
52760
|
+
}
|
|
52761
|
+
const evaluatedContentHash = result.evaluatedSnapshot?.contentHash ?? page?.contentHash;
|
|
52762
|
+
const isSeedAbsence = operatorClarifications.some(
|
|
52763
|
+
(c) => c.rawQuestion === SEED_ABSENCE_QUESTION || c.rawQuestion.includes(SEED_ABSENCE_MARKER)
|
|
52764
|
+
);
|
|
52765
|
+
if (!isSeedAbsence && operatorClarifications.length > 0 && evaluatedContentHash) {
|
|
52766
|
+
const domain2 = await resolveSpecDomain(ctx.client, input.projectSlug, {
|
|
52767
|
+
id: input.pageId,
|
|
52768
|
+
taskId
|
|
52769
|
+
});
|
|
52770
|
+
const suppressionEvidence = (result.droppedByCap?.length ?? 0) > 0 || suppressedAsAdjudicated.length > 0 ? {
|
|
52771
|
+
...result.droppedByCap?.length ? { droppedByCap: result.droppedByCap } : {},
|
|
52772
|
+
...suppressedAsAdjudicated.length ? { suppressedAsAdjudicated } : {}
|
|
52773
|
+
} : void 0;
|
|
52774
|
+
let ownership;
|
|
52775
|
+
if (result.evaluatedOwnership) {
|
|
52776
|
+
ownership = result.evaluatedOwnership;
|
|
52777
|
+
} else if (taskId !== null) {
|
|
52778
|
+
const task = await ctx.client.getTask(input.projectSlug, taskId).catch(() => null);
|
|
52779
|
+
ownership = { kind: "task", taskId, domains: task?.domains ?? [domain2] };
|
|
52780
|
+
} else if (planId !== null) {
|
|
52781
|
+
const plans = await ctx.client.listPlans(input.projectSlug).catch(() => []);
|
|
52782
|
+
const plan = plans.find((p) => p.id === planId);
|
|
52783
|
+
ownership = { kind: "plan", planId, domains: plan?.domains ?? ["code"] };
|
|
52784
|
+
} else {
|
|
52785
|
+
ownership = { kind: "project", domains: ["code"] };
|
|
52786
|
+
}
|
|
52787
|
+
if (ctx.client.finalizeSpecReviewCas) {
|
|
52788
|
+
const finalizeRes = await ctx.client.finalizeSpecReviewCas(input.projectSlug, input.pageId, {
|
|
52789
|
+
expectedContentHash: evaluatedContentHash,
|
|
52790
|
+
ownership,
|
|
52791
|
+
review: {
|
|
52792
|
+
outcome: "CONSENSUS_BLOCKED_CLARIFICATION",
|
|
52793
|
+
rounds: result.rounds !== void 0 ? result.rounds : result.history?.length ? result.history.length : 0,
|
|
52794
|
+
...result.history !== void 0 ? { history: result.history } : {},
|
|
52795
|
+
...result.highlights !== void 0 ? { highlights: Array.isArray(result.highlights) ? { findings: result.highlights } : result.highlights } : {},
|
|
52796
|
+
...suppressionEvidence ? { suppressionEvidence } : {}
|
|
52797
|
+
},
|
|
52798
|
+
specReady: false
|
|
52799
|
+
});
|
|
52800
|
+
if (finalizeRes.status === "superseded") {
|
|
52801
|
+
return {
|
|
52802
|
+
kind: "superseded",
|
|
52803
|
+
finalReviewedHash: evaluatedContentHash,
|
|
52804
|
+
error: "Spec content changed during review",
|
|
52805
|
+
...resolvedTaskId ? { taskId: resolvedTaskId } : {}
|
|
52806
|
+
};
|
|
52807
|
+
}
|
|
52808
|
+
if (finalizeRes.status !== "applied") {
|
|
52809
|
+
return {
|
|
52810
|
+
kind: "failed",
|
|
52811
|
+
failureReason: "consensus_blocked",
|
|
52812
|
+
error: finalizeRes.reason ?? "cas_rejected",
|
|
52813
|
+
finalReviewedHash: evaluatedContentHash,
|
|
52814
|
+
...resolvedTaskId ? { taskId: resolvedTaskId } : {}
|
|
52815
|
+
};
|
|
52816
|
+
}
|
|
52817
|
+
} else if (ctx.client.recordSpecConsensusReview) {
|
|
52818
|
+
try {
|
|
52819
|
+
await ctx.client.recordSpecConsensusReview(input.projectSlug, input.pageId, {
|
|
52820
|
+
contentHash: domainScopedReviewHash2(evaluatedContentHash, domain2),
|
|
52821
|
+
outcome: "CONSENSUS_BLOCKED_CLARIFICATION",
|
|
52822
|
+
rounds: result.rounds !== void 0 ? result.rounds : result.history?.length ? result.history.length : 0,
|
|
52823
|
+
...result.history !== void 0 ? { history: result.history } : {},
|
|
52824
|
+
...result.highlights !== void 0 ? { highlights: Array.isArray(result.highlights) ? { findings: result.highlights } : result.highlights } : {},
|
|
52825
|
+
...suppressionEvidence ? { suppressionEvidence } : {}
|
|
52826
|
+
});
|
|
52827
|
+
} catch (error52) {
|
|
52828
|
+
process.stderr.write(
|
|
52829
|
+
`[mrrlin-mcp consensus] legacy clarification write failed for ${input.projectSlug}/${input.pageId}: ${error52 instanceof Error ? error52.message : String(error52)}
|
|
52830
|
+
`
|
|
52831
|
+
);
|
|
52832
|
+
}
|
|
52833
|
+
}
|
|
52834
|
+
}
|
|
52287
52835
|
let filedQuestionCount = 0;
|
|
52288
52836
|
if (toFile.length > 0) {
|
|
52289
|
-
let planId = null;
|
|
52290
|
-
if (!taskId) {
|
|
52291
|
-
const plans = await ctx.client.listPlans(input.projectSlug);
|
|
52292
|
-
planId = plans.find((p) => p.specWikiPageId === input.pageId)?.id ?? null;
|
|
52293
|
-
}
|
|
52294
52837
|
if (taskId) {
|
|
52295
52838
|
const scopeId = taskId;
|
|
52296
52839
|
const groupId = toFile.length > 1 ? `II-${scopeId}-gate-group-${(0, import_node_crypto4.createHash)("sha256").update(toFile.map((c) => c.rawQuestion).sort().join("\n")).digest("hex").slice(0, 10)}` : void 0;
|
|
@@ -52307,7 +52850,11 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
52307
52850
|
questionOptions,
|
|
52308
52851
|
...groupId ? { groupId } : {}
|
|
52309
52852
|
});
|
|
52310
|
-
} catch {
|
|
52853
|
+
} catch (err) {
|
|
52854
|
+
const isConflict = err?.status === 409 || err?.statusCode === 409 || err?.code === "23505" || String(err?.message ?? "").includes("already exists") || String(err?.message ?? "").includes("duplicate key");
|
|
52855
|
+
if (!isConflict) {
|
|
52856
|
+
throw err;
|
|
52857
|
+
}
|
|
52311
52858
|
}
|
|
52312
52859
|
}
|
|
52313
52860
|
filedQuestionCount = toFile.length;
|
|
@@ -52327,7 +52874,11 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
52327
52874
|
questionOptions,
|
|
52328
52875
|
...groupId ? { groupId } : {}
|
|
52329
52876
|
});
|
|
52330
|
-
} catch {
|
|
52877
|
+
} catch (err) {
|
|
52878
|
+
const isConflict = err?.status === 409 || err?.statusCode === 409 || err?.code === "23505" || String(err?.message ?? "").includes("already exists") || String(err?.message ?? "").includes("duplicate key");
|
|
52879
|
+
if (!isConflict) {
|
|
52880
|
+
throw err;
|
|
52881
|
+
}
|
|
52331
52882
|
}
|
|
52332
52883
|
}
|
|
52333
52884
|
filedQuestionCount = toFile.length;
|
|
@@ -52356,40 +52907,15 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
52356
52907
|
} catch {
|
|
52357
52908
|
}
|
|
52358
52909
|
}
|
|
52359
|
-
if (operatorClarifications.length > 0) {
|
|
52360
|
-
try {
|
|
52361
|
-
if (page) {
|
|
52362
|
-
const domain2 = await resolveSpecDomain(ctx.client, input.projectSlug, {
|
|
52363
|
-
id: page.id,
|
|
52364
|
-
taskId: page.taskId ?? null
|
|
52365
|
-
});
|
|
52366
|
-
const suppressionEvidence = (result.droppedByCap?.length ?? 0) > 0 || suppressedAsAdjudicated.length > 0 ? {
|
|
52367
|
-
...result.droppedByCap?.length ? { droppedByCap: result.droppedByCap } : {},
|
|
52368
|
-
...suppressedAsAdjudicated.length ? { suppressedAsAdjudicated } : {}
|
|
52369
|
-
} : void 0;
|
|
52370
|
-
await ctx.client.recordSpecConsensusReview(input.projectSlug, input.pageId, {
|
|
52371
|
-
contentHash: domainScopedReviewHash2(page.contentHash, domain2),
|
|
52372
|
-
outcome: "CONSENSUS_BLOCKED_CLARIFICATION",
|
|
52373
|
-
rounds: 0,
|
|
52374
|
-
...suppressionEvidence ? { suppressionEvidence } : {}
|
|
52375
|
-
});
|
|
52376
|
-
}
|
|
52377
|
-
} catch (error52) {
|
|
52378
|
-
process.stderr.write(
|
|
52379
|
-
`[mrrlin-mcp consensus] clarification verdict write failed for ${input.projectSlug}/${input.pageId}: ${error52 instanceof Error ? error52.message : String(error52)}
|
|
52380
|
-
`
|
|
52381
|
-
);
|
|
52382
|
-
}
|
|
52383
|
-
}
|
|
52384
52910
|
let error51;
|
|
52385
52911
|
if (filedQuestionCount > 0) {
|
|
52386
52912
|
error51 = `Consensus blocked on ${filedQuestionCount} clarification(s); filed as operator question(s).`;
|
|
52387
52913
|
} else if (diagnostics.length > 0 && operatorClarifications.length === 0) {
|
|
52388
52914
|
const diagnosticKinds = new Set(diagnostics.map((d) => d.kind));
|
|
52389
52915
|
if (diagnosticKinds.has("acceptance_criteria_unauthorable")) {
|
|
52390
|
-
error51 = "Consensus
|
|
52916
|
+
error51 = "Consensus paused: the spec needs a clear `## Acceptance criteria` checklist (`- [ ]`) before review can complete. Please add or update the checklist in the spec.";
|
|
52391
52917
|
} else {
|
|
52392
|
-
error51 = "Consensus
|
|
52918
|
+
error51 = "Consensus paused: the spec content does not match its linked task. Please update the spec or task relationship directly, not an inbox answer.";
|
|
52393
52919
|
}
|
|
52394
52920
|
} else if (operatorClarifications.length > 0 && toFile.length === 0 && suppressedAsAdjudicated.length > 0) {
|
|
52395
52921
|
error51 = `The reviewers raised ${suppressedAsAdjudicated.length} clarification(s), but each was a reworded repeat of one already answered \u2014 nothing new to answer.`;
|
|
@@ -52401,11 +52927,13 @@ async function handleConsensusBlocked(result, ctx, input, resolvedTaskId) {
|
|
|
52401
52927
|
if (result.escalation) {
|
|
52402
52928
|
error51 += ` (reason: ${result.escalation.diagnosis})`;
|
|
52403
52929
|
}
|
|
52930
|
+
const isRelationshipDiagnostic = isSeedAbsence || result.diagnostics?.some((d) => d.kind === "spec_identity_mismatch") || result.escalation?.diagnosis === "spec_identity_mismatch";
|
|
52404
52931
|
return {
|
|
52405
52932
|
kind: "failed",
|
|
52406
52933
|
failureReason: "consensus_blocked",
|
|
52407
52934
|
error: error51,
|
|
52408
52935
|
filedQuestionCount,
|
|
52936
|
+
finalReviewedHash: isRelationshipDiagnostic ? null : result.evaluatedSnapshot?.contentHash ?? null,
|
|
52409
52937
|
...resolvedTaskId ? { taskId: resolvedTaskId } : {}
|
|
52410
52938
|
};
|
|
52411
52939
|
}
|
|
@@ -52419,18 +52947,30 @@ async function retireGateQuestionsOnConverge(ctx, input, resolvedTaskId) {
|
|
|
52419
52947
|
const contentHash = page?.contentHash ?? null;
|
|
52420
52948
|
const inboxItems = await ctx.client.listInboxItems(input.projectSlug, { taskId: resolvedTaskId });
|
|
52421
52949
|
for (const item of inboxItems) {
|
|
52422
|
-
if (item.
|
|
52950
|
+
if (item.status !== "pending") {
|
|
52951
|
+
continue;
|
|
52952
|
+
}
|
|
52953
|
+
if (item.taskId != null && item.taskId !== resolvedTaskId) {
|
|
52954
|
+
continue;
|
|
52955
|
+
}
|
|
52956
|
+
const isGateQuestion = (item.origin === "gate" || item.origin === "gate_block") && item.kind === "question";
|
|
52957
|
+
const specRefPattern = new RegExp(
|
|
52958
|
+
`Specs/${input.pageId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![0-9a-zA-Z_-])`
|
|
52959
|
+
);
|
|
52960
|
+
const isSpecHandoff = item.kind === "handoff" && item.id.startsWith(`II-SPEC-STOPPED-${input.pageId}-`) && specRefPattern.test(item.body);
|
|
52961
|
+
if (!isGateQuestion && !isSpecHandoff) {
|
|
52423
52962
|
continue;
|
|
52424
52963
|
}
|
|
52425
52964
|
try {
|
|
52965
|
+
const verdict = isGateQuestion ? "rejected" : "acknowledged";
|
|
52426
52966
|
await ctx.client.decideInboxItem(input.projectSlug, item.id, {
|
|
52427
|
-
verdict
|
|
52967
|
+
verdict,
|
|
52428
52968
|
decidedBy: "gate",
|
|
52429
52969
|
reason: `Superseded by converged spec Specs/${input.pageId} (contentHash: ${contentHash ?? "unknown"})`
|
|
52430
52970
|
});
|
|
52431
52971
|
} catch (itemError) {
|
|
52432
52972
|
process.stderr.write(
|
|
52433
|
-
`[mrrlin-mcp consensus] gate
|
|
52973
|
+
`[mrrlin-mcp consensus] gate retirement failed for ${input.projectSlug}/${input.pageId} item ${item.id}: ${itemError instanceof Error ? itemError.message : String(itemError)}
|
|
52434
52974
|
`
|
|
52435
52975
|
);
|
|
52436
52976
|
}
|
|
@@ -52520,6 +53060,13 @@ async function run(ctx, input, builders = defaultNativeSpecGateBuilders) {
|
|
|
52520
53060
|
return { kind: "retry", failureReason: "provider_unavailable", error: "Consensus unavailable (provider/quota).", ...resolvedTaskId ? { taskId: resolvedTaskId } : {} };
|
|
52521
53061
|
case "SPEC_CHANGED_DURING_REVIEW":
|
|
52522
53062
|
return { kind: "retry", failureReason: "spec_changed", error: "Spec changed during review.", ...resolvedTaskId ? { taskId: resolvedTaskId } : {} };
|
|
53063
|
+
case "SUPERSEDED":
|
|
53064
|
+
return {
|
|
53065
|
+
kind: "superseded",
|
|
53066
|
+
reason: "Spec changed or ownership unlinked during review",
|
|
53067
|
+
error: "Spec changed or ownership unlinked during review",
|
|
53068
|
+
...resolvedTaskId ? { taskId: resolvedTaskId } : {}
|
|
53069
|
+
};
|
|
52523
53070
|
default:
|
|
52524
53071
|
return {
|
|
52525
53072
|
kind: "failed",
|
|
@@ -52530,6 +53077,14 @@ async function run(ctx, input, builders = defaultNativeSpecGateBuilders) {
|
|
|
52530
53077
|
}
|
|
52531
53078
|
}
|
|
52532
53079
|
function onComplete(outcome, input) {
|
|
53080
|
+
if (outcome.kind === "superseded") {
|
|
53081
|
+
return {
|
|
53082
|
+
kind: "info",
|
|
53083
|
+
suppressInbox: true,
|
|
53084
|
+
body: outcome.reason ?? "Superseded",
|
|
53085
|
+
...outcome.taskId ? { taskId: outcome.taskId } : {}
|
|
53086
|
+
};
|
|
53087
|
+
}
|
|
52533
53088
|
const taskId = "taskId" in outcome ? outcome.taskId : void 0;
|
|
52534
53089
|
const taskRef = taskId ? outcome.kind === "done" && outcome.taskTitle ? `${taskId} \u2014 ${outcome.taskTitle}` : taskId : null;
|
|
52535
53090
|
if (outcome.kind === "done") {
|
|
@@ -52579,10 +53134,12 @@ ${findings.map((f) => `\u2022 ${f}`).join("\n")}` : "";
|
|
|
52579
53134
|
};
|
|
52580
53135
|
}
|
|
52581
53136
|
const handoffTaskRef = taskRef ? ` (${taskRef})` : "";
|
|
53137
|
+
const revisionSuffix = "finalReviewedHash" in outcome && outcome.finalReviewedHash ? outcome.finalReviewedHash.slice(0, 8) : "init";
|
|
52582
53138
|
return {
|
|
52583
53139
|
kind: "handoff",
|
|
53140
|
+
id: `II-SPEC-STOPPED-${input.pageId}-${revisionSuffix}`,
|
|
52584
53141
|
...taskId ? { taskId } : {},
|
|
52585
|
-
body: `The spec review stopped for Specs/${input.pageId}${handoffTaskRef}. ${outcome.error} Review the
|
|
53142
|
+
body: `The spec review stopped for Specs/${input.pageId}${handoffTaskRef}. ${outcome.error} Review the spec or consult the Director to update it.`
|
|
52586
53143
|
};
|
|
52587
53144
|
}
|
|
52588
53145
|
var consensusDescriptor = {
|
|
@@ -52761,6 +53318,14 @@ async function run2(ctx, input) {
|
|
|
52761
53318
|
};
|
|
52762
53319
|
}
|
|
52763
53320
|
function onComplete2(outcome, input) {
|
|
53321
|
+
if (outcome.kind === "superseded") {
|
|
53322
|
+
return {
|
|
53323
|
+
kind: "info",
|
|
53324
|
+
suppressInbox: true,
|
|
53325
|
+
body: outcome.reason ?? outcome.error ?? "Superseded",
|
|
53326
|
+
...outcome.taskId ? { taskId: outcome.taskId } : {}
|
|
53327
|
+
};
|
|
53328
|
+
}
|
|
52764
53329
|
const taskId = outcome.kind === "failed" || outcome.kind === "done" ? outcome.taskId : void 0;
|
|
52765
53330
|
const taskRef = taskId ? outcome.kind === "done" && outcome.taskTitle ? `${taskId} \u2014 ${outcome.taskTitle}` : taskId : null;
|
|
52766
53331
|
if (outcome.kind === "done") {
|
|
@@ -52818,7 +53383,7 @@ function defaultSleep(ms) {
|
|
|
52818
53383
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
52819
53384
|
}
|
|
52820
53385
|
function enforceSpecOutputContract(markdown, taskId, title) {
|
|
52821
|
-
let body = markdown.trim();
|
|
53386
|
+
let body = normalizeAcceptanceCriteria(markdown.trim());
|
|
52822
53387
|
const headingRe = new RegExp(`^#\\s+${taskId}\\b`, "m");
|
|
52823
53388
|
if (!headingRe.test(body)) {
|
|
52824
53389
|
body = `# ${taskId} \u2014 ${title}
|
|
@@ -53275,6 +53840,14 @@ async function runDraftSpec(ctx, input, deps) {
|
|
|
53275
53840
|
};
|
|
53276
53841
|
}
|
|
53277
53842
|
function onComplete3(outcome, input) {
|
|
53843
|
+
if (outcome.kind === "superseded") {
|
|
53844
|
+
return {
|
|
53845
|
+
kind: "info",
|
|
53846
|
+
suppressInbox: true,
|
|
53847
|
+
body: outcome.reason ?? outcome.error ?? "Superseded",
|
|
53848
|
+
taskId: input.taskId
|
|
53849
|
+
};
|
|
53850
|
+
}
|
|
53278
53851
|
if (outcome.kind === "done") {
|
|
53279
53852
|
let parsed = {};
|
|
53280
53853
|
try {
|
|
@@ -54167,6 +54740,9 @@ async function runReportEvidence(ctx, input) {
|
|
|
54167
54740
|
return { kind: "done", resultJson: JSON.stringify(artifact), finalReviewedHash: null };
|
|
54168
54741
|
}
|
|
54169
54742
|
function onComplete5(outcome, input) {
|
|
54743
|
+
if (outcome.kind === "superseded") {
|
|
54744
|
+
return { kind: "info", body: `Evidence assembly superseded for report ${input.reportId}.`, suppressInbox: true };
|
|
54745
|
+
}
|
|
54170
54746
|
if (outcome.kind === "done") {
|
|
54171
54747
|
return { kind: "info", body: `Evidence assembled for report ${input.reportId}.`, suppressInbox: true };
|
|
54172
54748
|
}
|
|
@@ -55350,7 +55926,7 @@ var mcpToolDescriptions = {
|
|
|
55350
55926
|
[mcpToolNames.appendTaskEvent]: "Append an operator note to a task's activity timeline. Also use it as the task-linked receipt path for material immediate work that has a parent task; summarize scope, outcome/no-result, evidence or artifacts, blockers, and next action in the note body.",
|
|
55351
55927
|
[mcpToolNames.createWikiPage]: "Create a markdown Wiki page.",
|
|
55352
55928
|
[mcpToolNames.deleteTaskDependency]: "Delete a blocked-by relationship.",
|
|
55353
|
-
[mcpToolNames.createTask]: "Create a new task in a project. Defaults to status='
|
|
55929
|
+
[mcpToolNames.createTask]: "Create a new task in a project. Defaults to status='todo' to authorize immediate autopilot work; pass status='backlog' only if explicitly parking the task for later. 'backlog' and 'todo' are the only creatable statuses. Pass parentTaskId when spawning a follow-up task to continue work from an existing task \u2014 its operator_seed_request is inherited automatically if you don't supply your own. Domain is required UNLESS parentTaskId is supplied (a follow-up inherits its parent's domain automatically). Domain selects which reviewer roster judges the task's spec. Valid values: code, seo, marketing, content. When creating tasks for work that produces a published web page, author the work as a [code_change] task and a [page] task (with domain: 'content') linked by blocked_by. Set autoDeploy according to the publish policy: 'prod' if only meaningful once live (robots.txt, sitemap), 'dev' to stop at preview (landing page, layout), or 'off' if the repo is not the operator's own.",
|
|
55354
55930
|
[mcpToolNames.getProject]: "Fetch a single project summary by slug.",
|
|
55355
55931
|
[mcpToolNames.getProjectSnapshot]: "Fetch the full project snapshot: GitHub binding, tasks, plans, and wiki pages.",
|
|
55356
55932
|
[mcpToolNames.getTask]: "Fetch a single task by project slug and task id.",
|
|
@@ -55591,20 +56167,18 @@ async function getAgentProviderForPanel(client, opts = {}) {
|
|
|
55591
56167
|
}
|
|
55592
56168
|
function createMrrlinTools(options) {
|
|
55593
56169
|
const client = createMrrlinClient(withAuth(options));
|
|
55594
|
-
let boundSlug = options.activeProjectSlug ?? (process.env.MRRLIN_PROJECT_SLUG?.trim() || void 0);
|
|
55595
|
-
let sessionResolved = false;
|
|
55596
56170
|
async function resolveBoundSlug() {
|
|
55597
|
-
if (
|
|
55598
|
-
|
|
56171
|
+
if (options.activeProjectSlug !== void 0) return options.activeProjectSlug;
|
|
56172
|
+
const envSlug = process.env.MRRLIN_PROJECT_SLUG?.trim();
|
|
56173
|
+
if (envSlug) return envSlug;
|
|
55599
56174
|
try {
|
|
55600
56175
|
const settings = await client.getOperatorSettings();
|
|
55601
56176
|
if (settings?.activeProjectSlug) {
|
|
55602
|
-
|
|
56177
|
+
return settings.activeProjectSlug;
|
|
55603
56178
|
}
|
|
55604
56179
|
} catch {
|
|
55605
56180
|
}
|
|
55606
|
-
|
|
55607
|
-
return boundSlug;
|
|
56181
|
+
return void 0;
|
|
55608
56182
|
}
|
|
55609
56183
|
async function checkTenantBoundary(targetSlug, resourceName) {
|
|
55610
56184
|
const active = await resolveBoundSlug();
|
|
@@ -55621,6 +56195,22 @@ function createMrrlinTools(options) {
|
|
|
55621
56195
|
);
|
|
55622
56196
|
}
|
|
55623
56197
|
}
|
|
56198
|
+
async function autoRetirePendingScopeQuestions(c, projectSlug, resolutionContext) {
|
|
56199
|
+
try {
|
|
56200
|
+
const pendingQuestions = await c.listInboxItems(projectSlug, { status: "pending", kind: "question" });
|
|
56201
|
+
for (const item of pendingQuestions) {
|
|
56202
|
+
if (!item.taskId) {
|
|
56203
|
+
await c.decideInboxItem(projectSlug, item.id, {
|
|
56204
|
+
decidedBy: "director",
|
|
56205
|
+
verdict: "acknowledged",
|
|
56206
|
+
answer: `Addressed in materialized plan/tasks (${resolutionContext})`
|
|
56207
|
+
}).catch(() => {
|
|
56208
|
+
});
|
|
56209
|
+
}
|
|
56210
|
+
}
|
|
56211
|
+
} catch {
|
|
56212
|
+
}
|
|
56213
|
+
}
|
|
55624
56214
|
return {
|
|
55625
56215
|
[mcpToolNames.archivePlan]: makeTool(
|
|
55626
56216
|
client,
|
|
@@ -55639,7 +56229,9 @@ function createMrrlinTools(options) {
|
|
|
55639
56229
|
"Unable to create plan.",
|
|
55640
56230
|
async (c, { projectSlug, ...input }) => {
|
|
55641
56231
|
await checkTenantBoundary(projectSlug, "plan");
|
|
55642
|
-
|
|
56232
|
+
const plan = await c.createPlan(projectSlug, input);
|
|
56233
|
+
await autoRetirePendingScopeQuestions(c, projectSlug, `Plan ${plan.id}: ${plan.name}`);
|
|
56234
|
+
return plan;
|
|
55643
56235
|
}
|
|
55644
56236
|
),
|
|
55645
56237
|
[mcpToolNames.createTaskDependency]: makeTool(
|
|
@@ -55734,6 +56326,13 @@ function createMrrlinTools(options) {
|
|
|
55734
56326
|
...input,
|
|
55735
56327
|
folder: input.folder?.trim().toLowerCase() === "specs" ? "Specs" : input.folder
|
|
55736
56328
|
};
|
|
56329
|
+
if (pageInput.folder === "Specs") {
|
|
56330
|
+
if (!parseAcceptanceCriteria(pageInput.markdownBody)) {
|
|
56331
|
+
throw new Error(
|
|
56332
|
+
"Error: Specifications in folder 'Specs' MUST include an '## Acceptance criteria' section with at least one top-level markdown checklist item ('- [ ] <condition>'). Please re-author the page body with verifiable checklist criteria."
|
|
56333
|
+
);
|
|
56334
|
+
}
|
|
56335
|
+
}
|
|
55737
56336
|
return await deliverBuiltIn(
|
|
55738
56337
|
c,
|
|
55739
56338
|
projectSlug,
|
|
@@ -55760,7 +56359,10 @@ function createMrrlinTools(options) {
|
|
|
55760
56359
|
"Unable to create task.",
|
|
55761
56360
|
async (c, { projectSlug, ...input }) => {
|
|
55762
56361
|
await checkTenantBoundary(projectSlug, "task");
|
|
55763
|
-
|
|
56362
|
+
const status = input.status ?? "todo";
|
|
56363
|
+
const task = await c.createTask(projectSlug, { ...input, status });
|
|
56364
|
+
await autoRetirePendingScopeQuestions(c, projectSlug, `Task ${task.id}: ${task.title}`);
|
|
56365
|
+
return task;
|
|
55764
56366
|
}
|
|
55765
56367
|
),
|
|
55766
56368
|
[mcpToolNames.getProject]: makeTool(
|
|
@@ -55979,6 +56581,20 @@ function createMrrlinTools(options) {
|
|
|
55979
56581
|
...patch,
|
|
55980
56582
|
...patch.folder !== void 0 ? { folder: patch.folder.trim().toLowerCase() === "specs" ? "Specs" : patch.folder } : {}
|
|
55981
56583
|
};
|
|
56584
|
+
let targetFolder = patchInput.folder;
|
|
56585
|
+
let targetBody = patch.markdownBody;
|
|
56586
|
+
if ((targetFolder === void 0 || targetBody === void 0) && (targetFolder === "Specs" || targetBody !== void 0)) {
|
|
56587
|
+
const existing = await c.getWikiPage(projectSlug, pageId);
|
|
56588
|
+
if (targetFolder === void 0) targetFolder = existing.folder;
|
|
56589
|
+
if (targetBody === void 0) targetBody = existing.markdownBody;
|
|
56590
|
+
}
|
|
56591
|
+
if (targetFolder === "Specs" && targetBody !== void 0) {
|
|
56592
|
+
if (!parseAcceptanceCriteria(targetBody)) {
|
|
56593
|
+
throw new Error(
|
|
56594
|
+
"Error: Specifications in folder 'Specs' MUST include an '## Acceptance criteria' section with at least one top-level markdown checklist item ('- [ ] <condition>'). Please re-author the page body with verifiable checklist criteria."
|
|
56595
|
+
);
|
|
56596
|
+
}
|
|
56597
|
+
}
|
|
55982
56598
|
return await deliverBuiltIn(
|
|
55983
56599
|
c,
|
|
55984
56600
|
projectSlug,
|
|
@@ -58077,7 +58693,719 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
|
|
|
58077
58693
|
}
|
|
58078
58694
|
|
|
58079
58695
|
// src/_generated/version.ts
|
|
58080
|
-
var PKG_VERSION = "0.4.
|
|
58696
|
+
var PKG_VERSION = "0.4.97";
|
|
58697
|
+
|
|
58698
|
+
// src/director-closure.ts
|
|
58699
|
+
var CLOSURE_SNAPSHOT_TIMEOUT_MS = 2e3;
|
|
58700
|
+
var CLOSURE_MAX_SNAPSHOT_PAGES = 500;
|
|
58701
|
+
var CLOSURE_MAX_SNAPSHOT_TASKS = 2e3;
|
|
58702
|
+
var CLOSURE_MAX_SNAPSHOT_INBOX = 500;
|
|
58703
|
+
var CLOSURE_MIN_PROSE_CHARS = 25;
|
|
58704
|
+
var CLOSURE_MIN_PROSE_WORDS = 4;
|
|
58705
|
+
var ID_RE = /\b(?:GT|WK)-\d+\b/g;
|
|
58706
|
+
function diffSnapshots(before, after) {
|
|
58707
|
+
const receipts = [];
|
|
58708
|
+
const diffMap = (kind, prev, next) => {
|
|
58709
|
+
for (const [id, entry] of next) {
|
|
58710
|
+
const old = prev.get(id);
|
|
58711
|
+
if (!old) {
|
|
58712
|
+
receipts.push({ kind, action: "created", id, title: entry.title });
|
|
58713
|
+
} else if (entry.updatedAt !== null && entry.updatedAt !== old.updatedAt) {
|
|
58714
|
+
receipts.push({ kind, action: "updated", id, title: entry.title });
|
|
58715
|
+
}
|
|
58716
|
+
}
|
|
58717
|
+
};
|
|
58718
|
+
diffMap("task", before.tasks, after.tasks);
|
|
58719
|
+
if (before.pages && after.pages) diffMap("page", before.pages, after.pages);
|
|
58720
|
+
if (before.inbox && after.inbox) {
|
|
58721
|
+
for (const id of after.inbox) {
|
|
58722
|
+
if (!before.inbox.has(id)) receipts.push({ kind: "inbox", action: "created", id, title: "" });
|
|
58723
|
+
}
|
|
58724
|
+
}
|
|
58725
|
+
return receipts;
|
|
58726
|
+
}
|
|
58727
|
+
function finalAssistantText(events) {
|
|
58728
|
+
let lastToolResult = -1;
|
|
58729
|
+
events.forEach((event, index) => {
|
|
58730
|
+
if (event.type === "tool_result") lastToolResult = index;
|
|
58731
|
+
});
|
|
58732
|
+
return events.slice(lastToolResult + 1).filter((event) => event.type === "assistant" && typeof event.content === "string").map((event) => event.content).join("");
|
|
58733
|
+
}
|
|
58734
|
+
var MUTATION_TOOLS = /* @__PURE__ */ new Set([
|
|
58735
|
+
"create_task",
|
|
58736
|
+
"update_task",
|
|
58737
|
+
"create_wiki_page",
|
|
58738
|
+
"update_wiki_page",
|
|
58739
|
+
"create_inbox_item"
|
|
58740
|
+
]);
|
|
58741
|
+
function extractStructuredResultId(content) {
|
|
58742
|
+
if (!content) return null;
|
|
58743
|
+
let parsed = content;
|
|
58744
|
+
if (typeof content === "string") {
|
|
58745
|
+
try {
|
|
58746
|
+
parsed = JSON.parse(content);
|
|
58747
|
+
} catch {
|
|
58748
|
+
const trimmed = content.trim();
|
|
58749
|
+
if (/^(?:GT|WK)-\d+$/.test(trimmed) || /^II-[0-9a-zA-Z-]+$/.test(trimmed)) {
|
|
58750
|
+
return trimmed;
|
|
58751
|
+
}
|
|
58752
|
+
return null;
|
|
58753
|
+
}
|
|
58754
|
+
}
|
|
58755
|
+
if (Array.isArray(parsed)) {
|
|
58756
|
+
for (const item of parsed) {
|
|
58757
|
+
const id = extractStructuredResultId(item);
|
|
58758
|
+
if (id) return id;
|
|
58759
|
+
}
|
|
58760
|
+
return null;
|
|
58761
|
+
}
|
|
58762
|
+
if (parsed && typeof parsed === "object") {
|
|
58763
|
+
const obj = parsed;
|
|
58764
|
+
if (typeof obj.text === "string") {
|
|
58765
|
+
const id = extractStructuredResultId(obj.text);
|
|
58766
|
+
if (id) return id;
|
|
58767
|
+
}
|
|
58768
|
+
if (obj.structuredContent && typeof obj.structuredContent === "object") {
|
|
58769
|
+
const sc = obj.structuredContent;
|
|
58770
|
+
if (sc.data && typeof sc.data === "object" && typeof sc.data.id === "string") {
|
|
58771
|
+
return sc.data.id;
|
|
58772
|
+
}
|
|
58773
|
+
if (typeof sc.id === "string") return sc.id;
|
|
58774
|
+
}
|
|
58775
|
+
if (obj.data && typeof obj.data === "object" && typeof obj.data.id === "string") {
|
|
58776
|
+
return obj.data.id;
|
|
58777
|
+
}
|
|
58778
|
+
if (typeof obj.id === "string") {
|
|
58779
|
+
return obj.id;
|
|
58780
|
+
}
|
|
58781
|
+
if (Array.isArray(obj.content)) {
|
|
58782
|
+
for (const item of obj.content) {
|
|
58783
|
+
const id = extractStructuredResultId(item);
|
|
58784
|
+
if (id) return id;
|
|
58785
|
+
}
|
|
58786
|
+
}
|
|
58787
|
+
}
|
|
58788
|
+
return null;
|
|
58789
|
+
}
|
|
58790
|
+
function isErrorResult(event) {
|
|
58791
|
+
if (event.is_error === true || event.is_error === "true") return true;
|
|
58792
|
+
if (event.tool && typeof event.tool === "object") {
|
|
58793
|
+
if (typeof event.tool.exitCode === "number" && event.tool.exitCode !== 0) return true;
|
|
58794
|
+
if (typeof event.tool.status === "string" && event.tool.status === "error") return true;
|
|
58795
|
+
}
|
|
58796
|
+
const checkError = (val) => {
|
|
58797
|
+
if (!val) return false;
|
|
58798
|
+
if (Array.isArray(val)) {
|
|
58799
|
+
return val.some(checkError);
|
|
58800
|
+
}
|
|
58801
|
+
if (typeof val === "object") {
|
|
58802
|
+
const obj = val;
|
|
58803
|
+
if (obj.isError === true || obj.isError === "true" || obj.error !== void 0) return true;
|
|
58804
|
+
if (Array.isArray(obj.content) && obj.content.some(checkError)) return true;
|
|
58805
|
+
if (typeof obj.text === "string") {
|
|
58806
|
+
try {
|
|
58807
|
+
return checkError(JSON.parse(obj.text));
|
|
58808
|
+
} catch {
|
|
58809
|
+
}
|
|
58810
|
+
}
|
|
58811
|
+
} else if (typeof val === "string") {
|
|
58812
|
+
try {
|
|
58813
|
+
return checkError(JSON.parse(val));
|
|
58814
|
+
} catch {
|
|
58815
|
+
}
|
|
58816
|
+
}
|
|
58817
|
+
return false;
|
|
58818
|
+
};
|
|
58819
|
+
return checkError(event.content);
|
|
58820
|
+
}
|
|
58821
|
+
function turnReferences(events) {
|
|
58822
|
+
const mutatedIds = /* @__PURE__ */ new Set();
|
|
58823
|
+
const mutatedInboxIds = /* @__PURE__ */ new Set();
|
|
58824
|
+
const textIds = /* @__PURE__ */ new Set();
|
|
58825
|
+
let hasToolEvents = false;
|
|
58826
|
+
const keyedPending = /* @__PURE__ */ new Map();
|
|
58827
|
+
const unkeyedCalls = [];
|
|
58828
|
+
for (const event of events) {
|
|
58829
|
+
if (event.type === "tool_use") {
|
|
58830
|
+
hasToolEvents = true;
|
|
58831
|
+
const rawName = event.tool?.name ?? event.name ?? "";
|
|
58832
|
+
const toolName = rawName.replace(/^.*__/, "");
|
|
58833
|
+
const isMutation = MUTATION_TOOLS.has(toolName);
|
|
58834
|
+
const inputObj = event.tool?.input && typeof event.tool.input === "object" ? event.tool.input : event.input && typeof event.input === "object" ? event.input : {};
|
|
58835
|
+
const rawTargetId = typeof inputObj.taskId === "string" ? inputObj.taskId : typeof inputObj.pageId === "string" ? inputObj.pageId : typeof inputObj.id === "string" ? inputObj.id : null;
|
|
58836
|
+
let targetId = null;
|
|
58837
|
+
if (rawTargetId) {
|
|
58838
|
+
if ((toolName === "create_task" || toolName === "update_task") && /^GT-\d+$/.test(rawTargetId)) {
|
|
58839
|
+
targetId = rawTargetId;
|
|
58840
|
+
} else if ((toolName === "create_wiki_page" || toolName === "update_wiki_page") && /^WK-\d+$/.test(rawTargetId)) {
|
|
58841
|
+
targetId = rawTargetId;
|
|
58842
|
+
}
|
|
58843
|
+
}
|
|
58844
|
+
const callId = event.tool?.id ?? event.tool_use_id ?? event.id;
|
|
58845
|
+
if (callId && typeof callId === "string") {
|
|
58846
|
+
if (isMutation) {
|
|
58847
|
+
keyedPending.set(callId, { toolName, targetId });
|
|
58848
|
+
}
|
|
58849
|
+
} else {
|
|
58850
|
+
if (isMutation) {
|
|
58851
|
+
unkeyedCalls.push({ isMutation: true, toolName, targetId });
|
|
58852
|
+
} else {
|
|
58853
|
+
unkeyedCalls.push({ isMutation: false });
|
|
58854
|
+
}
|
|
58855
|
+
}
|
|
58856
|
+
} else if (event.type === "tool_result") {
|
|
58857
|
+
hasToolEvents = true;
|
|
58858
|
+
const callId = event.tool?.id ?? event.tool_use_id ?? event.id;
|
|
58859
|
+
let mutation;
|
|
58860
|
+
if (callId && typeof callId === "string") {
|
|
58861
|
+
mutation = keyedPending.get(callId);
|
|
58862
|
+
keyedPending.delete(callId);
|
|
58863
|
+
} else if (unkeyedCalls.length > 0) {
|
|
58864
|
+
const unkeyed = unkeyedCalls.shift();
|
|
58865
|
+
if (unkeyed && unkeyed.isMutation) {
|
|
58866
|
+
mutation = { toolName: unkeyed.toolName, targetId: unkeyed.targetId };
|
|
58867
|
+
}
|
|
58868
|
+
}
|
|
58869
|
+
if (mutation && !isErrorResult(event)) {
|
|
58870
|
+
const returnedId = extractStructuredResultId(event.content);
|
|
58871
|
+
if (mutation.toolName === "create_inbox_item") {
|
|
58872
|
+
if (returnedId && /^II-[0-9a-zA-Z-]+$/.test(returnedId)) {
|
|
58873
|
+
mutatedInboxIds.add(returnedId);
|
|
58874
|
+
}
|
|
58875
|
+
} else {
|
|
58876
|
+
if (mutation.targetId) {
|
|
58877
|
+
mutatedIds.add(mutation.targetId);
|
|
58878
|
+
}
|
|
58879
|
+
if (returnedId) {
|
|
58880
|
+
if ((mutation.toolName === "create_task" || mutation.toolName === "update_task") && /^GT-\d+$/.test(returnedId)) {
|
|
58881
|
+
mutatedIds.add(returnedId);
|
|
58882
|
+
} else if ((mutation.toolName === "create_wiki_page" || mutation.toolName === "update_wiki_page") && /^WK-\d+$/.test(returnedId)) {
|
|
58883
|
+
mutatedIds.add(returnedId);
|
|
58884
|
+
}
|
|
58885
|
+
}
|
|
58886
|
+
}
|
|
58887
|
+
}
|
|
58888
|
+
} else if (event.type === "assistant" && typeof event.content === "string") {
|
|
58889
|
+
for (const id of event.content.match(ID_RE) ?? []) textIds.add(id);
|
|
58890
|
+
}
|
|
58891
|
+
}
|
|
58892
|
+
return { mutatedIds, mutatedInboxIds, textIds, hasToolEvents };
|
|
58893
|
+
}
|
|
58894
|
+
function attributeReceipts(receipts, refs) {
|
|
58895
|
+
if (refs.hasToolEvents) {
|
|
58896
|
+
const board2 = receipts.filter((r) => r.kind !== "inbox" && refs.mutatedIds.has(r.id));
|
|
58897
|
+
const inbox = receipts.filter((r) => r.kind === "inbox" && refs.mutatedInboxIds.has(r.id));
|
|
58898
|
+
return [...board2, ...inbox];
|
|
58899
|
+
}
|
|
58900
|
+
const board = receipts.filter((r) => r.kind !== "inbox" && refs.textIds.has(r.id));
|
|
58901
|
+
return [...board];
|
|
58902
|
+
}
|
|
58903
|
+
function closurePresent(finalText, receipts) {
|
|
58904
|
+
const mentioned = new Set(finalText.match(ID_RE) ?? []);
|
|
58905
|
+
if (!receipts.every((receipt) => receipt.kind === "inbox" || mentioned.has(receipt.id))) return false;
|
|
58906
|
+
if (receipts.some((receipt) => receipt.kind === "inbox") && !/(?<![\p{L}\p{N}])(?:inbox|інбокс|вхідні)(?![\p{L}\p{N}])/iu.test(finalText)) return false;
|
|
58907
|
+
const nonIdText = finalText.replace(ID_RE, "");
|
|
58908
|
+
const letters = nonIdText.match(new RegExp("\\p{L}", "gu")) ?? [];
|
|
58909
|
+
if (letters.length < CLOSURE_MIN_PROSE_CHARS) return false;
|
|
58910
|
+
const words = nonIdText.match(new RegExp("\\p{L}{2,}", "gu")) ?? [];
|
|
58911
|
+
const uniqueWords = new Set(words.map((w) => w.toLowerCase()));
|
|
58912
|
+
if (uniqueWords.size < CLOSURE_MIN_PROSE_WORDS) return false;
|
|
58913
|
+
return true;
|
|
58914
|
+
}
|
|
58915
|
+
function describe3(receipt, lang = "en") {
|
|
58916
|
+
const title = receipt.title.trim();
|
|
58917
|
+
const noun = lang === "uk" ? receipt.kind === "task" ? "\u0437\u0430\u0434\u0430\u0447\u0430" : "\u0441\u043F\u0435\u0446\u0438\u0444\u0456\u043A\u0430\u0446\u0456\u044F" : receipt.kind === "task" ? "task" : "page";
|
|
58918
|
+
return title ? `${noun} ${receipt.id} "${title}"` : `${noun} ${receipt.id}`;
|
|
58919
|
+
}
|
|
58920
|
+
function renderClosure(receipts, lang = "en") {
|
|
58921
|
+
const board = receipts.filter((r) => r.kind !== "inbox");
|
|
58922
|
+
const created = board.filter((r) => r.action === "created");
|
|
58923
|
+
const updated = board.filter((r) => r.action === "updated");
|
|
58924
|
+
const inboxCount = receipts.filter((r) => r.kind === "inbox").length;
|
|
58925
|
+
const parts = [];
|
|
58926
|
+
if (lang === "uk") {
|
|
58927
|
+
if (created.length) parts.push(`\u041F\u0456\u0434 \u0447\u0430\u0441 \u0446\u044C\u043E\u0433\u043E \u0445\u043E\u0434\u0443 \u0432 \u043F\u0440\u043E\u0454\u043A\u0442\u0456 \u0434\u043E\u0434\u0430\u043D\u043E: ${created.map((r) => describe3(r, "uk")).join("; ")}.`);
|
|
58928
|
+
if (updated.length) parts.push(`\u041E\u043D\u043E\u0432\u043B\u0435\u043D\u043E: ${updated.map((r) => describe3(r, "uk")).join("; ")}.`);
|
|
58929
|
+
if (inboxCount) parts.push(`\u041D\u043E\u0432\u0456 \u0435\u043B\u0435\u043C\u0435\u043D\u0442\u0438 Inbox: ${inboxCount}.`);
|
|
58930
|
+
if (created.some((r) => r.kind === "task")) parts.push("\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u043A\u0440\u043E\u043A: \u0437\u0430\u0434\u0430\u0447\u0430 \u043D\u0430 \u0434\u043E\u0448\u0446\u0456 \u0437\u0430\u0434\u0430\u0447.");
|
|
58931
|
+
return `
|
|
58932
|
+
|
|
58933
|
+
**\u041F\u0456\u0434\u0441\u0443\u043C\u043E\u043A:** ${parts.join(" ")}`;
|
|
58934
|
+
}
|
|
58935
|
+
if (created.length) parts.push(`During this turn the project gained: ${created.map((r) => describe3(r, "en")).join("; ")}.`);
|
|
58936
|
+
if (updated.length) parts.push(`Updated: ${updated.map((r) => describe3(r, "en")).join("; ")}.`);
|
|
58937
|
+
if (inboxCount) parts.push(`New Inbox items: ${inboxCount}.`);
|
|
58938
|
+
if (created.some((r) => r.kind === "task")) parts.push("Next: the task is on the task board.");
|
|
58939
|
+
return `
|
|
58940
|
+
|
|
58941
|
+
**Summary:** ${parts.join(" ")}`;
|
|
58942
|
+
}
|
|
58943
|
+
function closureFor(input) {
|
|
58944
|
+
const receipts = attributeReceipts(diffSnapshots(input.before, input.after), turnReferences(input.events));
|
|
58945
|
+
if (receipts.length === 0) return null;
|
|
58946
|
+
if (closurePresent(finalAssistantText(input.events), receipts)) return null;
|
|
58947
|
+
const isUk = Boolean(input.operatorText && /[Ѐ-ӿ]/.test(input.operatorText));
|
|
58948
|
+
return renderClosure(receipts, isUk ? "uk" : "en");
|
|
58949
|
+
}
|
|
58950
|
+
|
|
58951
|
+
// src/clarification-ledger.ts
|
|
58952
|
+
var LEDGER_MAX_ITEMS = 20;
|
|
58953
|
+
var LEDGER_MAX_QUESTIONS_PER_TURN = 5;
|
|
58954
|
+
var LEDGER_MAX_EXTRACTED_QUESTIONS = 50;
|
|
58955
|
+
var LEDGER_REASK_JACCARD = 0.6;
|
|
58956
|
+
var LEDGER_MAX_QUESTION_CHARS = 300;
|
|
58957
|
+
function emptyLedger() {
|
|
58958
|
+
return { turn: 0, items: [] };
|
|
58959
|
+
}
|
|
58960
|
+
var NEGATION_RE = /\b(?:no\s+default|not\s+(?:\w+\s+)*(?:answered|assumed|resolved|decided|selected|chosen)|unanswered|unresolved|undecided|remains?\s+(?:\w+\s+)*(?:open|unanswered|unresolved|undecided)|still\s+open|without\s+(?:a\s+)?default|no\s+assumption|has\s+not\s+(?:been\s+)?(?:answered|assumed|resolved|decided|selected|chosen)|is\s+not\s+(?:answered|assumed|resolved|decided|selected|chosen)|was\s+not\s+(?:answered|assumed|resolved|decided|selected|chosen)|cannot\s+be\s+(?:answered|assumed|resolved|decided)|(?:default|assumed|assuming|assumption|decided|resolved|answered|choice|decision|option|selection)\s+(?:is\s+|was\s+|are\s+|were\s+|will\s+be\s+|to\s+|:\s*|\s*=\s*)?(?:not|never|no))\b|не\s+(?:\w+\s+)*(?:вирішено|відповіли|прийнято|обрано|визначено|узгоджено|припускаю)|без\s+(?:відповіді|замовчування)|залишається\s+відкритим|невирішено|(?:дефолт|за\s+замовчуванням|вирішено|відповіли|прийнято|припускаємо|припущення|вибір|рішення|варіант)\s*(?::\s*)?(?:не|ні|ніколи)/iu;
|
|
58961
|
+
var DEFERRED_DECISION_RE = /\b(?:will\s+(?:be\s+)?(?:decided|determined|resolved|chosen|selected|answered|settled|clarified|discussed|picked)|to\s+be\s+(?:decided|determined|resolved|chosen|selected|answered|settled|clarified|discussed|picked)|pending\s+(?:decision|resolution|clarification|answer|choice)|tbd|to\s+be\s+determined|deferred|postponed|will\s+(?:decide|determine|choose|resolve|answer)|decide\s+later|determine\s+later|deciding\s+later|at\s+a\s+later\s+time|tomorrow|next\s+(?:sprint|week|turn)|depends\s+on|contingent\s+on|conditional\s+on|subject\s+to|if\s+and\s+when)\b|буде\s+(?:вирішено|визначено|обрано|узгоджено|встановлено|з'ясовано)|вирішимо\s+пізніше|визначимо\s+пізніше|відкладено|залежить\s+від|потребує\s+уточнення|ще\s+не\s+(?:вирішено|визначено|обрано)/iu;
|
|
58962
|
+
var RESOLUTION_MARKER_RE = /\b(?:answered|assumed|assuming|assumption|blocker|default|resolved|decided)\b|відпові|прийнят|приймаю|беру|за замовчуванням|блокер|припуска|вирішено|узгоджено/iu;
|
|
58963
|
+
var LEDGER_STOP_WORDS = /* @__PURE__ */ new Set([
|
|
58964
|
+
"which",
|
|
58965
|
+
"what",
|
|
58966
|
+
"where",
|
|
58967
|
+
"when",
|
|
58968
|
+
"who",
|
|
58969
|
+
"whom",
|
|
58970
|
+
"how",
|
|
58971
|
+
"why",
|
|
58972
|
+
"should",
|
|
58973
|
+
"would",
|
|
58974
|
+
"could",
|
|
58975
|
+
"will",
|
|
58976
|
+
"can",
|
|
58977
|
+
"may",
|
|
58978
|
+
"might",
|
|
58979
|
+
"must",
|
|
58980
|
+
"need",
|
|
58981
|
+
"needed",
|
|
58982
|
+
"use",
|
|
58983
|
+
"used",
|
|
58984
|
+
"have",
|
|
58985
|
+
"been",
|
|
58986
|
+
"there",
|
|
58987
|
+
"this",
|
|
58988
|
+
"that",
|
|
58989
|
+
"question",
|
|
58990
|
+
"questions",
|
|
58991
|
+
"item",
|
|
58992
|
+
"items",
|
|
58993
|
+
"task",
|
|
58994
|
+
"tasks",
|
|
58995
|
+
"about",
|
|
58996
|
+
"\u044F\u043A\u0456",
|
|
58997
|
+
"\u044F\u043A\u0438\u0439",
|
|
58998
|
+
"\u044F\u043A\u0430",
|
|
58999
|
+
"\u044F\u043A\u0435",
|
|
59000
|
+
"\u044F\u043A\u0438\u0445",
|
|
59001
|
+
"\u044F\u043A\u043E\u043C\u0443",
|
|
59002
|
+
"\u044F\u043A\u0456\u0439",
|
|
59003
|
+
"\u0445\u0442\u043E",
|
|
59004
|
+
"\u043A\u043E\u0433\u043E",
|
|
59005
|
+
"\u043A\u043E\u043C\u0443",
|
|
59006
|
+
"\u043A\u0438\u043C",
|
|
59007
|
+
"\u0449\u043E",
|
|
59008
|
+
"\u0447\u043E\u0433\u043E",
|
|
59009
|
+
"\u0447\u043E\u043C\u0443",
|
|
59010
|
+
"\u0447\u0438\u043C",
|
|
59011
|
+
"\u0434\u0435",
|
|
59012
|
+
"\u043A\u0443\u0434\u0438",
|
|
59013
|
+
"\u0437\u0432\u0456\u0434\u043A\u0438",
|
|
59014
|
+
"\u043A\u043E\u043B\u0438",
|
|
59015
|
+
"\u0447\u043E\u043C\u0443",
|
|
59016
|
+
"\u043D\u0430\u0432\u0456\u0449\u043E",
|
|
59017
|
+
"\u044F\u043A",
|
|
59018
|
+
"\u0447\u0438",
|
|
59019
|
+
"\u0430\u0431\u043E",
|
|
59020
|
+
"\u0434\u043B\u044F",
|
|
59021
|
+
"\u043F\u0440\u043E",
|
|
59022
|
+
"\u043F\u0440\u0438",
|
|
59023
|
+
"\u043D\u0430\u0434",
|
|
59024
|
+
"\u043F\u0456\u0434",
|
|
59025
|
+
"\u0432\u0456\u0434",
|
|
59026
|
+
"\u0434\u043E",
|
|
59027
|
+
"\u043F\u0438\u0442\u0430\u043D\u043D\u044F",
|
|
59028
|
+
"\u0437\u0430\u0434\u0430\u0447\u0430",
|
|
59029
|
+
"\u0437\u0430\u0434\u0430\u0447\u0456",
|
|
59030
|
+
"\u043F\u043E\u0442\u0440\u0456\u0431\u043D\u043E",
|
|
59031
|
+
"\u043F\u043E\u0442\u0440\u0456\u0431\u043D\u0456",
|
|
59032
|
+
"\u0442\u0440\u0435\u0431\u0430"
|
|
59033
|
+
]);
|
|
59034
|
+
function tokens(text) {
|
|
59035
|
+
return new Set(
|
|
59036
|
+
text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 3)
|
|
59037
|
+
);
|
|
59038
|
+
}
|
|
59039
|
+
function tokenMatches(a, b) {
|
|
59040
|
+
if (a === b) return true;
|
|
59041
|
+
if (a.length >= 4 && b.length >= 4 && (a.startsWith(b.slice(0, 4)) || b.startsWith(a.slice(0, 4)))) {
|
|
59042
|
+
return true;
|
|
59043
|
+
}
|
|
59044
|
+
return false;
|
|
59045
|
+
}
|
|
59046
|
+
function isSameQuestion(a, b) {
|
|
59047
|
+
const ta = tokens(a);
|
|
59048
|
+
const tb = tokens(b);
|
|
59049
|
+
if (ta.size === 0 || tb.size === 0) return false;
|
|
59050
|
+
let shared = 0;
|
|
59051
|
+
for (const token of ta) if (tb.has(token)) shared += 1;
|
|
59052
|
+
return shared / (ta.size + tb.size - shared) >= LEDGER_REASK_JACCARD;
|
|
59053
|
+
}
|
|
59054
|
+
function isAffirmativeResolutionForQuestion(question, text, otherQuestions) {
|
|
59055
|
+
if (!text || !RESOLUTION_MARKER_RE.test(text)) return false;
|
|
59056
|
+
if (NEGATION_RE.test(text)) return false;
|
|
59057
|
+
if (DEFERRED_DECISION_RE.test(text)) return false;
|
|
59058
|
+
const substantiveQuestionToks = new Set(
|
|
59059
|
+
Array.from(tokens(question)).filter((t) => !LEDGER_STOP_WORDS.has(t) && t.length >= 3)
|
|
59060
|
+
);
|
|
59061
|
+
if (otherQuestions && otherQuestions.length > 0) {
|
|
59062
|
+
const textToks2 = tokens(text);
|
|
59063
|
+
for (const otherQ of otherQuestions) {
|
|
59064
|
+
if (otherQ === question || isSameQuestion(otherQ, question)) continue;
|
|
59065
|
+
const otherToks = new Set(
|
|
59066
|
+
Array.from(tokens(otherQ)).filter((t) => !LEDGER_STOP_WORDS.has(t) && t.length >= 3)
|
|
59067
|
+
);
|
|
59068
|
+
let hasSharedDomain = false;
|
|
59069
|
+
for (const t of substantiveQuestionToks) {
|
|
59070
|
+
for (const ot of otherToks) {
|
|
59071
|
+
if (tokenMatches(t, ot)) {
|
|
59072
|
+
hasSharedDomain = true;
|
|
59073
|
+
break;
|
|
59074
|
+
}
|
|
59075
|
+
}
|
|
59076
|
+
if (hasSharedDomain) break;
|
|
59077
|
+
}
|
|
59078
|
+
if (hasSharedDomain) {
|
|
59079
|
+
const otherDistinguishingToks = [];
|
|
59080
|
+
for (const ot of otherToks) {
|
|
59081
|
+
let inThis = false;
|
|
59082
|
+
for (const t of substantiveQuestionToks) {
|
|
59083
|
+
if (tokenMatches(t, ot)) {
|
|
59084
|
+
inThis = true;
|
|
59085
|
+
break;
|
|
59086
|
+
}
|
|
59087
|
+
}
|
|
59088
|
+
if (!inThis) otherDistinguishingToks.push(ot);
|
|
59089
|
+
}
|
|
59090
|
+
const thisDistinguishingToks = [];
|
|
59091
|
+
for (const t of substantiveQuestionToks) {
|
|
59092
|
+
let inOther = false;
|
|
59093
|
+
for (const ot of otherToks) {
|
|
59094
|
+
if (tokenMatches(t, ot)) {
|
|
59095
|
+
inOther = true;
|
|
59096
|
+
break;
|
|
59097
|
+
}
|
|
59098
|
+
}
|
|
59099
|
+
if (!inOther) thisDistinguishingToks.push(t);
|
|
59100
|
+
}
|
|
59101
|
+
if (otherDistinguishingToks.length > 0 && thisDistinguishingToks.length > 0) {
|
|
59102
|
+
let textMatchesOtherDistinguishing = false;
|
|
59103
|
+
for (const ot of otherDistinguishingToks) {
|
|
59104
|
+
for (const lt of textToks2) {
|
|
59105
|
+
if (tokenMatches(ot, lt)) {
|
|
59106
|
+
textMatchesOtherDistinguishing = true;
|
|
59107
|
+
break;
|
|
59108
|
+
}
|
|
59109
|
+
}
|
|
59110
|
+
if (textMatchesOtherDistinguishing) break;
|
|
59111
|
+
}
|
|
59112
|
+
let textMatchesThisDistinguishing = false;
|
|
59113
|
+
for (const t of thisDistinguishingToks) {
|
|
59114
|
+
for (const lt of textToks2) {
|
|
59115
|
+
if (tokenMatches(t, lt)) {
|
|
59116
|
+
textMatchesThisDistinguishing = true;
|
|
59117
|
+
break;
|
|
59118
|
+
}
|
|
59119
|
+
}
|
|
59120
|
+
if (textMatchesThisDistinguishing) break;
|
|
59121
|
+
}
|
|
59122
|
+
if (textMatchesOtherDistinguishing && !textMatchesThisDistinguishing) {
|
|
59123
|
+
return false;
|
|
59124
|
+
}
|
|
59125
|
+
}
|
|
59126
|
+
}
|
|
59127
|
+
}
|
|
59128
|
+
}
|
|
59129
|
+
if (!isTopicConsistentWithQuestion(question, text)) {
|
|
59130
|
+
return false;
|
|
59131
|
+
}
|
|
59132
|
+
const textToks = tokens(text);
|
|
59133
|
+
let shared = 0;
|
|
59134
|
+
for (const it of substantiveQuestionToks) {
|
|
59135
|
+
for (const lt of textToks) {
|
|
59136
|
+
if (tokenMatches(it, lt)) {
|
|
59137
|
+
shared += 1;
|
|
59138
|
+
break;
|
|
59139
|
+
}
|
|
59140
|
+
}
|
|
59141
|
+
}
|
|
59142
|
+
return shared >= 1 && (shared >= 2 || substantiveQuestionToks.size <= 2 || shared / substantiveQuestionToks.size >= 0.25);
|
|
59143
|
+
}
|
|
59144
|
+
function isTopicConsistentWithQuestion(question, text) {
|
|
59145
|
+
const substantiveQuestionToks = Array.from(tokens(question)).filter((t) => !LEDGER_STOP_WORDS.has(t) && t.length >= 3);
|
|
59146
|
+
const genericQualifiers = /* @__PURE__ */ new Set([
|
|
59147
|
+
"the",
|
|
59148
|
+
"a",
|
|
59149
|
+
"an",
|
|
59150
|
+
"this",
|
|
59151
|
+
"that",
|
|
59152
|
+
"our",
|
|
59153
|
+
"its",
|
|
59154
|
+
"their",
|
|
59155
|
+
"your",
|
|
59156
|
+
"\u0446\u0435\u0439",
|
|
59157
|
+
"\u0446\u044F",
|
|
59158
|
+
"\u0446\u0435",
|
|
59159
|
+
"\u043D\u0430\u0448",
|
|
59160
|
+
"\u043D\u0430\u0448\u0430",
|
|
59161
|
+
"\u0439\u043E\u0433\u043E",
|
|
59162
|
+
"\u0457\u0457",
|
|
59163
|
+
"is",
|
|
59164
|
+
"as",
|
|
59165
|
+
"by",
|
|
59166
|
+
"take",
|
|
59167
|
+
"taking",
|
|
59168
|
+
"taken",
|
|
59169
|
+
"set",
|
|
59170
|
+
"use",
|
|
59171
|
+
"default",
|
|
59172
|
+
"assumed",
|
|
59173
|
+
"assuming",
|
|
59174
|
+
"decision",
|
|
59175
|
+
"outcome",
|
|
59176
|
+
"choice",
|
|
59177
|
+
"\u0434\u0435\u0444\u043E\u043B\u0442",
|
|
59178
|
+
"\u0437\u0430\u043C\u043E\u0432\u0447\u0443\u0432\u0430\u043D\u043D\u044F",
|
|
59179
|
+
"\u0432\u0438\u0431\u0456\u0440",
|
|
59180
|
+
"\u0431\u0435\u0440\u0443",
|
|
59181
|
+
"\u0432\u0432\u0430\u0436\u0430\u044E",
|
|
59182
|
+
"\u043F\u0440\u0438\u0439\u043D\u044F\u0442\u043E",
|
|
59183
|
+
"\u043E\u0431\u0440\u0430\u043D\u043E"
|
|
59184
|
+
]);
|
|
59185
|
+
const beforeTopicMatch = text.match(/([a-zA-Z\u0400-\u04FF_-]+)\s+(?:default|дефолт|за\s+замовчуванням|is|are|was|were|will\s+be|є|це|буде|будуть|обрано|було|=|:|—|-)\b/i);
|
|
59186
|
+
if (beforeTopicMatch && beforeTopicMatch[1]) {
|
|
59187
|
+
const qualifier = beforeTopicMatch[1].toLowerCase();
|
|
59188
|
+
if (!genericQualifiers.has(qualifier)) {
|
|
59189
|
+
let qualifierMatches = false;
|
|
59190
|
+
for (const it of substantiveQuestionToks) {
|
|
59191
|
+
if (tokenMatches(it, qualifier)) {
|
|
59192
|
+
qualifierMatches = true;
|
|
59193
|
+
break;
|
|
59194
|
+
}
|
|
59195
|
+
}
|
|
59196
|
+
if (!qualifierMatches) {
|
|
59197
|
+
return false;
|
|
59198
|
+
}
|
|
59199
|
+
}
|
|
59200
|
+
}
|
|
59201
|
+
const resolutionKeywords = /* @__PURE__ */ new Set([
|
|
59202
|
+
"answered",
|
|
59203
|
+
"assumed",
|
|
59204
|
+
"assuming",
|
|
59205
|
+
"default",
|
|
59206
|
+
"blocker",
|
|
59207
|
+
"decision",
|
|
59208
|
+
"outcome",
|
|
59209
|
+
"choice",
|
|
59210
|
+
"\u0432\u0456\u0434\u043F\u043E\u0432\u0456",
|
|
59211
|
+
"\u043F\u0440\u0438\u0439\u043D\u044F\u0442",
|
|
59212
|
+
"\u043F\u0440\u0438\u0439\u043C\u0430\u044E",
|
|
59213
|
+
"\u0431\u0435\u0440\u0443",
|
|
59214
|
+
"\u0437\u0430\u043C\u043E\u0432\u0447\u0443\u0432\u0430\u043D\u043D\u044F\u043C",
|
|
59215
|
+
"\u0431\u043B\u043E\u043A\u0435\u0440",
|
|
59216
|
+
"\u043F\u0440\u0438\u043F\u0443\u0441\u043A\u0430",
|
|
59217
|
+
"\u0432\u0438\u0440\u0456\u0448\u0435\u043D\u043E",
|
|
59218
|
+
"\u0443\u0437\u0433\u043E\u0434\u0436\u0435\u043D\u043E"
|
|
59219
|
+
]);
|
|
59220
|
+
const textToks = Array.from(tokens(text)).filter((t) => !LEDGER_STOP_WORDS.has(t) && t.length >= 3);
|
|
59221
|
+
const contentToks = textToks.filter((t) => !resolutionKeywords.has(t) && !genericQualifiers.has(t));
|
|
59222
|
+
for (const ct of contentToks) {
|
|
59223
|
+
const topicMatch = new RegExp(`\\b${ct}\\s+(?:default|\u0434\u0435\u0444\u043E\u043B\u0442|is|are|was|were|will\\s+be|\u0454|\u0446\u0435|\u0431\u0443\u0434\u0435|\u0431\u0443\u0434\u0443\u0442\u044C|=|:|\u2014|-)\\b`, "i").test(text);
|
|
59224
|
+
if (topicMatch) {
|
|
59225
|
+
let matchesQ = false;
|
|
59226
|
+
for (const qt of substantiveQuestionToks) {
|
|
59227
|
+
if (tokenMatches(qt, ct)) {
|
|
59228
|
+
matchesQ = true;
|
|
59229
|
+
break;
|
|
59230
|
+
}
|
|
59231
|
+
}
|
|
59232
|
+
if (!matchesQ) {
|
|
59233
|
+
return false;
|
|
59234
|
+
}
|
|
59235
|
+
}
|
|
59236
|
+
}
|
|
59237
|
+
return true;
|
|
59238
|
+
}
|
|
59239
|
+
function isDirectInlineResolutionForQuestion(question, afterText) {
|
|
59240
|
+
if (!afterText) return false;
|
|
59241
|
+
const isDirect = /^\s*(?:[-—–:]|\()\s*(?:answered|assumed|assuming|default|blocker|відпові|прийнят|приймаю|беру|за\s+замовчуванням|блокер|припуска|вирішено|узгоджено)/i.test(afterText);
|
|
59242
|
+
if (!isDirect) return false;
|
|
59243
|
+
if (NEGATION_RE.test(afterText)) return false;
|
|
59244
|
+
if (DEFERRED_DECISION_RE.test(afterText)) return false;
|
|
59245
|
+
return isTopicConsistentWithQuestion(question, afterText);
|
|
59246
|
+
}
|
|
59247
|
+
function extractQuestions(text) {
|
|
59248
|
+
const withoutCode = text.replace(/```[\s\S]*?```|~~~[\s\S]*?~~~/g, " ").replace(/`[^`\n]*`/g, " ");
|
|
59249
|
+
const found = [];
|
|
59250
|
+
for (const rawLine of withoutCode.split(/\n+/)) {
|
|
59251
|
+
const line = rawLine.replace(/^\s*(?:[-*•]|\d+[.)])\s+/, "").replace(/(\*\*|__|[*_~"«»“”])/g, "").trim();
|
|
59252
|
+
if (!line) continue;
|
|
59253
|
+
for (const match of line.matchAll(/[^.!?]*\?(?=\s|$)/g)) {
|
|
59254
|
+
const sentence = match[0].trim();
|
|
59255
|
+
const after = line.slice((match.index ?? 0) + match[0].length).split("?")[0] ?? "";
|
|
59256
|
+
const hasInlineMarker = /^\s*(?:[-—–:]|\()\s*(?:answered|assumed|assuming|default|blocker|відпові|прийнят|приймаю|беру|за\s+замовчуванням|блокер|припуска|вирішено|узгоджено)/i.test(after);
|
|
59257
|
+
if (hasInlineMarker) {
|
|
59258
|
+
if (isDirectInlineResolutionForQuestion(sentence, after)) continue;
|
|
59259
|
+
} else {
|
|
59260
|
+
if (isAffirmativeResolutionForQuestion(sentence, after)) continue;
|
|
59261
|
+
}
|
|
59262
|
+
if (/https?:\/\/\S*$/.test(sentence)) continue;
|
|
59263
|
+
if (sentence.replace(/[^\p{L}]/gu, "").length < 6) continue;
|
|
59264
|
+
const question = sentence.slice(0, LEDGER_MAX_QUESTION_CHARS);
|
|
59265
|
+
if (found.some((seen) => isSameQuestion(seen, question))) continue;
|
|
59266
|
+
found.push(question);
|
|
59267
|
+
if (found.length >= LEDGER_MAX_EXTRACTED_QUESTIONS) return found;
|
|
59268
|
+
}
|
|
59269
|
+
}
|
|
59270
|
+
return found;
|
|
59271
|
+
}
|
|
59272
|
+
function presentLedger(ledger) {
|
|
59273
|
+
const turn = ledger.turn + 1;
|
|
59274
|
+
const candidates = ledger.items.map((item, index) => ({ item, index })).filter(({ item }) => item.status === "open" || item.status === "reasked" || item.status === "addressed");
|
|
59275
|
+
const shownIndexes = new Set(candidates.slice(-LEDGER_MAX_ITEMS).map(({ index }) => index));
|
|
59276
|
+
const items = ledger.items.map((item, index) => shownIndexes.has(index) ? { ...item, presentedTurn: turn } : item);
|
|
59277
|
+
const shown = items.filter((_, index) => shownIndexes.has(index)).map(({ question, status, askedTurn }) => ({ question, status, askedTurn }));
|
|
59278
|
+
return { ledger: { turn, items }, shown };
|
|
59279
|
+
}
|
|
59280
|
+
function isQuestionExplicitlyResolved(item, text, otherOpenQuestions) {
|
|
59281
|
+
if (!text || !RESOLUTION_MARKER_RE.test(text)) return false;
|
|
59282
|
+
const withoutCode = text.replace(/```[\s\S]*?```|~~~[\s\S]*?~~~/g, " ").replace(/`[^`\n]*`/g, " ");
|
|
59283
|
+
for (const rawLine of withoutCode.split(/\n+/)) {
|
|
59284
|
+
const line = rawLine.trim();
|
|
59285
|
+
if (!line) continue;
|
|
59286
|
+
if (line.includes(item.question)) {
|
|
59287
|
+
const qIndex = line.indexOf(item.question);
|
|
59288
|
+
const after = line.slice(qIndex + item.question.length).trim();
|
|
59289
|
+
const isDirectAttachment = /^[:—–\->]\s*|[^\w\s]\s*(?:assumed|default|дефолт|outcome|decision|обрано|прийнято)\b/i.test(after);
|
|
59290
|
+
if (isDirectAttachment && isDirectInlineResolutionForQuestion(item.question, after)) {
|
|
59291
|
+
const hasOtherTokens = otherOpenQuestions?.some((oq) => {
|
|
59292
|
+
if (oq === item.question || isSameQuestion(oq, item.question)) return false;
|
|
59293
|
+
const otherToks = Array.from(tokens(oq)).filter((t) => !LEDGER_STOP_WORDS.has(t) && t.length >= 3);
|
|
59294
|
+
const thisToks = Array.from(tokens(item.question)).filter((t) => !LEDGER_STOP_WORDS.has(t) && t.length >= 3);
|
|
59295
|
+
const otherUnique = otherToks.filter((ot) => !thisToks.some((tt) => tokenMatches(ot, tt)));
|
|
59296
|
+
const afterToks = tokens(after);
|
|
59297
|
+
return otherUnique.length > 0 && otherUnique.some((ot) => Array.from(afterToks).some((at) => tokenMatches(ot, at)));
|
|
59298
|
+
});
|
|
59299
|
+
if (!hasOtherTokens) {
|
|
59300
|
+
return true;
|
|
59301
|
+
}
|
|
59302
|
+
}
|
|
59303
|
+
}
|
|
59304
|
+
}
|
|
59305
|
+
const segments = withoutCode.split(/\n+|(?<=[.!?])\s+/).map((l) => l.trim()).filter(Boolean);
|
|
59306
|
+
for (const segment of segments) {
|
|
59307
|
+
if (!RESOLUTION_MARKER_RE.test(segment)) continue;
|
|
59308
|
+
if (NEGATION_RE.test(segment)) continue;
|
|
59309
|
+
if (DEFERRED_DECISION_RE.test(segment)) continue;
|
|
59310
|
+
if (segment.includes(item.question) || isSameQuestion(segment, item.question)) {
|
|
59311
|
+
const hasOtherTokens = otherOpenQuestions?.some((oq) => {
|
|
59312
|
+
if (oq === item.question || isSameQuestion(oq, item.question)) return false;
|
|
59313
|
+
const otherToks = Array.from(tokens(oq)).filter((t) => !LEDGER_STOP_WORDS.has(t) && t.length >= 3);
|
|
59314
|
+
const thisToks = Array.from(tokens(item.question)).filter((t) => !LEDGER_STOP_WORDS.has(t) && t.length >= 3);
|
|
59315
|
+
const otherUnique = otherToks.filter((ot) => !thisToks.some((tt) => tokenMatches(ot, tt)));
|
|
59316
|
+
const segToks = tokens(segment);
|
|
59317
|
+
return otherUnique.length > 0 && otherUnique.some((ot) => Array.from(segToks).some((st) => tokenMatches(ot, st)));
|
|
59318
|
+
});
|
|
59319
|
+
if (!hasOtherTokens) {
|
|
59320
|
+
return true;
|
|
59321
|
+
}
|
|
59322
|
+
}
|
|
59323
|
+
if (isAffirmativeResolutionForQuestion(item.question, segment, otherOpenQuestions)) {
|
|
59324
|
+
return true;
|
|
59325
|
+
}
|
|
59326
|
+
}
|
|
59327
|
+
return false;
|
|
59328
|
+
}
|
|
59329
|
+
function recordTurn(ledger, finalText) {
|
|
59330
|
+
const turn = ledger.turn;
|
|
59331
|
+
const newQuestions = extractQuestions(finalText);
|
|
59332
|
+
const unmatched = [...newQuestions];
|
|
59333
|
+
const reasked = [];
|
|
59334
|
+
const openQuestions = ledger.items.filter((it) => it.status === "open" || it.status === "reasked").map((it) => it.question);
|
|
59335
|
+
const items = ledger.items.map((item) => {
|
|
59336
|
+
if (item.status === "archived") return item;
|
|
59337
|
+
const matchIndex = unmatched.findIndex((question) => isSameQuestion(question, item.question));
|
|
59338
|
+
if (matchIndex >= 0) {
|
|
59339
|
+
unmatched.splice(matchIndex, 1);
|
|
59340
|
+
if (item.askedTurn === turn) return item;
|
|
59341
|
+
const again = { ...item, status: "reasked", reasked: item.reasked + 1 };
|
|
59342
|
+
delete again.addressedTurn;
|
|
59343
|
+
reasked.push(again);
|
|
59344
|
+
return again;
|
|
59345
|
+
}
|
|
59346
|
+
if (item.status === "addressed") {
|
|
59347
|
+
return item.addressedTurn !== void 0 && item.addressedTurn < turn ? { ...item, status: "archived" } : item;
|
|
59348
|
+
}
|
|
59349
|
+
const otherQuestions = openQuestions.filter((q) => q !== item.question);
|
|
59350
|
+
if (isQuestionExplicitlyResolved(item, finalText, otherQuestions)) {
|
|
59351
|
+
return { ...item, status: "addressed", addressedTurn: turn };
|
|
59352
|
+
}
|
|
59353
|
+
return item;
|
|
59354
|
+
});
|
|
59355
|
+
const overflow = [];
|
|
59356
|
+
let unresolved = items.filter((item) => item.status === "open" || item.status === "reasked").length;
|
|
59357
|
+
let recordedNew = 0;
|
|
59358
|
+
for (const question of unmatched) {
|
|
59359
|
+
if (recordedNew >= LEDGER_MAX_QUESTIONS_PER_TURN) {
|
|
59360
|
+
overflow.push(question);
|
|
59361
|
+
continue;
|
|
59362
|
+
}
|
|
59363
|
+
if (unresolved >= LEDGER_MAX_ITEMS) {
|
|
59364
|
+
overflow.push(question);
|
|
59365
|
+
continue;
|
|
59366
|
+
}
|
|
59367
|
+
items.push({ question, askedTurn: turn, status: "open", reasked: 0 });
|
|
59368
|
+
unresolved += 1;
|
|
59369
|
+
recordedNew += 1;
|
|
59370
|
+
}
|
|
59371
|
+
return { ...capLedger({ turn, items }), reasked, overflow };
|
|
59372
|
+
}
|
|
59373
|
+
var DROP_ORDER = ["archived", "addressed"];
|
|
59374
|
+
function capLedger(ledger) {
|
|
59375
|
+
const items = [...ledger.items];
|
|
59376
|
+
const dropped = [];
|
|
59377
|
+
for (const status of DROP_ORDER) {
|
|
59378
|
+
while (items.length > LEDGER_MAX_ITEMS) {
|
|
59379
|
+
const index = items.findIndex((item) => item.status === status);
|
|
59380
|
+
if (index < 0) break;
|
|
59381
|
+
dropped.push(...items.splice(index, 1));
|
|
59382
|
+
}
|
|
59383
|
+
}
|
|
59384
|
+
return { ledger: { turn: ledger.turn, items }, dropped };
|
|
59385
|
+
}
|
|
59386
|
+
function parseLedger(value) {
|
|
59387
|
+
if (!value || typeof value !== "object") return void 0;
|
|
59388
|
+
const raw = value;
|
|
59389
|
+
if (typeof raw.turn !== "number" || !Array.isArray(raw.items)) return void 0;
|
|
59390
|
+
if (raw.items.length > LEDGER_MAX_ITEMS) return void 0;
|
|
59391
|
+
const statuses = /* @__PURE__ */ new Set(["open", "reasked", "addressed", "archived"]);
|
|
59392
|
+
const items = [];
|
|
59393
|
+
for (const entry of raw.items) {
|
|
59394
|
+
if (!entry || typeof entry !== "object") continue;
|
|
59395
|
+
const item = entry;
|
|
59396
|
+
if (typeof item.question !== "string" || typeof item.askedTurn !== "number") continue;
|
|
59397
|
+
if (typeof item.status !== "string" || !statuses.has(item.status)) continue;
|
|
59398
|
+
items.push({
|
|
59399
|
+
question: item.question.slice(0, LEDGER_MAX_QUESTION_CHARS),
|
|
59400
|
+
askedTurn: item.askedTurn,
|
|
59401
|
+
status: item.status,
|
|
59402
|
+
reasked: typeof item.reasked === "number" ? item.reasked : 0,
|
|
59403
|
+
...typeof item.presentedTurn === "number" ? { presentedTurn: item.presentedTurn } : {},
|
|
59404
|
+
...typeof item.addressedTurn === "number" ? { addressedTurn: item.addressedTurn } : {}
|
|
59405
|
+
});
|
|
59406
|
+
}
|
|
59407
|
+
return { turn: raw.turn, items };
|
|
59408
|
+
}
|
|
58081
59409
|
|
|
58082
59410
|
// src/api-base-url-migrate.ts
|
|
58083
59411
|
var import_node_fs13 = __toESM(require("node:fs"), 1);
|
|
@@ -61546,6 +62874,22 @@ async function driveRun(deps, projectSlug, runId) {
|
|
|
61546
62874
|
}
|
|
61547
62875
|
}
|
|
61548
62876
|
}
|
|
62877
|
+
const needsBrowser = claimed.prerequisites?.some((p) => p.mcp === "mrrlin-browser") || task.domains?.includes("browser");
|
|
62878
|
+
if (needsBrowser) {
|
|
62879
|
+
const checkBusy = deps.checkBrowserBusy ?? checkBrowserBusy;
|
|
62880
|
+
const busy = checkBusy();
|
|
62881
|
+
if (busy.busy) {
|
|
62882
|
+
await failRun(
|
|
62883
|
+
deps,
|
|
62884
|
+
projectSlug,
|
|
62885
|
+
task,
|
|
62886
|
+
runId,
|
|
62887
|
+
leaseId,
|
|
62888
|
+
`browser_busy: run ${runId} needs the browser, but it is currently in use by another task (holder pid: ${busy.holderPid}). Please retry once it finishes.`
|
|
62889
|
+
);
|
|
62890
|
+
return;
|
|
62891
|
+
}
|
|
62892
|
+
}
|
|
61549
62893
|
const blockerTasks = task.blockedBy.length ? await Promise.all(task.blockedBy.map((blockerTaskId) => deps.client.getTask(projectSlug, blockerTaskId))) : [];
|
|
61550
62894
|
const missingBlockers = task.blockedBy.filter((_, index) => !blockerTasks[index]);
|
|
61551
62895
|
if (missingBlockers.length > 0) {
|
|
@@ -61724,12 +63068,20 @@ async function driveRun(deps, projectSlug, runId) {
|
|
|
61724
63068
|
turnCount: 0,
|
|
61725
63069
|
usage: null
|
|
61726
63070
|
};
|
|
61727
|
-
const
|
|
63071
|
+
const recordTurn2 = (outcome) => {
|
|
61728
63072
|
runTelemetry.promptBytes += outcome.telemetry.promptBytes;
|
|
61729
63073
|
runTelemetry.turnCount += 1;
|
|
61730
63074
|
runTelemetry.usage = addTurnTokenUsage(runTelemetry.usage, outcome.telemetry.usage);
|
|
61731
63075
|
return outcome;
|
|
61732
63076
|
};
|
|
63077
|
+
const isContextBudgetExhausted = (usage) => {
|
|
63078
|
+
if (!usage) return false;
|
|
63079
|
+
const contextTokens = (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
|
|
63080
|
+
if (contextTokens <= 0) return false;
|
|
63081
|
+
const maxTokens = 2e5;
|
|
63082
|
+
const remainingTokens = maxTokens - contextTokens;
|
|
63083
|
+
return remainingTokens / maxTokens < 0.03 || remainingTokens < 4e3;
|
|
63084
|
+
};
|
|
61733
63085
|
const effortDecision = deriveEffort({
|
|
61734
63086
|
impact: task.impact,
|
|
61735
63087
|
type: task.type,
|
|
@@ -61952,7 +63304,7 @@ ${renderTaskStateSection(taskState)}`;
|
|
|
61952
63304
|
);
|
|
61953
63305
|
return;
|
|
61954
63306
|
}
|
|
61955
|
-
const primed =
|
|
63307
|
+
const primed = recordTurn2(
|
|
61956
63308
|
await driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, bundle, primingBudgetMs, "priming", effortDecision?.level)
|
|
61957
63309
|
);
|
|
61958
63310
|
if (!primed.ok) {
|
|
@@ -62016,7 +63368,7 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
|
|
|
62016
63368
|
);
|
|
62017
63369
|
return;
|
|
62018
63370
|
}
|
|
62019
|
-
const outcome =
|
|
63371
|
+
const outcome = recordTurn2(
|
|
62020
63372
|
await driveTurn(deps, codex, projectSlug, runId, leaseId, threadId, executionPrompt, workBudgetMs, "work", effortDecision?.level)
|
|
62021
63373
|
);
|
|
62022
63374
|
if (!outcome.ok) {
|
|
@@ -62024,6 +63376,26 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
|
|
|
62024
63376
|
await failRun(deps, projectSlug, task, runId, leaseId, outcome.error);
|
|
62025
63377
|
return;
|
|
62026
63378
|
}
|
|
63379
|
+
if (isContextBudgetExhausted(outcome.telemetry.usage)) {
|
|
63380
|
+
console.warn(
|
|
63381
|
+
JSON.stringify({
|
|
63382
|
+
event: "context_budget_gate_triggered",
|
|
63383
|
+
projectSlug,
|
|
63384
|
+
runId,
|
|
63385
|
+
taskId: task.id,
|
|
63386
|
+
usage: outcome.telemetry.usage
|
|
63387
|
+
})
|
|
63388
|
+
);
|
|
63389
|
+
await failRun(
|
|
63390
|
+
deps,
|
|
63391
|
+
projectSlug,
|
|
63392
|
+
task,
|
|
63393
|
+
runId,
|
|
63394
|
+
leaseId,
|
|
63395
|
+
`context_budget_exhausted: context window is near capacity (<3% remaining). Execution run halted safely to preserve progress and prevent lost output.`
|
|
63396
|
+
);
|
|
63397
|
+
return;
|
|
63398
|
+
}
|
|
62027
63399
|
const prReadyStatuses = useExecutor ? CODE_PR_READY_TASK_STATUSES : COMPLETED_TASK_STATUSES;
|
|
62028
63400
|
let readiness = await assertRunCompletionReady(deps, projectSlug, task.id, runId, prReadyStatuses);
|
|
62029
63401
|
if (!readiness.ok) {
|
|
@@ -62042,7 +63414,7 @@ Continue executing task ${task.id}. Make the next concrete increment of progress
|
|
|
62042
63414
|
})
|
|
62043
63415
|
);
|
|
62044
63416
|
const completionPrompt = buildCompletionTurnPrompt(task.id, readiness.task.status);
|
|
62045
|
-
const completionOutcome =
|
|
63417
|
+
const completionOutcome = recordTurn2(
|
|
62046
63418
|
await driveTurn(
|
|
62047
63419
|
deps,
|
|
62048
63420
|
codex,
|
|
@@ -63207,12 +64579,21 @@ async function driveAsyncJob(deps, projectSlug, jobId) {
|
|
|
63207
64579
|
} finally {
|
|
63208
64580
|
clearInterval(touchTimer);
|
|
63209
64581
|
}
|
|
64582
|
+
if (outcome.kind === "superseded") {
|
|
64583
|
+
await deps.client.terminalizeAsyncJob(projectSlug, jobId, {
|
|
64584
|
+
leaseId,
|
|
64585
|
+
status: "superseded",
|
|
64586
|
+
error: outcome.reason ?? outcome.error ?? "Superseded"
|
|
64587
|
+
}).catch(() => {
|
|
64588
|
+
});
|
|
64589
|
+
return;
|
|
64590
|
+
}
|
|
63210
64591
|
const terminalStatus = outcome.kind === "done" ? "done" : "failed";
|
|
63211
64592
|
await deps.client.terminalizeAsyncJob(projectSlug, jobId, {
|
|
63212
64593
|
leaseId,
|
|
63213
64594
|
status: terminalStatus,
|
|
63214
64595
|
resultJson: outcome.kind === "done" ? outcome.resultJson : null,
|
|
63215
|
-
finalReviewedHash: outcome.kind === "done" ? outcome.finalReviewedHash : null,
|
|
64596
|
+
finalReviewedHash: outcome.kind === "done" ? outcome.finalReviewedHash : outcome.kind === "failed" ? outcome.finalReviewedHash ?? null : null,
|
|
63216
64597
|
error: outcome.kind === "done" ? null : outcome.error,
|
|
63217
64598
|
failureReason: outcome.kind === "done" ? null : outcome.failureReason
|
|
63218
64599
|
}).catch(() => {
|
|
@@ -63599,7 +64980,9 @@ function loadThreadIndex(stateDir, ttlMs, now) {
|
|
|
63599
64980
|
lastUsedAt,
|
|
63600
64981
|
model: typeof entry.model === "string" ? entry.model : null,
|
|
63601
64982
|
reasoningEffort: typeof entry.reasoningEffort === "string" ? entry.reasoningEffort : null,
|
|
63602
|
-
provider: typeof entry.provider === "string" ? entry.provider : null
|
|
64983
|
+
provider: typeof entry.provider === "string" ? entry.provider : null,
|
|
64984
|
+
systemInstructionHash: typeof entry.systemInstructionHash === "string" ? entry.systemInstructionHash : null,
|
|
64985
|
+
...parseLedger(entry.clarificationLedger) ? { clarificationLedger: parseLedger(entry.clarificationLedger) } : {}
|
|
63603
64986
|
});
|
|
63604
64987
|
}
|
|
63605
64988
|
return result;
|
|
@@ -68399,6 +69782,25 @@ function buildThreadStartParams(msg, config2, desiredModel) {
|
|
|
68399
69782
|
"You are the Mrrlin Director assistant.",
|
|
68400
69783
|
"You can call MCP tools to read and write project state.",
|
|
68401
69784
|
"Prefer calling tools over guessing. Be concise.",
|
|
69785
|
+
// O-DIRECTOR-HARD-GATES: a compact behaviour contract replaced ~30 accumulated mandates, several of
|
|
69786
|
+
// which were fitted to single benchmark personas. Closure and localization are enforced in code
|
|
69787
|
+
// (director-closure.ts, linkifyMrrlinIds.ts); the clarification ledger arrives in the turn context.
|
|
69788
|
+
"Behaviour contract (three rules):",
|
|
69789
|
+
'- DEFAULT-FIRST: never open an open-ended question round. When the intent-confirmation contract below says you must ask before creating a task, ask only about what would change the architecture or the deliverable \u2014 never about anything the operator already stated in this conversation \u2014 and once the operator has replied, take a reasonable default for whatever is still open and say so plainly ("unless you object, I take X") instead of asking again. Never ask permission to create tasks or specs \u2014 creating them is your job.',
|
|
69790
|
+
"- USER-FACING CLOSURE: end every turn with plain text in the operator's language. If the turn created or changed anything, name each item (ID with a one-line scope), where the result goes, and what happens next; if it changed nothing, say plainly what you need from the operator or what happens next. Never invent an ID or a write to have something to report. A UI card or a bare list of IDs never replaces that text.",
|
|
69791
|
+
"- QUESTION LIFECYCLE: every question you asked must end in exactly one state \u2014 answered by the operator, assumed (you state the default you took), or blocker (you state what it blocks). The open-question list in the turn context is authoritative: resolve each item explicitly and never drop one silently.",
|
|
69792
|
+
"Core guards:",
|
|
69793
|
+
"- Never claim a deliverable, file, test result, or running execution exists unless a tool actually produced it. Never claim autopilot is running unless an execution run is active; for a human_review task say it waits in To Do for your approval.",
|
|
69794
|
+
"- Mention a GT- or WK- id only if a tool returned it in this conversation (server-authoritative receipts).",
|
|
69795
|
+
"- Reply in the operator's language for the whole turn (questions, task scopes, summaries).",
|
|
69796
|
+
"- Keep chat short and human. No code blocks, DDL, terminal output, local filesystem paths, internal job/inbox UUIDs or alert markers in chat \u2014 put long content into a wiki page and link it.",
|
|
69797
|
+
"- Spec pages in folder Specs must contain `## Acceptance criteria` followed by `- [ ]` items.",
|
|
69798
|
+
"- AGNOSTIC DELIVERY MANDATE: Mrrlin is an Agnostic Delivery Platform decoupled from mandatory GitHub. Delivery is routed strictly by artifact kind: (1) document -> wiki (specs, policies, legal compliance playbooks, project documentation); (2) page -> publish (web pages, landing pages, interactive HTML previews); (3) file_set -> artifacts (file uploads, bundles, build zips); (4) message -> send (outbound webhooks, Slack, notifications); (5) code_change -> repo_host (git / GitHub ONLY when modifying source repository code). NEVER stall or refuse non-code tasks (such as marketing landing pages, GDPR checklists, or legal compliance playbooks) by demanding a Git repository. Non-code tasks deliver directly via page (Publish) or document (Wiki).",
|
|
69799
|
+
"- 3-TIER COMMERCIAL SCOPE GOVERNANCE: Treat tasks by domain governance tier. (1) Tier 1 Core Tech Delivery (backend, frontend, SRE, databases): decompose autonomously into repository code changes (when a repo is bound) or code artifacts. (2) Tier 2 Planning & Content (landing pages, SEO, marketing): create actionable drafts; when details such as a phone number, brand name or copy are missing, do not loop on questions \u2014 create the task, record `## Placeholders: [NEED_INPUT: <item>]` in the Wiki spec, and set `autonomyLevel: 'human_review'`. (3) Tier 3 Guardrailed Advisory (high risk: privacy/GDPR, security/SOC 2, billing, production database migrations, infrastructure): always set `autonomyLevel: 'human_review'`, put the domain's standard safety controls into the spec, and state that irreversible actions need human sign-off.",
|
|
69800
|
+
"- INBOX QUESTION RETIREMENT MANDATE: If clarifying questions were banked or exist as pending items in Inbox from a previous turn (e.g. via `create_inbox_item(kind: 'question')`), and the operator has now answered them in chat in Turn 2+, you MUST call `decide_inbox_item(id, verdict: 'acknowledged', note: ...)` (or `verdict: 'rejected'` if moot) to retire the pending questions BEFORE creating tasks or plans on the board. Never leave questions you received answers to lingering as unanswered items in Inbox.",
|
|
69801
|
+
"- CHAT CLARIFICATIONS ARE IN CHAT ONLY: During synchronous interactive Director chat, ask clarifying questions directly in your chat response. DO NOT call `create_inbox_item(kind: 'question')` for routine conversational clarifications in Turn 1 \u2014 Inbox questions are reserved for autonomous background workers or asynchronous gate pauses that occur while the operator is away.",
|
|
69802
|
+
"- AUTOPILOT PRESENCE FENCE TRANSPARENCY: Autopilot requires operator web app presence to trigger execution ticks (`operator_absent`). When the operator asks about autopilot starting, remind them that keeping the web tab open allows the scheduler to advance the queue automatically.",
|
|
69803
|
+
"",
|
|
68402
69804
|
"Autonomy level is NOT a reason to skip clarification. `auto` means only that no human gate is",
|
|
68403
69805
|
"required before the result LANDS (merge/deploy); it does not authorize building on unvalidated",
|
|
68404
69806
|
"assumptions. Judge clarification on requirement clarity, independently of autonomy_level.",
|
|
@@ -68492,17 +69894,22 @@ function buildThreadStartParams(msg, config2, desiredModel) {
|
|
|
68492
69894
|
"turn with one sentence restating your understanding, then at most 3 scope-level questions, and",
|
|
68493
69895
|
"do NOT create the task yet. Create it after the operator replies.",
|
|
68494
69896
|
"",
|
|
68495
|
-
"Once the operator replies and clarifies or confirms the scope (or if scope was already clear),",
|
|
68496
|
-
"you MUST materialize the work onto the board: create the plan (if needed) and call `create_task`",
|
|
68497
|
-
"in the same turn so actionable task cards appear on `/tasks` immediately. Never leave the board",
|
|
68498
|
-
"empty with 0 tasks after agreeing on scope. Never claim tasks are created, queued, or ready unless",
|
|
68499
|
-
"`create_task` was actually invoked and returned the task ID.",
|
|
68500
|
-
"",
|
|
68501
69897
|
"Set operatorSeedRequest to the operator's OWN WORDS, verbatim. Select every message in this",
|
|
68502
69898
|
"conversation that forms part of the request \u2014 not only the last one \u2014 and concatenate them in",
|
|
68503
69899
|
"order, separated by a blank line. Do NOT paraphrase, summarise, or rewrite any of it, and do",
|
|
68504
69900
|
"NOT add words of your own.",
|
|
68505
69901
|
"",
|
|
69902
|
+
"Once the operator replies and clarifies or confirms the scope (or if scope was already clear),",
|
|
69903
|
+
"you MUST create the tasks: create the plan (if needed) and call `create_task` in the same turn so",
|
|
69904
|
+
"actionable task cards appear on the Tasks board immediately. Never leave the board empty with 0",
|
|
69905
|
+
"tasks after agreeing on scope. Never claim tasks are created, queued, or ready unless `create_task`",
|
|
69906
|
+
"was actually invoked and returned the task ID. Set `status: 'todo'` on newly created actionable",
|
|
69907
|
+
"tasks unless explicitly asked for backlog, so they appear in active Kanban columns immediately.",
|
|
69908
|
+
"When referring to created tasks or where to view them, use natural, human-friendly language with a markdown link",
|
|
69909
|
+
"to the Tasks page (e.g. 'on the [Tasks page](/tasks)', '[Tasks board](/tasks)', or in Ukrainian '\u043D\u0430 [\u0441\u0442\u043E\u0440\u0456\u043D\u0446\u0456 \u0437\u0430\u0434\u0430\u0447](/tasks)').",
|
|
69910
|
+
"Never output raw technical URL fragments like '\u043D\u0430 /tasks' or 'on /tasks', and never use developer database",
|
|
69911
|
+
"jargon like 'materialized tasks' or '\u043C\u0430\u0442\u0435\u0440\u0456\u0430\u043B\u0456\u0437\u0443\u0432\u0430\u0432 \u0437\u0430\u0434\u0430\u0447\u0456' \u2014 say 'created' / '\u0441\u0442\u0432\u043E\u0440\u0438\u0432' or 'added' / '\u0434\u043E\u0434\u0430\u0432' instead.",
|
|
69912
|
+
"",
|
|
68506
69913
|
"Set resolvedIntent to the accepted task intent in your own concise words only after the outcome",
|
|
68507
69914
|
"is resolved enough to create the task. Use it to capture the actionable understanding that came",
|
|
68508
69915
|
"from the conversation and, when needed, retrieved wiki context. Do NOT use resolvedIntent as a",
|
|
@@ -68541,6 +69948,9 @@ function buildThreadStartParams(msg, config2, desiredModel) {
|
|
|
68541
69948
|
" SHALL sentence stating the externally visible behavior.",
|
|
68542
69949
|
"- Give every requirement at least one `#### Scenario: <name>` with `- WHEN ...` / `- THEN ...`",
|
|
68543
69950
|
" bullets (`- AND ...` as needed). Scenarios make the spec verifiable.",
|
|
69951
|
+
"- MUST include `## Acceptance criteria`: a markdown checklist (`- [ ] ...`) of concrete, verifiable",
|
|
69952
|
+
" conditions \u2014 this is the machine-usable yardstick the consensus review scores against. NEVER format criteria as numbered lists (`1. ...`) or plain bullets.",
|
|
69953
|
+
"- MUST include `## Verification`: the exact tests, commands, or checks that prove each criterion holds.",
|
|
68544
69954
|
"- Keep behavior contracts in the spec; implementation details belong in `## Implementation Plan`.",
|
|
68545
69955
|
"- Apply this format ONLY to Specs/{id} pages; do not retro-fit it onto other wiki page types.",
|
|
68546
69956
|
'- Spec consensus runs ASYNCHRONOUSLY. Call `run_spec_consensus` for a `Specs/{id}` page; it returns IMMEDIATELY. On `already_converged`, the spec is already marked ready \u2014 proceed straight to `create_execution_run`; do NOT call `mark_spec_ready` and do NOT re-run consensus. On `queued` or `already_running`, tell the user it was launched and END your turn \u2014 do NOT poll or call it again; the result arrives in the inbox (an `info` item on CONVERGED, a `handoff` item if it was blocked/failed). When a `handoff` says it was blocked, address the recorded issues in the spec (any edit re-arms the gate) and call `run_spec_consensus` once more. For a NON-code task (domain seo/marketing/content, ADR 0020): the low-risk lanes `seo`, `marketing`, and `content` proceed to `create_execution_run` after consensus converges, using the same flow as code tasks. When the review agrees, tell the operator "the reviewers agreed on the spec" and proceed to create_execution_run. When it does not, tell them the reviewers could not agree and that there is a decision question in the inbox. Never say "consensus", "converged", or "CONVERGED" to the operator; those are internal words.',
|
|
@@ -68660,15 +70070,20 @@ function buildThreadStartParams(msg, config2, desiredModel) {
|
|
|
68660
70070
|
`Product-surface contract \u2014 never answer "I don't know where that is" about Mrrlin's own UI:`,
|
|
68661
70071
|
"- You are talking to an operator inside this product. When they ask where something is, name",
|
|
68662
70072
|
" the route and the control by the label actually printed on it, then let them click it.",
|
|
68663
|
-
"-
|
|
68664
|
-
"
|
|
68665
|
-
|
|
68666
|
-
"
|
|
68667
|
-
|
|
68668
|
-
"
|
|
68669
|
-
"
|
|
68670
|
-
"
|
|
68671
|
-
"
|
|
70073
|
+
"- Autopilot view lives on `/autopilot`. It displays autonomous execution runs, active background",
|
|
70074
|
+
" jobs, and tasks currently in handoff. To inspect why tasks are in handoff or diagnose autonomous",
|
|
70075
|
+
' progress, use the canonical typed tools: `list_inbox_items` (filtering by kind="handoff"),',
|
|
70076
|
+
" `list_tasks` (filtering by status), and `list_execution_runs`.",
|
|
70077
|
+
"- Tasks and board backlog live on `/tasks` (and individual task details on `/items/{taskId}`).",
|
|
70078
|
+
" Always use `list_tasks`, `get_task`, and `list_task_events` to inspect them.",
|
|
70079
|
+
"- Project knowledge, deliverable reports, and specifications live under `/wiki` and `/deliverables/*`.",
|
|
70080
|
+
" These are stored as project wiki pages \u2014 read them directly via `get_wiki_page` or search with",
|
|
70081
|
+
" `search_wiki_pages`/`list_wiki_pages`. Never open the browser for `/deliverables/*` or `/wiki/*`.",
|
|
70082
|
+
"- Workspace settings, scheduler configurations, and integrations live on `/settings`.",
|
|
70083
|
+
"- Universal Tool Availability: All canonical typed tools (`list_tasks`, `get_task`, `list_inbox_items`,",
|
|
70084
|
+
" `list_execution_runs`, `get_wiki_page`, `search_wiki_pages`, `list_wiki_pages`) are authoritative",
|
|
70085
|
+
" and fully available across ALL routes and session tabs. Never tell an operator that a typed tool",
|
|
70086
|
+
" is missing or unavailable because of the current page or route.",
|
|
68672
70087
|
"- If a surface is genuinely not in this list, say what you do know and check with a typed tool",
|
|
68673
70088
|
' rather than guessing a route \u2014 a wrong path costs the operator more than an honest "let me',
|
|
68674
70089
|
' check". Never send them to the browser to hunt for a page in their own app.',
|
|
@@ -68722,22 +70137,12 @@ function buildThreadStartParams(msg, config2, desiredModel) {
|
|
|
68722
70137
|
"- During a live chat turn, when a SPECIFIC task is ambiguous and the ambiguity affects",
|
|
68723
70138
|
" the work, ASK the operator a simple, concrete question inline in your reply (default to",
|
|
68724
70139
|
" asking rather than silently assuming). Low-impact ambiguity may still be recorded as an",
|
|
68725
|
-
" assumption on the spec page.",
|
|
68726
|
-
"
|
|
68727
|
-
'
|
|
68728
|
-
|
|
68729
|
-
"
|
|
68730
|
-
"
|
|
68731
|
-
" you explicitly prefer a path. The operator always has a custom-answer path in the UI, so",
|
|
68732
|
-
" your options should guide, not trap. (The server dedups identical open questions; do not",
|
|
68733
|
-
" supply a dedup key.)",
|
|
68734
|
-
"- When the operator answers in chat, write the answer into the Specs/{taskId}",
|
|
68735
|
-
' Clarifications section, then `decide_inbox_item` the question with `verdict="acknowledged"`',
|
|
68736
|
-
" and `answer` = the resolved answer.",
|
|
68737
|
-
"- Project-level ambiguity (scope or direction not yet tied to a specific task) MAY be banked",
|
|
68738
|
-
" the same way but with `taskId` OMITTED. There is no spec page yet, so incorporate the answer",
|
|
68739
|
-
" into your current scoping/conversation. Reserve taskless questions for genuine project-level",
|
|
68740
|
-
" scope \u2014 task-affecting questions still set `taskId`.",
|
|
70140
|
+
" assumption on the spec page. NEVER ask clarifying questions after Turn 1 when the operator",
|
|
70141
|
+
" has provided core project scope; adopt standard conventions for secondary edge cases and create tasks.",
|
|
70142
|
+
'- In synchronous live Director chat (Turn 1 scoping), ask your questions directly in your chat reply without creating Inbox items. Only bank questions in Inbox (`create_inbox_item` with `kind="question"`, `taskId` set, `directorSessionId` from Context JSON, `questionOptions` = 2-3 concrete answer options) when an autonomous background task pauses, when a task requires operator decision while the operator is away, or when explicitly requested. Each banked option MUST include a short `label`, a concise `description`, and a self-contained durable `answer`; you MAY mark at most one option as `recommended` when you explicitly prefer a path.',
|
|
70143
|
+
'- When the operator answers in chat, write the answer into the Specs/{taskId} Clarifications section, and if an Inbox question was previously open, `decide_inbox_item` with `verdict="acknowledged"` and `answer` = the resolved answer.',
|
|
70144
|
+
"- Project-level ambiguity (scope or direction not yet tied to a specific task) in live chat stays in chat; for genuine asynchronous pauses it MAY be banked with `taskId` OMITTED. Reserve taskless Inbox questions for genuine project-level scope.",
|
|
70145
|
+
"- Retiring answered questions: When the operator answers a previously banked question (task-specific or taskless) in chat, you MUST immediately call `decide_inbox_item` with `verdict=\"acknowledged\"` and note/answer BEFORE calling `create_task`. Do NOT leave answered questions in status 'pending' in Inbox.",
|
|
68741
70146
|
"- Binding taskless answers to a task: WHEN you create a task out of project-level scoping,",
|
|
68742
70147
|
" call `list_inbox_items` (kind=question), fold any relevant `acknowledged` TASKLESS answer",
|
|
68743
70148
|
" into the new task's Specs/{id} Clarifications, and `reject` (with a reason) any taskless",
|
|
@@ -69065,7 +70470,8 @@ function createBridgeRequestListener(opts) {
|
|
|
69065
70470
|
}
|
|
69066
70471
|
const nonce = import_node_crypto15.default.randomBytes(16).toString("base64url");
|
|
69067
70472
|
res.writeHead(200, { "content-type": "application/json" });
|
|
69068
|
-
|
|
70473
|
+
const publicHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
70474
|
+
res.end(JSON.stringify({ ok: true, wsUrl: `ws://${publicHost}:${port}/ws`, token, nonce }));
|
|
69069
70475
|
return;
|
|
69070
70476
|
}
|
|
69071
70477
|
res.writeHead(404).end();
|
|
@@ -69970,8 +71376,10 @@ function createBridgeMessageHandler(deps) {
|
|
|
69970
71376
|
const desiredEffort = normalizeReasoningEffort(msg.reasoningEffort);
|
|
69971
71377
|
const resolvedProviderId = turnSeat.id;
|
|
69972
71378
|
let startedFreshThread = false;
|
|
71379
|
+
let desiredInstructionHash = null;
|
|
69973
71380
|
try {
|
|
69974
71381
|
const startParams = deps.buildStartParams(msg, desiredModel);
|
|
71382
|
+
desiredInstructionHash = import_node_crypto15.default.createHash("sha256").update(startParams.baseInstructions ?? "").digest("hex");
|
|
69975
71383
|
if (!thread) {
|
|
69976
71384
|
const newThread = {
|
|
69977
71385
|
threadId: "",
|
|
@@ -69982,7 +71390,8 @@ function createBridgeMessageHandler(deps) {
|
|
|
69982
71390
|
reasoningEffort: desiredEffort,
|
|
69983
71391
|
replay: null,
|
|
69984
71392
|
supersededReplay: null,
|
|
69985
|
-
provider: resolvedProviderId
|
|
71393
|
+
provider: resolvedProviderId,
|
|
71394
|
+
systemInstructionHash: null
|
|
69986
71395
|
};
|
|
69987
71396
|
const started = await codexClient.startThread(startParams);
|
|
69988
71397
|
newThread.threadId = started.threadId;
|
|
@@ -69993,17 +71402,27 @@ function createBridgeMessageHandler(deps) {
|
|
|
69993
71402
|
} else {
|
|
69994
71403
|
const existingThread = thread;
|
|
69995
71404
|
const providerMismatch = existingThread.provider !== null && existingThread.provider !== resolvedProviderId;
|
|
71405
|
+
const instructionMismatch = (resolvedProviderId === "agy" || resolvedProviderId === "claude") && (!existingThread.systemInstructionHash || existingThread.systemInstructionHash !== desiredInstructionHash);
|
|
69996
71406
|
let resumed = true;
|
|
69997
71407
|
let result;
|
|
69998
|
-
if (!existingThread.threadId || providerMismatch) {
|
|
71408
|
+
if (!existingThread.threadId || providerMismatch || instructionMismatch) {
|
|
71409
|
+
if (existingThread.threadId) {
|
|
71410
|
+
await codexClient.interruptTurn(existingThread.threadId).catch(() => {
|
|
71411
|
+
});
|
|
71412
|
+
}
|
|
69999
71413
|
resumed = false;
|
|
70000
71414
|
result = await codexClient.startThread(startParams);
|
|
71415
|
+
existingThread.systemInstructionHash = null;
|
|
70001
71416
|
} else {
|
|
70002
71417
|
try {
|
|
70003
|
-
result = await codexClient.resumeThread(existingThread.threadId,
|
|
71418
|
+
result = await codexClient.resumeThread(existingThread.threadId, {
|
|
71419
|
+
...startParams,
|
|
71420
|
+
alreadySeeded: true
|
|
71421
|
+
});
|
|
70004
71422
|
} catch {
|
|
70005
71423
|
resumed = false;
|
|
70006
71424
|
result = await codexClient.startThread(startParams);
|
|
71425
|
+
existingThread.systemInstructionHash = null;
|
|
70007
71426
|
}
|
|
70008
71427
|
}
|
|
70009
71428
|
existingThread.threadId = result.threadId;
|
|
@@ -70137,10 +71556,13 @@ function createBridgeMessageHandler(deps) {
|
|
|
70137
71556
|
deps.releaseCodex(turnSeat.id);
|
|
70138
71557
|
return;
|
|
70139
71558
|
}
|
|
71559
|
+
const presentedLedger = presentLedger(thread.clarificationLedger ?? emptyLedger());
|
|
71560
|
+
thread.clarificationLedger = presentedLedger.ledger;
|
|
70140
71561
|
const contextPrefix = renderContextPrefix({
|
|
70141
71562
|
...msg.context,
|
|
70142
71563
|
project: projectContext,
|
|
70143
71564
|
answeredQuestions,
|
|
71565
|
+
openQuestions: presentedLedger.shown.length ? presentedLedger.shown : void 0,
|
|
70144
71566
|
directorSessionId
|
|
70145
71567
|
});
|
|
70146
71568
|
const sendInTurn = (msg2) => {
|
|
@@ -70195,7 +71617,30 @@ function createBridgeMessageHandler(deps) {
|
|
|
70195
71617
|
let exhaustedLimitSignal = null;
|
|
70196
71618
|
let turnTokenUsage = null;
|
|
70197
71619
|
let promptBytes = 0;
|
|
71620
|
+
let contextBudgetGateTriggered = false;
|
|
70198
71621
|
const deferredResults = [];
|
|
71622
|
+
const closureEvents = [];
|
|
71623
|
+
let closureBefore = null;
|
|
71624
|
+
const takeClosureSnapshot = async () => {
|
|
71625
|
+
const snapshot = deps.snapshotProjectObjects;
|
|
71626
|
+
if (!snapshot || !contextProjectSlug) return null;
|
|
71627
|
+
const controller = new AbortController();
|
|
71628
|
+
let timer;
|
|
71629
|
+
try {
|
|
71630
|
+
timer = setTimeout(() => {
|
|
71631
|
+
controller.abort(new Error("snapshot timed out"));
|
|
71632
|
+
}, CLOSURE_SNAPSHOT_TIMEOUT_MS);
|
|
71633
|
+
return await snapshot(contextProjectSlug, { signal: controller.signal });
|
|
71634
|
+
} catch (error51) {
|
|
71635
|
+
process.stderr.write(
|
|
71636
|
+
`[mrrlin-mcp director-bridge] closure_snapshot_failed for ${contextProjectSlug}: ${error51 instanceof Error ? error51.message : String(error51)}
|
|
71637
|
+
`
|
|
71638
|
+
);
|
|
71639
|
+
return null;
|
|
71640
|
+
} finally {
|
|
71641
|
+
if (timer) clearTimeout(timer);
|
|
71642
|
+
}
|
|
71643
|
+
};
|
|
70199
71644
|
let pokeActivity = () => {
|
|
70200
71645
|
};
|
|
70201
71646
|
const forward = (event) => {
|
|
@@ -70207,11 +71652,24 @@ function createBridgeMessageHandler(deps) {
|
|
|
70207
71652
|
if (event.type === "token_usage") {
|
|
70208
71653
|
const usage = event.usage;
|
|
70209
71654
|
turnTokenUsage = addTurnTokenUsage(turnTokenUsage, usage);
|
|
71655
|
+
if (turnTokenUsage) {
|
|
71656
|
+
const totalTokens = (turnTokenUsage.inputTokens ?? 0) + (turnTokenUsage.outputTokens ?? 0);
|
|
71657
|
+
if (totalTokens > 0) {
|
|
71658
|
+
const maxTokens = 2e5;
|
|
71659
|
+
const remainingTokens = maxTokens - totalTokens;
|
|
71660
|
+
if (remainingTokens / maxTokens < 0.03 || remainingTokens < 4e3) {
|
|
71661
|
+
contextBudgetGateTriggered = true;
|
|
71662
|
+
}
|
|
71663
|
+
}
|
|
71664
|
+
}
|
|
70210
71665
|
return;
|
|
70211
71666
|
}
|
|
70212
71667
|
if (event.type === "turn_completed" || event.type === "rate_limits") {
|
|
70213
71668
|
return;
|
|
70214
71669
|
}
|
|
71670
|
+
if (event.type === "assistant" || event.type === "tool_result" || event.type === "tool_use") {
|
|
71671
|
+
closureEvents.push(event);
|
|
71672
|
+
}
|
|
70215
71673
|
outputEvents += 1;
|
|
70216
71674
|
sendInTurn({ type: "event", directorSessionId, event });
|
|
70217
71675
|
};
|
|
@@ -70220,7 +71678,21 @@ function createBridgeMessageHandler(deps) {
|
|
|
70220
71678
|
pokeActivity();
|
|
70221
71679
|
});
|
|
70222
71680
|
const currentTurnBrowserToolIds = /* @__PURE__ */ new Set();
|
|
71681
|
+
let hadErrorOnTurn = false;
|
|
71682
|
+
let confirmedProductiveEvent = false;
|
|
70223
71683
|
const unsubEvent = codexClient.onEvent(threadId, (event) => {
|
|
71684
|
+
if (event.type === "error") {
|
|
71685
|
+
hadErrorOnTurn = true;
|
|
71686
|
+
} else if (!confirmedProductiveEvent && !hadErrorOnTurn) {
|
|
71687
|
+
const isProductive = event.type === "assistant" && typeof event.content === "string" && event.content.trim().length > 0 || event.type === "tool_use" || event.type === "tool_result" || event.type === "result" && event.subtype === "success";
|
|
71688
|
+
if (isProductive) {
|
|
71689
|
+
confirmedProductiveEvent = true;
|
|
71690
|
+
if (desiredInstructionHash && thread.systemInstructionHash !== desiredInstructionHash) {
|
|
71691
|
+
thread.systemInstructionHash = desiredInstructionHash;
|
|
71692
|
+
deps.persistThreads();
|
|
71693
|
+
}
|
|
71694
|
+
}
|
|
71695
|
+
}
|
|
70224
71696
|
if (event.type === "tool_use") {
|
|
70225
71697
|
toolBusy.start(event.tool.id);
|
|
70226
71698
|
if (event.tool.name.startsWith("mcp__mrrlin-browser__browser_")) {
|
|
@@ -70354,6 +71826,7 @@ ${effectiveText}` : effectiveText;
|
|
|
70354
71826
|
turnInput.push({ type: "image", url: imageDataUri });
|
|
70355
71827
|
}
|
|
70356
71828
|
sendInTurn({ type: "turn-executing", directorSessionId, turnId: currentReplayBuf.turnId });
|
|
71829
|
+
closureBefore = await takeClosureSnapshot();
|
|
70357
71830
|
await codexClient.startTurn(thread.threadId, turnInput, thread.reasoningEffort ?? void 0);
|
|
70358
71831
|
await turnFinished;
|
|
70359
71832
|
})();
|
|
@@ -70371,7 +71844,47 @@ ${effectiveText}` : effectiveText;
|
|
|
70371
71844
|
error: zeroOutputLimit.message
|
|
70372
71845
|
});
|
|
70373
71846
|
}
|
|
70374
|
-
|
|
71847
|
+
if (!hadErrorOnTurn && outputEvents > 0) {
|
|
71848
|
+
const recorded = recordTurn(thread.clarificationLedger ?? emptyLedger(), finalAssistantText(closureEvents));
|
|
71849
|
+
thread.clarificationLedger = recorded.ledger;
|
|
71850
|
+
for (const item of recorded.reasked) {
|
|
71851
|
+
process.stderr.write(
|
|
71852
|
+
`[mrrlin-mcp director-bridge] clarification_reasked (x${item.reasked}) for ${directorSessionId}: ${item.question}
|
|
71853
|
+
`
|
|
71854
|
+
);
|
|
71855
|
+
}
|
|
71856
|
+
for (const question of recorded.overflow) {
|
|
71857
|
+
process.stderr.write(
|
|
71858
|
+
`[mrrlin-mcp director-bridge] clarification_overflow for ${directorSessionId}: ${question}
|
|
71859
|
+
`
|
|
71860
|
+
);
|
|
71861
|
+
}
|
|
71862
|
+
for (const item of recorded.dropped) {
|
|
71863
|
+
process.stderr.write(
|
|
71864
|
+
`[mrrlin-mcp director-bridge] clarification_dropped (${item.status}) for ${directorSessionId}: ${item.question}
|
|
71865
|
+
`
|
|
71866
|
+
);
|
|
71867
|
+
}
|
|
71868
|
+
deps.persistThreads();
|
|
71869
|
+
}
|
|
71870
|
+
if (closureBefore && !hadErrorOnTurn && outputEvents > 0) {
|
|
71871
|
+
const closureAfter = await takeClosureSnapshot();
|
|
71872
|
+
const closureText = closureAfter ? closureFor({ before: closureBefore, after: closureAfter, events: closureEvents, operatorText: msg.message ?? "" }) : null;
|
|
71873
|
+
if (closureText) {
|
|
71874
|
+
sendInTurn({ type: "event", directorSessionId, event: { type: "assistant", content: closureText } });
|
|
71875
|
+
}
|
|
71876
|
+
}
|
|
71877
|
+
if (contextBudgetGateTriggered) {
|
|
71878
|
+
sendInTurn({
|
|
71879
|
+
type: "event",
|
|
71880
|
+
directorSessionId,
|
|
71881
|
+
event: {
|
|
71882
|
+
type: "assistant",
|
|
71883
|
+
content: "\n\n\u26A0\uFE0F **Context Budget Gate**: Model context window is near capacity (<3% remaining). Further tool dispatch disabled to preserve conversational integrity and results."
|
|
71884
|
+
}
|
|
71885
|
+
});
|
|
71886
|
+
}
|
|
71887
|
+
const telemetry = turnTokenUsage !== null ? { promptBytes, usage: turnTokenUsage, contextBudgetGateTriggered } : void 0;
|
|
70375
71888
|
for (const event of deferredResults) {
|
|
70376
71889
|
sendInTurn({ type: "event", directorSessionId, event, telemetry });
|
|
70377
71890
|
}
|
|
@@ -70655,7 +72168,7 @@ async function startDirectorBridge() {
|
|
|
70655
72168
|
await tokenRefresher.start();
|
|
70656
72169
|
process.stderr.write("[mrrlin-mcp director-bridge] agent credential active; operator token auto-refresh enabled.\n");
|
|
70657
72170
|
} else {
|
|
70658
|
-
|
|
72171
|
+
let apiToken = envToken ?? readOperatorToken() ?? void 0;
|
|
70659
72172
|
if (envToken) {
|
|
70660
72173
|
try {
|
|
70661
72174
|
persistOperatorToken(envToken);
|
|
@@ -70858,7 +72371,9 @@ async function startDirectorBridge() {
|
|
|
70858
72371
|
reasoningEffort: normalizeReasoningEffort(entry.reasoningEffort),
|
|
70859
72372
|
replay: null,
|
|
70860
72373
|
supersededReplay: null,
|
|
70861
|
-
provider: entry.provider
|
|
72374
|
+
provider: entry.provider,
|
|
72375
|
+
systemInstructionHash: entry.systemInstructionHash ?? null,
|
|
72376
|
+
...entry.clarificationLedger ? { clarificationLedger: entry.clarificationLedger } : {}
|
|
70862
72377
|
});
|
|
70863
72378
|
}
|
|
70864
72379
|
const persistThreads = () => {
|
|
@@ -70870,7 +72385,9 @@ async function startDirectorBridge() {
|
|
|
70870
72385
|
lastUsedAt: thread.lastUsedAt,
|
|
70871
72386
|
model: thread.model,
|
|
70872
72387
|
reasoningEffort: thread.reasoningEffort,
|
|
70873
|
-
provider: thread.provider
|
|
72388
|
+
provider: thread.provider,
|
|
72389
|
+
systemInstructionHash: thread.systemInstructionHash ?? null,
|
|
72390
|
+
...thread.clarificationLedger ? { clarificationLedger: thread.clarificationLedger } : {}
|
|
70874
72391
|
}
|
|
70875
72392
|
])
|
|
70876
72393
|
);
|
|
@@ -71037,6 +72554,35 @@ async function startDirectorBridge() {
|
|
|
71037
72554
|
}
|
|
71038
72555
|
},
|
|
71039
72556
|
listAnsweredQuestions: (projectSlug, taskId) => client.listInboxItems(projectSlug, { status: "acknowledged", kind: "question", taskId }),
|
|
72557
|
+
snapshotProjectObjects: async (projectSlug, opts) => {
|
|
72558
|
+
const signal = opts?.signal;
|
|
72559
|
+
if (signal?.aborted) throw new Error("snapshot aborted");
|
|
72560
|
+
const tasks = await client.listTasks(
|
|
72561
|
+
projectSlug,
|
|
72562
|
+
{ archived: "all", limit: CLOSURE_MAX_SNAPSHOT_TASKS + 1 },
|
|
72563
|
+
{ view: "summary", signal }
|
|
72564
|
+
);
|
|
72565
|
+
if (tasks.length > CLOSURE_MAX_SNAPSHOT_TASKS) {
|
|
72566
|
+
throw new Error(`${tasks.length} tasks exceed the closure snapshot cap (${CLOSURE_MAX_SNAPSHOT_TASKS})`);
|
|
72567
|
+
}
|
|
72568
|
+
const [pages, inbox] = await Promise.all([
|
|
72569
|
+
client.listWikiPages(
|
|
72570
|
+
projectSlug,
|
|
72571
|
+
{ folder: "Specs", limit: CLOSURE_MAX_SNAPSHOT_PAGES + 1 },
|
|
72572
|
+
{ view: "summary", signal }
|
|
72573
|
+
),
|
|
72574
|
+
client.listInboxItems(
|
|
72575
|
+
projectSlug,
|
|
72576
|
+
{ status: "pending", limit: CLOSURE_MAX_SNAPSHOT_INBOX + 1 },
|
|
72577
|
+
{ view: "summary", signal }
|
|
72578
|
+
)
|
|
72579
|
+
]);
|
|
72580
|
+
return {
|
|
72581
|
+
inbox: inbox.length > CLOSURE_MAX_SNAPSHOT_INBOX ? null : new Set(inbox.map((item) => item.id)),
|
|
72582
|
+
tasks: new Map(tasks.map((task) => [task.id, { title: task.title, updatedAt: task.updatedAt ?? null }])),
|
|
72583
|
+
pages: pages.length > CLOSURE_MAX_SNAPSHOT_PAGES ? null : new Map(pages.map((page) => [page.id, { title: page.title, updatedAt: page.updatedAt }]))
|
|
72584
|
+
};
|
|
72585
|
+
},
|
|
71040
72586
|
getProject: (projectSlug) => client.getProject(projectSlug),
|
|
71041
72587
|
// External-agents control plane: the real spawn runner, with the EXTERNAL_AGENTS_CLI operator
|
|
71042
72588
|
// escape hatch bound here (the handlers stay spawn-free and this is the only production seam).
|
|
@@ -71571,10 +73117,17 @@ function renderContextPrefix(context) {
|
|
|
71571
73117
|
if (directorSessionId) payload.directorSessionId = directorSessionId;
|
|
71572
73118
|
if (contextMessages?.length) payload.contextMessages = contextMessages;
|
|
71573
73119
|
if (answeredQuestions) payload.answeredQuestions = answeredQuestions;
|
|
73120
|
+
const openQuestions = context?.openQuestions?.length ? context.openQuestions : void 0;
|
|
73121
|
+
if (openQuestions) payload.openQuestions = openQuestions;
|
|
71574
73122
|
const keys = Object.keys(payload);
|
|
71575
73123
|
if (keys.length > 0) {
|
|
71576
73124
|
parts.push(`Context (JSON): ${JSON.stringify(payload)}`);
|
|
71577
73125
|
}
|
|
73126
|
+
if (openQuestions) {
|
|
73127
|
+
parts.push(
|
|
73128
|
+
"[OPEN QUESTIONS] `openQuestions` lists questions you asked earlier in this chat. For each one, state in this reply whether it is answered (by the operator), assumed (say the default you took) or a blocker (say what it blocks). Do not ask it again unless the answer would change the architecture or the deliverable."
|
|
73129
|
+
);
|
|
73130
|
+
}
|
|
71578
73131
|
return parts.join("\n\n");
|
|
71579
73132
|
}
|
|
71580
73133
|
|
|
@@ -72299,7 +73852,9 @@ function jsonSchemaProperty(schema) {
|
|
|
72299
73852
|
property.enum = jsonSchema.enum;
|
|
72300
73853
|
}
|
|
72301
73854
|
if (typeof jsonSchema.pattern === "string") {
|
|
72302
|
-
|
|
73855
|
+
if (!/\(\?<?[!=]/.test(jsonSchema.pattern)) {
|
|
73856
|
+
property.pattern = jsonSchema.pattern;
|
|
73857
|
+
}
|
|
72303
73858
|
}
|
|
72304
73859
|
return property;
|
|
72305
73860
|
}
|
|
@@ -72909,7 +74464,8 @@ function installService(deps) {
|
|
|
72909
74464
|
return { ok: false };
|
|
72910
74465
|
}
|
|
72911
74466
|
const claudeBin = deps.which("claude");
|
|
72912
|
-
const
|
|
74467
|
+
const agyBin = deps.which("agy");
|
|
74468
|
+
const resolvedBins = [...bins, claudeBin, agyBin].filter((b) => b !== null);
|
|
72913
74469
|
const isWin = import_node_os18.default.platform() === "win32";
|
|
72914
74470
|
const systemDirs = isWin ? (deps.env.PATH || "").split(import_node_path39.default.delimiter).filter(Boolean) : SYSTEM_PATH_DIRS;
|
|
72915
74471
|
const pathEnv = Array.from(
|
|
@@ -73184,6 +74740,24 @@ Missing required flag(s): ${missing.join(
|
|
|
73184
74740
|
}
|
|
73185
74741
|
|
|
73186
74742
|
// src/bin.ts
|
|
74743
|
+
function augmentProcessPathWithCommonDirs() {
|
|
74744
|
+
const isWin = process.platform === "win32";
|
|
74745
|
+
if (isWin) return;
|
|
74746
|
+
const home = process.env.HOME || (0, import_node_os20.homedir)();
|
|
74747
|
+
const candidates = [
|
|
74748
|
+
import_node_path43.default.join(home, ".local", "bin"),
|
|
74749
|
+
import_node_path43.default.join(home, ".cargo", "bin"),
|
|
74750
|
+
"/usr/local/bin",
|
|
74751
|
+
"/opt/homebrew/bin"
|
|
74752
|
+
];
|
|
74753
|
+
const existing = (process.env.PATH || "").split(import_node_path43.default.delimiter).filter(Boolean);
|
|
74754
|
+
const existingSet = new Set(existing);
|
|
74755
|
+
const toAdd = candidates.filter((d) => !existingSet.has(d) && (0, import_node_fs25.existsSync)(d));
|
|
74756
|
+
if (toAdd.length > 0) {
|
|
74757
|
+
process.env.PATH = [...existing, ...toAdd].join(import_node_path43.default.delimiter);
|
|
74758
|
+
}
|
|
74759
|
+
}
|
|
74760
|
+
augmentProcessPathWithCommonDirs();
|
|
73187
74761
|
function pm2Runner(args) {
|
|
73188
74762
|
const r = (0, import_node_child_process22.spawnSync)("pm2", args, { encoding: "utf8" });
|
|
73189
74763
|
process.stderr.write(r.stdout ?? "");
|
|
@@ -73690,6 +75264,10 @@ main().catch((error51) => {
|
|
|
73690
75264
|
`);
|
|
73691
75265
|
process.exit(1);
|
|
73692
75266
|
});
|
|
75267
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
75268
|
+
0 && (module.exports = {
|
|
75269
|
+
augmentProcessPathWithCommonDirs
|
|
75270
|
+
});
|
|
73693
75271
|
/*! Bundled license information:
|
|
73694
75272
|
|
|
73695
75273
|
content-type/index.js:
|