@@ -19,16 +19,42 @@ import { AgentRuntime } from '../agent-runtime.js';
1919import { SkillRegistry } from '../skill-registry.js' ;
2020import type { AgentChatContext } from '../agent-runtime.js' ;
2121import { buildAgentRoutes } from '../routes/agent-routes.js' ;
22- import { ASK_AGENT } from '../agents/ask-agent.js' ;
2322import { registerAgentAlias } from '../agents/agent-aliases.js' ;
23+ import type { Agent } from '@objectstack/spec/ai' ;
24+
25+ // BOTH platform PERSONAS moved to the cloud-only @objectstack/service-ai-studio
26+ // package (the `ask` data product + the `build` authoring agent are commercial
27+ // features). The open-source AI runtime is headless. These local stubs stand in
28+ // as the two PLATFORM agents for the runtime/route agent-listing tests below;
29+ // they satisfy AgentSchema and carry just enough persona text for the generic
30+ // `buildSystemMessages` assertions. The persona-as-subject specs (skill bundles,
31+ // instruction wording, surface affinity) live with the agents in the cloud
32+ // package now (see metadata-assistant-agent.test.ts / ask-agent.test.ts there).
33+ //
34+ // The `ask` stub's instructions intentionally include the "assistant for this
35+ // business application platform" / "do NOT build" / "Builder" phrases the
36+ // runtime tests assert on, so those tests still exercise buildSystemMessages
37+ // without depending on the moved persona.
38+ const ASK_AGENT : Agent = {
39+ name : 'ask' ,
40+ label : 'Assistant' ,
41+ role : 'Business Application Assistant' ,
42+ surface : 'ask' ,
43+ instructions :
44+ 'You are the assistant for this business application platform. You help the ' +
45+ 'user explore their data. You do NOT build or change the application itself; ' +
46+ 'app-building lives in the Builder (the separate "build" experience).' ,
47+ model : { provider : 'openai' , model : 'gpt-4' , temperature : 0.2 , maxTokens : 4096 } ,
48+ skills : [ 'schema_reader' , 'data_explorer' , 'actions_executor' ] ,
49+ active : true ,
50+ visibility : 'global' ,
51+ guardrails : { maxTokensPerInvocation : 8192 , maxExecutionTimeSec : 30 , blockedTopics : [ 'raw_sql' ] } ,
52+ planning : { strategy : 'react' , maxIterations : 10 , allowReplan : true } ,
53+ } as any ;
2454
25- // The real `build` agent moved to the cloud-only @objectstack/service-ai-studio
26- // package (AI authoring is a commercial feature). This local stub stands in as
27- // the second PLATFORM agent for the runtime/route agent-listing tests below. It
28- // derives from the `ask` agent so it satisfies AgentSchema, then overrides
29- // identity fields. Registering the `metadata_assistant`→`build` alias (as the
30- // cloud plugin does at init) makes `build` a recognised platform agent so the
31- // runtime catalog surfaces it (ADR-0063 §2).
55+ // Registering the `metadata_assistant`→`build` alias (as the cloud plugin does
56+ // at init) makes `build` a recognised platform agent so the runtime catalog
57+ // surfaces it (ADR-0063 §2).
3258registerAgentAlias ( 'metadata_assistant' , 'build' ) ;
3359const BUILD_AGENT = {
3460 ...ASK_AGENT ,
@@ -639,12 +665,6 @@ describe('AgentRuntime', () => {
639665 expect ( messages [ 0 ] . content ) . not . toContain ( 'Capabilities in this deployment' ) ;
640666 } ) ;
641667
642- it ( 'the ask persona itself declines app-building and points at the Builder' , ( ) => {
643- // Affinity is carried by the persona, not a runtime gate.
644- expect ( ASK_AGENT . instructions ) . toContain ( 'do NOT build' ) ;
645- expect ( ASK_AGENT . instructions . toLowerCase ( ) ) . toContain ( 'builder' ) ;
646- expect ( ASK_AGENT . surface ) . toBe ( 'ask' ) ;
647- } ) ;
648668 } ) ;
649669
650670 describe ( 'buildContextSchemaMessages' , ( ) => {
@@ -694,11 +714,19 @@ describe('AgentRuntime', () => {
694714 { name : 'unrelated_tool' , description : 'Not in any skill' , parameters : { } } ,
695715 ] ;
696716
697- // ASK_AGENT's tool set is the union of its skills' claimed tools:
698- // schema_reader owns list_objects, data_explorer owns query_records.
699- const { DATA_EXPLORER_SKILL } = await import ( '../skills/data-explorer-skill.js' ) ;
717+ // Mechanism test: the resolved tool set is the union of the active skills'
718+ // claimed tools. `schema_reader` stays open in this package; `data_explorer`
719+ // moved to the cloud studio package, so we stub it inline here — the runtime
720+ // resolver doesn't care WHERE a skill is defined, only what tools it claims.
700721 const { SCHEMA_READER_SKILL } = await import ( '../skills/schema-reader-skill.js' ) ;
701- const options = runtime . buildRequestOptions ( ASK_AGENT , availableTools , [ SCHEMA_READER_SKILL , DATA_EXPLORER_SKILL ] ) ;
722+ const dataExplorerStub = {
723+ name : 'data_explorer' ,
724+ label : 'Data Explorer' ,
725+ surface : 'ask' ,
726+ tools : [ 'query_records' , 'get_record' , 'aggregate_data' , 'visualize_data' ] ,
727+ active : true ,
728+ } as never ;
729+ const options = runtime . buildRequestOptions ( ASK_AGENT , availableTools , [ SCHEMA_READER_SKILL , dataExplorerStub ] ) ;
702730
703731 const resolvedNames = options . tools ?. map ( t => t . name ) ?? [ ] ;
704732 expect ( resolvedNames ) . toContain ( 'list_objects' ) ;
@@ -1085,133 +1113,9 @@ describe('Agent Routes', () => {
10851113 } ) ;
10861114} ) ;
10871115
1088- // ═══════════════════════════════════════════════════════════════════
1089- // Data Chat Agent Spec
1090- // ═══════════════════════════════════════════════════════════════════
1091-
1092- describe ( 'ASK_AGENT' , ( ) => {
1093- it ( 'should be a valid agent definition' , ( ) => {
1094- // Path A rename: canonical id is now `ask` (was `data_chat`).
1095- expect ( ASK_AGENT . name ) . toBe ( 'ask' ) ;
1096- expect ( ASK_AGENT . role ) . toBe ( 'Business Application Assistant' ) ;
1097- expect ( ASK_AGENT . active ) . toBe ( true ) ;
1098- expect ( ASK_AGENT . visibility ) . toBe ( 'global' ) ;
1099- } ) ;
1100-
1101- it ( 'should bind only ask-surface skills — no authoring skills (ADR-0063)' , ( ) => {
1102- expect ( ASK_AGENT . tools ?? [ ] ) . toHaveLength ( 0 ) ;
1103- // schema_reader (both) + data_explorer/actions_executor (ask). The build
1104- // skills (metadata_authoring/solution_design) are NOT here — they live on
1105- // the cloud `build` agent.
1106- expect ( ASK_AGENT . skills ) . toEqual ( [ 'schema_reader' , 'data_explorer' , 'actions_executor' ] ) ;
1107- expect ( ASK_AGENT . skills ) . not . toContain ( 'metadata_authoring' ) ;
1108- expect ( ASK_AGENT . skills ) . not . toContain ( 'solution_design' ) ;
1109- } ) ;
1110-
1111- it ( 'declares surface "ask"' , ( ) => {
1112- expect ( ASK_AGENT . surface ) . toBe ( 'ask' ) ;
1113- } ) ;
1114-
1115- it ( 'should have guardrails configured' , ( ) => {
1116- expect ( ASK_AGENT . guardrails ) . toBeDefined ( ) ;
1117- expect ( ASK_AGENT . guardrails ! . maxTokensPerInvocation ) . toBeGreaterThan ( 0 ) ;
1118- expect ( ASK_AGENT . guardrails ! . blockedTopics ) . toBeDefined ( ) ;
1119- } ) ;
1120-
1121- it ( 'should have model config' , ( ) => {
1122- expect ( ASK_AGENT . model ) . toBeDefined ( ) ;
1123- expect ( ASK_AGENT . model ! . temperature ) . toBeLessThanOrEqual ( 0.5 ) ; // low temp for data queries
1124- } ) ;
1125- } ) ;
1126-
1127- // ═══════════════════════════════════════════════════════════════════
1128- // ADR-0063 §3 / ADR-0064 — surface affinity & tool scoping
1129- // ═══════════════════════════════════════════════════════════════════
1130-
1131- describe ( 'surface affinity & tool scoping (ADR-0063/0064)' , ( ) => {
1132- it ( 'tools(ask) excludes every authoring tool — ask cannot author by construction' , async ( ) => {
1133- const { SCHEMA_READER_SKILL } = await import ( '../skills/schema-reader-skill.js' ) ;
1134- const { DATA_EXPLORER_SKILL } = await import ( '../skills/data-explorer-skill.js' ) ;
1135- const { ACTIONS_EXECUTOR_SKILL } = await import ( '../skills/actions-executor-skill.js' ) ;
1136- const askSkills = [ SCHEMA_READER_SKILL , DATA_EXPLORER_SKILL , ACTIONS_EXECUTOR_SKILL ] ;
1137-
1138- const availableTools : AIToolDefinition [ ] = [
1139- // shared reads + ask tools
1140- { name : 'query_data' , description : '' , parameters : { } } ,
1141- { name : 'describe_object' , description : '' , parameters : { } } ,
1142- { name : 'list_objects' , description : '' , parameters : { } } ,
1143- { name : 'query_records' , description : '' , parameters : { } } ,
1144- { name : 'visualize_data' , description : '' , parameters : { } } ,
1145- { name : 'action_complete_task' , description : '' , parameters : { } } ,
1146- // authoring tools that MUST NOT leak into the ask agent
1147- { name : 'create_metadata' , description : '' , parameters : { } } ,
1148- { name : 'update_metadata' , description : '' , parameters : { } } ,
1149- { name : 'add_field' , description : '' , parameters : { } } ,
1150- { name : 'apply_blueprint' , description : '' , parameters : { } } ,
1151- ] ;
1152-
1153- const registry = new SkillRegistry ( createMockMetadataService ( ) ) ;
1154- const tools = registry . flattenToTools ( askSkills , availableTools ) . map ( ( t ) => t . name ) ;
1155-
1156- // shared/ask tools present
1157- expect ( tools ) . toContain ( 'describe_object' ) ;
1158- expect ( tools ) . toContain ( 'query_data' ) ;
1159- expect ( tools ) . toContain ( 'query_records' ) ;
1160- expect ( tools ) . toContain ( 'action_complete_task' ) ;
1161- // no create_* / *_metadata / blueprint tools (issue acceptance criterion)
1162- for ( const t of [ 'create_metadata' , 'update_metadata' , 'add_field' , 'apply_blueprint' ] ) {
1163- expect ( tools ) . not . toContain ( t ) ;
1164- }
1165- expect (
1166- tools . some ( ( n ) => n . startsWith ( 'create_' ) || / _ m e t a d a t a $ / . test ( n ) || n . includes ( 'blueprint' ) ) ,
1167- ) . toBe ( false ) ;
1168- } ) ;
1169-
1170- it ( 'binding a surface:build skill to the ask agent is a fast load error (ADR-0064 §3)' , async ( ) => {
1171- const md = createMockMetadataService ( {
1172- list : vi . fn ( async ( type : string ) =>
1173- type === 'skill'
1174- ? [
1175- {
1176- name : 'metadata_authoring' ,
1177- label : 'Metadata Authoring' ,
1178- surface : 'build' ,
1179- tools : [ 'create_metadata' ] ,
1180- active : true ,
1181- } ,
1182- ]
1183- : [ ] ,
1184- ) as any ,
1185- } ) ;
1186- const runtime = new AgentRuntime ( md , new SkillRegistry ( md ) ) ;
1187- const askAgent = { ...ASK_AGENT , skills : [ 'metadata_authoring' ] } as any ;
1188- await expect ( runtime . resolveActiveSkills ( askAgent ) ) . rejects . toThrow (
1189- / i n c o m p a t i b l e a f f i n i t y | c a n n o t b i n d / ,
1190- ) ;
1191- } ) ;
1192-
1193- it ( 'a surface:both skill binds to the ask agent without error' , async ( ) => {
1194- const md = createMockMetadataService ( {
1195- list : vi . fn ( async ( type : string ) =>
1196- type === 'skill'
1197- ? [
1198- {
1199- name : 'schema_reader' ,
1200- label : 'Schema Reader' ,
1201- surface : 'both' ,
1202- tools : [ 'describe_object' ] ,
1203- active : true ,
1204- } ,
1205- ]
1206- : [ ] ,
1207- ) as any ,
1208- } ) ;
1209- const runtime = new AgentRuntime ( md , new SkillRegistry ( md ) ) ;
1210- const askAgent = { ...ASK_AGENT , skills : [ 'schema_reader' ] } as any ;
1211- const skills = await runtime . resolveActiveSkills ( askAgent ) ;
1212- expect ( skills . map ( ( s ) => s . name ) ) . toContain ( 'schema_reader' ) ;
1213- } ) ;
1214- } ) ;
1215-
1216- // NOTE: the BUILD_AGENT spec tests moved with the agent to the
1217- // cloud-only @objectstack/service-ai-studio package (metadata-assistant-agent.test.ts).
1116+ // NOTE: the persona-as-subject specs — the `ASK_AGENT` definition spec, the
1117+ // `BUILD_AGENT` spec, and the ADR-0063/0064 surface-affinity & tool-scoping
1118+ // tests — moved WITH the agents/skills to the cloud-only
1119+ // @objectstack /service-ai-studio package (see ask-agent.test.ts /
1120+ // metadata-assistant-agent.test.ts there). What remains here is the generic AI
1121+ // runtime / route mechanism, exercised against local persona STUBS.
0 commit comments