- @objectstack/spec@10.3.0
- @objectstack/core@10.3.0
- @objectstack/types@10.3.0
- @objectstack/formula@10.3.0
- Updated dependencies [b496498]
- @objectstack/spec@10.2.0
- @objectstack/core@10.2.0
- @objectstack/formula@10.2.0
- @objectstack/types@10.2.0
- Updated dependencies [49da36e]
- Updated dependencies [ac79f16]
- @objectstack/spec@10.1.0
- @objectstack/core@10.1.0
- @objectstack/formula@10.1.0
- @objectstack/types@10.1.0
-
e411a82: feat(ai): split
ask/buildagents by surface + tool scoping (ADR-0063/0064).Two kernel agents bound by surface, not a per-turn classifier.
SkillSchemagainssurface: 'ask'|'build'|'both'andAgentSchemagainssurface: 'ask'|'build'(ADR-0063 §3); an agent's tools are exactly the union of its surface-compatible skills' tools — incompatible binding is a load error inresolveActiveSkills(ADR-0064 §3). Theaskagent is now data-only (the ADR-0040 unified "INTENT FIRST" classifier and thebuildRegisterActivedegradation shim are removed); a newschema_reader(surface:'both') owns the shared readsdescribe_object/list_objects/query_dataso the build agent reuses them without dual-listing.*.agent.tsis closed to third parties: theagentmetadata-type isallowRuntimeCreate:false, allowOrgOverride:falseand the runtime catalog lists only platform agents (ADR-0063 §2). Renamesdata-chat-agent.ts→ask-agent.ts,DEFAULT_DATA_AGENT_NAME→ASK_AGENT_NAME(thedata_chat/metadata_assistantaliases stay resolvable). -
be07ce7: Rename the built-in data agent
data_chat→ask(Path A: friendly console URL == real id). Back-compat preserved via a new process-wide alias registry:AgentRuntime.loadAgentnormalizes legacy names, so/agents/data_chat/chatand persistedagent_id='data_chat'keep resolving.registerAgentAlias()is exported so other packages register their own renames (cloud AI Studio:metadata_assistant→build). The plugin prunes the stale legacy agent record on upgrade so the catalog isn't doubled.
- Updated dependencies [d7ff626]
- Updated dependencies [2a1b16b]
- Updated dependencies [e16f2a8]
- Updated dependencies [cfd86ce]
- Updated dependencies [e411a82]
- Updated dependencies [a581385]
- Updated dependencies [d5f6d29]
- Updated dependencies [220ce5b]
- Updated dependencies [3efe334]
- Updated dependencies [feead7e]
- Updated dependencies [6ca20b3]
- Updated dependencies [5f875fe]
- Updated dependencies [b469950]
- Updated dependencies [48a307a]
- Updated dependencies [25fc0e4]
- @objectstack/spec@10.0.0
- @objectstack/formula@10.0.0
- @objectstack/core@10.0.0
- @objectstack/types@10.0.0
- Updated dependencies [e7f6539]
- Updated dependencies [2365d07]
- Updated dependencies [6595b53]
- Updated dependencies [fa8964d]
- Updated dependencies [36138c7]
- Updated dependencies [a8e4f3b]
- Updated dependencies [4c213c2]
- Updated dependencies [2afb612]
- @objectstack/spec@9.11.0
- @objectstack/core@9.11.0
- @objectstack/formula@9.11.0
- @objectstack/types@9.11.0
- Updated dependencies [db02bd5]
- Updated dependencies [641675d]
- Updated dependencies [1f88fd9]
- Updated dependencies [94e9040]
- Updated dependencies [1f88fd9]
- Updated dependencies [1f88fd9]
- @objectstack/spec@9.10.0
- @objectstack/formula@9.10.0
- @objectstack/core@9.10.0
- @objectstack/types@9.10.0
- @objectstack/spec@9.9.1
- @objectstack/core@9.9.1
- @objectstack/types@9.9.1
- @objectstack/formula@9.9.1
- Updated dependencies [84249a4]
- Updated dependencies [11af299]
- Updated dependencies [d5774b5]
- Updated dependencies [134043a]
- Updated dependencies [90108e0]
- Updated dependencies [9afeb2d]
- Updated dependencies [6bec07e]
- Updated dependencies [601cc11]
- Updated dependencies [d99a75a]
- Updated dependencies [575448d]
- @objectstack/spec@9.9.0
- @objectstack/core@9.9.0
- @objectstack/formula@9.9.0
- @objectstack/types@9.9.0
- Updated dependencies [c17d2c8]
- Updated dependencies [97c55b3]
- Updated dependencies [1b1f490]
- @objectstack/formula@9.8.0
- @objectstack/spec@9.8.0
- @objectstack/core@9.8.0
- @objectstack/types@9.8.0
- Updated dependencies [82c7438]
- Updated dependencies [417b6ac]
- Updated dependencies [ff0a87a]
- @objectstack/formula@9.7.0
- @objectstack/spec@9.7.0
- @objectstack/core@9.7.0
- @objectstack/types@9.7.0
- Updated dependencies [d1e930a]
- Updated dependencies [71578f2]
- Updated dependencies [bb00a50]
- Updated dependencies [5e3a301]
- Updated dependencies [5db2742]
- @objectstack/spec@9.6.0
- @objectstack/formula@9.6.0
- @objectstack/core@9.6.0
- @objectstack/types@9.6.0
- Updated dependencies [ee72aae]
- @objectstack/spec@9.5.1
- @objectstack/core@9.5.1
- @objectstack/formula@9.5.1
- @objectstack/types@9.5.1
- Updated dependencies [d08551c]
- Updated dependencies [707aeed]
- Updated dependencies [7a103d4]
- Updated dependencies [4b01250]
- @objectstack/spec@9.5.0
- @objectstack/core@9.5.0
- @objectstack/formula@9.5.0
- @objectstack/types@9.5.0
-
b678d8c: feat(service-ai): open framework AI is query-only, declines app-building
The unified
data_chatpersona (ADR-0040) advertised that it can BUILD or CHANGE the application, but that capability is supplied entirely by the cloud AI Studio plugin'smetadata_authoring/solution_designskills. On the open single-env framework those skills are not registered, so the authoring tools never resolve — yet the LLM, still reading the "you can build" persona, role-played designing a whole system (emitting design docs it had no tools to execute).Fix: in
buildSystemMessages, when no authoring (build-register) skill is active, append a deployment-capability note constraining the assistant to data/query and instructing it to decline build requests instead of pretending. Keyed off actual skill presence, so cloud/EE (AI Studio loaded) keeps the full build UX with zero extra wiring. -
b678d8c: feat(service-ai): steer the agent to
visualize_datafor chart requestsSmall models sometimes answered a "draw a bar chart" request with a markdown TABLE instead of calling
visualize_data— a tool-selection problem where the chart preference was buried as a low-priority guideline competing with "format with markdown tables".data-explorer-skill.ts— adds a prominent "Choosing the right tool" section above the guidelines: chart intent (incl. CN terms 图表/柱状图/折线图/饼图/画图) MUST callvisualize_data, never substitute a table; reconciles the table-formatting guideline and fixes duplicate guideline numbering.visualize-data.tool.ts— strengthens the tool description to be imperative ("the ONLY tool that draws a chart… if you already fetched the numbers, still callvisualize_datato render them").
Prompt-only tuning — no behavior/contract change.
-
b678d8c: feat(service-ai):
visualize_datatool — return charts from AI data queriesAdds a
visualize_dataAI tool so the data-query assistant can answer with a CHART instead of plain text/markdown. The tool runs an aggregation through the existing analytics service (auto-inferred cube), maps the result into the SDUI<chart>contract, and emits it to the client as adata-chartcustom stream part (the sameonProgresschanneldata-build-progressalready uses). It also returns a compact textual summary so the model narrates the answer alongside the rendered chart.tools/visualize-data.tool.ts— tool definition, handler, and register fn (function+field → analytics measure key; single dimension → x-axis; measures → series;chartTypebar/line/pie/…).plugin.ts— registers the tool when an analytics service is present and persists it as tool metadata in lockstep (Studio visibility).skills/data-explorer-skill.ts— exposesvisualize_dataplus chart trigger phrases and guidance to prefer it for "chart/plot/trend/breakdown" requests.
-
b678d8c: fix(service-ai): resolve the current object for AI chat across languages
The console assistant reported "can't find the X object" when asked to analyse the object on the current page — most visibly for non-English prompts. Three compounding gaps fixed:
SchemaRetriever.tokenise()dropped all CJK text, so a Chinese request yielded zero terms; it now emits CJK single-char + bigram terms.- Nothing fed the current object's schema to the agent, so "this object" could
not be resolved without a lucky keyword hit.
AgentRuntime.buildContextSchema Messages()now injects the current object's schema into the system prompt and both chat routes call it. ToolExecutionContext(and theai-servicespec contract) gainscurrentObjectName/currentViewName; routes thread them through andquery_datafalls back to the current object when keyword retrieval is empty (so the open edition, which lacksdescribe_object/list_objects, still resolves the page's object).
-
Updated dependencies [060467a]
-
Updated dependencies [0856476]
-
Updated dependencies [b678d8c]
-
Updated dependencies [b678d8c]
-
Updated dependencies [b678d8c]
- @objectstack/spec@9.4.0
- @objectstack/core@9.4.0
- @objectstack/formula@9.4.0
- @objectstack/types@9.4.0
-
d100707: AI provider misconfiguration is now visible, rejected at save time, and recoverable from the UI. Background: a half-saved
aisettings row (provider=cloudflare, empty key) silently overrode env auto-detection and the only symptom was a bare "Bad Request" in chat.GET /api/v1/ai/status— active adapter provenance:source(explicit/env/settings/fallback), provider, model, plussettingsErrorexplaining why saved settings were NOT applied.AIServicePlugintracks this through boot detection, settings rebuilds, and resets.- Save-time validation in
SettingsService.setMany(fulfilling the spec promise thatrequiredis enforced server-side): visible+required fields andpatternmismatches reject the whole batch with field-level errors (400 SETTINGS_VALIDATION). Visibility expressions (${data.provider === '…'}) are evaluated server-side by a restricted-grammar parser; unparseable expressions and all-null patches (resets) stay lenient.gateway_model/cloudflare_modelgainprovider/modelpatterns. - Built-in
resetsettings action for every namespace (SettingsService.resetNamespace), overridden foraito also re-run env adapter detection immediately; the AI manifest ships a "Reset to environment defaults" button — no more hand-editingsys_setting. - Chat/agent/assistant stream errors are enriched with the active adapter description and actionable hints (400 → model-id format, 401/403 → credential, 404 → unknown model, 429 → rate limit) instead of a bare HTTP status.
- Updated dependencies [1ada658]
- Updated dependencies [3219191]
- Updated dependencies [290f631]
- Updated dependencies [50b7b47]
- Updated dependencies [f15d6f6]
- Updated dependencies [f8684ea]
- Updated dependencies [b4765be]
- @objectstack/spec@9.3.0
- @objectstack/core@9.3.0
- @objectstack/formula@9.3.0
- @objectstack/types@9.3.0
- Updated dependencies [2f57b75]
- Updated dependencies [2f57b75]
- @objectstack/spec@9.2.0
- @objectstack/core@9.2.0
- @objectstack/formula@9.2.0
- @objectstack/types@9.2.0
- Updated dependencies [b9062c9]
- @objectstack/spec@9.1.0
- @objectstack/core@9.1.0
- @objectstack/formula@9.1.0
- @objectstack/types@9.1.0
- Updated dependencies [1817845]
- @objectstack/spec@9.0.1
- @objectstack/core@9.0.1
- @objectstack/formula@9.0.1
- @objectstack/types@9.0.1
-
f533f42: Settings namespace environment overrides now use the canonical ObjectStack
OS_<NAMESPACE>_<KEY>form, with no unprefixed aliases. For example,ai.openai_base_urlis nowOS_AI_OPENAI_BASE_URL, andfeature_flags.ai_enabledis nowOS_FEATURE_FLAGS_AI_ENABLED.The AI service now treats a stored or env-locked
provider=memorysetting as an explicit override, while the manifest default still leaves boot-time provider auto-detection intact.The auth plugin now binds the
authsettings namespace to better-auth runtime configuration, exposes an extension hook for provider packages, and includes a basic Google sign-in implementation configured either in Setup → Authentication or by deployment-levelGOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRET. -
Updated dependencies [4c3f693]
-
Updated dependencies [0bf39f1]
-
Updated dependencies [f533f42]
-
Updated dependencies [1c83ee8]
- @objectstack/spec@9.0.0
- @objectstack/core@9.0.0
- @objectstack/formula@9.0.0
- @objectstack/types@9.0.0
- @objectstack/spec@8.0.1
- @objectstack/core@8.0.1
- @objectstack/types@8.0.1
- @objectstack/formula@8.0.1
-
a46c017: feat(ai): actions opt in to being AI tools via an
ai:block (ADR-0011)Realigns ADR-0011 with its original opt-in design. An Action becomes an AI-callable tool only when its metadata sets
ai.exposed: true, which requires an explicit, LLM-facingai.description(≥40 chars, distinct from the UIlabel). There is no heuristic auto-exposure and no description derived from the label — a clean break from the first implementation's opt-outaiExposedflag, which is removed (no compatibility shim; the platform has not shipped).The
ai:block also carriescategory,paramHints(per-parameter JSON-Schema refinement),outputSchema(summarised into the tool description for chaining), andrequiresConfirmation(overrides the destructive-action HITL default).AIToolDefinitionis extended to carrycategory/outputSchema/objectName/requiresConfirmation. The@objectstack/service-aibridge (action-tools.ts) now gates on opt-in, mergesparamHints, and emits a lint warning when an exposed destructive-looking action asserts itself safe viaai.requiresConfirmation: false. -
Updated dependencies [a46c017]
-
Updated dependencies [b990b89]
-
Updated dependencies [99111ec]
-
Updated dependencies [d5a8161]
-
Updated dependencies [5cf1f1b]
-
Updated dependencies [9ef89d4]
-
Updated dependencies [3306d2f]
-
Updated dependencies [c262301]
-
Updated dependencies [bc44195]
-
Updated dependencies [9e2e229]
- @objectstack/spec@8.0.0
- @objectstack/core@8.0.0
- @objectstack/formula@8.0.0
- @objectstack/types@8.0.0
-
ac1fc4c: fix(ai): authoring tools can see their own drafts; blueprint surfaces the package to bind to
Two gaps that broke the multi-step "build app → author a flow for it" path (found while verifying the new solution_design guardrail):
-
The agent couldn't discover its own draft objects.
list_objects/list_metadatareadgetMetaItemsactive-only, so a brand-new object the agent had just drafted (never published) was reported as "not found" when it then tried to author an approval flow against it. They now passpreviewDrafts: true, overlaying pending drafts on the active list (older runtimes ignore the flag → stay active-only).describe_metadatawas already draft-first. -
The auto-authored flow had no package to bind to.
apply_blueprintalready homes its artifacts in an app package, but its result only nested the id underpackage. It now also surfaces a top-levelpackageIdand abindingHinttelling the agent to pass thatpackageIdtocreate_metadatawhen it drafts follow-up automation (e.g. the approval flow) — so the flow lands in the app package instead of becoming an orphan draft.
Together with the solution_design process guardrail, this makes the "model the data, then proactively draft the approval flow bound to the app" flow actually executable end-to-end.
-
-
4705fb8: fix(ai): solution_design no longer models a process/approval as a table
Asking the assistant to "design expense reimbursement" made it, by default, invent an
approval_recordTABLE to represent the approval process — a non-functional "process-as-data" anti-pattern. It only switched to a flow after the user pushed back.Hardens the default in two places:
-
propose_blueprintgeneration prompt (the "metadata architect" system message): status/lifecycle is modeled as aselectfield, never a table; it must NOT createapproval/approval_record/approval_step/workflow/processobjects (a process is a flow, its trail comes from platform history); the people a process references (approver/reviewer/owner) arelookupfields; and if the goal implies a process it adds an assumption that the approval flow is a separate step. -
solution_designskill instructions: the same modeling rules, plus — when the goal involves a process — the agent now PROACTIVELY drafts the approval flow afterapply_blueprint(callget_metadata_schema('flow'), thencreate_metadata(type:'flow', …)with the approval node(s), bound to the same app package) instead of waiting for the user to ask "now create the flow". Optionally adds astate_machinerule to block illegal status transitions.
Regression-tested:
solution-design-guardrail.test.tsasserts the skill instructions carry the no-process-as-table rule, status-as-select, and the proactive-flow step.- @objectstack/spec@7.9.0
- @objectstack/core@7.9.0
- @objectstack/types@7.9.0
- @objectstack/formula@7.9.0
-
-
6b82e68: feat(ai): zero-package app building — auto-home a blueprint's app in a writable package
When the AI blueprint flow builds an app, it now silently gives that app a writable "home" package (one app ⇒ one
app.<name>package) and binds every drafted artifact (objects, views, dashboards, the app) to it — so a business user never has to create a "package" to start building (the mainstream AI-builder UX: Power Apps' default solution, Salesforce orgs). Packaging/versioning stays an opt-in, later concern.apply_blueprintensures the app package up front (idempotent: reuse if it exists, else create via the runtimepackageservice) and threads itspackageIdthrough everystageDraft→sys_metadata.package_id. The result envelope gainspackage: { id, name, created }.- The
packageservice is resolved lazily (per call, not at plugin-init time) so it works regardless of service-init order and picks up the opt-inmarketplacecapability when present. - Best-effort, non-fatal: if no
packageservice is wired, drafting proceeds package-less exactly as before — the build never fails on packaging.
Scope/caveats: this stamps the legacy
sys_metadata.package_id(a real grouping + the foundation for later version/export/promote), not the sealedsys_package_versionmodel — full cross-environment promotion and Studio package-selector visibility depend on finishing the runtime package subsystem (ADR-0027), tracked separately. (The showcase example enables themarketplacecapability to exercise this.) -
4888ea2: feat(ai): add
get_metadata_schematool so the agent can read a type's contract before authoringThe metadata-authoring agent never sees the real spec Zod schemas — it works against a simplified blueprint or sends a free-form
definitionand only learns the true shape from post-hoc validation errors. For complex types (view, dashboard, flow, …) that means guessing, e.g. a kanban view's requiredkanban: { groupByField, columns }block.New
get_metadata_schematool returns the JSON Schema (via Zod v4'stoJSONSchema) derived from the SAME live schemasaveMetaItemvalidates against (getMetadataTypeSchema). Themetadata_authoringskill now instructs the agent to call it before authoring a non-trivial type, so it conforms first time instead of trial-and-error. Read-only; resolves plural type names; returns a graceful error for types that can't be serialized (e.g.object, which the dedicatedcreate_objecttools cover anyway). -
36719db: fix: AI-built apps are usable immediately — sync new object tables on publish + emit valid kanban config
Two gaps found by end-to-end testing of an AI-built app:
-
A freshly-published object couldn't accept records until a server restart. Publishing a drafted object registered it in the in-memory registry but never created its physical table (table sync only ran at boot), so inserts failed with
object_not_found("no such table"). AddedObjectQL.syncObjectSchema(name)(a targeted, idempotent single-object schema sync) and call it from the publish paths (protocol.publishMetaItemandsaveMetaItemmode:'publish', viaensureObjectStorage). Best-effort + non-fatal. New objects are now CRUD-able the moment they're published. -
AI-generated kanban views rendered as plain lists (and sometimes failed validation). The blueprint
viewBodyemittedlist.type:'kanban'with nokanbanconfig;KanbanConfigSchemarequiresgroupByFieldandcolumns. Added an optionalgroupByto the blueprint view schema (lenient + strict) and haveapply_blueprintsetlist.kanban = { groupByField, columns }— using the view's explicitgroupBywhen given, else inferring the object's firstselectfield. AI-built kanban views now validate, publish, and carry a real group-by field.
-
-
06f2bbb: fix(ai): make ADR-0033 blueprint authoring work with OpenAI structured outputs
Two bugs surfaced by a live end-to-end run (Studio chat → blueprint → draft → review → publish) against a real model (OpenAI via the Vercel AI Gateway) — both invisible to the existing unit tests:
-
propose_blueprintfailed against OpenAI strict structured outputs.SolutionBlueprintSchemauses optional fields and a free-formseedDatarecord; OpenAI's strict mode requires every property listed inrequiredand rejects openadditionalProperties, sogenerateObjecterrored ('required' … must include every key in properties) and the agent silently fell back to free-text. AddsSolutionBlueprintStrictSchema— a strict-compatible mirror (optional → nullable, noz.record) used only as thegenerateObjectoutput contract. The lenientSolutionBlueprintSchema(and every existing consumer/test) is unchanged; the blueprint tools strip thenulls the strict contract emits so downstream stays clean. -
Tool-only assistant turns failed to persist.
ai_messages.contentis required, but an assistant turn that only calls a tool has no text, so the insert failed, the turn was dropped, and the next turn lost context (the agent re-proposed instead of applying the confirmed blueprint).ObjectQLConversationService.addMessagenow synthesizes a readable placeholder from the tool names ((called propose_blueprint)) plus a defensive non-empty fallback.
With both fixes the full plan-first loop runs end-to-end on OpenAI models: propose → confirm → batch-draft objects/views/dashboards/app → review/diff → publish.
-
-
4fbb86a: feat(packages): consolidate the package subsystem so AI-built app packages surface in Studio
The package subsystem was split across two stores that never met: the in-memory
SchemaRegistry(what the dispatcher's/api/v1/packageslist/detail andgetMetaItems({type:'package'})read — i.e. Studio's package selector) and the durablesys_packagestable (where the AI's auto app package, and anypackage-service publish, were written). Nothing reconciled the two, so an AI-createdapp.<name>package never appeared in Studio.This unifies them around one write primitive and one read source:
protocol.installPackageis now implemented (it was declared-but-missing). It is the single canonical write path: it registers the package in the in-memory registry and best-effort persists it tosys_packagesvia thepackageservice. Non-fatal when nopackageservice is wired (registry write still succeeds).- Dispatcher
POST /api/v1/packagesroutes throughprotocol.installPackage(falling back to the bare registry write when the protocol is unavailable), so HTTP installs are durable too. @objectstack/service-packagereconcilessys_packagesback into the registry on boot, without clobbering filesystem-registered packages — so persisted packages survive a restart and stay visible in the registry-backed read paths.@objectstack/service-aiapply_blueprintnow homes an app viaprotocol.installPackage(falling back to the legacypackage-service publish), so the app package lands where Studio reads it.
Still the legacy
package_idplane — sealedsys_package_versionversioning and cross-environment promotion remain ADR-0027 follow-ups. -
328a7c4: fix(ai): AI-authored views now bind to their object and render (kanban as a board, not a list)
An AI-built app's views (including kanban) appeared only as the default list and never as selectable tabs. Diagnosis (vs the working showcase kanban) showed it was a metadata-shape bug in the blueprint's
viewBody, not the renderer or skill: it emitted a bare{ list: {…} }fragment instead of the canonical view record. Three things were missing/wrong:- no top-level
name→getMetaItemsonly surfaces overlay rows whose body hasname, so every AI view was silently dropped from the object's view list; - no top-level
object/viewKind→ the console couldn't bind the view to its object; - the view name wasn't
<object>.<key>-prefixed (the convention the console keys view tabs off).
viewBodynow emits{ name: '<object>.<key>', object, viewKind: 'list'|'form', config: <ListView|FormView> }, matching the shape the showcase's own views use (verified against the realViewSchema). End-to-end verified: an AI-built kanban app surfaces 看板 + 列表 as tabs and renders the kanban as a board grouped by status. - no top-level
-
Updated dependencies [06f2bbb]
-
Updated dependencies [f01f9fa]
-
Updated dependencies [36719db]
-
Updated dependencies [424ab26]
- @objectstack/spec@7.8.0
- @objectstack/formula@7.8.0
- @objectstack/core@7.8.0
- @objectstack/types@7.8.0
-
b391955: feat(ai): blueprint app-building — propose/draft the navigation app, not just the data model
The plan-first blueprint (ADR-0033 §4) now also designs the app (the navigation shell end users open in the App Launcher), so "build me a project-management application" yields an openable app — not just its objects, views, and dashboards.
SolutionBlueprintSchema(@objectstack/spec/ai) gains an optionalapp: { name, label?, icon?, nav? }, where each nav entry targets a created object or dashboard.navmay be omitted to auto-surface every object (then dashboard).apply_blueprintexpands the app into anAppSchemabody (single-levelnavigationof object/dashboard items) and drafts it last — through the same draft-gated, per-type-validatedstageDraftpath as everything else. It never setsisDefault.propose_blueprintnow asks the agent to include the app and reportscounts.app.
Still draft-gated: nothing is live until the human publishes. Scope is basic app-building (one app, flat nav); areas/groups/mobile-nav remain author-it-later via
update_metadata. -
984ddff: feat(service-ai): ADR-0033 Phase A — draft-gate AI metadata authoring
AI metadata mutations no longer publish straight to the live schema. Every write now routes through the ADR-0027 draft workspace via
protocol.saveMetaItem({ mode:'draft' })— nothing an agent authors goes live until a human reviews the diff and publishes. The draft is the approval gate (the never-enforcedrequiresConfirmationflag is retired).Adds a type-agnostic apply surface —
create_metadata/update_metadata/describe_metadata/list_metadata— that works for any metadata type (view, dashboard, flow, …), validated against each type's Zod schema with errors fed back to the agent for self-correction. The existing object/field tools become thin draft-writing wrappers. Tool results return{ status:'drafted', type, name, summary, changedKeys }. -
f06b64e: feat(ai): ADR-0033 Phase C — plan-first blueprint authoring
For high-level goals ("build me a project-management system") the metadata assistant now designs before it builds. Adds a
SolutionBlueprintSchema(@objectstack/spec/ai) describing proposed objects, fields, relationships, views, dashboards, and seed data with stated assumptions, plus two tools:propose_blueprint(goal)— emits a structured blueprint via structured output. Nothing is persisted; the agent presents it for conversational confirmation and asks at most 1–2 structure-deciding questions.apply_blueprint(blueprint)— only after the human approves, batch-drafts every artifact through the Phase A draft path (protocol.saveMetaItem({mode:'draft'})), validated per-type and partial-tolerant (a bad item is reported, the rest still draft). Seed data is reported as proposed, not auto-applied (no runtimedatasettype).
A new
solution_designskill carries the plan-first instructions and is bound tometadata_assistantalongsidemetadata_authoring. The shared draft-write primitive is exported from the metadata tools asstageDraftand reused, keeping one draft-write path.
- Updated dependencies [b391955]
- Updated dependencies [f06b64e]
- Updated dependencies [825ab06]
- Updated dependencies [023bf93]
- @objectstack/spec@7.7.0
- @objectstack/formula@7.7.0
- @objectstack/core@7.7.0
- @objectstack/types@7.7.0
-
c4a4cbd: ADR-0032 (phase 1): validate-by-default expression layer — no silent failure.
Kills the #1491 class where a malformed predicate (e.g. the
{record.x}template-brace-in-CEL mistake) silently evaluated tofalseand made a flow "fire" with no effect:- service-automation: flow
evaluateConditionno longer swallows CEL failures tofalse— it throws an attributed, corrective error; andregisterFlownow parse-validates every predicate (start/decision/edge condition) at registration, failing loudly with the offending location + source + the fix. - formula: new shared validator —
validateExpression(role, src, schema?),introspectScope,CEL_STDLIB_FUNCTIONS— with schema-aware field-existence- did-you-mean. The
{{ }}template engine gains a formatter whitelist (currency/number/percent/date/datetime/truncate/upper/lower/default/…) with defined value→string semantics; arbitrary logic in holes is rejected. Plain{{ path }}stays back-compatible.
- did-you-mean. The
- cli:
objectstack compilevalidates every flow / validation-rule / field-formula predicate against the resolved object schema and fails the build with located, corrective messages. - service-ai: new agent-callable
validate_expressiontool so authoring agents self-correct before committing. - spec: fix the
FlowSchemaJSDoc example that taught the badcondition: "{amount} < 500"single-brace form.
- service-automation: flow
-
3377e38: fix(release): stop the fixed-group major cascade caused by internal
@objectstack/*peerDependencies.These packages declared workspace peerDependencies on other framework packages in the changesets
fixedgroup. Inside a fixed group, changesets rewrites those peer ranges on every release and treats a peer-range change as breaking → major, which cascaded to all 69 packages → 8.0.0 on any minor changeset. Required internal peers are now regulardependencies; optional ones move todevDependencies(kept for in-workspace tests, no longer a published peer edge). Releases now bump correctly (patch/minor) instead of a spurious major. -
Updated dependencies [955d4c8]
-
Updated dependencies [c4a4cbd]
-
Updated dependencies [b046ec2]
-
Updated dependencies [2170ad9]
-
Updated dependencies [02d6359]
-
Updated dependencies [7648242]
-
Updated dependencies [8fa1e7f]
-
Updated dependencies [55866f5]
-
Updated dependencies [60f9c45]
- @objectstack/spec@7.6.0
- @objectstack/formula@7.6.0
- @objectstack/core@7.6.0
- @objectstack/types@7.6.0
- @objectstack/spec@7.5.0
- @objectstack/core@7.5.0
- @objectstack/types@7.5.0
- @objectstack/spec@7.4.1
- @objectstack/core@7.4.1
- @objectstack/types@7.4.1
-
2faf9f2: External Datasource Federation (ADR-0015) — Phase 4: AI awareness.
SchemaRetriever.renderSnippetnow annotates federated objects in the auto-injected schema context, e.g.### wh_order — Warehouse Order [external, read-only, datasource=warehouse], so the LLM knows an object comes from a customer's production database and must not propose schema changes or unsafe writes.ObjectShapegainsdatasourceexternal(read from object metadata). Managed objects are unannotated.
- Updated dependencies [23c7107]
- Updated dependencies [c72daad]
- Updated dependencies [f115182]
- Updated dependencies [2faf9f2]
- Updated dependencies [2faf9f2]
- Updated dependencies [2faf9f2]
- Updated dependencies [58b450b]
- Updated dependencies [82eb6cf]
- Updated dependencies [13d8653]
- Updated dependencies [ff3d006]
- Updated dependencies [5e831de]
- @objectstack/spec@7.4.0
- @objectstack/core@7.4.0
- @objectstack/types@7.4.0
- Updated dependencies [5e7c554]
- @objectstack/spec@7.3.0
- @objectstack/core@7.3.0
- @objectstack/types@7.3.0
-
9096dfe:
OS_env-var prefix migration (issue #1382).All ObjectStack-owned environment variables now use the
OS_prefix. Legacy names still work for one release and emit a one-shot deprecation warning via the newreadEnvWithDeprecation()helper in@objectstack/types.Renamed (with legacy fallback):
New Legacy (deprecated) OS_AUTH_SECRETAUTH_SECRET,BETTER_AUTH_SECRETOS_AUTH_URLAUTH_BASE_URL,BETTER_AUTH_URL,OS_AUTH_BASE_URLOS_PORTPORTOS_DATABASE_URLDATABASE_URLOS_ROOT_DOMAINROOT_DOMAINOS_MULTI_ORG_ENABLEDOS_MULTI_TENANTOS_CORS_ENABLEDCORS_ENABLEDOS_CORS_ORIGINCORS_ORIGINOS_CORS_CREDENTIALSCORS_CREDENTIALSOS_CORS_MAX_AGECORS_MAX_AGEOS_AI_MODELAI_MODELOS_MCP_SERVER_ENABLEDMCP_SERVER_ENABLEDOS_MCP_SERVER_NAMEMCP_SERVER_NAMEOS_MCP_SERVER_TRANSPORTMCP_SERVER_TRANSPORTOS_NODE_IDOBJECTSTACK_NODE_IDOS_METADATA_WRITABLEOBJECTSTACK_METADATA_WRITABLEOS_DEV_CRYPTO_KEYOBJECTSTACK_DEV_CRYPTO_KEYOS_HOMEOBJECTSTACK_HOMEMigration: rename in your
.env. Legacy names continue to work this release and will be removed in a future major. Industry-standard names (NODE_ENV,HOME,OPENAI_API_KEY,TURSO_*, OAuth*_CLIENT_ID/SECRET,RESEND_API_KEY,POSTMARK_TOKEN,AI_GATEWAY_*,SMTP_*) are NOT renamed. -
Updated dependencies [9096dfe]
- @objectstack/types@7.2.1
- @objectstack/spec@7.2.1
- @objectstack/core@7.2.1
- @objectstack/spec@7.2.0
- @objectstack/core@7.2.0
- Updated dependencies [47a92f4]
- @objectstack/spec@7.1.0
- @objectstack/core@7.1.0
- Updated dependencies [74470ad]
- Updated dependencies [d29617e]
- Updated dependencies [dc72172]
- @objectstack/spec@7.0.0
- @objectstack/core@7.0.0
- @objectstack/embedder-openai@7.0.0
-
e9bacda: Auto-generate concise titles for AI conversations.
AIServicenow exposessummarizeConversation(id)and fires it once per conversation after the first assistant turn lands. The generated title (≤ 16 chars by default) is PATCHed onto theai_conversationsrow so the sidebar shows a meaningful label instead of "New conversation". Failures are silently swallowed — title generation is purely cosmetic and never blocks chat.Plumbing:
- New AI settings (in the
aiSettings namespace):title_generation_enabled(toggle, default on for non-memory providers)title_max_length(number, 8–80, default 16)
AIService.setTitleGenerationConfig({ enabled, maxLength })— called byAIServicePlugin.bindSettings()whenever theainamespace changes, so admins can toggle the feature live from Setup without a restart.AIServicecallssummarizeConversation()fire-and-forget at the natural end ofchatWithToolsandstreamChatWithTools. Idempotent per service instance — a single titling attempt per conversation per process.
Defaults are conservative: memory provider stays untouched (no LLM call is made), and any per-test
AIServicethat doesn't explicitly callsetTitleGenerationConfig({ enabled: true })behaves exactly as before. - New AI settings (in the
- @objectstack/spec@6.9.0
- @objectstack/core@6.9.0
- @objectstack/spec@6.8.1
- @objectstack/core@6.8.1
-
6e88f77: Auto-persist chat history when a
conversationIdis supplied.AIService.chatWithToolsandstreamChatWithToolsnow write the inbound user turn, each intermediate assistant/tool round, and the final assistant turn toai_messageswhenevertoolExecutionContext.conversationIdis set. Persistence is best-effort: failures are warned and never break the chat response.- Add
IAIConversationService.update(conversationId, { title?, metadata? })and a matchingPATCH /api/v1/ai/conversations/:idroute so clients can rename conversations and edit metadata. ObjectQLConversationServiceandInMemoryConversationServiceboth implement the newupdatemethod.
-
50ccd9c: Fix peer-dependency version range from
workspace:*toworkspace:^to avoid forced major bumps in fixed-group releases.workspace:*expands to an exact version on publish; any minor bump of the peer then falls out of range and triggers a semver-major bump on the dependent.workspace:^expands to^x.y.zwhich correctly accepts minor bumps.Affects:
service-aipeer on@objectstack/embedder-openairuntimepeer on@objectstack/driver-turso
-
Updated dependencies [6e88f77]
-
Updated dependencies [c8b9f57]
- @objectstack/spec@6.8.0
- @objectstack/core@6.8.0
- @objectstack/spec@6.7.1
- @objectstack/core@6.7.1
-
4f9e9d4: Settings → runtime bridge:
embedder_*settings now build a realIEmbedderand register it as a kernel-level DI service.@objectstack/spec- Exports
EMBEDDER_SERVICE = 'embedder'fromcontracts/embedder.tsas the canonical DI token for the kernel-registered embedder.
@objectstack/service-ai- Adds
@objectstack/embedder-openaias an optional peer dependency (matches the@ai-sdk/*provider plugins pattern). AIServicePlugin.bindSettings()now also:- Reads
embedder_provider/embedder_api_key/embedder_model/embedder_base_url/embedder_dimensionsfrom theainamespace. - Dynamically imports
@objectstack/embedder-openaiand constructs anOpenAIEmbedderviacreateOpenAIEmbedder({ preset, … }). - Registers / replaces the instance under
EMBEDDER_SERVICE. When the operator setsembedder_provider = none, the service is left unset so adapters can fail fast with a clear message. - Subscribes to
settings:changedfor theainamespace so embedder swaps go live without restart (mirrors the chat-adapter pattern). - Overrides the manifest's fallback
ai/test_embedderaction with a live one-shotembed(['ping'])round-trip against the form's (possibly unsaved) values. Reports vector dims + latency.
- Reads
@objectstack/knowledge-tursoKnowledgeTursoPlugin'sembeddingconstructor option is now optional. When omitted, the plugin resolvesEMBEDDER_SERVICEfrom the kernel atstart()time — typically the embedder built by@objectstack/service-aifrom theaisettings namespace.- Explicit
embeddingstill wins when both are present (useful for tests and multi-embedder setups). - Logs
(embedder=<id>, dims=<n>)on adapter registration so operators can confirm wiring at a glance. - When neither path resolves, the plugin warns with a one-line hint
pointing to
Settings → AI & Embedderand no-ops gracefully (the host kernel still boots).
Tests
service-ai: +5 cases (now 85) coveringai/test_embedderaction registration,provider=nonewarning, missing-api-key error, custom-provider-without-base-URL error, and the full happy path (mocked fetch → embedder registered underEMBEDDER_SERVICE→ test_embedder action returns vector dims).knowledge-turso: newplugin.test.ts(+5 cases) covering deferred construction, EMBEDDER_SERVICE fallback, explicit-wins precedence, missing-both warn-and-noop, and missing-knowledge-service warn.
End-to-end now possible: operator opens Settings → AI & Embedder, picks 硅基流动 + paste API key + chooses
BAAI/bge-m3, hits Save. Within the same process,EMBEDDER_SERVICEis registered/replaced,KnowledgeTursoPlugin(if started without an explicit embedder) picks it up, and subsequentknowledge.search()calls embed via the new provider — no restart, no env vars. - Exports
- Updated dependencies [430067b]
- Updated dependencies [4f9e9d4]
- @objectstack/spec@6.7.0
- @objectstack/core@6.7.0
- Updated dependencies [a49cfc2]
- @objectstack/spec@6.6.0
- @objectstack/core@6.6.0
- @objectstack/spec@6.5.1
- @objectstack/core@6.5.1
- @objectstack/spec@6.5.0
- @objectstack/core@6.5.0
-
f8651cc: Knowledge Protocol MVP — protocol-first RAG via adapter plugins.
What's new:
@objectstack/spec— newKnowledgeSource/KnowledgeDocument/KnowledgeChunk/KnowledgeHitschemas (under@objectstack/spec/ai) andIKnowledgeService/IKnowledgeAdaptercontracts (under@objectstack/spec/contracts).@objectstack/service-knowledge—KnowledgeServiceorchestrator +KnowledgeServicePlugin. Routes search/index calls to the appropriate adapter, runs permission-aware retrieval by re-checking every hit'ssourceRecordIdagainst the caller'sExecutionContextviaIDataEngine(same RLS that gates plain ObjectQL), and subscribes toIRealtimeServicefor inline record→adapter sync.@objectstack/knowledge-memory— deterministic, dependency-free in-memory adapter for dev/tests/reference. Hash-token embedder + brute-force cosine + paragraph chunking.@objectstack/knowledge-ragflow— production-grade adapter against the Apache-2.0 RAGFlow REST API. Plug in your dataset id; ObjectStack handles permission filtering after retrieval.@objectstack/service-ai— newsearch_knowledgetool wired through the registry. Threads the LLM caller's actor intoKnowledgeService.searchso retrieval honours RLS automatically.
Why this design: ObjectStack does NOT own chunking / embedding / vector storage / rerank — those are commodity capabilities best handled by mature OSS (RAGFlow, LlamaIndex, Dify, …). What ObjectStack uniquely owns is the protocol + permission-aware orchestration on top.
See
content/docs/protocol/knowledge.mdxfor the full design. -
f8651cc: AI tools now execute with the end-user's
ExecutionContext, so the existing ObjectQL row-level-security rules automatically scope what an agent can read and mutate.What changed
- New
ToolExecutionContext(on@objectstack/spec/contracts'sChatWithToolsOptions) carries the authenticated actor, conversation id, and environment id through to tool handlers. - The built-in data tools (
query_records,get_record,aggregate_data, legacyquery_data) and the auto-generatedaction_*tools now passoptions.contexttoIDataEnginecalls, mapping the actor to{ userId, roles, permissions, isSystem: false }. - Assistant + agent REST routes forward
req.userinto the new context automatically — no caller changes required. - When no actor is provided (cron jobs, internal callers, existing tests)
the helpers fall back to
{ isSystem: true }, preserving today's behaviour. Fully backward compatible.
Why this matters
Before this change, an AI tool call ran with system privileges and saw every row in the tenant. Now the agent sees exactly what the human operator would see — same RLS, same field-level masking, same audit trail. This is the foundation for trustworthy autonomous agents.
For custom call sites
If you invoke
aiService.chatWithTools(...)from your own route, passtoolExecutionContext: { actor: { id, roles, permissions } }to inherit the user's permissions. Omit it to keep the legacy system-level behaviour. - New
- a981d57: Auto-persist chat messages when
conversationIdis supplied.AIService.chatWithToolsandstreamChatWithToolsnow write the inbound user turn, every intermediate assistant/tool round, and the final assistant turn toai_messagesvia the configured conversation service. Persistence is best-effort: failures are logged atwarnlevel and never fail the chat request. - b486666: Add
GET /api/v1/ai/conversations/:idroute to fetch a single conversation with its full message history. Enforces ownership via the authenticated user: returns404when the conversation does not exist and403when it belongs to another user. Enables clients to hydrate chat UIs from server-persisted history instead of relying on local storage. - Updated dependencies [f8651cc]
- Updated dependencies [f8651cc]
- Updated dependencies [0bf6f9a]
- @objectstack/spec@6.4.0
- @objectstack/core@6.4.0
- @objectstack/spec@6.3.0
- @objectstack/core@6.3.0
-
b4c74a9: Actions-as-tools Phase 3 — Human-In-The-Loop approval queue.
Dangerous declarative actions (
confirmText,mode:'delete',variant:'danger') can now be exposed to the LLM safely. Instead of being skipped outright, they are registered as tools whose handler enqueues a pending request and returns{ status: 'pending_approval', pendingActionId }to the model. A human approves (or rejects) from Studio's pending-actions inbox; the service then re-runs the exact same dispatcher.- New system object
ai_pending_actions(id, conversation_id?, message_id?, object_name, action_name, tool_name, tool_input, status [pending|approved|executed|failed|rejected], result?, error?, rejection_reason?, proposed_by, decided_by?, proposed_at, decided_at?). - New built-in Studio view
AiPendingActionViewwithpending/executed/rejected/failedsub-views and per-row Approve / Reject API actions. - New methods on
IAIService(all optional, gated on a wiredIDataEngine):proposePendingAction(input) → { id }approvePendingAction(id, actorId) → { status, result?, error? }rejectPendingAction(id, actorId, reason?)listPendingActions(filter?) → PendingActionRow[]
- New exported types:
PendingActionStatus,ProposePendingActionInput,PendingActionRow. - New REST routes (auth required):
GET /api/v1/ai/pending-actions(ai:read)GET /api/v1/ai/pending-actions/:id(ai:read)POST /api/v1/ai/pending-actions/:id/approve(ai:approve)POST /api/v1/ai/pending-actions/:id/reject(ai:approve)
- New exported predicate
actionRequiresApproval(action)for Studio's exposure surface.
AIServicePluginOptionsgainsenableActionApproval?: boolean(defaultfalse). Whentrueand anIDataEngineis available, dangerous actions are registered and routed through the queue.kernel.use( new AIServicePlugin({ enableActionApproval: true, // opt in apiActionBaseUrl: "http://localhost:3000", }) );
actionSkipReason()acceptsenableActionApproval+aiServicein its ctx and stops returning"requires confirmation"/"mode='delete'"/"variant='danger'"when HITL is wired.registerActionsAsTools()pre-registers a bypass-approval dispatcher per dangerous tool viaaiService.registerPendingActionDispatcher(toolName, fn); approval calls back into the same code path withenableActionApprovalflipped off, so a single handler implementation serves both proposal and execution.createActionToolHandler()short-circuits toproposePendingAction()whenenableActionApproval && actionRequiresApproval(action) && ctx.aiService?.proposePendingAction.
Slack/email notifications, approver routing (any signed-in user can approve in v1), auto-expiry of pending requests, resuming the same LLM turn after approval (operators get a fresh assistant message instead).
- New system object
-
bce47a0: Polish Studio HITL pending-action inbox UI
The
AiPendingActionViewshipped byservice-aiis now an actual operator console rather than a flat grid:- Drawer detail panel — clicking any row opens a side drawer
(
navigation: { mode: 'drawer', view: 'detail' }) with four sections: Proposal · Tool input · Conversation context · Decision. - JSON widget on
tool_input,result, anderrorso structured tool arguments and responses are readable without copy-pasting into a formatter. - Relative timestamps (
type: 'datetime-relative') onproposed_at/decided_atcolumns and form fields. - Conversation/message linkbacks — the existing
Field.lookupreferences toai_conversations/ai_messagesare surfaced in a collapsed "Conversation context" section, giving operators one-click access from a pending action back to the chat that proposed it. - Status-conditional fields via
visibleOnpredicates —rejection_reasononly appears for rejected rows,erroronly for failed rows, etc. - Per-row approve/reject buttons on the Pending tab via
rowActionspointing at the existingapprove_pending_action/reject_pending_actionobject actions; the same actions also render in the drawer header. - Status-coloured rows to make pending vs failed vs executed scannable.
Snapshot-style tests in
__tests__/ai-pending-action.view.test.tslock the shape so future Studio contract changes (widget renames, navigation modes) fail loudly in one place.This is a metadata-only change — Studio (
@object-ui/studio) interprets the new view automatically. No backend, REST, or HITL semantics changed; the end-to-end demos inexamples/app-todo/test/ai-hitl*.test.tscontinue to pass unmodified. - Drawer detail panel — clicking any row opens a side drawer
(
-
13a4f38: Actions-as-tools Phase 2: the AI tool runtime can now dispatch
type:'api'andtype:'flow'actions in addition totype:'script'.- New exported
ApiActionClientinterface andcreateFetchApiClient({ baseUrl, headers, fetch })factory — default fetch-based dispatch resolves relativetargetpaths againstbaseUrl, throws on non-2xx with${method} ${url} → ${status}: ${body}, and JSON-parses the response. - New exported
buildApiRequestBody(action, args, record, recordId)helper — honoursbodyShape.wrap,recordIdParam+recordIdField(defaults to'id'), and mergesbodyExtralast so constants win. ActionToolsContextextended (additive):automation,apiClient,apiBaseUrl,apiHeaders.actionSkipReason()gains an optional secondctxparameter that returns precise wiring-availability reasons ('no automation service available','no apiClient or apiBaseUrl configured'). Studio-only types (url/modal/form) and all dangerous variants (confirmText,mode:'delete',variant:'danger') remain skipped.AIServicePluginoptions acceptapiActionBaseUrl(falls back toOS_AI_ACTION_API_BASE_URL) andapiActionHeaders; the plugin now resolves theautomationservice silently and threads everything intoregisterActionsAsTools.
Net result: every non-destructive declarative action with a target —
script,api,flow— is now LLM-callable end-to-end as soon as the corresponding wiring is in place. - New exported
-
bce47a0: HITL Phase 3 — end-to-end demos + bug fix in handler-engine adapter.
Two runnable integration demos for the action-approval queue ship under
examples/app-todo/test/:ai-hitl.test.ts— drives the tool registry directly (no LLM). Assertsvariant:'danger'actions register as tools, invocation returnspending_approval, row persists,approvePendingAction(id, actor)re-runs the handler, row flips toexecuted. Reject path covered too. Run withpnpm --filter @example/app-todo test:hitl.ai-hitl-llm.test.ts— same scenario behind a real model on Vercel AI Gateway. The LLM autonomously picksaction_delete_completed, the framework gates the call withpending_approval, the model summarises the wait without retrying, and the operator-side approve completes the deletion. Gated onAI_GATEWAY_API_KEY. Run withAI_GATEWAY_API_KEY=... pnpm --filter @example/app-todo test:hitl:llm.
While wiring the demos, two bugs surfaced in the bypass-approval dispatcher and the handler-engine adapter:
-
Bulk delete from declarative handlers was silently failing. The adapter built by
buildHandlerEngineAdapter()wrapped multi-id deletes asengine.delete(obj, { where: { id: { $in: ids } } }), butObjectQLEngine.delete()prefers the scalaridbranch wheneverwhere.idis set — so the{ $in: [...] }object was forwarded todriver.delete(scalar)and rejected as"Wrong API use: tried to bind a value of an unknown type ([object Object])". The adapter now loops scalar deletes, which is correct and driver-agnostic. -
Approval pathway swallowed handler errors.
createActionToolHandlerreturns a{ ok: false, error }envelope on failure rather than throwing. The pre-registered bypass dispatcher just JSON-parsed and returned that envelope, soapprovePendingActionthought the run succeeded and flipped the row toexecuted. The dispatcher now treatsok === falseas a thrown error, so failed approvals are correctly persisted asstatus: 'failed'with the original message.
Also: added
delete/remove/purge/destroy/erasetoMemoryLLMAdapter.ACTION_VERBSso the in-memory adapter can route delete-style intents during tests that don't have a real LLM.Docs:
content/docs/guides/ai-capabilities.mdxnow points at the two integration demos with copy-pasteable run commands. -
449e35d: Real-LLM smoke test for the
data_chatagent loop, plus twoquery_datarobustness fixes shaken out by running it againstopenai/gpt-4.1-minivia the Vercel AI Gateway.query_datatool fixes- Removed the LLM-controllable
modelparameter from the public tool schema. Frontier models were hallucinatingtext-davinci-003and other long-dead model ids, breaking every plan generation. - Switched the structured-output filter shape from
z.record(...)(which emitspropertyNamesin JSON Schema, rejected by OpenAI Structured Outputs) to awhereJsonstring field. The model emits a JSON-encoded ObjectQL filter; the tool parses & validates it before execution. This also fixes a parallel issue with OpenAI's strict mode requiring every property to appear inrequired. - Switched all optional fields to
.nullable()so the planner Zod schema satisfies OpenAI Structured Outputs' "every property must be required" rule. - Beefed up the planner system prompt with explicit operator hints — most
importantly: use
$containsfor partial string matches ("task named Foo"→{"subject":{"$contains":"Foo"}}), not equality. Without this hint the model defaulted to exact-match equality and never found anything.
New smoke test
examples/app-todo/test/ai-llm.test.ts(gated onAI_GATEWAY_API_KEY): boots the full ObjectStack, registersquery_data+ the six auto-generatedaction_*tools, sends "Please mark the 'Build' task as complete." to a real LLM, and asserts that- the model picked the right tools in the right order
(
query_data→action_complete_task), - a task row actually flipped to
completed, and - an
ai_traceschat_with_toolsrow landed.
Run with:
pnpm --filter @example/app-todo test:llm.Verified end-to-end against
openai/gpt-4.1-mini(~6.6 s, 2 tool calls, 1 task completed, trace persisted). - Removed the LLM-controllable
-
Updated dependencies [b4c74a9]
- @objectstack/spec@6.2.0
- @objectstack/core@6.2.0
- @objectstack/spec@6.1.1
- @objectstack/core@6.1.1
-
93c0589: AI v1: Actions-as-Tools — every declarative UI
Actionoftype: 'script'is now auto-exposed as an AI-callable tool namedaction_<name>. Agents can perform business operations ("complete the groceries task") via natural language, routed through the samedataEngine.executeAction()dispatcher Studio uses. This is the write-side counterpart toquery_data.Highlights
registerActionsAsTools(toolRegistry, { metadata, dataEngine })walks every object'sactions[]and registers script-type ones, auto-injecting arecordIdargument for row-context actions and inheriting JSON-Schema parameter types from the owning object's fields.- Safety filters skip destructive actions by default:
confirmText,mode: 'delete',variant: 'danger', or explicitaiExposed: false. - New
aiExposed?: booleanflag onActionSchemafor fine-grained opt-out. - New
actions_executorskill bundle subscribes toaction_*(wildcard tool names now supported inSkillSchema.tools). - The built-in
data_chatagent now references bothdata_explorerandactions_executorskills, so users get read + write capabilities out of the box. MemoryLLMAdapterlearned a small two-step heuristic — when it sees an action verb ("complete", "start", "clone", ...) it routes to the matchingaction_*tool, resolvingrecordIdfrom any priorquery_dataresult.- New
examples/app-todo/test/ai-action.test.tsdemo proves the loop: user says "please complete the groceries task" → agent finds the task → agent callsaction_complete_task→ task status flips →ai_tracesrecords the run.
Breaking changes
None.
aiExposedis additive; existing actions remain exposed unless they fail an existing safety filter.Phase-1 limitations (Phase-2 roadmap items)
- Only
type: 'script'actions;api/flow/url/modal/formskipped. - No human-in-the-loop approval flow for destructive actions yet.
- No CEL evaluation of
visible/disabledpredicates against agent context. - No bulk action support (single-record only).
- Updated dependencies [93c0589]
- @objectstack/spec@6.1.0
- @objectstack/core@6.1.0
-
dbc4f7d: feat(ai): v1 AI capabilities — ModelRegistry, structured output, tracing, schema retrieval, and
query_datatoolThis release lights up the first concrete capabilities on the slimmed AI protocol. All additions are non-breaking — new contract methods are optional and existing callers keep working unchanged.
-
ModelRegistry (
@objectstack/service-ai): in-memory runtime registry forAI.ModelConfig. Wire models viaAIServicePluginOptions.models/defaultModelId. Exposesget,getOrThrow,getDefault,list, andestimateCost(modelId, usage)for ex-post token cost computation. -
ai_traces object + auto-tracing: every LLM call from
AIService(chat,complete,stream_chat,chat_with_tools,generate_object,embed) is now instrumented with latency, token usage, status, and (when pricing is registered) cost. The defaultObjectQLTraceRecorderis auto-wired when the runtime exposes anIDataEngine, persisting rows to the newai_tracesobject. Drop in a customTraceRecorderviaAIServicePluginOptions.traceRecorder, or passnullto opt out. -
Structured output (
IAIService.generateObject): new optional method onIAIServiceandLLMAdapterthat returns a parsed, schema-validated object instead of free-form text. Implemented end-to-end inVercelLLMAdapter(uses the AI SDK'sgenerateObject— provider strict-mode is automatic when supported).MemoryLLMAdapterships a deterministic heuristic implementation so tests and demos work without an API key. -
SchemaRetriever: lightweight keyword-based retriever over
IMetadataService.listObjects(). Scores by object name (×3), label/plural (×2), description (×1), field name (×2), and field label (×1) with English stop-word filtering. Tokenisation splits snake_case sotodo_taskin a query matchesname: 'todo_task'.SchemaRetriever.renderSnippet()produces a Markdown block ready to inject into a system prompt — no embeddings, no extra infra. -
query_datatool: auto-registered when AI + Metadata + Data engine are all present. Takes a natural-languagerequest, retrieves relevant schemas, asks the model for a structuredQueryPlanviagenerateObject, validates the plan targets a real object, and executes it throughIDataEngine.find. Returns{ plan, count, records }. The composed primitive that closes the loop from "ask in English" → "validated SQL-shaped result". -
Working demo in
examples/app-todo:pnpm --filter @example/app-todo test:aiboots the full Todo stack, invokesquery_dataagainst the seeded tasks, and verifies the call lands inai_traces. Zero API keys, ~3 seconds end-to-end. Serves as the canonical reference for wiring AI into a real app.
- Strict tool schemas: nested
orderByandaggregationsitems indata-toolsnow declareadditionalProperties: false+required, matching the top-level contract and making them safe for provider strict mode.
TraceOperationvalues are now snake_case (stream_chat,chat_with_tools,generate_object) to match the project's data-value convention and so theai_traces.operationselect validates. CustomTraceRecorderimplementations that hard-code the old camelCase names need to be updated. The values are an internal observability artefact — no public protocol surface exposes them.
zodis now a direct dependency of@objectstack/service-ai(previously transitive viaai) because contract signatures and the new tool definition usez.ZodTypetypes directly.- All new methods on
IAIService/LLMAdapterare optional — existing custom adapters and callers continue to work without changes. - 12 new unit tests cover
ModelRegistry(cost math, defaults, throwing lookups) andSchemaRetriever(scoring, snake_case tokenisation, limits, snippet rendering). Full suite: 323/323 ✓.
-
- Updated dependencies [629a716]
- Updated dependencies [dbc4f7d]
- Updated dependencies [944f187]
- @objectstack/spec@6.0.0
- @objectstack/core@6.0.0
- Updated dependencies [bab2b20]
- Updated dependencies [fa011d8]
- Updated dependencies [b806f58]
- @objectstack/spec@5.2.0
- @objectstack/core@5.2.0
- Updated dependencies [75f4ee6]
- Updated dependencies [823d559]
- @objectstack/spec@5.1.0
- @objectstack/core@5.1.0
- Updated dependencies [2f9073a]
- @objectstack/spec@5.0.0
- @objectstack/core@5.0.0
- Updated dependencies [2869891]
- @objectstack/spec@4.2.0
- @objectstack/core@4.2.0
- @objectstack/spec@4.1.1
- @objectstack/core@4.1.1
- Updated dependencies [2108c30]
- Updated dependencies [23db640]
- @objectstack/spec@4.1.0
- @objectstack/core@4.1.0
- 15e0df6: chore: unify all package versions to a single patch release
- Updated dependencies [15e0df6]
- @objectstack/spec@4.0.5
- @objectstack/core@4.0.5
- Updated dependencies [326b66b]
- @objectstack/spec@4.0.4
- @objectstack/core@4.0.4
- ee39bff: fix ai.
- @objectstack/spec@4.0.3
- @objectstack/core@4.0.3
- 5f659e9: fix ai
- Updated dependencies [5f659e9]
- @objectstack/spec@4.0.2
- @objectstack/core@4.0.2
- Route auth/permissions metadata: Every route definition (
RouteDefinition) now declaresauthandpermissionsfields, enabling HTTP server adapters to enforce authentication and authorization automatically. - User context on RouteRequest:
RouteRequestnow carries an optionaluser: RouteUserContextobject populated by the auth middleware, providinguserId,displayName,roles, andpermissions. - Conversation ownership enforcement: Conversation routes (create, list, add message, delete) are scoped to the authenticated user when a user context is present and the conversation has a
userId. For backward compatibility, requests without user context and conversations created without auserIdremain accessible under the existing behavior. - Enhanced tool-call loop error handling:
chatWithToolsnow tracks tool execution errors across iterations and supports anonToolErrorcallback ('continue'|'abort') for fine-grained error control. streamChatWithTools: New streaming tool-call loop that yields SSE events while automatically resolving intermediate tool calls.- New
RouteUserContexttype: Exported from the package for use by HTTP adapters and middleware.
- ad4e04b: service ai
- Updated dependencies [f08ffc3]
- Updated dependencies [e0b0a78]
- @objectstack/spec@4.0.0
- @objectstack/core@4.0.0
- Initial release of AI Service plugin
- LLM adapter layer with provider abstraction (memory adapter included)
- Conversation management service with in-memory persistence
- Tool registry for metadata/business tool registration
- REST/SSE route self-registration (
/api/v1/ai/*) - Kernel plugin registering as
'ai'service conforming toIAIServicecontract