@p4code/cli 0.5.14 → 0.5.16
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 +436 -57
- package/dist/client/assets/{DiffPanel-Y_5Ia30d.js → DiffPanel-D67u97Q2.js} +2 -2
- package/dist/client/assets/{FilePreviewPanel-BCttBIPR.js → FilePreviewPanel-ByLSi1JL.js} +2 -2
- package/dist/client/assets/{PreviewPanel-W6mUm9Or.js → PreviewPanel-hzSHtmkO.js} +2 -2
- package/dist/client/assets/{PullRequestCodeTab-DkjmJEJK.js → PullRequestCodeTab-DoXs9qKG.js} +2 -2
- package/dist/client/assets/{fileCommentAnnotations-PMEbyK4D.js → fileCommentAnnotations-T78XG7k8.js} +2 -2
- package/dist/client/assets/{index-D_D9GtC6.js → index-DwVt0vnY.js} +17 -8
- package/dist/client/assets/{previewAssetResource-DIMp8MLe.js → previewAssetResource-CKOao7Zb.js} +35 -35
- package/dist/client/assets/{renderFileChildren-1-7OIJeP.js → renderFileChildren-BcefTgOQ.js} +2 -2
- package/dist/client/assets/{toggle-group-D5iDw8-4.js → toggle-group-NIaLKwkg.js} +2 -2
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
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.16";
|
|
269
269
|
//#endregion
|
|
270
270
|
//#region src/config.ts
|
|
271
271
|
/**
|
|
@@ -12885,11 +12885,11 @@ const ThreadWatchReviewResult = Schema$1.Struct({
|
|
|
12885
12885
|
/**
|
|
12886
12886
|
* How a review ended, from the supervisor's own account of it.
|
|
12887
12887
|
*
|
|
12888
|
-
*
|
|
12888
|
+
* Three states rather than a boolean because they are acted on differently: a
|
|
12889
12889
|
* summary that arrived is evidence, a fallback is a working review that cost
|
|
12890
|
-
* full context, a failure is a misconfiguration worth fixing
|
|
12891
|
-
*
|
|
12892
|
-
*
|
|
12890
|
+
* full context, and a failure is a misconfiguration worth fixing. The fourth
|
|
12891
|
+
* state - a read no report ever answered - is not sayable here: only the
|
|
12892
|
+
* server can observe it, through `ThreadEvidenceActivityOutcome`.
|
|
12893
12893
|
*/
|
|
12894
12894
|
const ThreadEvidenceReportOutcome = Schema$1.Literals([
|
|
12895
12895
|
"summarized",
|
|
@@ -12942,19 +12942,52 @@ const ThreadEvidenceReportContext = Schema$1.Struct({
|
|
|
12942
12942
|
maxTokens: Schema$1.NullOr(NonNegativeInt),
|
|
12943
12943
|
capacityPercent: Schema$1.NullOr(Schema$1.Number)
|
|
12944
12944
|
});
|
|
12945
|
+
/**
|
|
12946
|
+
* How the recorded row ended, which is the supervisor's three outcomes plus the
|
|
12947
|
+
* one only the server can state: the review closed with no report at all.
|
|
12948
|
+
*
|
|
12949
|
+
* `unreported` is never accepted from a tool call. The server writes it when a
|
|
12950
|
+
* supervisor turn ends over an evidence read that nothing answered, so the
|
|
12951
|
+
* absence survives outside a live client that could infer it from two rows.
|
|
12952
|
+
*/
|
|
12953
|
+
const ThreadEvidenceActivityOutcome = Schema$1.Literals([
|
|
12954
|
+
"summarized",
|
|
12955
|
+
"fallback",
|
|
12956
|
+
"failed",
|
|
12957
|
+
"unreported"
|
|
12958
|
+
]);
|
|
12959
|
+
/**
|
|
12960
|
+
* Server clock around one review, never the supervisor's own account of it.
|
|
12961
|
+
*
|
|
12962
|
+
* `readAt` is when the evidence-read row was recorded and `closedAt` when the
|
|
12963
|
+
* row that closes the review was: the report for a reported review, the
|
|
12964
|
+
* completed supervisor turn for an unreported one. A supervisor turn that
|
|
12965
|
+
* failed or was interrupted closes nothing, so its read keeps no timing until
|
|
12966
|
+
* some later turn reports on it.
|
|
12967
|
+
*/
|
|
12968
|
+
const ThreadEvidenceReportTiming = Schema$1.Struct({
|
|
12969
|
+
readAt: IsoDateTime,
|
|
12970
|
+
closedAt: IsoDateTime,
|
|
12971
|
+
durationMs: NonNegativeInt
|
|
12972
|
+
});
|
|
12945
12973
|
Schema$1.Struct({
|
|
12946
12974
|
watchedThreadId: ThreadId,
|
|
12947
12975
|
firstSequence: NonNegativeInt,
|
|
12948
12976
|
lastSequence: NonNegativeInt,
|
|
12949
12977
|
throughSequence: NonNegativeInt,
|
|
12950
|
-
outcome:
|
|
12978
|
+
outcome: ThreadEvidenceActivityOutcome,
|
|
12951
12979
|
summary: Schema$1.NullOr(Schema$1.String),
|
|
12952
12980
|
/** True when a snapshot dropped the text of a superseded report. */
|
|
12953
12981
|
summaryDropped: Schema$1.optionalKey(Schema$1.Boolean),
|
|
12954
12982
|
model: Schema$1.NullOr(Schema$1.String),
|
|
12955
12983
|
reason: Schema$1.NullOr(Schema$1.String),
|
|
12956
12984
|
/** Null when no context-window row exists yet for the reporting thread. */
|
|
12957
|
-
context: Schema$1.NullOr(ThreadEvidenceReportContext)
|
|
12985
|
+
context: Schema$1.NullOr(ThreadEvidenceReportContext),
|
|
12986
|
+
/**
|
|
12987
|
+
* Null when the evidence-read row behind this review is no longer readable,
|
|
12988
|
+
* and absent on rows stored before the server measured a review at all.
|
|
12989
|
+
*/
|
|
12990
|
+
timing: Schema$1.optionalKey(Schema$1.NullOr(ThreadEvidenceReportTiming))
|
|
12958
12991
|
});
|
|
12959
12992
|
const ThreadEvidenceReportResult = Schema$1.Struct({
|
|
12960
12993
|
recorded: Schema$1.Boolean,
|
|
@@ -41541,6 +41574,28 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
41541
41574
|
detached_at AS "detachedAt"
|
|
41542
41575
|
FROM thread_pairs
|
|
41543
41576
|
ORDER BY created_at ASC, pair_id ASC
|
|
41577
|
+
`
|
|
41578
|
+
});
|
|
41579
|
+
const listActiveThreadPairRows = SqlSchema.findAll({
|
|
41580
|
+
Request: Schema$1.Void,
|
|
41581
|
+
Result: ProjectionThreadPairDbRowSchema,
|
|
41582
|
+
execute: () => sql`
|
|
41583
|
+
SELECT
|
|
41584
|
+
pair_id AS "id",
|
|
41585
|
+
implementer_thread_id AS "implementerThreadId",
|
|
41586
|
+
watcher_thread_id AS "watcherThreadId",
|
|
41587
|
+
last_reviewed_implementer_sequence AS "lastReviewedImplementerSequence",
|
|
41588
|
+
triaged_from_sequence AS "triagedFromSequence",
|
|
41589
|
+
activation_turn_id AS "activationTurnId",
|
|
41590
|
+
round_cap AS "roundCap",
|
|
41591
|
+
gate_timeout_ms AS "gateTimeoutMs",
|
|
41592
|
+
review_experiment_json AS "reviewExperiment",
|
|
41593
|
+
active_gate_json AS "activeGate",
|
|
41594
|
+
created_at AS "createdAt",
|
|
41595
|
+
detached_at AS "detachedAt"
|
|
41596
|
+
FROM thread_pairs
|
|
41597
|
+
WHERE detached_at IS NULL
|
|
41598
|
+
ORDER BY created_at ASC, pair_id ASC
|
|
41544
41599
|
`
|
|
41545
41600
|
});
|
|
41546
41601
|
const LatestContextWindowRow = Schema$1.Struct({
|
|
@@ -41562,15 +41617,137 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
41562
41617
|
LIMIT 1
|
|
41563
41618
|
`
|
|
41564
41619
|
});
|
|
41565
|
-
const EvidenceReadContextRow = Schema$1.Struct({
|
|
41620
|
+
const EvidenceReadContextRow = Schema$1.Struct({
|
|
41621
|
+
usedTokensAtRead: Schema$1.NullOr(Schema$1.Number),
|
|
41622
|
+
readAt: Schema$1.String,
|
|
41623
|
+
afterSequence: Schema$1.NullOr(Schema$1.Number)
|
|
41624
|
+
});
|
|
41566
41625
|
const getEvidenceReadContextRow = SqlSchema.findOneOption({
|
|
41567
41626
|
Request: Schema$1.String,
|
|
41568
41627
|
Result: EvidenceReadContextRow,
|
|
41569
41628
|
execute: (activityId) => sql`
|
|
41570
|
-
SELECT
|
|
41629
|
+
SELECT
|
|
41630
|
+
CASE
|
|
41631
|
+
WHEN json_extract(payload_json, '$.usedTokensAtRead') >= 0
|
|
41632
|
+
THEN json_extract(payload_json, '$.usedTokensAtRead')
|
|
41633
|
+
ELSE NULL
|
|
41634
|
+
END AS "usedTokensAtRead",
|
|
41635
|
+
created_at AS "readAt",
|
|
41636
|
+
json_extract(payload_json, '$.afterSequence') AS "afterSequence"
|
|
41571
41637
|
FROM projection_thread_activities
|
|
41572
41638
|
WHERE activity_id = ${activityId}
|
|
41573
|
-
|
|
41639
|
+
LIMIT 1
|
|
41640
|
+
`
|
|
41641
|
+
});
|
|
41642
|
+
const TurnStartRow = Schema$1.Struct({
|
|
41643
|
+
threadId: ThreadId,
|
|
41644
|
+
messageId: Schema$1.NullOr(Schema$1.String),
|
|
41645
|
+
startedAt: Schema$1.NullOr(Schema$1.String)
|
|
41646
|
+
});
|
|
41647
|
+
/**
|
|
41648
|
+
* The message a turn was started from, by the table's own key.
|
|
41649
|
+
*
|
|
41650
|
+
* Thread and turn together, because the unique key is the pair: a turn id is
|
|
41651
|
+
* provider-supplied for some providers, and matching on it alone would let
|
|
41652
|
+
* one provider's collision answer for another thread.
|
|
41653
|
+
*/
|
|
41654
|
+
const getTurnStartRow = SqlSchema.findOneOption({
|
|
41655
|
+
Request: Schema$1.Struct({
|
|
41656
|
+
threadId: ThreadId,
|
|
41657
|
+
turnId: TurnId
|
|
41658
|
+
}),
|
|
41659
|
+
Result: TurnStartRow,
|
|
41660
|
+
execute: (input) => sql`
|
|
41661
|
+
SELECT
|
|
41662
|
+
thread_id AS "threadId",
|
|
41663
|
+
pending_message_id AS "messageId",
|
|
41664
|
+
started_at AS "startedAt"
|
|
41665
|
+
FROM projection_turns
|
|
41666
|
+
WHERE thread_id = ${input.threadId}
|
|
41667
|
+
AND turn_id = ${input.turnId}
|
|
41668
|
+
LIMIT 1
|
|
41669
|
+
`
|
|
41670
|
+
});
|
|
41671
|
+
/** The turn a thread's session is running, by the session projection's own row. */
|
|
41672
|
+
const getActiveTurnIdRow = SqlSchema.findOneOption({
|
|
41673
|
+
Request: ThreadId,
|
|
41674
|
+
Result: Schema$1.Struct({ activeTurnId: Schema$1.NullOr(Schema$1.String) }),
|
|
41675
|
+
execute: (threadId) => sql`
|
|
41676
|
+
SELECT active_turn_id AS "activeTurnId"
|
|
41677
|
+
FROM projection_thread_sessions
|
|
41678
|
+
WHERE thread_id = ${threadId}
|
|
41679
|
+
LIMIT 1
|
|
41680
|
+
`
|
|
41681
|
+
});
|
|
41682
|
+
const ThreadMessageStampRow = Schema$1.Struct({
|
|
41683
|
+
messageId: Schema$1.String,
|
|
41684
|
+
createdAt: Schema$1.String
|
|
41685
|
+
});
|
|
41686
|
+
/**
|
|
41687
|
+
* The newest message on a thread whose id starts with a prefix.
|
|
41688
|
+
*
|
|
41689
|
+
* The server names the messages it sends itself, so asking by id prefix asks
|
|
41690
|
+
* what the server last sent - a question no turn row can be talked out of by
|
|
41691
|
+
* whichever turn happened to adopt a pending start.
|
|
41692
|
+
*/
|
|
41693
|
+
const getLatestThreadMessageWithIdPrefixRow = SqlSchema.findOneOption({
|
|
41694
|
+
Request: Schema$1.Struct({
|
|
41695
|
+
threadId: ThreadId,
|
|
41696
|
+
idPrefix: Schema$1.String
|
|
41697
|
+
}),
|
|
41698
|
+
Result: ThreadMessageStampRow,
|
|
41699
|
+
execute: (input) => sql`
|
|
41700
|
+
SELECT
|
|
41701
|
+
message_id AS "messageId",
|
|
41702
|
+
created_at AS "createdAt"
|
|
41703
|
+
FROM projection_thread_messages
|
|
41704
|
+
WHERE thread_id = ${input.threadId}
|
|
41705
|
+
AND message_id LIKE ${`${input.idPrefix}%`} ESCAPE '\\'
|
|
41706
|
+
ORDER BY created_at DESC, message_id DESC
|
|
41707
|
+
LIMIT 1
|
|
41708
|
+
`
|
|
41709
|
+
});
|
|
41710
|
+
const LatestEvidenceReadRow = Schema$1.Struct({
|
|
41711
|
+
watchedThreadId: ThreadId,
|
|
41712
|
+
afterSequence: Schema$1.NullOr(Schema$1.Number),
|
|
41713
|
+
throughSequence: Schema$1.Number,
|
|
41714
|
+
/** The supervisor turn that recorded the read, absent on rows written before it was stamped. */
|
|
41715
|
+
turnId: Schema$1.NullOr(Schema$1.String),
|
|
41716
|
+
readAt: Schema$1.String,
|
|
41717
|
+
hasReport: Schema$1.Number
|
|
41718
|
+
});
|
|
41719
|
+
/**
|
|
41720
|
+
* The newest evidence read on a thread, with whether a report answered it.
|
|
41721
|
+
*
|
|
41722
|
+
* Newest only: a supervisor that read again against a later bound superseded
|
|
41723
|
+
* the earlier read rather than leaving it unanswered, which is the same
|
|
41724
|
+
* pairing rule the client's own rows follow.
|
|
41725
|
+
*/
|
|
41726
|
+
const getLatestEvidenceReadRow = SqlSchema.findOneOption({
|
|
41727
|
+
Request: ThreadId,
|
|
41728
|
+
Result: LatestEvidenceReadRow,
|
|
41729
|
+
execute: (threadId) => sql`
|
|
41730
|
+
SELECT
|
|
41731
|
+
json_extract(reads.payload_json, '$.watchedThreadId') AS "watchedThreadId",
|
|
41732
|
+
json_extract(reads.payload_json, '$.afterSequence') AS "afterSequence",
|
|
41733
|
+
json_extract(reads.payload_json, '$.throughSequence') AS "throughSequence",
|
|
41734
|
+
json_extract(reads.payload_json, '$.turnId') AS "turnId",
|
|
41735
|
+
reads.created_at AS "readAt",
|
|
41736
|
+
EXISTS (
|
|
41737
|
+
SELECT 1
|
|
41738
|
+
FROM projection_thread_activities reports
|
|
41739
|
+
WHERE reports.thread_id = reads.thread_id
|
|
41740
|
+
AND reports.kind = 'fusion.evidence.summary'
|
|
41741
|
+
AND json_extract(reports.payload_json, '$.watchedThreadId')
|
|
41742
|
+
= json_extract(reads.payload_json, '$.watchedThreadId')
|
|
41743
|
+
AND json_extract(reports.payload_json, '$.throughSequence')
|
|
41744
|
+
= json_extract(reads.payload_json, '$.throughSequence')
|
|
41745
|
+
) AS "hasReport"
|
|
41746
|
+
FROM projection_thread_activities reads
|
|
41747
|
+
WHERE reads.thread_id = ${threadId}
|
|
41748
|
+
AND reads.kind = 'fusion.evidence.read'
|
|
41749
|
+
AND json_extract(reads.payload_json, '$.throughSequence') >= 0
|
|
41750
|
+
ORDER BY reads.created_at DESC, reads.rowid DESC
|
|
41574
41751
|
LIMIT 1
|
|
41575
41752
|
`
|
|
41576
41753
|
});
|
|
@@ -43362,8 +43539,26 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
43362
43539
|
})) };
|
|
43363
43540
|
});
|
|
43364
43541
|
const getLatestThreadContextWindow = (threadId) => getLatestContextWindowRow(threadId).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getLatestThreadContextWindow:query", "ProjectionSnapshotQuery.getLatestThreadContextWindow:decodeRow")));
|
|
43365
|
-
const getEvidenceReadContext = (activityId) => getEvidenceReadContextRow(activityId).pipe(Effect.
|
|
43542
|
+
const getEvidenceReadContext = (activityId) => getEvidenceReadContextRow(activityId).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getEvidenceReadContext:query", "ProjectionSnapshotQuery.getEvidenceReadContext:decodeRow")));
|
|
43543
|
+
const getTurnStart = (threadId, turnId) => getTurnStartRow({
|
|
43544
|
+
threadId,
|
|
43545
|
+
turnId
|
|
43546
|
+
}).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getTurnStart:query", "ProjectionSnapshotQuery.getTurnStart:decodeRow")));
|
|
43547
|
+
const getActiveTurnId = (threadId) => getActiveTurnIdRow(threadId).pipe(Effect.map(Option.flatMap((row) => Option.fromNullable(row.activeTurnId))), Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getActiveTurnId:query", "ProjectionSnapshotQuery.getActiveTurnId:decodeRow")));
|
|
43548
|
+
const getLatestThreadMessageWithIdPrefix = (threadId, idPrefix) => getLatestThreadMessageWithIdPrefixRow({
|
|
43549
|
+
threadId,
|
|
43550
|
+
idPrefix
|
|
43551
|
+
}).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getLatestThreadMessageWithIdPrefix:query", "ProjectionSnapshotQuery.getLatestThreadMessageWithIdPrefix:decodeRow")));
|
|
43552
|
+
const getLatestEvidenceRead = (threadId) => getLatestEvidenceReadRow(threadId).pipe(Effect.map(Option.map((row) => ({
|
|
43553
|
+
watchedThreadId: row.watchedThreadId,
|
|
43554
|
+
afterSequence: row.afterSequence,
|
|
43555
|
+
throughSequence: row.throughSequence,
|
|
43556
|
+
turnId: row.turnId,
|
|
43557
|
+
readAt: row.readAt,
|
|
43558
|
+
hasReport: row.hasReport !== 0
|
|
43559
|
+
}))), Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getLatestEvidenceRead:query", "ProjectionSnapshotQuery.getLatestEvidenceRead:decodeRow")));
|
|
43366
43560
|
const getThreadPairById = (pairId) => getThreadPairRow(pairId).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadPairById:query", "ProjectionSnapshotQuery.getThreadPairById:decodeRow")));
|
|
43561
|
+
const getActiveThreadPairs = () => listActiveThreadPairRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getActiveThreadPairs:query", "ProjectionSnapshotQuery.getActiveThreadPairs:decodeRows")));
|
|
43367
43562
|
const getThreadShellById = (threadId) => Effect.gen(function* () {
|
|
43368
43563
|
const [threadRow, latestTurnRow, sessionRow] = yield* Effect.all([
|
|
43369
43564
|
getActiveThreadRowById({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadShellById:getThread:query", "ProjectionSnapshotQuery.getThreadShellById:getThread:decodeRow"))),
|
|
@@ -43584,6 +43779,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
43584
43779
|
getThreadCheckpointContext,
|
|
43585
43780
|
getFullThreadDiffContext,
|
|
43586
43781
|
getThreadPairById,
|
|
43782
|
+
getActiveThreadPairs,
|
|
43783
|
+
getLatestEvidenceRead,
|
|
43784
|
+
getTurnStart,
|
|
43785
|
+
getLatestThreadMessageWithIdPrefix,
|
|
43786
|
+
getActiveTurnId,
|
|
43587
43787
|
getThreadShellById,
|
|
43588
43788
|
getThreadDetailById,
|
|
43589
43789
|
getHandoffContext,
|
|
@@ -59353,6 +59553,21 @@ function fusionRoleInstructionsFor(role, options) {
|
|
|
59353
59553
|
const model = fusionSummarizerModelFor(options.provider, options.summarizerModelOverride);
|
|
59354
59554
|
return `${FUSION_WATCHER_INSTRUCTIONS} ${FUSION_EVIDENCE_SUMMARY_INSTRUCTIONS} ${summarizerModelLine(model)}`;
|
|
59355
59555
|
}
|
|
59556
|
+
/**
|
|
59557
|
+
* The closing duty, restated in the wake that asks for the review.
|
|
59558
|
+
*
|
|
59559
|
+
* The same duty is in the supervisor's role block, but a wake message spells
|
|
59560
|
+
* out the whole turn and a model follows the instruction it was just handed:
|
|
59561
|
+
* with the duty only in the session channel, this install recorded 58 evidence
|
|
59562
|
+
* reads and no reports at all. Stated here it travels the common provider
|
|
59563
|
+
* input path, so it reaches every provider whatever its session prompt does.
|
|
59564
|
+
*/
|
|
59565
|
+
function fusionReviewClosingInstruction(input) {
|
|
59566
|
+
const close = `Then close this review with exactly one thread_evidence_report call: threadId ${input.implementerThreadId}, throughSequence ${input.throughSequence} - that exact number, never lastSequence and never the final page's nextAfterSequence, because the server pairs the report to the read by it - plus the first and last sequence you actually covered.`;
|
|
59567
|
+
return input.evidenceSummary ? `Read that evidence through one summarizer subagent against the same fixed throughSequence ${input.throughSequence} and review from what it returns. Fail open: if no subagent is available, the spawn errors, it times out, or what comes back is empty or cites no sequences, read the range yourself instead. Never skip or shorten the review because a summary was unavailable.
|
|
59568
|
+
|
|
59569
|
+
${close} Outcome summarized with the subagent's exact returned text and the model it ran on, fallback when you read the range yourself, or failed when the summarizer could not run - with your reason in the last two cases. Report even when no summary ever arrived; an unreported review cannot be told from a supervisor that never tried. Never paste builder tool output into the report.` : `${close} Evidence summarization is off for this environment, so the outcome is fallback with the reason you read the range yourself. Never paste builder tool output into the report.`;
|
|
59570
|
+
}
|
|
59356
59571
|
function fusionRoleReferenceLineFor(role) {
|
|
59357
59572
|
return role === "implementer" ? FUSION_BUILDER_REFERENCE_LINE : FUSION_WATCHER_REFERENCE_LINE;
|
|
59358
59573
|
}
|
|
@@ -134032,6 +134247,86 @@ const TypeSafeToolkitHandlersLive = TypeSafeToolkit.toLayer({ typesafe_ask: (inp
|
|
|
134032
134247
|
}).pipe(Effect.mapError((error) => new TypeSafeAskFailedError({ reason: `TypeSafe: ${error.reason}` })));
|
|
134033
134248
|
}) });
|
|
134034
134249
|
//#endregion
|
|
134250
|
+
//#region src/orchestration/fusionEvidenceReport.ts
|
|
134251
|
+
/**
|
|
134252
|
+
* Shared shape of one Fusion review's evidence rows.
|
|
134253
|
+
*
|
|
134254
|
+
* Two writers close a review: the supervisor calling `thread_evidence_report`,
|
|
134255
|
+
* and the server itself when a supervisor turn ends over a read nothing
|
|
134256
|
+
* answered. Both key their row the same way and measure the same numbers from
|
|
134257
|
+
* the same recorded rows, so a reader cannot tell which writer it is looking at
|
|
134258
|
+
* apart from the outcome the row carries.
|
|
134259
|
+
*
|
|
134260
|
+
* @module fusionEvidenceReport
|
|
134261
|
+
*/
|
|
134262
|
+
/**
|
|
134263
|
+
* One activity per review, not one per page.
|
|
134264
|
+
*
|
|
134265
|
+
* A supervisor pages a range with a fixed `throughSequence`, so keying the id
|
|
134266
|
+
* on the watched thread and that bound collapses a whole paged read into a
|
|
134267
|
+
* single row: the projector replaces an activity of the same id rather than
|
|
134268
|
+
* appending beside it.
|
|
134269
|
+
*/
|
|
134270
|
+
const evidenceReadActivityId = (watchedThreadId, throughSequence) => EventId.make(`fusion-evidence:${watchedThreadId}:${throughSequence}`);
|
|
134271
|
+
/** One report per review, keyed like the read it reports on so a retry replaces. */
|
|
134272
|
+
const evidenceReportActivityId = (watchedThreadId, throughSequence) => EventId.make(`fusion-evidence-summary:${watchedThreadId}:${throughSequence}`);
|
|
134273
|
+
/**
|
|
134274
|
+
* Server clock across the review, from the two recorded rows that bracket it.
|
|
134275
|
+
*
|
|
134276
|
+
* An unparsable timestamp yields null rather than a zero, and a closing time
|
|
134277
|
+
* behind the read yields null as well: a negative duration is a clock problem,
|
|
134278
|
+
* not a review that took no time.
|
|
134279
|
+
*/
|
|
134280
|
+
const evidenceTiming = (readAt, closedAt) => {
|
|
134281
|
+
const read = Date.parse(readAt);
|
|
134282
|
+
const closed = Date.parse(closedAt);
|
|
134283
|
+
if (Number.isNaN(read) || Number.isNaN(closed) || closed < read) return null;
|
|
134284
|
+
return {
|
|
134285
|
+
readAt,
|
|
134286
|
+
closedAt,
|
|
134287
|
+
durationMs: closed - read
|
|
134288
|
+
};
|
|
134289
|
+
};
|
|
134290
|
+
/**
|
|
134291
|
+
* The reporting thread's own context and elapsed time around the read it is
|
|
134292
|
+
* closing.
|
|
134293
|
+
*
|
|
134294
|
+
* `before` comes off the evidence-read activity rather than the closing row, so
|
|
134295
|
+
* the pair of numbers brackets the ingestion instead of describing only the
|
|
134296
|
+
* moment the supervisor got around to reporting. A thread with no
|
|
134297
|
+
* context-window row yet yields null rather than a zero that would read as a
|
|
134298
|
+
* real measurement.
|
|
134299
|
+
*/
|
|
134300
|
+
const correlateEvidence = Effect.fn("fusionEvidenceReport.correlateEvidence")(function* (input) {
|
|
134301
|
+
const projections = yield* ProjectionSnapshotQuery;
|
|
134302
|
+
const latest = yield* projections.getLatestThreadContextWindow(input.readerThreadId).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
134303
|
+
const read = yield* projections.getEvidenceReadContext(input.readActivityId).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
134304
|
+
const timing = Option.match(read, {
|
|
134305
|
+
onNone: () => null,
|
|
134306
|
+
onSome: (row) => evidenceTiming(row.readAt, input.closedAt)
|
|
134307
|
+
});
|
|
134308
|
+
if (Option.isNone(latest)) return {
|
|
134309
|
+
context: null,
|
|
134310
|
+
timing
|
|
134311
|
+
};
|
|
134312
|
+
const usedTokensAfter = latest.value.usedTokens;
|
|
134313
|
+
const usedTokensBefore = Option.match(read, {
|
|
134314
|
+
onNone: () => null,
|
|
134315
|
+
onSome: (row) => row.usedTokensAtRead
|
|
134316
|
+
});
|
|
134317
|
+
const maxTokens = latest.value.maxTokens;
|
|
134318
|
+
return {
|
|
134319
|
+
context: {
|
|
134320
|
+
usedTokensBefore,
|
|
134321
|
+
usedTokensAfter,
|
|
134322
|
+
deltaTokens: usedTokensBefore === null ? null : usedTokensAfter - usedTokensBefore,
|
|
134323
|
+
maxTokens,
|
|
134324
|
+
capacityPercent: maxTokens === null || maxTokens <= 0 ? null : Math.round(usedTokensAfter / maxTokens * 1e3) / 10
|
|
134325
|
+
},
|
|
134326
|
+
timing
|
|
134327
|
+
};
|
|
134328
|
+
});
|
|
134329
|
+
//#endregion
|
|
134035
134330
|
//#region src/mcp/toolkits/watch/tools.ts
|
|
134036
134331
|
/**
|
|
134037
134332
|
* Cross-thread by design, which is exactly why it is the most guarded tool in
|
|
@@ -134085,17 +134380,6 @@ const ThreadEvidenceReportTool = Tool.make("thread_evidence_report", {
|
|
|
134085
134380
|
const WatchToolkit = Toolkit.make(ThreadWatchEventsTool, ThreadWatchReviewTool, ThreadEvidenceReportTool);
|
|
134086
134381
|
//#endregion
|
|
134087
134382
|
//#region src/mcp/toolkits/watch/handlers.ts
|
|
134088
|
-
/**
|
|
134089
|
-
* One activity per review, not one per page.
|
|
134090
|
-
*
|
|
134091
|
-
* A supervisor pages a range with a fixed `throughSequence`, so keying the id
|
|
134092
|
-
* on the watched thread and that bound collapses a whole paged read into a
|
|
134093
|
-
* single row: the projector replaces an activity of the same id rather than
|
|
134094
|
-
* appending beside it.
|
|
134095
|
-
*/
|
|
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
134383
|
const MODEL_ID_MAX_CHARS = 120;
|
|
134100
134384
|
/** Plain words, because the client capitalizes and renders them as the row heading. */
|
|
134101
134385
|
const REPORT_HEADING = {
|
|
@@ -134110,31 +134394,43 @@ const REPORT_HEADING = {
|
|
|
134110
134394
|
* for; losing the note that it happened is a smaller loss than losing the page,
|
|
134111
134395
|
* so a rejected dispatch is logged and the page is returned anyway.
|
|
134112
134396
|
*/
|
|
134113
|
-
const recordEvidenceRead = Effect.fn("mcp.watch.recordEvidenceRead")(function* (readerThreadId, page) {
|
|
134397
|
+
const recordEvidenceRead = Effect.fn("mcp.watch.recordEvidenceRead")(function* (readerThreadId, page, afterSequence) {
|
|
134114
134398
|
const engine = yield* OrchestrationEngineService;
|
|
134115
134399
|
const crypto = yield* Crypto.Crypto;
|
|
134116
134400
|
const projections = yield* ProjectionSnapshotQuery;
|
|
134117
134401
|
const createdAt = DateTime.formatIso(yield* DateTime.now);
|
|
134118
|
-
const recorded = yield* projections.getEvidenceReadContext(
|
|
134119
|
-
const
|
|
134402
|
+
const recorded = yield* projections.getEvidenceReadContext(evidenceReadActivityId(page.threadId, page.throughSequence)).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
134403
|
+
const opened = Option.isSome(recorded);
|
|
134404
|
+
const readCreatedAt = Option.match(recorded, {
|
|
134405
|
+
onNone: () => createdAt,
|
|
134406
|
+
onSome: (row) => row.readAt
|
|
134407
|
+
});
|
|
134408
|
+
const readAfterSequence = Option.match(recorded, {
|
|
134409
|
+
onNone: () => afterSequence,
|
|
134410
|
+
onSome: (row) => row.afterSequence ?? afterSequence
|
|
134411
|
+
});
|
|
134412
|
+
const activeTurnId = yield* projections.getActiveTurnId(readerThreadId).pipe(Effect.orElseSucceed(() => Option.none()), Effect.map(Option.getOrNull));
|
|
134413
|
+
const usedTokensAtRead = opened ? Option.getOrNull(Option.map(recorded, (row) => row.usedTokensAtRead)) : yield* projections.getLatestThreadContextWindow(readerThreadId).pipe(Effect.map(Option.map((row) => row.usedTokens)), Effect.orElseSucceed(() => Option.none()), Effect.map(Option.getOrNull));
|
|
134120
134414
|
yield* engine.dispatch({
|
|
134121
134415
|
type: "thread.activity.append",
|
|
134122
134416
|
commandId: CommandId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie)),
|
|
134123
134417
|
threadId: readerThreadId,
|
|
134124
134418
|
createdAt,
|
|
134125
134419
|
activity: {
|
|
134126
|
-
id:
|
|
134127
|
-
createdAt,
|
|
134420
|
+
id: evidenceReadActivityId(page.threadId, page.throughSequence),
|
|
134421
|
+
createdAt: readCreatedAt,
|
|
134128
134422
|
tone: "info",
|
|
134129
134423
|
kind: "fusion.evidence.read",
|
|
134130
134424
|
summary: "Read builder evidence",
|
|
134131
134425
|
turnId: null,
|
|
134132
134426
|
payload: {
|
|
134133
134427
|
watchedThreadId: page.threadId,
|
|
134428
|
+
afterSequence: readAfterSequence,
|
|
134134
134429
|
throughSequence: page.throughSequence,
|
|
134135
134430
|
nextAfterSequence: page.nextAfterSequence,
|
|
134136
134431
|
hasMore: page.hasMore,
|
|
134137
134432
|
scannedEvents: page.scannedEvents,
|
|
134433
|
+
...activeTurnId === null ? {} : { turnId: activeTurnId },
|
|
134138
134434
|
...usedTokensAtRead === null ? {} : { usedTokensAtRead }
|
|
134139
134435
|
}
|
|
134140
134436
|
}
|
|
@@ -134150,30 +134446,6 @@ const clip = (value, limit) => {
|
|
|
134150
134446
|
if (!trimmed) return null;
|
|
134151
134447
|
return trimmed.length <= limit ? trimmed : `${trimmed.slice(0, limit)}…`;
|
|
134152
134448
|
};
|
|
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
|
-
});
|
|
134177
134449
|
const WatchToolkitHandlersLive = WatchToolkit.toLayer({
|
|
134178
134450
|
thread_watch_review: (input) => Effect.gen(function* () {
|
|
134179
134451
|
const invocation = yield* requireWatchCapability(input.threadId);
|
|
@@ -134181,7 +134453,7 @@ const WatchToolkitHandlersLive = WatchToolkit.toLayer({
|
|
|
134181
134453
|
threadId: input.threadId,
|
|
134182
134454
|
detail: cause.message
|
|
134183
134455
|
})));
|
|
134184
|
-
yield* recordEvidenceRead(invocation.threadId, page);
|
|
134456
|
+
yield* recordEvidenceRead(invocation.threadId, page, input.afterSequence ?? 0);
|
|
134185
134457
|
return page;
|
|
134186
134458
|
}),
|
|
134187
134459
|
thread_evidence_report: (input) => Effect.gen(function* () {
|
|
@@ -134189,7 +134461,11 @@ const WatchToolkitHandlersLive = WatchToolkit.toLayer({
|
|
|
134189
134461
|
const engine = yield* OrchestrationEngineService;
|
|
134190
134462
|
const crypto = yield* Crypto.Crypto;
|
|
134191
134463
|
const createdAt = DateTime.formatIso(yield* DateTime.now);
|
|
134192
|
-
const context = yield*
|
|
134464
|
+
const { context, timing } = yield* correlateEvidence({
|
|
134465
|
+
readerThreadId: invocation.threadId,
|
|
134466
|
+
readActivityId: evidenceReadActivityId(input.threadId, input.throughSequence),
|
|
134467
|
+
closedAt: createdAt
|
|
134468
|
+
});
|
|
134193
134469
|
const summary = clip(input.summary, THREAD_EVIDENCE_REPORT_MAX_CHARS);
|
|
134194
134470
|
const reason = clip(input.reason, 500);
|
|
134195
134471
|
const outcome = summary === null && input.outcome === "summarized" ? "failed" : input.outcome;
|
|
@@ -134200,7 +134476,7 @@ const WatchToolkitHandlersLive = WatchToolkit.toLayer({
|
|
|
134200
134476
|
threadId: invocation.threadId,
|
|
134201
134477
|
createdAt,
|
|
134202
134478
|
activity: {
|
|
134203
|
-
id:
|
|
134479
|
+
id: evidenceReportActivityId(input.threadId, input.throughSequence),
|
|
134204
134480
|
createdAt,
|
|
134205
134481
|
tone: outcome === "failed" ? "error" : "info",
|
|
134206
134482
|
kind: "fusion.evidence.summary",
|
|
@@ -134215,7 +134491,8 @@ const WatchToolkitHandlersLive = WatchToolkit.toLayer({
|
|
|
134215
134491
|
summary,
|
|
134216
134492
|
model: clip(input.model, MODEL_ID_MAX_CHARS),
|
|
134217
134493
|
reason: reason ?? (outcome === "summarized" ? null : "the supervisor gave no reason"),
|
|
134218
|
-
context
|
|
134494
|
+
context,
|
|
134495
|
+
timing
|
|
134219
134496
|
}
|
|
134220
134497
|
}
|
|
134221
134498
|
}).pipe(Effect.as(true), Effect.catchCause((cause) => Effect.logWarning("could not record the supervisor's evidence report", {
|
|
@@ -139153,6 +139430,12 @@ ${FUSION_WATCHER_TOOL_INSTRUCTIONS}
|
|
|
139153
139430
|
|
|
139154
139431
|
Call thread_watch_review, threadId ${input.implementerThreadId}, afterSequence ${input.afterSequence}, throughSequence ${input.throughSequence}. Continue with nextAfterSequence and the same throughSequence until hasMore is false, including empty pages. Apply message append/replace operations by identity across pages. Recover truncated or uncertain evidence with targeted thread_watch_events reads using its source sequence range. Inspect repo when useful.
|
|
139155
139432
|
|
|
139433
|
+
${fusionReviewClosingInstruction({
|
|
139434
|
+
implementerThreadId: input.implementerThreadId,
|
|
139435
|
+
throughSequence: input.throughSequence,
|
|
139436
|
+
evidenceSummary: input.evidenceSummary
|
|
139437
|
+
})}
|
|
139438
|
+
|
|
139156
139439
|
Always report concise:
|
|
139157
139440
|
|
|
139158
139441
|
- Progress: work done; completeness.
|
|
@@ -139234,6 +139517,13 @@ const make$3 = Effect.gen(function* () {
|
|
|
139234
139517
|
activePairs: (readModel.threadPairs ?? []).filter((pair) => pair.detachedAt === null)
|
|
139235
139518
|
};
|
|
139236
139519
|
});
|
|
139520
|
+
/**
|
|
139521
|
+
* Activity events arrive at provider streaming speed on every thread in the
|
|
139522
|
+
* install, and only a pair's own activities matter here. The pair table
|
|
139523
|
+
* alone answers that, so the hot path never decodes the whole command read
|
|
139524
|
+
* model; the rare branches that need threads read it themselves.
|
|
139525
|
+
*/
|
|
139526
|
+
const readActivePairs = projectionSnapshotQuery.getActiveThreadPairs();
|
|
139237
139527
|
const appendGateActivity = Effect.fn("FusionWatcherReactor.appendGateActivity")(function* (input) {
|
|
139238
139528
|
yield* orchestrationEngine.dispatch({
|
|
139239
139529
|
type: "thread.activity.append",
|
|
@@ -139488,10 +139778,18 @@ const make$3 = Effect.gen(function* () {
|
|
|
139488
139778
|
const requestId = payload["requestId"];
|
|
139489
139779
|
return typeof requestId === "string" && requestId.length > 0 ? requestId : void 0;
|
|
139490
139780
|
};
|
|
139781
|
+
/** The only activity kinds this reactor reacts to; everything else returns first. */
|
|
139782
|
+
const FUSION_ACTIVITY_KINDS = /* @__PURE__ */ new Set([
|
|
139783
|
+
"provider.turn.start.failed",
|
|
139784
|
+
"approval.requested",
|
|
139785
|
+
"approval.resolved",
|
|
139786
|
+
"tool.completed"
|
|
139787
|
+
]);
|
|
139491
139788
|
const processActivityAppended = Effect.fn("FusionWatcherReactor.processActivityAppended")(function* (event) {
|
|
139492
139789
|
if (event.sequence <= liveEventsAfterSequence) return;
|
|
139493
139790
|
const activity = event.payload.activity;
|
|
139494
|
-
|
|
139791
|
+
if (!FUSION_ACTIVITY_KINDS.has(activity.kind)) return;
|
|
139792
|
+
const activePairs = yield* readActivePairs;
|
|
139495
139793
|
const pairedThread = activePairs.find((candidate) => candidate.implementerThreadId === event.payload.threadId || candidate.watcherThreadId === event.payload.threadId);
|
|
139496
139794
|
if (activity.kind === "provider.turn.start.failed" && pairedThread !== void 0) {
|
|
139497
139795
|
yield* pausePairAfterProviderFailure({
|
|
@@ -139657,6 +139955,14 @@ const make$3 = Effect.gen(function* () {
|
|
|
139657
139955
|
});
|
|
139658
139956
|
});
|
|
139659
139957
|
/**
|
|
139958
|
+
* Unreadable settings mean the shipped default, not off: a supervisor that
|
|
139959
|
+
* was told nothing about closing its review is the failure this line exists
|
|
139960
|
+
* to prevent.
|
|
139961
|
+
*/
|
|
139962
|
+
const readEvidenceSummaryEnabled = Effect.gen(function* () {
|
|
139963
|
+
return (yield* serverSettings.getSettings.pipe(Effect.catchCause(() => Effect.succeed(null))))?.fusionEvidenceSummary ?? DEFAULT_FUSION_PROMPT_SETTINGS.evidenceSummary;
|
|
139964
|
+
});
|
|
139965
|
+
/**
|
|
139660
139966
|
* The pre-filter's configuration, or null when it must not run at all.
|
|
139661
139967
|
*
|
|
139662
139968
|
* A settings read failure returns null rather than a default: an unreadable
|
|
@@ -139752,7 +140058,8 @@ const make$3 = Effect.gen(function* () {
|
|
|
139752
140058
|
text: watcherPrompt({
|
|
139753
140059
|
implementerThreadId: currentPair.implementerThreadId,
|
|
139754
140060
|
afterSequence: currentPair.lastReviewedImplementerSequence,
|
|
139755
|
-
throughSequence: completion.sequence
|
|
140061
|
+
throughSequence: completion.sequence,
|
|
140062
|
+
evidenceSummary: yield* readEvidenceSummaryEnabled
|
|
139756
140063
|
}) + "\n\n" + fusionReviewPolicyInstructions(currentPair.reviewExperiment),
|
|
139757
140064
|
attachments: []
|
|
139758
140065
|
},
|
|
@@ -139820,6 +140127,73 @@ const make$3 = Effect.gen(function* () {
|
|
|
139820
140127
|
yield* processReview(pair, completion, turn);
|
|
139821
140128
|
}
|
|
139822
140129
|
});
|
|
140130
|
+
/** Writes the row, once the caller has established the turn owed one. */
|
|
140131
|
+
const stampUnreported = Effect.fn("FusionWatcherReactor.stampUnreported")(function* (pair, completion, latest) {
|
|
140132
|
+
const { context, timing } = yield* correlateEvidence({
|
|
140133
|
+
readerThreadId: pair.watcherThreadId,
|
|
140134
|
+
readActivityId: evidenceReadActivityId(latest.watchedThreadId, latest.throughSequence),
|
|
140135
|
+
closedAt: completion.occurredAt
|
|
140136
|
+
});
|
|
140137
|
+
yield* orchestrationEngine.dispatch({
|
|
140138
|
+
type: "thread.activity.append",
|
|
140139
|
+
commandId: CommandId.make(`server:fusion:${pair.id}:evidence-unreported:${latest.throughSequence}`),
|
|
140140
|
+
threadId: pair.watcherThreadId,
|
|
140141
|
+
createdAt: completion.occurredAt,
|
|
140142
|
+
activity: {
|
|
140143
|
+
id: evidenceReportActivityId(latest.watchedThreadId, latest.throughSequence),
|
|
140144
|
+
createdAt: completion.occurredAt,
|
|
140145
|
+
tone: "error",
|
|
140146
|
+
kind: "fusion.evidence.summary",
|
|
140147
|
+
summary: "Evidence never reported",
|
|
140148
|
+
turnId: null,
|
|
140149
|
+
payload: {
|
|
140150
|
+
watchedThreadId: latest.watchedThreadId,
|
|
140151
|
+
firstSequence: latest.afterSequence === null ? latest.throughSequence : Math.min(latest.afterSequence + 1, latest.throughSequence),
|
|
140152
|
+
lastSequence: latest.throughSequence,
|
|
140153
|
+
throughSequence: latest.throughSequence,
|
|
140154
|
+
outcome: "unreported",
|
|
140155
|
+
summary: null,
|
|
140156
|
+
model: null,
|
|
140157
|
+
reason: "the supervisor ended its turn without reporting on the evidence it read",
|
|
140158
|
+
context,
|
|
140159
|
+
timing
|
|
140160
|
+
}
|
|
140161
|
+
}
|
|
140162
|
+
});
|
|
140163
|
+
});
|
|
140164
|
+
/**
|
|
140165
|
+
* Closes a supervisor turn that read the builder's evidence and ended without
|
|
140166
|
+
* reporting on it.
|
|
140167
|
+
*
|
|
140168
|
+
* The row it writes is keyed exactly like the supervisor's own report, so a
|
|
140169
|
+
* later report replaces it rather than doubling it, and it carries the same
|
|
140170
|
+
* server-measured context and timing. Without it, a review that was never
|
|
140171
|
+
* reported is indistinguishable in stored history from one that was never
|
|
140172
|
+
* attempted.
|
|
140173
|
+
*/
|
|
140174
|
+
const recordUnreportedEvidence = Effect.fn("FusionWatcherReactor.recordUnreportedEvidence")(function* (pair, completion, input) {
|
|
140175
|
+
const read = yield* projectionSnapshotQuery.getLatestEvidenceRead(pair.watcherThreadId).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
140176
|
+
if (Option.isNone(read)) return;
|
|
140177
|
+
const latest = read.value;
|
|
140178
|
+
if (latest.hasReport || latest.watchedThreadId !== pair.implementerThreadId) return;
|
|
140179
|
+
if (completion.payload.turnId === null) return;
|
|
140180
|
+
const turn = yield* projectionSnapshotQuery.getTurnStart(pair.watcherThreadId, completion.payload.turnId).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
140181
|
+
if (Option.isNone(turn)) return;
|
|
140182
|
+
const started = turn.value;
|
|
140183
|
+
if (started.threadId !== pair.watcherThreadId) return;
|
|
140184
|
+
const readAt = Date.parse(latest.readAt);
|
|
140185
|
+
if (latest.turnId === null) {
|
|
140186
|
+
if (input.replayed) return;
|
|
140187
|
+
if (started.startedAt !== null && readAt < Date.parse(started.startedAt)) return;
|
|
140188
|
+
if (readAt > Date.parse(completion.occurredAt)) return;
|
|
140189
|
+
} else if (latest.turnId !== completion.payload.turnId) return;
|
|
140190
|
+
const wakeAt = (prefix) => projectionSnapshotQuery.getLatestThreadMessageWithIdPrefix(pair.watcherThreadId, prefix).pipe(Effect.map(Option.flatMap((wake) => {
|
|
140191
|
+
const sentAt = Date.parse(wake.createdAt);
|
|
140192
|
+
return Number.isNaN(sentAt) || sentAt > readAt ? Option.none() : Option.some(sentAt);
|
|
140193
|
+
})), Effect.orElseSucceed(() => Option.none()));
|
|
140194
|
+
if (!(started.messageId === null ? yield* Effect.map(Effect.all([wakeAt("fusion-review:"), wakeAt("fusion-gate:")]), ([review, gate]) => Option.isSome(review) && (Option.isNone(gate) || gate.value < review.value)) : started.messageId.startsWith("fusion-review:"))) return;
|
|
140195
|
+
yield* stampUnreported(pair, completion, latest);
|
|
140196
|
+
});
|
|
139823
140197
|
const processCompletion = Effect.fn("FusionWatcherReactor.processCompletion")(function* (event) {
|
|
139824
140198
|
const activePairs = ((yield* projectionSnapshotQuery.getCommandReadModel()).threadPairs ?? []).filter((pair) => pair.detachedAt === null);
|
|
139825
140199
|
const failedPair = activePairs.find((pair) => pair.implementerThreadId === event.payload.threadId || pair.watcherThreadId === event.payload.threadId);
|
|
@@ -139834,6 +140208,11 @@ const make$3 = Effect.gen(function* () {
|
|
|
139834
140208
|
return;
|
|
139835
140209
|
}
|
|
139836
140210
|
if (event.payload.state !== "completed") return;
|
|
140211
|
+
const watcherPair = activePairs.find((pair) => pair.watcherThreadId === event.payload.threadId);
|
|
140212
|
+
if (watcherPair !== void 0) yield* recordUnreportedEvidence(watcherPair, event, { replayed: event.sequence <= liveEventsAfterSequence }).pipe(Effect.catchCause((cause) => Effect.logWarning("could not record an unreported Fusion evidence read", {
|
|
140213
|
+
pairId: watcherPair.id,
|
|
140214
|
+
cause: Cause.pretty(cause)
|
|
140215
|
+
})));
|
|
139837
140216
|
const pairs = activePairs.filter((pair) => pair.implementerThreadId === event.payload.threadId && event.sequence > pair.lastReviewedImplementerSequence);
|
|
139838
140217
|
yield* Effect.forEach(pairs, (pair) => catchUpPair(pair, event.sequence), {
|
|
139839
140218
|
concurrency: 1,
|