Skip to content

feat(service-ai): add package management tools and package-aware metadata#1169

Merged
hotlong merged 7 commits intomainfrom
claude/create-new-package-for-metadata
Apr 17, 2026
Merged

feat(service-ai): add package management tools and package-aware metadata#1169
hotlong merged 7 commits intomainfrom
claude/create-new-package-for-metadata

Conversation

@Claude
Copy link
Copy Markdown
Contributor

@Claude Claude AI commented Apr 17, 2026

Implements package-aware metadata management to ensure all metadata belongs to a package and code-loaded packages remain read-only while database packages stay mutable.

Package Management Tools

Added 5 AI tools for package lifecycle management:

  • list_packages - List installed packages with status/enabled filtering
  • get_package - Retrieve package manifest and metadata
  • create_package - Create packages with reverse domain ID validation, auto-derived namespaces, semver checking
  • get_active_package / set_active_package - Manage conversation-level package context

Tool handlers in package-tools.ts implement IPackageRegistry and IConversationService interfaces for package CRUD and conversation state tracking.

Metadata Tool Enhancements

Extended MetadataToolContext with:

  • conversationService - retrieve active package from conversation metadata
  • packageRegistry - validate package existence and check read-only status
  • conversationId - track conversation context

Updated create_object tool to accept optional packageId parameter, falling back to active package from conversation context.

Usage Example

// AI creates package and sets as active
await toolRegistry.execute('create_package', {
  id: 'com.acme.crm',
  name: 'CRM Application',
  version: '1.0.0'
});

// Subsequent metadata uses active package implicitly
await toolRegistry.execute('create_object', {
  name: 'account',
  label: 'Account'
  // packageId automatically resolved from conversation context
});

Infrastructure Ready

Schema support already exists in MetadataRecordSchema:

  • packageId - ownership tracking
  • managedBy - lifecycle control ('package' | 'platform' | 'user')
  • scope - mutability ('system' | 'platform' | 'user')
  • source - origin ('filesystem' | 'database' | 'api' | 'migration')

Read-only enforcement and overlay customization require metadata service integration (documented in PACKAGE_METADATA_IMPLEMENTATION.md).

Validation

  • Package IDs: reverse domain notation (com.acme.crm)
  • Namespaces: snake_case, auto-derived from package ID
  • Versions: semantic versioning

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectstack-demo Ready Ready Preview, Comment Apr 17, 2026 3:50am
spec Ready Ready Preview, Comment Apr 17, 2026 3:50am

Request Review

- Add packageId parameter to all metadata tool definitions
- Implement package resolution helpers (getActivePackageId, resolvePackageId)
- Update all metadata handlers to validate and use package context
- Enforce read-only protection for filesystem-based packages
- Return packageId in all tool responses for tracking

Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/69c4b16a-f22c-45a6-88a1-e6b2d10527d8

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
ManifestSchema does not include an 'author' field, causing TypeScript compilation error.
Removed 'author' from:
- create_package tool parameter definition
- createCreatePackageHandler args extraction
- createGetPackageHandler response
- manifest object construction

Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/b89b68d5-0416-434e-b34e-a10f8d15c625

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
ToolCategorySchema only supports: data, action, flow, integration, vector_search, analytics, utility.
Changed all 5 package management tools from 'system' to 'utility' category.

Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/78d4dda5-4db1-46bc-ba64-1eab41672f23

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…rd compatibility

- Changed resolvePackageId to return null (with warning) instead of error when no packageId is provided
- Updated createCreateObjectHandler to conditionally include packageId in object definitions
- This maintains backward compatibility with existing tests while supporting new package-aware features
- All 298 tests in service-ai now pass

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@hotlong hotlong marked this pull request as ready for review April 17, 2026 04:12
Copilot AI review requested due to automatic review settings April 17, 2026 04:12
@hotlong hotlong merged commit bc21aad into main Apr 17, 2026
12 of 14 checks passed
Copilot stopped work on behalf of hotlong due to an error April 17, 2026 04:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces package-aware behavior to the service-ai tool layer by adding package lifecycle management tools and extending metadata tool context so metadata operations can be associated with a package (optionally via conversation “active package” state).

Changes:

  • Added 5 new package management tool definitions (list_packages, get_package, create_package, get_active_package, set_active_package) plus a registration module with handlers.
  • Extended MetadataToolContext and updated metadata handlers to accept/resolve an optional packageId and include it in metadata payloads/responses.
  • Added a design/implementation doc describing the phased rollout for package-aware metadata management.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/services/service-ai/src/tools/package-tools.ts New package tool registration + handler implementations (list/get/create + active package tracking).
