@p4code/cli 0.5.12 → 0.5.13
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.mjs +603 -41
- package/dist/client/apple-touch-icon.png +0 -0
- package/dist/client/assets/{DiffPanel-BhA4tVdu.js → DiffPanel-DDYngqV4.js} +2 -2
- package/dist/client/assets/{FilePreviewPanel-CzCo6FbL.js → FilePreviewPanel-7E2gCB2m.js} +2 -2
- package/dist/client/assets/{PreviewPanel-mAlnfcs-.js → PreviewPanel-Be5PEQnT.js} +2 -2
- package/dist/client/assets/{PullRequestCodeTab-Bxsoc8RP.js → PullRequestCodeTab-Bb2LrIf6.js} +2 -2
- package/dist/client/assets/{fileCommentAnnotations-DLmItgXx.js → fileCommentAnnotations-C497TzTs.js} +2 -2
- package/dist/client/assets/{index-O022yjIn.js → index-9kIqWgcJ.js} +225 -214
- package/dist/client/assets/previewAssetResource-DIMp8MLe.js +48 -0
- package/dist/client/assets/{renderFileChildren-DirXcMDq.js → renderFileChildren-DT7LLxbw.js} +2 -2
- package/dist/client/assets/{toggle-group-DyERzp83.js → toggle-group-Rp7tI9mQ.js} +2 -2
- package/dist/client/favicon-16x16.png +0 -0
- package/dist/client/favicon-32x32.png +0 -0
- package/dist/client/favicon.ico +0 -0
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
- package/dist/client/assets/previewAssetResource-BxWrAHMS.js +0 -48
package/dist/bin.mjs
CHANGED
|
@@ -265,7 +265,7 @@ const make$120 = () => {
|
|
|
265
265
|
const layer$122 = Layer.sync(NetService, make$120);
|
|
266
266
|
//#endregion
|
|
267
267
|
//#region package.json
|
|
268
|
-
var version$1 = "0.5.
|
|
268
|
+
var version$1 = "0.5.13";
|
|
269
269
|
//#endregion
|
|
270
270
|
//#region src/config.ts
|
|
271
271
|
/**
|
|
@@ -12516,6 +12516,16 @@ const ThreadRenameResult = Schema$1.Struct({
|
|
|
12516
12516
|
title: TrimmedNonEmptyString
|
|
12517
12517
|
});
|
|
12518
12518
|
const ThreadPlanUpdateInput = Schema$1.Struct({
|
|
12519
|
+
/**
|
|
12520
|
+
* The thread whose banner this plan belongs to.
|
|
12521
|
+
*
|
|
12522
|
+
* Omitted means the caller's own thread, which is every ordinary use. A
|
|
12523
|
+
* Fusion supervisor names its paired builder here: the plan is the
|
|
12524
|
+
* supervisor's to author, but it describes the builder's work, so it has to
|
|
12525
|
+
* land on the builder's banner. Only a thread this session holds a watch
|
|
12526
|
+
* grant for is accepted.
|
|
12527
|
+
*/
|
|
12528
|
+
threadId: Schema$1.optional(ThreadId.annotate({ description: "The thread to update. Omit for this session's own thread. Only a thread this session may watch can be named here." })),
|
|
12519
12529
|
explanation: Schema$1.optional(TrimmedNonEmptyString),
|
|
12520
12530
|
plan: Schema$1.Array(Schema$1.Struct({
|
|
12521
12531
|
step: TrimmedNonEmptyString,
|
|
@@ -12812,7 +12822,22 @@ const ThreadReviewItem = Schema$1.Union([Schema$1.Struct({
|
|
|
12812
12822
|
/** Apply append/replace by message identity across page boundaries. */
|
|
12813
12823
|
operation: Schema$1.Literals(["append", "replace"]),
|
|
12814
12824
|
complete: Schema$1.Boolean,
|
|
12815
|
-
text: Schema$1.String
|
|
12825
|
+
text: Schema$1.String,
|
|
12826
|
+
/**
|
|
12827
|
+
* What the message carried besides text.
|
|
12828
|
+
*
|
|
12829
|
+
* Present only when there was something: a message whose fragments are
|
|
12830
|
+
* merged by identity would otherwise lose every trace of an image or a
|
|
12831
|
+
* document the builder attached. Bounded by
|
|
12832
|
+
* {@link THREAD_REVIEW_ITEM_MAX_ATTACHMENTS} so the item stays trimmable -
|
|
12833
|
+
* only its text shrinks under the byte cap, so nothing else may be able to
|
|
12834
|
+
* grow without limit.
|
|
12835
|
+
*/
|
|
12836
|
+
attachments: Schema$1.optionalKey(Schema$1.Array(Schema$1.Struct({
|
|
12837
|
+
type: Schema$1.String,
|
|
12838
|
+
name: Schema$1.String,
|
|
12839
|
+
sizeBytes: NonNegativeInt
|
|
12840
|
+
})))
|
|
12816
12841
|
}), Schema$1.Struct({
|
|
12817
12842
|
...ReviewSourceFields,
|
|
12818
12843
|
kind: Schema$1.Literal("event"),
|
|
@@ -12857,6 +12882,84 @@ const ThreadWatchReviewResult = Schema$1.Struct({
|
|
|
12857
12882
|
*/
|
|
12858
12883
|
summary: Schema$1.optionalKey(Schema$1.NullOr(ThreadReviewSummary))
|
|
12859
12884
|
});
|
|
12885
|
+
/**
|
|
12886
|
+
* How a review ended, from the supervisor's own account of it.
|
|
12887
|
+
*
|
|
12888
|
+
* Four states rather than a boolean because they are acted on differently: a
|
|
12889
|
+
* summary that arrived is evidence, a fallback is a working review that cost
|
|
12890
|
+
* full context, a failure is a misconfiguration worth fixing, and a read with
|
|
12891
|
+
* no report at all is the only one that cannot be told from a supervisor that
|
|
12892
|
+
* simply never reported - which is why the absence has to be visible too.
|
|
12893
|
+
*/
|
|
12894
|
+
const ThreadEvidenceReportOutcome = Schema$1.Literals([
|
|
12895
|
+
"summarized",
|
|
12896
|
+
"fallback",
|
|
12897
|
+
"failed"
|
|
12898
|
+
]);
|
|
12899
|
+
/** Bounded so one report cannot dominate a thread's activity storage. */
|
|
12900
|
+
const THREAD_EVIDENCE_REPORT_MAX_CHARS = 8e3;
|
|
12901
|
+
const ThreadEvidenceReportInput = Schema$1.Struct({
|
|
12902
|
+
/** The watched thread the summary covers, not the reporting thread. */
|
|
12903
|
+
threadId: ThreadId,
|
|
12904
|
+
firstSequence: NonNegativeInt,
|
|
12905
|
+
lastSequence: NonNegativeInt,
|
|
12906
|
+
/**
|
|
12907
|
+
* The fixed review boundary the pages were read against.
|
|
12908
|
+
*
|
|
12909
|
+
* Identity, not description: the evidence-read rows are keyed on it, so a
|
|
12910
|
+
* report that guessed from its own last sequence would look for a baseline
|
|
12911
|
+
* that never existed whenever the range ended on a filtered event.
|
|
12912
|
+
*/
|
|
12913
|
+
throughSequence: NonNegativeInt,
|
|
12914
|
+
outcome: ThreadEvidenceReportOutcome,
|
|
12915
|
+
/** The summarizer's exact text. Required when the outcome is summarized. */
|
|
12916
|
+
summary: Schema$1.optional(Schema$1.String),
|
|
12917
|
+
/** The model the subagent ran on, when one was spawned. */
|
|
12918
|
+
model: Schema$1.optional(Schema$1.String),
|
|
12919
|
+
/** Why the summary is missing or degraded. Required unless summarized. */
|
|
12920
|
+
reason: Schema$1.optional(Schema$1.String)
|
|
12921
|
+
});
|
|
12922
|
+
/**
|
|
12923
|
+
* The supervisor's own context around the read, correlated by the server.
|
|
12924
|
+
*
|
|
12925
|
+
* Self-reported numbers would be worth little, so these are read from the
|
|
12926
|
+
* reporting thread's projected context-window rows: `before` is what was
|
|
12927
|
+
* recorded when the evidence read happened, `after` what is recorded when the
|
|
12928
|
+
* report lands. `delta` can be negative when a compaction ran in between, and
|
|
12929
|
+
* is null whenever no read-time baseline exists.
|
|
12930
|
+
*/
|
|
12931
|
+
const ThreadEvidenceReportContext = Schema$1.Struct({
|
|
12932
|
+
/**
|
|
12933
|
+
* Null when no reading was recorded at read time.
|
|
12934
|
+
*
|
|
12935
|
+
* Never the report-time value standing in for itself: a delta of zero read
|
|
12936
|
+
* as measurement would be a lie about a review that cost context.
|
|
12937
|
+
*/
|
|
12938
|
+
usedTokensBefore: Schema$1.NullOr(NonNegativeInt),
|
|
12939
|
+
usedTokensAfter: NonNegativeInt,
|
|
12940
|
+
/** Null exactly when `usedTokensBefore` is. */
|
|
12941
|
+
deltaTokens: Schema$1.NullOr(Schema$1.Number),
|
|
12942
|
+
maxTokens: Schema$1.NullOr(NonNegativeInt),
|
|
12943
|
+
capacityPercent: Schema$1.NullOr(Schema$1.Number)
|
|
12944
|
+
});
|
|
12945
|
+
Schema$1.Struct({
|
|
12946
|
+
watchedThreadId: ThreadId,
|
|
12947
|
+
firstSequence: NonNegativeInt,
|
|
12948
|
+
lastSequence: NonNegativeInt,
|
|
12949
|
+
throughSequence: NonNegativeInt,
|
|
12950
|
+
outcome: ThreadEvidenceReportOutcome,
|
|
12951
|
+
summary: Schema$1.NullOr(Schema$1.String),
|
|
12952
|
+
/** True when a snapshot dropped the text of a superseded report. */
|
|
12953
|
+
summaryDropped: Schema$1.optionalKey(Schema$1.Boolean),
|
|
12954
|
+
model: Schema$1.NullOr(Schema$1.String),
|
|
12955
|
+
reason: Schema$1.NullOr(Schema$1.String),
|
|
12956
|
+
/** Null when no context-window row exists yet for the reporting thread. */
|
|
12957
|
+
context: Schema$1.NullOr(ThreadEvidenceReportContext)
|
|
12958
|
+
});
|
|
12959
|
+
const ThreadEvidenceReportResult = Schema$1.Struct({
|
|
12960
|
+
recorded: Schema$1.Boolean,
|
|
12961
|
+
context: Schema$1.NullOr(ThreadEvidenceReportContext)
|
|
12962
|
+
});
|
|
12860
12963
|
var WatchToolUnavailableError = class extends Schema$1.TaggedErrorClass()("WatchToolUnavailableError", {
|
|
12861
12964
|
capability: Schema$1.Literal("watch"),
|
|
12862
12965
|
environmentId: EnvironmentId,
|
|
@@ -20405,12 +20508,46 @@ function dropSupersededToolUpdatedActivities(activities) {
|
|
|
20405
20508
|
return completionIndex === void 0 || completionIndex <= index || !completionPreservesUpdate(activity, activities[completionIndex]);
|
|
20406
20509
|
});
|
|
20407
20510
|
}
|
|
20511
|
+
/**
|
|
20512
|
+
* Keeps only the newest evidence report's text per watched thread.
|
|
20513
|
+
*
|
|
20514
|
+
* A report carries the summarizer's exact words, and a snapshot retains
|
|
20515
|
+
* hundreds of activities: left whole, a long-running pair would put megabytes
|
|
20516
|
+
* of superseded diagnostics on the wire on every reconnect. Superseded rows
|
|
20517
|
+
* keep their outcome, range and context - everything the work log renders
|
|
20518
|
+
* collapsed - and lose only the body, flagged so the client can say the text
|
|
20519
|
+
* is no longer retained rather than that the review produced none.
|
|
20520
|
+
*/
|
|
20521
|
+
function dropSupersededEvidenceSummaries(activities) {
|
|
20522
|
+
const latestIndexByWatched = /* @__PURE__ */ new Map();
|
|
20523
|
+
for (let index = 0; index < activities.length; index += 1) {
|
|
20524
|
+
const activity = activities[index];
|
|
20525
|
+
if (activity.kind !== "fusion.evidence.summary") continue;
|
|
20526
|
+
const watchedThreadId = asRecord$7(activity.payload)?.watchedThreadId;
|
|
20527
|
+
if (typeof watchedThreadId === "string") latestIndexByWatched.set(watchedThreadId, index);
|
|
20528
|
+
}
|
|
20529
|
+
if (latestIndexByWatched.size === 0) return activities;
|
|
20530
|
+
return activities.map((activity, index) => {
|
|
20531
|
+
if (activity.kind !== "fusion.evidence.summary") return activity;
|
|
20532
|
+
const payload = asRecord$7(activity.payload);
|
|
20533
|
+
const watchedThreadId = payload?.watchedThreadId;
|
|
20534
|
+
if (!payload || typeof watchedThreadId !== "string" || latestIndexByWatched.get(watchedThreadId) === index || payload.summary === null || payload.summary === void 0) return activity;
|
|
20535
|
+
return {
|
|
20536
|
+
...activity,
|
|
20537
|
+
payload: {
|
|
20538
|
+
...payload,
|
|
20539
|
+
summary: null,
|
|
20540
|
+
summaryDropped: true
|
|
20541
|
+
}
|
|
20542
|
+
};
|
|
20543
|
+
});
|
|
20544
|
+
}
|
|
20408
20545
|
function projectThreadDetailSnapshot(snapshot) {
|
|
20409
20546
|
return {
|
|
20410
20547
|
...snapshot,
|
|
20411
20548
|
thread: {
|
|
20412
20549
|
...snapshot.thread,
|
|
20413
|
-
activities: dropSupersededToolUpdatedActivities(dropStaleContextWindowActivities(snapshot.thread.activities).map(projectActivityPayload))
|
|
20550
|
+
activities: dropSupersededEvidenceSummaries(dropSupersededToolUpdatedActivities(dropStaleContextWindowActivities(snapshot.thread.activities).map(projectActivityPayload)))
|
|
20414
20551
|
}
|
|
20415
20552
|
};
|
|
20416
20553
|
}
|
|
@@ -33071,7 +33208,7 @@ const layer$87 = Layer.effect(AssetSync, make$103);
|
|
|
33071
33208
|
* runtime template. Applies to assistant prose only: code blocks, commits,
|
|
33072
33209
|
* PRs, error strings, and safety-critical text stay uncompressed.
|
|
33073
33210
|
*/
|
|
33074
|
-
const COMPRESS_SHARED_RULES = `Respond terse.
|
|
33211
|
+
const COMPRESS_SHARED_RULES = `Respond terse like smart caveman. All technical substance stay. Only fluff die.
|
|
33075
33212
|
|
|
33076
33213
|
## Persistence
|
|
33077
33214
|
|
|
@@ -41404,6 +41541,37 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
41404
41541
|
detached_at AS "detachedAt"
|
|
41405
41542
|
FROM thread_pairs
|
|
41406
41543
|
ORDER BY created_at ASC, pair_id ASC
|
|
41544
|
+
`
|
|
41545
|
+
});
|
|
41546
|
+
const LatestContextWindowRow = Schema$1.Struct({
|
|
41547
|
+
usedTokens: Schema$1.Number,
|
|
41548
|
+
maxTokens: Schema$1.NullOr(Schema$1.Number)
|
|
41549
|
+
});
|
|
41550
|
+
const getLatestContextWindowRow = SqlSchema.findOneOption({
|
|
41551
|
+
Request: ThreadId,
|
|
41552
|
+
Result: LatestContextWindowRow,
|
|
41553
|
+
execute: (threadId) => sql`
|
|
41554
|
+
SELECT
|
|
41555
|
+
json_extract(payload_json, '$.usedTokens') AS "usedTokens",
|
|
41556
|
+
json_extract(payload_json, '$.maxTokens') AS "maxTokens"
|
|
41557
|
+
FROM projection_thread_activities
|
|
41558
|
+
WHERE thread_id = ${threadId}
|
|
41559
|
+
AND kind = 'context-window.updated'
|
|
41560
|
+
AND json_extract(payload_json, '$.usedTokens') >= 0
|
|
41561
|
+
ORDER BY created_at DESC, rowid DESC
|
|
41562
|
+
LIMIT 1
|
|
41563
|
+
`
|
|
41564
|
+
});
|
|
41565
|
+
const EvidenceReadContextRow = Schema$1.Struct({ usedTokensAtRead: Schema$1.Number });
|
|
41566
|
+
const getEvidenceReadContextRow = SqlSchema.findOneOption({
|
|
41567
|
+
Request: Schema$1.String,
|
|
41568
|
+
Result: EvidenceReadContextRow,
|
|
41569
|
+
execute: (activityId) => sql`
|
|
41570
|
+
SELECT json_extract(payload_json, '$.usedTokensAtRead') AS "usedTokensAtRead"
|
|
41571
|
+
FROM projection_thread_activities
|
|
41572
|
+
WHERE activity_id = ${activityId}
|
|
41573
|
+
AND json_extract(payload_json, '$.usedTokensAtRead') >= 0
|
|
41574
|
+
LIMIT 1
|
|
41407
41575
|
`
|
|
41408
41576
|
});
|
|
41409
41577
|
const getThreadPairRow = SqlSchema.findOneOption({
|
|
@@ -43193,6 +43361,8 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
43193
43361
|
messageCreatedAt: row.messageCreatedAt
|
|
43194
43362
|
})) };
|
|
43195
43363
|
});
|
|
43364
|
+
const getLatestThreadContextWindow = (threadId) => getLatestContextWindowRow(threadId).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getLatestThreadContextWindow:query", "ProjectionSnapshotQuery.getLatestThreadContextWindow:decodeRow")));
|
|
43365
|
+
const getEvidenceReadContext = (activityId) => getEvidenceReadContextRow(activityId).pipe(Effect.map(Option.map((row) => row.usedTokensAtRead)), Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getEvidenceReadContext:query", "ProjectionSnapshotQuery.getEvidenceReadContext:decodeRow")));
|
|
43196
43366
|
const getThreadPairById = (pairId) => getThreadPairRow(pairId).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadPairById:query", "ProjectionSnapshotQuery.getThreadPairById:decodeRow")));
|
|
43197
43367
|
const getThreadShellById = (threadId) => Effect.gen(function* () {
|
|
43198
43368
|
const [threadRow, latestTurnRow, sessionRow] = yield* Effect.all([
|
|
@@ -43418,7 +43588,9 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
43418
43588
|
getThreadDetailById,
|
|
43419
43589
|
getHandoffContext,
|
|
43420
43590
|
getBtwContext,
|
|
43421
|
-
getThreadDetailSnapshot
|
|
43591
|
+
getThreadDetailSnapshot,
|
|
43592
|
+
getLatestThreadContextWindow,
|
|
43593
|
+
getEvidenceReadContext
|
|
43422
43594
|
};
|
|
43423
43595
|
});
|
|
43424
43596
|
const OrchestrationProjectionSnapshotQueryLive = Layer.effect(ProjectionSnapshotQuery, makeProjectionSnapshotQuery).pipe(Layer.provide(layer$86));
|
|
@@ -43482,6 +43654,38 @@ function payloadPreview(payload) {
|
|
|
43482
43654
|
truncated
|
|
43483
43655
|
};
|
|
43484
43656
|
}
|
|
43657
|
+
/**
|
|
43658
|
+
* Context-window rows are bookkeeping, not work.
|
|
43659
|
+
*
|
|
43660
|
+
* Every turn emits a stream of them and a single one can be large enough to
|
|
43661
|
+
* truncate, so leaving them in spends a reviewer's page budget on a token
|
|
43662
|
+
* meter they did not ask about. The prefix test covers the truncated case,
|
|
43663
|
+
* where the payload arrives as a JSON fragment that can no longer be parsed
|
|
43664
|
+
* but still carries the kind near its head.
|
|
43665
|
+
*/
|
|
43666
|
+
const CONTEXT_WINDOW_ACTIVITY_MARKER = "\"kind\":\"context-window.updated\"";
|
|
43667
|
+
function isContextWindowBookkeeping(type, payload, sourceTruncated) {
|
|
43668
|
+
if (type !== "thread.activity-appended") return false;
|
|
43669
|
+
if (sourceTruncated) return typeof payload === "string" && payload.includes(CONTEXT_WINDOW_ACTIVITY_MARKER);
|
|
43670
|
+
const record = payload;
|
|
43671
|
+
return typeof record === "object" && record !== null && record.activity?.kind === "context-window.updated";
|
|
43672
|
+
}
|
|
43673
|
+
/**
|
|
43674
|
+
* The trace an attachment leaves on a merged message.
|
|
43675
|
+
*
|
|
43676
|
+
* Fragments of one message collapse into a single item by identity, so an
|
|
43677
|
+
* image or a document has nowhere else to appear. Name and size are what a
|
|
43678
|
+
* reviewer needs to ask for it at source; the bytes themselves never belong on
|
|
43679
|
+
* a review page.
|
|
43680
|
+
*/
|
|
43681
|
+
function attachmentEvidence(attachments) {
|
|
43682
|
+
if (!attachments?.length) return void 0;
|
|
43683
|
+
return attachments.slice(0, 10).map((attachment) => ({
|
|
43684
|
+
type: attachment.type,
|
|
43685
|
+
name: attachment.name.slice(0, 120),
|
|
43686
|
+
sizeBytes: attachment.sizeBytes
|
|
43687
|
+
}));
|
|
43688
|
+
}
|
|
43485
43689
|
function fitItem(item) {
|
|
43486
43690
|
let result = item;
|
|
43487
43691
|
let bytes = Buffer.byteLength(JSON.stringify(result));
|
|
@@ -43518,7 +43722,7 @@ function createThreadReviewPage(input) {
|
|
|
43518
43722
|
}));
|
|
43519
43723
|
function offer(event, sourceTruncated = false) {
|
|
43520
43724
|
if (event.sequence > input.throughSequence) return false;
|
|
43521
|
-
if (event.aggregateKind !== "thread" || event.aggregateId !== input.threadId) {
|
|
43725
|
+
if (event.aggregateKind !== "thread" || event.aggregateId !== input.threadId || isContextWindowBookkeeping(event.type, event.payload, sourceTruncated)) {
|
|
43522
43726
|
nextAfterSequence = event.sequence;
|
|
43523
43727
|
scannedEvents += 1;
|
|
43524
43728
|
return true;
|
|
@@ -43526,7 +43730,7 @@ function createThreadReviewPage(input) {
|
|
|
43526
43730
|
let item;
|
|
43527
43731
|
let index;
|
|
43528
43732
|
let messageKey;
|
|
43529
|
-
if (event.type === "thread.message-sent" && !sourceTruncated && isMessagePayload(event.payload) && event.payload.role === "assistant" &&
|
|
43733
|
+
if (event.type === "thread.message-sent" && !sourceTruncated && isMessagePayload(event.payload) && event.payload.role === "assistant" && event.payload.messageId.length + (event.payload.turnId?.length ?? 0) <= MAX_MESSAGE_IDENTITY_LENGTH) {
|
|
43530
43734
|
const payload = event.payload;
|
|
43531
43735
|
messageKey = JSON.stringify([payload.turnId, payload.messageId]);
|
|
43532
43736
|
index = messageIndexes.get(messageKey);
|
|
@@ -43534,6 +43738,7 @@ function createThreadReviewPage(input) {
|
|
|
43534
43738
|
const previousMessage = previous?.kind === "message" ? previous : void 0;
|
|
43535
43739
|
const replaces = !payload.streaming && payload.text.length > 0;
|
|
43536
43740
|
const part = excerpt(payload.text, THREAD_REVIEW_ITEM_MAX_BYTES);
|
|
43741
|
+
const attachments = attachmentEvidence(payload.attachments) ?? previousMessage?.attachments;
|
|
43537
43742
|
item = fitItem({
|
|
43538
43743
|
kind: "message",
|
|
43539
43744
|
firstSequence: previousMessage?.firstSequence ?? event.sequence,
|
|
@@ -43544,7 +43749,8 @@ function createThreadReviewPage(input) {
|
|
|
43544
43749
|
operation: replaces ? "replace" : previousMessage?.operation ?? "append",
|
|
43545
43750
|
complete: !payload.streaming,
|
|
43546
43751
|
text: replaces ? part : (previousMessage?.text ?? "") + part,
|
|
43547
|
-
truncated: part !== payload.text || !replaces && (previousMessage?.truncated ?? false)
|
|
43752
|
+
truncated: part !== payload.text || !replaces && (previousMessage?.truncated ?? false),
|
|
43753
|
+
...attachments === void 0 ? {} : { attachments }
|
|
43548
43754
|
});
|
|
43549
43755
|
} else {
|
|
43550
43756
|
const preview = sourceTruncated && typeof event.payload === "string" ? {
|
|
@@ -59043,14 +59249,14 @@ const readWorkflowScript = Effect.fn("orchestration.readWorkflowScript")(functio
|
|
|
59043
59249
|
* written as if the setting applied only to user-facing answers.
|
|
59044
59250
|
*/
|
|
59045
59251
|
const FUSION_PAIR_COMMUNICATION_INSTRUCTIONS = `Everything you send the other half of the pair obeys the thread's active response-compression mode, exactly as a user-facing answer does: phase reports, advice, reviews, questions, and handoffs. When the mode is off, write normal prose. Compression governs form only - never drop a requirement, a negation, a number or unit, a file, symbol, or command name, code, security or warning text, the evidence behind a claim, or an exact protocol heading or token in order to save words.`;
|
|
59046
|
-
const FUSION_BUILDER_INSTRUCTIONS = `You are Fusion Builder in an already-created native server pair. Server owns pairing and coordination. Follow the current turn's [fusion-review-policy] block when present; it overrides the phase-boundary scheduling rules below. Do not inspect or invoke the Fusion skill, create/pair/rename threads, or announce/setup Fusion. Start the user's task directly.
|
|
59047
|
-
const FUSION_WATCHER_TOOL_INSTRUCTIONS = `Supervisor tools belong to the p4-code MCP server: thread_watch_review, thread_watch_events, thread_advise, and thread_gate_respond. If absent from the visible tool list, first use the available tool search/discovery facility to load those exact tools, including provider-prefixed names; absence from the initial list does not establish unavailability. If discovery or a call fails, report the exact missing capability or error and the need to restore the p4-code MCP connection. Do not offer manual relay, draft or implement the task yourself, or abandon the supervisor role as a workaround. For an actionable user requirement, read the relevant builder evidence and relay it through thread_advise; when a gate is open, answer through thread_gate_respond instead. Structure every actionable supervisor advice message with these Markdown headings, in order:
|
|
59252
|
+
const FUSION_BUILDER_INSTRUCTIONS = `You are Fusion Builder in an already-created native server pair. Server owns pairing and coordination. Follow the current turn's [fusion-review-policy] block when present; it overrides the phase-boundary scheduling rules below. Do not inspect or invoke the Fusion skill, create/pair/rename threads, or announce/setup Fusion. Start the user's task directly. The phase plan is not yours: Supervisor authors it, orders it, and moves every phase status, and it renders in your task banner from there. Never create or edit a phase list yourself - not with TaskCreate, TaskUpdate, TodoWrite, update_plan, the MCP task board tools, or thread_plan_update - and never mark your own phase complete. Work the one phase Supervisor put in progress, and report what you did. Step-level notes for your own use are fine as long as they never become a phase list. Complete exactly one phase per turn, and finish the whole phase in that turn rather than stopping early. Do not run tests, typecheck, lint, or builds per phase; write the tests the change needs, then run verification once in the final phase over the whole task. Exception: a phase whose own correctness is unclear may run the single narrowest check that resolves it. End every phase turn with a report of at most six non-empty lines: the phase you finished, what changed and where, what you verified with its result, and anything unresolved. One line each, every fact once, no restatement of the plan or of advice you were given. Code, exact error text and security warnings are exempt from the cap. Do not start the next phase in the same turn. Server then wakes the paired Supervisor, which resumes you through ${FUSION_ADVICE_PROMPT_PREFIX}; a user message may also revise or resume the work. Final phase verifies the entire task against the original request and labels it ready for whole-task review. Supervisor is unreachable during your turn. Never spawn/use another Supervisor thread/subagent or attribute Supervisor decisions without ${FUSION_ADVICE_PROMPT_PREFIX}. Within the current phase, continue when straightforward or evidence is clear. For a concrete unresolved tradeoff, correctness risk, or design decision materially needing judgment, stop safely before the risky choice; final response states the exact question and why review is needed. Evaluate/follow Supervisor advice unless conflicting with user request or verified repo state. ${FUSION_PAIR_COMMUNICATION_INSTRUCTIONS}`;
|
|
59253
|
+
const FUSION_WATCHER_TOOL_INSTRUCTIONS = `Supervisor tools belong to the p4-code MCP server: thread_watch_review, thread_watch_events, thread_evidence_report, thread_plan_update, thread_advise, and thread_gate_respond. If absent from the visible tool list, first use the available tool search/discovery facility to load those exact tools, including provider-prefixed names; absence from the initial list does not establish unavailability. If discovery or a call fails, report the exact missing capability or error and the need to restore the p4-code MCP connection. Do not offer manual relay, draft or implement the task yourself, or abandon the supervisor role as a workaround. For an actionable user requirement, read the relevant builder evidence and relay it through thread_advise; when a gate is open, answer through thread_gate_respond instead. Structure every actionable supervisor advice message with these Markdown headings, in order:
|
|
59048
59254
|
## Recommendation
|
|
59049
59255
|
State the decision or correction in one sentence. For a phase review, say whether the phase is approved or needs changes.
|
|
59050
59256
|
## Reason
|
|
59051
59257
|
Give concise evidence, relevant file/line references, and any unresolved risk. Distinguish observed facts from assumptions; do not claim checks you did not run. Before prescribing a blocking product fix, identify the violated requirement and evidence of product ownership. If ownership is uncertain, state the hypothesis and request the smallest discriminating check, such as comparing the unchanged baseline or another app, rather than prescribing a speculative fix. Do not block a product fix on an artifact reproduced outside the product unless evidence still ties it to an unmet task requirement.
|
|
59052
59258
|
## Next step
|
|
59053
|
-
Give ordered, concrete actions for the builder, including the next phase and its completion condition. Preserve user requirements, constraints, and delivery authorization. Before repeating an objection, compare the builder's response and intervening changes with the prior finding; cite new evidence and explain why the builder's response does not resolve it. Without new evidence and an actionable next step, do not resend the objection or repeat passed checks; report the unresolved state in your own thread and end. A supported unresolved blocker remains unapproved; avoiding repetition never justifies approval. For an open gate, follow its response and escalation protocol.
|
|
59259
|
+
Give ordered, concrete actions for the builder, including the next phase and its completion condition. Preserve user requirements, constraints, and delivery authorization. Before repeating an objection, compare the builder's response and intervening changes with the prior finding; cite new evidence and explain why the builder's response does not resolve it. Without new evidence and an actionable next step, do not resend the objection or repeat passed checks; report the unresolved state in your own thread and end. A supported unresolved blocker remains unapproved; avoiding repetition never justifies approval. For an open gate, follow its response and escalation protocol. One line per section is the target and three is the ceiling; omit empty bullets and repeated status. Quote only the evidence the decision rests on, once, with its file:line or sequence. Exact errors, code and security text are exempt. Formatting does not authorize an otherwise unnecessary advice turn or replace an exact protocol response.`;
|
|
59054
59260
|
/**
|
|
59055
59261
|
* The directive that makes the supervisor read evidence through a subagent.
|
|
59056
59262
|
*
|
|
@@ -59058,8 +59264,8 @@ Give ordered, concrete actions for the builder, including the next phase and its
|
|
|
59058
59264
|
* turn off: with `Review evidence` off the supervisor reads the range itself,
|
|
59059
59265
|
* which is what it did before this existed.
|
|
59060
59266
|
*/
|
|
59061
|
-
const FUSION_EVIDENCE_SUMMARY_INSTRUCTIONS = `Read the builder's evidence through a summarizer subagent rather than into your own context: spawn one subagent with the evidence range and have it call thread_watch_review itself, paging with nextAfterSequence against a fixed throughSequence until hasMore is false, and return a summary. Your tool grants reach it, so it can read the paired builder thread exactly as you can. Instruct that subagent that thread_watch_review and thread_watch_events may be absent from its visible tool list and that absence does not establish unavailability: it must first use the available tool search/discovery facility to load those exact tools by name, including provider-prefixed names, and if discovery itself fails it must report that exact error rather than reporting no such tool. Require of that summary: every file path touched with the scale of the change to each, every command run and whether it succeeded or failed, check and test results with their counts, exact error text quoted rather than characterized, the builder's completion claims marked as claims, and anything the builder flagged as uncertain, blocked or unresolved. Over a multi-turn range it must also carry unresolved objections of yours and whether each was answered, phases completed versus reopened, claims never verified, and files touched repeatedly. Every statement cites the sequences behind it so you can check any one of them with a targeted thread_watch_events read. Fail open: if no subagent is available, the spawn errors, it times out, or what comes back is empty or does not cite sequences, read the range yourself and review from the raw evidence. Never skip or shorten a review because a summary was unavailable. Record which fallback you took: a subagent that could not load the watch tools is a misconfiguration worth naming in your report, and it is not the same as a spawn that errored or timed out.`;
|
|
59062
|
-
const FUSION_WATCHER_INSTRUCTIONS = `You are Fusion Supervisor (watcher) in an already-created native server pair. Follow the current turn's [fusion-review-policy] block when present; it overrides phase scheduling. Server owns pairing and coordination and wakes you with ${FUSION_REVIEW_PROMPT_PREFIX} or ${FUSION_GATE_PROMPT_PREFIX} prompts at builder turn boundaries. A plain message outside such a wake may arrive after your conversational memory of the pair is gone; its [fusion-pair] metadata block is authoritative: the builder thread exists and is the counterpart thread id. Never report that no builder thread exists. For a new pair, the initial user request is yours, and its first turn is research, not relay. Research it: when the request names a ticket, read that ticket AND its comments, including replies and inline comments, and treat a later comment as newer intent than the description; read the code, docs, project instructions, and current state the request touches. Analyse what you found: root cause or the concrete design constraint, scope, what the user actually wants delivered, and which delivery steps they authorized. Only then
|
|
59267
|
+
const FUSION_EVIDENCE_SUMMARY_INSTRUCTIONS = `Read the builder's evidence through a summarizer subagent rather than into your own context: spawn one subagent with the evidence range and have it call thread_watch_review itself, paging with nextAfterSequence against a fixed throughSequence until hasMore is false, and return a summary. Your tool grants reach it, so it can read the paired builder thread exactly as you can. Instruct that subagent that thread_watch_review and thread_watch_events may be absent from its visible tool list and that absence does not establish unavailability: it must first use the available tool search/discovery facility to load those exact tools by name, including provider-prefixed names, and if discovery itself fails it must report that exact error rather than reporting no such tool. Require of that summary: every file path touched with the scale of the change to each, every command run and whether it succeeded or failed, check and test results with their counts, exact error text quoted rather than characterized, the builder's completion claims marked as claims, and anything the builder flagged as uncertain, blocked or unresolved. Over a multi-turn range it must also carry unresolved objections of yours and whether each was answered, phases completed versus reopened, claims never verified, and files touched repeatedly. Every statement cites the sequences behind it so you can check any one of them with a targeted thread_watch_events read. Fail open: if no subagent is available, the spawn errors, it times out, or what comes back is empty or does not cite sequences, read the range yourself and review from the raw evidence. Never skip or shorten a review because a summary was unavailable. Record which fallback you took: a subagent that could not load the watch tools is a misconfiguration worth naming in your report, and it is not the same as a spawn that errored or timed out. Then close the review by calling thread_evidence_report exactly once, after the last page: pass the watched thread id, the same fixed throughSequence the first review page returned - not lastSequence and not the final page's nextAfterSequence, because the server pairs the report to the read by that exact number - the first and last sequence you actually covered, the model you spawned on, and outcome summarized with the subagent's exact returned text, fallback when you read the raw range yourself, or failed when the summarizer could not run or could not report - with the reason in your own words for the last two. Report even when the summary never arrived; a review with no report cannot be told apart from a supervisor that never tried. Never paste builder tool output into it.`;
|
|
59268
|
+
const FUSION_WATCHER_INSTRUCTIONS = `You are Fusion Supervisor (watcher) in an already-created native server pair. Follow the current turn's [fusion-review-policy] block when present; it overrides phase scheduling. Server owns pairing and coordination and wakes you with ${FUSION_REVIEW_PROMPT_PREFIX} or ${FUSION_GATE_PROMPT_PREFIX} prompts at builder turn boundaries. A plain message outside such a wake may arrive after your conversational memory of the pair is gone; its [fusion-pair] metadata block is authoritative: the builder thread exists and is the counterpart thread id. Never report that no builder thread exists. For a new pair, the initial user request is yours, and its first turn is research, not relay. Research it: when the request names a ticket, read that ticket AND its comments, including replies and inline comments, and treat a later comment as newer intent than the description; read the code, docs, project instructions, and current state the request touches. Analyse what you found: root cause or the concrete design constraint, scope, what the user actually wants delivered, and which delivery steps they authorized. Only then write the phase plan and direct its first phase. The plan is yours alone: call thread_plan_update with the builder's threadId, sending the complete ordered list every time, each phase named in 3-6 words by its outcome rather than by a command, file path, or flag, with exactly one in progress. Split it into the fewest substantial phases the task genuinely needs plus a final integration/whole-task phase; most tasks need one to three work phases, each a complete reviewable slice of behavior. Never split per file, per function, or per trivial step. State each phase's completion condition to the builder in the advice, not in the banner title. On every later review, rewrite the same plan: an approved phase becomes completed and the next becomes in progress in the same call, an objection leaves the current phase in progress or reopens a phase you had marked completed, and the final approval completes the last phase. The builder never touches it, so a phase you do not move stays where it is. Then direct the phase to the idle builder through thread_advise, stating the findings that make the direction actionable alongside the original requirements, constraints, and authorized delivery scope. Never echo the request back as its own direction, never hand over a plan you did not ground in evidence, and never send ${FUSION_NO_OBJECTION_TEXT} before the builder has received its first phase and produced a turn to review. The builder has not received the initial prompt and must not start before your direction. Missing builder turn evidence is expected before that first direction; do not wait for it or implement the work yourself. Ask the user only when needed to resolve a blocking requirement. ${FUSION_WATCHER_TOOL_INSTRUCTIONS} To resume supervision, read builder evidence with thread_watch_review from lastReviewedImplementerSequence, capture its throughSequence on the first page and reuse that fixed bound while paging with nextAfterSequence until hasMore is false (including empty pages). Apply message append/replace operations by identity; recover truncated evidence through targeted raw thread_watch_events source ranges. Retain reviewed requirements and evidence for final review; recover missing context with targeted history reads rather than mandatory raw replay from zero, derive phase from artifacts (git log/status, PR, builder events, including its turn.plan.updated phase list), steer with thread_advise, and answer an open gate with thread_gate_respond. When a review or gate wake prompt specifies an explicit event range, that range wins over this metadata. Never poll or wait for the builder; deliver review or advice, then end the turn. Every thread_advise starts a builder turn whose completion wakes you again, so never advise a builder that is idle on an external wait or has nothing actionable; report the state in your own thread and end without advising. ${FUSION_PAIR_COMMUNICATION_INSTRUCTIONS}`;
|
|
59063
59269
|
/**
|
|
59064
59270
|
* The one-line stand-in for the full block on a message whose session already
|
|
59065
59271
|
* carries the role instructions.
|
|
@@ -59119,10 +59325,20 @@ function resolveFusionSummarizerSelection(settings) {
|
|
|
59119
59325
|
function summarizerModelLine(model) {
|
|
59120
59326
|
return model === null ? "No summarizer model is configured for this provider, so read the range yourself." : `Spawn that subagent on ${model}, a cheap fast model of your own provider.`;
|
|
59121
59327
|
}
|
|
59328
|
+
/**
|
|
59329
|
+
* What a pair is told when the settings file cannot be read.
|
|
59330
|
+
*
|
|
59331
|
+
* Every field here has to match the shipped default of the setting it stands
|
|
59332
|
+
* in for, or an unreadable settings file silently changes what a pair is told -
|
|
59333
|
+
* which is the one thing the fallback exists to prevent. `evidenceSummary`
|
|
59334
|
+
* tracks `fusionEvidenceSummary` in `packages/contracts/src/settings.ts`, whose
|
|
59335
|
+
* decoding default is on; a test asserts the two stay equal.
|
|
59336
|
+
*/
|
|
59122
59337
|
const DEFAULT_FUSION_PROMPT_SETTINGS = {
|
|
59123
59338
|
promptOverrides: null,
|
|
59124
59339
|
summarizerModel: null,
|
|
59125
|
-
evidenceSummary:
|
|
59340
|
+
evidenceSummary: true,
|
|
59341
|
+
optimizedContext: DEFAULT_SERVER_SETTINGS.enableOptimizedFusionPromptDelivery
|
|
59126
59342
|
};
|
|
59127
59343
|
/**
|
|
59128
59344
|
* @param options.promptOverrides - The user's settings. An override replaces
|
|
@@ -59153,10 +59369,165 @@ function fusionRoleInstructionsWith(role, provider, settings) {
|
|
|
59153
59369
|
...provider === void 0 ? {} : { provider },
|
|
59154
59370
|
summarizerModelOverride: settings?.summarizerModel ?? null,
|
|
59155
59371
|
promptOverrides: settings?.promptOverrides ?? null,
|
|
59156
|
-
evidenceSummary: settings?.evidenceSummary ??
|
|
59372
|
+
evidenceSummary: settings?.evidenceSummary ?? DEFAULT_FUSION_PROMPT_SETTINGS.evidenceSummary
|
|
59157
59373
|
});
|
|
59158
59374
|
}
|
|
59159
59375
|
//#endregion
|
|
59376
|
+
//#region src/provider/FusionContextPolicy.ts
|
|
59377
|
+
/**
|
|
59378
|
+
* What a Fusion role is allowed to carry into its context.
|
|
59379
|
+
*
|
|
59380
|
+
* A pair pays for its context twice: once in the builder's window and once in
|
|
59381
|
+
* the supervisor's, on every turn of a review loop. Measured on a live pair,
|
|
59382
|
+
* the builder's session opened with 107,320 tokens of MCP tool definitions, of
|
|
59383
|
+
* which 86,005 belonged to servers neither role can use, plus 13,270 tokens of
|
|
59384
|
+
* global memory files. None of that is the user's task.
|
|
59385
|
+
*
|
|
59386
|
+
* The policy is provider-neutral on purpose: it says what a role may carry,
|
|
59387
|
+
* and each adapter expresses it with whatever native control it has. An
|
|
59388
|
+
* adapter that cannot express a rule keeps the baseline rather than pretending,
|
|
59389
|
+
* which is why the exported helpers are pure and the adapters stay the only
|
|
59390
|
+
* place that knows how a given CLI is configured.
|
|
59391
|
+
*
|
|
59392
|
+
* All of it is gated on the user's existing Fusion context optimization
|
|
59393
|
+
* setting, which is therefore the rollback: off reproduces today's context
|
|
59394
|
+
* byte for byte, for both roles and for every ordinary thread.
|
|
59395
|
+
*
|
|
59396
|
+
* @module provider/FusionContextPolicy
|
|
59397
|
+
*/
|
|
59398
|
+
/**
|
|
59399
|
+
* The four constraints that must survive losing global memory.
|
|
59400
|
+
*
|
|
59401
|
+
* Deliberately not a copy of the user's `CLAUDE.md`: that file is thousands of
|
|
59402
|
+
* tokens of preference, and only these four are things a wrong answer cannot be
|
|
59403
|
+
* walked back from. Everything else a pair needs is in the project's own
|
|
59404
|
+
* instructions, which this policy never drops.
|
|
59405
|
+
*/
|
|
59406
|
+
const FUSION_SAFETY_KERNEL = [
|
|
59407
|
+
"Safety rules for this session, which override any default behaviour:",
|
|
59408
|
+
"- Never commit secrets or .env files, and never delete a file without explicit confirmation in the current turn.",
|
|
59409
|
+
"- Only touch work this session created. Never revert, stash, or overwrite uncommitted changes belonging to another session, and stage explicit paths rather than everything.",
|
|
59410
|
+
"- Never commit, push, or open a pull request unless the current turn asked for it, and never commit to main or master directly.",
|
|
59411
|
+
"- Never add AI attribution to a commit message, pull request, issue, comment, code, or document."
|
|
59412
|
+
].join("\n");
|
|
59413
|
+
/** Everything the supervisor half needs from the P4 MCP server, and nothing else. */
|
|
59414
|
+
const FUSION_SUPERVISOR_P4_TOOLS = /* @__PURE__ */ new Set([
|
|
59415
|
+
"thread_watch_review",
|
|
59416
|
+
"thread_watch_events",
|
|
59417
|
+
"thread_evidence_report",
|
|
59418
|
+
"thread_advise",
|
|
59419
|
+
"thread_gate_respond",
|
|
59420
|
+
"thread_plan_update",
|
|
59421
|
+
"ask_user_question",
|
|
59422
|
+
"task_current"
|
|
59423
|
+
]);
|
|
59424
|
+
/**
|
|
59425
|
+
* What the builder half must not reach.
|
|
59426
|
+
*
|
|
59427
|
+
* Framed as a denial rather than an allowance because the builder's job is
|
|
59428
|
+
* open-ended: it needs coding, verification and preview tools, and a new one of
|
|
59429
|
+
* those should reach it without an edit here. What it must never have is the
|
|
59430
|
+
* pair's own machinery - spawning threads, creating or detaching pairs,
|
|
59431
|
+
* advising, answering its own gate - the phase plan, which the supervisor
|
|
59432
|
+
* owns outright, plus task-board mutation and the external services that
|
|
59433
|
+
* belong to a person rather than to an implementation turn.
|
|
59434
|
+
*/
|
|
59435
|
+
const FUSION_BUILDER_DENIED_P4_TOOLS = /* @__PURE__ */ new Set([
|
|
59436
|
+
"thread_spawn",
|
|
59437
|
+
"thread_pair_create",
|
|
59438
|
+
"thread_pair_detach",
|
|
59439
|
+
"thread_cleanup",
|
|
59440
|
+
"thread_rename",
|
|
59441
|
+
"thread_configure",
|
|
59442
|
+
"thread_snooze",
|
|
59443
|
+
"thread_settle",
|
|
59444
|
+
"thread_advise",
|
|
59445
|
+
"thread_gate_respond",
|
|
59446
|
+
"thread_plan_update",
|
|
59447
|
+
"task_create",
|
|
59448
|
+
"task_update",
|
|
59449
|
+
"task_propose",
|
|
59450
|
+
"memory_append",
|
|
59451
|
+
"ticket_resolve"
|
|
59452
|
+
]);
|
|
59453
|
+
/**
|
|
59454
|
+
* Native planning tools the builder must not see either.
|
|
59455
|
+
*
|
|
59456
|
+
* The MCP denial above is only half of it: every provider ships its own
|
|
59457
|
+
* step-tracking tool, and a builder holding one would keep authoring the plan
|
|
59458
|
+
* the supervisor now owns. Named per provider because the names are the
|
|
59459
|
+
* provider's, not ours.
|
|
59460
|
+
*/
|
|
59461
|
+
const FUSION_BUILDER_DENIED_NATIVE_TOOLS = [
|
|
59462
|
+
"TaskCreate",
|
|
59463
|
+
"TaskUpdate",
|
|
59464
|
+
"TaskGet",
|
|
59465
|
+
"TaskList",
|
|
59466
|
+
"TodoWrite",
|
|
59467
|
+
"update_plan"
|
|
59468
|
+
];
|
|
59469
|
+
/**
|
|
59470
|
+
* What an adapter assumes when no optimization resolver was supplied.
|
|
59471
|
+
*
|
|
59472
|
+
* Mirrors the shipped setting rather than defaulting to off, so a driver that
|
|
59473
|
+
* has not been wired yet does not silently disable the profile.
|
|
59474
|
+
*/
|
|
59475
|
+
const DEFAULT_FUSION_CONTEXT_OPTIMIZATION = DEFAULT_SERVER_SETTINGS.enableOptimizedFusionPromptDelivery;
|
|
59476
|
+
/** The policy input, for adapters that read the two values separately. */
|
|
59477
|
+
function fusionContextPolicyFor(role, optimizedContext) {
|
|
59478
|
+
return {
|
|
59479
|
+
role,
|
|
59480
|
+
optimizedContext
|
|
59481
|
+
};
|
|
59482
|
+
}
|
|
59483
|
+
/** Whether the lean profile applies at all. Ordinary threads never reach it. */
|
|
59484
|
+
function fusionLeanContextApplies(input) {
|
|
59485
|
+
return input.role !== void 0 && input.optimizedContext;
|
|
59486
|
+
}
|
|
59487
|
+
/**
|
|
59488
|
+
* The external MCP registrations a Fusion session keeps: none.
|
|
59489
|
+
*
|
|
59490
|
+
* Everything a user registered for their own work goes, because neither role
|
|
59491
|
+
* can act on it during a pair turn and every definition is paid for on every
|
|
59492
|
+
* turn of the loop. Computer Use needs no exception here - it is a
|
|
59493
|
+
* provider-owned endpoint attached only to a session that was granted it, not
|
|
59494
|
+
* a registration in this map, so a granted session keeps it either way.
|
|
59495
|
+
*/
|
|
59496
|
+
function fusionExternalMcpServers(servers, input) {
|
|
59497
|
+
return fusionLeanContextApplies(input) ? {} : { ...servers };
|
|
59498
|
+
}
|
|
59499
|
+
/**
|
|
59500
|
+
* P4 tools this role must not see, given the full catalogue.
|
|
59501
|
+
*
|
|
59502
|
+
* Takes the catalogue rather than hard-coding it so a tool added to the server
|
|
59503
|
+
* is denied to the supervisor by default and offered to the builder by
|
|
59504
|
+
* default, which is the safe direction for each.
|
|
59505
|
+
*/
|
|
59506
|
+
function fusionHiddenP4Tools(catalogue, input) {
|
|
59507
|
+
if (!fusionLeanContextApplies(input)) return [];
|
|
59508
|
+
return input.role === "watcher" ? catalogue.filter((name) => !FUSION_SUPERVISOR_P4_TOOLS.has(name)) : catalogue.filter((name) => FUSION_BUILDER_DENIED_P4_TOOLS.has(name));
|
|
59509
|
+
}
|
|
59510
|
+
/**
|
|
59511
|
+
* Provider-native tools this role must not see.
|
|
59512
|
+
*
|
|
59513
|
+
* The supervisor keeps its own planning tool: it is the half that plans, and
|
|
59514
|
+
* for its own thread that tool is the ordinary one.
|
|
59515
|
+
*/
|
|
59516
|
+
function fusionHiddenNativeTools(input) {
|
|
59517
|
+
return fusionLeanContextApplies(input) && input.role === "implementer" ? FUSION_BUILDER_DENIED_NATIVE_TOOLS : [];
|
|
59518
|
+
}
|
|
59519
|
+
/**
|
|
59520
|
+
* Setting sources a Fusion session reads.
|
|
59521
|
+
*
|
|
59522
|
+
* `user` carries the global memory files and the user-level skills; the
|
|
59523
|
+
* project's own instructions live in `project` and `local` and stay, because
|
|
59524
|
+
* they are the ones that describe the code being changed.
|
|
59525
|
+
*/
|
|
59526
|
+
function fusionSettingSources(sources, input) {
|
|
59527
|
+
if (!fusionLeanContextApplies(input)) return sources;
|
|
59528
|
+
return sources.filter((source) => source !== "user");
|
|
59529
|
+
}
|
|
59530
|
+
//#endregion
|
|
59160
59531
|
//#region src/provider/ProviderOwnedProcessRegistry.ts
|
|
59161
59532
|
/**
|
|
59162
59533
|
* ProviderOwnedProcessRegistry - threadId to owned provider process handles.
|
|
@@ -61590,6 +61961,67 @@ function providerMcpEndpoints(config) {
|
|
|
61590
61961
|
authorizationHeader: config.computerUse.authorizationHeader
|
|
61591
61962
|
}] : []];
|
|
61592
61963
|
}
|
|
61964
|
+
/**
|
|
61965
|
+
* Every tool the built-in `p4-code` server registers, by bare name.
|
|
61966
|
+
*
|
|
61967
|
+
* Declared rather than derived so an adapter can filter a session's tools
|
|
61968
|
+
* without importing the MCP server layer, which would drag the whole
|
|
61969
|
+
* orchestration graph into a provider process. `McpHttpServer.test.ts` asserts
|
|
61970
|
+
* this list equals what the toolkits actually register, so a tool added there
|
|
61971
|
+
* cannot silently go missing here.
|
|
61972
|
+
*/
|
|
61973
|
+
const P4CODE_MCP_TOOL_NAMES = [
|
|
61974
|
+
"ask_user_question",
|
|
61975
|
+
"asset_compress",
|
|
61976
|
+
"device_action",
|
|
61977
|
+
"device_close",
|
|
61978
|
+
"device_command",
|
|
61979
|
+
"device_inspect",
|
|
61980
|
+
"device_list",
|
|
61981
|
+
"device_open",
|
|
61982
|
+
"device_screenshot",
|
|
61983
|
+
"memory_append",
|
|
61984
|
+
"preview_click",
|
|
61985
|
+
"preview_evaluate",
|
|
61986
|
+
"preview_navigate",
|
|
61987
|
+
"preview_open",
|
|
61988
|
+
"preview_press",
|
|
61989
|
+
"preview_recording_start",
|
|
61990
|
+
"preview_recording_stop",
|
|
61991
|
+
"preview_resize",
|
|
61992
|
+
"preview_save_screenshot",
|
|
61993
|
+
"preview_scroll",
|
|
61994
|
+
"preview_set_appearance",
|
|
61995
|
+
"preview_snapshot",
|
|
61996
|
+
"preview_status",
|
|
61997
|
+
"preview_type",
|
|
61998
|
+
"preview_wait_for",
|
|
61999
|
+
"task_create",
|
|
62000
|
+
"task_current",
|
|
62001
|
+
"task_get",
|
|
62002
|
+
"task_list",
|
|
62003
|
+
"task_propose",
|
|
62004
|
+
"task_update",
|
|
62005
|
+
"thread_advise",
|
|
62006
|
+
"thread_cleanup",
|
|
62007
|
+
"thread_configure",
|
|
62008
|
+
"thread_evidence_report",
|
|
62009
|
+
"thread_gate_respond",
|
|
62010
|
+
"thread_pair_create",
|
|
62011
|
+
"thread_pair_detach",
|
|
62012
|
+
"thread_plan_update",
|
|
62013
|
+
"thread_rename",
|
|
62014
|
+
"thread_settle",
|
|
62015
|
+
"thread_snooze",
|
|
62016
|
+
"thread_spawn",
|
|
62017
|
+
"thread_watch_events",
|
|
62018
|
+
"thread_watch_review",
|
|
62019
|
+
"ticket_resolve"
|
|
62020
|
+
];
|
|
62021
|
+
/** The catalogue as the adapters read it. */
|
|
62022
|
+
function providerMcpToolNames() {
|
|
62023
|
+
return P4CODE_MCP_TOOL_NAMES;
|
|
62024
|
+
}
|
|
61593
62025
|
/** Non-Codex identities stay reserved even when their endpoint is disabled. */
|
|
61594
62026
|
function providerMcpReservedNames() {
|
|
61595
62027
|
return /* @__PURE__ */ new Set([P4CODE_MCP_SERVER_NAME, COMPUTER_USE_MCP_SERVER_NAME]);
|
|
@@ -62279,7 +62711,7 @@ const makeAntigravityAdapter = Effect.fn("makeAntigravityAdapter")(function* (se
|
|
|
62279
62711
|
},
|
|
62280
62712
|
clientFileSystem: true,
|
|
62281
62713
|
...Option.isSome(cursor) ? { resumeSessionId: cursor.value.sessionId } : {},
|
|
62282
|
-
mcpServers: antigravitySessionMcpServers(mcp, options.resolveMcpServers ? yield* options.resolveMcpServers : {}),
|
|
62714
|
+
mcpServers: antigravitySessionMcpServers(mcp, fusionExternalMcpServers(options.resolveMcpServers ? yield* options.resolveMcpServers : {}, fusionContextPolicyFor(input.fusionRole, options.resolveFusionContextOptimization === void 0 ? DEFAULT_FUSION_CONTEXT_OPTIMIZATION : yield* options.resolveFusionContextOptimization))),
|
|
62283
62715
|
...makeNativeLoggers({
|
|
62284
62716
|
nativeEventLogger: options.nativeEventLogger,
|
|
62285
62717
|
provider: PROVIDER$7,
|
|
@@ -64621,6 +65053,7 @@ const AntigravityDriver = {
|
|
|
64621
65053
|
continuation: { groupKey: continuationIdentity.continuationKey }
|
|
64622
65054
|
});
|
|
64623
65055
|
const mcpRegistry = yield* McpRegistry;
|
|
65056
|
+
const serverSettings = yield* ServerSettingsService;
|
|
64624
65057
|
const classifyModels = (draft) => modelManifest.current.pipe(Effect.map((manifest) => stampIdentity(applyModelManifest(draft, manifest, DRIVER))));
|
|
64625
65058
|
const makeRuntime = Effect.fn("AntigravityDriver.makeRuntime")(function* (input) {
|
|
64626
65059
|
if (authConfigIssue !== null) return yield* new ProviderSetupError({
|
|
@@ -64734,6 +65167,7 @@ const AntigravityDriver = {
|
|
|
64734
65167
|
makeRuntime,
|
|
64735
65168
|
withProcess: authFlow.withProcess,
|
|
64736
65169
|
resolveMcpServers: mcpRegistry.resolveForSession,
|
|
65170
|
+
resolveFusionContextOptimization: serverSettings.getSettings.pipe(Effect.map((settings) => settings.enableOptimizedFusionPromptDelivery), Effect.orElseSucceed(() => DEFAULT_FUSION_CONTEXT_OPTIMIZATION)),
|
|
64737
65171
|
defaultModel,
|
|
64738
65172
|
onSessionStarted: provider.onSessionStarted,
|
|
64739
65173
|
onConfigOptionsUpdated: provider.onConfigOptionsUpdated,
|
|
@@ -69266,13 +69700,19 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
|
|
|
69266
69700
|
...skillOverrides ? { skillOverrides } : {}
|
|
69267
69701
|
};
|
|
69268
69702
|
const mcpSession = readMcpProviderSession(input.threadId);
|
|
69269
|
-
const
|
|
69703
|
+
const registeredMcpServers = Object.fromEntries(Object.entries(options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers(input.cwd)).filter(([name]) => !providerMcpReservedNames().has(name)));
|
|
69270
69704
|
const narrateBeforeTools = options?.resolveToolCallNarration === void 0 ? DEFAULT_SERVER_SETTINGS.enableToolCallNarration : yield* options.resolveToolCallNarration;
|
|
69271
69705
|
const guardrailSettings = options?.resolveGuardrailPrompts === void 0 ? {
|
|
69272
69706
|
enableVerificationBeforeCompletion: DEFAULT_SERVER_SETTINGS.enableVerificationBeforeCompletion,
|
|
69273
69707
|
enableRootCauseBeforeFix: DEFAULT_SERVER_SETTINGS.enableRootCauseBeforeFix
|
|
69274
69708
|
} : yield* options.resolveGuardrailPrompts;
|
|
69275
69709
|
const fusionPromptSettings = options?.resolveFusionPromptSettings === void 0 ? void 0 : yield* options.resolveFusionPromptSettings;
|
|
69710
|
+
const fusionContextPolicy = {
|
|
69711
|
+
role: input.fusionRole,
|
|
69712
|
+
optimizedContext: fusionPromptSettings?.optimizedContext ?? DEFAULT_FUSION_PROMPT_SETTINGS.optimizedContext
|
|
69713
|
+
};
|
|
69714
|
+
const externalMcpServers = fusionExternalMcpServers(registeredMcpServers, fusionContextPolicy);
|
|
69715
|
+
const hiddenTools = [...fusionHiddenP4Tools(providerMcpToolNames(), fusionContextPolicy).map((name) => `mcp__${P4CODE_MCP_SERVER_NAME}__${name}`), ...fusionHiddenNativeTools(fusionContextPolicy)];
|
|
69276
69716
|
const unpromptedSubagents = input.unpromptedSubagents !== void 0 ? input.unpromptedSubagents : options?.resolveUnpromptedSubagents === void 0 ? DEFAULT_SERVER_SETTINGS.enableUnpromptedSubagents : yield* options.resolveUnpromptedSubagents;
|
|
69277
69717
|
const compressRuleset = compressRulesetFor(input.compressMode ?? "off");
|
|
69278
69718
|
const systemPromptAppend = [
|
|
@@ -69281,8 +69721,9 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
|
|
|
69281
69721
|
...narrateBeforeTools ? [NARRATE_BEFORE_TOOLS_PROMPT] : [],
|
|
69282
69722
|
...guardrailPromptsFor(guardrailSettings),
|
|
69283
69723
|
unpromptedSubagents ? SUBAGENTS_ALLOWED_PROMPT : SUBAGENTS_ON_REQUEST_PROMPT,
|
|
69284
|
-
...
|
|
69285
|
-
...
|
|
69724
|
+
...input.fusionRole !== void 0 ? [fusionRoleInstructionsWith(input.fusionRole, "claudeAgent", fusionPromptSettings)] : [],
|
|
69725
|
+
...fusionLeanContextApplies(fusionContextPolicy) ? [FUSION_SAFETY_KERNEL] : [],
|
|
69726
|
+
...compressRuleset !== void 0 ? [compressRuleset] : []
|
|
69286
69727
|
].join("\n\n");
|
|
69287
69728
|
const compressionSubagentHook = async (hookInput) => {
|
|
69288
69729
|
if (hookInput.hook_event_name !== "SubagentStart") return {};
|
|
@@ -69301,7 +69742,8 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
|
|
|
69301
69742
|
preset: "claude_code",
|
|
69302
69743
|
...systemPromptAppend.length > 0 ? { append: systemPromptAppend } : {}
|
|
69303
69744
|
},
|
|
69304
|
-
settingSources: [...CLAUDE_SETTING_SOURCES],
|
|
69745
|
+
settingSources: [...fusionSettingSources(CLAUDE_SETTING_SOURCES, fusionContextPolicy)],
|
|
69746
|
+
...hiddenTools.length > 0 ? { disallowedTools: hiddenTools } : {},
|
|
69305
69747
|
...effectiveEffort ? { effort: effectiveEffort } : {},
|
|
69306
69748
|
...permissionMode ? { permissionMode } : {},
|
|
69307
69749
|
...permissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {},
|
|
@@ -69710,7 +70152,8 @@ const ClaudeDriver = {
|
|
|
69710
70152
|
resolveFusionPromptSettings: serverSettings.getSettings.pipe(Effect.map((settings) => ({
|
|
69711
70153
|
promptOverrides: settings.fusionPromptOverrides,
|
|
69712
70154
|
summarizerModel: resolveFusionSummarizerSelection(settings),
|
|
69713
|
-
evidenceSummary: settings.fusionEvidenceSummary
|
|
70155
|
+
evidenceSummary: settings.fusionEvidenceSummary,
|
|
70156
|
+
optimizedContext: settings.enableOptimizedFusionPromptDelivery
|
|
69714
70157
|
})), Effect.orElseSucceed(() => DEFAULT_FUSION_PROMPT_SETTINGS)),
|
|
69715
70158
|
resolveUnpromptedSubagents: serverSettings.getSettings.pipe(Effect.map((settings) => settings.enableUnpromptedSubagents), Effect.orElseSucceed(() => DEFAULT_SERVER_SETTINGS.enableUnpromptedSubagents)),
|
|
69716
70159
|
...eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}
|
|
@@ -89079,8 +89522,11 @@ function buildCodexDeveloperInstructions(interactionMode, runtime, compressMode,
|
|
|
89079
89522
|
return [
|
|
89080
89523
|
browserToolsAvailable ? base : base.replace(P4_CODE_BROWSER_TOOL_INSTRUCTIONS, ""),
|
|
89081
89524
|
...guardrailPromptsFor(guardrailSettings),
|
|
89525
|
+
...fusionRole === void 0 ? [] : [`<fusion_role>${fusionRoleInstructionsWith(fusionRole, "codex", fusionPromptSettings)}</fusion_role>`, ...fusionLeanContextApplies({
|
|
89526
|
+
role: fusionRole,
|
|
89527
|
+
optimizedContext: fusionPromptSettings?.optimizedContext ?? DEFAULT_FUSION_PROMPT_SETTINGS.optimizedContext
|
|
89528
|
+
}) ? [`<safety_rules>${FUSION_SAFETY_KERNEL}</safety_rules>`] : []],
|
|
89082
89529
|
...compressRuleset === void 0 ? [] : [`<response_style>${compressRuleset}</response_style>`],
|
|
89083
|
-
...fusionRole === void 0 ? [] : [`<fusion_role>${fusionRoleInstructionsWith(fusionRole, "codex", fusionPromptSettings)}</fusion_role>`],
|
|
89084
89530
|
`<runtime_info>In case you're asked: you are running in P4Code through the Codex harness, as ${toSingleLine(runtime.model)} with ${toSingleLine(runtime.reasoningEffort)} reasoning effort. No need to mention this otherwise.</runtime_info>`
|
|
89085
89531
|
].join("\n\n");
|
|
89086
89532
|
}
|
|
@@ -90940,7 +91386,11 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (codexConfig, o
|
|
|
90940
91386
|
if (existing && !existing.stopped) yield* Effect.suspend(() => stopSessionInternal(existing));
|
|
90941
91387
|
const serviceTier = input.modelSelection?.instanceId === boundInstanceId ? getCodexServiceTierOptionValue(input.modelSelection) : void 0;
|
|
90942
91388
|
const mcpSession = readMcpProviderSession(input.threadId);
|
|
90943
|
-
const
|
|
91389
|
+
const resolvedFusionPromptSettings = options?.resolveFusionPromptSettings === void 0 ? void 0 : yield* options.resolveFusionPromptSettings;
|
|
91390
|
+
const externalMcp = toCodexMcpConfig(fusionExternalMcpServers(options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers, {
|
|
91391
|
+
role: input.fusionRole,
|
|
91392
|
+
optimizedContext: resolvedFusionPromptSettings?.optimizedContext ?? DEFAULT_FUSION_PROMPT_SETTINGS.optimizedContext
|
|
91393
|
+
}), P4CODE_MCP_SERVER_NAMES);
|
|
90944
91394
|
for (const dropped of externalMcp.skipped) yield* Effect.logWarning("codex cannot express an mcp registration", {
|
|
90945
91395
|
server: dropped.name,
|
|
90946
91396
|
reason: dropped.reason
|
|
@@ -90949,7 +91399,6 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (codexConfig, o
|
|
|
90949
91399
|
enableVerificationBeforeCompletion: DEFAULT_SERVER_SETTINGS.enableVerificationBeforeCompletion,
|
|
90950
91400
|
enableRootCauseBeforeFix: DEFAULT_SERVER_SETTINGS.enableRootCauseBeforeFix
|
|
90951
91401
|
} : yield* options.resolveGuardrailPrompts;
|
|
90952
|
-
const resolvedFusionPromptSettings = options?.resolveFusionPromptSettings === void 0 ? void 0 : yield* options.resolveFusionPromptSettings;
|
|
90953
91402
|
const guardrailPromptsEnabled = resolvedGuardrailPrompts.enableVerificationBeforeCompletion || resolvedGuardrailPrompts.enableRootCauseBeforeFix;
|
|
90954
91403
|
const runtimeInput = {
|
|
90955
91404
|
...options?.onUsageLimits ? { onUsageLimits: options.onUsageLimits } : {},
|
|
@@ -91509,7 +91958,8 @@ const CodexDriver = {
|
|
|
91509
91958
|
const resolveFusionPromptSettings = serverSettings.getSettings.pipe(Effect.map((settings) => ({
|
|
91510
91959
|
promptOverrides: settings.fusionPromptOverrides,
|
|
91511
91960
|
summarizerModel: resolveFusionSummarizerSelection(settings),
|
|
91512
|
-
evidenceSummary: settings.fusionEvidenceSummary
|
|
91961
|
+
evidenceSummary: settings.fusionEvidenceSummary,
|
|
91962
|
+
optimizedContext: settings.enableOptimizedFusionPromptDelivery
|
|
91513
91963
|
})), Effect.orElseSucceed(() => DEFAULT_FUSION_PROMPT_SETTINGS));
|
|
91514
91964
|
const textGeneration = yield* makeCodexTextGeneration(effectiveConfig, processEnv);
|
|
91515
91965
|
const checkProvider = checkCodexProviderStatus(effectiveConfig, void 0, processEnv).pipe(Effect.map(stampIdentity), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.provideService(FileSystem.FileSystem, fileSystem));
|
|
@@ -92700,7 +93150,7 @@ function makeCursorAdapter(cursorSettings, options) {
|
|
|
92700
93150
|
});
|
|
92701
93151
|
const effectiveCursorSettings = options?.resolveSettings ? yield* options.resolveSettings : cursorSettings;
|
|
92702
93152
|
const mcpSession = readMcpProviderSession(input.threadId);
|
|
92703
|
-
const mcpServers = [...toAcpMcpServers(options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers, providerMcpReservedNames()), ...providerMcpEndpoints(mcpSession).map((entry) => ({
|
|
93153
|
+
const mcpServers = [...toAcpMcpServers(fusionExternalMcpServers(options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers, fusionContextPolicyFor(input.fusionRole, options?.resolveFusionContextOptimization === void 0 ? DEFAULT_FUSION_CONTEXT_OPTIMIZATION : yield* options.resolveFusionContextOptimization)), providerMcpReservedNames()), ...providerMcpEndpoints(mcpSession).map((entry) => ({
|
|
92704
93154
|
type: "http",
|
|
92705
93155
|
name: entry.name,
|
|
92706
93156
|
url: entry.url,
|
|
@@ -93207,6 +93657,7 @@ const CursorDriver = {
|
|
|
93207
93657
|
const adapter = yield* makeCursorAdapter(effectiveConfig, {
|
|
93208
93658
|
environment: processEnv,
|
|
93209
93659
|
resolveMcpServers: (yield* McpRegistry).resolveForSession,
|
|
93660
|
+
resolveFusionContextOptimization: serverSettings.getSettings.pipe(Effect.map((settings) => settings.enableOptimizedFusionPromptDelivery), Effect.orElseSucceed(() => DEFAULT_FUSION_CONTEXT_OPTIMIZATION)),
|
|
93210
93661
|
...eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {},
|
|
93211
93662
|
instanceId
|
|
93212
93663
|
});
|
|
@@ -93962,7 +94413,7 @@ function makeGrokAdapter(grokSettings, options) {
|
|
|
93962
94413
|
threadId: input.threadId
|
|
93963
94414
|
});
|
|
93964
94415
|
const mcpSession = readMcpProviderSession(input.threadId);
|
|
93965
|
-
const mcpServers = [...toAcpMcpServers(options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers, providerMcpReservedNames()), ...providerMcpEndpoints(mcpSession).map((entry) => ({
|
|
94416
|
+
const mcpServers = [...toAcpMcpServers(fusionExternalMcpServers(options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers, fusionContextPolicyFor(input.fusionRole, options?.resolveFusionContextOptimization === void 0 ? DEFAULT_FUSION_CONTEXT_OPTIMIZATION : yield* options.resolveFusionContextOptimization)), providerMcpReservedNames()), ...providerMcpEndpoints(mcpSession).map((entry) => ({
|
|
93966
94417
|
type: "http",
|
|
93967
94418
|
name: entry.name,
|
|
93968
94419
|
url: entry.url,
|
|
@@ -94806,6 +95257,7 @@ const GrokDriver = {
|
|
|
94806
95257
|
const adapter = yield* makeGrokAdapter(effectiveConfig, {
|
|
94807
95258
|
environment: processEnv,
|
|
94808
95259
|
resolveMcpServers: (yield* McpRegistry).resolveForSession,
|
|
95260
|
+
resolveFusionContextOptimization: serverSettings.getSettings.pipe(Effect.map((settings) => settings.enableOptimizedFusionPromptDelivery), Effect.orElseSucceed(() => DEFAULT_FUSION_CONTEXT_OPTIMIZATION)),
|
|
94809
95261
|
...eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {},
|
|
94810
95262
|
instanceId
|
|
94811
95263
|
});
|
|
@@ -95995,7 +96447,7 @@ function makeMuseAdapter(museSettings, options) {
|
|
|
95995
96447
|
authorizationHeader: mcpSession.authorizationHeader
|
|
95996
96448
|
},
|
|
95997
96449
|
additionalOwnedServers: providerMcpEndpoints(mcpSession).filter((entry) => entry.name !== P4CODE_MCP_SERVER_NAME),
|
|
95998
|
-
externalServers: options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers
|
|
96450
|
+
externalServers: fusionExternalMcpServers(options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers, fusionContextPolicyFor(input.fusionRole, options?.resolveFusionContextOptimization === void 0 ? DEFAULT_FUSION_CONTEXT_OPTIMIZATION : yield* options.resolveFusionContextOptimization))
|
|
95999
96451
|
}).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path$1.Path, path), Effect.orElseSucceed(() => void 0)) : void 0;
|
|
96000
96452
|
if (overlay && overlay.skippedServers.length > 0) yield* Effect.logWarning("Muse cannot express these MCP servers", {
|
|
96001
96453
|
threadId: input.threadId,
|
|
@@ -96505,7 +96957,8 @@ const MuseDriver = {
|
|
|
96505
96957
|
const adapter = yield* makeMuseAdapter(effectiveConfig, {
|
|
96506
96958
|
environment: processEnv,
|
|
96507
96959
|
instanceId,
|
|
96508
|
-
resolveMcpServers: (yield* McpRegistry).resolveForSession
|
|
96960
|
+
resolveMcpServers: (yield* McpRegistry).resolveForSession,
|
|
96961
|
+
resolveFusionContextOptimization: serverSettings.getSettings.pipe(Effect.map((settings) => settings.enableOptimizedFusionPromptDelivery), Effect.orElseSucceed(() => DEFAULT_FUSION_CONTEXT_OPTIMIZATION))
|
|
96509
96962
|
});
|
|
96510
96963
|
const textGeneration = yield* makeMuseTextGeneration(effectiveConfig, processEnv);
|
|
96511
96964
|
const checkProvider = serverSettings.getSettings.pipe(Effect.map((settings) => settings.disabledSkills), Effect.orElseSucceed(() => [])).pipe(Effect.flatMap((disabledSkills) => checkMuseProviderStatus(effectiveConfig, processEnv).pipe(Effect.flatMap((draft) => withProviderSkills(draft, {
|
|
@@ -98079,7 +98532,7 @@ function makeOpenCodeAdapter(openCodeSettings, options) {
|
|
|
98079
98532
|
});
|
|
98080
98533
|
const mcpSession = readMcpProviderSession(input.threadId);
|
|
98081
98534
|
if (!server.external) {
|
|
98082
|
-
const externalMcpServers = toOpenCodeMcpServers(options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers, providerMcpReservedNames());
|
|
98535
|
+
const externalMcpServers = toOpenCodeMcpServers(fusionExternalMcpServers(options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers, fusionContextPolicyFor(input.fusionRole, options?.resolveFusionContextOptimization === void 0 ? DEFAULT_FUSION_CONTEXT_OPTIMIZATION : yield* options.resolveFusionContextOptimization)), providerMcpReservedNames());
|
|
98083
98536
|
for (const entry of externalMcpServers) yield* runOpenCodeSdk("mcp.add", () => client.mcp.add(entry)).pipe(Effect.catchCause((cause) => Effect.logWarning("opencode rejected an mcp registration", {
|
|
98084
98537
|
server: entry.name,
|
|
98085
98538
|
cause
|
|
@@ -98814,6 +99267,7 @@ const BUILT_IN_DRIVERS = [
|
|
|
98814
99267
|
instanceId,
|
|
98815
99268
|
environment: processEnv,
|
|
98816
99269
|
resolveMcpServers: (yield* McpRegistry).resolveForSession,
|
|
99270
|
+
resolveFusionContextOptimization: serverSettings.getSettings.pipe(Effect.map((settings) => settings.enableOptimizedFusionPromptDelivery), Effect.orElseSucceed(() => DEFAULT_FUSION_CONTEXT_OPTIMIZATION)),
|
|
98817
99271
|
...eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}
|
|
98818
99272
|
});
|
|
98819
99273
|
const textGeneration = yield* makeOpenCodeTextGeneration(effectiveConfig, processEnv);
|
|
@@ -132527,14 +132981,14 @@ const resolveSpawnModelSelection = (requested, inherited) => requested === void
|
|
|
132527
132981
|
};
|
|
132528
132982
|
const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
|
|
132529
132983
|
thread_plan_update: (input) => Effect.gen(function* () {
|
|
132530
|
-
const
|
|
132531
|
-
const threads = yield* ProjectionThreadRepository;
|
|
132984
|
+
const invocation = yield* requireThreadCapability();
|
|
132532
132985
|
const reject = (detail) => new ThreadControlRejectedError({
|
|
132533
|
-
threadId,
|
|
132986
|
+
threadId: invocation.threadId,
|
|
132534
132987
|
commandType: "thread.activity.append",
|
|
132535
132988
|
detail
|
|
132536
132989
|
});
|
|
132537
|
-
const
|
|
132990
|
+
const threadId = input.threadId === void 0 || input.threadId === invocation.threadId ? invocation.threadId : yield* requireWatchCapability(input.threadId).pipe(Effect.as(input.threadId), Effect.catch(() => reject("This session may not update that thread's plan")));
|
|
132991
|
+
const thread = yield* (yield* ProjectionThreadRepository).getById({ threadId }).pipe(Effect.mapError((cause) => reject(cause.message)));
|
|
132538
132992
|
if (Option.isNone(thread)) return yield* reject("Current thread does not exist.");
|
|
132539
132993
|
if (input.plan.filter((step) => step.status === "in_progress").length > 1) return yield* reject("At most one phase may be in progress.");
|
|
132540
132994
|
const crypto = yield* Crypto.Crypto;
|
|
@@ -133470,19 +133924,19 @@ const estimateTokens = (text) => Math.ceil(text.length / 4);
|
|
|
133470
133924
|
/** One line per event is plenty to answer "did anything reviewable happen". */
|
|
133471
133925
|
const MAX_LINE_CHARS = 400;
|
|
133472
133926
|
const oneLine = (text) => text.replace(/\s+/gu, " ").trim();
|
|
133473
|
-
const clip = (text) => text.length <= MAX_LINE_CHARS ? text : `${text.slice(0, MAX_LINE_CHARS)}…`;
|
|
133927
|
+
const clip$1 = (text) => text.length <= MAX_LINE_CHARS ? text : `${text.slice(0, MAX_LINE_CHARS)}…`;
|
|
133474
133928
|
/** The line an event contributes, or null when it says nothing about the work. */
|
|
133475
133929
|
function describeEvent(event, implementerThreadId) {
|
|
133476
133930
|
switch (event.type) {
|
|
133477
133931
|
case "thread.message-sent": {
|
|
133478
133932
|
if (event.payload.threadId !== implementerThreadId) return null;
|
|
133479
133933
|
const text = oneLine(event.payload.text);
|
|
133480
|
-
return text.length === 0 ? null : clip(`${event.payload.role}: ${text}`);
|
|
133934
|
+
return text.length === 0 ? null : clip$1(`${event.payload.role}: ${text}`);
|
|
133481
133935
|
}
|
|
133482
133936
|
case "thread.activity-appended": {
|
|
133483
133937
|
if (event.payload.threadId !== implementerThreadId) return null;
|
|
133484
133938
|
const activity = event.payload.activity;
|
|
133485
|
-
return clip(oneLine(`${activity.kind}: ${activity.summary}`));
|
|
133939
|
+
return clip$1(oneLine(`${activity.kind}: ${activity.summary}`));
|
|
133486
133940
|
}
|
|
133487
133941
|
case "thread.turn-completed":
|
|
133488
133942
|
if (event.payload.threadId !== implementerThreadId) return null;
|
|
@@ -133603,10 +134057,32 @@ const ThreadWatchReviewTool = Tool.make("thread_watch_review", {
|
|
|
133603
134057
|
McpInvocationContext,
|
|
133604
134058
|
ThreadEventStreamService,
|
|
133605
134059
|
OrchestrationEngineService,
|
|
134060
|
+
ProjectionSnapshotQuery,
|
|
134061
|
+
Crypto.Crypto
|
|
134062
|
+
]
|
|
134063
|
+
}).annotate(Tool.Title, "Read compact thread review").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
|
|
134064
|
+
/**
|
|
134065
|
+
* What the supervisor says happened to the evidence it just read.
|
|
134066
|
+
*
|
|
134067
|
+
* Without it the review path is unobservable: a summarized read, a raw
|
|
134068
|
+
* fallback and a supervisor that never spawned anything all look identical
|
|
134069
|
+
* from outside. The context numbers are not taken from the report - the server
|
|
134070
|
+
* reads them off the reporting thread's own context-window rows - so the one
|
|
134071
|
+
* number a supervisor could be wrong about is the one it does not supply.
|
|
134072
|
+
*/
|
|
134073
|
+
const ThreadEvidenceReportTool = Tool.make("thread_evidence_report", {
|
|
134074
|
+
description: "Report what happened to the builder evidence you just read: outcome summarized with the summarizer's exact text, fallback when you read the raw range yourself, or failed when the summarizer could not run or report. Give the sequence range you covered and the model you spawned on. Call it once per review, after the last page. The server records it and correlates your own context usage; do not include builder tool output.",
|
|
134075
|
+
parameters: ThreadEvidenceReportInput,
|
|
134076
|
+
success: ThreadEvidenceReportResult,
|
|
134077
|
+
failure: ThreadWatchToolError,
|
|
134078
|
+
dependencies: [
|
|
134079
|
+
McpInvocationContext,
|
|
134080
|
+
OrchestrationEngineService,
|
|
134081
|
+
ProjectionSnapshotQuery,
|
|
133606
134082
|
Crypto.Crypto
|
|
133607
134083
|
]
|
|
133608
|
-
}).annotate(Tool.Title, "
|
|
133609
|
-
const WatchToolkit = Toolkit.make(ThreadWatchEventsTool, ThreadWatchReviewTool);
|
|
134084
|
+
}).annotate(Tool.Title, "Report evidence summary").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
|
|
134085
|
+
const WatchToolkit = Toolkit.make(ThreadWatchEventsTool, ThreadWatchReviewTool, ThreadEvidenceReportTool);
|
|
133610
134086
|
//#endregion
|
|
133611
134087
|
//#region src/mcp/toolkits/watch/handlers.ts
|
|
133612
134088
|
/**
|
|
@@ -133618,6 +134094,15 @@ const WatchToolkit = Toolkit.make(ThreadWatchEventsTool, ThreadWatchReviewTool);
|
|
|
133618
134094
|
* appending beside it.
|
|
133619
134095
|
*/
|
|
133620
134096
|
const evidenceActivityId = (watchedThreadId, throughSequence) => EventId.make(`fusion-evidence:${watchedThreadId}:${throughSequence}`);
|
|
134097
|
+
/** One report per review, keyed like the read it reports on so a retry replaces. */
|
|
134098
|
+
const reportActivityId = (watchedThreadId, lastSequence) => EventId.make(`fusion-evidence-summary:${watchedThreadId}:${lastSequence}`);
|
|
134099
|
+
const MODEL_ID_MAX_CHARS = 120;
|
|
134100
|
+
/** Plain words, because the client capitalizes and renders them as the row heading. */
|
|
134101
|
+
const REPORT_HEADING = {
|
|
134102
|
+
summarized: "Evidence summary",
|
|
134103
|
+
fallback: "Evidence read raw",
|
|
134104
|
+
failed: "Evidence summary failed"
|
|
134105
|
+
};
|
|
133621
134106
|
/**
|
|
133622
134107
|
* Records that the supervisor read the builder's evidence, in its own thread.
|
|
133623
134108
|
*
|
|
@@ -133628,7 +134113,10 @@ const evidenceActivityId = (watchedThreadId, throughSequence) => EventId.make(`f
|
|
|
133628
134113
|
const recordEvidenceRead = Effect.fn("mcp.watch.recordEvidenceRead")(function* (readerThreadId, page) {
|
|
133629
134114
|
const engine = yield* OrchestrationEngineService;
|
|
133630
134115
|
const crypto = yield* Crypto.Crypto;
|
|
134116
|
+
const projections = yield* ProjectionSnapshotQuery;
|
|
133631
134117
|
const createdAt = DateTime.formatIso(yield* DateTime.now);
|
|
134118
|
+
const recorded = yield* projections.getEvidenceReadContext(evidenceActivityId(page.threadId, page.throughSequence)).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
134119
|
+
const usedTokensAtRead = Option.isSome(recorded) ? recorded.value : yield* projections.getLatestThreadContextWindow(readerThreadId).pipe(Effect.map(Option.map((row) => row.usedTokens)), Effect.orElseSucceed(() => Option.none()), Effect.map(Option.getOrNull));
|
|
133632
134120
|
yield* engine.dispatch({
|
|
133633
134121
|
type: "thread.activity.append",
|
|
133634
134122
|
commandId: CommandId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie)),
|
|
@@ -133646,7 +134134,8 @@ const recordEvidenceRead = Effect.fn("mcp.watch.recordEvidenceRead")(function* (
|
|
|
133646
134134
|
throughSequence: page.throughSequence,
|
|
133647
134135
|
nextAfterSequence: page.nextAfterSequence,
|
|
133648
134136
|
hasMore: page.hasMore,
|
|
133649
|
-
scannedEvents: page.scannedEvents
|
|
134137
|
+
scannedEvents: page.scannedEvents,
|
|
134138
|
+
...usedTokensAtRead === null ? {} : { usedTokensAtRead }
|
|
133650
134139
|
}
|
|
133651
134140
|
}
|
|
133652
134141
|
}).pipe(Effect.catchCause((cause) => Effect.logWarning("could not record the supervisor's evidence read", {
|
|
@@ -133655,6 +134144,36 @@ const recordEvidenceRead = Effect.fn("mcp.watch.recordEvidenceRead")(function* (
|
|
|
133655
134144
|
cause
|
|
133656
134145
|
})));
|
|
133657
134146
|
});
|
|
134147
|
+
/** Bounded so one report cannot dominate a thread's activity storage. */
|
|
134148
|
+
const clip = (value, limit) => {
|
|
134149
|
+
const trimmed = value?.trim();
|
|
134150
|
+
if (!trimmed) return null;
|
|
134151
|
+
return trimmed.length <= limit ? trimmed : `${trimmed.slice(0, limit)}…`;
|
|
134152
|
+
};
|
|
134153
|
+
/**
|
|
134154
|
+
* The reporting thread's own context around the read it is reporting on.
|
|
134155
|
+
*
|
|
134156
|
+
* `before` comes off the evidence-read activity rather than the report, so the
|
|
134157
|
+
* pair of numbers brackets the ingestion instead of describing only the moment
|
|
134158
|
+
* the supervisor got around to reporting. A thread with no context-window row
|
|
134159
|
+
* yet yields null rather than a zero that would read as a real measurement.
|
|
134160
|
+
*/
|
|
134161
|
+
const correlateContext = Effect.fn("mcp.watch.correlateContext")(function* (readerThreadId, readActivityId) {
|
|
134162
|
+
const projections = yield* ProjectionSnapshotQuery;
|
|
134163
|
+
const latest = yield* projections.getLatestThreadContextWindow(readerThreadId).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
134164
|
+
const before = yield* projections.getEvidenceReadContext(readActivityId).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
134165
|
+
if (Option.isNone(latest)) return null;
|
|
134166
|
+
const usedTokensAfter = latest.value.usedTokens;
|
|
134167
|
+
const usedTokensBefore = Option.getOrNull(before);
|
|
134168
|
+
const maxTokens = latest.value.maxTokens;
|
|
134169
|
+
return {
|
|
134170
|
+
usedTokensBefore,
|
|
134171
|
+
usedTokensAfter,
|
|
134172
|
+
deltaTokens: usedTokensBefore === null ? null : usedTokensAfter - usedTokensBefore,
|
|
134173
|
+
maxTokens,
|
|
134174
|
+
capacityPercent: maxTokens === null || maxTokens <= 0 ? null : Math.round(usedTokensAfter / maxTokens * 1e3) / 10
|
|
134175
|
+
};
|
|
134176
|
+
});
|
|
133658
134177
|
const WatchToolkitHandlersLive = WatchToolkit.toLayer({
|
|
133659
134178
|
thread_watch_review: (input) => Effect.gen(function* () {
|
|
133660
134179
|
const invocation = yield* requireWatchCapability(input.threadId);
|
|
@@ -133665,6 +134184,48 @@ const WatchToolkitHandlersLive = WatchToolkit.toLayer({
|
|
|
133665
134184
|
yield* recordEvidenceRead(invocation.threadId, page);
|
|
133666
134185
|
return page;
|
|
133667
134186
|
}),
|
|
134187
|
+
thread_evidence_report: (input) => Effect.gen(function* () {
|
|
134188
|
+
const invocation = yield* requireWatchCapability(input.threadId);
|
|
134189
|
+
const engine = yield* OrchestrationEngineService;
|
|
134190
|
+
const crypto = yield* Crypto.Crypto;
|
|
134191
|
+
const createdAt = DateTime.formatIso(yield* DateTime.now);
|
|
134192
|
+
const context = yield* correlateContext(invocation.threadId, evidenceActivityId(input.threadId, input.throughSequence));
|
|
134193
|
+
const summary = clip(input.summary, THREAD_EVIDENCE_REPORT_MAX_CHARS);
|
|
134194
|
+
const reason = clip(input.reason, 500);
|
|
134195
|
+
const outcome = summary === null && input.outcome === "summarized" ? "failed" : input.outcome;
|
|
134196
|
+
return {
|
|
134197
|
+
recorded: yield* engine.dispatch({
|
|
134198
|
+
type: "thread.activity.append",
|
|
134199
|
+
commandId: CommandId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie)),
|
|
134200
|
+
threadId: invocation.threadId,
|
|
134201
|
+
createdAt,
|
|
134202
|
+
activity: {
|
|
134203
|
+
id: reportActivityId(input.threadId, input.throughSequence),
|
|
134204
|
+
createdAt,
|
|
134205
|
+
tone: outcome === "failed" ? "error" : "info",
|
|
134206
|
+
kind: "fusion.evidence.summary",
|
|
134207
|
+
summary: REPORT_HEADING[outcome],
|
|
134208
|
+
turnId: null,
|
|
134209
|
+
payload: {
|
|
134210
|
+
watchedThreadId: input.threadId,
|
|
134211
|
+
firstSequence: input.firstSequence,
|
|
134212
|
+
lastSequence: input.lastSequence,
|
|
134213
|
+
throughSequence: input.throughSequence,
|
|
134214
|
+
outcome,
|
|
134215
|
+
summary,
|
|
134216
|
+
model: clip(input.model, MODEL_ID_MAX_CHARS),
|
|
134217
|
+
reason: reason ?? (outcome === "summarized" ? null : "the supervisor gave no reason"),
|
|
134218
|
+
context
|
|
134219
|
+
}
|
|
134220
|
+
}
|
|
134221
|
+
}).pipe(Effect.as(true), Effect.catchCause((cause) => Effect.logWarning("could not record the supervisor's evidence report", {
|
|
134222
|
+
readerThreadId: invocation.threadId,
|
|
134223
|
+
watchedThreadId: input.threadId,
|
|
134224
|
+
cause
|
|
134225
|
+
}).pipe(Effect.as(false)))),
|
|
134226
|
+
context
|
|
134227
|
+
};
|
|
134228
|
+
}),
|
|
133668
134229
|
thread_watch_events: (input) => Effect.gen(function* () {
|
|
133669
134230
|
yield* requireWatchCapability(input.threadId);
|
|
133670
134231
|
const page = yield* (yield* ThreadEventStreamService).read({
|
|
@@ -137405,7 +137966,8 @@ const make$6 = Effect.gen(function* () {
|
|
|
137405
137966
|
const fusionPromptSettings = yield* serverSettingsService.getSettings.pipe(Effect.map((value) => ({
|
|
137406
137967
|
promptOverrides: value.fusionPromptOverrides,
|
|
137407
137968
|
summarizerModel: resolveFusionSummarizerSelection(value),
|
|
137408
|
-
evidenceSummary: value.fusionEvidenceSummary
|
|
137969
|
+
evidenceSummary: value.fusionEvidenceSummary,
|
|
137970
|
+
optimizedContext: value.enableOptimizedFusionPromptDelivery
|
|
137409
137971
|
})), Effect.catchCause(() => Effect.succeed(DEFAULT_FUSION_PROMPT_SETTINGS)));
|
|
137410
137972
|
const activeFusionPair = findActiveFusionPair(commandReadModel.threadPairs, input.threadId);
|
|
137411
137973
|
const fusionRole = activeFusionPair === void 0 ? void 0 : activeFusionPair.implementerThreadId === input.threadId ? "implementer" : "watcher";
|