@open-mercato/enterprise 0.7.1-develop.7202.1.a52dc6707c → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/agent_orchestrator/lib/sdk/defineAgent.js +20 -5
- package/dist/modules/agent_orchestrator/lib/sdk/defineAgent.js.map +2 -2
- package/dist/modules/agent_orchestrator/lib/sdk/file-agents-generated.d.js +1 -0
- package/dist/modules/agent_orchestrator/lib/sdk/file-agents-generated.d.js.map +7 -0
- package/package.json +10 -11
- package/src/modules/agent_orchestrator/lib/sdk/defineAgent.ts +48 -5
- package/src/modules/agent_orchestrator/lib/sdk/file-agents-generated.d.ts +14 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
[build:enterprise] found
|
|
1
|
+
[build:enterprise] found 696 entry points
|
|
2
2
|
[build:enterprise] built successfully
|
|
@@ -133,14 +133,29 @@ async function loadMutationToolPredicate() {
|
|
|
133
133
|
return () => "skip";
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
-
async function
|
|
137
|
-
|
|
136
|
+
async function importFileAgentsManifest() {
|
|
137
|
+
try {
|
|
138
|
+
return await import("../../generated/file-agents.generated.js");
|
|
139
|
+
} catch {
|
|
140
|
+
}
|
|
138
141
|
try {
|
|
139
|
-
|
|
140
|
-
descriptors = manifest.fileAgentDescriptors ?? [];
|
|
142
|
+
return await import("@/.mercato/generated/file-agents.generated");
|
|
141
143
|
} catch {
|
|
142
|
-
return;
|
|
143
144
|
}
|
|
145
|
+
try {
|
|
146
|
+
const { findGeneratedFile, compileAndImportGenerated } = await import("@open-mercato/ai-assistant/modules/ai_assistant/lib/generated-registry-loader");
|
|
147
|
+
const tsPath = findGeneratedFile("file-agents.generated.ts");
|
|
148
|
+
if (!tsPath) return null;
|
|
149
|
+
return await compileAndImportGenerated(tsPath);
|
|
150
|
+
} catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function loadFileAgents() {
|
|
155
|
+
let descriptors;
|
|
156
|
+
const manifest = await importFileAgentsManifest();
|
|
157
|
+
if (!manifest) return;
|
|
158
|
+
descriptors = manifest.fileAgentDescriptors ?? [];
|
|
144
159
|
const { compileOutcome } = await import("./outcomeSchema.js");
|
|
145
160
|
const { registerAgentSkills } = await import("../runtime/fileAgentSkills.js");
|
|
146
161
|
const isMutationTool = await loadMutationToolPredicate();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/agent_orchestrator/lib/sdk/defineAgent.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n defineAiAgent,\n type AiAgentDefinition,\n} from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-agent-definition'\nimport type { ZodTypeAny } from 'zod'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { AgentType } from '../../data/validators'\nimport { getSkillEntry } from './defineSkill'\nimport type { JsonSchemaNode } from './outcomeSchema'\nimport type { AgentTokenUsage, FileAgentFile } from '../tokens/types'\n\nconst logger = createLogger('agent_orchestrator').child({ component: 'define-agent' })\n\n/**\n * What KIND of thing came back.\n *\n * `research` enriches the context, `proposal` states an intent a human or a\n * rule disposes, `artifact` produces a file. All three are typed results the\n * WORKFLOW decides what to do with \u2014 an agent never writes to workflow state\n * itself, which is what keeps parallel branches safe.\n */\nexport type AgentResultKind = 'proposal' | 'research' | 'artifact'\n\n/**\n * Where an agent runs. `'native'` agents are authored with `defineAgent` and\n * execute via the Vercel AI SDK object mode inside this process on the\n * `NativeAgentRunner` (lightweight-agent-runtime spec Phase 1). `'in-process'`\n * is the accepted legacy alias for the same runner \u2014 the union stays\n * ADDITIVE-ONLY per BACKWARD_COMPATIBILITY.md, so entries registered with\n * `'in-process'` dispatch identically. `'opencode'` agents are authored as\n * `agents/<id>/` file conventions (AGENT.md + OUTCOME.md), registered via\n * `registerFileAgent`, and run on the OpenCode runtime (deprecation planned \u2014\n * see the spec's Phase 3/6).\n */\nexport type AgentRuntime = 'in-process' | 'native' | 'opencode' | 'external'\n\n/**\n * Runtime labels that execute on the native (in-process) runner. Persisted\n * `agent_runs.runtime` values from before the `'native'` flip carry\n * `'in-process'`; new runs stamp `'native'`. Filters, rollups, and dashboards\n * MUST treat the two labels as ONE cohort (Migration & BC section of the\n * lightweight-agent-runtime spec).\n */\nexport const NATIVE_RUNTIME_VALUES = ['native', 'in-process'] as const\n\n/**\n * Tool id of the built-in delegation tool (declared in agent_orchestrator's\n * `ai-tools.ts`). An agent that lists `subAgents` automatically gains this tool,\n * letting it run other (read-only, researcher) agents as sub-agents \u2014 and fan\n * them out in parallel. Sub-agents stay propose-only: they only inform.\n */\nexport const DELEGATE_TOOL_ID = 'agent_orchestrator.delegate_agent'\n\nexport interface DefineAgentInput {\n /** 'module.agent' \u2014 STABLE contract id (see BACKWARD_COMPATIBILITY.md). */\n id: string\n moduleId: string\n label: string\n description: string\n /** System prompt \u2192 AiAgentDefinition.systemPrompt. */\n instructions: string\n /**\n * defineAiTool ids the agent may call. Propose-only stays structural: the agent\n * is declared read-only, so any `isMutation: true` tool is stripped by the\n * runtime \u2014 only READ tools survive. When non-empty the runtime runs a read-only\n * tool loop (`runAiAgentObject({ enableTools })`) so the agent can gather data\n * before proposing; all writes still flow through proposal \u2192 disposition \u2192\n * effector, never the agent directly.\n */\n tools?: string[]\n /**\n * Skill ids (see `defineSkill`). Each resolved skill injects its instructions\n * into the system prompt and unions its read-only tools into the agent's\n * allowlist. Unknown ids are warned and skipped (the agent still loads).\n */\n skills?: string[]\n /**\n * Agent ids this agent may delegate to as sub-agents. When non-empty the agent\n * gains the `DELEGATE_TOOL_ID` tool and a prompt section listing the ids; the\n * model calls them (in parallel when independent). Sub-agents MUST be\n * researcher (they inform; only the parent proposes) and may not themselves\n * delegate \u2014 enforced by the delegate tool, capping the tree depth.\n */\n subAgents?: string[]\n defaultProvider?: string\n defaultModel?: string\n /** Object-safe loop subset only. */\n loop?: { maxSteps?: number }\n /**\n * Runtime the agent executes on. `defineAgent` only ever produces agents for\n * the native in-process runner (registered as `'native'`), so this is\n * reserved for future use and kept additive.\n */\n runtime?: AgentRuntime\n /** Zod from data/validators.ts; IS the output.schema (single source). */\n result: { kind: AgentResultKind; schema: ZodTypeAny }\n /**\n * What this agent is FOR (`researcher` | `decision_maker` | `action`). An AUTHORING\n * declaration, unlike `result.kind`, which describes the shape that comes back.\n * Optional and additive: an agent that declares none is listed as untyped and\n * `agent_runs.agent_type` stays null for its runs.\n */\n agentType?: AgentType\n /**\n * Per-agent narrowing of the action vocabulary. The catalogue\n * (`listWorkflowSafeCommands() \u222A workflowActivityTypes()`) is the OUTER limit; this\n * list only ever INTERSECTS with it and can never widen it. An entry naming\n * something outside the catalogue is DROPPED with a warning at registration \u2014\n * fail-closed and loud, because a silently-dropped permission reads as a granted\n * one. Omitted means \"the whole catalogue\", which is what every agent had before.\n */\n allowedActions?: string[]\n /**\n * Optional example `input` surfaced by the Playground's \"Insert sample\"\n * button. Co-located with the agent so each agent ships its own runnable\n * example instead of the Playground hardcoding per-agent samples. Any JSON\n * value the agent accepts as input. Additive/BC-safe: agents without it just\n * hide the button.\n */\n sampleInput?: unknown\n /**\n * Optional fact declarations for the Caseload decision panel. Each maps a\n * labelled dot-path into the run input / proposal payload / run output so the\n * operator sees the data behind a proposal. Additive/BC-safe: agents without\n * it fall back to the panel's generic derivation.\n */\n facts?: AgentFact[]\n}\n\n/**\n * File-plane opt-in (#12) for an OpenCode file agent. When enabled (and the\n * global `OM_OPENCODE_FILES_ENABLED` kill-switch is on), the runner leases a\n * per-run sandbox, the frontmatter grants sandbox-scoped `write`/`edit`/`read`,\n * and outputs written to `out/` are captured as `AgentRunArtifact`s. OpenCode-only.\n */\nexport type FileAgentFilesConfig = {\n enabled: true\n /** Accept staged attachment inputs (default true when enabled). */\n inputs?: boolean\n /** Capture artifacts from the sandbox `out/` dir (default true when enabled). */\n outputs?: boolean\n /** Allow `bash` in the sandbox (default false \u2014 large blast radius). */\n bash?: boolean\n}\n\n/** One declared Caseload fact: label + dot-path into a run/proposal source. */\nexport type AgentFact = {\n label: string\n source: 'input' | 'payload' | 'output'\n path: string\n format?: 'text' | 'number' | 'boolean' | 'percent'\n}\n\nexport interface AgentRegistryEntry {\n id: string\n moduleId: string\n resultKind: AgentResultKind\n /** Declared agent type (see DefineAgentInput); undefined when the agent declares none. */\n agentType?: AgentType\n /**\n * Effective action vocabulary after the registration-time intersection with the\n * platform catalogue (see `lib/runtime/actionVocabulary.ts`). `undefined` means the\n * agent declared no narrowing and is bounded by the catalogue alone.\n */\n allowedActions?: string[]\n schema: ZodTypeAny\n tools: string[]\n skills: string[]\n subAgents: string[]\n label: string\n description: string\n /** System prompt the agent runs with. */\n instructions: string\n defaultProvider?: string\n defaultModel?: string\n /** Object-safe loop subset (see DefineAgentInput). */\n loop?: { maxSteps?: number }\n /** Runtime the agent executes on. Always set (`'native'` for `defineAgent` agents; `'in-process'` accepted as a legacy alias). */\n runtime: AgentRuntime\n /** Optional Playground \"Insert sample\" input (see DefineAgentInput). */\n sampleInput?: unknown\n /**\n * Raw OUTCOME JSON-Schema as authored, for file-defined agents. `schema` above\n * is the compiled envelope; this is the inner shape the author declared, kept\n * verbatim so consumers (agents API, workflow INVOKE_AGENT mapping pickers)\n * read the same subset the generator validated. In-process agents leave it\n * undefined \u2014 their outcome is derived from `schema` instead\n * (lib/sdk/agentOutcomeContract.ts).\n */\n outcomeSchema?: JsonSchemaNode\n /** Optional Caseload fact declarations (see DefineAgentInput). */\n facts?: AgentFact[]\n /** File-plane opt-in (#12). OpenCode file agents only; undefined for native agents. */\n files?: FileAgentFilesConfig\n /**\n * Baked token-usage estimate of the agent's construction files. File-defined\n * (`runtime: 'opencode'`) agents only; undefined for native agents.\n */\n tokenUsage?: AgentTokenUsage\n /**\n * Baked raw content of the agent's construction files, for the read-only Files\n * tab. File-defined (`runtime: 'opencode'`) agents only; undefined for native.\n */\n sourceFiles?: FileAgentFile[]\n}\n\nconst registry = new Map<string, AgentRegistryEntry>()\n\nexport function defineAgent(input: DefineAgentInput): AiAgentDefinition {\n if (registry.has(input.id)) {\n throw new Error(`[internal] duplicate agent id \"${input.id}\"`)\n }\n // Resolve declared skills from the registry (ai-skills.ts must be imported\n // before the agents that reference them \u2014 ai-agents.ts does this at the top).\n // Each skill injects real instructions AND contributes its read-only tools.\n const ownTools = input.tools ?? []\n const skillIds = input.skills ?? []\n const resolvedSkills = skillIds\n .map((skillId) => {\n const skill = getSkillEntry(skillId)\n if (!skill) {\n logger.warn('agent references unknown skill; skipping', { agentId: input.id, skillId })\n }\n return skill\n })\n .filter((skill): skill is NonNullable<typeof skill> => !!skill)\n const skillTools = resolvedSkills.flatMap((skill) => skill.tools)\n // Sub-agents: when declared, the agent gains the delegation tool so it can run\n // other agents as read-only sub-agents (and fan them out in parallel).\n const subAgents = input.subAgents ?? []\n const delegationTools = subAgents.length > 0 ? [DELEGATE_TOOL_ID] : []\n const effectiveTools = Array.from(new Set([...ownTools, ...skillTools, ...delegationTools]))\n\n registry.set(input.id, {\n id: input.id,\n moduleId: input.moduleId,\n resultKind: input.result.kind,\n agentType: input.agentType,\n // Stored as DECLARED here; `narrowRegisteredActionVocabularies` intersects it with\n // the platform catalogue once every module has registered, because the catalogue\n // lives behind an OPTIONAL peer that can only be reached asynchronously.\n allowedActions: input.allowedActions ? [...input.allowedActions] : undefined,\n schema: input.result.schema,\n tools: ownTools,\n skills: skillIds,\n subAgents,\n label: input.label,\n description: input.description,\n instructions: input.instructions,\n defaultProvider: input.defaultProvider,\n defaultModel: input.defaultModel,\n loop: input.loop,\n runtime: 'native',\n sampleInput: input.sampleInput,\n facts: input.facts,\n })\n const skillSections = resolvedSkills.map(\n (skill) => `## Skill: ${skill.label}\\n${skill.instructions}`,\n )\n const subAgentSection = subAgents.length\n ? `## Sub-agents\\nYou may delegate independent sub-tasks to these agents by calling the \\`${DELEGATE_TOOL_ID}\\` tool with { agentId, input }. When several sub-tasks are independent, issue multiple delegate calls in the SAME step so they run in parallel, then combine their results. Available sub-agents: ${subAgents.join(', ')}.`\n : null\n const systemPrompt = [input.instructions, ...skillSections, ...(subAgentSection ? [subAgentSection] : [])]\n .join('\\n\\n')\n return defineAiAgent({\n id: input.id,\n moduleId: input.moduleId,\n label: input.label,\n description: input.description,\n systemPrompt,\n // Effective read-only allowlist = the agent's own tools + every read tool its\n // skills contribute. Read-only policy still strips any mutation tool.\n allowedTools: effectiveTools,\n executionMode: 'object',\n // Read-only is the propose-only guarantee: the runtime strips every\n // `isMutation: true` tool, so even a mis-declared write tool can never fire.\n // Reads are allowed; writes only ever happen via the proposal/effector path.\n readOnly: true,\n mutationPolicy: 'read-only',\n defaultProvider: input.defaultProvider,\n defaultModel: input.defaultModel,\n loop: input.loop,\n output: { schemaName: input.id.replace(/\\W+/g, '_'), schema: input.result.schema },\n })\n}\n\n/**\n * Register a file-defined (OpenCode) agent directly into the SAME registry that\n * `defineAgent` writes to, so file agents are discoverable via `getAgentEntry`,\n * `listAgentEntries`, and the Agents list/detail without going through\n * `defineAiAgent` (file agents don't run in-process, so they need no\n * `AiAgentDefinition`). Same dup-id guard as `defineAgent`.\n */\nexport function registerFileAgent(entry: AgentRegistryEntry): void {\n if (registry.has(entry.id)) {\n throw new Error(`[internal] duplicate agent id \"${entry.id}\"`)\n }\n registry.set(entry.id, entry)\n}\n\nexport function getAgentEntry(id: string): AgentRegistryEntry | undefined {\n return registry.get(id)\n}\n\nexport function listAgentEntries(): AgentRegistryEntry[] {\n return [...registry.values()]\n}\n\nlet agentsLoadPromise: Promise<void> | null = null\n\n/**\n * Load every module's agents into the registry. `defineAgent` registers by import\n * side effect, so an agent declared in ANY module is only discoverable once that\n * module's `ai-agents.ts` has executed. The generated `ai-agents.generated.ts`\n * aggregator statically imports every module's `ai-agents.ts`; ai_assistant's\n * `loadAgentRegistry()` imports that aggregator, so calling it runs every\n * `defineAgent()` across all modules and fills this registry. Packages must not\n * import the app-generated file directly, so we go through ai_assistant.\n *\n * Fallback: if the aggregator is unavailable (tests, fresh checkout before\n * `yarn generate`), import agent_orchestrator's own `ai-agents.ts` so at least\n * its built-in agents resolve.\n */\nasync function loadAllAgentModules(): Promise<void> {\n try {\n const { loadAgentRegistry } = await import(\n '@open-mercato/ai-assistant/modules/ai_assistant/lib/agent-registry'\n )\n await loadAgentRegistry()\n } catch {\n // ignore \u2014 fall through to the local import below\n }\n if (registry.size === 0) {\n await import('../../ai-agents')\n }\n await loadFileAgents()\n await narrowRegisteredActionVocabularies()\n}\n\n/**\n * Apply `effective = catalogue \u2229 agent.allowedActions` to every registered agent.\n *\n * It runs HERE, at the end of the registry load, rather than inside `defineAgent`:\n * `defineAgent` is synchronous and the catalogue lives in core `workflows`, an OPTIONAL\n * peer reachable only through a dynamic import. This is still registration time \u2014 no\n * agent is observable through `getAgentEntry`/`listAgentEntries` until\n * `ensureAgentsLoaded` resolves \u2014 and the check runs AGAIN before the effect\n * (`executeProposal`), because a tenant may revoke a safe command after registration.\n */\nasync function narrowRegisteredActionVocabularies(): Promise<void> {\n const declaring = [...registry.values()].filter((entry) => entry.allowedActions !== undefined)\n if (declaring.length === 0) return\n try {\n const { loadActionVocabulary, narrowAllowedActions } = await import('../runtime/actionVocabulary')\n const vocabulary = await loadActionVocabulary()\n for (const entry of declaring) {\n entry.allowedActions = narrowAllowedActions(vocabulary, entry.id, entry.allowedActions ?? [])\n }\n } catch (err) {\n logger.warn('action vocabulary narrowing skipped', {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n}\n\n/**\n * Load file-defined (OpenCode) agents from the committed, generator-owned\n * manifest (`generated/file-agents.generated.ts`). The manifest stores PLAIN\n * data (raw JSON-Schema, not a Zod instance); for each descriptor we recompile\n * the result schema via `compileOutcome` and register it with `runtime:'opencode'`.\n * The import is guarded: the manifest may be absent or empty before the first\n * `yarn generate`, and a descriptor whose schema fails to compile is skipped\n * (warned) so one bad agent never blocks the rest.\n */\n/**\n * Resolve a predicate `(toolId) => 'mutating' | 'read' | 'unknown'` for a declared\n * tool id, used by the load-time propose-only check below.\n *\n * This load-time check is an EARLY, advisory rejection \u2014 NOT the load-bearing\n * propose-only boundary. The actual runtime gates (which always hold) are: (1) the\n * generated OpenCode agent file's `tools` deny-by-default allowlist + its\n * `permission: { write/edit/bash: deny }` block, and (2) the per-run session-token\n * ACL the MCP server re-checks on every tool call. The MCP server does NOT strip\n * `isMutation` tools, so those two gates \u2014 not this predicate \u2014 are what enforce\n * propose-only.\n *\n * When the registry IS loadable we await `loadAllModuleTools()` first, so a tool\n * id absent afterward is genuinely unknown (a config error): we fail CLOSED and\n * report `'unknown'` (the caller rejects the agent). When the registry import\n * itself is unavailable (e.g. a unit-test harness without the AI tooling) the\n * check cannot run at all and reports `'skip'`; registration proceeds and the two\n * runtime gates above remain in force.\n */\ntype MutationCheck = (toolId: string) => 'mutating' | 'read' | 'unknown' | 'skip'\n\nasync function loadMutationToolPredicate(): Promise<MutationCheck> {\n try {\n const { loadAllModuleTools } = await import(\n '@open-mercato/ai-assistant/modules/ai_assistant/lib/tool-loader'\n )\n const { getToolRegistry } = await import(\n '@open-mercato/ai-assistant/modules/ai_assistant/lib/tool-registry'\n )\n try {\n await loadAllModuleTools()\n } catch {\n // Tools may already be loaded, or loading is a no-op in this harness.\n }\n const registry = getToolRegistry()\n return (toolId: string): 'mutating' | 'read' | 'unknown' | 'skip' => {\n const tool = registry.getTool(toolId) as { isMutation?: boolean } | undefined\n if (!tool) return 'unknown'\n return tool.isMutation ? 'mutating' : 'read'\n }\n } catch {\n return () => 'skip'\n }\n}\n\nasync function loadFileAgents(): Promise<void> {\n let descriptors: import('../../generated/file-agents.generated').FileAgentDescriptor[]\n try {\n const manifest = await import('../../generated/file-agents.generated')\n descriptors = manifest.fileAgentDescriptors ?? []\n } catch {\n return\n }\n const { compileOutcome } = await import('./outcomeSchema')\n const { registerAgentSkills } = await import('../runtime/fileAgentSkills')\n const isMutationTool = await loadMutationToolPredicate()\n // Flatten each descriptor's nested sub-agents (Phase 4) so they register as\n // individual file agents too: they are researcher, individually runnable, and\n // discoverable in the Agents list/detail. A sub-agent flows through the SAME\n // propose-only mutation-tool gate below; the registry dup-id guard prevents a\n // duplicate id (a sub-agent that collides with another agent's id is skipped).\n const allDescriptors = descriptors.flatMap((descriptor) => [\n descriptor,\n ...(descriptor.subAgentDescriptors ?? []),\n ])\n for (const descriptor of allDescriptors) {\n if (registry.has(descriptor.id)) continue\n // Propose-only early check (contract C8): a file agent may NEVER declare a\n // tool registered with `isMutation: true`. This is defense-in-depth on top of\n // the load-bearing runtime gates (the agent-file read-only allowlist +\n // write/edit/bash deny, and the per-call session-token ACL \u2014 see\n // `loadMutationToolPredicate`). When the tool registry is resolvable we fail\n // CLOSED: a declared tool that is `mutating` OR `unknown` (absent after\n // `loadAllModuleTools`, i.e. a config error) rejects the agent here so it never\n // reaches registration. `skip` means the registry could not be loaded at all\n // (test harness without AI tooling) \u2014 we let it through and rely on the runtime\n // gates. A rejected agent is skipped + warned so the rest still load.\n const offending = descriptor.tools\n .map((toolId) => ({ toolId, verdict: isMutationTool(toolId) }))\n .find(({ verdict }) => verdict === 'mutating' || verdict === 'unknown')\n if (offending) {\n logger.warn(\n 'file agent declares a non read-only tool; file agents are propose-only and may only list known read-only tools \u2014 skipping registration',\n { agentId: descriptor.id, verdict: offending.verdict, toolId: offending.toolId },\n )\n continue\n }\n try {\n const { resultSchema } = compileOutcome({\n kind: descriptor.resultKind,\n schema: descriptor.outcomeSchema,\n })\n registerFileAgent({\n id: descriptor.id,\n moduleId: descriptor.moduleId,\n resultKind: descriptor.resultKind,\n schema: resultSchema,\n tools: descriptor.tools,\n skills: descriptor.skills,\n subAgents: descriptor.subAgents,\n label: descriptor.label,\n description: descriptor.description,\n instructions: descriptor.instructions,\n defaultProvider: descriptor.provider,\n defaultModel: descriptor.model,\n loop: descriptor.maxSteps != null ? { maxSteps: descriptor.maxSteps } : undefined,\n runtime: 'opencode',\n outcomeSchema: descriptor.outcomeSchema,\n sampleInput: descriptor.sampleInput,\n facts: descriptor.facts,\n tokenUsage: descriptor.tokenUsage,\n sourceFiles: descriptor.sourceFiles,\n })\n // Phase 3: register the agent's resolved skill content into the runtime\n // lookup so `load_skill` can return it without fs access. Optional + BC: a\n // descriptor without `skillsContent` registers no skills (cleared).\n registerAgentSkills(descriptor.id, descriptor.skillsContent ?? [])\n } catch (err) {\n logger.warn('failed to load file agent', {\n agentId: descriptor.id,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n}\n\n/**\n * Ensure agents from every module have registered before the registry is read.\n * Idempotent: skips when already populated, and the underlying imports are no-ops\n * once their modules have executed. Code paths that don't transitively import any\n * `ai-agents.ts` \u2014 notably the workflow background executor and the agents API \u2014\n * call this first so they never read an empty registry.\n */\nexport async function ensureAgentsLoaded(): Promise<void> {\n // NOTE: do NOT short-circuit on `registry.size > 0`. In-process agents register\n // via `ai-agents.ts` import side-effects (e.g. the global AiAssistantLauncher\n // triggers `loadAgentRegistry()` on every backoffice page), so the registry is\n // often already non-empty before the first call here. File-defined (OpenCode)\n // agents only load inside `loadAllAgentModules()` \u2192 `loadFileAgents()`, so a\n // size-based early return would skip them entirely. The memoized\n // `agentsLoadPromise` already guarantees the full load runs exactly once.\n if (!agentsLoadPromise) {\n agentsLoadPromise = loadAllAgentModules()\n .then(() => undefined)\n .catch((err) => {\n agentsLoadPromise = null\n throw err\n })\n }\n await agentsLoadPromise\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/// <reference path=\"./file-agents-generated.d.ts\" />\nimport {\n defineAiAgent,\n type AiAgentDefinition,\n} from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-agent-definition'\nimport type { ZodTypeAny } from 'zod'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { AgentType } from '../../data/validators'\nimport { getSkillEntry } from './defineSkill'\nimport type { JsonSchemaNode } from './outcomeSchema'\nimport type { AgentTokenUsage, FileAgentFile } from '../tokens/types'\n\nconst logger = createLogger('agent_orchestrator').child({ component: 'define-agent' })\n\n/**\n * What KIND of thing came back.\n *\n * `research` enriches the context, `proposal` states an intent a human or a\n * rule disposes, `artifact` produces a file. All three are typed results the\n * WORKFLOW decides what to do with \u2014 an agent never writes to workflow state\n * itself, which is what keeps parallel branches safe.\n */\nexport type AgentResultKind = 'proposal' | 'research' | 'artifact'\n\n/**\n * Where an agent runs. `'native'` agents are authored with `defineAgent` and\n * execute via the Vercel AI SDK object mode inside this process on the\n * `NativeAgentRunner` (lightweight-agent-runtime spec Phase 1). `'in-process'`\n * is the accepted legacy alias for the same runner \u2014 the union stays\n * ADDITIVE-ONLY per BACKWARD_COMPATIBILITY.md, so entries registered with\n * `'in-process'` dispatch identically. `'opencode'` agents are authored as\n * `agents/<id>/` file conventions (AGENT.md + OUTCOME.md), registered via\n * `registerFileAgent`, and run on the OpenCode runtime (deprecation planned \u2014\n * see the spec's Phase 3/6).\n */\nexport type AgentRuntime = 'in-process' | 'native' | 'opencode' | 'external'\n\n/**\n * Runtime labels that execute on the native (in-process) runner. Persisted\n * `agent_runs.runtime` values from before the `'native'` flip carry\n * `'in-process'`; new runs stamp `'native'`. Filters, rollups, and dashboards\n * MUST treat the two labels as ONE cohort (Migration & BC section of the\n * lightweight-agent-runtime spec).\n */\nexport const NATIVE_RUNTIME_VALUES = ['native', 'in-process'] as const\n\n/**\n * Tool id of the built-in delegation tool (declared in agent_orchestrator's\n * `ai-tools.ts`). An agent that lists `subAgents` automatically gains this tool,\n * letting it run other (read-only, researcher) agents as sub-agents \u2014 and fan\n * them out in parallel. Sub-agents stay propose-only: they only inform.\n */\nexport const DELEGATE_TOOL_ID = 'agent_orchestrator.delegate_agent'\n\nexport interface DefineAgentInput {\n /** 'module.agent' \u2014 STABLE contract id (see BACKWARD_COMPATIBILITY.md). */\n id: string\n moduleId: string\n label: string\n description: string\n /** System prompt \u2192 AiAgentDefinition.systemPrompt. */\n instructions: string\n /**\n * defineAiTool ids the agent may call. Propose-only stays structural: the agent\n * is declared read-only, so any `isMutation: true` tool is stripped by the\n * runtime \u2014 only READ tools survive. When non-empty the runtime runs a read-only\n * tool loop (`runAiAgentObject({ enableTools })`) so the agent can gather data\n * before proposing; all writes still flow through proposal \u2192 disposition \u2192\n * effector, never the agent directly.\n */\n tools?: string[]\n /**\n * Skill ids (see `defineSkill`). Each resolved skill injects its instructions\n * into the system prompt and unions its read-only tools into the agent's\n * allowlist. Unknown ids are warned and skipped (the agent still loads).\n */\n skills?: string[]\n /**\n * Agent ids this agent may delegate to as sub-agents. When non-empty the agent\n * gains the `DELEGATE_TOOL_ID` tool and a prompt section listing the ids; the\n * model calls them (in parallel when independent). Sub-agents MUST be\n * researcher (they inform; only the parent proposes) and may not themselves\n * delegate \u2014 enforced by the delegate tool, capping the tree depth.\n */\n subAgents?: string[]\n defaultProvider?: string\n defaultModel?: string\n /** Object-safe loop subset only. */\n loop?: { maxSteps?: number }\n /**\n * Runtime the agent executes on. `defineAgent` only ever produces agents for\n * the native in-process runner (registered as `'native'`), so this is\n * reserved for future use and kept additive.\n */\n runtime?: AgentRuntime\n /** Zod from data/validators.ts; IS the output.schema (single source). */\n result: { kind: AgentResultKind; schema: ZodTypeAny }\n /**\n * What this agent is FOR (`researcher` | `decision_maker` | `action`). An AUTHORING\n * declaration, unlike `result.kind`, which describes the shape that comes back.\n * Optional and additive: an agent that declares none is listed as untyped and\n * `agent_runs.agent_type` stays null for its runs.\n */\n agentType?: AgentType\n /**\n * Per-agent narrowing of the action vocabulary. The catalogue\n * (`listWorkflowSafeCommands() \u222A workflowActivityTypes()`) is the OUTER limit; this\n * list only ever INTERSECTS with it and can never widen it. An entry naming\n * something outside the catalogue is DROPPED with a warning at registration \u2014\n * fail-closed and loud, because a silently-dropped permission reads as a granted\n * one. Omitted means \"the whole catalogue\", which is what every agent had before.\n */\n allowedActions?: string[]\n /**\n * Optional example `input` surfaced by the Playground's \"Insert sample\"\n * button. Co-located with the agent so each agent ships its own runnable\n * example instead of the Playground hardcoding per-agent samples. Any JSON\n * value the agent accepts as input. Additive/BC-safe: agents without it just\n * hide the button.\n */\n sampleInput?: unknown\n /**\n * Optional fact declarations for the Caseload decision panel. Each maps a\n * labelled dot-path into the run input / proposal payload / run output so the\n * operator sees the data behind a proposal. Additive/BC-safe: agents without\n * it fall back to the panel's generic derivation.\n */\n facts?: AgentFact[]\n}\n\n/**\n * File-plane opt-in (#12) for an OpenCode file agent. When enabled (and the\n * global `OM_OPENCODE_FILES_ENABLED` kill-switch is on), the runner leases a\n * per-run sandbox, the frontmatter grants sandbox-scoped `write`/`edit`/`read`,\n * and outputs written to `out/` are captured as `AgentRunArtifact`s. OpenCode-only.\n */\nexport type FileAgentFilesConfig = {\n enabled: true\n /** Accept staged attachment inputs (default true when enabled). */\n inputs?: boolean\n /** Capture artifacts from the sandbox `out/` dir (default true when enabled). */\n outputs?: boolean\n /** Allow `bash` in the sandbox (default false \u2014 large blast radius). */\n bash?: boolean\n}\n\n/** One declared Caseload fact: label + dot-path into a run/proposal source. */\nexport type AgentFact = {\n label: string\n source: 'input' | 'payload' | 'output'\n path: string\n format?: 'text' | 'number' | 'boolean' | 'percent'\n}\n\nexport interface AgentRegistryEntry {\n id: string\n moduleId: string\n resultKind: AgentResultKind\n /** Declared agent type (see DefineAgentInput); undefined when the agent declares none. */\n agentType?: AgentType\n /**\n * Effective action vocabulary after the registration-time intersection with the\n * platform catalogue (see `lib/runtime/actionVocabulary.ts`). `undefined` means the\n * agent declared no narrowing and is bounded by the catalogue alone.\n */\n allowedActions?: string[]\n schema: ZodTypeAny\n tools: string[]\n skills: string[]\n subAgents: string[]\n label: string\n description: string\n /** System prompt the agent runs with. */\n instructions: string\n defaultProvider?: string\n defaultModel?: string\n /** Object-safe loop subset (see DefineAgentInput). */\n loop?: { maxSteps?: number }\n /** Runtime the agent executes on. Always set (`'native'` for `defineAgent` agents; `'in-process'` accepted as a legacy alias). */\n runtime: AgentRuntime\n /** Optional Playground \"Insert sample\" input (see DefineAgentInput). */\n sampleInput?: unknown\n /**\n * Raw OUTCOME JSON-Schema as authored, for file-defined agents. `schema` above\n * is the compiled envelope; this is the inner shape the author declared, kept\n * verbatim so consumers (agents API, workflow INVOKE_AGENT mapping pickers)\n * read the same subset the generator validated. In-process agents leave it\n * undefined \u2014 their outcome is derived from `schema` instead\n * (lib/sdk/agentOutcomeContract.ts).\n */\n outcomeSchema?: JsonSchemaNode\n /** Optional Caseload fact declarations (see DefineAgentInput). */\n facts?: AgentFact[]\n /** File-plane opt-in (#12). OpenCode file agents only; undefined for native agents. */\n files?: FileAgentFilesConfig\n /**\n * Baked token-usage estimate of the agent's construction files. File-defined\n * (`runtime: 'opencode'`) agents only; undefined for native agents.\n */\n tokenUsage?: AgentTokenUsage\n /**\n * Baked raw content of the agent's construction files, for the read-only Files\n * tab. File-defined (`runtime: 'opencode'`) agents only; undefined for native.\n */\n sourceFiles?: FileAgentFile[]\n}\n\nconst registry = new Map<string, AgentRegistryEntry>()\n\nexport function defineAgent(input: DefineAgentInput): AiAgentDefinition {\n if (registry.has(input.id)) {\n throw new Error(`[internal] duplicate agent id \"${input.id}\"`)\n }\n // Resolve declared skills from the registry (ai-skills.ts must be imported\n // before the agents that reference them \u2014 ai-agents.ts does this at the top).\n // Each skill injects real instructions AND contributes its read-only tools.\n const ownTools = input.tools ?? []\n const skillIds = input.skills ?? []\n const resolvedSkills = skillIds\n .map((skillId) => {\n const skill = getSkillEntry(skillId)\n if (!skill) {\n logger.warn('agent references unknown skill; skipping', { agentId: input.id, skillId })\n }\n return skill\n })\n .filter((skill): skill is NonNullable<typeof skill> => !!skill)\n const skillTools = resolvedSkills.flatMap((skill) => skill.tools)\n // Sub-agents: when declared, the agent gains the delegation tool so it can run\n // other agents as read-only sub-agents (and fan them out in parallel).\n const subAgents = input.subAgents ?? []\n const delegationTools = subAgents.length > 0 ? [DELEGATE_TOOL_ID] : []\n const effectiveTools = Array.from(new Set([...ownTools, ...skillTools, ...delegationTools]))\n\n registry.set(input.id, {\n id: input.id,\n moduleId: input.moduleId,\n resultKind: input.result.kind,\n agentType: input.agentType,\n // Stored as DECLARED here; `narrowRegisteredActionVocabularies` intersects it with\n // the platform catalogue once every module has registered, because the catalogue\n // lives behind an OPTIONAL peer that can only be reached asynchronously.\n allowedActions: input.allowedActions ? [...input.allowedActions] : undefined,\n schema: input.result.schema,\n tools: ownTools,\n skills: skillIds,\n subAgents,\n label: input.label,\n description: input.description,\n instructions: input.instructions,\n defaultProvider: input.defaultProvider,\n defaultModel: input.defaultModel,\n loop: input.loop,\n runtime: 'native',\n sampleInput: input.sampleInput,\n facts: input.facts,\n })\n const skillSections = resolvedSkills.map(\n (skill) => `## Skill: ${skill.label}\\n${skill.instructions}`,\n )\n const subAgentSection = subAgents.length\n ? `## Sub-agents\\nYou may delegate independent sub-tasks to these agents by calling the \\`${DELEGATE_TOOL_ID}\\` tool with { agentId, input }. When several sub-tasks are independent, issue multiple delegate calls in the SAME step so they run in parallel, then combine their results. Available sub-agents: ${subAgents.join(', ')}.`\n : null\n const systemPrompt = [input.instructions, ...skillSections, ...(subAgentSection ? [subAgentSection] : [])]\n .join('\\n\\n')\n return defineAiAgent({\n id: input.id,\n moduleId: input.moduleId,\n label: input.label,\n description: input.description,\n systemPrompt,\n // Effective read-only allowlist = the agent's own tools + every read tool its\n // skills contribute. Read-only policy still strips any mutation tool.\n allowedTools: effectiveTools,\n executionMode: 'object',\n // Read-only is the propose-only guarantee: the runtime strips every\n // `isMutation: true` tool, so even a mis-declared write tool can never fire.\n // Reads are allowed; writes only ever happen via the proposal/effector path.\n readOnly: true,\n mutationPolicy: 'read-only',\n defaultProvider: input.defaultProvider,\n defaultModel: input.defaultModel,\n loop: input.loop,\n output: { schemaName: input.id.replace(/\\W+/g, '_'), schema: input.result.schema },\n })\n}\n\n/**\n * Register a file-defined (OpenCode) agent directly into the SAME registry that\n * `defineAgent` writes to, so file agents are discoverable via `getAgentEntry`,\n * `listAgentEntries`, and the Agents list/detail without going through\n * `defineAiAgent` (file agents don't run in-process, so they need no\n * `AiAgentDefinition`). Same dup-id guard as `defineAgent`.\n */\nexport function registerFileAgent(entry: AgentRegistryEntry): void {\n if (registry.has(entry.id)) {\n throw new Error(`[internal] duplicate agent id \"${entry.id}\"`)\n }\n registry.set(entry.id, entry)\n}\n\nexport function getAgentEntry(id: string): AgentRegistryEntry | undefined {\n return registry.get(id)\n}\n\nexport function listAgentEntries(): AgentRegistryEntry[] {\n return [...registry.values()]\n}\n\nlet agentsLoadPromise: Promise<void> | null = null\n\n/**\n * Load every module's agents into the registry. `defineAgent` registers by import\n * side effect, so an agent declared in ANY module is only discoverable once that\n * module's `ai-agents.ts` has executed. The generated `ai-agents.generated.ts`\n * aggregator statically imports every module's `ai-agents.ts`; ai_assistant's\n * `loadAgentRegistry()` imports that aggregator, so calling it runs every\n * `defineAgent()` across all modules and fills this registry. Packages must not\n * import the app-generated file directly, so we go through ai_assistant.\n *\n * Fallback: if the aggregator is unavailable (tests, fresh checkout before\n * `yarn generate`), import agent_orchestrator's own `ai-agents.ts` so at least\n * its built-in agents resolve.\n */\nasync function loadAllAgentModules(): Promise<void> {\n try {\n const { loadAgentRegistry } = await import(\n '@open-mercato/ai-assistant/modules/ai_assistant/lib/agent-registry'\n )\n await loadAgentRegistry()\n } catch {\n // ignore \u2014 fall through to the local import below\n }\n if (registry.size === 0) {\n await import('../../ai-agents')\n }\n await loadFileAgents()\n await narrowRegisteredActionVocabularies()\n}\n\n/**\n * Apply `effective = catalogue \u2229 agent.allowedActions` to every registered agent.\n *\n * It runs HERE, at the end of the registry load, rather than inside `defineAgent`:\n * `defineAgent` is synchronous and the catalogue lives in core `workflows`, an OPTIONAL\n * peer reachable only through a dynamic import. This is still registration time \u2014 no\n * agent is observable through `getAgentEntry`/`listAgentEntries` until\n * `ensureAgentsLoaded` resolves \u2014 and the check runs AGAIN before the effect\n * (`executeProposal`), because a tenant may revoke a safe command after registration.\n */\nasync function narrowRegisteredActionVocabularies(): Promise<void> {\n const declaring = [...registry.values()].filter((entry) => entry.allowedActions !== undefined)\n if (declaring.length === 0) return\n try {\n const { loadActionVocabulary, narrowAllowedActions } = await import('../runtime/actionVocabulary')\n const vocabulary = await loadActionVocabulary()\n for (const entry of declaring) {\n entry.allowedActions = narrowAllowedActions(vocabulary, entry.id, entry.allowedActions ?? [])\n }\n } catch (err) {\n logger.warn('action vocabulary narrowing skipped', {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n}\n\n/**\n * Load file-defined (OpenCode) agents from the committed, generator-owned\n * manifest (`generated/file-agents.generated.ts`). The manifest stores PLAIN\n * data (raw JSON-Schema, not a Zod instance); for each descriptor we recompile\n * the result schema via `compileOutcome` and register it with `runtime:'opencode'`.\n * The import is guarded: the manifest may be absent or empty before the first\n * `yarn generate`, and a descriptor whose schema fails to compile is skipped\n * (warned) so one bad agent never blocks the rest.\n */\n/**\n * Resolve a predicate `(toolId) => 'mutating' | 'read' | 'unknown'` for a declared\n * tool id, used by the load-time propose-only check below.\n *\n * This load-time check is an EARLY, advisory rejection \u2014 NOT the load-bearing\n * propose-only boundary. The actual runtime gates (which always hold) are: (1) the\n * generated OpenCode agent file's `tools` deny-by-default allowlist + its\n * `permission: { write/edit/bash: deny }` block, and (2) the per-run session-token\n * ACL the MCP server re-checks on every tool call. The MCP server does NOT strip\n * `isMutation` tools, so those two gates \u2014 not this predicate \u2014 are what enforce\n * propose-only.\n *\n * When the registry IS loadable we await `loadAllModuleTools()` first, so a tool\n * id absent afterward is genuinely unknown (a config error): we fail CLOSED and\n * report `'unknown'` (the caller rejects the agent). When the registry import\n * itself is unavailable (e.g. a unit-test harness without the AI tooling) the\n * check cannot run at all and reports `'skip'`; registration proceeds and the two\n * runtime gates above remain in force.\n */\ntype MutationCheck = (toolId: string) => 'mutating' | 'read' | 'unknown' | 'skip'\n\nasync function loadMutationToolPredicate(): Promise<MutationCheck> {\n try {\n const { loadAllModuleTools } = await import(\n '@open-mercato/ai-assistant/modules/ai_assistant/lib/tool-loader'\n )\n const { getToolRegistry } = await import(\n '@open-mercato/ai-assistant/modules/ai_assistant/lib/tool-registry'\n )\n try {\n await loadAllModuleTools()\n } catch {\n // Tools may already be loaded, or loading is a no-op in this harness.\n }\n const registry = getToolRegistry()\n return (toolId: string): 'mutating' | 'read' | 'unknown' | 'skip' => {\n const tool = registry.getTool(toolId) as { isMutation?: boolean } | undefined\n if (!tool) return 'unknown'\n return tool.isMutation ? 'mutating' : 'read'\n }\n } catch {\n return () => 'skip'\n }\n}\n\n/**\n * Resolve the generator-owned file-agent manifest across both layouts.\n *\n * 1. Monorepo: the committed manifest sits next to this module's source and the\n * relative import resolves at build time exactly as it always has.\n * 2. Standalone: the enterprise module lives under `node_modules/`, which\n * `yarn install` rewrites, so the generator writes the manifest into the\n * app's `.mercato/generated/` instead. Inside the Next bundler the `@/`\n * alias resolves it; in a plain Node process (the `mcp:serve-http` sidecar,\n * workers, the CLI) the alias is not a real specifier and throws, so we fall\n * back to locating and compiling the file from disk.\n *\n * Mirrors `ai-assistant`'s `importGeneratedAiToolsModule()`, which solves the\n * same problem for `ai-tools.generated.ts`. Returns `null` when no manifest\n * exists yet (pre-generate builds, tests).\n */\nasync function importFileAgentsManifest(): Promise<Record<string, unknown> | null> {\n try {\n return (await import('../../generated/file-agents.generated')) as unknown as Record<\n string,\n unknown\n >\n } catch {\n // Not the monorepo layout \u2014 fall through to the standalone resolution.\n }\n try {\n return (await import('@/.mercato/generated/file-agents.generated')) as Record<string, unknown>\n } catch {\n // Not inside the Next bundler \u2014 fall through to on-disk resolution.\n }\n try {\n const { findGeneratedFile, compileAndImportGenerated } = await import(\n '@open-mercato/ai-assistant/modules/ai_assistant/lib/generated-registry-loader'\n )\n const tsPath = findGeneratedFile('file-agents.generated.ts')\n if (!tsPath) return null\n return await compileAndImportGenerated(tsPath)\n } catch {\n return null\n }\n}\n\nasync function loadFileAgents(): Promise<void> {\n let descriptors: import('../../generated/file-agents.generated').FileAgentDescriptor[]\n const manifest = await importFileAgentsManifest()\n if (!manifest) return\n descriptors =\n (manifest.fileAgentDescriptors as\n | import('../../generated/file-agents.generated').FileAgentDescriptor[]\n | undefined) ?? []\n const { compileOutcome } = await import('./outcomeSchema')\n const { registerAgentSkills } = await import('../runtime/fileAgentSkills')\n const isMutationTool = await loadMutationToolPredicate()\n // Flatten each descriptor's nested sub-agents (Phase 4) so they register as\n // individual file agents too: they are researcher, individually runnable, and\n // discoverable in the Agents list/detail. A sub-agent flows through the SAME\n // propose-only mutation-tool gate below; the registry dup-id guard prevents a\n // duplicate id (a sub-agent that collides with another agent's id is skipped).\n const allDescriptors = descriptors.flatMap((descriptor) => [\n descriptor,\n ...(descriptor.subAgentDescriptors ?? []),\n ])\n for (const descriptor of allDescriptors) {\n if (registry.has(descriptor.id)) continue\n // Propose-only early check (contract C8): a file agent may NEVER declare a\n // tool registered with `isMutation: true`. This is defense-in-depth on top of\n // the load-bearing runtime gates (the agent-file read-only allowlist +\n // write/edit/bash deny, and the per-call session-token ACL \u2014 see\n // `loadMutationToolPredicate`). When the tool registry is resolvable we fail\n // CLOSED: a declared tool that is `mutating` OR `unknown` (absent after\n // `loadAllModuleTools`, i.e. a config error) rejects the agent here so it never\n // reaches registration. `skip` means the registry could not be loaded at all\n // (test harness without AI tooling) \u2014 we let it through and rely on the runtime\n // gates. A rejected agent is skipped + warned so the rest still load.\n const offending = descriptor.tools\n .map((toolId) => ({ toolId, verdict: isMutationTool(toolId) }))\n .find(({ verdict }) => verdict === 'mutating' || verdict === 'unknown')\n if (offending) {\n logger.warn(\n 'file agent declares a non read-only tool; file agents are propose-only and may only list known read-only tools \u2014 skipping registration',\n { agentId: descriptor.id, verdict: offending.verdict, toolId: offending.toolId },\n )\n continue\n }\n try {\n const { resultSchema } = compileOutcome({\n kind: descriptor.resultKind,\n schema: descriptor.outcomeSchema,\n })\n registerFileAgent({\n id: descriptor.id,\n moduleId: descriptor.moduleId,\n resultKind: descriptor.resultKind,\n schema: resultSchema,\n tools: descriptor.tools,\n skills: descriptor.skills,\n subAgents: descriptor.subAgents,\n label: descriptor.label,\n description: descriptor.description,\n instructions: descriptor.instructions,\n defaultProvider: descriptor.provider,\n defaultModel: descriptor.model,\n loop: descriptor.maxSteps != null ? { maxSteps: descriptor.maxSteps } : undefined,\n runtime: 'opencode',\n outcomeSchema: descriptor.outcomeSchema,\n sampleInput: descriptor.sampleInput,\n facts: descriptor.facts,\n tokenUsage: descriptor.tokenUsage,\n sourceFiles: descriptor.sourceFiles,\n })\n // Phase 3: register the agent's resolved skill content into the runtime\n // lookup so `load_skill` can return it without fs access. Optional + BC: a\n // descriptor without `skillsContent` registers no skills (cleared).\n registerAgentSkills(descriptor.id, descriptor.skillsContent ?? [])\n } catch (err) {\n logger.warn('failed to load file agent', {\n agentId: descriptor.id,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n}\n\n/**\n * Ensure agents from every module have registered before the registry is read.\n * Idempotent: skips when already populated, and the underlying imports are no-ops\n * once their modules have executed. Code paths that don't transitively import any\n * `ai-agents.ts` \u2014 notably the workflow background executor and the agents API \u2014\n * call this first so they never read an empty registry.\n */\nexport async function ensureAgentsLoaded(): Promise<void> {\n // NOTE: do NOT short-circuit on `registry.size > 0`. In-process agents register\n // via `ai-agents.ts` import side-effects (e.g. the global AiAssistantLauncher\n // triggers `loadAgentRegistry()` on every backoffice page), so the registry is\n // often already non-empty before the first call here. File-defined (OpenCode)\n // agents only load inside `loadAllAgentModules()` \u2192 `loadFileAgents()`, so a\n // size-based early return would skip them entirely. The memoized\n // `agentsLoadPromise` already guarantees the full load runs exactly once.\n if (!agentsLoadPromise) {\n agentsLoadPromise = loadAllAgentModules()\n .then(() => undefined)\n .catch((err) => {\n agentsLoadPromise = null\n throw err\n })\n }\n await agentsLoadPromise\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["registry"]
|
|
7
7
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=file-agents-generated.d.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/enterprise",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -70,12 +70,12 @@
|
|
|
70
70
|
"@dnd-kit/core": "^6.3.1",
|
|
71
71
|
"@dnd-kit/sortable": "^10.0.0",
|
|
72
72
|
"@dnd-kit/utilities": "^3.2.2",
|
|
73
|
-
"@open-mercato/ai-assistant": "0.
|
|
74
|
-
"@open-mercato/core": "0.
|
|
75
|
-
"@open-mercato/queue": "0.
|
|
76
|
-
"@open-mercato/scheduler": "0.
|
|
77
|
-
"@open-mercato/ui": "0.
|
|
78
|
-
"@open-mercato/web-research": "0.
|
|
73
|
+
"@open-mercato/ai-assistant": "0.8.0",
|
|
74
|
+
"@open-mercato/core": "0.8.0",
|
|
75
|
+
"@open-mercato/queue": "0.8.0",
|
|
76
|
+
"@open-mercato/scheduler": "0.8.0",
|
|
77
|
+
"@open-mercato/ui": "0.8.0",
|
|
78
|
+
"@open-mercato/web-research": "0.8.0",
|
|
79
79
|
"@simplewebauthn/browser": "^13.3.0",
|
|
80
80
|
"@simplewebauthn/server": "^13.3.2",
|
|
81
81
|
"@simplewebauthn/types": "^12.0.0",
|
|
@@ -87,13 +87,13 @@
|
|
|
87
87
|
},
|
|
88
88
|
"peerDependencies": {
|
|
89
89
|
"@mikro-orm/core": "^7.0.14",
|
|
90
|
-
"@open-mercato/shared": "0.
|
|
90
|
+
"@open-mercato/shared": "0.8.0",
|
|
91
91
|
"react": "^19.0.0",
|
|
92
92
|
"react-dom": "^19.0.0",
|
|
93
93
|
"zod": ">=3.23.0"
|
|
94
94
|
},
|
|
95
95
|
"devDependencies": {
|
|
96
|
-
"@open-mercato/shared": "0.
|
|
96
|
+
"@open-mercato/shared": "0.8.0",
|
|
97
97
|
"@types/jest": "^30.0.0",
|
|
98
98
|
"@types/react": "^19.2.18",
|
|
99
99
|
"@types/react-dom": "^19.2.5",
|
|
@@ -110,6 +110,5 @@
|
|
|
110
110
|
"type": "git",
|
|
111
111
|
"url": "https://github.com/open-mercato/open-mercato",
|
|
112
112
|
"directory": "packages/enterprise"
|
|
113
|
-
}
|
|
114
|
-
"stableVersion": "0.7.0"
|
|
113
|
+
}
|
|
115
114
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference path="./file-agents-generated.d.ts" />
|
|
1
2
|
import {
|
|
2
3
|
defineAiAgent,
|
|
3
4
|
type AiAgentDefinition,
|
|
@@ -417,14 +418,56 @@ async function loadMutationToolPredicate(): Promise<MutationCheck> {
|
|
|
417
418
|
}
|
|
418
419
|
}
|
|
419
420
|
|
|
420
|
-
|
|
421
|
-
|
|
421
|
+
/**
|
|
422
|
+
* Resolve the generator-owned file-agent manifest across both layouts.
|
|
423
|
+
*
|
|
424
|
+
* 1. Monorepo: the committed manifest sits next to this module's source and the
|
|
425
|
+
* relative import resolves at build time exactly as it always has.
|
|
426
|
+
* 2. Standalone: the enterprise module lives under `node_modules/`, which
|
|
427
|
+
* `yarn install` rewrites, so the generator writes the manifest into the
|
|
428
|
+
* app's `.mercato/generated/` instead. Inside the Next bundler the `@/`
|
|
429
|
+
* alias resolves it; in a plain Node process (the `mcp:serve-http` sidecar,
|
|
430
|
+
* workers, the CLI) the alias is not a real specifier and throws, so we fall
|
|
431
|
+
* back to locating and compiling the file from disk.
|
|
432
|
+
*
|
|
433
|
+
* Mirrors `ai-assistant`'s `importGeneratedAiToolsModule()`, which solves the
|
|
434
|
+
* same problem for `ai-tools.generated.ts`. Returns `null` when no manifest
|
|
435
|
+
* exists yet (pre-generate builds, tests).
|
|
436
|
+
*/
|
|
437
|
+
async function importFileAgentsManifest(): Promise<Record<string, unknown> | null> {
|
|
438
|
+
try {
|
|
439
|
+
return (await import('../../generated/file-agents.generated')) as unknown as Record<
|
|
440
|
+
string,
|
|
441
|
+
unknown
|
|
442
|
+
>
|
|
443
|
+
} catch {
|
|
444
|
+
// Not the monorepo layout — fall through to the standalone resolution.
|
|
445
|
+
}
|
|
422
446
|
try {
|
|
423
|
-
|
|
424
|
-
descriptors = manifest.fileAgentDescriptors ?? []
|
|
447
|
+
return (await import('@/.mercato/generated/file-agents.generated')) as Record<string, unknown>
|
|
425
448
|
} catch {
|
|
426
|
-
|
|
449
|
+
// Not inside the Next bundler — fall through to on-disk resolution.
|
|
427
450
|
}
|
|
451
|
+
try {
|
|
452
|
+
const { findGeneratedFile, compileAndImportGenerated } = await import(
|
|
453
|
+
'@open-mercato/ai-assistant/modules/ai_assistant/lib/generated-registry-loader'
|
|
454
|
+
)
|
|
455
|
+
const tsPath = findGeneratedFile('file-agents.generated.ts')
|
|
456
|
+
if (!tsPath) return null
|
|
457
|
+
return await compileAndImportGenerated(tsPath)
|
|
458
|
+
} catch {
|
|
459
|
+
return null
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async function loadFileAgents(): Promise<void> {
|
|
464
|
+
let descriptors: import('../../generated/file-agents.generated').FileAgentDescriptor[]
|
|
465
|
+
const manifest = await importFileAgentsManifest()
|
|
466
|
+
if (!manifest) return
|
|
467
|
+
descriptors =
|
|
468
|
+
(manifest.fileAgentDescriptors as
|
|
469
|
+
| import('../../generated/file-agents.generated').FileAgentDescriptor[]
|
|
470
|
+
| undefined) ?? []
|
|
428
471
|
const { compileOutcome } = await import('./outcomeSchema')
|
|
429
472
|
const { registerAgentSkills } = await import('../runtime/fileAgentSkills')
|
|
430
473
|
const isMutationTool = await loadMutationToolPredicate()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// In a standalone app the generator writes the file-agent manifest into the
|
|
2
|
+
// app's `.mercato/generated/` (the enterprise module itself lives under
|
|
3
|
+
// `node_modules/`, which `yarn install` rewrites). The `@/` alias only exists
|
|
4
|
+
// inside the consuming app's bundler, so declare the module here the same way
|
|
5
|
+
// `ai-assistant` declares its own generated registries.
|
|
6
|
+
//
|
|
7
|
+
// Typed as `unknown[]` on purpose: the real shape is
|
|
8
|
+
// `FileAgentDescriptor[]` from `../../generated/file-agents.generated`, and the
|
|
9
|
+
// loader narrows it there. Importing that type into an ambient declaration would
|
|
10
|
+
// couple this file to the committed monorepo manifest, which is exactly the
|
|
11
|
+
// artifact a standalone app does not have.
|
|
12
|
+
declare module '@/.mercato/generated/file-agents.generated' {
|
|
13
|
+
export const fileAgentDescriptors: unknown[]
|
|
14
|
+
}
|