packages/services/service-ai/src/tools/list-packages.tool.ts Tool metadata for listing installed packages.
packages/services/service-ai/src/tools/get-package.tool.ts Tool metadata for retrieving a package by ID.
packages/services/service-ai/src/tools/create-package.tool.ts Tool metadata for creating a package (includes validation-related schema).
packages/services/service-ai/src/tools/get-active-package.tool.ts Tool metadata for reading conversation active package.
packages/services/service-ai/src/tools/set-active-package.tool.ts Tool metadata for setting conversation active package.
packages/services/service-ai/src/tools/metadata-tools.ts Adds package resolution helpers + context fields; wires packageId into create/add/modify/delete handlers.
packages/services/service-ai/src/tools/create-object.tool.ts Adds optional packageId parameter to tool schema.
packages/services/service-ai/src/tools/add-field.tool.ts Adds optional packageId parameter to tool schema.
packages/services/service-ai/src/tools/modify-field.tool.ts Adds optional packageId parameter to tool schema.
packages/services/service-ai/src/tools/delete-field.tool.ts Adds optional packageId parameter to tool schema.
packages/services/service-ai/src/index.ts Exports package tool registration/definitions and individual package tool metadata.
PACKAGE_METADATA_IMPLEMENTATION.md New documentation describing the intended end-state and phased implementation plan.

Comment on lines +95 to +103
/** Reverse domain notation pattern (e.g. com.acme.crm). */
const REVERSE_DOMAIN_RE = /^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$/;

/** snake_case identifier pattern. */
const SNAKE_CASE_RE = /^[a-z_][a-z0-9_]*$/;

/** Semantic version pattern. */
const SEMVER_RE = /^\d+\.\d+\.\d+(-[a-z0-9]+(\.[a-z0-9]+)*)?$/;

Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local SEMVER_RE / SNAKE_CASE_RE validation is looser than the platform schemas: ManifestSchema version currently requires x.y.z only, and namespace must start with a letter and be 2–20 chars. As written, create_package can accept versions/namespaces that later fail during install. Reuse the zod schemas (ManifestSchema / its namespace + version shapes) for validation, or update these regexes to match the canonical constraints.

Copilot uses AI. Check for mistakes.
Comment on lines +257 to +276
// Set as active package in conversation if conversation service is available
if (ctx.conversationService && ctx.conversationId) {
try {
await ctx.conversationService.updateMetadata?.(ctx.conversationId, {
activePackageId: id,
});
} catch (err) {
// Non-critical error - package was created successfully
console.warn('Failed to set active package in conversation:', err);
}
}

return JSON.stringify({
packageId: installedPackage.manifest.id,
name: installedPackage.manifest.name,
version: installedPackage.manifest.version,
namespace: installedPackage.manifest.namespace,
status: installedPackage.status,
message: `Package "${name}" created successfully and set as active package`,
});
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create_package always returns the message "created successfully and set as active package", but setting the active package is best-effort and conversationService.updateMetadata is optional (and may be missing even when conversationService is provided). This can mislead callers into thinking the active package was updated. Return a flag like activePackageSet: boolean and tailor the message based on whether the metadata write actually happened (or error if setting active is required).

