@@ -21,6 +21,7 @@ import * as DateTime from "effect/DateTime";
2121import * as Effect from "effect/Effect" ;
2222import * as Layer from "effect/Layer" ;
2323import * as Stream from "effect/Stream" ;
24+ import * as Tracer from "effect/Tracer" ;
2425
2526import * as CheckpointStore from "../checkpointing/CheckpointStore.ts" ;
2627import * as ServerConfig from "../config.ts" ;
@@ -749,6 +750,24 @@ it.layer(layerTest)("delegated completion delivery repairs", (it) => {
749750
750751// Runtime reconciliation writes restart cancellations under this command
751752// prefix, which the live terminal-run listener skips.
753+ // Collects the SQL text of every statement run under the returned tracer.
754+ const traceSqlStatements = ( ) => {
755+ const statements : Array < string > = [ ] ;
756+ const tracer = Tracer . make ( {
757+ span ( options ) {
758+ const span = new Tracer . NativeSpan ( options ) ;
759+ const end = span . end . bind ( span ) ;
760+ span . end = ( endTime , exit ) => {
761+ end ( endTime , exit ) ;
762+ const query = span . attributes . get ( "db.query.text" ) ;
763+ if ( typeof query === "string" ) statements . push ( query ) ;
764+ } ;
765+ return span ;
766+ } ,
767+ } ) ;
768+ return { statements, tracer } ;
769+ } ;
770+
752771const reconcileCommandId = ( name : string ) => CommandId . make ( `command:runtime-reconcile:${ name } ` ) ;
753772
754773const parentProviderThreadId = ( threadId : ThreadId ) =>
@@ -1578,6 +1597,111 @@ it.layer(layerTest)("delegated tasks across a server restart", (it) => {
15781597 } ) ,
15791598 ) ;
15801599
1600+ it . effect ( "settles only the current delivery of a parent with long delivery history" , ( ) =>
1601+ Effect . gen ( function * ( ) {
1602+ const orchestrator = yield * Orchestrator . OrchestratorV2 ;
1603+ const eventSink = yield * EventSink . EventSinkV2 ;
1604+ const now = yield * DateTime . now ;
1605+ const threadId = ThreadId . make ( "thread:delivery-history" ) ;
1606+ const runId = RunId . make ( "run:delivery-history" ) ;
1607+ const taskId = NodeId . make ( "node:delivery-history-task" ) ;
1608+ const currentMessageId = MessageId . make ( `message:delegated-delivery:${ threadId } ` ) ;
1609+ const currentRunId = RunId . make ( "run:delivery-history:current" ) ;
1610+ const HISTORY = 40 ;
1611+ yield * seedParentWithTerminalTask ( {
1612+ threadId,
1613+ projectId : ProjectId . make ( "project:delivery-history" ) ,
1614+ runId,
1615+ rootNodeId : NodeId . make ( "node:delivery-history-root" ) ,
1616+ taskId,
1617+ deliveryState : "claimed" ,
1618+ completionWake : "always" ,
1619+ deliveryTaskIds : [ taskId ] ,
1620+ now,
1621+ } ) ;
1622+ const deliveryMessage = ( id : MessageId , deliveryRunId : RunId ) => ( {
1623+ id : EventId . make ( `event:${ deliveryRunId } :message` ) ,
1624+ type : "message.updated" as const ,
1625+ threadId,
1626+ runId : deliveryRunId ,
1627+ occurredAt : now ,
1628+ payload : {
1629+ id,
1630+ threadId,
1631+ runId : deliveryRunId ,
1632+ nodeId : null ,
1633+ role : "user" as const ,
1634+ text : `Delegated task ${ taskId } reached a terminal state.` ,
1635+ attachments : [ ] ,
1636+ streaming : false ,
1637+ createdBy : "agent" as const ,
1638+ creationSource : "server" as const ,
1639+ createdAt : now ,
1640+ updatedAt : now ,
1641+ delegatedCompletion : { parentRunId : runId , generation : 1 , taskIds : [ taskId ] } ,
1642+ } ,
1643+ } ) ;
1644+ // Earlier deliveries that already settled. Each one still owns a
1645+ // delegated message and a terminal run.
1646+ const history = Array . from ( { length : HISTORY } , ( _ , index ) => {
1647+ const historyRunId = RunId . make ( `run:delivery-history:${ index } ` ) ;
1648+ const messageId = MessageId . make ( `message:delivery-history:${ index } ` ) ;
1649+ return [
1650+ deliveryMessage ( messageId , historyRunId ) ,
1651+ runEvent ( {
1652+ threadId,
1653+ runId : historyRunId ,
1654+ ordinal : index + 2 ,
1655+ status : "completed" ,
1656+ now,
1657+ providerThreadId : parentProviderThreadId ( threadId ) ,
1658+ userMessageId : messageId ,
1659+ } ) ,
1660+ ] ;
1661+ } ) . flat ( ) ;
1662+ yield * eventSink . write ( {
1663+ commandId : reconcileCommandId ( "delivery-history" ) ,
1664+ events : [
1665+ ...history ,
1666+ deliveryMessage ( currentMessageId , currentRunId ) ,
1667+ runEvent ( {
1668+ threadId,
1669+ runId : currentRunId ,
1670+ ordinal : HISTORY + 2 ,
1671+ status : "completed" ,
1672+ now,
1673+ providerThreadId : parentProviderThreadId ( threadId ) ,
1674+ userMessageId : currentMessageId ,
1675+ } ) ,
1676+ ] ,
1677+ } ) ;
1678+
1679+ const { statements, tracer } = traceSqlStatements ( ) ;
1680+ yield * orchestrator . recoverDelegatedTasks . pipe ( Effect . withTracer ( tracer ) ) ;
1681+
1682+ const projection = yield * orchestrator . getThreadProjection ( threadId ) ;
1683+ assert . equal (
1684+ projection . subagents . find ( ( row ) => row . id === taskId ) ?. completionDelivery ?. state ,
1685+ "delivered" ,
1686+ ) ;
1687+ assert . isNull ( projection . runs . find ( ( row ) => row . id === runId ) ?. delegatedCompletion ?. delivery ) ;
1688+ // Recovery reads the delivery message once, for the current delivery
1689+ // run only. History must not cost one thread reload per past delivery.
1690+ assert . lengthOf (
1691+ statements . filter ( ( statement ) =>
1692+ statement . includes ( "FROM orchestration_v2_projection_messages AS message" ) ,
1693+ ) ,
1694+ 1 ,
1695+ ) ;
1696+
1697+ // A second recovery finds nothing open on this thread and changes nothing.
1698+ yield * orchestrator . recoverDelegatedTasks ;
1699+ const again = yield * orchestrator . getThreadProjection ( threadId ) ;
1700+ assert . deepEqual ( again . runs , projection . runs ) ;
1701+ assert . deepEqual ( again . subagents , projection . subagents ) ;
1702+ } ) ,
1703+ ) ;
1704+
15811705 it . effect ( "settles a cancelled child whose held wakes wait behind it" , ( ) =>
15821706 Effect . gen ( function * ( ) {
15831707 const orchestrator = yield * Orchestrator . OrchestratorV2 ;
0 commit comments