@@ -69,6 +69,76 @@ function isSnakeCase(value: string): boolean {
6969 return SNAKE_CASE_RE . test ( value ) ;
7070}
7171
72+ // ---------------------------------------------------------------------------
73+ // Package Resolution Helpers
74+ // ---------------------------------------------------------------------------
75+
76+ /**
77+ * Retrieves the active package ID from the conversation context.
78+ * Returns null if no conversation service is available or no active package is set.
79+ */
80+ async function getActivePackageId ( ctx : MetadataToolContext ) : Promise < string | null > {
81+ if ( ! ctx . conversationService ?. getMetadata || ! ctx . conversationId ) {
82+ return null ;
83+ }
84+
85+ const metadata = await ctx . conversationService . getMetadata ( ctx . conversationId ) ;
86+ return ( metadata ?. activePackageId as string ) ?? null ;
87+ }
88+
89+ /**
90+ * Resolves the package ID to use for a metadata operation.
91+ * Priority: explicit packageId > active package from conversation > error
92+ *
93+ * Also validates that the package exists and checks if it's read-only.
94+ *
95+ * @returns Object with packageId or error message
96+ */
97+ async function resolvePackageId (
98+ ctx : MetadataToolContext ,
99+ explicitPackageId ?: string ,
100+ ) : Promise < { packageId : string | null ; error ?: string ; warning ?: string } > {
101+ let packageId : string | null = null ;
102+
103+ // 1. Try explicit packageId parameter
104+ if ( explicitPackageId ) {
105+ packageId = explicitPackageId ;
106+ } else {
107+ // 2. Try active package from conversation
108+ packageId = await getActivePackageId ( ctx ) ;
109+ }
110+
111+ // If no package ID could be resolved, return error
112+ if ( ! packageId ) {
113+ return {
114+ packageId : null ,
115+ error : 'No package specified. Either provide packageId parameter or set an active package using set_active_package.' ,
116+ } ;
117+ }
118+
119+ // Validate package exists (if registry is available)
120+ if ( ctx . packageRegistry ) {
121+ const exists = await ctx . packageRegistry . exists ( packageId ) ;
122+ if ( ! exists ) {
123+ return {
124+ packageId : null ,
125+ error : `Package "${ packageId } " not found. Use list_packages to see available packages or create_package to create a new one.` ,
126+ } ;
127+ }
128+
129+ // Check if package is read-only (code-based)
130+ const pkg = await ctx . packageRegistry . get ( packageId ) ;
131+ if ( pkg ?. manifest . source === 'filesystem' ) {
132+ return {
133+ packageId : null ,
134+ error : `Package "${ packageId } " is read-only (loaded from code). Only database packages can be modified. Use create_package to create a new database package.` ,
135+ } ;
136+ }
137+ }
138+
139+ return { packageId } ;
140+ }
141+
72142// ---------------------------------------------------------------------------
73143// Context — injected once at registration time
74144// ---------------------------------------------------------------------------
@@ -104,9 +174,10 @@ export interface MetadataToolContext {
104174
105175function createCreateObjectHandler ( ctx : MetadataToolContext ) : ToolHandler {
106176 return async ( args ) => {
107- const { name, label, fields, enableFeatures } = args as {
177+ const { name, label, packageId : explicitPackageId , fields, enableFeatures } = args as {
108178 name : string ;
109179 label : string ;
180+ packageId ?: string ;
110181 fields ?: Array < { name : string ; label ?: string ; type : string ; required ?: boolean } > ;
111182 enableFeatures ?: Record < string , boolean > ;
112183 } ;
@@ -115,6 +186,13 @@ function createCreateObjectHandler(ctx: MetadataToolContext): ToolHandler {
115186 return JSON . stringify ( { error : 'Both "name" and "label" are required' } ) ;
116187 }
117188
189+ // Resolve package ID
190+ const resolved = await resolvePackageId ( ctx , explicitPackageId ) ;
191+ if ( resolved . error ) {
192+ return JSON . stringify ( { error : resolved . error } ) ;
193+ }
194+ const packageId = resolved . packageId ! ;
195+
118196 // Validate snake_case name
119197 if ( ! isSnakeCase ( name ) ) {
120198 return JSON . stringify ( { error : `Invalid object name "${ name } ". Must be snake_case.` } ) ;
@@ -152,6 +230,7 @@ function createCreateObjectHandler(ctx: MetadataToolContext): ToolHandler {
152230 const objectDef : Record < string , unknown > = {
153231 name,
154232 label,
233+ packageId,
155234 ...( Object . keys ( fieldMap ) . length > 0 ? { fields : fieldMap } : { } ) ,
156235 ...( enableFeatures ? { enable : enableFeatures } : { } ) ,
157236 } ;
@@ -161,14 +240,15 @@ function createCreateObjectHandler(ctx: MetadataToolContext): ToolHandler {
161240 return JSON . stringify ( {
162241 name,
163242 label,
243+ packageId,
164244 fieldCount : Object . keys ( fieldMap ) . length ,
165245 } ) ;
166246 } ;
167247}
168248
169249function createAddFieldHandler ( ctx : MetadataToolContext ) : ToolHandler {
170250 return async ( args ) => {
171- const { objectName, name, label, type, required, defaultValue, options, reference } = args as {
251+ const { objectName, name, label, type, required, defaultValue, options, reference, packageId : explicitPackageId } = args as {
172252 objectName : string ;
173253 name : string ;
174254 label ?: string ;
@@ -177,12 +257,19 @@ function createAddFieldHandler(ctx: MetadataToolContext): ToolHandler {
177257 defaultValue ?: unknown ;
178258 options ?: Array < { label : string ; value : string } > ;
179259 reference ?: string ;
260+ packageId ?: string ;
180261 } ;
181262
182263 if ( ! objectName || ! name || ! type ) {
183264 return JSON . stringify ( { error : '"objectName", "name", and "type" are required' } ) ;
184265 }
185266
267+ // Resolve package ID (for validation and tracking)
268+ const resolved = await resolvePackageId ( ctx , explicitPackageId ) ;
269+ if ( resolved . error ) {
270+ return JSON . stringify ( { error : resolved . error } ) ;
271+ }
272+
186273 // Validate snake_case names
187274 if ( ! isSnakeCase ( objectName ) ) {
188275 return JSON . stringify ( { error : `Invalid object name "${ objectName } ". Must be snake_case.` } ) ;
@@ -238,22 +325,30 @@ function createAddFieldHandler(ctx: MetadataToolContext): ToolHandler {
238325 objectName,
239326 fieldName : name ,
240327 fieldType : type ,
328+ packageId : resolved . packageId ,
241329 } ) ;
242330 } ;
243331}
244332
245333function createModifyFieldHandler ( ctx : MetadataToolContext ) : ToolHandler {
246334 return async ( args ) => {
247- const { objectName, fieldName, changes } = args as {
335+ const { objectName, fieldName, changes, packageId : explicitPackageId } = args as {
248336 objectName : string ;
249337 fieldName : string ;
250338 changes : Record < string , unknown > ;
339+ packageId ?: string ;
251340 } ;
252341
253342 if ( ! objectName || ! fieldName || ! changes ) {
254343 return JSON . stringify ( { error : '"objectName", "fieldName", and "changes" are required' } ) ;
255344 }
256345
346+ // Resolve package ID (for validation and tracking)
347+ const resolved = await resolvePackageId ( ctx , explicitPackageId ) ;
348+ if ( resolved . error ) {
349+ return JSON . stringify ( { error : resolved . error } ) ;
350+ }
351+
257352 // Validate snake_case names
258353 if ( ! isSnakeCase ( objectName ) ) {
259354 return JSON . stringify ( { error : `Invalid object name "${ objectName } ". Must be snake_case.` } ) ;
@@ -287,21 +382,29 @@ function createModifyFieldHandler(ctx: MetadataToolContext): ToolHandler {
287382 objectName,
288383 fieldName,
289384 updatedProperties : Object . keys ( changes ) ,
385+ packageId : resolved . packageId ,
290386 } ) ;
291387 } ;
292388}
293389
294390function createDeleteFieldHandler ( ctx : MetadataToolContext ) : ToolHandler {
295391 return async ( args ) => {
296- const { objectName, fieldName } = args as {
392+ const { objectName, fieldName, packageId : explicitPackageId } = args as {
297393 objectName : string ;
298394 fieldName : string ;
395+ packageId ?: string ;
299396 } ;
300397
301398 if ( ! objectName || ! fieldName ) {
302399 return JSON . stringify ( { error : '"objectName" and "fieldName" are required' } ) ;
303400 }
304401
402+ // Resolve package ID (for validation and tracking)
403+ const resolved = await resolvePackageId ( ctx , explicitPackageId ) ;
404+ if ( resolved . error ) {
405+ return JSON . stringify ( { error : resolved . error } ) ;
406+ }
407+
305408 // Validate snake_case names
306409 if ( ! isSnakeCase ( objectName ) ) {
307410 return JSON . stringify ( { error : `Invalid object name "${ objectName } ". Must be snake_case.` } ) ;
@@ -332,6 +435,7 @@ function createDeleteFieldHandler(ctx: MetadataToolContext): ToolHandler {
332435 objectName,
333436 fieldName,
334437 success : true ,
438+ packageId : resolved . packageId ,
335439 } ) ;
336440 } ;
337441}
0 commit comments