Copilot uses AI. Check for mistakes.
Comment on lines +120 to +137
// Validate package exists (if registry is available)
if (ctx.packageRegistry) {
const exists = await ctx.packageRegistry.exists(packageId);
if (!exists) {
return {
packageId: null,
error: `Package "${packageId}" not found. Use list_packages to see available packages or create_package to create a new one.`,
};
}

// Check if package is read-only (code-based)
const pkg = await ctx.packageRegistry.get(packageId);
if (pkg?.manifest.source === 'filesystem') {
return {
packageId: null,
error: `Package "${packageId}" is read-only (loaded from code). Only database packages can be modified. Use create_package to create a new database package.`,
};
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read-only enforcement checks pkg?.manifest.source === 'filesystem', but ManifestSchema/InstalledPackage manifests don’t have a source field. This condition will never be true with the current spec shapes, so code-loaded packages won’t actually be treated as read-only. Use a real package origin signal from the package registry/service (e.g., an explicit source/origin property on the InstalledPackage wrapper) or move read-only checks to the metadata layer where MetadataRecord.source/scope exists.

Copilot uses AI. Check for mistakes.
Comment on lines +111 to +117
// If no package ID could be resolved, return null (backward compatibility)
// This allows metadata to be stored without package association
if (!packageId) {
return {
packageId: null,
warning: 'No package specified. Metadata will be created without package association. Consider using set_active_package or providing packageId parameter.',
};
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says package-aware metadata management should ensure all metadata belongs to a package, but resolvePackageId() explicitly allows packageId: null for backward compatibility and continues the operation. If the intent is to enforce package ownership, this should return an error when neither an explicit packageId nor an active conversation package is available (or be gated behind a feature flag/migration mode).

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +93
- Added `packageId` parameter to `create_object` tool
- Optional parameter
- Falls back to active package from conversation
- Provides clear error message if no package context available

**Remaining Work:**
- Update `createObjectHandler` to:
- Resolve package ID (explicit > active > error)
- Check if package is read-only
- Attach package metadata to object definition
- Return package info in success response

- Update other metadata tools (`add_field`, `modify_field`, `delete_field`)
- Add `packageId` parameter where appropriate
- Implement read-only validation

Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doc’s Phase 2 status is out of sync with the code in this PR: it lists updating createObjectHandler/add_field/modify_field/delete_field as “Remaining Work”, but the handlers in packages/services/service-ai/src/tools/metadata-tools.ts have already been updated to accept/resolve packageId and attach it to responses. It also claims create_object provides an error when no package context is available, but the current implementation returns packageId: null with a warning. Please update the doc to reflect the actual behavior and what is still missing (e.g., true read-only enforcement and hard package ownership enforcement).

Suggested change
- Added `packageId` parameter to `create_object` tool
- Optional parameter
- Falls back to active package from conversation
- Provides clear error message if no package context available
**Remaining Work:**
- Update `createObjectHandler` to:
- Resolve package ID (explicit > active > error)
- Check if package is read-only
- Attach package metadata to object definition
- Return package info in success response
- Update other metadata tools (`add_field`, `modify_field`, `delete_field`)
- Add `packageId` parameter where appropriate
- Implement read-only validation
- Updated `create_object` to support package-aware execution
- Accepts optional `packageId`
- Resolves package context using explicit `packageId` first, then active package from conversation
- Returns `packageId: null` with a warning when no package context is available today
- Includes resolved package information in the success response
- Updated other metadata mutation tools (`add_field`, `modify_field`, `delete_field`)
- Accept or resolve `packageId` where applicable
- Include package information in responses
**Remaining Work:**
- Implement true read-only enforcement for code/filesystem packages
- Block metadata mutations when target package source/scope is read-only
- Ensure enforcement happens consistently across all mutation handlers
- Implement hard package ownership enforcement
- Verify that object and field mutations only affect metadata owned by the resolved package
- Prevent cross-package modification without an explicit overlay/override model
- Tighten package validation and mutation semantics
- Standardize warning vs error behavior when package context is missing
- Align tool responses and persistence rules with final package-ownership policy

Copilot uses AI. Check for mistakes.
Comment on lines +387 to +396
export function registerPackageTools(
registry: ToolRegistry,
context: PackageToolContext,
): void {
registry.register(listPackagesTool, createListPackagesHandler(context));
registry.register(getPackageTool, createGetPackageHandler(context));
registry.register(createPackageTool, createCreatePackageHandler(context));
registry.register(getActivePackageTool, createGetActivePackageHandler(context));
registry.register(setActivePackageTool, createSetActivePackageHandler(context));
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Package tool handlers were added in package-tools.ts (validation, create/get/list, conversation active package tracking), but there are no corresponding tests (service-ai already has extensive tool tests like metadata-tools.test.ts). Add unit tests for registerPackageTools and the new handlers, especially for ID/version/namespace validation, “already exists”, activePackageId persistence behavior, and error paths.

Copilot uses AI. Check for mistakes.
Comment on lines +202 to +252
const { id, name, version = '1.0.0', description, namespace, type = 'application' } = args as {
id: string;
name: string;
version?: string;
description?: string;
namespace?: string;
type?: string;
};

// Validate required fields
if (!id || !name) {
return JSON.stringify({ error: 'Both "id" and "name" are required' });
}

// Validate package ID format (reverse domain notation)
if (!isReverseDomain(id)) {
return JSON.stringify({
error: `Invalid package ID "${id}". Must be in reverse domain notation (e.g., com.acme.crm, org.mycompany.sales)`,
});
}

// Validate version format
if (!isSemVer(version)) {
return JSON.stringify({
error: `Invalid version "${version}". Must be semantic version (e.g., 1.0.0, 2.1.3-beta)`,
});
}

// Check if package already exists
const exists = await ctx.packageRegistry.exists(id);
if (exists) {
return JSON.stringify({ error: `Package "${id}" already exists` });
}

// Derive or validate namespace
const derivedNamespace = namespace || deriveNamespace(id);
if (!isSnakeCase(derivedNamespace)) {
return JSON.stringify({
error: `Invalid namespace "${derivedNamespace}". Must be snake_case (e.g., crm, sales_module)`,
});
}

// Build manifest
const manifest: Record<string, unknown> = {
id,
name,
version,
type,
namespace: derivedNamespace,
...(description ? { description } : {}),
};
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create_package builds a manifest with default type = 'application', but @objectstack/spec's ManifestSchema only allows types like app, plugin, ui, driver, etc. This will likely cause the underlying package install/validation to reject the manifest. Align the tool schema + handler defaults/validation with ManifestSchema’s type enum (e.g., use app instead of application) and consider validating by parsing with ManifestSchema before calling install().

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants