@smthrs/gateway 0.35.0 → 1.0.0-rc.4
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/CHANGELOG.md +198 -0
- package/LICENSE +1 -1
- package/README.md +92 -0
- package/dist/cjs/Diagnosis.d.ts +275 -0
- package/dist/cjs/Diagnosis.d.ts.map +1 -0
- package/dist/cjs/Diagnosis.js +476 -0
- package/dist/cjs/Diagnosis.js.map +7 -0
- package/dist/cjs/EngineTrace.d.ts +76 -0
- package/dist/cjs/EngineTrace.d.ts.map +1 -0
- package/dist/cjs/EngineTrace.js +509 -0
- package/dist/cjs/EngineTrace.js.map +7 -0
- package/dist/cjs/GatewayError.d.ts +54 -0
- package/dist/cjs/GatewayError.d.ts.map +1 -0
- package/dist/cjs/GatewayError.js +51 -0
- package/dist/cjs/GatewayError.js.map +7 -0
- package/dist/cjs/GatewayProjection.d.ts +480 -0
- package/dist/cjs/GatewayProjection.d.ts.map +1 -0
- package/dist/cjs/GatewayProjection.js +471 -0
- package/dist/cjs/GatewayProjection.js.map +7 -0
- package/dist/cjs/GatewayRpcs.d.ts +1886 -0
- package/dist/cjs/GatewayRpcs.d.ts.map +1 -0
- package/dist/cjs/GatewayRpcs.js +121 -0
- package/dist/cjs/GatewayRpcs.js.map +7 -0
- package/dist/cjs/GatewaySchema.d.ts +3274 -0
- package/dist/cjs/GatewaySchema.d.ts.map +1 -0
- package/dist/cjs/GatewaySchema.js +141 -0
- package/dist/cjs/GatewaySchema.js.map +7 -0
- package/dist/cjs/GatewayServer.d.ts +427 -0
- package/dist/cjs/GatewayServer.d.ts.map +1 -0
- package/dist/cjs/GatewayServer.js +428 -0
- package/dist/cjs/GatewayServer.js.map +7 -0
- package/dist/cjs/HostBranchPresence.d.ts +19 -0
- package/dist/cjs/HostBranchPresence.d.ts.map +1 -0
- package/dist/cjs/HostBranchPresence.js +82 -0
- package/dist/cjs/HostBranchPresence.js.map +7 -0
- package/dist/cjs/Projections.d.ts +209 -0
- package/dist/cjs/Projections.d.ts.map +1 -0
- package/dist/cjs/Projections.js +906 -0
- package/dist/cjs/Projections.js.map +7 -0
- package/dist/cjs/RunDevTools.d.ts +181 -0
- package/dist/cjs/RunDevTools.d.ts.map +1 -0
- package/dist/cjs/RunDevTools.js +195 -0
- package/dist/cjs/RunDevTools.js.map +7 -0
- package/dist/cjs/RunHistory.d.ts +185 -0
- package/dist/cjs/RunHistory.d.ts.map +1 -0
- package/dist/cjs/RunHistory.js +68 -0
- package/dist/cjs/RunHistory.js.map +7 -0
- package/dist/cjs/RunTrace.d.ts +753 -0
- package/dist/cjs/RunTrace.d.ts.map +1 -0
- package/dist/cjs/RunTrace.js +1595 -0
- package/dist/cjs/RunTrace.js.map +7 -0
- package/dist/cjs/RuntimeBridge.d.ts +1386 -0
- package/dist/cjs/RuntimeBridge.d.ts.map +1 -0
- package/dist/cjs/RuntimeBridge.js +598 -0
- package/dist/cjs/RuntimeBridge.js.map +7 -0
- package/dist/cjs/bun/BunGateway.d.ts +20 -0
- package/dist/cjs/bun/BunGateway.d.ts.map +1 -0
- package/dist/cjs/bun/BunGateway.js +62 -0
- package/dist/cjs/bun/BunGateway.js.map +7 -0
- package/dist/cjs/index.d.ts +65 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +59 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/NativeGateway.d.ts +176 -0
- package/dist/cjs/internal/NativeGateway.d.ts.map +1 -0
- package/dist/cjs/internal/NativeGateway.js +187 -0
- package/dist/cjs/internal/NativeGateway.js.map +7 -0
- package/dist/cjs/internal/actionRenderings.d.ts +12 -0
- package/dist/cjs/internal/actionRenderings.d.ts.map +1 -0
- package/dist/cjs/internal/actionRenderings.js +224 -0
- package/dist/cjs/internal/actionRenderings.js.map +7 -0
- package/dist/cjs/internal/callEvents.d.ts +97 -0
- package/dist/cjs/internal/callEvents.d.ts.map +1 -0
- package/dist/cjs/internal/callEvents.js +169 -0
- package/dist/cjs/internal/callEvents.js.map +7 -0
- package/dist/cjs/internal/digestIndex.d.ts +31 -0
- package/dist/cjs/internal/digestIndex.d.ts.map +1 -0
- package/dist/cjs/internal/digestIndex.js +105 -0
- package/dist/cjs/internal/digestIndex.js.map +7 -0
- package/dist/cjs/internal/digestMemory.d.ts +19 -0
- package/dist/cjs/internal/digestMemory.d.ts.map +1 -0
- package/dist/cjs/internal/digestMemory.js +33 -0
- package/dist/cjs/internal/digestMemory.js.map +7 -0
- package/dist/cjs/internal/inspectLabels.d.ts +13 -0
- package/dist/cjs/internal/inspectLabels.d.ts.map +1 -0
- package/dist/cjs/internal/inspectLabels.js +29 -0
- package/dist/cjs/internal/inspectLabels.js.map +7 -0
- package/dist/cjs/internal/nativeResolution.d.ts +41 -0
- package/dist/cjs/internal/nativeResolution.d.ts.map +1 -0
- package/dist/cjs/internal/nativeResolution.js +62 -0
- package/dist/cjs/internal/nativeResolution.js.map +7 -0
- package/dist/cjs/internal/nodeEvents.d.ts +47 -0
- package/dist/cjs/internal/nodeEvents.d.ts.map +1 -0
- package/dist/cjs/internal/nodeEvents.js +60 -0
- package/dist/cjs/internal/nodeEvents.js.map +7 -0
- package/dist/cjs/node/NodeGateway.d.ts +16 -0
- package/dist/cjs/node/NodeGateway.d.ts.map +1 -0
- package/dist/cjs/node/NodeGateway.js +48 -0
- package/dist/cjs/node/NodeGateway.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/Diagnosis.d.ts +275 -0
- package/dist/esm/Diagnosis.d.ts.map +1 -0
- package/dist/esm/Diagnosis.js +697 -0
- package/dist/esm/Diagnosis.js.map +1 -0
- package/dist/esm/EngineTrace.d.ts +76 -0
- package/dist/esm/EngineTrace.d.ts.map +1 -0
- package/dist/esm/EngineTrace.js +557 -0
- package/dist/esm/EngineTrace.js.map +1 -0
- package/dist/esm/GatewayError.d.ts +54 -0
- package/dist/esm/GatewayError.d.ts.map +1 -0
- package/dist/esm/GatewayError.js +60 -0
- package/dist/esm/GatewayError.js.map +1 -0
- package/dist/esm/GatewayProjection.d.ts +480 -0
- package/dist/esm/GatewayProjection.d.ts.map +1 -0
- package/dist/esm/GatewayProjection.js +799 -0
- package/dist/esm/GatewayProjection.js.map +1 -0
- package/dist/esm/GatewayRpcs.d.ts +1886 -0
- package/dist/esm/GatewayRpcs.d.ts.map +1 -0
- package/dist/esm/GatewayRpcs.js +145 -0
- package/dist/esm/GatewayRpcs.js.map +1 -0
- package/dist/esm/GatewaySchema.d.ts +3274 -0
- package/dist/esm/GatewaySchema.d.ts.map +1 -0
- package/dist/esm/GatewaySchema.js +244 -0
- package/dist/esm/GatewaySchema.js.map +1 -0
- package/dist/esm/GatewayServer.d.ts +427 -0
- package/dist/esm/GatewayServer.d.ts.map +1 -0
- package/dist/esm/GatewayServer.js +725 -0
- package/dist/esm/GatewayServer.js.map +1 -0
- package/dist/esm/HostBranchPresence.d.ts +19 -0
- package/dist/esm/HostBranchPresence.d.ts.map +1 -0
- package/dist/esm/HostBranchPresence.js +57 -0
- package/dist/esm/HostBranchPresence.js.map +1 -0
- package/dist/esm/Projections.d.ts +209 -0
- package/dist/esm/Projections.d.ts.map +1 -0
- package/dist/esm/Projections.js +1254 -0
- package/dist/esm/Projections.js.map +1 -0
- package/dist/esm/RunDevTools.d.ts +181 -0
- package/dist/esm/RunDevTools.d.ts.map +1 -0
- package/dist/esm/RunDevTools.js +281 -0
- package/dist/esm/RunDevTools.js.map +1 -0
- package/dist/esm/RunHistory.d.ts +185 -0
- package/dist/esm/RunHistory.d.ts.map +1 -0
- package/dist/esm/RunHistory.js +109 -0
- package/dist/esm/RunHistory.js.map +1 -0
- package/dist/esm/RunTrace.d.ts +753 -0
- package/dist/esm/RunTrace.d.ts.map +1 -0
- package/dist/esm/RunTrace.js +2106 -0
- package/dist/esm/RunTrace.js.map +1 -0
- package/dist/esm/RuntimeBridge.d.ts +1386 -0
- package/dist/esm/RuntimeBridge.d.ts.map +1 -0
- package/dist/esm/RuntimeBridge.js +659 -0
- package/dist/esm/RuntimeBridge.js.map +1 -0
- package/dist/esm/bun/BunGateway.d.ts +20 -0
- package/dist/esm/bun/BunGateway.d.ts.map +1 -0
- package/dist/esm/bun/BunGateway.js +32 -0
- package/dist/esm/bun/BunGateway.js.map +1 -0
- package/dist/esm/index.d.ts +65 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +65 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/NativeGateway.d.ts +176 -0
- package/dist/esm/internal/NativeGateway.d.ts.map +1 -0
- package/dist/esm/internal/NativeGateway.js +268 -0
- package/dist/esm/internal/NativeGateway.js.map +1 -0
- package/dist/esm/internal/actionRenderings.d.ts +12 -0
- package/dist/esm/internal/actionRenderings.d.ts.map +1 -0
- package/dist/esm/internal/actionRenderings.js +212 -0
- package/dist/esm/internal/actionRenderings.js.map +1 -0
- package/dist/esm/internal/callEvents.d.ts +97 -0
- package/dist/esm/internal/callEvents.d.ts.map +1 -0
- package/dist/esm/internal/callEvents.js +206 -0
- package/dist/esm/internal/callEvents.js.map +1 -0
- package/dist/esm/internal/digestIndex.d.ts +31 -0
- package/dist/esm/internal/digestIndex.d.ts.map +1 -0
- package/dist/esm/internal/digestIndex.js +87 -0
- package/dist/esm/internal/digestIndex.js.map +1 -0
- package/dist/esm/internal/digestMemory.d.ts +19 -0
- package/dist/esm/internal/digestMemory.d.ts.map +1 -0
- package/dist/esm/internal/digestMemory.js +23 -0
- package/dist/esm/internal/digestMemory.js.map +1 -0
- package/dist/esm/internal/inspectLabels.d.ts +13 -0
- package/dist/esm/internal/inspectLabels.d.ts.map +1 -0
- package/dist/esm/internal/inspectLabels.js +17 -0
- package/dist/esm/internal/inspectLabels.js.map +1 -0
- package/dist/esm/internal/nativeResolution.d.ts +41 -0
- package/dist/esm/internal/nativeResolution.d.ts.map +1 -0
- package/dist/esm/internal/nativeResolution.js +78 -0
- package/dist/esm/internal/nativeResolution.js.map +1 -0
- package/dist/esm/internal/nodeEvents.d.ts +47 -0
- package/dist/esm/internal/nodeEvents.d.ts.map +1 -0
- package/dist/esm/internal/nodeEvents.js +60 -0
- package/dist/esm/internal/nodeEvents.js.map +1 -0
- package/dist/esm/node/NodeGateway.d.ts +16 -0
- package/dist/esm/node/NodeGateway.d.ts.map +1 -0
- package/dist/esm/node/NodeGateway.js +13 -0
- package/dist/esm/node/NodeGateway.js.map +1 -0
- package/dist/vendor/adapters.json +2707 -0
- package/dist/vendor/bun/BunChildProcessSpawner.d.ts +7 -0
- package/dist/vendor/bun/BunChildProcessSpawner.js +14 -0
- package/dist/vendor/bun/BunClusterHttp.d.ts +83 -0
- package/dist/vendor/bun/BunClusterHttp.js +39 -0
- package/dist/vendor/bun/BunClusterSocket.d.ts +65 -0
- package/dist/vendor/bun/BunClusterSocket.js +22 -0
- package/dist/vendor/bun/BunCrypto.d.ts +10 -0
- package/dist/vendor/bun/BunCrypto.js +8 -0
- package/dist/vendor/bun/BunFileSystem.d.ts +10 -0
- package/dist/vendor/bun/BunFileSystem.js +9 -0
- package/dist/vendor/bun/BunHttpClient.d.ts +9 -0
- package/dist/vendor/bun/BunHttpClient.js +1 -0
- package/dist/vendor/bun/BunHttpPlatform.d.ts +10 -0
- package/dist/vendor/bun/BunHttpPlatform.js +11 -0
- package/dist/vendor/bun/BunHttpServer.d.ts +142 -0
- package/dist/vendor/bun/BunHttpServer.js +38 -0
- package/dist/vendor/bun/BunHttpServerRequest.d.ts +18 -0
- package/dist/vendor/bun/BunHttpServerRequest.js +7 -0
- package/dist/vendor/bun/BunMultipart.d.ts +31 -0
- package/dist/vendor/bun/BunMultipart.js +12 -0
- package/dist/vendor/bun/BunPath.d.ts +24 -0
- package/dist/vendor/bun/BunPath.js +12 -0
- package/dist/vendor/bun/BunRedis.d.ts +49 -0
- package/dist/vendor/bun/BunRedis.js +11 -0
- package/dist/vendor/bun/BunRuntime.d.ts +85 -0
- package/dist/vendor/bun/BunRuntime.js +8 -0
- package/dist/vendor/bun/BunServices.d.ts +34 -0
- package/dist/vendor/bun/BunServices.js +23 -0
- package/dist/vendor/bun/BunSink.d.ts +8 -0
- package/dist/vendor/bun/BunSink.js +12 -0
- package/dist/vendor/bun/BunSocket.d.ts +40 -0
- package/dist/vendor/bun/BunSocket.js +29 -0
- package/dist/vendor/bun/BunSocketServer.d.ts +8 -0
- package/dist/vendor/bun/BunSocketServer.js +21 -0
- package/dist/vendor/bun/BunStdio.d.ts +11 -0
- package/dist/vendor/bun/BunStdio.js +10 -0
- package/dist/vendor/bun/BunStream.d.ts +19 -0
- package/dist/vendor/bun/BunStream.js +30 -0
- package/dist/vendor/bun/BunTerminal.d.ts +21 -0
- package/dist/vendor/bun/BunTerminal.js +10 -0
- package/dist/vendor/bun/BunWorker.d.ts +20 -0
- package/dist/vendor/bun/BunWorker.js +9 -0
- package/dist/vendor/bun/BunWorkerRunner.d.ts +12 -0
- package/dist/vendor/bun/BunWorkerRunner.js +7 -0
- package/dist/vendor/bun/LICENSE +21 -0
- package/dist/vendor/bun/index.d.ts +92 -0
- package/dist/vendor/bun/index.js +107 -0
- package/dist/vendor/chunk-2GCOZTTW.js +48 -0
- package/dist/vendor/chunk-37CUR3PO.js +81 -0
- package/dist/vendor/chunk-3ARKKENZ.js +45 -0
- package/dist/vendor/chunk-3NV2UDBI.js +7 -0
- package/dist/vendor/chunk-3OSW7CT7.js +143 -0
- package/dist/vendor/chunk-62R63L74.js +62 -0
- package/dist/vendor/chunk-6AQLBC2I.js +118 -0
- package/dist/vendor/chunk-6C5H7S6B.js +18 -0
- package/dist/vendor/chunk-6DY5RBZD.js +85 -0
- package/dist/vendor/chunk-6UPBD47P.js +53 -0
- package/dist/vendor/chunk-AVVVN75Z.js +96 -0
- package/dist/vendor/chunk-B4DHEMBB.js +14 -0
- package/dist/vendor/chunk-BCK3NJOX.js +15 -0
- package/dist/vendor/chunk-BFBIHJ6X.js +0 -0
- package/dist/vendor/chunk-CES4OUM4.js +63 -0
- package/dist/vendor/chunk-D64L6YND.js +59 -0
- package/dist/vendor/chunk-DS2R6WOM.js +18 -0
- package/dist/vendor/chunk-DX4Z23DM.js +65 -0
- package/dist/vendor/chunk-DZ2VOUDP.js +564 -0
- package/dist/vendor/chunk-FOXN6JPJ.js +61 -0
- package/dist/vendor/chunk-FPQF7CR5.js +55 -0
- package/dist/vendor/chunk-FUTCTCEG.js +20 -0
- package/dist/vendor/chunk-GUPAV2YZ.js +18 -0
- package/dist/vendor/chunk-H2Z2Y4QH.js +70 -0
- package/dist/vendor/chunk-HAEDVSSM.js +129 -0
- package/dist/vendor/chunk-HY7BBL3K.js +93 -0
- package/dist/vendor/chunk-IFTTT6D5.js +31 -0
- package/dist/vendor/chunk-KQDSNB7M.js +26 -0
- package/dist/vendor/chunk-LIHLXXGQ.js +26 -0
- package/dist/vendor/chunk-LKCGQEE5.js +28 -0
- package/dist/vendor/chunk-MH2J3BYB.js +0 -0
- package/dist/vendor/chunk-ML2NAFH2.js +131 -0
- package/dist/vendor/chunk-MLFIZ5O2.js +0 -0
- package/dist/vendor/chunk-MLKGABMK.js +9 -0
- package/dist/vendor/chunk-MSCLEMYX.js +71 -0
- package/dist/vendor/chunk-OBWXQKVP.js +57 -0
- package/dist/vendor/chunk-QR557K66.js +20 -0
- package/dist/vendor/chunk-R4QUY7DH.js +18 -0
- package/dist/vendor/chunk-RGECXXJS.js +18 -0
- package/dist/vendor/chunk-TFY7W6D4.js +36 -0
- package/dist/vendor/chunk-TGPIVY4A.js +104 -0
- package/dist/vendor/chunk-TGXCX6W3.js +398 -0
- package/dist/vendor/chunk-TOHSFXMV.js +437 -0
- package/dist/vendor/chunk-UHIDPUFF.js +34 -0
- package/dist/vendor/chunk-ULT6CWUI.js +109 -0
- package/dist/vendor/chunk-UXBGYQR3.js +18 -0
- package/dist/vendor/chunk-V6LUGQ7O.js +21 -0
- package/dist/vendor/chunk-W4OASVX6.js +92 -0
- package/dist/vendor/chunk-WAII54GA.js +18 -0
- package/dist/vendor/chunk-WECQDT36.js +426 -0
- package/dist/vendor/chunk-WHKMOC7Z.js +28 -0
- package/dist/vendor/chunk-WVFOZ2UL.js +40 -0
- package/dist/vendor/chunk-X73E2UPI.js +18 -0
- package/dist/vendor/chunk-XCYARUII.js +63 -0
- package/dist/vendor/chunk-XFEVTDVD.js +34 -0
- package/dist/vendor/chunk-XFZZYSJ4.js +22 -0
- package/dist/vendor/chunk-XJWJXLXJ.js +18 -0
- package/dist/vendor/chunk-XY3Q62EO.js +18 -0
- package/dist/vendor/chunk-YDASSC75.js +394 -0
- package/dist/vendor/chunk-Z6C7SO5Z.js +507 -0
- package/dist/vendor/chunk-Z7QNPJEB.js +229 -0
- package/dist/vendor/chunk-Z7QPP4YH.js +249 -0
- package/dist/vendor/chunk-ZCHYLHJH.js +86 -0
- package/dist/vendor/chunk-ZIQVYVMA.js +22 -0
- package/dist/vendor/chunk-ZPIG2CUA.js +34 -0
- package/dist/vendor/cjs/bun/BunChildProcessSpawner.d.ts +7 -0
- package/dist/vendor/cjs/bun/BunChildProcessSpawner.js +1 -0
- package/dist/vendor/cjs/bun/BunClusterHttp.d.ts +83 -0
- package/dist/vendor/cjs/bun/BunClusterHttp.js +1 -0
- package/dist/vendor/cjs/bun/BunClusterSocket.d.ts +65 -0
- package/dist/vendor/cjs/bun/BunClusterSocket.js +1 -0
- package/dist/vendor/cjs/bun/BunCrypto.d.ts +10 -0
- package/dist/vendor/cjs/bun/BunCrypto.js +1 -0
- package/dist/vendor/cjs/bun/BunFileSystem.d.ts +10 -0
- package/dist/vendor/cjs/bun/BunFileSystem.js +1 -0
- package/dist/vendor/cjs/bun/BunHttpClient.d.ts +9 -0
- package/dist/vendor/cjs/bun/BunHttpClient.js +1 -0
- package/dist/vendor/cjs/bun/BunHttpPlatform.d.ts +10 -0
- package/dist/vendor/cjs/bun/BunHttpPlatform.js +1 -0
- package/dist/vendor/cjs/bun/BunHttpServer.d.ts +142 -0
- package/dist/vendor/cjs/bun/BunHttpServer.js +1 -0
- package/dist/vendor/cjs/bun/BunHttpServerRequest.d.ts +18 -0
- package/dist/vendor/cjs/bun/BunHttpServerRequest.js +1 -0
- package/dist/vendor/cjs/bun/BunMultipart.d.ts +31 -0
- package/dist/vendor/cjs/bun/BunMultipart.js +1 -0
- package/dist/vendor/cjs/bun/BunPath.d.ts +24 -0
- package/dist/vendor/cjs/bun/BunPath.js +1 -0
- package/dist/vendor/cjs/bun/BunRedis.d.ts +49 -0
- package/dist/vendor/cjs/bun/BunRedis.js +1 -0
- package/dist/vendor/cjs/bun/BunRuntime.d.ts +85 -0
- package/dist/vendor/cjs/bun/BunRuntime.js +1 -0
- package/dist/vendor/cjs/bun/BunServices.d.ts +34 -0
- package/dist/vendor/cjs/bun/BunServices.js +1 -0
- package/dist/vendor/cjs/bun/BunSink.d.ts +8 -0
- package/dist/vendor/cjs/bun/BunSink.js +1 -0
- package/dist/vendor/cjs/bun/BunSocket.d.ts +40 -0
- package/dist/vendor/cjs/bun/BunSocket.js +1 -0
- package/dist/vendor/cjs/bun/BunSocketServer.d.ts +8 -0
- package/dist/vendor/cjs/bun/BunSocketServer.js +1 -0
- package/dist/vendor/cjs/bun/BunStdio.d.ts +11 -0
- package/dist/vendor/cjs/bun/BunStdio.js +1 -0
- package/dist/vendor/cjs/bun/BunStream.d.ts +19 -0
- package/dist/vendor/cjs/bun/BunStream.js +1 -0
- package/dist/vendor/cjs/bun/BunTerminal.d.ts +21 -0
- package/dist/vendor/cjs/bun/BunTerminal.js +1 -0
- package/dist/vendor/cjs/bun/BunWorker.d.ts +20 -0
- package/dist/vendor/cjs/bun/BunWorker.js +1 -0
- package/dist/vendor/cjs/bun/BunWorkerRunner.d.ts +12 -0
- package/dist/vendor/cjs/bun/BunWorkerRunner.js +1 -0
- package/dist/vendor/cjs/bun/index.d.ts +92 -0
- package/dist/vendor/cjs/bun/index.js +1 -0
- package/dist/vendor/cjs/node/NodeChildProcessSpawner.d.ts +7 -0
- package/dist/vendor/cjs/node/NodeChildProcessSpawner.js +1 -0
- package/dist/vendor/cjs/node/NodeClusterHttp.d.ts +63 -0
- package/dist/vendor/cjs/node/NodeClusterHttp.js +1 -0
- package/dist/vendor/cjs/node/NodeClusterSocket.d.ts +79 -0
- package/dist/vendor/cjs/node/NodeClusterSocket.js +1 -0
- package/dist/vendor/cjs/node/NodeCrypto.d.ts +10 -0
- package/dist/vendor/cjs/node/NodeCrypto.js +1 -0
- package/dist/vendor/cjs/node/NodeFileSystem.d.ts +10 -0
- package/dist/vendor/cjs/node/NodeFileSystem.js +1 -0
- package/dist/vendor/cjs/node/NodeHttpClient.d.ts +179 -0
- package/dist/vendor/cjs/node/NodeHttpClient.js +1 -0
- package/dist/vendor/cjs/node/NodeHttpIncomingMessage.d.ts +62 -0
- package/dist/vendor/cjs/node/NodeHttpIncomingMessage.js +1 -0
- package/dist/vendor/cjs/node/NodeHttpPlatform.d.ts +36 -0
- package/dist/vendor/cjs/node/NodeHttpPlatform.js +1 -0
- package/dist/vendor/cjs/node/NodeHttpServer.d.ts +120 -0
- package/dist/vendor/cjs/node/NodeHttpServer.js +1 -0
- package/dist/vendor/cjs/node/NodeHttpServerRequest.d.ts +29 -0
- package/dist/vendor/cjs/node/NodeHttpServerRequest.js +1 -0
- package/dist/vendor/cjs/node/NodeMultipart.d.ts +46 -0
- package/dist/vendor/cjs/node/NodeMultipart.js +1 -0
- package/dist/vendor/cjs/node/NodeMultipartParser.d.ts +97 -0
- package/dist/vendor/cjs/node/NodeMultipartParser.js +1 -0
- package/dist/vendor/cjs/node/NodePath.d.ts +27 -0
- package/dist/vendor/cjs/node/NodePath.js +1 -0
- package/dist/vendor/cjs/node/NodeRedis.d.ts +74 -0
- package/dist/vendor/cjs/node/NodeRedis.js +1 -0
- package/dist/vendor/cjs/node/NodeRuntime.d.ts +85 -0
- package/dist/vendor/cjs/node/NodeRuntime.js +1 -0
- package/dist/vendor/cjs/node/NodeServices.d.ts +34 -0
- package/dist/vendor/cjs/node/NodeServices.js +1 -0
- package/dist/vendor/cjs/node/NodeSink.d.ts +8 -0
- package/dist/vendor/cjs/node/NodeSink.js +1 -0
- package/dist/vendor/cjs/node/NodeSocket.d.ts +38 -0
- package/dist/vendor/cjs/node/NodeSocket.js +1 -0
- package/dist/vendor/cjs/node/NodeSocketServer.d.ts +8 -0
- package/dist/vendor/cjs/node/NodeSocketServer.js +1 -0
- package/dist/vendor/cjs/node/NodeStdio.d.ts +11 -0
- package/dist/vendor/cjs/node/NodeStdio.js +1 -0
- package/dist/vendor/cjs/node/NodeStream.d.ts +8 -0
- package/dist/vendor/cjs/node/NodeStream.js +1 -0
- package/dist/vendor/cjs/node/NodeTerminal.d.ts +21 -0
- package/dist/vendor/cjs/node/NodeTerminal.js +1 -0
- package/dist/vendor/cjs/node/NodeWorker.d.ts +22 -0
- package/dist/vendor/cjs/node/NodeWorker.js +1 -0
- package/dist/vendor/cjs/node/NodeWorkerRunner.d.ts +12 -0
- package/dist/vendor/cjs/node/NodeWorkerRunner.js +1 -0
- package/dist/vendor/cjs/node/Undici.d.ts +28 -0
- package/dist/vendor/cjs/node/Undici.js +1 -0
- package/dist/vendor/cjs/node/index.d.ts +100 -0
- package/dist/vendor/cjs/node/index.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeChildProcessSpawner.d.ts +57 -0
- package/dist/vendor/cjs/node-shared/NodeChildProcessSpawner.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeClusterSocket.d.ts +22 -0
- package/dist/vendor/cjs/node-shared/NodeClusterSocket.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeCrypto.d.ts +27 -0
- package/dist/vendor/cjs/node-shared/NodeCrypto.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeFileSystem.d.ts +11 -0
- package/dist/vendor/cjs/node-shared/NodeFileSystem.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeHttpCompression.d.ts +36 -0
- package/dist/vendor/cjs/node-shared/NodeHttpCompression.js +1 -0
- package/dist/vendor/cjs/node-shared/NodePath.d.ts +27 -0
- package/dist/vendor/cjs/node-shared/NodePath.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeRuntime.d.ts +47 -0
- package/dist/vendor/cjs/node-shared/NodeRuntime.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeSink.d.ts +69 -0
- package/dist/vendor/cjs/node-shared/NodeSink.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeSocket.d.ts +134 -0
- package/dist/vendor/cjs/node-shared/NodeSocket.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeSocketServer.d.ts +80 -0
- package/dist/vendor/cjs/node-shared/NodeSocketServer.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeStdio.d.ts +12 -0
- package/dist/vendor/cjs/node-shared/NodeStdio.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeStream.d.ts +179 -0
- package/dist/vendor/cjs/node-shared/NodeStream.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeTerminal.d.ts +22 -0
- package/dist/vendor/cjs/node-shared/NodeTerminal.js +1 -0
- package/dist/vendor/cjs/node-shared/index.d.ts +56 -0
- package/dist/vendor/cjs/node-shared/index.js +1 -0
- package/dist/vendor/cjs/node-shared/internal/nodeChildProcessSpawner.d.ts +4 -0
- package/dist/vendor/cjs/node-shared/internal/nodeChildProcessSpawner.js +1 -0
- package/dist/vendor/cjs/node-shared/internal/utils.d.ts +2 -0
- package/dist/vendor/cjs/node-shared/internal/utils.js +1 -0
- package/dist/vendor/cjs/package.json +1 -0
- package/dist/vendor/cjs/platform-bun.d.ts +92 -0
- package/dist/vendor/cjs/platform-bun.js +1 -0
- package/dist/vendor/cjs/platform-node-shared.d.ts +56 -0
- package/dist/vendor/cjs/platform-node-shared.js +1 -0
- package/dist/vendor/cjs/platform-node.d.ts +100 -0
- package/dist/vendor/cjs/platform-node.js +1 -0
- package/dist/vendor/node/LICENSE +21 -0
- package/dist/vendor/node/NodeChildProcessSpawner.d.ts +7 -0
- package/dist/vendor/node/NodeChildProcessSpawner.js +14 -0
- package/dist/vendor/node/NodeClusterHttp.d.ts +63 -0
- package/dist/vendor/node/NodeClusterHttp.js +43 -0
- package/dist/vendor/node/NodeClusterSocket.d.ts +79 -0
- package/dist/vendor/node/NodeClusterSocket.js +30 -0
- package/dist/vendor/node/NodeCrypto.d.ts +10 -0
- package/dist/vendor/node/NodeCrypto.js +8 -0
- package/dist/vendor/node/NodeFileSystem.d.ts +10 -0
- package/dist/vendor/node/NodeFileSystem.js +9 -0
- package/dist/vendor/node/NodeHttpClient.d.ts +179 -0
- package/dist/vendor/node/NodeHttpClient.js +46 -0
- package/dist/vendor/node/NodeHttpIncomingMessage.d.ts +62 -0
- package/dist/vendor/node/NodeHttpIncomingMessage.js +10 -0
- package/dist/vendor/node/NodeHttpPlatform.d.ts +36 -0
- package/dist/vendor/node/NodeHttpPlatform.js +16 -0
- package/dist/vendor/node/NodeHttpServer.d.ts +120 -0
- package/dist/vendor/node/NodeHttpServer.js +46 -0
- package/dist/vendor/node/NodeHttpServerRequest.d.ts +29 -0
- package/dist/vendor/node/NodeHttpServerRequest.js +9 -0
- package/dist/vendor/node/NodeMultipart.d.ts +46 -0
- package/dist/vendor/node/NodeMultipart.js +15 -0
- package/dist/vendor/node/NodeMultipartParser.d.ts +97 -0
- package/dist/vendor/node/NodeMultipartParser.js +11 -0
- package/dist/vendor/node/NodePath.d.ts +27 -0
- package/dist/vendor/node/NodePath.js +12 -0
- package/dist/vendor/node/NodeRedis.d.ts +74 -0
- package/dist/vendor/node/NodeRedis.js +11 -0
- package/dist/vendor/node/NodeRuntime.d.ts +85 -0
- package/dist/vendor/node/NodeRuntime.js +8 -0
- package/dist/vendor/node/NodeServices.d.ts +34 -0
- package/dist/vendor/node/NodeServices.js +23 -0
- package/dist/vendor/node/NodeSink.d.ts +8 -0
- package/dist/vendor/node/NodeSink.js +12 -0
- package/dist/vendor/node/NodeSocket.d.ts +38 -0
- package/dist/vendor/node/NodeSocket.js +31 -0
- package/dist/vendor/node/NodeSocketServer.d.ts +8 -0
- package/dist/vendor/node/NodeSocketServer.js +21 -0
- package/dist/vendor/node/NodeStdio.d.ts +11 -0
- package/dist/vendor/node/NodeStdio.js +10 -0
- package/dist/vendor/node/NodeStream.d.ts +8 -0
- package/dist/vendor/node/NodeStream.js +27 -0
- package/dist/vendor/node/NodeTerminal.d.ts +21 -0
- package/dist/vendor/node/NodeTerminal.js +10 -0
- package/dist/vendor/node/NodeWorker.d.ts +22 -0
- package/dist/vendor/node/NodeWorker.js +9 -0
- package/dist/vendor/node/NodeWorkerRunner.d.ts +12 -0
- package/dist/vendor/node/NodeWorkerRunner.js +7 -0
- package/dist/vendor/node/Undici.d.ts +28 -0
- package/dist/vendor/node/Undici.js +9 -0
- package/dist/vendor/node/index.d.ts +100 -0
- package/dist/vendor/node/index.js +115 -0
- package/dist/vendor/node-shared/LICENSE +21 -0
- package/dist/vendor/node-shared/NodeChildProcessSpawner.d.ts +57 -0
- package/dist/vendor/node-shared/NodeChildProcessSpawner.js +13 -0
- package/dist/vendor/node-shared/NodeClusterSocket.d.ts +22 -0
- package/dist/vendor/node-shared/NodeClusterSocket.js +11 -0
- package/dist/vendor/node-shared/NodeCrypto.d.ts +27 -0
- package/dist/vendor/node-shared/NodeCrypto.js +9 -0
- package/dist/vendor/node-shared/NodeFileSystem.d.ts +11 -0
- package/dist/vendor/node-shared/NodeFileSystem.js +8 -0
- package/dist/vendor/node-shared/NodeHttpCompression.d.ts +36 -0
- package/dist/vendor/node-shared/NodeHttpCompression.js +13 -0
- package/dist/vendor/node-shared/NodePath.d.ts +27 -0
- package/dist/vendor/node-shared/NodePath.js +11 -0
- package/dist/vendor/node-shared/NodeRuntime.d.ts +47 -0
- package/dist/vendor/node-shared/NodeRuntime.js +7 -0
- package/dist/vendor/node-shared/NodeSink.d.ts +69 -0
- package/dist/vendor/node-shared/NodeSink.js +11 -0
- package/dist/vendor/node-shared/NodeSocket.d.ts +134 -0
- package/dist/vendor/node-shared/NodeSocket.js +23 -0
- package/dist/vendor/node-shared/NodeSocketServer.d.ts +80 -0
- package/dist/vendor/node-shared/NodeSocketServer.js +20 -0
- package/dist/vendor/node-shared/NodeStdio.d.ts +12 -0
- package/dist/vendor/node-shared/NodeStdio.js +9 -0
- package/dist/vendor/node-shared/NodeStream.d.ts +179 -0
- package/dist/vendor/node-shared/NodeStream.js +26 -0
- package/dist/vendor/node-shared/NodeTerminal.d.ts +22 -0
- package/dist/vendor/node-shared/NodeTerminal.js +9 -0
- package/dist/vendor/node-shared/index.d.ts +56 -0
- package/dist/vendor/node-shared/index.js +58 -0
- package/dist/vendor/node-shared/internal/nodeChildProcessSpawner.d.ts +4 -0
- package/dist/vendor/node-shared/internal/nodeChildProcessSpawner.js +7 -0
- package/dist/vendor/node-shared/internal/utils.d.ts +2 -0
- package/dist/vendor/node-shared/internal/utils.js +7 -0
- package/dist/vendor/platform-bun.d.ts +92 -0
- package/dist/vendor/platform-bun.js +107 -0
- package/dist/vendor/platform-node-shared.d.ts +56 -0
- package/dist/vendor/platform-node-shared.js +58 -0
- package/dist/vendor/platform-node.d.ts +100 -0
- package/dist/vendor/platform-node.js +115 -0
- package/docs/README.md +158 -0
- package/docs/api.md +601 -0
- package/docs/concepts/projections.md +250 -0
- package/docs/concepts/subscriptions.md +204 -0
- package/docs/concepts/trust-boundary.md +256 -0
- package/docs/guides/diagnose-a-run.md +115 -0
- package/docs/guides/follow-a-run.md +150 -0
- package/docs/guides/host-the-gateway.md +153 -0
- package/docs/guides/read-a-projection.md +138 -0
- package/docs/guides/serve-beyond-loopback.md +166 -0
- package/docs/guides/submit-an-approval.md +187 -0
- package/docs/guides/testing.md +148 -0
- package/docs/installation.md +89 -0
- package/docs/quickstart.md +200 -0
- package/docs/troubleshooting.md +355 -0
- package/package.json +264 -32
- package/src/Diagnosis.ts +892 -0
- package/src/EngineTrace.ts +627 -0
- package/src/GatewayError.ts +71 -0
- package/src/GatewayProjection.ts +959 -0
- package/src/GatewayRpcs.ts +179 -0
- package/src/GatewaySchema.ts +516 -0
- package/src/GatewayServer.ts +932 -0
- package/src/HostBranchPresence.ts +76 -0
- package/src/Projections.ts +1735 -0
- package/src/RunDevTools.ts +394 -0
- package/src/RunHistory.ts +174 -0
- package/src/RunTrace.ts +2796 -0
- package/src/RuntimeBridge.ts +835 -0
- package/src/bun/BunGateway.ts +52 -0
- package/src/index.ts +77 -0
- package/src/internal/NativeGateway.ts +360 -0
- package/src/internal/actionRenderings.ts +212 -0
- package/src/internal/callEvents.ts +257 -0
- package/src/internal/digestIndex.ts +134 -0
- package/src/internal/digestMemory.ts +27 -0
- package/src/internal/inspectLabels.ts +18 -0
- package/src/internal/nativeResolution.ts +103 -0
- package/src/internal/nodeEvents.ts +100 -0
- package/src/node/NodeGateway.ts +30 -0
- package/openapi.yaml +0 -29945
- package/src/README.md +0 -39
- package/src/api/README.md +0 -23
- package/src/api/apiCollectionNames.js +0 -10
- package/src/api/index.d.ts +0 -126
- package/src/api/index.js +0 -14
- package/src/api/normalizeApiRow.js +0 -36
- package/src/api/serializeAccountRow.js +0 -10
- package/src/api/serializeApprovalRow.js +0 -53
- package/src/api/serializeComparisonScoreRow.js +0 -18
- package/src/api/serializeCronRow.js +0 -30
- package/src/api/serializeDocRow.js +0 -20
- package/src/api/serializeMemoryFactRow.js +0 -21
- package/src/api/serializePromptRow.js +0 -10
- package/src/api/serializeRunEventRow.js +0 -28
- package/src/api/serializeRunRow.js +0 -93
- package/src/api/serializeScoreDetailRow.js +0 -25
- package/src/api/serializeScoreRow.js +0 -26
- package/src/api/serializeTicketRow.js +0 -20
- package/src/api/serializeWorkflowRow.js +0 -10
- package/src/auth/scopes.d.ts +0 -19
- package/src/auth/scopes.js +0 -164
- package/src/index.d.ts +0 -316
- package/src/index.js +0 -108
- package/src/rpc/gatewayRpcTypes.ts +0 -186
- package/src/rpc/index.d.ts +0 -223
- package/src/rpc/index.js +0 -2527
|
@@ -0,0 +1,2106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run trace model (factory spec 06, "the run trace": one card shows every
|
|
3
|
+
* run).
|
|
4
|
+
*
|
|
5
|
+
* A run is a recursive program: the agent writes REPL cells in a code
|
|
6
|
+
* container, every `ctx.call` is a durable span, and each turn is a frame. The
|
|
7
|
+
* control journal already records exactly that (`@smthrs/agent` AgentSession
|
|
8
|
+
* journals turn-opened, model-settled, cell-produced, cell-call-started,
|
|
9
|
+
* cell-call-settled, cell-printed, cell-settled, resolved), and the gateway
|
|
10
|
+
* serves it as the `run-events` projection the run card already reads. This
|
|
11
|
+
* module folds those records into a span tree, run → frame → cell → call, and
|
|
12
|
+
* the geometry of one bar per span on a shared time axis. No second data
|
|
13
|
+
* source: what the journal does not carry (realm variables, child runs' own
|
|
14
|
+
* frames) the model does not invent.
|
|
15
|
+
*
|
|
16
|
+
* Pure: the card renders the model, the tests read it from a fixture.
|
|
17
|
+
* @since 1.0.0
|
|
18
|
+
*/
|
|
19
|
+
import { CallFact } from "@smthrs/journal";
|
|
20
|
+
import { CallPresentation, FlowActivity } from "@smthrs/registry/Descriptor";
|
|
21
|
+
import { Schema } from "effect";
|
|
22
|
+
import { callScope, openCallIndex } from "./Diagnosis.js";
|
|
23
|
+
import { engineExecutionEvidence, engineTraceFromJournal } from "./EngineTrace.js";
|
|
24
|
+
import { ACTION_RENDERINGS } from "./internal/actionRenderings.js";
|
|
25
|
+
import { callEventFilter, nativeStepEvent, uniqueCallEvents } from "./internal/callEvents.js";
|
|
26
|
+
import { inspectLabel } from "./internal/inspectLabels.js";
|
|
27
|
+
export { inspectLabel } from "./internal/inspectLabels.js";
|
|
28
|
+
const TERMINAL_RUN = new Set(["completed", "failed", "cancelled", "no-capacity"]);
|
|
29
|
+
const asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
30
|
+
const asString = (value) => typeof value === "string" ? value : undefined;
|
|
31
|
+
const asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
32
|
+
/** The record's time: the agent's own `at` stamp when it carries one, the journal's otherwise (the gateway's rule). */
|
|
33
|
+
const timeOf = (record, payload) => asNumber(payload.at) ?? asNumber(record.occurredAt) ?? 0;
|
|
34
|
+
/** One line of text for a value the journal traced, or nothing. */
|
|
35
|
+
const textOf = (value) => {
|
|
36
|
+
if (value === undefined || value === null)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (typeof value === "string")
|
|
39
|
+
return value;
|
|
40
|
+
try {
|
|
41
|
+
return JSON.stringify(value);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return String(value);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/** The payload fields the pane does not already show by name. */
|
|
48
|
+
const restOf = (payload, shown) => {
|
|
49
|
+
const rest = {};
|
|
50
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
51
|
+
if (shown.includes(key) || key === "at" || key === "journalVersion")
|
|
52
|
+
continue;
|
|
53
|
+
rest[key] = value;
|
|
54
|
+
}
|
|
55
|
+
return Object.keys(rest).length === 0 ? undefined : rest;
|
|
56
|
+
};
|
|
57
|
+
const builder = (id, kind, label, status, startedAt, detail) => ({ id, kind, label, status, startedAt, children: [], detail });
|
|
58
|
+
/** Compatibility for journals whose descriptors predate presentation metadata. */
|
|
59
|
+
const legacy = (activity, pending, success, failure, subject, result) => ({
|
|
60
|
+
...(activity === undefined ? {} : { activity }),
|
|
61
|
+
presentation: { verb: { pending, success, failure }, subject, result }
|
|
62
|
+
});
|
|
63
|
+
const LEGACY_PRESENTATION = new Map([
|
|
64
|
+
// `bash` has no compatibility activity for the same reason `@smthrs/std`
|
|
65
|
+
// declares none: what a shell call does is in the command, which the rule
|
|
66
|
+
// below reads off the record.
|
|
67
|
+
["bash", legacy(undefined, "running", "ran", "failed to run", "command", "command")],
|
|
68
|
+
["test", legacy("tests", "running", "ran", "failed to run", "selection", "tests")],
|
|
69
|
+
["edit", legacy("writes", "editing", "edited", "failed to edit", "path", "edit")],
|
|
70
|
+
["write", legacy("writes", "writing", "wrote", "failed to write", "path", "write")],
|
|
71
|
+
["apply_patch", legacy("writes", "patching", "patched", "failed to patch", "patch", "patch")],
|
|
72
|
+
["read", legacy("reads", "reading", "read", "failed to read", "path", "read")],
|
|
73
|
+
["grep", legacy("reads", "searching", "searched", "failed to search", "pattern", "matches")],
|
|
74
|
+
["glob", legacy("reads", "listing", "listed", "failed to list", "pattern", "paths")],
|
|
75
|
+
["ls", legacy("reads", "listing", "listed", "failed to list", "path", "entries")]
|
|
76
|
+
]);
|
|
77
|
+
/** Read only the descriptor's validated display fields; a call input is never metadata. */
|
|
78
|
+
const recordedMetadata = (value, flowName) => {
|
|
79
|
+
const descriptor = asRecord(value);
|
|
80
|
+
if (descriptor.name !== flowName)
|
|
81
|
+
return undefined;
|
|
82
|
+
if (descriptor.activity === undefined && descriptor.presentation === undefined)
|
|
83
|
+
return undefined;
|
|
84
|
+
const activity = Schema.is(FlowActivity)(descriptor.activity) ? descriptor.activity : undefined;
|
|
85
|
+
const presentation = Schema.is(CallPresentation)(descriptor.presentation) ? descriptor.presentation : undefined;
|
|
86
|
+
return { activity, presentation };
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* The name a queued checkpoint mint carries (`QuickJSSandbox` `checkpointFlow`).
|
|
90
|
+
* A mint journals `control.agent.checkpoint-minted` and never a call, so no
|
|
91
|
+
* call in a journal wears this name today; the dominant-call rule skips it
|
|
92
|
+
* anyway, so a host that binds a flow by that name cannot make a frame's
|
|
93
|
+
* headline read as bookkeeping.
|
|
94
|
+
*/
|
|
95
|
+
const CHECKPOINT_FLOW = "checkpoint";
|
|
96
|
+
/** The command line a call ran, for the flows that run one. */
|
|
97
|
+
const commandOf = (input) => {
|
|
98
|
+
const fields = asRecord(input);
|
|
99
|
+
return asString(fields.command) ?? asString(fields.script);
|
|
100
|
+
};
|
|
101
|
+
/** The last segment of a path: the name a person calls the file. */
|
|
102
|
+
const basename = (path) => path.split("/").filter((part) => part !== "").pop() ?? path;
|
|
103
|
+
/**
|
|
104
|
+
* The files a V4A patch names (`@smthrs/std` ApplyPatch's `*** Add File: `,
|
|
105
|
+
* `*** Delete File: ` and `*** Update File: ` markers).
|
|
106
|
+
*
|
|
107
|
+
* `apply_patch` carries its entire patch in one `input` string, and the patch
|
|
108
|
+
* is not the subject: what it patches is. A string that is not a patch names no
|
|
109
|
+
* files and yields none, so a flow that happens to call its field `input` is
|
|
110
|
+
* not mistaken for one.
|
|
111
|
+
*/
|
|
112
|
+
const patchedPaths = (patch) => {
|
|
113
|
+
const paths = [];
|
|
114
|
+
for (const line of patch.split("\n")) {
|
|
115
|
+
const named = /^\*\*\* (?:Add|Delete|Update) File:\s*(\S.*)$/.exec(line.trim())?.[1];
|
|
116
|
+
if (named !== undefined)
|
|
117
|
+
paths.push(named.trim());
|
|
118
|
+
}
|
|
119
|
+
return paths;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Several files in the room one name has: the first named, the rest counted.
|
|
123
|
+
*
|
|
124
|
+
* A multi-file patch's line and a frame's write pin both say it through here,
|
|
125
|
+
* so `views.py +1` means one thing wherever the card prints it. The count is a
|
|
126
|
+
* fact the journal carried, never an estimate. No file, no words.
|
|
127
|
+
*/
|
|
128
|
+
const filesLabel = (paths) => {
|
|
129
|
+
const first = paths[0];
|
|
130
|
+
if (first === undefined)
|
|
131
|
+
return "";
|
|
132
|
+
return paths.length === 1 ? basename(first) : `${basename(first)} +${paths.length - 1}`;
|
|
133
|
+
};
|
|
134
|
+
/** The files an edit-like call names: its `path`, or every file its patch names. */
|
|
135
|
+
const writtenPaths = (input) => {
|
|
136
|
+
const fields = asRecord(input);
|
|
137
|
+
const path = asString(fields.path);
|
|
138
|
+
if (path !== undefined)
|
|
139
|
+
return [path];
|
|
140
|
+
const patch = asString(fields.input);
|
|
141
|
+
return patch === undefined ? [] : patchedPaths(patch);
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* What one recorded call was about, in the shape its declaration chose.
|
|
145
|
+
*
|
|
146
|
+
* Exported because the run header names the same subject as the row beneath
|
|
147
|
+
* it: two readings of one input is two answers to one question.
|
|
148
|
+
*
|
|
149
|
+
* @param input the call input, as journaled
|
|
150
|
+
* @param format the declared subject field, when the declaration named one
|
|
151
|
+
*
|
|
152
|
+
* @category utilities
|
|
153
|
+
* @since 1.0.0
|
|
154
|
+
*/
|
|
155
|
+
export const callSubject = (input, format) => {
|
|
156
|
+
const fields = asRecord(input);
|
|
157
|
+
if (format === "none")
|
|
158
|
+
return "";
|
|
159
|
+
if (format === "path")
|
|
160
|
+
return asString(fields.path) === undefined ? "" : basename(fields.path);
|
|
161
|
+
if (format === "command")
|
|
162
|
+
return commandOf(input) ?? "";
|
|
163
|
+
if (format === "patch")
|
|
164
|
+
return filesLabel(patchedPaths(asString(fields.input) ?? ""));
|
|
165
|
+
if (format === "pattern")
|
|
166
|
+
return asString(fields.pattern) ?? "";
|
|
167
|
+
if (format === "selection")
|
|
168
|
+
return stringList(fields.selection)?.join(" ") ?? "";
|
|
169
|
+
const path = asString(fields.path);
|
|
170
|
+
if (path !== undefined)
|
|
171
|
+
return basename(path);
|
|
172
|
+
const command = commandOf(input);
|
|
173
|
+
if (command !== undefined)
|
|
174
|
+
return command;
|
|
175
|
+
const patch = asString(fields.input);
|
|
176
|
+
if (patch !== undefined) {
|
|
177
|
+
// The patch is not the subject; the files it patches are.
|
|
178
|
+
const files = filesLabel(patchedPaths(patch));
|
|
179
|
+
if (files !== "")
|
|
180
|
+
return files;
|
|
181
|
+
}
|
|
182
|
+
// `test` names what it selected (`@smthrs/std` TestRun `Input.selection`).
|
|
183
|
+
// An omitted selection means the record named nothing; that the flow would
|
|
184
|
+
// then run everything is the flow's default, not a fact this record carries.
|
|
185
|
+
const selection = Array.isArray(fields.selection)
|
|
186
|
+
? fields.selection.filter((one) => typeof one === "string")
|
|
187
|
+
: undefined;
|
|
188
|
+
if (selection !== undefined && selection.length > 0)
|
|
189
|
+
return selection.join(" ");
|
|
190
|
+
return asString(fields.pattern) ?? "";
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Legacy shell journals have no structured activity. Recognize only direct
|
|
194
|
+
* invocations of known runners. Shell composition, expansion and quoted
|
|
195
|
+
* programs remain unknown. Arguments can narrow a check without changing
|
|
196
|
+
* its activity; no command here establishes a plan's required coverage.
|
|
197
|
+
*/
|
|
198
|
+
const shellActivity = (command) => {
|
|
199
|
+
if (/[;&|<>`$\n\r]/.test(command))
|
|
200
|
+
return "other";
|
|
201
|
+
const words = command.trim().split(/\s+/);
|
|
202
|
+
const first = words[0];
|
|
203
|
+
if (["pytest", "vitest", "jest"].includes(first))
|
|
204
|
+
return "tests";
|
|
205
|
+
if (/^python[23]?$/.test(first) && words[1] === "-m" && ["pytest", "unittest"].includes(words[2] ?? "")) {
|
|
206
|
+
return "tests";
|
|
207
|
+
}
|
|
208
|
+
if (first === "bun" && words[1] === "test")
|
|
209
|
+
return "tests";
|
|
210
|
+
if (["pnpm", "npm", "bun"].includes(first)) {
|
|
211
|
+
if (words[1] === "test" || (words[1] === "run" && words[2] === "test"))
|
|
212
|
+
return "tests";
|
|
213
|
+
if (words[1] === "exec" && ["vitest", "jest"].includes(words[2] ?? ""))
|
|
214
|
+
return "tests";
|
|
215
|
+
if (words[1] === "run" && ["check", "typecheck", "lint"].includes(words[2] ?? ""))
|
|
216
|
+
return "checks";
|
|
217
|
+
}
|
|
218
|
+
if (first === "go" && words[1] === "test")
|
|
219
|
+
return "tests";
|
|
220
|
+
if (first === "cargo" && words[1] === "test")
|
|
221
|
+
return "tests";
|
|
222
|
+
if (first === "tsc")
|
|
223
|
+
return "checks";
|
|
224
|
+
return "other";
|
|
225
|
+
};
|
|
226
|
+
/**
|
|
227
|
+
* The one reading of a recorded call's semantics, shared by the rows, the
|
|
228
|
+
* phase bands and the run header.
|
|
229
|
+
*
|
|
230
|
+
* Precedence, and why: the record's own `descriptor` wins, because it is what
|
|
231
|
+
* the declaration said when the call was made, and only a record that carries
|
|
232
|
+
* none falls back to {@link LEGACY_PRESENTATION}, which is a guess about the
|
|
233
|
+
* flows that were journaled before a declaration could say anything. A flow the
|
|
234
|
+
* table does not know stays unknown, and unknown is said as unknown.
|
|
235
|
+
*
|
|
236
|
+
* `bash` is the one activity read off the call rather than the declaration.
|
|
237
|
+
* `@smthrs/std` declares none for it on purpose — the same flow runs a test
|
|
238
|
+
* suite, a type check and `cat` — so a recognised runner in the recorded
|
|
239
|
+
* command is the only thing that names the activity, and an explicit
|
|
240
|
+
* declaration still outranks it.
|
|
241
|
+
*
|
|
242
|
+
* @param flowName the flow the record names
|
|
243
|
+
* @param payload the `cell-call-started` payload, as journaled
|
|
244
|
+
*
|
|
245
|
+
* @category utilities
|
|
246
|
+
* @since 1.0.0
|
|
247
|
+
*/
|
|
248
|
+
export const callSemantics = (flowName, payload) => {
|
|
249
|
+
const recorded = recordedMetadata(payload.descriptor, flowName);
|
|
250
|
+
const metadata = recorded ?? LEGACY_PRESENTATION.get(flowName);
|
|
251
|
+
const activity = metadata?.activity
|
|
252
|
+
?? (flowName === "bash" ? shellActivity(commandOf(payload.input) ?? "") : undefined);
|
|
253
|
+
return {
|
|
254
|
+
...(activity === undefined ? {} : { activity }),
|
|
255
|
+
...(metadata?.presentation === undefined ? {} : { presentation: metadata.presentation })
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
const stringList = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : undefined;
|
|
259
|
+
const countOf = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
260
|
+
const counted = (value, noun, plural = `${noun}s`) => `${value} ${value === 1 ? noun : plural}`;
|
|
261
|
+
/** Known output fields only. Unknown objects remain in the call's raw output. */
|
|
262
|
+
const resultOf = (value, format) => {
|
|
263
|
+
if (format === "none")
|
|
264
|
+
return "";
|
|
265
|
+
if (typeof value === "string")
|
|
266
|
+
return clip(value) ?? "";
|
|
267
|
+
const fields = asRecord(value);
|
|
268
|
+
if (fields.truncated === true && typeof fields.digest === "string")
|
|
269
|
+
return "";
|
|
270
|
+
switch (format) {
|
|
271
|
+
case "read": {
|
|
272
|
+
const start = countOf(fields.startLine);
|
|
273
|
+
const end = countOf(fields.endLine);
|
|
274
|
+
return start !== undefined && start > 0 && end !== undefined && end >= start - 1
|
|
275
|
+
? counted(end - start + 1, "line") :
|
|
276
|
+
"";
|
|
277
|
+
}
|
|
278
|
+
case "write":
|
|
279
|
+
return countOf(fields.bytesWritten) === undefined ? "" : counted(fields.bytesWritten, "byte");
|
|
280
|
+
case "edit":
|
|
281
|
+
return countOf(fields.replacements) === undefined ? "" : counted(fields.replacements, "replacement");
|
|
282
|
+
case "patch": {
|
|
283
|
+
const groups = [stringList(fields.added), stringList(fields.modified), stringList(fields.deleted)];
|
|
284
|
+
return groups.every((group) => group !== undefined) ? counted(new Set(groups.flat()).size, "file") : "";
|
|
285
|
+
}
|
|
286
|
+
case "tests": {
|
|
287
|
+
const passed = countOf(fields.passed);
|
|
288
|
+
const failed = stringList(fields.failed);
|
|
289
|
+
if (fields.parsed === true && passed !== undefined && failed !== undefined && fields.invalidProbe === undefined) {
|
|
290
|
+
const exit = countOf(fields.exitCode);
|
|
291
|
+
return `${passed} passed${failed.length > 0 ? ` · ${failed.length} failed` : exit !== undefined && exit !== 0 ? ` · exit ${exit}` : ""}`;
|
|
292
|
+
}
|
|
293
|
+
return countOf(fields.exitCode) === undefined ? "" : `exit ${fields.exitCode}`;
|
|
294
|
+
}
|
|
295
|
+
case "command":
|
|
296
|
+
return countOf(fields.exitCode) === undefined ? "" : `exit ${fields.exitCode}`;
|
|
297
|
+
case "matches":
|
|
298
|
+
return Array.isArray(fields.matches) ? counted(fields.matches.length, "match", "matches") : "";
|
|
299
|
+
case "paths":
|
|
300
|
+
return stringList(fields.paths) === undefined
|
|
301
|
+
? ""
|
|
302
|
+
: counted(fields.paths.length, "file");
|
|
303
|
+
case "entries":
|
|
304
|
+
return Array.isArray(fields.entries) ? counted(fields.entries.length, "entry", "entries") : "";
|
|
305
|
+
default:
|
|
306
|
+
return "";
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
/** A note body from the sentences the payload carried; a field the record omitted contributes nothing. */
|
|
310
|
+
const bodyOf = (...sentences) => sentences.filter((sentence) => sentence !== undefined).join(" ");
|
|
311
|
+
/** One quoted line of evidence, bounded the way the turn list bounds prose. */
|
|
312
|
+
const clip = (value) => {
|
|
313
|
+
const line = asString(value);
|
|
314
|
+
if (line === undefined || line === "")
|
|
315
|
+
return undefined;
|
|
316
|
+
return line.length <= 160 ? line : `${line.slice(0, 159).trimEnd()}…`;
|
|
317
|
+
};
|
|
318
|
+
/** Whether the frame made an edit-like call the journal recorded settling successfully. */
|
|
319
|
+
const madeAWrite = (entry) => entry.calls.some((call) => call.activity === "writes" && call.settled?.failed === false && !call.settled.denied);
|
|
320
|
+
/**
|
|
321
|
+
* The call a frame's line is about: a change it made, else the check it ran,
|
|
322
|
+
* else the first thing it asked for.
|
|
323
|
+
*
|
|
324
|
+
* The phase rules and the lines read the same answer, so the two can never
|
|
325
|
+
* disagree about whether the journal said anything about a frame at all.
|
|
326
|
+
*/
|
|
327
|
+
const dominantCall = (entry) => entry.calls.find((one) => one.activity === "writes")
|
|
328
|
+
?? entry.calls.find((one) => one.activity === "checks" || one.activity === "tests")
|
|
329
|
+
?? entry.calls.find((one) => one.flowName !== CHECKPOINT_FLOW);
|
|
330
|
+
const disciplineInit = (runId) => ({
|
|
331
|
+
runId,
|
|
332
|
+
frames: [],
|
|
333
|
+
notes: [],
|
|
334
|
+
milestones: [],
|
|
335
|
+
owners: [],
|
|
336
|
+
open: [],
|
|
337
|
+
frame: undefined,
|
|
338
|
+
lastAt: 0,
|
|
339
|
+
lineOf: new WeakMap()
|
|
340
|
+
});
|
|
341
|
+
/** One record of the discipline fold; the state is advanced in place. */
|
|
342
|
+
const disciplineStep = (state, record) => {
|
|
343
|
+
const { runId, frames, notes, milestones, owners, open } = state;
|
|
344
|
+
let frame = state.frame;
|
|
345
|
+
/** Where a discipline record attaches: the open frame, else the run itself. */
|
|
346
|
+
const here = () => frame?.id ?? `run:${runId}`;
|
|
347
|
+
const note = (seq, tone, title, body, evidence) => {
|
|
348
|
+
const quoted = evidence.filter((line) => line !== undefined);
|
|
349
|
+
notes.push({ seq, spanId: here(), tone, title, body, ...(quoted.length === 0 ? {} : { evidence: quoted }) });
|
|
350
|
+
};
|
|
351
|
+
/** A moment, owned by the frame that recorded it, exactly the way a note is. */
|
|
352
|
+
const pin = (seq, at, label, tone) => {
|
|
353
|
+
milestones.push({ seq, at, label, tone, spanId: here() });
|
|
354
|
+
};
|
|
355
|
+
{
|
|
356
|
+
const kind = record.kind ?? "";
|
|
357
|
+
const payload = asRecord(record.payload);
|
|
358
|
+
const at = timeOf(record, payload);
|
|
359
|
+
const seq = record.sequence ?? 0;
|
|
360
|
+
state.lastAt = Math.max(state.lastAt, at);
|
|
361
|
+
if (frame !== undefined)
|
|
362
|
+
frame.endedAt = Math.max(frame.endedAt, at);
|
|
363
|
+
switch (kind) {
|
|
364
|
+
case "control.agent.turn-opened": {
|
|
365
|
+
frame = {
|
|
366
|
+
id: `frame-${frames.length + 1}`,
|
|
367
|
+
frame: frames.length + 1,
|
|
368
|
+
seq,
|
|
369
|
+
startedAt: at,
|
|
370
|
+
endedAt: at,
|
|
371
|
+
blocked: false,
|
|
372
|
+
calls: []
|
|
373
|
+
};
|
|
374
|
+
frames.push(frame);
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
case "control.agent.cell-call-started": {
|
|
378
|
+
const flowName = asString(payload.flowName) ?? "";
|
|
379
|
+
const metadata = callSemantics(flowName, payload);
|
|
380
|
+
const call = {
|
|
381
|
+
flowName,
|
|
382
|
+
callId: asString(payload.callId),
|
|
383
|
+
input: payload.input,
|
|
384
|
+
activity: metadata.activity,
|
|
385
|
+
presentation: metadata.presentation,
|
|
386
|
+
signature: `${flowName} ${textOf(payload.input) ?? ""}`
|
|
387
|
+
};
|
|
388
|
+
open.push(call);
|
|
389
|
+
// A call journaled outside a frame belongs to no frame, and the tree
|
|
390
|
+
// above puts it on the run. It is still open work, so it still pairs.
|
|
391
|
+
frame?.calls.push(call);
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
case "control.agent.cell-call-settled": {
|
|
395
|
+
if (payload.outcome !== "success" && payload.outcome !== "failure")
|
|
396
|
+
break;
|
|
397
|
+
const index = openCallIndex(open, asString(payload.callId), asString(payload.flowName));
|
|
398
|
+
const call = index < 0 ? undefined : open.splice(index, 1)[0];
|
|
399
|
+
if (call === undefined)
|
|
400
|
+
break;
|
|
401
|
+
const failed = asString(payload.outcome) === "failure";
|
|
402
|
+
// `ask` settles SUCCESSFULLY with the person's answer (`AgentSession`
|
|
403
|
+
// answers `{ answer, approved }`), so a refusal is a denial and not a
|
|
404
|
+
// failure. That is the call that settles with a denial.
|
|
405
|
+
const denied = !failed && asRecord(payload.value).approved === false;
|
|
406
|
+
call.settled = {
|
|
407
|
+
failed,
|
|
408
|
+
result: (failed ? textOf(payload.message) : textOf(payload.value)) ?? "",
|
|
409
|
+
denied,
|
|
410
|
+
value: payload.value
|
|
411
|
+
};
|
|
412
|
+
if (denied && frame !== undefined)
|
|
413
|
+
frame.blocked = true;
|
|
414
|
+
// A write is the fastest thing a run does, so its band is always too
|
|
415
|
+
// narrow to carry a label, and its own marker is the only way to reach
|
|
416
|
+
// the moment that matters most.
|
|
417
|
+
if (!failed && !denied && call.activity === "writes") {
|
|
418
|
+
// A pin is its label, so a pin with none is a marker nobody can read
|
|
419
|
+
// and nobody can tell from the next one. The journal gave this call
|
|
420
|
+
// no file; no file, no pin.
|
|
421
|
+
const paths = [...new Set(writtenPaths(call.input))];
|
|
422
|
+
const minted = frame?.wrote;
|
|
423
|
+
if (minted !== undefined) {
|
|
424
|
+
// A frame's writes are ONE moment. A frame that edits fifteen files
|
|
425
|
+
// does it inside a second, so a pin per file is fifteen names
|
|
426
|
+
// stacked over a point on the axis nobody can aim between. The pin
|
|
427
|
+
// stays where the frame started writing, on the file it names, and
|
|
428
|
+
// counts the rest; a file written twice is still one file.
|
|
429
|
+
for (const path of paths)
|
|
430
|
+
if (!minted.paths.includes(path))
|
|
431
|
+
minted.paths.push(path);
|
|
432
|
+
milestones[minted.milestone] = { ...milestones[minted.milestone], label: filesLabel(minted.paths) };
|
|
433
|
+
}
|
|
434
|
+
else if (paths.length > 0) {
|
|
435
|
+
// A write journaled outside a frame has no frame to be counted
|
|
436
|
+
// into, so it stays a moment of its own.
|
|
437
|
+
if (frame !== undefined)
|
|
438
|
+
frame.wrote = { milestone: milestones.length, paths };
|
|
439
|
+
pin(seq, at, filesLabel(paths), "brand");
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
case "control.agent.mutation-observed": {
|
|
445
|
+
const mutated = payload.mutated === true;
|
|
446
|
+
if (frame !== undefined)
|
|
447
|
+
frame.mutated = mutated;
|
|
448
|
+
if (frame !== undefined && payload.basis === "observed" && typeof payload.mutated === "boolean") {
|
|
449
|
+
frame.observedMutation = payload.mutated;
|
|
450
|
+
}
|
|
451
|
+
// Written for every frame, so only a frame that changed something is
|
|
452
|
+
// worth a note. `basis` is the body because a `declared` answer is
|
|
453
|
+
// paperwork and an `observed` one is a fact about the tree, and a
|
|
454
|
+
// reader must not have to guess which one they are holding. `paths` is
|
|
455
|
+
// a COUNT of what the measurement covered, not the paths themselves,
|
|
456
|
+
// so it is not quotable evidence and is not quoted.
|
|
457
|
+
if (mutated) {
|
|
458
|
+
note(seq, asString(payload.basis) === "observed" ? "good" : "warn", "changed", asString(payload.basis) ?? "", []);
|
|
459
|
+
}
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
case "control.agent.permission-required": {
|
|
463
|
+
if (frame !== undefined)
|
|
464
|
+
frame.blocked = true;
|
|
465
|
+
pin(seq, at, "permission", "warn");
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
case "control.agent.suspended": {
|
|
469
|
+
if (frame !== undefined)
|
|
470
|
+
frame.blocked = true;
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
case "control.agent.checkpoint-minted": {
|
|
474
|
+
note(seq, "good", "checkpoint", asString(payload.ref) ?? "", []);
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
case "control.agent.read-only-demanded": {
|
|
478
|
+
// The caps are this run's own armed numbers, read off the payload: a
|
|
479
|
+
// host overrides any of them, so a constant here would print a number
|
|
480
|
+
// the run never used.
|
|
481
|
+
const streak = asNumber(payload.streak);
|
|
482
|
+
const cap = asNumber(payload.cap);
|
|
483
|
+
const nextFrame = asNumber(payload.nextFrame);
|
|
484
|
+
const nextAction = asString(payload.nextAction);
|
|
485
|
+
note(seq, "warn", "read-only", bodyOf(streak === undefined || cap === undefined ? undefined : `${streak} of ${cap} frames changed nothing.`, nextFrame === undefined || nextAction === undefined ? undefined : `Frame ${nextFrame}: ${nextAction}.`), []);
|
|
486
|
+
pin(seq, at, "read-only", "warn");
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
case "control.agent.repeat-demanded": {
|
|
490
|
+
const spent = asNumber(payload.frames);
|
|
491
|
+
const cap = asNumber(payload.cap);
|
|
492
|
+
const nextFrame = asNumber(payload.nextFrame);
|
|
493
|
+
note(seq, "warn", "repeat", bodyOf(spent === undefined || cap === undefined ? undefined : `${spent} of ${cap} frames repeated calls.`, nextFrame === undefined ? undefined : `Frame ${nextFrame}.`), []);
|
|
494
|
+
pin(seq, at, "repeat", "warn");
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
case "control.agent.narrowed-demanded": {
|
|
498
|
+
const flow = asString(payload.flow);
|
|
499
|
+
const nextFrame = asNumber(payload.nextFrame);
|
|
500
|
+
note(seq, "bad", "narrowed", bodyOf(flow === undefined ? undefined : `${flow} ran narrower than the reading it stands in for.`, nextFrame === undefined ? undefined : `Frame ${nextFrame}.`), [clip(payload.broader), clip(payload.narrower)]);
|
|
501
|
+
pin(seq, at, "narrowed", "bad");
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
case "control.agent.unmoved-demanded": {
|
|
505
|
+
const nextFrame = asNumber(payload.nextFrame);
|
|
506
|
+
// Both digests or neither: one alone cannot tell an unmoved tree from a
|
|
507
|
+
// measurement that never happened, and either may be the empty string.
|
|
508
|
+
const opened = clip(payload.openedDigest);
|
|
509
|
+
const current = clip(payload.currentDigest);
|
|
510
|
+
note(seq, "bad", "unmoved", bodyOf("The tree the run opened on is the tree it closed on.", nextFrame === undefined ? undefined : `Frame ${nextFrame}.`), opened === undefined || current === undefined ? [] : [opened, current]);
|
|
511
|
+
pin(seq, at, "unmoved", "bad");
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
case "control.agent.unresolved-demanded": {
|
|
515
|
+
const flow = asString(payload.flow);
|
|
516
|
+
const nextFrame = asNumber(payload.nextFrame);
|
|
517
|
+
note(seq, "bad", "unresolved", bodyOf(flow === undefined ? undefined : `${flow} failed and was not answered.`, nextFrame === undefined ? undefined : `Frame ${nextFrame}.`), [clip(payload.failed), clip(payload.instead)]);
|
|
518
|
+
pin(seq, at, "unresolved", "bad");
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
case "control.agent.claim-demanded": {
|
|
522
|
+
// Written on EVERY evaluation, and a reading that neither handed the
|
|
523
|
+
// completion back nor refused it cost the run nothing and changed
|
|
524
|
+
// nothing, so only those two are moments anyone would scrub to. A
|
|
525
|
+
// refusal is the larger of them: it ends the run and takes its answer.
|
|
526
|
+
if (payload.demanded !== true && payload.refused !== true)
|
|
527
|
+
break;
|
|
528
|
+
const complete = asNumber(payload.complete);
|
|
529
|
+
const overclaims = asNumber(payload.overclaims);
|
|
530
|
+
const nextFrame = asNumber(payload.nextFrame);
|
|
531
|
+
note(seq, "bad", payload.refused === true ? "claim refused" : "claim", bodyOf(complete === undefined || overclaims === undefined
|
|
532
|
+
? undefined
|
|
533
|
+
: `complete ${complete}, overclaims ${overclaims}.`, nextFrame === undefined ? undefined : `Frame ${nextFrame}.`), []);
|
|
534
|
+
pin(seq, at, payload.refused === true ? "claim refused" : "claim", "bad");
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
/*
|
|
538
|
+
* The five kinds `AgentSession`'s `default` arm once journaled with
|
|
539
|
+
* `payload: {}`, however rich their `AgentEvent` schema was:
|
|
540
|
+
* cell-rejected-in-frame, read-only-demand-issued,
|
|
541
|
+
* narrow-only-demanded, steering-drained and sufficiency-observed.
|
|
542
|
+
* Their whole payload is late (`AgentSession` `lateFields`), so one
|
|
543
|
+
* record carries every field or none, and each arm below reads what its
|
|
544
|
+
* record has: a legacy record still folds, and still says nothing the
|
|
545
|
+
* record does not carry. A note with no body and no evidence is a title
|
|
546
|
+
* on its own, so a fieldless record writes none.
|
|
547
|
+
*/
|
|
548
|
+
case "control.agent.cell-rejected-in-frame": {
|
|
549
|
+
// A refused reply is a model call the frame paid for. The code is why
|
|
550
|
+
// the parse refused it; the message is what the frame said back.
|
|
551
|
+
const attempt = asNumber(payload.attempt);
|
|
552
|
+
const code = asString(payload.code);
|
|
553
|
+
const body = bodyOf(code === undefined ? undefined : `${code}.`, attempt === undefined ? undefined : `Attempt ${attempt}.`);
|
|
554
|
+
if (body !== "")
|
|
555
|
+
note(seq, "warn", "rejected", body, [clip(payload.message)]);
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
case "control.agent.read-only-demand-issued": {
|
|
559
|
+
// The issuance's own numbers. `read-only-demanded` above carries the
|
|
560
|
+
// same demand's later answer, and a run that crashes between the two
|
|
561
|
+
// boundaries still has this one.
|
|
562
|
+
const streak = asNumber(payload.streak);
|
|
563
|
+
const cap = asNumber(payload.cap);
|
|
564
|
+
const nextFrame = asNumber(payload.nextFrame);
|
|
565
|
+
const body = bodyOf(streak === undefined || cap === undefined ? undefined : `${streak} of ${cap} frames changed nothing.`, nextFrame === undefined ? undefined : `Frame ${nextFrame}.`);
|
|
566
|
+
if (body !== "")
|
|
567
|
+
note(seq, "warn", "read-only", body, []);
|
|
568
|
+
pin(seq, at, "read-only", "warn");
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
case "control.agent.narrow-only-demanded": {
|
|
572
|
+
// The sibling of `narrowed-demanded`: that one fires when a broader
|
|
573
|
+
// reading exists and was not re-run, this one when none was ever
|
|
574
|
+
// taken. There is no broader input to quote, so the subjects the
|
|
575
|
+
// demand is about stand in its place.
|
|
576
|
+
const flow = asString(payload.flow);
|
|
577
|
+
const targets = Array.isArray(payload.targets)
|
|
578
|
+
? payload.targets.filter((one) => typeof one === "string")
|
|
579
|
+
: [];
|
|
580
|
+
const nextFrame = asNumber(payload.nextFrame);
|
|
581
|
+
const body = bodyOf(flow === undefined || targets.length === 0
|
|
582
|
+
? undefined
|
|
583
|
+
: `${flow} ran on ${targets.join(", ")} and nothing broader.`, nextFrame === undefined ? undefined : `Frame ${nextFrame}.`);
|
|
584
|
+
if (body !== "")
|
|
585
|
+
note(seq, "warn", "narrow-only", body, [clip(payload.check)]);
|
|
586
|
+
pin(seq, at, "narrow-only", "warn");
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
case "control.agent.steering-drained": {
|
|
590
|
+
// A drain is written at every frame boundary, and the queue is empty
|
|
591
|
+
// at almost all of them. Only messages ON the record establish that a
|
|
592
|
+
// person reached the run, so an empty drain is not a moment and a
|
|
593
|
+
// legacy record — which carries no messages either way — cannot be
|
|
594
|
+
// read as one. A steer too large to trace was still delivered; the
|
|
595
|
+
// record says so even where its words are gone.
|
|
596
|
+
const messages = Array.isArray(payload.messages) ? payload.messages : [];
|
|
597
|
+
if (messages.length === 0)
|
|
598
|
+
break;
|
|
599
|
+
note(seq, "warn", "steering", messages.length === 1 ? "1 steer." : `${messages.length} steers.`, messages.map((message) => clip(asRecord(message).text)));
|
|
600
|
+
pin(seq, at, "steering", "warn");
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
case "control.agent.sufficiency-observed": {
|
|
604
|
+
// The one observation that rewards a run rather than braking it: a
|
|
605
|
+
// failing check answered by a passing one. The two inputs are the
|
|
606
|
+
// whole of it, so they are quoted.
|
|
607
|
+
const flow = asString(payload.flow);
|
|
608
|
+
const nextFrame = asNumber(payload.nextFrame);
|
|
609
|
+
const body = bodyOf(flow === undefined ? undefined : `${flow} failed before the change and passed after it.`, nextFrame === undefined ? undefined : `Frame ${nextFrame}.`);
|
|
610
|
+
if (body !== "")
|
|
611
|
+
note(seq, "good", "sufficiency", body, [clip(payload.failed), clip(payload.passed)]);
|
|
612
|
+
pin(seq, at, "sufficiency", "good");
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
case "control.run.completed":
|
|
616
|
+
case "control.run.failed":
|
|
617
|
+
case "control.run.cancelled": {
|
|
618
|
+
const verdict = kind.slice("control.run.".length);
|
|
619
|
+
// A person's stop reads as the product word every surface uses, and
|
|
620
|
+
// is never drawn as a failure.
|
|
621
|
+
if (verdict === "cancelled")
|
|
622
|
+
pin(seq, at, "stopped", "muted");
|
|
623
|
+
else
|
|
624
|
+
pin(seq, at, verdict, verdict === "completed" ? "good" : "bad");
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
default:
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
// After the record's own arm, so a turn-opened belongs to the frame it
|
|
631
|
+
// opened: the rule `here()` already gives a note and a pin.
|
|
632
|
+
owners.push({ seq, spanId: here() });
|
|
633
|
+
}
|
|
634
|
+
state.frame = frame;
|
|
635
|
+
};
|
|
636
|
+
/**
|
|
637
|
+
* What {@link disciplineStep} has read so far, as the model's arrays.
|
|
638
|
+
*
|
|
639
|
+
* The derived rows are rebuilt from the frames; the recorded ones are copied,
|
|
640
|
+
* so a model taken now never sees a record stepped after it.
|
|
641
|
+
*/
|
|
642
|
+
const disciplineModel = (state) => {
|
|
643
|
+
const { frames, lastAt } = state;
|
|
644
|
+
const notes = state.notes.slice();
|
|
645
|
+
const milestones = state.milestones.slice();
|
|
646
|
+
const owners = state.owners.slice();
|
|
647
|
+
// A measured unchanged tree is authoritative even when a write succeeded.
|
|
648
|
+
const changed = frames.map((entry) => entry.observedMutation ?? (entry.mutated === true || madeAWrite(entry)));
|
|
649
|
+
// A repeated call is only evidence of a stall inside a STREAK, and only
|
|
650
|
+
// across a span the workspace never moved in.
|
|
651
|
+
//
|
|
652
|
+
// Re-running one check after an edit is the OPPOSITE of a stall: it is the
|
|
653
|
+
// answer `control.agent.sufficiency-observed` rewards. A rule that flagged
|
|
654
|
+
// any second occurrence of a call would mark that good frame and a looping
|
|
655
|
+
// one identically, and a rule that only checked THIS frame for a change
|
|
656
|
+
// would still flag it, because the edit it answers happened in the frame
|
|
657
|
+
// before. So a change resets what a first occurrence is: a call is judged
|
|
658
|
+
// against the last time it ran on THIS tree, never against a run from before
|
|
659
|
+
// an edit landed. On top of that a repeat counts only where the frames beside
|
|
660
|
+
// it repeat too — each of them recording no change of its own — and
|
|
661
|
+
// `repeatOf` is set only inside a streak of two or more such frames. Every
|
|
662
|
+
// substantive call must repeat a settled reading; a fresh call, new result,
|
|
663
|
+
// or pending result means this frame has not been shown to be redundant.
|
|
664
|
+
const firstSeen = new Map();
|
|
665
|
+
const repeatedFrom = new Map();
|
|
666
|
+
const redundant = [];
|
|
667
|
+
frames.forEach((entry, index) => {
|
|
668
|
+
const calls = entry.calls.filter((call) => call.flowName !== CHECKPOINT_FLOW);
|
|
669
|
+
for (const call of calls) {
|
|
670
|
+
if (call.settled === undefined)
|
|
671
|
+
continue;
|
|
672
|
+
// A call settles once, so its reading is built once, not per model taken.
|
|
673
|
+
call.reading ??= `${call.signature} ${JSON.stringify(call.settled)}`;
|
|
674
|
+
const reading = call.reading;
|
|
675
|
+
const seen = firstSeen.get(reading);
|
|
676
|
+
if (seen === undefined)
|
|
677
|
+
firstSeen.set(reading, entry.frame);
|
|
678
|
+
else if (seen < entry.frame)
|
|
679
|
+
repeatedFrom.set(call, seen);
|
|
680
|
+
}
|
|
681
|
+
redundant.push(calls.length > 0 && calls.every((call) => repeatedFrom.has(call)));
|
|
682
|
+
// The tree moved in this frame, so every call recorded before it ran
|
|
683
|
+
// against a tree that no longer exists and none of them can be repeated.
|
|
684
|
+
if (changed[index] === true)
|
|
685
|
+
firstSeen.clear();
|
|
686
|
+
});
|
|
687
|
+
const stalling = frames.map((_frame, index) => redundant[index] === true && changed[index] !== true);
|
|
688
|
+
const stuck = stalling.map((flag, index) => flag && (stalling[index - 1] === true || stalling[index + 1] === true));
|
|
689
|
+
// Phase, most specific rule first. The last rule is the honesty one: every
|
|
690
|
+
// other word is a claim about what the frame was doing, and `researching` is
|
|
691
|
+
// as much of a claim as the rest. A frame this fold cannot name one call for
|
|
692
|
+
// is a frame it declines to write a line about, so it declines to name a
|
|
693
|
+
// phase for it too.
|
|
694
|
+
const phases = frames.map((entry, index) => entry.blocked
|
|
695
|
+
? "blocked"
|
|
696
|
+
: stuck[index] === true
|
|
697
|
+
? "stuck"
|
|
698
|
+
: changed[index] === true || entry.calls.some((call) => call.activity === "writes")
|
|
699
|
+
? "implementing"
|
|
700
|
+
: entry.calls.some((call) => call.activity === "checks" || call.activity === "tests")
|
|
701
|
+
? "testing"
|
|
702
|
+
: entry.calls.some((call) => call.activity === "reads")
|
|
703
|
+
? "researching"
|
|
704
|
+
: "unrecorded");
|
|
705
|
+
const bands = [];
|
|
706
|
+
frames.forEach((entry, index) => {
|
|
707
|
+
const phase = phases[index];
|
|
708
|
+
// A frame's band ends where the next frame opens, and the last one ends
|
|
709
|
+
// where the journal does: the rule the tree's own frames close by.
|
|
710
|
+
const endedAt = frames[index + 1]?.startedAt ?? Math.max(entry.endedAt, lastAt);
|
|
711
|
+
const last = bands[bands.length - 1];
|
|
712
|
+
if (last !== undefined && last.phase === phase) {
|
|
713
|
+
bands[bands.length - 1] = { ...last, endedAt, frames: [...last.frames, entry.id] };
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
bands.push({ phase, startedAt: entry.startedAt, endedAt, frames: [entry.id], seq: entry.seq });
|
|
717
|
+
});
|
|
718
|
+
const lines = frames.flatMap((entry, index) => {
|
|
719
|
+
const call = dominantCall(entry);
|
|
720
|
+
// A frame that called nothing has no line. Absence is absence.
|
|
721
|
+
if (call === undefined)
|
|
722
|
+
return [];
|
|
723
|
+
const repeatOf = stuck[index] === true ? repeatedFrom.get(call) : undefined;
|
|
724
|
+
const held = state.lineOf.get(entry);
|
|
725
|
+
if (held !== undefined && held.call === call && held.settled === call.settled && held.repeatOf === repeatOf) {
|
|
726
|
+
return [held.line];
|
|
727
|
+
}
|
|
728
|
+
const outcome = call.settled === undefined
|
|
729
|
+
? "pending"
|
|
730
|
+
: call.settled.failed || call.settled.denied
|
|
731
|
+
? "failure"
|
|
732
|
+
: "success";
|
|
733
|
+
const line = {
|
|
734
|
+
spanId: entry.id,
|
|
735
|
+
frame: entry.frame,
|
|
736
|
+
verb: call.presentation?.verb[outcome] ??
|
|
737
|
+
`${call.flowName}${outcome === "pending" ? " pending" : outcome === "failure" ? " failed" : ""}`,
|
|
738
|
+
subject: callSubject(call.input, call.presentation?.subject),
|
|
739
|
+
// Read supported output fields through the descriptor's presentation.
|
|
740
|
+
// The selected call retains the raw value, including unsupported shapes.
|
|
741
|
+
result: call.settled?.failed === true
|
|
742
|
+
? clip(call.settled.result) ?? ""
|
|
743
|
+
: resultOf(call.settled?.value, call.presentation?.result),
|
|
744
|
+
failed: call.settled?.failed === true,
|
|
745
|
+
wrote: call.activity === "writes" && call.settled?.failed === false && !call.settled.denied,
|
|
746
|
+
...(repeatOf === undefined ? {} : { repeatOf })
|
|
747
|
+
};
|
|
748
|
+
state.lineOf.set(entry, { call, settled: call.settled, repeatOf, line });
|
|
749
|
+
return [line];
|
|
750
|
+
});
|
|
751
|
+
return { bands, milestones, lines, notes, owners };
|
|
752
|
+
};
|
|
753
|
+
const isEngineRow = (record) => record.kind === "control.engine.event" || record.kind === "control.engine.projection-gap";
|
|
754
|
+
const foldInit = (run) => ({
|
|
755
|
+
run,
|
|
756
|
+
root: builder(`run:${run.runId}`, "run", `run ${run.runId} · ${run.flowId}`, "running", 0, {
|
|
757
|
+
...(run.kind === undefined ? {} : { fields: { kind: run.kind } })
|
|
758
|
+
}),
|
|
759
|
+
frame: undefined,
|
|
760
|
+
cell: undefined,
|
|
761
|
+
frames: 0,
|
|
762
|
+
calls: 0,
|
|
763
|
+
seat: undefined,
|
|
764
|
+
lastAt: 0,
|
|
765
|
+
count: 0,
|
|
766
|
+
verdict: undefined,
|
|
767
|
+
openCalls: [],
|
|
768
|
+
approvals: new Map(),
|
|
769
|
+
discipline: disciplineInit(run.runId),
|
|
770
|
+
engine: [],
|
|
771
|
+
engineTrace: undefined,
|
|
772
|
+
frozen: new WeakMap(),
|
|
773
|
+
dirty: new Set(),
|
|
774
|
+
topOf: new WeakMap()
|
|
775
|
+
});
|
|
776
|
+
/**
|
|
777
|
+
* Folds one record, in sequence order, into a dispatch's trace.
|
|
778
|
+
*
|
|
779
|
+
* Frames open on `turn-opened` and close when the next opens or the run ends;
|
|
780
|
+
* cells open on `cell-produced` and close on `cell-settled`; calls open on
|
|
781
|
+
* `cell-call-started` under the open cell (or the frame, when a call is
|
|
782
|
+
* journaled outside a cell) and settle by stable call identity the way the gateway's own
|
|
783
|
+
* `run-tree` pairs them, so a call's `call-N` id is the node id the
|
|
784
|
+
* `node-output` projection knows it by. A journal with no records yields the
|
|
785
|
+
* run root alone, wearing the run's status: the honest empty trace.
|
|
786
|
+
*
|
|
787
|
+
* The same records fold a second time, into what a person would say the run
|
|
788
|
+
* was doing: see {@link disciplineStep}.
|
|
789
|
+
*/
|
|
790
|
+
const foldStep = (state, record) => {
|
|
791
|
+
if (isEngineRow(record)) {
|
|
792
|
+
state.engine.push(record);
|
|
793
|
+
state.engineTrace = undefined;
|
|
794
|
+
}
|
|
795
|
+
// route receives only the records kept by the shared call-event filter.
|
|
796
|
+
const { root, openCalls, approvals, dirty, topOf } = state;
|
|
797
|
+
let { frame, cell, frames, calls, seat } = state;
|
|
798
|
+
const top = frame ?? cell;
|
|
799
|
+
if (top !== undefined)
|
|
800
|
+
dirty.add(top);
|
|
801
|
+
/** Marks the run's child that `span` sits under as changed. */
|
|
802
|
+
const touch = (span) => {
|
|
803
|
+
// Both callers retrieve spans registered in topOf when they were opened.
|
|
804
|
+
dirty.add(topOf.get(span));
|
|
805
|
+
};
|
|
806
|
+
/** Where a new span attaches: the open cell, else the open frame, else the run. */
|
|
807
|
+
const parent = () => cell ?? frame ?? root;
|
|
808
|
+
const closeCell = (at, status) => {
|
|
809
|
+
if (cell === undefined)
|
|
810
|
+
return;
|
|
811
|
+
cell.endedAt = at;
|
|
812
|
+
cell.status = status;
|
|
813
|
+
cell = undefined;
|
|
814
|
+
};
|
|
815
|
+
const closeFrame = (at) => {
|
|
816
|
+
if (frame === undefined)
|
|
817
|
+
return;
|
|
818
|
+
closeCell(at, cell?.children.some((child) => child.status === "failed") === true ? "failed" : "completed");
|
|
819
|
+
frame.endedAt = at;
|
|
820
|
+
frame.status = frame.children.some((child) => child.status === "failed") ? "failed" : "completed";
|
|
821
|
+
frame = undefined;
|
|
822
|
+
};
|
|
823
|
+
{
|
|
824
|
+
const kind = record.kind ?? "";
|
|
825
|
+
const payload = asRecord(record.payload);
|
|
826
|
+
const at = timeOf(record, payload);
|
|
827
|
+
if (state.count === 0) {
|
|
828
|
+
root.startedAt = at;
|
|
829
|
+
state.lastAt = at;
|
|
830
|
+
}
|
|
831
|
+
else
|
|
832
|
+
state.lastAt = Math.max(state.lastAt, at);
|
|
833
|
+
state.count += 1;
|
|
834
|
+
const opened = { sequence: record.sequence, event: kind };
|
|
835
|
+
switch (kind) {
|
|
836
|
+
case "control.engine.event":
|
|
837
|
+
case "control.engine.projection-gap":
|
|
838
|
+
case "control.engine.projection-started":
|
|
839
|
+
case "control.engine.projection-settled":
|
|
840
|
+
// Native records have their own identities and lifecycle. Never put
|
|
841
|
+
// them under whichever agent frame happened to be open at ingestion.
|
|
842
|
+
break;
|
|
843
|
+
case "control.agent.turn-opened": {
|
|
844
|
+
closeFrame(at);
|
|
845
|
+
frames += 1;
|
|
846
|
+
seat = asString(payload.seat) ?? seat;
|
|
847
|
+
frame = builder(`frame-${frames}`, "frame", `frame ${frames}${seat === undefined ? "" : ` · ${seat}`}`, "running", at, {
|
|
848
|
+
...opened,
|
|
849
|
+
...(seat === undefined ? {} : { seat }),
|
|
850
|
+
fields: restOf(payload, ["seat"])
|
|
851
|
+
});
|
|
852
|
+
root.children.push(frame);
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
855
|
+
case "control.agent.model-settled": {
|
|
856
|
+
const usage = asRecord(payload.usage);
|
|
857
|
+
const duration = asNumber(payload.durationMillis);
|
|
858
|
+
const span = builder(`model-${record.sequence ?? at}`, "model", "model", "completed", duration === undefined ? at : at - duration, {
|
|
859
|
+
...opened,
|
|
860
|
+
...(seat === undefined ? {} : { seat }),
|
|
861
|
+
output: textOf(payload.text),
|
|
862
|
+
usage: { inputTokens: asNumber(usage.inputTokens), outputTokens: asNumber(usage.outputTokens) },
|
|
863
|
+
fields: restOf(payload, ["text", "usage", "durationMillis"])
|
|
864
|
+
});
|
|
865
|
+
span.endedAt = at;
|
|
866
|
+
(frame ?? root).children.push(span);
|
|
867
|
+
break;
|
|
868
|
+
}
|
|
869
|
+
case "control.agent.cell-produced": {
|
|
870
|
+
closeCell(at, "completed");
|
|
871
|
+
const language = asString(payload.language);
|
|
872
|
+
cell = builder(`cell-${record.sequence ?? at}`, "cell", `cell${language === undefined ? "" : ` · ${language}`}`, "running", at, {
|
|
873
|
+
...opened,
|
|
874
|
+
source: asString(payload.text),
|
|
875
|
+
fields: restOf(payload, ["text", "language"])
|
|
876
|
+
});
|
|
877
|
+
(frame ?? root).children.push(cell);
|
|
878
|
+
break;
|
|
879
|
+
}
|
|
880
|
+
case "control.agent.cell-call-started": {
|
|
881
|
+
calls += 1;
|
|
882
|
+
const recordedName = asString(payload.flowName);
|
|
883
|
+
const flowName = recordedName ?? `call-${calls}`;
|
|
884
|
+
const span = builder(`call-${calls}`, "call", inspectLabel(flowName), "running", at, {
|
|
885
|
+
...opened,
|
|
886
|
+
...(recordedName === undefined ? {} : { flowName: recordedName }),
|
|
887
|
+
input: payload.input,
|
|
888
|
+
fields: restOf(payload, ["flowName", "input"])
|
|
889
|
+
});
|
|
890
|
+
openCalls.push({ flowName, callId: asString(payload.callId), span });
|
|
891
|
+
topOf.set(span, frame ?? cell ?? span);
|
|
892
|
+
parent().children.push(span);
|
|
893
|
+
break;
|
|
894
|
+
}
|
|
895
|
+
case "control.agent.cell-call-settled": {
|
|
896
|
+
const index = openCallIndex(openCalls, asString(payload.callId), asString(payload.flowName));
|
|
897
|
+
const settled = index < 0 ? undefined : openCalls.splice(index, 1)[0];
|
|
898
|
+
if (settled === undefined)
|
|
899
|
+
break;
|
|
900
|
+
const failed = asString(payload.outcome) === "failure";
|
|
901
|
+
touch(settled.span);
|
|
902
|
+
settled.span.endedAt = at;
|
|
903
|
+
settled.span.status = failed ? "failed" : "completed";
|
|
904
|
+
settled.span.detail = {
|
|
905
|
+
...settled.span.detail,
|
|
906
|
+
...(failed ? { message: textOf(payload.message) } : { output: textOf(payload.value) }),
|
|
907
|
+
...(!failed && settled.flowName === "agent/spawn" && asString(asRecord(payload.value).child) !== undefined
|
|
908
|
+
? { childRunId: asString(asRecord(payload.value).child) }
|
|
909
|
+
: {})
|
|
910
|
+
};
|
|
911
|
+
break;
|
|
912
|
+
}
|
|
913
|
+
case "control.agent.cell-printed": {
|
|
914
|
+
if (cell === undefined) {
|
|
915
|
+
parent().children.push(builder(`printed-${record.sequence ?? at}`, "event", "printed", "completed", at, {
|
|
916
|
+
...opened,
|
|
917
|
+
printed: textOf(payload.text)
|
|
918
|
+
}));
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
cell.detail = {
|
|
922
|
+
...cell.detail,
|
|
923
|
+
printed: [cell.detail.printed, textOf(payload.text)].filter((text) => text !== undefined).join("\n")
|
|
924
|
+
};
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
927
|
+
case "control.agent.cell-settled": {
|
|
928
|
+
closeCell(at, asString(payload.outcome) === "failure" ? "failed" : "completed");
|
|
929
|
+
break;
|
|
930
|
+
}
|
|
931
|
+
case "control.agent.resolved": {
|
|
932
|
+
const span = builder(`resolved-${record.sequence ?? at}`, "resolved", "resolved", "completed", at, {
|
|
933
|
+
...opened,
|
|
934
|
+
output: textOf(payload.text)
|
|
935
|
+
});
|
|
936
|
+
span.endedAt = at;
|
|
937
|
+
(frame ?? root).children.push(span);
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
case "control.approval.requested": {
|
|
941
|
+
const requestId = asString(payload.requestId) ?? `approval-${record.sequence ?? at}`;
|
|
942
|
+
const question = asString(payload.question);
|
|
943
|
+
const span = builder(`approval-${requestId}`, "approval", `approval${question === undefined ? "" : ` · ${question}`}`, "waiting", at, {
|
|
944
|
+
...opened,
|
|
945
|
+
fields: restOf(payload, ["question", "requestId", "payload", "runId"])
|
|
946
|
+
});
|
|
947
|
+
approvals.set(requestId, span);
|
|
948
|
+
topOf.set(span, frame ?? span);
|
|
949
|
+
(frame ?? root).children.push(span);
|
|
950
|
+
break;
|
|
951
|
+
}
|
|
952
|
+
case "control.approval.approved":
|
|
953
|
+
case "control.approval.denied": {
|
|
954
|
+
const decided = kind === "control.approval.approved" ? "approved" : "denied";
|
|
955
|
+
const key = asString(payload.tokenId) ?? asString(payload.requestId);
|
|
956
|
+
const span = key === undefined
|
|
957
|
+
? [...approvals.values()].find((entry) => entry.status === "waiting")
|
|
958
|
+
: approvals.get(key);
|
|
959
|
+
if (span === undefined)
|
|
960
|
+
break;
|
|
961
|
+
touch(span);
|
|
962
|
+
span.status = decided;
|
|
963
|
+
span.endedAt = at;
|
|
964
|
+
break;
|
|
965
|
+
}
|
|
966
|
+
case "control.run.completed":
|
|
967
|
+
case "control.run.failed":
|
|
968
|
+
case "control.run.cancelled": {
|
|
969
|
+
closeFrame(at);
|
|
970
|
+
root.endedAt = at;
|
|
971
|
+
// The journal's own verdict outranks the card's phase word: a scrub to this record shows the run settled.
|
|
972
|
+
state.verdict = kind.slice("control.run.".length);
|
|
973
|
+
break;
|
|
974
|
+
}
|
|
975
|
+
default: {
|
|
976
|
+
if (!kind.startsWith("control."))
|
|
977
|
+
break;
|
|
978
|
+
const span = builder(`event-${record.sequence ?? at}`, "event", kind.slice("control.".length), "completed", at, {
|
|
979
|
+
...opened,
|
|
980
|
+
fields: restOf(payload, [])
|
|
981
|
+
});
|
|
982
|
+
span.endedAt = at;
|
|
983
|
+
parent().children.push(span);
|
|
984
|
+
if (kind === "control.agent.turn-closed" && payload.step !== undefined) {
|
|
985
|
+
const closed = frame;
|
|
986
|
+
closeFrame(at);
|
|
987
|
+
if (closed !== undefined && payload.outcome === "suspended")
|
|
988
|
+
closed.status = "waiting";
|
|
989
|
+
if (closed !== undefined && payload.outcome === "aborted")
|
|
990
|
+
closed.status = "cancelled";
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
Object.assign(state, { frame, cell, frames, calls, seat });
|
|
996
|
+
const after = frame ?? cell;
|
|
997
|
+
if (after !== undefined)
|
|
998
|
+
dirty.add(after);
|
|
999
|
+
disciplineStep(state.discipline, record);
|
|
1000
|
+
};
|
|
1001
|
+
/**
|
|
1002
|
+
* The trace {@link foldStep} has read so far, as the run with `status` would
|
|
1003
|
+
* show it.
|
|
1004
|
+
*
|
|
1005
|
+
* Nothing live is mutated. A settled run closes its open frame where the
|
|
1006
|
+
* journal ends, so that close is applied to the frozen copy only.
|
|
1007
|
+
*/
|
|
1008
|
+
const foldModel = (state, status, journal, engine) => {
|
|
1009
|
+
const { root, frame, cell, lastAt } = state;
|
|
1010
|
+
const closed = new Map();
|
|
1011
|
+
const statusOf = (node) => closed.get(node)?.status ?? node.status;
|
|
1012
|
+
// A settled run leaves no frame open: the last frame ends where the journal does.
|
|
1013
|
+
const terminal = TERMINAL_RUN.has(status);
|
|
1014
|
+
if (terminal && frame !== undefined) {
|
|
1015
|
+
if (cell !== undefined) {
|
|
1016
|
+
closed.set(cell, {
|
|
1017
|
+
status: cell.children.some((child) => child.status === "failed") ? "failed" : "completed",
|
|
1018
|
+
endedAt: lastAt
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
closed.set(frame, {
|
|
1022
|
+
status: frame.children.some((child) => statusOf(child) === "failed") ? "failed" : "completed",
|
|
1023
|
+
endedAt: lastAt
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
if (engine === undefined && state.engineTrace === undefined)
|
|
1027
|
+
state.engineTrace = engineTraceFromJournal(state.engine);
|
|
1028
|
+
const children = [...root.children, ...(engine ?? state.engineTrace)];
|
|
1029
|
+
closed.set(root, {
|
|
1030
|
+
status: state.verdict ?? status,
|
|
1031
|
+
endedAt: root.endedAt ?? (terminal && state.count > 0 ? lastAt : Number.NaN)
|
|
1032
|
+
});
|
|
1033
|
+
const freeze = (node, depth, members) => freezeWith(node, depth, members.map((child) => freeze(child, depth + 1, child.children)));
|
|
1034
|
+
const freezeWith = (node, depth, frozenChildren) => {
|
|
1035
|
+
const override = closed.get(node);
|
|
1036
|
+
const nodeStatus = override?.status ?? node.status;
|
|
1037
|
+
const endedAt = override === undefined || Number.isNaN(override.endedAt) ? node.endedAt : override.endedAt;
|
|
1038
|
+
const held = state.frozen.get(node);
|
|
1039
|
+
if (held !== undefined && held.depth === depth && held.label === node.label && held.status === nodeStatus &&
|
|
1040
|
+
held.startedAt === node.startedAt && held.endedAt === endedAt && held.detail === node.detail &&
|
|
1041
|
+
held.children.length === frozenChildren.length &&
|
|
1042
|
+
held.children.every((child, index) => child === frozenChildren[index]))
|
|
1043
|
+
return held;
|
|
1044
|
+
const span = {
|
|
1045
|
+
id: node.id,
|
|
1046
|
+
kind: node.kind,
|
|
1047
|
+
label: node.label,
|
|
1048
|
+
status: nodeStatus,
|
|
1049
|
+
startedAt: node.startedAt,
|
|
1050
|
+
...(endedAt === undefined ? {} : { endedAt }),
|
|
1051
|
+
depth,
|
|
1052
|
+
children: frozenChildren,
|
|
1053
|
+
detail: node.detail
|
|
1054
|
+
};
|
|
1055
|
+
state.frozen.set(node, span);
|
|
1056
|
+
return span;
|
|
1057
|
+
};
|
|
1058
|
+
// A child no step touched since the last model is a closed subtree: its
|
|
1059
|
+
// frozen span stands, and nothing under it is walked again.
|
|
1060
|
+
const settledChild = (child) => {
|
|
1061
|
+
if (state.dirty.has(child) || closed.has(child))
|
|
1062
|
+
return undefined;
|
|
1063
|
+
const held = state.frozen.get(child);
|
|
1064
|
+
return held?.depth === 1 ? held : undefined;
|
|
1065
|
+
};
|
|
1066
|
+
const frozenChildren = children.map((child) => settledChild(child) ?? freeze(child, 1, child.children));
|
|
1067
|
+
state.dirty.clear();
|
|
1068
|
+
const frozenRoot = freezeWith(root, 0, frozenChildren);
|
|
1069
|
+
const rows = [];
|
|
1070
|
+
const walk = (span) => {
|
|
1071
|
+
rows.push(span);
|
|
1072
|
+
for (const child of span.children)
|
|
1073
|
+
walk(child);
|
|
1074
|
+
};
|
|
1075
|
+
walk(frozenRoot);
|
|
1076
|
+
let start = Number.POSITIVE_INFINITY;
|
|
1077
|
+
let end = 0;
|
|
1078
|
+
let running = 0;
|
|
1079
|
+
let failed = 0;
|
|
1080
|
+
for (const span of rows) {
|
|
1081
|
+
start = Math.min(start, span.startedAt);
|
|
1082
|
+
end = Math.max(end, span.endedAt ?? span.startedAt);
|
|
1083
|
+
if (span.kind !== "run" && span.status === "running")
|
|
1084
|
+
running += 1;
|
|
1085
|
+
if (span.kind !== "run" && span.status === "failed")
|
|
1086
|
+
failed += 1;
|
|
1087
|
+
}
|
|
1088
|
+
const extent = state.count === 0 || !Number.isFinite(start)
|
|
1089
|
+
? { start: 0, end: 0 }
|
|
1090
|
+
: { start, end: Math.max(end, start) };
|
|
1091
|
+
return {
|
|
1092
|
+
journal,
|
|
1093
|
+
root: frozenRoot,
|
|
1094
|
+
rows,
|
|
1095
|
+
extent,
|
|
1096
|
+
counts: { spans: rows.length - 1, running, failed },
|
|
1097
|
+
...disciplineModel(state.discipline)
|
|
1098
|
+
};
|
|
1099
|
+
};
|
|
1100
|
+
const sequenceOf = (record) => record.sequence ?? 0;
|
|
1101
|
+
const TERMINAL_KINDS = new Set([
|
|
1102
|
+
"control.run.completed",
|
|
1103
|
+
"control.run.failed",
|
|
1104
|
+
"control.run.cancelled"
|
|
1105
|
+
]);
|
|
1106
|
+
/** One deduplicated record to the fold of the dispatch it was recorded under. */
|
|
1107
|
+
const route = (state, record) => {
|
|
1108
|
+
// A run verdict stamped with another run's id (a child's, riding in this
|
|
1109
|
+
// journal) must not settle this run's trace or pin its milestone.
|
|
1110
|
+
if (record.kind?.startsWith("control.run.") === true && record.runId !== undefined && record.runId !== state.run.runId) {
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
state.firstAt = Math.min(state.firstAt, timeOf(record, asRecord(record.payload)));
|
|
1114
|
+
if (TERMINAL_KINDS.has(record.kind ?? ""))
|
|
1115
|
+
state.terminal = record;
|
|
1116
|
+
const scope = callScope(record);
|
|
1117
|
+
if (scope === undefined)
|
|
1118
|
+
return foldStep(state.base, record);
|
|
1119
|
+
let group = state.groups.get(scope);
|
|
1120
|
+
if (group === undefined) {
|
|
1121
|
+
group = foldInit(state.run);
|
|
1122
|
+
state.groups.set(scope, group);
|
|
1123
|
+
}
|
|
1124
|
+
foldStep(group, record);
|
|
1125
|
+
};
|
|
1126
|
+
/** Refolds `arrived` from nothing: the batch rules, exactly. */
|
|
1127
|
+
const reseed = (state, arrived) => {
|
|
1128
|
+
const journal = [...arrived].sort((left, right) => sequenceOf(left) - sequenceOf(right));
|
|
1129
|
+
const filter = callEventFilter();
|
|
1130
|
+
for (const record of journal)
|
|
1131
|
+
filter.push(record);
|
|
1132
|
+
Object.assign(state, {
|
|
1133
|
+
arrived,
|
|
1134
|
+
journal,
|
|
1135
|
+
filter,
|
|
1136
|
+
base: foldInit(state.run),
|
|
1137
|
+
groups: new Map(),
|
|
1138
|
+
engine: journal.filter(isEngineRow),
|
|
1139
|
+
engineTrace: undefined,
|
|
1140
|
+
firstAt: Number.POSITIVE_INFINITY,
|
|
1141
|
+
terminal: undefined,
|
|
1142
|
+
memo: undefined
|
|
1143
|
+
});
|
|
1144
|
+
for (const record of uniqueCallEvents(journal))
|
|
1145
|
+
route(state, record);
|
|
1146
|
+
};
|
|
1147
|
+
/**
|
|
1148
|
+
* Folds `records` into a fold that later records can be stepped onto.
|
|
1149
|
+
*
|
|
1150
|
+
* @param run the run the journal belongs to
|
|
1151
|
+
* @param records its journal so far, in any order
|
|
1152
|
+
*
|
|
1153
|
+
* @category constructors
|
|
1154
|
+
* @since 1.0.0
|
|
1155
|
+
*/
|
|
1156
|
+
export const traceFold = (run, records = []) => {
|
|
1157
|
+
const state = {
|
|
1158
|
+
run: { runId: run.runId, flowId: run.flowId, ...(run.kind === undefined ? {} : { kind: run.kind }) }
|
|
1159
|
+
};
|
|
1160
|
+
state.source = undefined;
|
|
1161
|
+
reseed(state, [...records]);
|
|
1162
|
+
return state;
|
|
1163
|
+
};
|
|
1164
|
+
/**
|
|
1165
|
+
* Folds one more record onto `fold`, which it returns.
|
|
1166
|
+
*
|
|
1167
|
+
* A record in sequence order costs its own work. One that arrives before a
|
|
1168
|
+
* sequence already stepped, or a native fact that supersedes telemetry the
|
|
1169
|
+
* fold already placed, refolds the journal, because the batch rules move an
|
|
1170
|
+
* earlier record for both.
|
|
1171
|
+
*
|
|
1172
|
+
* @param fold the fold; consumed
|
|
1173
|
+
* @param record the next journal record
|
|
1174
|
+
*
|
|
1175
|
+
* @category combinators
|
|
1176
|
+
* @since 1.0.0
|
|
1177
|
+
*/
|
|
1178
|
+
export const traceFoldStep = (fold, record) => {
|
|
1179
|
+
const state = fold;
|
|
1180
|
+
state.memo = undefined;
|
|
1181
|
+
state.arrived.push(record);
|
|
1182
|
+
const last = state.journal.at(-1);
|
|
1183
|
+
if (last !== undefined && !(sequenceOf(record) >= sequenceOf(last))) {
|
|
1184
|
+
reseed(state, state.arrived);
|
|
1185
|
+
return fold;
|
|
1186
|
+
}
|
|
1187
|
+
state.journal.push(record);
|
|
1188
|
+
if (isEngineRow(record)) {
|
|
1189
|
+
state.engine.push(record);
|
|
1190
|
+
state.engineTrace = undefined;
|
|
1191
|
+
}
|
|
1192
|
+
const verdict = state.filter.push(record);
|
|
1193
|
+
if (verdict._tag === "rewrite")
|
|
1194
|
+
reseed(state, state.arrived);
|
|
1195
|
+
else if (verdict._tag === "keep")
|
|
1196
|
+
route(state, verdict.event);
|
|
1197
|
+
return fold;
|
|
1198
|
+
};
|
|
1199
|
+
/**
|
|
1200
|
+
* Brings `fold` up to `records`, stepping only what it has not read.
|
|
1201
|
+
*
|
|
1202
|
+
* `records` extends the array the fold last read when every earlier element is
|
|
1203
|
+
* the same object; anything else, or a different run, starts a new fold. The
|
|
1204
|
+
* comparison is against the records the fold consumed, not the caller's array,
|
|
1205
|
+
* so growing or rewriting that same array in place is seen. A reader that
|
|
1206
|
+
* re-renders one growing journal therefore pays per new record.
|
|
1207
|
+
*
|
|
1208
|
+
* @param fold the fold the reader holds, if any; consumed
|
|
1209
|
+
* @param run the run the journal belongs to
|
|
1210
|
+
* @param records the whole journal as the reader now holds it
|
|
1211
|
+
*
|
|
1212
|
+
* @category combinators
|
|
1213
|
+
* @since 1.0.0
|
|
1214
|
+
*/
|
|
1215
|
+
export const traceFoldSync = (fold, run, records) => {
|
|
1216
|
+
const held = fold;
|
|
1217
|
+
const source = held?.source;
|
|
1218
|
+
const extends_ = held !== undefined && source !== undefined && held.run.runId === run.runId &&
|
|
1219
|
+
held.run.flowId === run.flowId && held.run.kind === run.kind && records.length >= source.length &&
|
|
1220
|
+
source.every((record, index) => records[index] === record);
|
|
1221
|
+
const next = extends_ ? held : traceFold(run);
|
|
1222
|
+
for (let index = extends_ ? source.length : 0; index < records.length; index++)
|
|
1223
|
+
traceFoldStep(next, records[index]);
|
|
1224
|
+
next.source = records.slice();
|
|
1225
|
+
return next;
|
|
1226
|
+
};
|
|
1227
|
+
/**
|
|
1228
|
+
* The trace a fold has read, as the run with `status` shows it.
|
|
1229
|
+
*
|
|
1230
|
+
* Taken again without a step between, it is the same object.
|
|
1231
|
+
*
|
|
1232
|
+
* @param fold the fold
|
|
1233
|
+
* @param status the run card's phase word
|
|
1234
|
+
*
|
|
1235
|
+
* @category utilities
|
|
1236
|
+
* @since 1.0.0
|
|
1237
|
+
*/
|
|
1238
|
+
export const traceFoldModel = (fold, status) => {
|
|
1239
|
+
const state = fold;
|
|
1240
|
+
if (state.memo?.status === status)
|
|
1241
|
+
return state.memo.model;
|
|
1242
|
+
const model = mergeModel(state, status);
|
|
1243
|
+
state.memo = { status, model };
|
|
1244
|
+
return model;
|
|
1245
|
+
};
|
|
1246
|
+
const mergeModel = (state, status) => {
|
|
1247
|
+
const journal = state.journal.slice();
|
|
1248
|
+
if (state.groups.size === 0) {
|
|
1249
|
+
if (state.engineTrace === undefined)
|
|
1250
|
+
state.engineTrace = engineTraceFromJournal(state.engine);
|
|
1251
|
+
return foldModel(state.base, status, journal, state.engineTrace);
|
|
1252
|
+
}
|
|
1253
|
+
const base = foldModel(state.base, status, journal);
|
|
1254
|
+
const children = [...base.root.children];
|
|
1255
|
+
const bands = [...base.bands];
|
|
1256
|
+
const milestones = [...base.milestones];
|
|
1257
|
+
const lines = [...base.lines];
|
|
1258
|
+
const notes = [...base.notes];
|
|
1259
|
+
const owners = [...base.owners];
|
|
1260
|
+
const terminal = state.terminal;
|
|
1261
|
+
const terminalAt = terminal === undefined ? undefined : timeOf(terminal, asRecord(terminal.payload));
|
|
1262
|
+
for (const [scope, group] of state.groups) {
|
|
1263
|
+
const prefix = `step:${encodeURIComponent(scope)}/`;
|
|
1264
|
+
const scoped = foldModel(group, "running", journal);
|
|
1265
|
+
const rename = (span) => ({
|
|
1266
|
+
...span,
|
|
1267
|
+
id: `${prefix}${span.id}`,
|
|
1268
|
+
children: span.children.map(rename),
|
|
1269
|
+
...(span.status === "running" && TERMINAL_RUN.has(base.root.status) ?
|
|
1270
|
+
{
|
|
1271
|
+
// A terminal run proves work stopped. It does not prove which open
|
|
1272
|
+
// step succeeded or caused the run to fail.
|
|
1273
|
+
status: "stopped",
|
|
1274
|
+
...(terminalAt === undefined ? {} : { endedAt: terminalAt })
|
|
1275
|
+
} :
|
|
1276
|
+
{})
|
|
1277
|
+
});
|
|
1278
|
+
children.push(...scoped.root.children.map(rename));
|
|
1279
|
+
bands.push(...scoped.bands.map((band) => ({ ...band, frames: band.frames.map((id) => `${prefix}${id}`) })));
|
|
1280
|
+
// A moment, a line, a note and a position all name the frame they were
|
|
1281
|
+
// recorded under, so the merge renames each of them the same way: the
|
|
1282
|
+
// step's own frame, or this run's root for what the step recorded outside
|
|
1283
|
+
// any frame.
|
|
1284
|
+
const owned = (spanId) => spanId === scoped.root.id ? base.root.id : `${prefix}${spanId}`;
|
|
1285
|
+
milestones.push(...scoped.milestones.map((one) => ({ ...one, spanId: owned(one.spanId) })));
|
|
1286
|
+
lines.push(...scoped.lines.map((line) => ({ ...line, spanId: `${prefix}${line.spanId}` })));
|
|
1287
|
+
notes.push(...scoped.notes.map((note) => ({ ...note, spanId: owned(note.spanId) })));
|
|
1288
|
+
owners.push(...scoped.owners.map((one) => ({ ...one, spanId: owned(one.spanId) })));
|
|
1289
|
+
}
|
|
1290
|
+
children.sort((left, right) => left.startedAt - right.startedAt || (left.detail.sequence ?? 0) - (right.detail.sequence ?? 0));
|
|
1291
|
+
const root = { ...base.root, startedAt: state.firstAt, children };
|
|
1292
|
+
const rows = [];
|
|
1293
|
+
const walk = (span) => {
|
|
1294
|
+
rows.push(span);
|
|
1295
|
+
span.children.forEach(walk);
|
|
1296
|
+
};
|
|
1297
|
+
walk(root);
|
|
1298
|
+
const positions = new Map(rows.map((span, index) => [span.id, index]));
|
|
1299
|
+
// Each step folds its own frames from 1, so a merged run would print two
|
|
1300
|
+
// rows numbered 1 and a `same as 1` that names either. The merged list
|
|
1301
|
+
// numbers the rows a person reads, and a repeat names the renumbered row of
|
|
1302
|
+
// the step that recorded it.
|
|
1303
|
+
const merged = lines.sort((left, right) => positions.get(left.spanId) - positions.get(right.spanId));
|
|
1304
|
+
const stepOf = (spanId) => spanId.slice(0, spanId.lastIndexOf("/") + 1);
|
|
1305
|
+
const renumbered = new Map(merged.map((line, index) => [`${stepOf(line.spanId)}${line.frame}`, index + 1]));
|
|
1306
|
+
let start = Number.POSITIVE_INFINITY;
|
|
1307
|
+
let end = Number.NEGATIVE_INFINITY;
|
|
1308
|
+
for (const span of rows) {
|
|
1309
|
+
start = Math.min(start, span.startedAt);
|
|
1310
|
+
end = Math.max(end, span.endedAt ?? span.startedAt);
|
|
1311
|
+
}
|
|
1312
|
+
return {
|
|
1313
|
+
journal,
|
|
1314
|
+
root,
|
|
1315
|
+
rows,
|
|
1316
|
+
extent: { start, end },
|
|
1317
|
+
counts: {
|
|
1318
|
+
spans: rows.length - 1,
|
|
1319
|
+
running: rows.filter((span) => span.kind !== "run" && span.status === "running").length,
|
|
1320
|
+
failed: rows.filter((span) => span.kind !== "run" && span.status === "failed").length
|
|
1321
|
+
},
|
|
1322
|
+
bands: bands.sort((left, right) => left.startedAt - right.startedAt || left.seq - right.seq),
|
|
1323
|
+
milestones: milestones.sort((left, right) => left.seq - right.seq),
|
|
1324
|
+
lines: merged.map((line, index) => ({
|
|
1325
|
+
...line,
|
|
1326
|
+
frame: index + 1,
|
|
1327
|
+
...(line.repeatOf === undefined
|
|
1328
|
+
? {}
|
|
1329
|
+
: { repeatOf: renumbered.get(`${stepOf(line.spanId)}${line.repeatOf}`) })
|
|
1330
|
+
})),
|
|
1331
|
+
notes: notes.sort((left, right) => left.seq - right.seq),
|
|
1332
|
+
owners: owners.sort((left, right) => left.seq - right.seq)
|
|
1333
|
+
};
|
|
1334
|
+
};
|
|
1335
|
+
/**
|
|
1336
|
+
* Fold each recorded dispatch independently, keeping prompt-run addresses unchanged.
|
|
1337
|
+
*
|
|
1338
|
+
* The same fold a live reader advances with {@link traceFoldStep}, taken over
|
|
1339
|
+
* the whole journal at once.
|
|
1340
|
+
*
|
|
1341
|
+
* The options the card carries are accepted and read by nothing: a plan's
|
|
1342
|
+
* check targets are a coverage requirement, and a requirement is not evidence
|
|
1343
|
+
* that any call ran it. Every word this fold says is read off a record.
|
|
1344
|
+
*
|
|
1345
|
+
* @category utilities
|
|
1346
|
+
* @since 1.0.0
|
|
1347
|
+
*/
|
|
1348
|
+
export const traceFromJournal = (run, records, _options = {}) => traceFoldModel(traceFold(run, records), run.status);
|
|
1349
|
+
/**
|
|
1350
|
+
* One span's bar on the shared axis, as percentages. An open span runs to the
|
|
1351
|
+
* axis end; an instant span is zero width and renders as a marker.
|
|
1352
|
+
*
|
|
1353
|
+
* @param span the span
|
|
1354
|
+
* @param extent the axis
|
|
1355
|
+
*
|
|
1356
|
+
* @category utilities
|
|
1357
|
+
* @since 1.0.0
|
|
1358
|
+
*/
|
|
1359
|
+
export const waterfallGeometry = (span, extent) => {
|
|
1360
|
+
const width = Math.max(extent.end - extent.start, 1);
|
|
1361
|
+
const from = span.startedAt;
|
|
1362
|
+
const to = span.endedAt ?? extent.end;
|
|
1363
|
+
const left = ((from - extent.start) / width) * 100;
|
|
1364
|
+
const bar = Math.max(((to - from) / width) * 100, 0);
|
|
1365
|
+
return { left: Math.round(left * 100) / 100, width: Math.round(bar * 100) / 100 };
|
|
1366
|
+
};
|
|
1367
|
+
/**
|
|
1368
|
+
* The axis the phase strip measures on: the bands' own span when the journal
|
|
1369
|
+
* opened frames, the run's whole extent when it opened none.
|
|
1370
|
+
*
|
|
1371
|
+
* A milestone is a journal record, and the records that write one do not need a
|
|
1372
|
+
* frame: a journal of `read-only-demanded` and `sufficiency-observed` with no
|
|
1373
|
+
* `turn-opened` carries two moments and opens no band. Milestones therefore
|
|
1374
|
+
* survive without bands, and the axis that places them cannot be read off the
|
|
1375
|
+
* bands alone. The other choice — dropping a moment because no band was drawn
|
|
1376
|
+
* beside it — would be this fold deciding a record the journal made did not
|
|
1377
|
+
* happen, which is the one thing it may never do.
|
|
1378
|
+
*
|
|
1379
|
+
* @param model the trace
|
|
1380
|
+
*
|
|
1381
|
+
* @category utilities
|
|
1382
|
+
* @since 1.0.0
|
|
1383
|
+
*/
|
|
1384
|
+
export const phaseExtent = (model) => {
|
|
1385
|
+
return model.bands.length === 0 ? model.extent : {
|
|
1386
|
+
start: Math.min(...model.bands.map((band) => band.startedAt)),
|
|
1387
|
+
end: Math.max(...model.bands.map((band) => band.endedAt))
|
|
1388
|
+
};
|
|
1389
|
+
};
|
|
1390
|
+
/**
|
|
1391
|
+
* One band's share of that axis, as percentages, the way the waterfall measures
|
|
1392
|
+
* a span.
|
|
1393
|
+
*
|
|
1394
|
+
* A run whose frames all land in one millisecond recorded no elapsed time. Read
|
|
1395
|
+
* against a floored axis every such band is zero wide at left 0, so the whole
|
|
1396
|
+
* strip collapses onto the left edge and says nothing at all. The bands' ORDER
|
|
1397
|
+
* is a journal fact even where their durations are not, so with no duration to
|
|
1398
|
+
* measure the ordinal IS the axis: equal slices, left to right.
|
|
1399
|
+
*
|
|
1400
|
+
* @param band the band
|
|
1401
|
+
* @param extent the axis, from {@link phaseExtent}
|
|
1402
|
+
* @param index the band's place in the strip
|
|
1403
|
+
* @param count how many bands the strip holds
|
|
1404
|
+
*
|
|
1405
|
+
* @category utilities
|
|
1406
|
+
* @since 1.0.0
|
|
1407
|
+
*/
|
|
1408
|
+
export const phaseBandGeometry = (band, extent, index, count) => {
|
|
1409
|
+
const round = (value) => Math.round(value * 100) / 100;
|
|
1410
|
+
const axis = extent.end - extent.start;
|
|
1411
|
+
if (axis <= 0) {
|
|
1412
|
+
const share = 100 / Math.max(count, 1);
|
|
1413
|
+
return { left: round(index * share), width: round(share) };
|
|
1414
|
+
}
|
|
1415
|
+
return {
|
|
1416
|
+
left: round(((band.startedAt - extent.start) / axis) * 100),
|
|
1417
|
+
width: round((Math.max(band.endedAt - band.startedAt, 0) / axis) * 100)
|
|
1418
|
+
};
|
|
1419
|
+
};
|
|
1420
|
+
/** The flows whose spans are messages between a coordinator and its workers (spec 06 §3). */
|
|
1421
|
+
const MESSAGE_FLOWS = new Set(["agent/send", "agent/await"]);
|
|
1422
|
+
/** Whether a span, or any span under it, passes the filter.
|
|
1423
|
+
*
|
|
1424
|
+
* @category utilities
|
|
1425
|
+
* @since 1.0.0
|
|
1426
|
+
*/
|
|
1427
|
+
export const spanMatches = (span, filter) => {
|
|
1428
|
+
const own = filter === "all"
|
|
1429
|
+
? true
|
|
1430
|
+
: filter === "running"
|
|
1431
|
+
? span.status === "running" || span.status === "waiting"
|
|
1432
|
+
: filter === "failed"
|
|
1433
|
+
? span.status === "failed"
|
|
1434
|
+
: filter === "model"
|
|
1435
|
+
? span.kind === "model"
|
|
1436
|
+
: filter === "flow"
|
|
1437
|
+
? span.kind === "call" || span.kind === "execution" || span.kind === "attempt"
|
|
1438
|
+
: span.kind === "call" && MESSAGE_FLOWS.has(span.label);
|
|
1439
|
+
return own || span.children.some((child) => spanMatches(child, filter));
|
|
1440
|
+
};
|
|
1441
|
+
/**
|
|
1442
|
+
* Stable filter identifiers accepted by run trace commands.
|
|
1443
|
+
*
|
|
1444
|
+
* @category utilities
|
|
1445
|
+
* @since 1.0.0
|
|
1446
|
+
*/
|
|
1447
|
+
export const TRACE_FILTER_IDS = [
|
|
1448
|
+
"all",
|
|
1449
|
+
"running",
|
|
1450
|
+
"failed",
|
|
1451
|
+
"model",
|
|
1452
|
+
"flow",
|
|
1453
|
+
"messages"
|
|
1454
|
+
];
|
|
1455
|
+
const FILTER_LABELS = {
|
|
1456
|
+
all: "all",
|
|
1457
|
+
running: "running",
|
|
1458
|
+
failed: "failed",
|
|
1459
|
+
model: "model calls",
|
|
1460
|
+
flow: "flow calls",
|
|
1461
|
+
messages: "messages"
|
|
1462
|
+
};
|
|
1463
|
+
/**
|
|
1464
|
+
* The filter chips a run of this kind shows (spec 06 §2 and §3): a prototype
|
|
1465
|
+
* has `all | messages | failed` and nothing else; every other run has the
|
|
1466
|
+
* shared set. `messages` is a prototype's conversation across workers, so it
|
|
1467
|
+
* is not offered elsewhere.
|
|
1468
|
+
*
|
|
1469
|
+
* @param kind the run's kind
|
|
1470
|
+
*
|
|
1471
|
+
* @category utilities
|
|
1472
|
+
* @since 1.0.0
|
|
1473
|
+
*/
|
|
1474
|
+
export const traceFiltersFor = (kind) => (kind === "prototype"
|
|
1475
|
+
? ["all", "messages", "failed"]
|
|
1476
|
+
: ["all", "running", "failed", "model", "flow"]).map((id) => [id, FILTER_LABELS[id]]);
|
|
1477
|
+
/** Whether a filter word is one the trace knows.
|
|
1478
|
+
*
|
|
1479
|
+
* @category utilities
|
|
1480
|
+
* @since 1.0.0
|
|
1481
|
+
*/
|
|
1482
|
+
export const isTraceFilter = (value) => TRACE_FILTER_IDS.includes(value);
|
|
1483
|
+
/** The first prose line outside fenced code, bounded for the cheap turn list. Full text stays in the model span. */
|
|
1484
|
+
const proseLine = (text) => {
|
|
1485
|
+
if (text === undefined)
|
|
1486
|
+
return undefined;
|
|
1487
|
+
let fence;
|
|
1488
|
+
for (const line of text.split("\n")) {
|
|
1489
|
+
const trimmed = line.trim();
|
|
1490
|
+
const marker = /^(?:`{3,}|~{3,})/.exec(trimmed)?.[0];
|
|
1491
|
+
if (marker !== undefined) {
|
|
1492
|
+
if (fence === undefined)
|
|
1493
|
+
fence = marker;
|
|
1494
|
+
else if (marker[0] === fence[0] && marker.length >= fence.length)
|
|
1495
|
+
fence = undefined;
|
|
1496
|
+
continue;
|
|
1497
|
+
}
|
|
1498
|
+
if (fence !== undefined || trimmed === "")
|
|
1499
|
+
continue;
|
|
1500
|
+
// A code-only response or the journal's truncated-field object is not explanatory prose.
|
|
1501
|
+
if (/^(?:[[{]|(?:const|let|var|await|import|export|function|return)\b)/.test(trimmed))
|
|
1502
|
+
return undefined;
|
|
1503
|
+
const words = trimmed.replace(/^#{1,6}\s+|^[-*+]\s+|^>\s+/, "").replace(/\s+/g, " ");
|
|
1504
|
+
const sentence = words.match(/^.*?[.!?](?=\s|$)/)?.[0] ?? `${words}.`;
|
|
1505
|
+
return sentence.length <= 180 ? sentence : `${sentence.slice(0, 179).trimEnd()}…`;
|
|
1506
|
+
}
|
|
1507
|
+
return undefined;
|
|
1508
|
+
};
|
|
1509
|
+
/** One cheap line per turn; falls back to actual call names when the model recorded only code.
|
|
1510
|
+
*
|
|
1511
|
+
* @category utilities
|
|
1512
|
+
* @since 1.0.0
|
|
1513
|
+
*/
|
|
1514
|
+
export const turnNarratives = (model) => model.root.children.filter((span) => span.kind === "frame").map((frame, index) => {
|
|
1515
|
+
const text = frame.children.filter((span) => span.kind === "model").map((span) => proseLine(span.detail.output))
|
|
1516
|
+
.find((value) => value !== undefined);
|
|
1517
|
+
if (text !== undefined)
|
|
1518
|
+
return { frame, number: index + 1, text, source: "model" };
|
|
1519
|
+
const names = [];
|
|
1520
|
+
const walk = (span) => {
|
|
1521
|
+
if (span.kind === "call" && !names.includes(span.label))
|
|
1522
|
+
names.push(span.label);
|
|
1523
|
+
for (const child of span.children)
|
|
1524
|
+
walk(child);
|
|
1525
|
+
};
|
|
1526
|
+
walk(frame);
|
|
1527
|
+
return names.length > 0
|
|
1528
|
+
? {
|
|
1529
|
+
frame,
|
|
1530
|
+
number: index + 1,
|
|
1531
|
+
text: `The agent called ${names.slice(0, 3).join(", ")}${names.length > 3 ? ` and ${names.length - 3} other flows` : ""}.`,
|
|
1532
|
+
source: "calls"
|
|
1533
|
+
}
|
|
1534
|
+
: {
|
|
1535
|
+
frame,
|
|
1536
|
+
number: index + 1,
|
|
1537
|
+
text: frame.children.some((span) => span.kind === "cell")
|
|
1538
|
+
? "The agent produced a script; no flow calls were recorded in this turn."
|
|
1539
|
+
: frame.children.some((span) => span.kind === "model")
|
|
1540
|
+
? "The model responded; no script or flow calls were recorded in this turn."
|
|
1541
|
+
: "The turn started; no model response or flow calls have been recorded.",
|
|
1542
|
+
source: "journal"
|
|
1543
|
+
};
|
|
1544
|
+
});
|
|
1545
|
+
/** The recorded ancestry of a selection, for breadcrumbs and the selected turn's scoped call tree.
|
|
1546
|
+
*
|
|
1547
|
+
* @category utilities
|
|
1548
|
+
* @since 1.0.0
|
|
1549
|
+
*/
|
|
1550
|
+
export const spanPath = (model, id) => {
|
|
1551
|
+
const visit = (span) => {
|
|
1552
|
+
if (span.id === id)
|
|
1553
|
+
return [span];
|
|
1554
|
+
for (const child of span.children) {
|
|
1555
|
+
const path = visit(child);
|
|
1556
|
+
if (path !== undefined)
|
|
1557
|
+
return [span, ...path];
|
|
1558
|
+
}
|
|
1559
|
+
return undefined;
|
|
1560
|
+
};
|
|
1561
|
+
return visit(model.root) ?? [model.root];
|
|
1562
|
+
};
|
|
1563
|
+
/** A duration in the trace's units: milliseconds under a second, seconds under a minute, minutes and seconds under an hour, hours and minutes after.
|
|
1564
|
+
*
|
|
1565
|
+
* @category utilities
|
|
1566
|
+
* @since 1.0.0
|
|
1567
|
+
*/
|
|
1568
|
+
export const durationWords = (ms) => {
|
|
1569
|
+
if (ms < 1000)
|
|
1570
|
+
return `${Math.round(ms)}ms`;
|
|
1571
|
+
// Round before splitting, so 59.96s reads 1m00s rather than 60.0s, and 51m59.6s reads 52m00s rather than 51m60s.
|
|
1572
|
+
if (ms < 59_950)
|
|
1573
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
1574
|
+
const total = Math.round(ms / 1000);
|
|
1575
|
+
if (total < 3600) {
|
|
1576
|
+
const minutes = Math.floor(total / 60);
|
|
1577
|
+
return `${minutes}m${String(total - minutes * 60).padStart(2, "0")}s`;
|
|
1578
|
+
}
|
|
1579
|
+
const minutes = Math.round(ms / 60_000);
|
|
1580
|
+
const hours = Math.floor(minutes / 60);
|
|
1581
|
+
return `${hours}h${String(minutes - hours * 60).padStart(2, "0")}m`;
|
|
1582
|
+
};
|
|
1583
|
+
const memoryItems = (value) => Array.isArray(value) ?
|
|
1584
|
+
value.flatMap((raw) => {
|
|
1585
|
+
const item = typeof raw === "object" && raw !== null ? raw : undefined;
|
|
1586
|
+
return item?.kind === "memory" && typeof item.id === "string" && typeof item.p === "number"
|
|
1587
|
+
? [{ id: item.id, kind: "memory", relevance: 1 - item.p }]
|
|
1588
|
+
: [];
|
|
1589
|
+
}) :
|
|
1590
|
+
[];
|
|
1591
|
+
/**
|
|
1592
|
+
* What memory a run was brought, from the `control.agent.relevance-settled`
|
|
1593
|
+
* rows of its `run-events` projection: ids and probabilities, never text. A
|
|
1594
|
+
* reading also weighs flows, skills and instructions; only memory counts here.
|
|
1595
|
+
* An item any reading kept is in; one only ever withheld is withheld; both
|
|
1596
|
+
* most relevant first. A row whose verdicts were not retained is no reading.
|
|
1597
|
+
* Undefined when Jev never read for the run.
|
|
1598
|
+
*
|
|
1599
|
+
* @category folds
|
|
1600
|
+
* @since 1.0.0
|
|
1601
|
+
*/
|
|
1602
|
+
export const runMemoryOf = (records) => {
|
|
1603
|
+
const kept = new Map();
|
|
1604
|
+
const withheld = new Map();
|
|
1605
|
+
let read = false;
|
|
1606
|
+
for (const record of records) {
|
|
1607
|
+
if (record.kind !== "control.agent.relevance-settled")
|
|
1608
|
+
continue;
|
|
1609
|
+
const payload = typeof record.payload === "object" && record.payload !== null
|
|
1610
|
+
? record.payload
|
|
1611
|
+
: {};
|
|
1612
|
+
if (!Array.isArray(payload.kept) && !Array.isArray(payload.withheld))
|
|
1613
|
+
continue;
|
|
1614
|
+
read = true;
|
|
1615
|
+
for (const item of memoryItems(payload.kept))
|
|
1616
|
+
kept.set(item.id, item);
|
|
1617
|
+
for (const item of memoryItems(payload.withheld))
|
|
1618
|
+
withheld.set(item.id, item);
|
|
1619
|
+
}
|
|
1620
|
+
if (!read)
|
|
1621
|
+
return undefined;
|
|
1622
|
+
const byRelevance = (left, right) => right.relevance - left.relevance || left.id.localeCompare(right.id);
|
|
1623
|
+
return {
|
|
1624
|
+
kept: [...kept.values()].sort(byRelevance),
|
|
1625
|
+
withheld: [...withheld.values()].filter((item) => !kept.has(item.id)).sort(byRelevance)
|
|
1626
|
+
};
|
|
1627
|
+
};
|
|
1628
|
+
/** The system part an agent step's task travels in (`Agent.ts`). */
|
|
1629
|
+
const TASK_PREFIX = "The task for this run:\n\n";
|
|
1630
|
+
/** The agent's task from a traced request: its own `task` field, or the task
|
|
1631
|
+
* part of its system text when that fit the journal. Parsed when it is JSON. */
|
|
1632
|
+
const taskOf = (request) => {
|
|
1633
|
+
const parts = Array.isArray(request.system) ? request.system : [];
|
|
1634
|
+
const part = parts.find((text) => typeof text === "string" && text.startsWith(TASK_PREFIX));
|
|
1635
|
+
const task = typeof request.task === "string" ? request.task : part?.slice(TASK_PREFIX.length);
|
|
1636
|
+
if (task === undefined)
|
|
1637
|
+
return undefined;
|
|
1638
|
+
try {
|
|
1639
|
+
return JSON.parse(task);
|
|
1640
|
+
}
|
|
1641
|
+
catch {
|
|
1642
|
+
return task;
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1645
|
+
const hexDigest = /^[0-9a-f]{64}$/;
|
|
1646
|
+
/** A transcript cell shows an excerpt; the journal keeps the full record. */
|
|
1647
|
+
const cellText = (value) => {
|
|
1648
|
+
const text = textOf(value);
|
|
1649
|
+
return text === undefined || text.length <= 2000 ? text : `${text.slice(0, 2000)}…`;
|
|
1650
|
+
};
|
|
1651
|
+
/** The recorded presentation of a tool call: "Read t5.md", "Failed to write t5.md". */
|
|
1652
|
+
const callCellLabel = (started, failed) => {
|
|
1653
|
+
const descriptor = asRecord(started.descriptor);
|
|
1654
|
+
const presentation = asRecord(descriptor.presentation);
|
|
1655
|
+
const verbs = asRecord(presentation.verb);
|
|
1656
|
+
const name = started.flowName;
|
|
1657
|
+
const verb = asString(failed ? verbs.failure : verbs.success) ?? (failed ? `failed ${name}` : name);
|
|
1658
|
+
const subjectKey = asString(presentation.subject);
|
|
1659
|
+
const subject = subjectKey === undefined ? undefined : asString(asRecord(started.input)[subjectKey]);
|
|
1660
|
+
const text = subject === undefined ? verb : `${verb} ${subject}`;
|
|
1661
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
1662
|
+
};
|
|
1663
|
+
const callCellKind = (started) => {
|
|
1664
|
+
const activity = asString(asRecord(started.descriptor).activity);
|
|
1665
|
+
const name = started.flowName;
|
|
1666
|
+
return name === "ask" ? "ask" : activity === "reads" ? "read" : activity === "writes" ? "edit" : "run";
|
|
1667
|
+
};
|
|
1668
|
+
const decodeCallFact = Schema.decodeUnknownOption(CallFact.Fact);
|
|
1669
|
+
/** A native tool-call fact and the dispatch digest its scope names. */
|
|
1670
|
+
const callFactOf = (record) => {
|
|
1671
|
+
if (record.kind !== "control.engine.event")
|
|
1672
|
+
return undefined;
|
|
1673
|
+
const envelope = asRecord(record.payload);
|
|
1674
|
+
if (envelope.version !== 1 || envelope.eventType !== CallFact.eventType || envelope.sourceSequence !== 0) {
|
|
1675
|
+
return undefined;
|
|
1676
|
+
}
|
|
1677
|
+
const fact = decodeCallFact(envelope.payload);
|
|
1678
|
+
if (fact._tag === "None" || envelope.sourceId !== `call-fact-v1:${fact.value.callId}:${fact.value.phase}`) {
|
|
1679
|
+
return undefined;
|
|
1680
|
+
}
|
|
1681
|
+
const stepId = /@([0-9a-f]{64}):\d+#\d+$/.exec(fact.value.identity.runId)?.[1];
|
|
1682
|
+
return stepId === undefined ? undefined : { stepId, fact: fact.value };
|
|
1683
|
+
};
|
|
1684
|
+
/**
|
|
1685
|
+
* The agent transcript of every native dispatch in `journal`: model replies,
|
|
1686
|
+
* cells with what they printed, and tool calls, in journal order. Keyed by the
|
|
1687
|
+
* dispatch's step key digest, which the engine's node settlement names in
|
|
1688
|
+
* `stepKeyDigests`. Pure over the records; no model call.
|
|
1689
|
+
*/
|
|
1690
|
+
const stepTranscripts = (journal) => {
|
|
1691
|
+
const transcripts = new Map();
|
|
1692
|
+
const entry = (key) => {
|
|
1693
|
+
let found = transcripts.get(key);
|
|
1694
|
+
if (found === undefined)
|
|
1695
|
+
transcripts.set(key, found = { cells: [], tokens: 0, models: 0 });
|
|
1696
|
+
return found;
|
|
1697
|
+
};
|
|
1698
|
+
const lastCell = new Map();
|
|
1699
|
+
const calls = new Map();
|
|
1700
|
+
for (const record of journal) {
|
|
1701
|
+
const sequence = record.sequence ?? 0;
|
|
1702
|
+
const step = nativeStepEvent(record);
|
|
1703
|
+
if (step !== undefined) {
|
|
1704
|
+
const payload = asRecord(step.payload);
|
|
1705
|
+
// nativeStepEvent has decoded StepFact.Scope, whose stepId is a 64-digit hex string.
|
|
1706
|
+
const stepId = asString(asRecord(payload.step).stepId);
|
|
1707
|
+
const transcript = entry(stepId);
|
|
1708
|
+
if (step.kind === "control.agent.model-requested" && transcript.input === undefined) {
|
|
1709
|
+
const task = taskOf(payload);
|
|
1710
|
+
if (task !== undefined)
|
|
1711
|
+
transcript.input = task;
|
|
1712
|
+
}
|
|
1713
|
+
else if (step.kind === "control.agent.model-settled") {
|
|
1714
|
+
const usage = asRecord(payload.usage);
|
|
1715
|
+
const tokens = (asNumber(usage.inputTokens) ?? 0) + (asNumber(usage.outputTokens) ?? 0);
|
|
1716
|
+
const duration = asNumber(payload.durationMillis);
|
|
1717
|
+
const text = cellText(payload.text);
|
|
1718
|
+
transcript.tokens += tokens;
|
|
1719
|
+
transcript.models += 1;
|
|
1720
|
+
transcript.cells.push({
|
|
1721
|
+
id: `cell:${sequence}`,
|
|
1722
|
+
kind: "think",
|
|
1723
|
+
label: "Model reply",
|
|
1724
|
+
...(text === undefined ? {} : { quote: text }),
|
|
1725
|
+
tokens,
|
|
1726
|
+
...(duration === undefined ? {} : { took_s: Math.max(0, duration) / 1000 })
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
else if (step.kind === "control.agent.cell-produced") {
|
|
1730
|
+
const code = cellText(payload.text);
|
|
1731
|
+
const cell = {
|
|
1732
|
+
id: `cell:${sequence}`,
|
|
1733
|
+
kind: "run",
|
|
1734
|
+
label: "Ran a cell",
|
|
1735
|
+
...(code === undefined ? {} : { code })
|
|
1736
|
+
};
|
|
1737
|
+
transcript.cells.push(cell);
|
|
1738
|
+
lastCell.set(stepId, cell);
|
|
1739
|
+
}
|
|
1740
|
+
else if (step.kind === "control.agent.cell-printed") {
|
|
1741
|
+
const cell = lastCell.get(stepId);
|
|
1742
|
+
const printed = cellText(payload.text);
|
|
1743
|
+
if (cell !== undefined && printed !== undefined && printed !== "") {
|
|
1744
|
+
cell.output = cell.output === undefined ? printed : `${cell.output}\n${printed}`;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
else if (step.kind === "control.agent.cell-settled") {
|
|
1748
|
+
const cell = lastCell.get(stepId);
|
|
1749
|
+
if (cell !== undefined)
|
|
1750
|
+
cell.tone = asString(payload.outcome) === "failure" ? "fail" : "ok";
|
|
1751
|
+
}
|
|
1752
|
+
continue;
|
|
1753
|
+
}
|
|
1754
|
+
const call = callFactOf(record);
|
|
1755
|
+
if (call === undefined)
|
|
1756
|
+
continue;
|
|
1757
|
+
const handle = `${call.stepId}:${call.fact.callId}`;
|
|
1758
|
+
if (call.fact.phase === "invoked") {
|
|
1759
|
+
const input = cellText(call.fact.input);
|
|
1760
|
+
const cell = {
|
|
1761
|
+
id: `cell:${sequence}`,
|
|
1762
|
+
kind: callCellKind(call.fact),
|
|
1763
|
+
label: callCellLabel(call.fact, false),
|
|
1764
|
+
...(input === undefined ? {} : { code: input }),
|
|
1765
|
+
tone: "live"
|
|
1766
|
+
};
|
|
1767
|
+
entry(call.stepId).cells.push(cell);
|
|
1768
|
+
calls.set(handle, { cell, started: call.fact });
|
|
1769
|
+
continue;
|
|
1770
|
+
}
|
|
1771
|
+
const open = calls.get(handle);
|
|
1772
|
+
if (open === undefined)
|
|
1773
|
+
continue;
|
|
1774
|
+
const failed = call.fact.outcome === "failure";
|
|
1775
|
+
open.cell.label = callCellLabel(open.started, failed);
|
|
1776
|
+
open.cell.tone = failed ? "fail" : "ok";
|
|
1777
|
+
const result = failed ? cellText(call.fact.message) : cellText(call.fact.value);
|
|
1778
|
+
if (result !== undefined)
|
|
1779
|
+
open.cell.output = result;
|
|
1780
|
+
}
|
|
1781
|
+
return transcripts;
|
|
1782
|
+
};
|
|
1783
|
+
/** The dispatches a settled engine node drove, as `<execution id>:<step key digest>`. */
|
|
1784
|
+
const nodeDispatches = (row) => {
|
|
1785
|
+
if (!row.id.startsWith("engine-node:"))
|
|
1786
|
+
return [];
|
|
1787
|
+
const fields = asRecord(row.detail.fields);
|
|
1788
|
+
const execution = asString(fields.executionId);
|
|
1789
|
+
const digests = asRecord(fields.payload).stepKeyDigests;
|
|
1790
|
+
if (execution === undefined || !Array.isArray(digests))
|
|
1791
|
+
return [];
|
|
1792
|
+
return [
|
|
1793
|
+
...new Set(digests.filter((digest) => typeof digest === "string" && hexDigest.test(digest)))
|
|
1794
|
+
]
|
|
1795
|
+
.map((digest) => `${execution}:${digest}`);
|
|
1796
|
+
};
|
|
1797
|
+
/** Plan node kinds that only shape the plan (@smthrs/plan); a child flow's own nodes are its steps. */
|
|
1798
|
+
const PLAN_STRUCTURE = new Set([
|
|
1799
|
+
"Succeed",
|
|
1800
|
+
"Fail",
|
|
1801
|
+
"All",
|
|
1802
|
+
"Race",
|
|
1803
|
+
"Map",
|
|
1804
|
+
"AndThen",
|
|
1805
|
+
"Branch",
|
|
1806
|
+
"Catch",
|
|
1807
|
+
"FlowCall",
|
|
1808
|
+
"FunctionIdentity",
|
|
1809
|
+
"PlannedReference"
|
|
1810
|
+
]);
|
|
1811
|
+
/** A check receipt's leading scalar fields, read from a preview the engine
|
|
1812
|
+
* may have cut short (its evidence comes last): `{"checkId":…,"status":…`. */
|
|
1813
|
+
const RECEIPT_HEAD = /^\{"checkId":"[^"\\]*"(?:,"[A-Za-z]+":(?:"[^"\\]*"|-?\d+))*,"status":"(passed|failed|superseded)"/;
|
|
1814
|
+
/** How many checks a step's recorded output reports failed: a check receipt
|
|
1815
|
+
* (`{checkId, status, …}`, possibly a cut-short preview) or a list of them;
|
|
1816
|
+
* zero for any other output. */
|
|
1817
|
+
const checkFailures = (output) => {
|
|
1818
|
+
if (output === undefined)
|
|
1819
|
+
return 0;
|
|
1820
|
+
const head = RECEIPT_HEAD.exec(output);
|
|
1821
|
+
if (head !== null)
|
|
1822
|
+
return head[1] === "failed" ? 1 : 0;
|
|
1823
|
+
let value;
|
|
1824
|
+
try {
|
|
1825
|
+
value = JSON.parse(output);
|
|
1826
|
+
}
|
|
1827
|
+
catch {
|
|
1828
|
+
return 0;
|
|
1829
|
+
}
|
|
1830
|
+
return (Array.isArray(value) ? value : []).filter((receipt) => {
|
|
1831
|
+
const record = asRecord(receipt);
|
|
1832
|
+
return typeof record.checkId === "string" && record.status === "failed";
|
|
1833
|
+
}).length;
|
|
1834
|
+
};
|
|
1835
|
+
/** Each recorded engine node's kind and action, by its trace span id (without instance). */
|
|
1836
|
+
const engineNodeShapes = (journal) => {
|
|
1837
|
+
const shapes = new Map();
|
|
1838
|
+
for (const record of journal) {
|
|
1839
|
+
if (record.kind !== "control.engine.event")
|
|
1840
|
+
continue;
|
|
1841
|
+
const envelope = asRecord(record.payload);
|
|
1842
|
+
const executionId = asString(envelope.executionId);
|
|
1843
|
+
const generation = asNumber(envelope.generation);
|
|
1844
|
+
const payload = asRecord(envelope.payload);
|
|
1845
|
+
if (executionId === undefined || generation === undefined)
|
|
1846
|
+
continue;
|
|
1847
|
+
const prefix = `engine-node:${encodeURIComponent(`${encodeURIComponent(executionId)}:${generation}`)}:`;
|
|
1848
|
+
if (envelope.eventType === "flows.engine.plan-recorded" || envelope.eventType === "flows.engine.subgraph-appended") {
|
|
1849
|
+
const nodes = asRecord(payload.graph).nodes;
|
|
1850
|
+
for (const node of Array.isArray(nodes) ? nodes : []) {
|
|
1851
|
+
const shape = asRecord(node);
|
|
1852
|
+
const id = asString(shape.id);
|
|
1853
|
+
if (id !== undefined) {
|
|
1854
|
+
shapes.set(prefix + encodeURIComponent(id), { kind: asString(shape.kind), action: asString(shape.action) });
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
else if (envelope.eventType === "flows.engine.node-scheduled") {
|
|
1859
|
+
const id = asString(payload.nodeId);
|
|
1860
|
+
if (id === undefined)
|
|
1861
|
+
continue;
|
|
1862
|
+
const known = shapes.get(prefix + encodeURIComponent(id));
|
|
1863
|
+
shapes.set(prefix + encodeURIComponent(id), {
|
|
1864
|
+
kind: asString(payload.kind) ?? known?.kind,
|
|
1865
|
+
action: asString(payload.action) ?? known?.action
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
return shapes;
|
|
1870
|
+
};
|
|
1871
|
+
/** Adapt the canonical trace to the install monitor. IDs and timestamps come
|
|
1872
|
+
* from recorded spans; inspection never evaluates flow bodies or presentations.
|
|
1873
|
+
* @since 1.0.0
|
|
1874
|
+
* @category constructors
|
|
1875
|
+
*/
|
|
1876
|
+
export const monitorFromJournal = (run, records, at) => {
|
|
1877
|
+
const last = records.reduce((last, row) => Math.max(last, row.sequence ?? 0), 0);
|
|
1878
|
+
const journal = at === undefined ? records : records.filter((row) => (row.sequence ?? 0) <= at);
|
|
1879
|
+
// A historical frame must not inherit the current run's terminal status.
|
|
1880
|
+
const lifecycleRow = [...journal].reverse().find((row) => /^control\.run\.(running|waiting-approval|paused|suspended|held|completed|failed|cancelled|interrupted)$/.test(row.kind ?? "") &&
|
|
1881
|
+
(row.runId === undefined || row.runId === run.runId));
|
|
1882
|
+
const status = at === undefined ? run.status : lifecycleRow?.kind?.split(".").at(-1) ?? "running";
|
|
1883
|
+
const state = (value) => value === "completed" || value === "done" ? "done" : value === "failed" ?
|
|
1884
|
+
"failed"
|
|
1885
|
+
: value === "cancelled" || value === "interrupted" ?
|
|
1886
|
+
"interrupted" :
|
|
1887
|
+
value === "held" ?
|
|
1888
|
+
"held"
|
|
1889
|
+
: value === "waiting" || value === "waiting-approval" || value === "paused" || value === "suspended"
|
|
1890
|
+
? "waiting"
|
|
1891
|
+
: "running";
|
|
1892
|
+
const trace = traceFromJournal({ ...run, status }, journal);
|
|
1893
|
+
const engineLabel = (label) => ACTION_RENDERINGS[label] === null || /^<(?:boundary:|quota-park>|structured-output-count>)/.test(label);
|
|
1894
|
+
const bookkeeping = (row) => engineLabel(row.label);
|
|
1895
|
+
const transcripts = stepTranscripts(journal);
|
|
1896
|
+
// Every call below is a member of trace.rows, so both lookups are total.
|
|
1897
|
+
const dispatchesOf = new Map(trace.rows.map((row) => [row.id, nodeDispatches(row)]));
|
|
1898
|
+
// A step is a node that dispatched an action. Plan structure (sequences,
|
|
1899
|
+
// branches, maps) and child-flow calls, whose own nodes are the steps, are
|
|
1900
|
+
// never steps of their own.
|
|
1901
|
+
const structural = (kind) => kind !== undefined && PLAN_STRUCTURE.has(kind);
|
|
1902
|
+
const nodeShapes = engineNodeShapes(journal);
|
|
1903
|
+
const calls = trace.rows.filter((row) => {
|
|
1904
|
+
// A tool call an agent made inside a step is a cell of that step's
|
|
1905
|
+
// transcript (step:<scope>/…), never a step of its own.
|
|
1906
|
+
if (row.kind !== "call" || bookkeeping(row) || row.id.startsWith("step:"))
|
|
1907
|
+
return false;
|
|
1908
|
+
const shape = nodeShapes.get(row.id.replace(/#\d+$/, ""));
|
|
1909
|
+
return shape === undefined || !structural(shape.kind);
|
|
1910
|
+
});
|
|
1911
|
+
const transcriptOf = (row) => {
|
|
1912
|
+
const parts = dispatchesOf.get(row.id).flatMap((dispatch) => {
|
|
1913
|
+
const found = transcripts.get(dispatch.slice(dispatch.lastIndexOf(":") + 1));
|
|
1914
|
+
return found === undefined ? [] : [found];
|
|
1915
|
+
});
|
|
1916
|
+
return {
|
|
1917
|
+
cells: parts.flatMap((part) => part.cells).sort((left, right) => Number(left.id.slice(5)) - Number(right.id.slice(5))),
|
|
1918
|
+
tokens: parts.reduce((total, part) => total + part.tokens, 0),
|
|
1919
|
+
models: parts.reduce((total, part) => total + part.models, 0),
|
|
1920
|
+
input: parts.find((part) => part.input !== undefined)?.input
|
|
1921
|
+
};
|
|
1922
|
+
};
|
|
1923
|
+
const steps = calls.map((row) => {
|
|
1924
|
+
const meter = dispatchesOf.get(row.id);
|
|
1925
|
+
const transcript = transcriptOf(row);
|
|
1926
|
+
return {
|
|
1927
|
+
...(() => {
|
|
1928
|
+
const matched = /^(engine-node:.*)#(\d+)$/.exec(row.id);
|
|
1929
|
+
const id = matched?.[1] ?? row.id;
|
|
1930
|
+
const k = Number(matched?.[2] ?? 1);
|
|
1931
|
+
return { key: `${id}#${k}`, id, k };
|
|
1932
|
+
})(),
|
|
1933
|
+
label: row.label,
|
|
1934
|
+
state: row.status,
|
|
1935
|
+
started_at: new Date(row.startedAt).toISOString(),
|
|
1936
|
+
...(row.endedAt === undefined
|
|
1937
|
+
? {}
|
|
1938
|
+
: { ended_at: new Date(row.endedAt).toISOString(), took_s: Math.max(0, row.endedAt - row.startedAt) / 1000 }),
|
|
1939
|
+
...(row.detail.input !== undefined
|
|
1940
|
+
? { input: row.detail.input }
|
|
1941
|
+
: transcript.input !== undefined
|
|
1942
|
+
? { input: transcript.input }
|
|
1943
|
+
: {}),
|
|
1944
|
+
...(row.detail.output === undefined ? {} : { output: row.detail.output }),
|
|
1945
|
+
// The install prices these dispatches from its metered proxy rows and
|
|
1946
|
+
// replaces them with `usage`; the journal's own token count rides along.
|
|
1947
|
+
...(meter.length === 0 ? {} : { meter }),
|
|
1948
|
+
...(transcript.models === 0 ? {} : { model_calls: transcript.models, tokens: transcript.tokens })
|
|
1949
|
+
};
|
|
1950
|
+
});
|
|
1951
|
+
const tone = (value) => value === "failed" ?
|
|
1952
|
+
"fail"
|
|
1953
|
+
: value === "completed"
|
|
1954
|
+
? "ok"
|
|
1955
|
+
: value === "waiting"
|
|
1956
|
+
? "wait"
|
|
1957
|
+
: "live";
|
|
1958
|
+
const phases = calls.map((row, index) => {
|
|
1959
|
+
const transcript = transcriptOf(row).cells;
|
|
1960
|
+
const failedChecks = checkFailures(row.detail.output);
|
|
1961
|
+
const cells = transcript.length > 0
|
|
1962
|
+
? transcript
|
|
1963
|
+
: [{
|
|
1964
|
+
id: `cell:${row.id}`,
|
|
1965
|
+
kind: /Edited/.test(row.label) ? "edit" : /Read/.test(row.label) ? "read" : "run",
|
|
1966
|
+
label: row.label,
|
|
1967
|
+
...(row.detail.output === undefined ? {} : { output: row.detail.output }),
|
|
1968
|
+
tone: tone(row.status)
|
|
1969
|
+
}];
|
|
1970
|
+
return {
|
|
1971
|
+
id: `phase:${row.id}`,
|
|
1972
|
+
step: steps[index].key,
|
|
1973
|
+
// The title is the Appendix C label plus the outcome its recorded
|
|
1974
|
+
// output reports: "Ran checks · 1 failed".
|
|
1975
|
+
title: failedChecks === 0 ? row.label : `${row.label} · ${failedChecks} failed`,
|
|
1976
|
+
took_s: Math.max(0, (row.endedAt ?? row.startedAt) - row.startedAt) / 1000,
|
|
1977
|
+
tone: failedChecks === 0 ? tone(row.status) : "fail",
|
|
1978
|
+
cells
|
|
1979
|
+
};
|
|
1980
|
+
});
|
|
1981
|
+
const declaredGraph = engineExecutionEvidence(journal).filter((execution) => execution.coherent).flatMap((execution) => {
|
|
1982
|
+
const prefix = `engine-node:${encodeURIComponent(`${encodeURIComponent(execution.executionId)}:${execution.generation}`)}:`;
|
|
1983
|
+
const nodes = execution.graph?.nodes ?? [];
|
|
1984
|
+
const hidden = new Map(nodes.filter((node) => engineLabel(node.action ?? node.id) || structural(node.kind))
|
|
1985
|
+
.map((node) => [node.id, node]));
|
|
1986
|
+
// Engine bookkeeping stays in the Engine row. Its dependency edges still
|
|
1987
|
+
// order the visible steps, including across several hidden boundaries.
|
|
1988
|
+
const visibleDeps = (id, seen) => {
|
|
1989
|
+
const node = hidden.get(id);
|
|
1990
|
+
if (node === undefined)
|
|
1991
|
+
return [prefix + encodeURIComponent(id)];
|
|
1992
|
+
if (seen.has(id))
|
|
1993
|
+
return [];
|
|
1994
|
+
return node.dependsOn.flatMap((dependency) => visibleDeps(dependency, new Set([...seen, id])));
|
|
1995
|
+
};
|
|
1996
|
+
return nodes.filter((node) => !hidden.has(node.id)).map((node) => {
|
|
1997
|
+
const id = prefix + encodeURIComponent(node.id);
|
|
1998
|
+
const instances = calls.filter((row) => row.id.startsWith(`${id}#`));
|
|
1999
|
+
const current = instances.at(-1);
|
|
2000
|
+
return {
|
|
2001
|
+
id,
|
|
2002
|
+
label: inspectLabel(node.action ?? node.id),
|
|
2003
|
+
// A branch not taken never ran: it stays next, even in a finished run.
|
|
2004
|
+
state: current === undefined || current.status === "skipped" ?
|
|
2005
|
+
"next" :
|
|
2006
|
+
current.status === "completed" ?
|
|
2007
|
+
"done"
|
|
2008
|
+
: current.status === "failed"
|
|
2009
|
+
? "failed"
|
|
2010
|
+
: current.status === "waiting"
|
|
2011
|
+
? "waiting"
|
|
2012
|
+
: "current",
|
|
2013
|
+
deps: [...new Set(node.dependsOn.flatMap((dependency) => visibleDeps(dependency, new Set([node.id]))))]
|
|
2014
|
+
};
|
|
2015
|
+
});
|
|
2016
|
+
});
|
|
2017
|
+
const usageOf = (row) => (row.detail.usage?.inputTokens ?? 0) + (row.detail.usage?.outputTokens ?? 0);
|
|
2018
|
+
const tokens = trace.rows.reduce((total, row) => total + usageOf(row), 0);
|
|
2019
|
+
// Model calls no native dispatch recorded (a legacy agent journal) can
|
|
2020
|
+
// never be joined to metered rows; the install refuses to price them.
|
|
2021
|
+
const unmetered = trace.rows.filter((row) => row.kind === "model" && asRecord(row.detail.fields).step === undefined)
|
|
2022
|
+
.reduce((total, row) => total + usageOf(row), 0);
|
|
2023
|
+
const approvalWaits = trace.rows.filter((row) => row.kind === "approval").map((row) => ({
|
|
2024
|
+
id: row.id,
|
|
2025
|
+
kind: "approval",
|
|
2026
|
+
label: row.label,
|
|
2027
|
+
since: new Date(row.startedAt).toISOString(),
|
|
2028
|
+
...(row.endedAt === undefined
|
|
2029
|
+
? {}
|
|
2030
|
+
: {
|
|
2031
|
+
settled: { by: { kind: "system", color_index: 7 }, at: new Date(row.endedAt).toISOString() }
|
|
2032
|
+
})
|
|
2033
|
+
}));
|
|
2034
|
+
// The machine records its declared custom view as data. Only the shipped
|
|
2035
|
+
// text renderer is supported; module paths, HTML and executable data never
|
|
2036
|
+
// cross this seam. Replay selects the last declaration in its own prefix.
|
|
2037
|
+
const custom = [...journal].reverse().find((row) => (row.runId === undefined || row.runId === run.runId) &&
|
|
2038
|
+
row.kind === "control.engine.event" && asRecord(row.payload).eventType === "flows.run.presentation");
|
|
2039
|
+
const presentation = asRecord(asRecord(custom?.payload).payload);
|
|
2040
|
+
const text = asString(presentation.text);
|
|
2041
|
+
const customView = presentation.kind === "text" && text !== undefined && text.length <= 65536 &&
|
|
2042
|
+
Object.keys(presentation).every((key) => key === "kind" || key === "text")
|
|
2043
|
+
? { presentation: { kind: "text", text } } :
|
|
2044
|
+
{};
|
|
2045
|
+
return {
|
|
2046
|
+
...customView,
|
|
2047
|
+
id: run.runId,
|
|
2048
|
+
flow: run.flowId,
|
|
2049
|
+
version: "",
|
|
2050
|
+
title: run.flowId,
|
|
2051
|
+
state: state(status),
|
|
2052
|
+
attempts: [{
|
|
2053
|
+
n: 1,
|
|
2054
|
+
run_id: run.runId,
|
|
2055
|
+
state: state(status),
|
|
2056
|
+
steps,
|
|
2057
|
+
phases,
|
|
2058
|
+
graph: declaredGraph.length > 0 ?
|
|
2059
|
+
declaredGraph :
|
|
2060
|
+
calls.map((row) => ({
|
|
2061
|
+
id: row.id,
|
|
2062
|
+
label: row.label,
|
|
2063
|
+
state: row.status === "skipped" ? "next" : row.status === "completed" ?
|
|
2064
|
+
"done"
|
|
2065
|
+
: row.status === "failed"
|
|
2066
|
+
? "failed"
|
|
2067
|
+
: row.status === "waiting"
|
|
2068
|
+
? "waiting"
|
|
2069
|
+
: "current",
|
|
2070
|
+
deps: []
|
|
2071
|
+
}))
|
|
2072
|
+
}],
|
|
2073
|
+
waits: [
|
|
2074
|
+
...approvalWaits,
|
|
2075
|
+
...engineExecutionEvidence(journal).filter((execution) => execution.coherent).flatMap((execution) => execution.waits.map((wait) => ({
|
|
2076
|
+
id: wait.id,
|
|
2077
|
+
kind: wait.kind,
|
|
2078
|
+
label: inspectLabel("wait"),
|
|
2079
|
+
since: new Date(wait.since).toISOString(),
|
|
2080
|
+
...(wait.settledAt === undefined
|
|
2081
|
+
? {}
|
|
2082
|
+
: {
|
|
2083
|
+
settled: {
|
|
2084
|
+
by: { kind: "system", color_index: 7 },
|
|
2085
|
+
at: new Date(wait.settledAt).toISOString()
|
|
2086
|
+
}
|
|
2087
|
+
})
|
|
2088
|
+
})))
|
|
2089
|
+
],
|
|
2090
|
+
tokens,
|
|
2091
|
+
// Spend is priced where model calls are metered. The host never publishes
|
|
2092
|
+
// an unknown total as zero: a run with model tokens leaves cost to the install.
|
|
2093
|
+
time_s: Math.max(0, trace.extent.end - trace.extent.start) / 1000,
|
|
2094
|
+
...(tokens === 0 ? { cost_usd: 0 } : {}),
|
|
2095
|
+
...(unmetered === 0 ? {} : { unmetered_tokens: unmetered }),
|
|
2096
|
+
engine: trace.rows.filter((row) => row.kind === "event" || row.kind === "execution" || row.kind === "attempt" || bookkeeping(row)).map((row) => ({ label: row.label, detail: row.detail.output ?? row.detail.message ?? "" })),
|
|
2097
|
+
journal: journal.map((row) => ({
|
|
2098
|
+
seq: row.sequence ?? 0,
|
|
2099
|
+
at: new Date(row.occurredAt ?? 0).toISOString(),
|
|
2100
|
+
type: row.kind ?? "event",
|
|
2101
|
+
text: JSON.stringify(row.payload) ?? "null"
|
|
2102
|
+
})),
|
|
2103
|
+
...(at === undefined ? {} : { replay: { at: Math.min(at, last), last } })
|
|
2104
|
+
};
|
|
2105
|
+
};
|
|
2106
|
+
//# sourceMappingURL=RunTrace.js.map
|