@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 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.14";
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
- * Four states rather than a boolean because they are acted on differently: a
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, 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.
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: ThreadEvidenceReportOutcome,
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({ usedTokensAtRead: Schema$1.Number });
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 json_extract(payload_json, '$.usedTokensAtRead') AS "usedTokensAtRead"
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
- AND json_extract(payload_json, '$.usedTokensAtRead') >= 0
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.map(Option.map((row) => row.usedTokensAtRead)), Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getEvidenceReadContext:query", "ProjectionSnapshotQuery.getEvidenceReadContext:decodeRow")));
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(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));
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: evidenceActivityId(page.threadId, page.throughSequence),
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* correlateContext(invocation.threadId, evidenceActivityId(input.threadId, input.throughSequence));
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: reportActivityId(input.threadId, input.throughSequence),
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
- const { activePairs } = yield* readPairs;
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,