@@ -23,12 +23,12 @@ import {
2323 serializeAgentDefinitionWorkflow ,
2424 withAgentToolPackagePin ,
2525 readPinnedSkillNames ,
26- reindexPinnedSkills ,
2726} from "../src/agent-workflow" ;
2827import {
2928 agentDefinitionSourceTree ,
3029 AGENT_DEFINITION_ENTRY_PATH ,
3130 readAgentDefinitionWorkflowJson ,
31+ RetiredWorkflowEnvelopeError ,
3232} from "../src/definition-asset" ;
3333import { createAgentDefinitionRoutes } from "../src/routes" ;
3434import type { PinnedSkillIndexResolver } from "../src/routes" ;
@@ -38,7 +38,11 @@ import {
3838} from "../src/workflow-skill-pin-routes" ;
3939import type { DefinitionAssetHistory } from "../src/definition-history" ;
4040import type { CapabilityInventoryProvider } from "../src/capability-inventory" ;
41- import { definitionFrom , SOURCE_TREE_PATHS } from "./source-tree" ;
41+ import {
42+ definitionFrom ,
43+ SOURCE_TREE_PATHS ,
44+ storedDefinitionBytesWithSkills ,
45+ } from "./source-tree" ;
4246
4347/** A `readAssetBlob` that always answers the definition's entry module
4448 * with `workflowBytes` — pins live in the asset's own stanza, so a test
@@ -213,27 +217,6 @@ function storedDefinitionBytesWithModel(model: string): Uint8Array {
213217 return new TextEncoder ( ) . encode ( tree [ AGENT_DEFINITION_ENTRY_PATH ] ) ;
214218}
215219
216- /** A stored definition that already pins skills — the state every
217- * pin-reading route observes. The stanza is the seed: no side table to
218- * write, the bytes carry the pins like a real asset would. */
219- function storedDefinitionBytesWithSkills ( ...names : string [ ] ) : Uint8Array {
220- const tree = agentDefinitionSourceTree ( {
221- handle : "research-buddy" ,
222- workflowJson : reindexPinnedSkills (
223- serializeAgentDefinitionWorkflow (
224- buildAgentDefinitionWorkflow ( {
225- handle : "research-buddy" ,
226- tenantDomain : TENANT . domain ,
227- description : "" ,
228- systemPrompt : "You are a careful research assistant." ,
229- } ) ,
230- ) ,
231- names . map ( ( name ) => ( { name, description : `What ${ name } does.` } ) ) ,
232- ) ,
233- } ) ;
234- return new TextEncoder ( ) . encode ( tree [ AGENT_DEFINITION_ENTRY_PATH ] ) ;
235- }
236-
237220/** The model the one step agent resolves against, or `undefined` when it
238221 * pins none. */
239222function modelFrom ( workflowJson : string ) : string | undefined {
@@ -763,6 +746,49 @@ test("GET /skills omits unknown definition ids from the map rather than erroring
763746 expect ( body . skills ) . toEqual ( { } ) ;
764747} ) ;
765748
749+ test ( "GET /skills serves the healthy ids when one asset is on the retired envelope" , async ( ) => {
750+ // The route issues one `findFirst` per requested id, in request order
751+ // (each `map` callback runs synchronously to its first await), so the
752+ // fake answers each call from this queue — drizzle's `where`
753+ // expression tree isn't inspectable without a real query builder.
754+ const rows = [
755+ { id : "def_healthy" , assetId : "ast_healthy" } ,
756+ { id : "def_retired" , assetId : "ast_retired" } ,
757+ ] ;
758+ const db = {
759+ query : {
760+ workflowDefinition : {
761+ findFirst : async ( ) => {
762+ const row = rows . shift ( ) ;
763+ return row === undefined
764+ ? undefined
765+ : {
766+ id : row . id ,
767+ tenantId : TENANT . id ,
768+ assetId : row . assetId ,
769+ name : "Research Buddy" ,
770+ } ;
771+ } ,
772+ } ,
773+ } ,
774+ } as unknown as DB [ "db" ] ;
775+ const app = buildApp (
776+ fakeAssetService ( {
777+ readAssetBlob : ( params ) =>
778+ params . assetId === "ast_healthy"
779+ ? Promise . resolve ( storedDefinitionBytesWithSkills ( "web-research" ) )
780+ : Promise . reject ( new RetiredWorkflowEnvelopeError ( params . assetId ) ) ,
781+ } ) ,
782+ db ,
783+ ) ;
784+ const response = await app . request ( "/skills?ids=def_healthy,def_retired" ) ;
785+ expect ( response . status ) . toBe ( 200 ) ;
786+ const body = ( await response . json ( ) ) as {
787+ skills : Record < string , readonly string [ ] > ;
788+ } ;
789+ expect ( body . skills ) . toEqual ( { def_healthy : [ "web-research" ] } ) ;
790+ } ) ;
791+
766792test ( "PUT /:definitionId/skills replaces the skill set, writing the definition source tree to the asset" , async ( ) => {
767793 let writtenFiles : Record < string , string | Uint8Array > | undefined ;
768794 const app = buildApp (
0 commit comments