diff --git a/cli/commands/pull/command.test.ts b/cli/commands/pull/command.test.ts index ec851e3beb..b3b82f84a0 100644 --- a/cli/commands/pull/command.test.ts +++ b/cli/commands/pull/command.test.ts @@ -11,7 +11,7 @@ import { assertThrows, } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; -import { cliLogger } from "#cli/utils"; +import { cliLogger, VERSION } from "#cli/utils"; import { _resetEnvironmentConfig } from "#veryfront/config/environment-config.ts"; import { buildFileContentUrl, @@ -117,7 +117,7 @@ function expectedBootstrapPackage(name: string): Record { dependencies: { react: "^19.2.4", "react-dom": "^19.2.4", - veryfront: "^0.1.1175", + veryfront: `^${VERSION}`, }, }; } @@ -153,7 +153,7 @@ const EXPECTED_BOOTSTRAP_PACKAGE = { dependencies: { react: "^19.2.4", "react-dom": "^19.2.4", - veryfront: "^0.1.1175", + veryfront: `^${VERSION}`, }, }; diff --git a/cli/mcp/tools/deploy-tool.test.ts b/cli/mcp/tools/deploy-tool.test.ts index 6fac60cdbd..6c6773520c 100644 --- a/cli/mcp/tools/deploy-tool.test.ts +++ b/cli/mcp/tools/deploy-tool.test.ts @@ -382,9 +382,10 @@ describe("mcp/tools/deploy-tool", () => { using time = new FakeTime(); resumeReleaseSourceRead(); await time.tickAsync(0); + // Parallel suites can need several microtask turns per fake timer. for ( let tick = 0; - releaseSourceReads < 20 && tick < 40; + releaseSourceReads < 20 && tick < 200; tick++ ) { await time.tickAsync(500); diff --git a/docs/api-reference/veryfront/agent.md b/docs/api-reference/veryfront/agent.md index 2d0febab4b..85496f3062 100644 --- a/docs/api-reference/veryfront/agent.md +++ b/docs/api-reference/veryfront/agent.md @@ -157,7 +157,7 @@ Agent helper. | `resolveModelTransport?` | `ModelTransportResolver` | Optional request-aware hook for overriding the resolved model runtime and provider transport options on a per-call basis. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/types.ts#L212) | | `resolveRuntimeState?` | `RuntimeStateResolver` | Optional step-boundary hook for refreshing the runtime system prompt and host-owned context during a long-lived run. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/types.ts#L217) | | `onToolResult?` | `ToolExecutionResultHandler` | Optional hook invoked after the runtime executes a configured local, registry, integration, or remote tool and before the tool result is persisted or streamed back to callers. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/types.ts#L223) | -| `skills?` | `true \| false \| string[]` | Select the skills advertised in this agent's system prompt. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/types.ts#L235) | +| `skills?` | `true \| false \| string[]` | Select visible skill IDs or this agent's own skill short names advertised in this agent's system prompt and authorized for `load_skill`. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/types.ts#L235) | | `suggestions?` | `SuggestionsConfig` | Prompt starters shown on an empty chat. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/types.ts#L242) | | `security?` | `false` | Set to false to disable the default security middleware | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/types.ts#L244) | @@ -285,8 +285,8 @@ Clear all stored messages from memory. | `PROJECT_AGENT_KINDS` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/identity-contracts.ts#L15) | | `PROJECT_STEERING_FILE_MUTATION_TOOL_NAMES` | Shared project steering file mutation tool names value. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/project/steering-mutation.ts#L8) | | `ROOT_OWNED_CHILD_RESULT_INSTRUCTION` | Shared root owned child result instruction value. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/delegation-policy.ts#L42) | -| `RUNTIME_LOAD_SKILL_CONTINUATION_NOTE` | Legacy continuation-note fallback used when runtime tool inventory is unavailable. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L33) | -| `RUNTIME_LOAD_SKILL_DESCRIPTION` | Shared runtime load skill description value. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L37) | +| `RUNTIME_LOAD_SKILL_CONTINUATION_NOTE` | Legacy continuation-note fallback used when runtime tool inventory is unavailable. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L34) | +| `RUNTIME_LOAD_SKILL_DESCRIPTION` | Shared runtime load skill description value. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L38) | | `RuntimeAgentContextItemSchema` | Schema for runtime agent context item. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-invocation-contract.ts#L125) | | `RuntimeAgentIdSchema` | Schema for runtime agent ID. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-invocation-contract.ts#L58) | | `RuntimeAgentProjectContextSchema` | Schema for runtime agent project context. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-invocation-contract.ts#L217) | @@ -300,7 +300,7 @@ Clear all stored messages from memory. | `RuntimeAgentToolNameSchema` | Schema for runtime agent tool name. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-invocation-contract.ts#L70) | | `RuntimeAgentToolSchema` | Schema for runtime agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-invocation-contract.ts#L95) | | `RuntimeAgentValidatedClaimsSchema` | Schema for runtime agent validated claims. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-invocation-contract.ts#L231) | -| `RuntimeSkillFrontmatterSchema` | Schema for runtime skill frontmatter. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L61) | +| `RuntimeSkillFrontmatterSchema` | Schema for runtime skill frontmatter. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L66) | | `SLASH_COMMAND_ARTIFACT_REMINDER` | Shared slash command artifact reminder value. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/delegation-policy.ts#L121) | | `SYNTHESIZE_DELEGATED_FINDINGS_IN_ROOT_VOICE` | Shared synthesize delegated findings in root voice value. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/delegation-policy.ts#L15) | @@ -311,14 +311,14 @@ Clear all stored messages from memory. | `addFirstTurnStarterIntentRootOwnershipReminder` | Add first turn starter intent root ownership reminder helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/delegation-policy.ts#L228) | | `addLoadSkillContinuationReminder` | Add load skill continuation reminder helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/delegation-policy.ts#L292) | | `addSlashCommandArtifactReminder` | Add slash command artifact reminder helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/delegation-policy.ts#L315) | -| `agent` | Agent helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/factory.ts#L99) | +| `agent` | Agent helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/factory.ts#L114) | | `agentAsTool` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/composition/composition.ts#L63) | | `appendAgentServiceChildMirrorChunk` | Append hosted child mirror chunk. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-mirror.ts#L180) | | `appendConversationRunEvents` | Append conversation run events. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/durable.ts#L988) | | `appendHostedChildMirrorChunk` | Append hosted child mirror chunk. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-mirror.ts#L180) | | `appendMissingChildRunToolCalls` | Append missing child run tool calls. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/child-run/final-step-support.ts#L15) | | `appendMissingChildRunToolResults` | Append missing child run tool results. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/child-run/final-step-support.ts#L31) | -| `applyAgentProjectContextChange` | Apply agent project context change helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/project/context.ts#L13) | +| `applyAgentProjectContextChange` | Apply agent project context change helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/project/context.ts#L14) | | `applyDefaultResearchArtifactPath` | Apply default research artifact path helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/artifacts/default-research-artifact-support.ts#L218) | | `applyPartToStreamedStepState` | State for apply part to streamed step. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/streaming/fork-runtime-step-state.ts#L75) | | `bootstrapAgentService` | Bootstrap agent service helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/bootstrap.ts#L64) | @@ -378,8 +378,8 @@ Clear all stored messages from memory. | `buildRootOwnedDelegatedFindingsInstruction` | Builds root owned delegated findings instruction. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/delegation-policy.ts#L37) | | `buildRuntimeAgentControlPlaneStreamRequestFromInvocation` | Builds runtime agent control plane stream request from invocation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-invocation-contract.ts#L391) | | `buildRuntimeAvailableSkillsPromptBlock` | Builds runtime available skills prompt block. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-prompt.ts#L69) | -| `buildRuntimeLoadedSkillResponse` | Response payload for build runtime loaded skill. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L306) | -| `buildRuntimeSkillDefinition` | Definition for build runtime skill. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L255) | +| `buildRuntimeLoadedSkillResponse` | Response payload for build runtime loaded skill. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L339) | +| `buildRuntimeSkillDefinition` | Definition for build runtime skill. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L288) | | `buildScheduleTraceAttributes` | Builds schedule trigger trace attributes from schedule forwarded props. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/trace-attributes.ts#L76) | | `buildStarterIntentRootOwnershipBlockMessage` | Message shape for build starter intent root ownership block. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/delegation-policy.ts#L113) | | `buildStarterIntentRootOwnershipReminder` | Builds starter intent root ownership reminder. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/delegation-policy.ts#L108) | @@ -448,12 +448,12 @@ Clear all stored messages from memory. | `createConversationRunEventQueueController` | Create conversation run event queue controller. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/durable.ts#L693) | | `createConversationRunMirror` | Create conversation run mirror. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/run-mirror.ts#L83) | | `createConversationRunStreamMirror` | Create conversation run stream mirror. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/run-stream-mirror.ts#L24) | -| `createDefaultAgentServiceChatRuntime` | Create default hosted chat runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L344) | -| `createDefaultAgentServiceInvokeAgentTool` | Create default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L766) | -| `createDefaultAgentServiceProjectSteeringRefresh` | Create default hosted project steering refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L169) | -| `createDefaultHostedChatRuntime` | Create default hosted chat runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L344) | -| `createDefaultHostedInvokeAgentTool` | Create default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L766) | -| `createDefaultHostedProjectSteeringRefresh` | Create default hosted project steering refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L169) | +| `createDefaultAgentServiceChatRuntime` | Create default hosted chat runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L346) | +| `createDefaultAgentServiceInvokeAgentTool` | Create default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L767) | +| `createDefaultAgentServiceProjectSteeringRefresh` | Create default hosted project steering refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L214) | +| `createDefaultHostedChatRuntime` | Create default hosted chat runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L346) | +| `createDefaultHostedInvokeAgentTool` | Create default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L767) | +| `createDefaultHostedProjectSteeringRefresh` | Create default hosted project steering refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L214) | | `createDefaultResearchRunArtifactMirrorHandler` | Handler for create default research run artifact mirror. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/artifacts/default-research-artifact-support.ts#L362) | | `createDetachedRunShutdownLifecycle` | Create detached run shutdown lifecycle. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/detached-run-tracker.ts#L139) | | `createDetachedRunTracker` | Create detached run tracker. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/detached-run-tracker.ts#L56) | @@ -485,7 +485,7 @@ Clear all stored messages from memory. | `createHostedMirroredUiStream` | Create hosted mirrored UI stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/streaming/mirrored-tool-chunk-state.ts#L251) | | `createHostedProjectRemoteToolSource` | Create hosted project remote tool source. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-remote-tool-source.ts#L88) | | `createHostedProjectRemoteToolSources` | Create hosted project remote tool sources. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-remote-tool-source.ts#L358) | -| `createHostedProjectSteeringAdapter` | Create hosted project steering adapter. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-steering-adapter.ts#L138) | +| `createHostedProjectSteeringAdapter` | Create hosted project steering adapter. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-steering-adapter.ts#L169) | | `createHostedRootRunLifecycleRuntimeAdapter` | Create hosted root run lifecycle runtime adapter. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/agent-run-lifecycle.ts#L162) | | `createHostedRuntimeStateResolver` | Create hosted runtime state resolver. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/runtime-state-resolver.ts#L72) | | `createHostedServiceAuth` | Create hosted service auth. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/auth.ts#L284) | @@ -495,13 +495,13 @@ Clear all stored messages from memory. | `createMemory` | Create memory. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/memory/memory.ts#L352) | | `createMirroredToolChunkState` | State for create mirrored tool chunk. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/streaming/mirrored-tool-chunk-state.ts#L40) | | `createNodeAgentServiceRuntimeInfrastructure` | Create node agent service runtime infrastructure. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/node-runtime-infrastructure.ts#L52) | -| `createNodeVeryfrontCloudAgentServiceRuntime` | Create node Veryfront Cloud agent service runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L190) | +| `createNodeVeryfrontCloudAgentServiceRuntime` | Create node Veryfront Cloud agent service runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L192) | | `createRedisMemory` | Create redis memory. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/memory/redis.ts#L249) | | `createRequestAuthCache` | Create request auth cache. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/request-auth-cache.ts#L19) | | `createRuntimeAgentDefinitionFromAgent` | Create runtime agent definition from agent. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/project/agent-runtime.ts#L169) | | `createRuntimeAgentFromMarkdownDefinition` | Definition for create runtime agent from markdown. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-markdown-adapter.ts#L8) | | `createRuntimeAgentSystemMessages` | Create runtime agent system messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-definition.ts#L256) | -| `createRuntimeLoadSkillTool` | Create runtime load skill tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L562) | +| `createRuntimeLoadSkillTool` | Create runtime load skill tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L574) | | `createRuntimeProjectFilesClient` | Create runtime project files client. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/project-files-client.ts#L104) | | `createRuntimeProjectSkillLoader` | Create runtime project skill loader. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/project-skill-loader.ts#L327) | | `createRuntimePromptBlock` | Create runtime prompt block. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/prompt-block.ts#L9) | @@ -529,8 +529,8 @@ Clear all stored messages from memory. | `evaluateSlashCommandArtifactPolicy` | Evaluate slash command artifact policy helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/artifacts/slash-command-artifact-policy.ts#L200) | | `evaluateStarterIntentTurnPolicy` | Evaluate starter intent turn policy helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/delegation-policy.ts#L210) | | `executeAgUiDetachedStart` | Execute AG-UI detached start. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/ag-ui/detached-start.ts#L300) | -| `executeDefaultAgentServiceInvokeAgentTool` | Execute default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L565) | -| `executeDefaultHostedInvokeAgentTool` | Execute default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L565) | +| `executeDefaultAgentServiceInvokeAgentTool` | Execute default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L566) | +| `executeDefaultHostedInvokeAgentTool` | Execute default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L566) | | `executeDurableHumanInputFlow` | Execute durable human input flow. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/input/human-input.ts#L220) | | `executeHostedChildForkRunContextStream` | Execute hosted child fork run context stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-fork-run-context.ts#L224) | | `executeHostedChildForkStream` | Execute hosted child fork stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-fork-stream-execution.ts#L487) | @@ -545,8 +545,8 @@ Clear all stored messages from memory. | `extractLatestUserText` | Extract latest user text. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/artifacts/default-research-artifact-support.ts#L68) | | `extractStarterIntentId` | Extract starter intent ID. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/delegation-policy.ts#L192) | | `fetchConversationRecord` | Record shape for fetch conversation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/bootstrap.ts#L134) | -| `fetchDefaultAgentServiceProjectSteering` | Fetch default hosted project steering helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L69) | -| `fetchDefaultHostedProjectSteering` | Fetch default hosted project steering helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L69) | +| `fetchDefaultAgentServiceProjectSteering` | Fetch default hosted project steering helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L74) | +| `fetchDefaultHostedProjectSteering` | Fetch default hosted project steering helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L74) | | `fetchLatestConversationUserText` | Fetch latest conversation user text helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/artifacts/default-research-artifact-support.ts#L112) | | `filterAgentTraceAttributes` | Filter agent trace attributes. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/trace-attributes.ts#L61) | | `filterHostedChatRuntimeLocalTools` | Filter hosted chat runtime local tools. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-tool-assembly.ts#L187) | @@ -577,7 +577,7 @@ Clear all stored messages from memory. | `getAgUiSseStringField` | Return a string field from a parsed AG-UI SSE event record. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/ag-ui/sse-parser.ts#L61) | | `getAllAgentIds` | Return all agent IDs. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/composition/composition.ts#L226) | | `getChildRunSnapshotUsage` | Return child run snapshot usage. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/child-run/execution-snapshot.ts#L72) | -| `getConfirmedProjectContextSwitchId` | Return confirmed project context switch ID. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/project/context.ts#L58) | +| `getConfirmedProjectContextSwitchId` | Return confirmed project context switch ID. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/project/context.ts#L59) | | `getConversationRun` | Return conversation run. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/durable.ts#L912) | | `getConversationRunEventJsonByteLength` | Return conversation run event JSON byte length. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/run-event-normalization.ts#L20) | | `getEmptyHostedFinalizedMessageTerminalError` | Error shape for get empty hosted finalized message terminal. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/stream-terminal-error.ts#L118) | @@ -660,9 +660,9 @@ Clear all stored messages from memory. | `normalizeConversationRunEvents` | Normalizes conversation run events. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/run-event-normalization.ts#L82) | | `normalizeEncodedConversationRunEvents` | Normalizes encoded conversation run events. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/run-events.ts#L292) | | `normalizeHostedChildArtifactPath` | Normalizes hosted child artifact path. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-artifact-support.ts#L133) | -| `normalizeParsedAgentServiceChatRequest` | Request payload for normalize parsed hosted chat. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L262) | -| `normalizeParsedHostedChatRequest` | Request payload for normalize parsed hosted chat. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L262) | -| `normalizeRuntimeSkillReferencePath` | Normalizes runtime skill reference path. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L290) | +| `normalizeParsedAgentServiceChatRequest` | Request payload for normalize parsed hosted chat. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L263) | +| `normalizeParsedHostedChatRequest` | Request payload for normalize parsed hosted chat. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L263) | +| `normalizeRuntimeSkillReferencePath` | Normalizes runtime skill reference path. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L323) | | `parseAgentServiceChatRequestFromRequest` | Request payload for parse hosted chat request from. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request-parser.ts#L365) | | `parseAgentServiceConfig` | Configuration used by parse agent service. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/config.ts#L150) | | `parseAgUiContextBoolean` | Parses AG-UI context boolean. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/ag-ui/forwarded-context.ts#L56) | @@ -684,15 +684,15 @@ Clear all stored messages from memory. | `parseRuntimeAgentRunInvocationAgentServiceChatRequestFromRequest` | Request payload for parse runtime agent run invocation hosted chat request from. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request-parser.ts#L394) | | `parseRuntimeAgentRunInvocationHostedChatRequestFromRequest` | Request payload for parse runtime agent run invocation hosted chat request from. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request-parser.ts#L394) | | `parseRuntimeAgentRunInvocationOrError` | Error shape for parse runtime agent run invocation or. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-invocation-contract.ts#L418) | -| `parseRuntimeSkillDocument` | Parses runtime skill document. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L219) | -| `parseRuntimeSkillMetadata` | Parses runtime skill metadata. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L247) | +| `parseRuntimeSkillDocument` | Parses runtime skill document. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L252) | +| `parseRuntimeSkillMetadata` | Parses runtime skill metadata. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L280) | | `parseToolInputObject` | Parses tool input object. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/streaming/tool-input.ts#L135) | | `persistConversationUserMessage` | Message shape for persist conversation user. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/bootstrap.ts#L211) | | `persistLatestConversationUserMessage` | Message shape for persist latest conversation user. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/bootstrap.ts#L277) | | `prepareAgentRuntimeMessagesFromUiMessages` | Prepare agent runtime messages from UI messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/message-preparation.ts#L34) | -| `prepareAgentServiceChatExecution` | Prepare hosted chat execution. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L409) | -| `prepareAgentServiceChatRuntimeCreationOptions` | Options accepted by prepare hosted chat runtime creation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L297) | -| `prepareAgentServiceChatRuntimeMessages` | Prepare hosted chat runtime messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L526) | +| `prepareAgentServiceChatExecution` | Prepare hosted chat execution. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L403) | +| `prepareAgentServiceChatRuntimeCreationOptions` | Options accepted by prepare hosted chat runtime creation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L300) | +| `prepareAgentServiceChatRuntimeMessages` | Prepare hosted chat runtime messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L520) | | `prepareAgentServiceConversationRootRunContext` | Context for prepare hosted conversation root run. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/root-run-lifecycle.ts#L118) | | `prepareConversationRootRunContext` | Context for prepare conversation root run. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/root-run-context.ts#L127) | | `prepareConversationRootRunLifecycle` | Prepare conversation root run lifecycle. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/root-run-lifecycle.ts#L38) | @@ -703,9 +703,9 @@ Clear all stored messages from memory. | `prepareDefaultHostedChildForkSandboxToolSources` | Prepare default hosted child fork sandbox tool sources. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-fork-tool-sources.ts#L195) | | `prepareDefaultHostedChildForkToolAssembly` | Prepare default hosted child fork tool assembly. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-requested-tools.ts#L366) | | `prepareDefaultHostedChildForkToolSources` | Prepare default hosted child fork tool sources. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-fork-tool-sources.ts#L87) | -| `prepareHostedChatExecution` | Prepare hosted chat execution. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L409) | -| `prepareHostedChatRuntimeCreationOptions` | Options accepted by prepare hosted chat runtime creation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L297) | -| `prepareHostedChatRuntimeMessages` | Prepare hosted chat runtime messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L526) | +| `prepareHostedChatExecution` | Prepare hosted chat execution. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L403) | +| `prepareHostedChatRuntimeCreationOptions` | Options accepted by prepare hosted chat runtime creation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L300) | +| `prepareHostedChatRuntimeMessages` | Prepare hosted chat runtime messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L520) | | `prepareHostedChatRuntimeToolAssembly` | Prepare hosted chat runtime tool assembly. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-tool-assembly.ts#L223) | | `prepareHostedChildForkRuntimeStepMessages` | Prepare hosted child fork runtime step messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-fork-step-message-preparation.ts#L113) | | `prepareHostedConversationRootRunContext` | Context for prepare hosted conversation root run. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/root-run-lifecycle.ts#L118) | @@ -774,7 +774,7 @@ Clear all stored messages from memory. | `snapshotHostedRuntimeSourceIdentity` | Capture a service-owned immutable copy of a declared runtime source. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/runtime-source-binding.ts#L18) | | `startAgentRuntimeFork` | Starts agent runtime fork. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/streaming/fork-runtime-stream.ts#L482) | | `startAgentRuntimeForkWithHostTools` | Starts agent runtime fork with host tools. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/streaming/fork-runtime-stream.ts#L159) | -| `startAgentService` | Starts agent service. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L224) | +| `startAgentService` | Starts agent service. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L226) | | `startAgentServiceRuntime` | Starts agent service runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/runtime.ts#L345) | | `startAgentServiceServer` | Starts agent service server. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/server.ts#L93) | | `startConversationRootRun` | Starts conversation root run. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/root-run-context.ts#L62) | @@ -782,7 +782,7 @@ Clear all stored messages from memory. | `startNodeAgentService` | Starts node agent service. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/runtime.ts#L310) | | `startNodeAgentServiceServer` | Starts node agent service server. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/server.ts#L76) | | `startNodeHostedAgentService` | Starts node hosted agent service. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/runtime.ts#L335) | -| `startNodeVeryfrontCloudAgentService` | Starts node Veryfront Cloud agent service. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L200) | +| `startNodeVeryfrontCloudAgentService` | Starts node Veryfront Cloud agent service. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L202) | | `streamDataStreamEvents` | Stream data stream events helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/streaming/data-stream.ts#L49) | | `streamPreparedAgentServiceChatExecutionToAgUiResponse` | Response payload for stream prepared hosted chat execution to AG-UI. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/prepared-chat-execution.ts#L123) | | `streamPreparedHostedChatExecutionToAgUiResponse` | Response payload for stream prepared hosted chat execution to AG-UI. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/prepared-chat-execution.ts#L123) | @@ -800,8 +800,8 @@ Clear all stored messages from memory. | `updateDefaultResearchArtifacts` | Update default research artifacts helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/artifacts/default-research-artifact-support.ts#L166) | | `validateRuntimeAgentTargetSelection` | Validates runtime agent target selection. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-invocation-contract.ts#L166) | | `verifyHostedRuntimeSourceBinding` | Verify that a control-plane request addresses the exact source snapshot served here. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/runtime-source-binding.ts#L25) | -| `veryfrontApiMcpServer` | Veryfront API MCP server helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L68) | -| `veryfrontStudioMcpServer` | Veryfront Studio MCP server helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L75) | +| `veryfrontApiMcpServer` | Veryfront API MCP server helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L69) | +| `veryfrontStudioMcpServer` | Veryfront Studio MCP server helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L76) | | `waitForDurableHumanInputResolution` | Wait for durable human input resolution helper. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/input/human-input.ts#L307) | | `waitForHumanInput` | Input payload for wait for human. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/input/human-input.ts#L281) | | `withDefaultResearchArtifactPath` | Applies default research artifact path. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/artifacts/default-research-artifact-policy.ts#L165) | @@ -882,16 +882,16 @@ Clear all stored messages from memory. | `AgentServiceChatProjectAccessError` | Error shape for hosted chat project access. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request-parser.ts#L36) | | `AgentServiceChatProjectAccessResult` | Result returned from hosted chat project access. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request-parser.ts#L43) | | `AgentServiceChatRequestPrincipal` | Public API contract for hosted chat request principal. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request-parser.ts#L30) | -| `AgentServiceChatRuntimeAgent` | Public API contract for hosted chat runtime agent. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L84) | -| `AgentServiceChatRuntimeCreationOptions` | Options accepted by hosted chat runtime creation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L116) | -| `AgentServiceChatRuntimeCreationResult` | Result returned from hosted chat runtime creation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L91) | -| `AgentServiceChatRuntimeFinishPart` | Public API contract for hosted chat runtime finish part. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L14) | -| `AgentServiceChatRuntimeOnFinishEvent` | Event emitted for hosted chat runtime on finish. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L51) | -| `AgentServiceChatRuntimeProjectSteering` | Public API contract for hosted chat runtime project steering. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L99) | -| `AgentServiceChatRuntimeStreamInput` | Input payload for hosted chat runtime stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L70) | -| `AgentServiceChatRuntimeStreamResult` | Result returned from hosted chat runtime stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L76) | +| `AgentServiceChatRuntimeAgent` | Public API contract for hosted chat runtime agent. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L85) | +| `AgentServiceChatRuntimeCreationOptions` | Options accepted by hosted chat runtime creation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L118) | +| `AgentServiceChatRuntimeCreationResult` | Result returned from hosted chat runtime creation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L92) | +| `AgentServiceChatRuntimeFinishPart` | Public API contract for hosted chat runtime finish part. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L15) | +| `AgentServiceChatRuntimeOnFinishEvent` | Event emitted for hosted chat runtime on finish. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L52) | +| `AgentServiceChatRuntimeProjectSteering` | Public API contract for hosted chat runtime project steering. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L100) | +| `AgentServiceChatRuntimeStreamInput` | Input payload for hosted chat runtime stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L71) | +| `AgentServiceChatRuntimeStreamResult` | Result returned from hosted chat runtime stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L77) | | `AgentServiceChatRuntimeToolAssemblyResult` | Result returned from hosted chat runtime tool assembly. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-tool-assembly.ts#L63) | -| `AgentServiceChatRuntimeToUiMessageStreamOptions` | Options accepted by hosted chat runtime to UI message stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L60) | +| `AgentServiceChatRuntimeToUiMessageStreamOptions` | Options accepted by hosted chat runtime to UI message stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L61) | | `AgentServiceChildChunkMirror` | Public API contract for hosted child chunk mirror. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-mirror.ts#L5) | | `AgentServiceChildMirrorContext` | Context for hosted child mirror. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-mirror.ts#L16) | | `AgentServiceChildMirrorPart` | Public API contract for hosted child mirror part. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-mirror.ts#L86) | @@ -909,12 +909,12 @@ Clear all stored messages from memory. | `AgentServiceFormInputToolContext` | Context for hosted form input tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/form-input-tool.ts#L25) | | `AgentServiceJwtError` | Error shape for hosted service jwt. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/auth.ts#L44) | | `AgentServiceJwtResult` | Result returned from hosted service jwt. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/auth.ts#L51) | -| `AgentServiceOptions` | Options accepted by agent service. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L128) | -| `AgentServicePreparedExecution` | Public API contract for agent service prepared execution. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L133) | -| `AgentServiceProcessTarget` | Public API contract for agent service process target. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L135) | +| `AgentServiceOptions` | Options accepted by agent service. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L129) | +| `AgentServicePreparedExecution` | Public API contract for agent service prepared execution. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L134) | +| `AgentServiceProcessTarget` | Public API contract for agent service process target. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L136) | | `AgentServiceProjectAccessError` | Error shape for hosted service project access. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/auth.ts#L56) | | `AgentServiceProjectAccessResult` | Result returned from hosted service project access. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/auth.ts#L63) | -| `AgentServiceProjectSkillIdsContext` | Context for hosted project skill IDs. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-steering-adapter.ts#L61) | +| `AgentServiceProjectSkillIdsContext` | Context for hosted project skill IDs. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-steering-adapter.ts#L66) | | `AgentServiceProjectSteering` | Public API contract for hosted agent project steering. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/agent-project-steering.ts#L64) | | `AgentServiceProjectSteeringLogger` | Public API contract for hosted agent project steering logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/agent-project-steering.ts#L51) | | `AgentServiceProjectSteeringOptions` | Options accepted by hosted agent project steering. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/agent-project-steering.ts#L56) | @@ -1084,12 +1084,12 @@ Clear all stored messages from memory. | `CreateBootstrappedHostedChatExecutionRuntimeInput` | Input payload for create bootstrapped hosted chat execution runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-execution-runtime.ts#L130) | | `CreateConversationHostedLifecycleAdapterOptions` | Options accepted by create conversation hosted lifecycle adapter. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/hosted-lifecycle.ts#L31) | | `CreateConversationHostedTerminalAdapterOptions` | Options accepted by create conversation hosted terminal adapter. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/hosted-terminal.ts#L113) | -| `CreateDefaultAgentServiceChatRuntimeContextInput` | Input payload for create default hosted chat runtime context. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L99) | -| `CreateDefaultAgentServiceChatRuntimeOptions` | Options accepted by create default hosted chat runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L124) | -| `CreateDefaultAgentServiceProjectSteeringRefreshOptions` | Options accepted by create default hosted project steering refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L44) | -| `CreateDefaultHostedChatRuntimeContextInput` | Input payload for create default hosted chat runtime context. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L99) | -| `CreateDefaultHostedChatRuntimeOptions` | Options accepted by create default hosted chat runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L124) | -| `CreateDefaultHostedProjectSteeringRefreshOptions` | Options accepted by create default hosted project steering refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L44) | +| `CreateDefaultAgentServiceChatRuntimeContextInput` | Input payload for create default hosted chat runtime context. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L100) | +| `CreateDefaultAgentServiceChatRuntimeOptions` | Options accepted by create default hosted chat runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L125) | +| `CreateDefaultAgentServiceProjectSteeringRefreshOptions` | Options accepted by create default hosted project steering refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L49) | +| `CreateDefaultHostedChatRuntimeContextInput` | Input payload for create default hosted chat runtime context. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L100) | +| `CreateDefaultHostedChatRuntimeOptions` | Options accepted by create default hosted chat runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L125) | +| `CreateDefaultHostedProjectSteeringRefreshOptions` | Options accepted by create default hosted project steering refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L49) | | `CreateHostedAgentRunSpanControllerInput` | Input payload for create hosted agent run span controller. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/agent-run-lifecycle.ts#L53) | | `CreateHostedAgentServiceRuntimeOptions` | Options accepted by create hosted agent service runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/runtime.ts#L68) | | `CreateHostedChatExecutionRuntimeBootstrapInput` | Input payload for create hosted chat execution runtime bootstrap. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-execution-runtime.ts#L101) | @@ -1110,28 +1110,28 @@ Clear all stored messages from memory. | `DefaultAgentServiceChatRuntimeConfig` | Configuration used by default hosted chat runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L52) | | `DefaultAgentServiceChatRuntimeCreationOptions` | Options accepted by default hosted chat runtime creation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L65) | | `DefaultAgentServiceChatRuntimeLogger` | Public API contract for default hosted chat runtime logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L60) | -| `DefaultAgentServiceChatRuntimeProjectSwitchInput` | Input payload for default hosted chat runtime project switch. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L118) | -| `DefaultAgentServiceChatRuntimeSteeringMutationInput` | Input payload for default hosted chat runtime steering mutation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L112) | -| `DefaultAgentServiceChatRuntimeSystemRefreshInput` | Input payload for default hosted chat runtime system refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L105) | +| `DefaultAgentServiceChatRuntimeProjectSwitchInput` | Input payload for default hosted chat runtime project switch. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L119) | +| `DefaultAgentServiceChatRuntimeSteeringMutationInput` | Input payload for default hosted chat runtime steering mutation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L113) | +| `DefaultAgentServiceChatRuntimeSystemRefreshInput` | Input payload for default hosted chat runtime system refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L106) | | `DefaultAgentServiceChatRuntimeTaskContext` | Context for default hosted chat runtime task. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L75) | | `DefaultAgentServiceInvokeAgentConfig` | Configuration used by default hosted invoke agent. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L93) | | `DefaultAgentServiceInvokeAgentContext` | Context for default hosted invoke agent. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L79) | -| `DefaultAgentServiceInvokeAgentInput` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L233) | -| `DefaultAgentServiceInvokeAgentLogger` | Public API contract for default hosted invoke agent logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L118) | -| `DefaultAgentServiceInvokeAgentProjectRefresh` | Public API contract for default hosted invoke agent project refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L143) | -| `DefaultAgentServiceInvokeAgentToolOptions` | Options accepted by default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L150) | -| `DefaultAgentServiceInvokeAgentToolResult` | Result returned from default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L138) | -| `DefaultAgentServiceInvokeAgentTrace` | Public API contract for default hosted invoke agent trace. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L132) | -| `DefaultAgentServiceInvokeAgentTraceAttributes` | Public API contract for default hosted invoke agent trace attributes. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L126) | -| `DefaultAgentServiceProjectSteeringFetchers` | Public API contract for default hosted project steering fetchers. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L34) | -| `DefaultAgentServiceProjectSteeringRefreshLogger` | Public API contract for default hosted project steering refresh logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L22) | -| `DefaultAgentServiceProjectSteeringRefreshLookup` | Public API contract for default hosted project steering refresh lookup. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L27) | +| `DefaultAgentServiceInvokeAgentInput` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L234) | +| `DefaultAgentServiceInvokeAgentLogger` | Public API contract for default hosted invoke agent logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L119) | +| `DefaultAgentServiceInvokeAgentProjectRefresh` | Public API contract for default hosted invoke agent project refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L144) | +| `DefaultAgentServiceInvokeAgentToolOptions` | Options accepted by default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L151) | +| `DefaultAgentServiceInvokeAgentToolResult` | Result returned from default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L139) | +| `DefaultAgentServiceInvokeAgentTrace` | Public API contract for default hosted invoke agent trace. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L133) | +| `DefaultAgentServiceInvokeAgentTraceAttributes` | Public API contract for default hosted invoke agent trace attributes. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L127) | +| `DefaultAgentServiceProjectSteeringFetchers` | Public API contract for default hosted project steering fetchers. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L39) | +| `DefaultAgentServiceProjectSteeringRefreshLogger` | Public API contract for default hosted project steering refresh logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L27) | +| `DefaultAgentServiceProjectSteeringRefreshLookup` | Public API contract for default hosted project steering refresh lookup. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L32) | | `DefaultHostedChatRuntimeConfig` | Configuration used by default hosted chat runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L52) | | `DefaultHostedChatRuntimeCreationOptions` | Options accepted by default hosted chat runtime creation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L65) | | `DefaultHostedChatRuntimeLogger` | Public API contract for default hosted chat runtime logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L60) | -| `DefaultHostedChatRuntimeProjectSwitchInput` | Input payload for default hosted chat runtime project switch. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L118) | -| `DefaultHostedChatRuntimeSteeringMutationInput` | Input payload for default hosted chat runtime steering mutation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L112) | -| `DefaultHostedChatRuntimeSystemRefreshInput` | Input payload for default hosted chat runtime system refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L105) | +| `DefaultHostedChatRuntimeProjectSwitchInput` | Input payload for default hosted chat runtime project switch. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L119) | +| `DefaultHostedChatRuntimeSteeringMutationInput` | Input payload for default hosted chat runtime steering mutation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L113) | +| `DefaultHostedChatRuntimeSystemRefreshInput` | Input payload for default hosted chat runtime system refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L106) | | `DefaultHostedChatRuntimeTaskContext` | Context for default hosted chat runtime task. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-chat-runtime.ts#L75) | | `DefaultHostedChildForkRuntimeToolPreparationResult` | Result returned from default hosted child fork runtime tool preparation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-requested-tools.ts#L169) | | `DefaultHostedChildForkToolAssemblyResult` | Result returned from default hosted child fork tool assembly. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-requested-tools.ts#L194) | @@ -1139,16 +1139,16 @@ Clear all stored messages from memory. | `DefaultHostedChildForkToolSourcesResult` | Result returned from default hosted child fork tool sources. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-fork-tool-sources.ts#L64) | | `DefaultHostedInvokeAgentConfig` | Configuration used by default hosted invoke agent. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L93) | | `DefaultHostedInvokeAgentContext` | Context for default hosted invoke agent. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L79) | -| `DefaultHostedInvokeAgentInput` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L233) | -| `DefaultHostedInvokeAgentLogger` | Public API contract for default hosted invoke agent logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L118) | -| `DefaultHostedInvokeAgentProjectRefresh` | Public API contract for default hosted invoke agent project refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L143) | -| `DefaultHostedInvokeAgentToolOptions` | Options accepted by default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L150) | -| `DefaultHostedInvokeAgentToolResult` | Result returned from default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L138) | -| `DefaultHostedInvokeAgentTrace` | Public API contract for default hosted invoke agent trace. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L132) | -| `DefaultHostedInvokeAgentTraceAttributes` | Public API contract for default hosted invoke agent trace attributes. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L126) | -| `DefaultHostedProjectSteeringFetchers` | Public API contract for default hosted project steering fetchers. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L34) | -| `DefaultHostedProjectSteeringRefreshLogger` | Public API contract for default hosted project steering refresh logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L22) | -| `DefaultHostedProjectSteeringRefreshLookup` | Public API contract for default hosted project steering refresh lookup. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L27) | +| `DefaultHostedInvokeAgentInput` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L234) | +| `DefaultHostedInvokeAgentLogger` | Public API contract for default hosted invoke agent logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L119) | +| `DefaultHostedInvokeAgentProjectRefresh` | Public API contract for default hosted invoke agent project refresh. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L144) | +| `DefaultHostedInvokeAgentToolOptions` | Options accepted by default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L151) | +| `DefaultHostedInvokeAgentToolResult` | Result returned from default hosted invoke agent tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L139) | +| `DefaultHostedInvokeAgentTrace` | Public API contract for default hosted invoke agent trace. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L133) | +| `DefaultHostedInvokeAgentTraceAttributes` | Public API contract for default hosted invoke agent trace attributes. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L127) | +| `DefaultHostedProjectSteeringFetchers` | Public API contract for default hosted project steering fetchers. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L39) | +| `DefaultHostedProjectSteeringRefreshLogger` | Public API contract for default hosted project steering refresh logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L27) | +| `DefaultHostedProjectSteeringRefreshLookup` | Public API contract for default hosted project steering refresh lookup. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L32) | | `DefaultResearchArtifactContext` | Context for default research artifact. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/artifacts/default-research-artifact-support.ts#L16) | | `DefaultResearchArtifactLogger` | Public API contract for default research artifact logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/artifacts/default-research-artifact-support.ts#L24) | | `DefaultResearchArtifactPaths` | Public API contract for default research artifact paths. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/artifacts/default-research-artifact-policy.ts#L67) | @@ -1182,8 +1182,8 @@ Clear all stored messages from memory. | `ExternalAgentWorkerRequestSnapshot` | Public API contract for external agent worker request snapshot. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/external-worker-client.ts#L21) | | `ExternalAgentWorkerRun` | Public API contract for external agent worker run. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/external-worker-client.ts#L50) | | `ExternalAgentWorkerSession` | Public API contract for external agent worker session. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/external-worker-client.ts#L36) | -| `FetchDefaultAgentServiceProjectSteeringInput` | Input payload for fetch default hosted project steering. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L55) | -| `FetchDefaultHostedProjectSteeringInput` | Input payload for fetch default hosted project steering. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L55) | +| `FetchDefaultAgentServiceProjectSteeringInput` | Input payload for fetch default hosted project steering. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L60) | +| `FetchDefaultHostedProjectSteeringInput` | Input payload for fetch default hosted project steering. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-project-steering-refresh.ts#L60) | | `FinalizedMessageState` | State for finalized message. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/finalized-message.ts#L37) | | `FinalizeHostedChildForkRunContextResourcesInput` | Input payload for finalize hosted child fork run context resources. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-fork-run-context.ts#L119) | | `FinalizeHostedDetachedOptions` | Options accepted by finalize hosted detached. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/stream-finalization.ts#L53) | @@ -1226,9 +1226,9 @@ Clear all stored messages from memory. | `HostedAgentServiceStreamExecutionInput` | Input payload for hosted agent service stream execution. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/routes.ts#L56) | | `HostedAgUiChatForwardedConfig` | Configuration used by hosted AG-UI chat forwarded. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/ag-ui-chat-request.ts#L44) | | `HostedChatExecutionLifecycleAdapter` | Public API contract for hosted chat execution lifecycle adapter. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-execution-lifecycle-types.ts#L5) | -| `HostedChatExecutionPreparationInput` | Input payload for hosted chat execution preparation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L207) | -| `HostedChatExecutionPreparationResult` | Result returned from hosted chat execution preparation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L246) | -| `HostedChatExecutionPreparationRootRunOptions` | Options accepted by hosted chat execution preparation root run. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L186) | +| `HostedChatExecutionPreparationInput` | Input payload for hosted chat execution preparation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L208) | +| `HostedChatExecutionPreparationResult` | Result returned from hosted chat execution preparation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L247) | +| `HostedChatExecutionPreparationRootRunOptions` | Options accepted by hosted chat execution preparation root run. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L187) | | `HostedChatExecutionRootStreamWatchdog` | Public API contract for hosted chat execution root stream watchdog. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-execution-runtime.ts#L86) | | `HostedChatExecutionRunContext` | Context for hosted chat execution run. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-execution-runtime.ts#L80) | | `HostedChatExecutionRuntime` | Public API contract for hosted chat execution runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-execution-runtime.ts#L67) | @@ -1239,26 +1239,26 @@ Clear all stored messages from memory. | `HostedChatRequest` | Request payload for hosted chat. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request.ts#L468) | | `HostedChatRequestInput` | Input payload for hosted chat request. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request.ts#L470) | | `HostedChatRequestPrincipal` | Public API contract for hosted chat request principal. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request-parser.ts#L30) | -| `HostedChatRuntimeAgent` | Public API contract for hosted chat runtime agent. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L84) | +| `HostedChatRuntimeAgent` | Public API contract for hosted chat runtime agent. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L85) | | `HostedChatRuntimeAgentAdapterInput` | Input payload for hosted chat runtime agent adapter. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-agent-adapter.ts#L25) | | `HostedChatRuntimeAgentAdapterRunner` | Public API contract for hosted chat runtime agent adapter runner. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-agent-adapter.ts#L14) | | `HostedChatRuntimeAgentAdapterWarning` | Public API contract for hosted chat runtime agent adapter warning. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-agent-adapter.ts#L19) | | `HostedChatRuntimeAllowedToolNames` | Public API contract for hosted chat runtime allowed tool names. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-tool-assembly.ts#L60) | -| `HostedChatRuntimeCreationOptions` | Options accepted by hosted chat runtime creation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L116) | -| `HostedChatRuntimeCreationPreparationInput` | Input payload for hosted chat runtime creation preparation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L92) | -| `HostedChatRuntimeCreationPreparationResult` | Result returned from hosted chat runtime creation preparation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L127) | -| `HostedChatRuntimeCreationResult` | Result returned from hosted chat runtime creation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L91) | -| `HostedChatRuntimeFinishPart` | Public API contract for hosted chat runtime finish part. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L14) | -| `HostedChatRuntimeInstructionsInput` | Input payload for hosted chat runtime instructions. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L81) | -| `HostedChatRuntimeOnFinishEvent` | Event emitted for hosted chat runtime on finish. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L51) | -| `HostedChatRuntimePreparationRootRunContext` | Context for hosted chat runtime preparation root run. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L67) | -| `HostedChatRuntimePreparationSteering` | Public API contract for hosted chat runtime preparation steering. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L75) | -| `HostedChatRuntimeProjectSteering` | Public API contract for hosted chat runtime project steering. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L99) | -| `HostedChatRuntimeStreamInput` | Input payload for hosted chat runtime stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L70) | -| `HostedChatRuntimeStreamResult` | Result returned from hosted chat runtime stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L76) | +| `HostedChatRuntimeCreationOptions` | Options accepted by hosted chat runtime creation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L118) | +| `HostedChatRuntimeCreationPreparationInput` | Input payload for hosted chat runtime creation preparation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L93) | +| `HostedChatRuntimeCreationPreparationResult` | Result returned from hosted chat runtime creation preparation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L128) | +| `HostedChatRuntimeCreationResult` | Result returned from hosted chat runtime creation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L92) | +| `HostedChatRuntimeFinishPart` | Public API contract for hosted chat runtime finish part. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L15) | +| `HostedChatRuntimeInstructionsInput` | Input payload for hosted chat runtime instructions. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L82) | +| `HostedChatRuntimeOnFinishEvent` | Event emitted for hosted chat runtime on finish. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L52) | +| `HostedChatRuntimePreparationRootRunContext` | Context for hosted chat runtime preparation root run. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L68) | +| `HostedChatRuntimePreparationSteering` | Public API contract for hosted chat runtime preparation steering. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L76) | +| `HostedChatRuntimeProjectSteering` | Public API contract for hosted chat runtime project steering. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L100) | +| `HostedChatRuntimeStreamInput` | Input payload for hosted chat runtime stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L71) | +| `HostedChatRuntimeStreamResult` | Result returned from hosted chat runtime stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L77) | | `HostedChatRuntimeToolAssemblyContext` | Context for hosted chat runtime tool assembly. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-tool-assembly.ts#L46) | | `HostedChatRuntimeToolAssemblyResult` | Result returned from hosted chat runtime tool assembly. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-tool-assembly.ts#L63) | -| `HostedChatRuntimeToUiMessageStreamOptions` | Options accepted by hosted chat runtime to UI message stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L60) | +| `HostedChatRuntimeToUiMessageStreamOptions` | Options accepted by hosted chat runtime to UI message stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-contract.ts#L61) | | `HostedChildChunkMirror` | Public API contract for hosted child chunk mirror. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-mirror.ts#L5) | | `HostedChildConversationBodyInput` | Input payload for hosted child conversation body. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-bootstrap.ts#L6) | | `HostedChildExecutionLifecycleOptions` | Options accepted by hosted child execution lifecycle. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-lifecycle.ts#L106) | @@ -1355,10 +1355,10 @@ Clear all stored messages from memory. | `HostedProjectRemoteToolSourcePrepareToolInput` | Input payload for hosted project remote tool source prepare tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-remote-tool-source.ts#L42) | | `HostedProjectRemoteToolSourceProjectSwitchHandler` | Handler for hosted project remote tool source project switch. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-remote-tool-source.ts#L37) | | `HostedProjectRemoteToolSourceRetryPolicy` | Public API contract for hosted project remote tool source retry policy. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-remote-tool-source.ts#L49) | -| `HostedProjectSkillIdsContext` | Context for hosted project skill IDs. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-steering-adapter.ts#L61) | -| `HostedProjectSteeringAdapter` | Public API contract for hosted project steering adapter. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-steering-adapter.ts#L73) | -| `HostedProjectSteeringAdapterOptions` | Options accepted by hosted project steering adapter. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-steering-adapter.ts#L48) | -| `HostedProjectSteeringLogger` | Public API contract for hosted project steering logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-steering-adapter.ts#L43) | +| `HostedProjectSkillIdsContext` | Context for hosted project skill IDs. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-steering-adapter.ts#L66) | +| `HostedProjectSteeringAdapter` | Public API contract for hosted project steering adapter. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-steering-adapter.ts#L79) | +| `HostedProjectSteeringAdapterOptions` | Options accepted by hosted project steering adapter. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-steering-adapter.ts#L53) | +| `HostedProjectSteeringLogger` | Public API contract for hosted project steering logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/project-steering-adapter.ts#L48) | | `HostedResponseFinalizationState` | State for hosted response finalization. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/stream-finalization.ts#L14) | | `HostedResponseStreamHeartbeat` | Public API contract for hosted response stream heartbeat. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/response-stream.ts#L15) | | `HostedResponseStreamHeartbeatState` | State for hosted response stream heartbeat. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/response-stream.ts#L9) | @@ -1436,13 +1436,13 @@ Clear all stored messages from memory. | `NodeHostedAgentServiceTelemetryEnv` | Public API contract for node hosted agent service telemetry env. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/node-telemetry.ts#L14) | | `NodeHostedAgentServiceTelemetryLogger` | Public API contract for node hosted agent service telemetry logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/node-telemetry.ts#L66) | | `NodeHostedAgentServiceTelemetryProcessTarget` | Public API contract for node hosted agent service telemetry process target. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/node-telemetry.ts#L72) | -| `NodeVeryfrontCloudAgentServiceMcpServer` | Public API contract for node Veryfront Cloud agent service MCP server. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L65) | -| `NodeVeryfrontCloudAgentServiceOptions` | Options accepted by node Veryfront Cloud agent service. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L82) | +| `NodeVeryfrontCloudAgentServiceMcpServer` | Public API contract for node Veryfront Cloud agent service MCP server. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L66) | +| `NodeVeryfrontCloudAgentServiceOptions` | Options accepted by node Veryfront Cloud agent service. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L83) | | `NodeVeryfrontCloudAgentServicePreparedExecution` | Full type of a prepared cloud agent chat execution, ready to stream or detach. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/cloud-agent-chat-execution.ts#L71) | -| `NodeVeryfrontCloudAgentServiceProcessTarget` | Public API contract for node Veryfront Cloud agent service process target. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L54) | -| `NormalizedAgentServiceChatRequest` | Request payload for normalized hosted chat. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L44) | +| `NodeVeryfrontCloudAgentServiceProcessTarget` | Public API contract for node Veryfront Cloud agent service process target. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L55) | +| `NormalizedAgentServiceChatRequest` | Request payload for normalized hosted chat. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L45) | | `NormalizedAgentServiceContract` | Public API contract for normalized agent service contract. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/definition.ts#L114) | -| `NormalizedHostedChatRequest` | Request payload for normalized hosted chat. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L44) | +| `NormalizedHostedChatRequest` | Request payload for normalized hosted chat. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L45) | | `OpenToolCalls` | Public API contract for open tool calls. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/streaming/mirrored-tool-chunk-state.ts#L110) | | `ParseAgentServiceChatRequestOptions` | Options accepted by parse hosted chat request. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request-parser.ts#L71) | | `ParseAgUiSseResponseOptions` | Options for `parseAgUiSseResponse()`. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/ag-ui/sse-parser.ts#L50) | @@ -1451,13 +1451,13 @@ Clear all stored messages from memory. | `ParsedAgUiSseRun` | Parsed AG-UI SSE response summary for evals, canaries, and host tests. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/ag-ui/sse-parser.ts#L30) | | `ParsedHostedAgUiRequest` | Request payload for parsed hosted AG-UI. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/ag-ui-chat-request.ts#L60) | | `ParsedHostedChatRequest` | Request payload for parsed hosted chat. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request-parser.ts#L48) | -| `ParsedRuntimeSkillDocument` | Public API contract for parsed runtime skill document. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L191) | +| `ParsedRuntimeSkillDocument` | Public API contract for parsed runtime skill document. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L224) | | `ParseHostedChatRequestOptions` | Options accepted by parse hosted chat request. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request-parser.ts#L71) | | `ParseRuntimeAgentMarkdownDefinitionInput` | Input payload for parse runtime agent markdown definition. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/agent-definition.ts#L102) | | `ParseRuntimeAgentRunInvocationHostedChatRequestOptions` | Options accepted when parsing a signed control-plane runtime invocation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-request-parser.ts#L80) | | `PersistConversationUserMessageFailure` | Public API contract for persist conversation user message failure. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/bootstrap.ts#L70) | | `PrepareAgentRuntimeMessagesFromUiMessagesOptions` | Options accepted by prepare agent runtime messages from UI messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/message-preparation.ts#L22) | -| `PrepareAgentServiceChatRuntimeMessagesOptions` | Options accepted by prepare hosted chat runtime messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L51) | +| `PrepareAgentServiceChatRuntimeMessagesOptions` | Options accepted by prepare hosted chat runtime messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L52) | | `PrepareAgentServiceConversationRootRunContextInput` | Input payload for prepare hosted conversation root run context. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/root-run-lifecycle.ts#L75) | | `PrepareConversationRootRunLifecycleOptions` | Options accepted by prepare conversation root run lifecycle. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/root-run-lifecycle.ts#L23) | | `PreparedAgentServiceChatExecution` | Public API contract for prepared hosted chat execution. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/prepared-chat-execution.ts#L13) | @@ -1470,7 +1470,7 @@ Clear all stored messages from memory. | `PreparedHostedChatExecutionDetachedInput` | Input payload for prepared hosted chat execution detached. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/prepared-chat-execution.ts#L64) | | `PreparedHostedChatExecutionRuntimeOptions` | Options accepted by prepared hosted chat execution runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/prepared-chat-execution.ts#L34) | | `PreparedHostedChatExecutionStreamInput` | Input payload for prepared hosted chat execution stream. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/prepared-chat-execution.ts#L56) | -| `PrepareHostedChatRuntimeMessagesOptions` | Options accepted by prepare hosted chat runtime messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L51) | +| `PrepareHostedChatRuntimeMessagesOptions` | Options accepted by prepare hosted chat runtime messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-preparation.ts#L52) | | `PrepareHostedChatRuntimeToolAssemblyInput` | Input payload for prepare hosted chat runtime tool assembly. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/chat-runtime-tool-assembly.ts#L77) | | `PrepareHostedChildForkRuntimeStepMessagesInput` | Input payload for prepare hosted child fork runtime step messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/child-fork-step-message-preparation.ts#L22) | | `PrepareHostedConversationRootRunContextInput` | Input payload for prepare hosted conversation root run context. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/root-run-lifecycle.ts#L75) | @@ -1534,16 +1534,16 @@ Clear all stored messages from memory. | `RuntimeFileUrlResolverInput` | Input payload for runtime file URL resolver. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/message-file-url-refresh.ts#L13) | | `RuntimeGetProjectFileOptions` | Options accepted by runtime get project file. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/project-files-client.ts#L62) | | `RuntimeLoadedProjectSkill` | Public API contract for runtime loaded project skill. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/project-skill-loader.ts#L30) | -| `RuntimeLoadedSkillResponse` | Response payload for runtime loaded skill. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L149) | -| `RuntimeLoadedSkillResponseMessages` | Public API contract for runtime loaded skill response messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L140) | -| `RuntimeLoadSkillBuiltinStore` | Public API contract for runtime load skill builtin store. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L118) | -| `RuntimeLoadSkillErrorOutput` | Output from runtime load skill error. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L172) | -| `RuntimeLoadSkillReferenceFileOutput` | Output from runtime load skill reference file. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L165) | -| `RuntimeLoadSkillToolContext` | Context for runtime load skill tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L108) | -| `RuntimeLoadSkillToolInput` | Input payload for runtime load skill tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L160) | -| `RuntimeLoadSkillToolMessages` | Public API contract for runtime load skill tool messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L125) | -| `RuntimeLoadSkillToolOptions` | Options accepted by runtime load skill tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L128) | -| `RuntimeLoadSkillToolOutput` | Output from runtime load skill tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L177) | +| `RuntimeLoadedSkillResponse` | Response payload for runtime loaded skill. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L182) | +| `RuntimeLoadedSkillResponseMessages` | Public API contract for runtime loaded skill response messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L173) | +| `RuntimeLoadSkillBuiltinStore` | Public API contract for runtime load skill builtin store. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L120) | +| `RuntimeLoadSkillErrorOutput` | Output from runtime load skill error. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L174) | +| `RuntimeLoadSkillReferenceFileOutput` | Output from runtime load skill reference file. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L167) | +| `RuntimeLoadSkillToolContext` | Context for runtime load skill tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L109) | +| `RuntimeLoadSkillToolInput` | Input payload for runtime load skill tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L162) | +| `RuntimeLoadSkillToolMessages` | Public API contract for runtime load skill tool messages. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L127) | +| `RuntimeLoadSkillToolOptions` | Options accepted by runtime load skill tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L130) | +| `RuntimeLoadSkillToolOutput` | Output from runtime load skill tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/load-skill-tool.ts#L179) | | `RuntimeProjectFile` | Public API contract for runtime project file. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/project-files-client.ts#L48) | | `RuntimeProjectFileListItem` | Public API contract for runtime project file list item. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/project-files-client.ts#L50) | | `RuntimeProjectFilesApiOptions` | Options accepted by runtime project files API. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/project-files-client.ts#L55) | @@ -1559,9 +1559,9 @@ Clear all stored messages from memory. | `RuntimeProjectSkillLoaderOptions` | Options accepted by runtime project skill loader. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/project-skill-loader.ts#L41) | | `RuntimeProjectSteeringLookup` | Public API contract for runtime project steering lookup. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/project-skill-catalog.ts#L25) | | `RuntimePromptBlockOptions` | Options accepted by runtime prompt block. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/prompt-block.ts#L2) | -| `RuntimeSkillDefinition` | Definition for runtime skill. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L64) | -| `RuntimeSkillFrontmatter` | Public API contract for runtime skill frontmatter. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L21) | -| `RuntimeSkillMetadataLogger` | Public API contract for runtime skill metadata logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L167) | +| `RuntimeSkillDefinition` | Definition for runtime skill. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L69) | +| `RuntimeSkillFrontmatter` | Public API contract for runtime skill frontmatter. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L26) | +| `RuntimeSkillMetadataLogger` | Public API contract for runtime skill metadata logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/skill-metadata.ts#L200) | | `RuntimeStateRequest` | Request payload for runtime state. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/types.ts#L280) | | `RuntimeStateResolver` | Public API contract for runtime state resolver. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/types.ts#L296) | | `RuntimeUploadUrlClientOptions` | Options accepted by runtime upload URL client. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/upload-url-client.ts#L24) | @@ -1598,7 +1598,7 @@ Clear all stored messages from memory. | `ToolExecutionDataEventPublisher` | Public API contract for tool execution data event publisher. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/streaming/tool-execution-data-event-bridge.ts#L5) | | `ToolResultPart` | Agent message part for a tool result. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/schemas/agent.schema.ts#L250) | | `VeryfrontCloudAgentServiceChatExecutionPreparationLogger` | Public API contract for Veryfront Cloud hosted chat execution preparation logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/cloud-chat-execution-preparation.ts#L20) | -| `VeryfrontCloudAgentServiceOptions` | Options accepted by Veryfront Cloud agent service. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L126) | +| `VeryfrontCloudAgentServiceOptions` | Options accepted by Veryfront Cloud agent service. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/veryfront-cloud-agent-service.ts#L127) | | `VeryfrontCloudHostedChatExecutionPreparationLogger` | Public API contract for Veryfront Cloud hosted chat execution preparation logger. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/cloud-chat-execution-preparation.ts#L20) | | `WaitForDurableHumanInputResolutionOptions` | Options accepted by wait for durable human input resolution. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/input/human-input.ts#L185) | | `WaitForHumanInputOptions` | Options accepted by wait for human input. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/input/human-input.ts#L193) | @@ -1618,8 +1618,8 @@ Clear all stored messages from memory. | `agUiSseEventTypes` | AG-UI runtime event type constants normalized from browser-wire SSE events. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/ag-ui/sse-parser.ts#L5) | | `conversationRunEventTypes` | Shared conversation run event types value. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/conversation/run-events.ts#L7) | | `createNodeHostedAgentServiceRuntimeInfrastructure` | Create node hosted agent service runtime infrastructure. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/service/node-runtime-infrastructure.ts#L96) | -| `defaultHostedInvokeAgentInputSchema` | Schema for default hosted invoke agent input. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L225) | -| `defaultHostedInvokeAgentSelectionSchema` | Schema for default hosted invoke agent selection. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L214) | +| `defaultHostedInvokeAgentInputSchema` | Schema for default hosted invoke agent input. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L226) | +| `defaultHostedInvokeAgentSelectionSchema` | Schema for default hosted invoke agent selection. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/default-invoke-agent-tool.ts#L215) | | `getAgUiRuntimeContextItemSchema` | Zod schema for get AG-UI runtime context item. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/ag-ui-contract.ts#L60) | | `getAgUiRuntimeInjectedToolSchema` | Zod schema for get AG-UI runtime injected tool. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/ag-ui-contract.ts#L46) | | `getAgUiRuntimeMessageSchema` | Zod schema for get AG-UI runtime message. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/runtime/ag-ui-contract.ts#L172) | diff --git a/scripts/docs/generate-api-reference.ts b/scripts/docs/generate-api-reference.ts index 4c2a6115ce..ea55ad58bf 100644 --- a/scripts/docs/generate-api-reference.ts +++ b/scripts/docs/generate-api-reference.ts @@ -1848,7 +1848,7 @@ const PROPERTY_DESCRIPTIONS: Record> = { allowedModels: 'Restrict runtime model overrides to these "provider/model" strings', skills: - "Enable all discovered skills (`true`) or only selected skill IDs (`string[]`)", + "Select visible skill IDs or this agent's own short names for prompts and `load_skill`", }, SandboxOptions: { apiUrl: diff --git a/src/agent/factory.test.ts b/src/agent/factory.test.ts index 7f30aae7d0..1ceb4c78cf 100644 --- a/src/agent/factory.test.ts +++ b/src/agent/factory.test.ts @@ -12,9 +12,57 @@ import { VeryfrontError } from "#veryfront/errors"; import { getEffectiveAgentSystem } from "./runtime/effective-agent-system.ts"; import { agentRegistry } from "./composition/index.ts"; import { agent } from "./factory.ts"; -import type { AgentConfig } from "./types.ts"; +import type { AgentConfig, AgentResponse } from "./types.ts"; import { registerSkill, skillRegistry } from "#veryfront/skill/registry.ts"; import { reset as resetExtensionContracts, tryResolve } from "#veryfront/extensions/contracts.ts"; +import { createSkillTestAdapter } from "#veryfront/skill/testing.ts"; +import type { ModelRuntime } from "#veryfront/provider"; + +function createSkill(id: string, description: string) { + return { + id, + metadata: { name: id, description }, + rootPath: `/test/skills/${id}`, + }; +} + +function createLoadSkillModel(skillId: string): ModelRuntime { + let callCount = 0; + return { + provider: "hosted", + modelId: `hosted/load-${skillId}`, + async doGenerate() { + callCount++; + if (callCount === 1) { + return { + content: [{ + type: "tool-call", + toolCallId: `load-${skillId}`, + toolName: "load_skill", + input: JSON.stringify({ skillId }), + }], + finishReason: "tool-calls", + usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 }, + }; + } + return { + content: [{ type: "text", text: "done" }], + finishReason: "stop", + usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 }, + }; + }, + async doStream() { + return { + stream: new ReadableStream({ + start(controller) { + controller.enqueue({ type: "finish", finishReason: "stop" }); + controller.close(); + }, + }), + }; + }, + }; +} describe("agent factory", () => { beforeEach(() => { @@ -29,22 +77,18 @@ describe("agent factory", () => { const assistant = agent({ id: "schema-bootstrap", system: "Stay helpful." }); assertEquals(typeof tryResolve<{ object: unknown }>("SchemaValidator")?.object, "function"); - assertEquals(assistant.config.tools, { - load_skill: true, - load_skill_reference: true, - execute_skill_script: true, - }); + assertEquals(Object.keys(assistant.config.tools ?? {}).sort(), [ + "execute_skill_script", + "load_skill", + "load_skill_reference", + ]); }); - it("enables skill infrastructure for every agent and defaults to visible skills", async () => { - registerSkill("support-triage", { - id: "support-triage", - metadata: { - name: "support-triage", - description: "Triage incoming support requests", - }, - rootPath: "/test/skills/support-triage", - }); + it("enables skill infrastructure for skill-enabled agents and defaults to visible skills", async () => { + registerSkill( + "support-triage", + createSkill("support-triage", "Triage incoming support requests"), + ); registerSkill("researcher--cite", { id: "researcher--cite", metadata: { name: "cite", description: "Cite primary sources" }, @@ -58,11 +102,11 @@ describe("agent factory", () => { system: "You are a custom agent.", }); - assertEquals(assistant.config.tools, { - load_skill: true, - load_skill_reference: true, - execute_skill_script: true, - }); + assertEquals(Object.keys(assistant.config.tools ?? {}).sort(), [ + "execute_skill_script", + "load_skill", + "load_skill_reference", + ]); assertEquals(toolRegistry.has("load_skill"), true); const effectiveSystem = getEffectiveAgentSystem(assistant); const prompt = typeof effectiveSystem === "function" @@ -79,11 +123,7 @@ describe("agent factory", () => { system: "Do not advertise skills.", skills: [], }); - assertEquals(explicitlyEmpty.config.tools, { - load_skill: true, - load_skill_reference: true, - execute_skill_script: true, - }); + assertEquals(explicitlyEmpty.config.tools, undefined); const explicitlyEmptySystem = getEffectiveAgentSystem(explicitlyEmpty); const explicitlyEmptyPrompt = typeof explicitlyEmptySystem === "function" ? await explicitlyEmptySystem() @@ -91,18 +131,191 @@ describe("agent factory", () => { assertEquals(explicitlyEmptyPrompt.includes("## Available Skills"), false); }); + it("uses the same selector snapshot for prompt disclosure and direct skill tools", async () => { + registerSkill("global-plan", createSkill("global-plan", "Plan the work")); + registerSkill("global-review", createSkill("global-review", "Review the work")); + registerSkill("writer--draft", { + ...createSkill("writer--draft", "Draft copy"), + ownerAgentId: "writer", + shortName: "draft", + }); + + const none = agent({ + id: "no-skills", + system: "No skills.", + skills: [], + tools: { + ordinary_tool: tool({ + id: "ordinary_tool", + description: "Ordinary tool", + inputSchema: defineSchema((v) => v.object({}))(), + execute: async () => ({ ok: true }), + }), + }, + }); + assertEquals(Object.keys(none.config.tools ?? {}).sort(), ["ordinary_tool"]); + const noneSystem = getEffectiveAgentSystem(none); + assertEquals( + (typeof noneSystem === "function" ? await noneSystem() : noneSystem ?? "").includes( + "Available Skills", + ), + false, + ); + + const allowlisted = agent({ + id: "writer", + system: "Use selected skills.", + skills: ["draft", "global-plan"], + }); + const allowlistedSystem = getEffectiveAgentSystem(allowlisted); + const prompt = typeof allowlistedSystem === "function" + ? await allowlistedSystem() + : allowlistedSystem ?? ""; + + assertStringIncludes(prompt, "**writer--draft**: Draft copy"); + assertStringIncludes(prompt, "**global-plan**: Plan the work"); + assertEquals(prompt.includes("global-review"), false); + + if (!allowlisted.config.tools || allowlisted.config.tools === true) { + throw new Error("Expected a concrete skill tool map"); + } + assertEquals(typeof allowlisted.config.tools.load_skill, "object"); + assertThrows( + () => + agent({ + id: "unknown-skill-agent", + system: "Bad config.", + skills: ["missing"], + }), + Error, + "configured skills are not available", + ); + }); + + it("enforces the skill allowlist for tools true registry execution", async () => { + let selectedReads = 0; + let excludedReads = 0; + const selectedAdapter = createSkillTestAdapter({ + "/test/skills/selected/SKILL.md": `--- +name: selected +description: Selected skill +--- +# Selected`, + }); + const excludedAdapter = createSkillTestAdapter({ + "/test/skills/excluded/SKILL.md": `--- +name: excluded +description: Excluded skill +--- +# Excluded`, + }); + registerSkill("selected", { + ...createSkill("selected", "Selected skill"), + fsAdapter: { + ...selectedAdapter, + async readFile(path) { + selectedReads++; + return await selectedAdapter.readFile(path); + }, + }, + }); + registerSkill("excluded", { + ...createSkill("excluded", "Excluded skill"), + fsAdapter: { + ...excludedAdapter, + async readFile(path) { + excludedReads++; + return await excludedAdapter.readFile(path); + }, + }, + }); + + async function runLoad(skillId: string): Promise { + const assistant = agent({ + id: `tools-true-${skillId}`, + model: "hosted/load-skill", + system: "Load a skill.", + tools: true, + skills: ["selected"], + resolveModelTransport: async () => ({ model: createLoadSkillModel(skillId) }), + }); + return await assistant.generate({ input: `Load ${skillId}` }); + } + + const selected = await runLoad("selected"); + assertEquals(selected.toolCalls[0]?.status, "completed"); + assertEquals(selectedReads, 1); + + const excluded = await runLoad("excluded"); + assertEquals(excluded.toolCalls[0]?.status, "error"); + assertStringIncludes(excluded.toolCalls[0]?.error ?? "", "not available to this agent"); + assertEquals(excludedReads, 0); + }); + + it("does not let runtime state spoof tools true skill authorization", async () => { + let excludedReads = 0; + const selectedAdapter = createSkillTestAdapter({ + "/test/skills/selected/SKILL.md": `--- +name: selected +description: Selected skill +--- +# Selected`, + }); + const excludedAdapter = createSkillTestAdapter({ + "/test/skills/excluded/SKILL.md": `--- +name: excluded +description: Excluded skill +--- +# Excluded`, + }); + registerSkill("selected", { + ...createSkill("selected", "Selected skill"), + fsAdapter: selectedAdapter, + }); + registerSkill("excluded", { + ...createSkill("excluded", "Excluded skill"), + fsAdapter: { + ...excludedAdapter, + async readFile(path) { + excludedReads++; + return await excludedAdapter.readFile(path); + }, + }, + }); + + const assistant = agent({ + id: "tools-true-spoofed-selector", + model: "hosted/load-skill", + system: "Load a skill.", + tools: true, + skills: ["selected"], + resolveRuntimeState: async () => ({ + context: { allowedSkillIds: ["excluded"] }, + }), + resolveModelTransport: async () => ({ model: createLoadSkillModel("excluded") }), + }); + + const response = await assistant.generate({ input: "Load excluded" }); + + assertEquals(response.toolCalls[0]?.status, "error"); + assertStringIncludes(response.toolCalls[0]?.error ?? "", "not available to this agent"); + assertEquals(excludedReads, 0); + }); + it("derives load_skill from skills without user-authored tools config", () => { + registerSkill("code-review", createSkill("code-review", "Review code")); + const assistant = agent({ id: "skill-platform-tool-test", system: "Use skills when they match the task.", skills: ["code-review"], }); - assertEquals(assistant.config.tools, { - load_skill: true, - load_skill_reference: true, - execute_skill_script: true, - }); + assertEquals(Object.keys(assistant.config.tools ?? {}).sort(), [ + "execute_skill_script", + "load_skill", + "load_skill_reference", + ]); assertEquals(toolRegistry.has("load_skill"), true); assertEquals(toolRegistry.has("load-skill"), false); }); @@ -125,14 +338,15 @@ describe("agent factory", () => { throw new Error("Expected an agent tool map"); } assertStrictEquals(assistant.config.tools.load_skill, runtimeLoadSkill); - assertEquals(assistant.config.tools.load_skill_reference, true); - assertEquals(assistant.config.tools.execute_skill_script, true); + assertEquals(typeof assistant.config.tools.load_skill_reference, "object"); + assertEquals(typeof assistant.config.tools.execute_skill_script, "object"); }); - it("does not let false disable universal skill infrastructure", () => { + it("treats legacy skills false as the explicit none selector", () => { const assistant = agent({ id: "universal-skill-tools", system: "Use skills when they match the task.", + skills: false, tools: { load_skill: false, load_skill_reference: false, @@ -140,11 +354,8 @@ describe("agent factory", () => { }, }); - assertEquals(assistant.config.tools, { - load_skill: true, - load_skill_reference: true, - execute_skill_script: true, - }); + assertEquals(assistant.config.tools, {}); + assertEquals(assistant.config.skills, false); }); it("binds one scoped tool for each declared delegate", () => { diff --git a/src/agent/factory.ts b/src/agent/factory.ts index 051664dab7..651c9ba169 100644 --- a/src/agent/factory.ts +++ b/src/agent/factory.ts @@ -83,6 +83,21 @@ const SKILL_TOOL_REGISTRATIONS = [ { id: "execute_skill_script", create: createExecuteSkillScriptTool }, ] as const; +function isExplicitNoneSkillSelector(skills: AgentConfig["skills"]): boolean { + return skills === false || (Array.isArray(skills) && skills.length === 0); +} + +function withAllowedSkillIdsContext( + context: Record | undefined, + allowedSkillIds: readonly string[], + shouldAttachAllowedSkillIds: boolean, +): Record | undefined { + if (!shouldAttachAllowedSkillIds) { + return context; + } + return { ...context, allowedSkillIds: [...allowedSkillIds] }; +} + function createAgentStreamResult(stream: ReadableStream): AgentStreamResult { return { toDataStreamResponse(options): Response { @@ -108,6 +123,15 @@ export function agent(config: AgentConfig): Agent { const id = config.id ?? generateAgentId(); const delegates = normalizeAgentDelegateIds(id, config.delegates); + const skillsConfig = config.skills === false ? [] : config.skills; + const shouldAttachAllowedSkillIds = skillsConfig !== undefined; + + const resolveSkillSnapshot = () => + skillRegistry.resolveSelectorForAgent(skillsConfig, { agentId: id }); + + if (Array.isArray(skillsConfig) && skillsConfig.length > 0) { + resolveSkillSnapshot(); + } const publicConfig: ResolvedAgentConfig = { ...config, @@ -128,9 +152,10 @@ export function agent(config: AgentConfig): Agent { } } - // Skill tools are framework infrastructure shared by every agent. Project - // skills remain project-scoped and owner-aware at resolution time. + // Skill tools are framework infrastructure shared by skill-enabled agents. + // Project skills remain project-scoped and owner-aware at resolution time. let mergedToolsConfig = config.tools; + const shouldExposeSkillTools = !isExplicitNoneSkillSelector(config.skills); ensureBuiltinSchemaValidator(); for (const registration of SKILL_TOOL_REGISTRATIONS) { @@ -142,14 +167,24 @@ export function agent(config: AgentConfig): Agent { if (config.tools !== true) { const configuredTools = { ...(config.tools ?? {}) }; for (const registration of SKILL_TOOL_REGISTRATIONS) { + if (!shouldExposeSkillTools) { + delete configuredTools[registration.id]; + continue; + } + const configuredTool = configuredTools[registration.id]; - // Skill infrastructure cannot be disabled with `false`. Preserve - // concrete tools because hosted runs bind them to request context. - if (typeof configuredTool !== "object" || configuredTool === null) { - configuredTools[registration.id] = true; + if (typeof configuredTool === "object" && configuredTool !== null) { + continue; } + + configuredTools[registration.id] = registration.create({ + resolveAllowedSkillIds: () => resolveSkillSnapshot().allowedSkillIds, + }); } - mergedToolsConfig = configuredTools; + const hasConfiguredTools = Object.keys(configuredTools).length > 0; + mergedToolsConfig = hasConfiguredTools || config.tools !== undefined + ? configuredTools + : undefined; } if (delegates?.length) { @@ -168,13 +203,13 @@ export function agent(config: AgentConfig): Agent { // System prompt augmentation with skill manifest. // Re-resolve registry-backed entries at invocation time so HMR changes are picked up. const originalSystem = config.system; - const skillsConfig = config.skills === false ? [] : config.skills ?? true; const augmentedSystem = async () => { // Owner-aware: omitted selectors advertise every skill visible to this // agent (unowned project skills plus its own). Explicit lists, including // an empty list, retain their authored catalog selection. - const currentSkills = skillRegistry.resolveForAgent(skillsConfig, { agentId: id }); + const snapshot = resolveSkillSnapshot(); + const currentSkills = new Map(snapshot.definitions.map((skill) => [skill.id, skill])); const basePrompt = (typeof originalSystem === "function" ? await originalSystem() : originalSystem) ?? "You are a helpful assistant."; @@ -227,10 +262,15 @@ export function agent(config: AgentConfig): Agent { generate(input): Promise { return withSpan( "agent.factory.generate", - () => - runtime.generate( + () => { + const skillSnapshot = resolveSkillSnapshot(); + return runtime.generate( input.input, - input.context, + withAllowedSkillIdsContext( + input.context, + skillSnapshot.allowedSkillIds, + shouldAttachAllowedSkillIds, + ), input.model, input.maxOutputTokens, input.abortSignal, @@ -238,7 +278,8 @@ export function agent(config: AgentConfig): Agent { toolReplacements: input.tools, retainSkillLoaderTools: input.retainSkillLoaderTools, }, - ), + ); + }, { "agent.id": id }, ); }, @@ -257,9 +298,14 @@ export function agent(config: AgentConfig): Agent { ] : (input.messages ?? []); + const skillSnapshot = resolveSkillSnapshot(); const stream = await runtime.stream( inputMessages, - input.context, + withAllowedSkillIdsContext( + input.context, + skillSnapshot.allowedSkillIds, + shouldAttachAllowedSkillIds, + ), { onToolCall: input.onToolCall, onChunk: input.onChunk, @@ -299,9 +345,14 @@ export function agent(config: AgentConfig): Agent { } const messages = body.messages; + const skillSnapshot = resolveSkillSnapshot(); const stream = await runtime.stream( messages, - body.context, + withAllowedSkillIdsContext( + body.context, + skillSnapshot.allowedSkillIds, + shouldAttachAllowedSkillIds, + ), undefined, modelOverride, body.maxOutputTokens, diff --git a/src/agent/hosted/agent-project-steering.test.ts b/src/agent/hosted/agent-project-steering.test.ts index f3b67ca8ca..900ea0594d 100644 --- a/src/agent/hosted/agent-project-steering.test.ts +++ b/src/agent/hosted/agent-project-steering.test.ts @@ -212,7 +212,7 @@ Plan carefully.`, projectId: null, authToken: "auth-token", branchId: null, - availableSkillIds: [], + availableSkillIds: ["plan"], availableToolNames: [], }); const result = await tool.execute({ skillId: "plan" }); diff --git a/src/agent/hosted/chat-preparation.test.ts b/src/agent/hosted/chat-preparation.test.ts index 68a3b409df..d9d7a3a9a7 100644 --- a/src/agent/hosted/chat-preparation.test.ts +++ b/src/agent/hosted/chat-preparation.test.ts @@ -268,6 +268,10 @@ Deno.test("prepareHostedChatRuntimeCreationOptions builds runtime options from r parentRunId: "run-1", parentMessageId: "message-1", availableSkillIds: ["debug"], + skillSelectorPolicy: { + kind: "all-visible", + source: "omitted", + }, publishParentRunEvents: result.creationOptions.publishParentRunEvents, clientProfile: null, liveProjectSteering: { @@ -278,6 +282,10 @@ Deno.test("prepareHostedChatRuntimeCreationOptions builds runtime options from r maxSteps: 50, tools: ["get_agent", "load_skill", "update_agent"], }, + skillSelectorPolicy: { + kind: "all-visible", + source: "omitted", + }, environmentContext: "Browser workspace", initialProjectInstructions: "Project instructions", initialSkills: [skill], @@ -1269,9 +1277,12 @@ Deno.test("prepareHostedChatRuntimeCreationOptions applies the skill selector an }); const advertised = ["researcher--cite"]; - const loadable = ["global-howto", "researcher--cite"]; assertEquals(seenByInstructions, [advertised]); - assertEquals(result.creationOptions.availableSkillIds, loadable); + assertEquals(result.creationOptions.availableSkillIds, advertised); + assertEquals(result.creationOptions.skillSelectorPolicy, { + kind: "allowlist", + entries: ["cite"], + }); assertEquals(result.creationOptions.skillSourcePaths, { "researcher--cite": "agents/researcher/skills/cite/SKILL.md", }); @@ -1284,7 +1295,7 @@ Deno.test("prepareHostedChatRuntimeCreationOptions applies the skill selector an assertEquals(result.steering.skills.map((skill) => skill.id), advertised); }); -Deno.test("prepareHostedChatRuntimeCreationOptions keeps the loader but advertises no skills for an empty selector", async () => { +Deno.test("prepareHostedChatRuntimeCreationOptions uses the exact selector snapshot for an empty selector", async () => { const result = await prepareHostedChatRuntimeCreationOptions({ request: createParsedHostedChatRequest({}), agentConfig: { id: "researcher", model: "configured-model", skills: [] }, @@ -1306,7 +1317,8 @@ Deno.test("prepareHostedChatRuntimeCreationOptions keeps the loader but advertis buildInstructions: (input) => [{ role: "system", content: `${input.skills.length}` }], }); - assertEquals(result.creationOptions.availableSkillIds, ["global-howto"]); + assertEquals(result.creationOptions.availableSkillIds, []); + assertEquals(result.creationOptions.skillSelectorPolicy, { kind: "none" }); assertEquals(result.creationOptions.instructions, [{ role: "system", content: "0" }]); assertEquals(result.steering.skills, []); }); diff --git a/src/agent/hosted/chat-preparation.ts b/src/agent/hosted/chat-preparation.ts index 138e3403e6..12cffa440e 100644 --- a/src/agent/hosted/chat-preparation.ts +++ b/src/agent/hosted/chat-preparation.ts @@ -29,9 +29,10 @@ import { import { getRuntimeUploadUrl } from "../runtime/upload-url-client.ts"; import { getProviderNativeToolNames } from "../runtime/provider-native-tool-inventory.ts"; import { - resolveRuntimeSkillsForAgent, + resolveRuntimeSkillSelectorForAgent, type RuntimeSkillDefinition, } from "../runtime/skill-metadata.ts"; +import type { ResolvedSkillSelectorPolicy } from "#veryfront/skill/selector.ts"; import { applyContextBudget, type ContextBudgetDiagnostics, @@ -281,12 +282,14 @@ export function normalizeParsedHostedChatRequest( function buildHostedChatRuntimeProjectSteering(input: { agentConfig: TRuntimeAgentDefinition; + skillSelectorPolicy: ResolvedSkillSelectorPolicy; environmentContext?: string; instructions: string; skills: RuntimeSkillDefinition[]; }): HostedChatRuntimeProjectSteering { return { agent: input.agentConfig, + skillSelectorPolicy: input.skillSelectorPolicy, ...(input.environmentContext ? { environmentContext: input.environmentContext } : {}), ...(input.instructions ? { initialProjectInstructions: input.instructions } : {}), ...(input.skills.length > 0 ? { initialSkills: input.skills } : {}), @@ -304,26 +307,19 @@ export async function prepareHostedChatRuntimeCreationOptions< authToken: input.authToken, branchId: input.branchId, }); - // The selector controls what the prompt advertises, not what load_skill can - // resolve. Keep the hosted execution gate aligned with the classic runtime: - // every owner-visible skill remains loadable by id. - const loadableSkills = resolveRuntimeSkillsForAgent({ + const skillSelectorSnapshot = resolveRuntimeSkillSelectorForAgent({ skills: steering.skills, agentId: input.agentConfig.id, - selector: true, - }); - const advertisedSkills = resolveRuntimeSkillsForAgent({ - skills: steering.skills, - agentId: input.agentConfig.id, - selector: input.agentConfig.skills, + selector: input.agentConfig.skills === false ? [] : input.agentConfig.skills, }); + const selectedSkills = skillSelectorSnapshot.definitions; const agentInstructions = input.buildInstructions({ agentConfig: input.agentConfig, projectId: input.projectId, branchId: input.branchId, environmentContext: input.environmentContext, instructions: steering.instructions, - skills: advertisedSkills, + skills: selectedSkills, }); const runtimeConfig = resolveHostedRuntimeRequestConfig({ request: input.request, @@ -375,14 +371,11 @@ export async function prepareHostedChatRuntimeCreationOptions< ...(input.rootRunContext?.effectiveParentMessageId ? { parentMessageId: input.rootRunContext.effectiveParentMessageId } : {}), - availableSkillIds: loadableSkills.map((skill) => skill.id), - ...(loadableSkills.some((skill) => skill.sourcePath) + availableSkillIds: skillSelectorSnapshot.allowedSkillIds, + skillSelectorPolicy: skillSelectorSnapshot.policy, + ...(Object.keys(skillSelectorSnapshot.skillSourcePaths).length > 0 ? { - skillSourcePaths: Object.fromEntries( - loadableSkills - .filter((skill) => skill.sourcePath) - .map((skill) => [skill.id, skill.sourcePath as string]), - ), + skillSourcePaths: skillSelectorSnapshot.skillSourcePaths, } : {}), ...(input.rootRunContext?.publishParentRunEvents @@ -391,14 +384,15 @@ export async function prepareHostedChatRuntimeCreationOptions< clientProfile: runtimeConfig.clientProfile, liveProjectSteering: buildHostedChatRuntimeProjectSteering({ agentConfig: input.agentConfig, + skillSelectorPolicy: skillSelectorSnapshot.policy, environmentContext: input.environmentContext, instructions: steering.instructions, - skills: advertisedSkills, + skills: selectedSkills, }), }, steering: { ...steering, - skills: advertisedSkills, + skills: selectedSkills, agentInstructions, }, runtimeConfig, diff --git a/src/agent/hosted/chat-runtime-contract.ts b/src/agent/hosted/chat-runtime-contract.ts index 52d29e721f..b671d86f72 100644 --- a/src/agent/hosted/chat-runtime-contract.ts +++ b/src/agent/hosted/chat-runtime-contract.ts @@ -9,6 +9,7 @@ import type { AgentRuntimeMessage } from "../runtime/message-adapter.ts"; import type { ConversationRunEvent } from "../conversation/run-events.ts"; import type { RuntimeClientProfile } from "../runtime/client-profile.ts"; import type { RuntimeSkillDefinition } from "../runtime/skill-metadata.ts"; +import type { ResolvedSkillSelectorPolicy } from "#veryfront/skill/selector.ts"; /** Public API contract for hosted chat runtime finish part. */ export type HostedChatRuntimeFinishPart = { @@ -98,6 +99,7 @@ export type HostedChatRuntimeCreationResult /** Public API contract for hosted chat runtime project steering. */ export type HostedChatRuntimeProjectSteering = { agent: TRuntimeAgentDefinition; + skillSelectorPolicy?: ResolvedSkillSelectorPolicy; environmentContext?: string; initialProjectInstructions?: string; initialSkills?: RuntimeSkillDefinition[]; @@ -138,6 +140,7 @@ export type HostedChatRuntimeCreationOptions discovered SKILL.md source path (owner-aware catalog). */ skillSourcePaths?: Readonly>; publishParentRunEvents?: (events: ConversationRunEvent[]) => Promise; diff --git a/src/agent/hosted/chat-runtime-tool-assembly.test.ts b/src/agent/hosted/chat-runtime-tool-assembly.test.ts index 5c15f55eff..b557774fd9 100644 --- a/src/agent/hosted/chat-runtime-tool-assembly.test.ts +++ b/src/agent/hosted/chat-runtime-tool-assembly.test.ts @@ -179,7 +179,7 @@ Deno.test("prepareHostedChatRuntimeToolAssembly keeps skill infrastructure for c assertEquals(taskContext.availableToolNames, ["invoke_agent", "load_skill"]); }); -Deno.test("prepareHostedChatRuntimeToolAssembly keeps the loader for config-derived empty non-skill runs", async () => { +Deno.test("prepareHostedChatRuntimeToolAssembly removes skill infrastructure for known empty skill runs", async () => { const taskContext: HostedChatRuntimeToolAssemblyContext = { authToken: "token", projectId: "project-1", @@ -204,8 +204,8 @@ Deno.test("prepareHostedChatRuntimeToolAssembly keeps the loader for config-deri preloadLatestConversationUserText: false, }); - assertEquals(toolAssembly.localToolNames, ["load_skill"]); - assertEquals(taskContext.availableToolNames, ["load_skill"]); + assertEquals(toolAssembly.localToolNames, []); + assertEquals(taskContext.availableToolNames, []); }); Deno.test("prepareHostedChatRuntimeToolAssembly builds provider-compatible runtime inventory", async () => { diff --git a/src/agent/hosted/cloud-agent-child-tools.ts b/src/agent/hosted/cloud-agent-child-tools.ts index 179270accc..8755117eb8 100644 --- a/src/agent/hosted/cloud-agent-child-tools.ts +++ b/src/agent/hosted/cloud-agent-child-tools.ts @@ -28,9 +28,10 @@ import type { AgentMcpToolPolicy } from "../types.ts"; import type { RuntimeLoadSkillToolContext } from "../runtime/load-skill-tool.ts"; import type { RuntimeProjectSteeringLookup } from "../runtime/project-skill-catalog.ts"; import { - resolveRuntimeSkillsForAgent, + resolveRuntimeSkillSelectorForAgent, type RuntimeSkillDefinition, } from "../runtime/skill-metadata.ts"; +import type { ResolvedSkillSelectorSnapshot } from "#veryfront/skill/selector.ts"; import type { RuntimeAgentMarkdownDefinition } from "../runtime/agent-definition.ts"; import { buildAgentDelegateTools } from "../runtime/agent-delegation.ts"; import { buildVeryfrontCloudRuntimeInstructions } from "./cloud-runtime-system-messages.ts"; @@ -55,6 +56,7 @@ import { const HOSTED_CHILD_LOCAL_SKILL_TOOL_NAMES = new Set([ "execute_skill_script", + "load_skill", "load_skill_reference", ]); @@ -68,6 +70,7 @@ export type ChildRunContext = RuntimeLoadSkillToolContext, | "agentId" | "availableSkillIds" + | "skillSelectorPolicy" | "skillSourcePaths" | "loadedSkillResponses" | "loadedSkillReferenceResponses" @@ -229,11 +232,17 @@ function shouldRethrowInvokeAgentError(error: unknown): boolean { /** Resolves the effective tool name allowlist for a hosted child agent run. */ export function resolveHostedChildToolNames( agentConfig: RuntimeAgentMarkdownDefinition, + skillSelectorSnapshot?: Pick< + ResolvedSkillSelectorSnapshot, + "allowedSkillIds" + >, ): string[] | undefined { if (agentConfig.tools === true) { return undefined; } + const hasAuthorizedSkills = skillSelectorSnapshot === undefined || + skillSelectorSnapshot.allowedSkillIds.length > 0; return [ ...new Set([ ...(agentConfig.tools ?? []).filter((toolName) => @@ -241,11 +250,35 @@ export function resolveHostedChildToolNames( ), ...(agentConfig.providerTools ?? []), ...(agentConfig.delegates ?? []).map((id) => `agent_${id}`), - "load_skill", + ...(hasAuthorizedSkills ? ["load_skill"] : []), ]), ]; } +/** Builds host tools for a configured hosted child run. */ +export function buildHostedChildGlobalTools( + context: NodeVeryfrontCloudAgentServiceContext, + input: { + childAgentId: string; + childConfig?: DefaultHostedChildAgentExecutionConfig; + childToolContext: ChildRunContext; + }, +): HostToolSet { + return { + ...(input.childConfig ? getDiscoveredHostTools({ agentId: input.childAgentId }) : {}), + ...(!input.childConfig || (input.childConfig.availableSkillIds?.length ?? 0) > 0 + ? { load_skill: createLoadSkillTool(context, input.childToolContext) } + : {}), + ...(input.childConfig?.delegateIds?.length + ? buildHostedDelegateTools(context, { + delegates: input.childConfig.delegateIds, + selfId: input.childAgentId, + taskContext: input.childToolContext, + }) + : {}), + }; +} + /** Builds the child run context for a nested hosted agent invocation. */ export function buildHostedChildToolContext( globalToolContext: ChildRunContext, @@ -256,8 +289,13 @@ export function buildHostedChildToolContext( return { ...globalToolContext, agentId: childAgentId, - ...(childConfig?.availableSkillIds ? { availableSkillIds: childConfig.availableSkillIds } : {}), - ...(childConfig?.skillSourcePaths ? { skillSourcePaths: childConfig.skillSourcePaths } : {}), + ...(childConfig + ? { + availableSkillIds: childConfig.availableSkillIds, + skillSelectorPolicy: childConfig.skillSelectorPolicy, + skillSourcePaths: childConfig.skillSourcePaths, + } + : {}), ...(childConfig?.toolNames ? { availableToolNames: childConfig.toolNames } : {}), loadedSkillResponses: {}, loadedSkillReferenceResponses: {}, @@ -304,22 +342,12 @@ export async function resolveHostedChildAgentExecutionConfig( authToken: taskContext.authToken, branchId, }, childAgentId); - const advertisedSkills = resolveRuntimeSkillsForAgent({ - skills: steering.skills, - agentId: childAgentId, - selector: agentConfig.skills, - }); - const loadableSkills = resolveRuntimeSkillsForAgent({ + const skillSelectorSnapshot = resolveRuntimeSkillSelectorForAgent({ skills: steering.skills, agentId: childAgentId, - selector: true, + selector: agentConfig.skills === false ? [] : agentConfig.skills, }); - const skillSourcePaths = Object.fromEntries( - loadableSkills - .filter((skill) => skill.sourcePath) - .map((skill) => [skill.id, skill.sourcePath as string]), - ); - const toolNames = resolveHostedChildToolNames(agentConfig); + const toolNames = resolveHostedChildToolNames(agentConfig, skillSelectorSnapshot); const thinking = agentConfig.thinking?.enabled === false ? 0 : agentConfig.thinking?.budgetTokens; return { @@ -328,7 +356,7 @@ export async function resolveHostedChildAgentExecutionConfig( projectId: projectId || null, branchId, instructions: steering.instructions, - skills: advertisedSkills, + skills: skillSelectorSnapshot.definitions, availableToolNames: toolNames, })), ...(agentConfig.model ? { model: agentConfig.model } : {}), @@ -337,8 +365,11 @@ export async function resolveHostedChildAgentExecutionConfig( ...(thinking === undefined ? {} : { thinking }), ...(toolNames === undefined ? {} : { toolNames }), mcpServers: resolveMcpServers(context.options, agentConfig), - availableSkillIds: loadableSkills.map((skill) => skill.id), - ...(Object.keys(skillSourcePaths).length > 0 ? { skillSourcePaths } : {}), + availableSkillIds: skillSelectorSnapshot.allowedSkillIds, + skillSelectorPolicy: skillSelectorSnapshot.policy, + ...(Object.keys(skillSelectorSnapshot.skillSourcePaths).length > 0 + ? { skillSourcePaths: skillSelectorSnapshot.skillSourcePaths } + : {}), ...(agentConfig.delegates === undefined ? {} : { delegateIds: agentConfig.delegates }), }; } @@ -369,17 +400,11 @@ export function createInvokeAgentTool( childConfig, durableChildRun, ); - return { - ...(childConfig ? getDiscoveredHostTools({ agentId: childAgentId }) : {}), - load_skill: createLoadSkillTool(context, childToolContext), - ...(childConfig?.delegateIds?.length - ? buildHostedDelegateTools(context, { - delegates: childConfig.delegateIds, - selfId: childAgentId, - taskContext: childToolContext, - }) - : {}), - }; + return buildHostedChildGlobalTools(context, { + childAgentId, + childConfig, + childToolContext, + }); }, resolveChildAgentExecutionConfig: (childAgentId, projectId) => resolveHostedChildAgentExecutionConfig(context, childContext, childAgentId, projectId), diff --git a/src/agent/hosted/default-chat-runtime.ts b/src/agent/hosted/default-chat-runtime.ts index d3196d8bd4..9b86e592aa 100644 --- a/src/agent/hosted/default-chat-runtime.ts +++ b/src/agent/hosted/default-chat-runtime.ts @@ -88,6 +88,7 @@ export type DefaultHostedChatRuntimeTaskContext = HostedRuntimeStateResolverCont parentRunId?: string; parentMessageId?: string; availableSkillIds?: string[]; + skillSelectorPolicy?: DefaultHostedChatRuntimeCreationOptions["skillSelectorPolicy"]; /** Per-run skill id -> discovered SKILL.md source path (owner-aware catalog). */ skillSourcePaths?: Readonly>; publishParentRunEvents?: DefaultHostedChatRuntimeCreationOptions["publishParentRunEvents"]; @@ -168,6 +169,7 @@ function createDefaultTaskContext( parentRunId: input.options.parentRunId, parentMessageId: input.options.parentMessageId, availableSkillIds: input.options.availableSkillIds, + skillSelectorPolicy: input.options.skillSelectorPolicy, skillSourcePaths: input.options.skillSourcePaths, publishParentRunEvents: input.options.publishParentRunEvents, submittedFormInputResult: input.options.submittedFormInputResult, diff --git a/src/agent/hosted/default-invoke-agent-tool.ts b/src/agent/hosted/default-invoke-agent-tool.ts index 22283cccf1..fa23105f9f 100644 --- a/src/agent/hosted/default-invoke-agent-tool.ts +++ b/src/agent/hosted/default-invoke-agent-tool.ts @@ -108,6 +108,7 @@ export type DefaultHostedChildAgentExecutionConfig = { toolNames?: string[]; mcpServers?: readonly AgentServiceMcpServerConfig[]; availableSkillIds?: string[]; + skillSelectorPolicy?: import("#veryfront/skill/selector.ts").ResolvedSkillSelectorPolicy; skillSourcePaths?: Readonly>; loadedSkillResponses?: RuntimeLoadSkillToolContext["loadedSkillResponses"]; loadedSkillReferenceResponses?: RuntimeLoadSkillToolContext["loadedSkillReferenceResponses"]; diff --git a/src/agent/hosted/default-project-steering-refresh.test.ts b/src/agent/hosted/default-project-steering-refresh.test.ts index 481f0f7e33..29f544bc04 100644 --- a/src/agent/hosted/default-project-steering-refresh.test.ts +++ b/src/agent/hosted/default-project-steering-refresh.test.ts @@ -1,4 +1,4 @@ -import { assertEquals, assertStringIncludes } from "#veryfront/testing/assert.ts"; +import { assertEquals, assertRejects, assertStringIncludes } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; import { normalizeSourceIntegrationPolicy } from "#veryfront/integrations/source-policy.ts"; import type { RemoteToolSource } from "#veryfront/tool"; @@ -223,6 +223,61 @@ describe("agent/default-hosted-project-steering-refresh", () => { assertEquals(system.includes("Fresh instructions:"), true); }); + it("refreshes omitted selectors dynamically but does not broaden explicit allowlists", async () => { + const refresh = createDefaultHostedProjectSteeringRefresh({ + fetchProjectInstructions: () => Promise.resolve("Fresh instructions"), + fetchSkills: () => Promise.resolve([createSkill("build"), createSkill("new-skill")]), + buildInstructions: (input) => + `${input.instructions}:${input.skills.map((skill) => skill.id).join(",")}`, + }); + + const dynamicInput = createRefreshInput(); + dynamicInput.taskContext.skillSelectorPolicy = { kind: "all-visible", source: "omitted" }; + dynamicInput.taskContext.availableSkillIds = ["build"]; + + const dynamicSystem = await refresh(dynamicInput); + + assertStringIncludes(dynamicSystem, "Fresh instructions:build,new-skill"); + assertEquals(dynamicInput.taskContext.availableSkillIds, ["build", "new-skill"]); + + const explicitInput = createRefreshInput(); + explicitInput.liveProjectSteering.agent.skills = ["build"]; + explicitInput.taskContext.skillSelectorPolicy = { kind: "allowlist", entries: ["build"] }; + explicitInput.taskContext.availableSkillIds = ["build"]; + + const explicitSystem = await refresh(explicitInput); + + assertStringIncludes(explicitSystem, "Fresh instructions:build"); + assertEquals(explicitSystem.includes("new-skill"), false); + assertEquals(explicitInput.taskContext.availableSkillIds, ["build"]); + }); + + it("rejects deleted explicit skill selections during refresh without narrowing state", async () => { + const refresh = createDefaultHostedProjectSteeringRefresh({ + fetchProjectInstructions: () => Promise.resolve("Fresh instructions"), + fetchSkills: () => Promise.resolve([createSkill("new-skill")]), + buildInstructions: (input) => + `${input.instructions}:${input.skills.map((skill) => skill.id).join(",")}`, + }); + const input = createRefreshInput(); + input.liveProjectSteering.agent.skills = ["build"]; + input.taskContext.skillSelectorPolicy = { kind: "allowlist", entries: ["build"] }; + input.taskContext.availableSkillIds = ["build"]; + + const error = await assertRejects( + () => refresh(input), + Error, + "configured skills are not available", + ); + + assertEquals(String(error).includes("build"), false); + assertEquals(input.taskContext.availableSkillIds, ["build"]); + assertEquals(input.taskContext.skillSelectorPolicy, { + kind: "allowlist", + entries: ["build"], + }); + }); + it("keeps provider-native tools in refreshed runtime inventory", async () => { const refresh = createDefaultHostedProjectSteeringRefresh({ fetchProjectInstructions: () => Promise.resolve("Fresh instructions"), diff --git a/src/agent/hosted/default-project-steering-refresh.ts b/src/agent/hosted/default-project-steering-refresh.ts index 5c41a2b6ea..ef8cf120c1 100644 --- a/src/agent/hosted/default-project-steering-refresh.ts +++ b/src/agent/hosted/default-project-steering-refresh.ts @@ -11,12 +11,17 @@ import type { import type { HostedChatRuntimePreparationSteering } from "./chat-preparation.ts"; import type { RuntimeAgentMarkdownDefinition } from "../runtime/agent-definition.ts"; import { - resolveRuntimeSkillsForAgent, + resolveRuntimeSkillSelectorSnapshotForAgent, type RuntimeSkillDefinition, } from "../runtime/skill-metadata.ts"; import { selectProviderCompatibleToolNames } from "../runtime/provider-tool-compat.ts"; import { flattenSystemInstructions, withRuntimeToolInventory } from "../runtime/tool-inventory.ts"; import type { HostedChatRuntimeInstructionsInput } from "./chat-preparation.ts"; +import { + assertResolvedSkillSelector, + createNoneSkillSelectorSnapshot, + type ResolvedSkillSelectorPolicy, +} from "#veryfront/skill/selector.ts"; /** Public API contract for default hosted project steering refresh logger. */ export type DefaultHostedProjectSteeringRefreshLogger = { @@ -165,6 +170,46 @@ async function fetchSkillsWithFallback(input: { } } +function resolveRefreshedSkillSnapshot(input: { + skills: readonly RuntimeSkillDefinition[]; + agentId: string; + selector: true | false | readonly string[] | undefined; + policy: ResolvedSkillSelectorPolicy | undefined; +}) { + if ( + !input.policy && + (input.selector === false || (Array.isArray(input.selector) && input.selector.length === 0)) + ) { + return createNoneSkillSelectorSnapshot(); + } + + if (!input.policy && Array.isArray(input.selector)) { + return resolveRuntimeSkillSelectorSnapshotForAgent({ + skills: input.skills, + agentId: input.agentId, + selector: [...input.selector], + }); + } + + if (!input.policy || input.policy.kind === "all-visible") { + return resolveRuntimeSkillSelectorSnapshotForAgent({ + skills: input.skills, + agentId: input.agentId, + selector: input.policy?.source === "true" ? true : undefined, + }); + } + + if (input.policy.kind === "none") { + return createNoneSkillSelectorSnapshot(input.policy); + } + + return resolveRuntimeSkillSelectorSnapshotForAgent({ + skills: input.skills, + agentId: input.agentId, + selector: input.policy.entries, + }); +} + /** Create default hosted project steering refresh. */ export function createDefaultHostedProjectSteeringRefresh( options: CreateDefaultHostedProjectSteeringRefreshOptions, @@ -200,11 +245,20 @@ export function createDefaultHostedProjectSteeringRefresh( }), ]); - const advertisedSkills = resolveRuntimeSkillsForAgent({ + const skillSelectorSnapshot = resolveRefreshedSkillSnapshot({ skills, agentId: input.liveProjectSteering.agent.id, selector: input.liveProjectSteering.agent.skills, + policy: input.taskContext.skillSelectorPolicy ?? + input.liveProjectSteering.skillSelectorPolicy, }); + assertResolvedSkillSelector(skillSelectorSnapshot); + input.taskContext.availableSkillIds = skillSelectorSnapshot.allowedSkillIds; + input.taskContext.skillSelectorPolicy = skillSelectorSnapshot.policy; + input.taskContext.skillSourcePaths = + Object.keys(skillSelectorSnapshot.skillSourcePaths).length > 0 + ? skillSelectorSnapshot.skillSourcePaths + : undefined; const sourceAllowedRemoteToolNames = applySourceIntegrationPolicy( remoteToolNames, input.toolAssembly.sourceIntegrationPolicy, @@ -228,7 +282,7 @@ export function createDefaultHostedProjectSteeringRefresh( branchId, environmentContext: input.liveProjectSteering.environmentContext, instructions: projectInstructions, - skills: advertisedSkills, + skills: skillSelectorSnapshot.definitions, availableToolNames: toolNames, }); diff --git a/src/agent/hosted/project-steering-adapter.test.ts b/src/agent/hosted/project-steering-adapter.test.ts index 19bdf2a9f4..815c4bcf1d 100644 --- a/src/agent/hosted/project-steering-adapter.test.ts +++ b/src/agent/hosted/project-steering-adapter.test.ts @@ -1,7 +1,10 @@ import "#veryfront/schemas/_test-setup.ts"; -import { assert, assertEquals } from "#veryfront/testing/assert.ts"; +import { assert, assertEquals, assertRejects } from "#veryfront/testing/assert.ts"; import { join } from "node:path"; -import { createHostedProjectSteeringAdapter } from "./project-steering-adapter.ts"; +import { + createHostedProjectSteeringAdapter, + type HostedProjectSkillIdsContext, +} from "./project-steering-adapter.ts"; import type { RuntimeGetProjectFileOptions, RuntimeProjectFile, @@ -118,7 +121,7 @@ Use project instructions.`, getProjectFiles: async () => [{ path: ".veryfront/skills/project/SKILL.md" }], }), }); - const context = { + const context: HostedProjectSkillIdsContext = { projectId: "project-1", authToken: "token-1", branchId: null, @@ -172,7 +175,6 @@ Deno.test("hosted project steering adapter accepts a custom builtin skill store" projectId: null, authToken: "token-1", branchId: null, - availableSkillIds: [], }); const result = await loadSkillTool.execute({ skillId: "custom" }); @@ -240,3 +242,50 @@ Body.`; assertEquals(projectContext.availableSkillIds, ["builtin", "global"]); }); }); + +Deno.test("refreshProjectSkillIds rejects unresolved authored allowlist entries without narrowing", async () => { + await withSkillsDir(async (skillsDir) => { + const adapter = createHostedProjectSteeringAdapter({ + apiUrl: "https://api.example.test", + skillsDir, + projectFilesClient: createProjectFilesClient({ + getProjectFile: async ({ path }) => + path === "skills/global/SKILL.md" || path === "skills/new-skill/SKILL.md" + ? { + path, + content: `--- +description: ${path} +--- +Body.`, + } + : null, + getProjectFiles: async () => [ + { path: "skills/global/SKILL.md" }, + { path: "skills/new-skill/SKILL.md" }, + ], + }), + }); + + const context: HostedProjectSkillIdsContext = { + projectId: "project-1", + authToken: "token-1", + branchId: null, + availableSkillIds: ["global", "new-skill"], + skillSelectorPolicy: { kind: "allowlist" as const, entries: ["global", "deleted"] }, + }; + + const error = await assertRejects( + () => adapter.refreshProjectSkillIds(context), + Error, + "configured skills are not available", + ); + + assertEquals(String(error).includes("deleted"), false); + assertEquals(context.availableSkillIds, ["global", "new-skill"]); + assertEquals(context.skillSelectorPolicy, { + kind: "allowlist", + entries: ["global", "deleted"], + }); + assertEquals(context.skillSourcePaths, undefined); + }); +}); diff --git a/src/agent/hosted/project-steering-adapter.ts b/src/agent/hosted/project-steering-adapter.ts index e9e7056fce..14b3622984 100644 --- a/src/agent/hosted/project-steering-adapter.ts +++ b/src/agent/hosted/project-steering-adapter.ts @@ -37,7 +37,12 @@ import type { RuntimeSkillDefinition, RuntimeSkillMetadataLogger, } from "../runtime/skill-metadata.ts"; -import { isRuntimeSkillVisibleTo } from "../runtime/skill-metadata.ts"; +import { resolveRuntimeSkillSelectorSnapshotForAgent } from "../runtime/skill-metadata.ts"; +import { + assertResolvedSkillSelector, + createNoneSkillSelectorSnapshot, + type ResolvedSkillSelectorPolicy, +} from "#veryfront/skill/selector.ts"; /** Public API contract for hosted project steering logger. */ export type HostedProjectSteeringLogger = @@ -67,6 +72,7 @@ export type HostedProjectSkillIdsContext = MutableAgentProjectContext & { * visibility beyond the caller's scope. */ agentId?: string; + skillSelectorPolicy?: ResolvedSkillSelectorPolicy; }; /** Public API contract for hosted project steering adapter. */ @@ -134,6 +140,31 @@ function createDefaultBuiltinStore(): RuntimeLoadSkillBuiltinStore { }; } +function resolveRefreshedSkillSnapshot(input: { + skills: readonly RuntimeSkillDefinition[]; + context: HostedProjectSkillIdsContext; +}) { + const policy = input.context.skillSelectorPolicy; + + if (!policy || policy.kind === "all-visible") { + return resolveRuntimeSkillSelectorSnapshotForAgent({ + skills: input.skills, + agentId: input.context.agentId ?? "", + selector: policy?.source === "true" ? true : undefined, + }); + } + + if (policy.kind === "none") { + return createNoneSkillSelectorSnapshot(policy); + } + + return resolveRuntimeSkillSelectorSnapshotForAgent({ + skills: input.skills, + agentId: input.context.agentId ?? "", + selector: policy.entries, + }); +} + /** Create hosted project steering adapter. */ export function createHostedProjectSteeringAdapter( options: HostedProjectSteeringAdapterOptions, @@ -191,20 +222,12 @@ export function createHostedProjectSteeringAdapter( branchId: context.branchId, }); - // Owner-aware: the refreshed per-run skill set keeps the caller's - // scope — never another agent's owned skills — and the source-path - // map stays in sync so colocated skills do not go stale. - const visibleSkills = skills.filter((skill) => - isRuntimeSkillVisibleTo(skill, { agentId: context.agentId }) - ); - context.availableSkillIds = visibleSkills.map((skill) => skill.id); - const skillSourcePaths = Object.fromEntries( - visibleSkills - .filter((skill) => skill.sourcePath) - .map((skill) => [skill.id, skill.sourcePath as string]), - ); - context.skillSourcePaths = Object.keys(skillSourcePaths).length > 0 - ? skillSourcePaths + const snapshot = resolveRefreshedSkillSnapshot({ skills, context }); + assertResolvedSkillSelector(snapshot); + context.availableSkillIds = snapshot.allowedSkillIds; + context.skillSelectorPolicy = snapshot.policy; + context.skillSourcePaths = Object.keys(snapshot.skillSourcePaths).length > 0 + ? snapshot.skillSourcePaths : undefined; }, }; diff --git a/src/agent/hosted/runtime-essential-tools.test.ts b/src/agent/hosted/runtime-essential-tools.test.ts index 6e66d6ca29..b37aa8869f 100644 --- a/src/agent/hosted/runtime-essential-tools.test.ts +++ b/src/agent/hosted/runtime-essential-tools.test.ts @@ -63,6 +63,28 @@ describe("resolveHostedRuntimeAllowedToolNames", () => { assertEquals(result, null); }); + it("removes skill infrastructure from allow-all tools when the known skill manifest is empty", () => { + const result = resolveHostedRuntimeAllowedToolNames({ + allowedToolNames: null, + localToolNames: [ + "search_tools", + "load_tools", + "sleep", + "load_skill", + "load_skill_reference", + "execute_skill_script", + ], + availableSkillIds: [], + }); + + assertEquals(result?.has("search_tools"), true); + assertEquals(result?.has("load_tools"), true); + assertEquals(result?.has("sleep"), true); + assertEquals(result?.has("load_skill"), false); + assertEquals(result?.has("load_skill_reference"), false); + assertEquals(result?.has("execute_skill_script"), false); + }); + it("returns empty set unchanged when allowedToolNames is empty", () => { const result = resolveHostedRuntimeAllowedToolNames({ allowedToolNames: new Set(), @@ -72,7 +94,7 @@ describe("resolveHostedRuntimeAllowedToolNames", () => { assertEquals(result?.size, 0); }); - it("keeps skill loading but not discovery tools for a config-derived empty selector", () => { + it("removes skill infrastructure for a config-derived empty selector with a known empty skill manifest", () => { // A sandboxed agent with tools: [] must not gain load_tools/search_tools // even when the config-derived essential-tools flag is set. Activating tools // is a broader capability than running pre-configured skills (load_skill), so @@ -86,12 +108,48 @@ describe("resolveHostedRuntimeAllowedToolNames", () => { "load_skill_reference", ], includeRuntimeEssentialToolsWhenEmpty: true, + availableSkillIds: [], }); assertEquals(result?.has("search_tools"), false); assertEquals(result?.has("load_tools"), false); + assertEquals(result?.has("load_skill"), false); + assertEquals(result?.has("load_skill_reference"), false); + }); + + it("keeps skill loading for legacy unscoped config-derived empty selectors", () => { + const result = resolveHostedRuntimeAllowedToolNames({ + allowedToolNames: new Set(), + localToolNames: ["load_skill", "load_skill_reference"], + includeRuntimeEssentialToolsWhenEmpty: true, + }); + assertEquals(result?.has("load_skill"), true); assertEquals(result?.has("load_skill_reference"), true); }); + + it("removes only skill-loading and script infrastructure for known empty skill manifests", () => { + const result = resolveHostedRuntimeAllowedToolNames({ + allowedToolNames: new Set(["execute_skill_script", "sleep"]), + localToolNames: ["execute_skill_script", "load_skill", "invoke_agent", "sleep"], + availableSkillIds: [], + }); + + assertEquals(result?.has("execute_skill_script"), false); + assertEquals(result?.has("load_skill"), false); + assertEquals(result?.has("invoke_agent"), false); + assertEquals(result?.has("sleep"), true); + }); + + it("preserves explicitly allowed invoke_agent for known empty skill manifests", () => { + const result = resolveHostedRuntimeAllowedToolNames({ + allowedToolNames: new Set(["invoke_agent", "load_skill"]), + localToolNames: ["invoke_agent", "load_skill"], + availableSkillIds: [], + }); + + assertEquals(result?.has("invoke_agent"), true); + assertEquals(result?.has("load_skill"), false); + }); }); }); diff --git a/src/agent/hosted/runtime-essential-tools.ts b/src/agent/hosted/runtime-essential-tools.ts index 4763d97c52..ff5f833fdd 100644 --- a/src/agent/hosted/runtime-essential-tools.ts +++ b/src/agent/hosted/runtime-essential-tools.ts @@ -15,6 +15,11 @@ export type ResolveHostedRuntimeAllowedToolNamesInput = { // project-provided script remains a direct execution capability. const SKILL_RUNTIME_TOOL_NAMES = ["load_skill", "load_skill_reference"] as const; const SKILL_DELEGATION_TOOL_NAMES = ["invoke_agent"] as const; +const SKILL_SCRIPT_TOOL_NAMES = ["execute_skill_script"] as const; +const EMPTY_SKILL_MANIFEST_TOOL_NAMES = [ + ...SKILL_RUNTIME_TOOL_NAMES, + ...SKILL_SCRIPT_TOOL_NAMES, +] as const; /** * Tool discovery tools are unconditionally essential: they must never be @@ -38,16 +43,34 @@ export function resolveHostedRuntimeAllowedToolNames( input: ResolveHostedRuntimeAllowedToolNamesInput, ): ReadonlySet | null { const allowedToolNames = normalizeHostedRuntimeAllowedToolNames(input.allowedToolNames); - if ( - !allowedToolNames || - (allowedToolNames.size === 0 && !input.includeRuntimeEssentialToolsWhenEmpty) - ) { + const localToolNames = new Set(input.localToolNames); + const hasKnownSkillManifest = input.availableSkillIds !== undefined; + const hasAuthorizedSkills = (input.availableSkillIds?.length ?? 0) > 0; + + if (!allowedToolNames) { + if (!hasKnownSkillManifest || hasAuthorizedSkills) { + return null; + } + + const resolvedToolNames = new Set(localToolNames); + for (const toolName of EMPTY_SKILL_MANIFEST_TOOL_NAMES) { + resolvedToolNames.delete(toolName); + } + return resolvedToolNames; + } + + if (allowedToolNames.size === 0 && !input.includeRuntimeEssentialToolsWhenEmpty) { return allowedToolNames; } - const localToolNames = new Set(input.localToolNames); const resolvedToolNames = new Set(allowedToolNames); + if (hasKnownSkillManifest && !hasAuthorizedSkills) { + for (const toolName of EMPTY_SKILL_MANIFEST_TOOL_NAMES) { + resolvedToolNames.delete(toolName); + } + } + // Tool discovery is essential only when the agent already has at least one // tool in its resolved set. Under deny-all (empty allowedToolNames), discovery // tools are intentionally excluded: activating new tools is a broader @@ -65,7 +88,10 @@ export function resolveHostedRuntimeAllowedToolNames( // Hosted cloud supplies load_skill; other adapters may also supply the // reference tool. Explicit request-level empty allowlists return above and // remain deny-all. - if (resolvedToolNames.size > 0 || input.includeRuntimeEssentialToolsWhenEmpty) { + if ( + (resolvedToolNames.size > 0 || input.includeRuntimeEssentialToolsWhenEmpty) && + (!hasKnownSkillManifest || hasAuthorizedSkills) + ) { for (const toolName of SKILL_RUNTIME_TOOL_NAMES) { if (localToolNames.has(toolName)) { resolvedToolNames.add(toolName); @@ -73,7 +99,7 @@ export function resolveHostedRuntimeAllowedToolNames( } } - if (!input.availableSkillIds?.length) { + if (!hasAuthorizedSkills) { return resolvedToolNames; } diff --git a/src/agent/hosted/veryfront-cloud-agent-service.test.ts b/src/agent/hosted/veryfront-cloud-agent-service.test.ts index 35a45f9a50..dce8043daa 100644 --- a/src/agent/hosted/veryfront-cloud-agent-service.test.ts +++ b/src/agent/hosted/veryfront-cloud-agent-service.test.ts @@ -264,8 +264,62 @@ Deno.test("hosted child project agents request only materialized skill and deleg ], providerTools: ["web_search"], delegates: ["validation-agent"], - }), - ["get_file", "load_skill", "web_search", "agent_validation-agent"], + })?.toSorted(), + ["agent_validation-agent", "get_file", "load_skill", "web_search"], + ); +}); + +Deno.test("hosted child project agents omit skill tools for an empty skill selector snapshot", () => { + assertEquals( + veryfrontCloudAgentServiceInternals.resolveHostedChildToolNames({ + id: "extraction-agent", + name: "Extraction agent", + description: "Extract an application", + instructions: "Extract the application.", + skills: [], + tools: [ + "get_file", + "execute_skill_script", + "load_skill", + "load_skill_reference", + ], + }, { allowedSkillIds: [] }), + ["get_file"], + ); +}); + +Deno.test("hosted child project agents keep delegation tools for an empty skill selector snapshot", () => { + assertEquals( + veryfrontCloudAgentServiceInternals.resolveHostedChildToolNames({ + id: "extraction-agent", + name: "Extraction agent", + description: "Extract an application", + instructions: "Extract the application.", + skills: [], + tools: [ + "get_file", + "execute_skill_script", + "load_skill", + "load_skill_reference", + ], + providerTools: ["web_search"], + delegates: ["validation-agent"], + }, { allowedSkillIds: [] })?.toSorted(), + ["agent_validation-agent", "get_file", "web_search"], + ); +}); + +Deno.test("hosted child project agents keep load_skill for a non-empty exact skill allowlist", () => { + assertEquals( + veryfrontCloudAgentServiceInternals.resolveHostedChildToolNames({ + id: "extraction-agent", + name: "Extraction agent", + description: "Extract an application", + instructions: "Extract the application.", + skills: ["extract"], + tools: ["get_file"], + }, { allowedSkillIds: ["extraction-agent--extract"] }), + ["get_file", "load_skill"], ); }); @@ -635,6 +689,67 @@ Deno.test("hosted nested delegates inherit child scope and durable lineage", () assertEquals(context.parentMessageId, "child-message"); }); +Deno.test("hosted nested delegates clear inherited skill catalog state for empty child selectors", () => { + const context = veryfrontCloudAgentServiceInternals.buildHostedChildToolContext( + { + authToken: "token-1", + projectId: "project-1", + branchId: "branch-1", + agentId: "orchestrator", + availableSkillIds: ["root-skill"], + skillSelectorPolicy: { kind: "allowlist", entries: ["root-skill"] }, + skillSourcePaths: { + "root-skill": "skills/root/SKILL.md", + }, + conversationId: "root-conversation", + parentRunId: "root-run", + parentMessageId: "root-message", + }, + "extraction-agent", + { + system: "Extract applications.", + toolNames: ["get_file", "agent_validation-agent"], + availableSkillIds: [], + skillSelectorPolicy: { kind: "none" }, + delegateIds: ["validation-agent"], + mcpServers: [], + }, + ); + + assertEquals(context.agentId, "extraction-agent"); + assertEquals(context.availableSkillIds, []); + assertEquals(context.skillSelectorPolicy, { kind: "none" }); + assertEquals(context.skillSourcePaths, undefined); +}); + +Deno.test("hosted generic delegates preserve inherited skill catalog state", () => { + const context = veryfrontCloudAgentServiceInternals.buildHostedChildToolContext( + { + authToken: "token-1", + projectId: "project-1", + branchId: "branch-1", + agentId: "orchestrator", + availableSkillIds: ["root-skill"], + skillSelectorPolicy: { kind: "allowlist", entries: ["root-skill"] }, + skillSourcePaths: { + "root-skill": "skills/root/SKILL.md", + }, + conversationId: "root-conversation", + parentRunId: "root-run", + parentMessageId: "root-message", + }, + "generic-agent", + undefined, + ); + + assertEquals(context.agentId, "generic-agent"); + assertEquals(context.availableSkillIds, ["root-skill"]); + assertEquals(context.skillSelectorPolicy, { kind: "allowlist", entries: ["root-skill"] }); + assertEquals(context.skillSourcePaths, { + "root-skill": "skills/root/SKILL.md", + }); +}); + Deno.test("hosted nested delegates preserve trusted root invocation context", () => { const context = veryfrontCloudAgentServiceInternals.buildHostedChildToolContext( { @@ -1252,6 +1367,202 @@ Deno.test("hosted child execution config resolves steering against the target pr ]); }); +Deno.test("hosted child execution config hides skill infrastructure for skills empty and false", async () => { + for (const skills of [[], false] as const) { + try { + toolRegistry.registerShared( + "get_file", + tool({ + id: "get_file", + description: "Get file", + inputSchema: defineSchema((v) => v.object({}))(), + execute: () => ({ ok: true }), + }), + ); + toolRegistry.registerShared("load_skill_reference", createLoadSkillReferenceTool()); + toolRegistry.registerShared("execute_skill_script", createExecuteSkillScriptTool()); + + const childAgent = { + id: "extraction-agent", + name: "Extraction agent", + description: "Extract job applications", + instructions: "Extract the application.", + skills, + tools: [ + "get_file", + "load_skill", + "load_skill_reference", + "execute_skill_script", + ], + }; + const context = { + options: { mcpServers: [] }, + discoveryResult: { agents: new Map([["extraction-agent", null]]) }, + agentConfigs: new Map([["extraction-agent", childAgent]]), + projectSteeringByAgentId: new Map([["extraction-agent", { + getProjectInstructions: () => Promise.resolve("Use extraction policy."), + getSkillsConfig: () => + Promise.resolve([{ + id: "global-skill", + name: "Global skill", + description: "Global skill", + instructions: "Use global skill.", + allowedTools: [], + }]), + createLoadSkillTool: () => + tool({ + id: "load_skill", + description: "Load skill", + inputSchema: defineSchema((v) => v.object({}))(), + execute: () => ({ ok: true }), + }), + }]]), + trace: (_name: string, operation: () => unknown) => operation(), + } as never; + const config = await veryfrontCloudAgentServiceInternals + .resolveHostedChildAgentExecutionConfig( + context, + { + authToken: "token-1", + projectId: "project-1", + branchId: "branch-1", + agentId: "orchestrator", + }, + "extraction-agent", + "project-1", + ); + + assertEquals(config?.availableSkillIds, []); + assertEquals(config?.toolNames, ["get_file"]); + assertEquals(config?.system.includes("global-skill"), false); + assertEquals(config?.system.includes("load_skill"), false); + assertEquals(config?.system.includes("load_skill_reference"), false); + assertEquals(config?.system.includes("execute_skill_script"), false); + const childToolContext = veryfrontCloudAgentServiceInternals.buildHostedChildToolContext( + { + authToken: "token-1", + projectId: "project-1", + branchId: "branch-1", + agentId: "orchestrator", + }, + "extraction-agent", + config, + ); + const hostTools = veryfrontCloudAgentServiceInternals.buildHostedChildGlobalTools( + context, + { + childAgentId: "extraction-agent", + childConfig: config, + childToolContext, + }, + ); + + assertEquals("get_file" in hostTools, true); + assertEquals("load_skill" in hostTools, false); + assertEquals("load_skill_reference" in hostTools, false); + assertEquals("execute_skill_script" in hostTools, false); + } finally { + toolRegistry.clearAll(); + } + } +}); + +Deno.test("hosted child execution config keeps exact non-empty skill authorization", async () => { + try { + toolRegistry.registerShared( + "get_file", + tool({ + id: "get_file", + description: "Get file", + inputSchema: defineSchema((v) => v.object({}))(), + execute: () => ({ ok: true }), + }), + ); + const childAgent = { + id: "extraction-agent", + name: "Extraction agent", + description: "Extract job applications", + instructions: "Extract the application.", + skills: ["extract"], + tools: ["get_file"], + }; + const context = { + options: { mcpServers: [] }, + discoveryResult: { agents: new Map([["extraction-agent", null]]) }, + agentConfigs: new Map([["extraction-agent", childAgent]]), + projectSteeringByAgentId: new Map([["extraction-agent", { + getProjectInstructions: () => Promise.resolve("Use extraction policy."), + getSkillsConfig: () => + Promise.resolve([{ + id: "extraction-agent--extract", + name: "Extract", + description: "Extract skill", + instructions: "Extract with skill.", + allowedTools: [], + ownerAgentId: "extraction-agent", + shortName: "extract", + sourcePath: "agents/extraction-agent/skills/extract/SKILL.md", + }, { + id: "global-skill", + name: "Global skill", + description: "Global skill", + instructions: "Use global skill.", + allowedTools: [], + }]), + createLoadSkillTool: () => + tool({ + id: "load_skill", + description: "Load skill", + inputSchema: defineSchema((v) => v.object({}))(), + execute: () => ({ ok: true }), + }), + }]]), + trace: (_name: string, operation: () => unknown) => operation(), + } as never; + const config = await veryfrontCloudAgentServiceInternals + .resolveHostedChildAgentExecutionConfig( + context, + { + authToken: "token-1", + projectId: "project-1", + branchId: "branch-1", + agentId: "orchestrator", + }, + "extraction-agent", + "project-1", + ); + + assertEquals(config?.availableSkillIds, ["extraction-agent--extract"]); + assertEquals(config?.toolNames, ["get_file", "load_skill"]); + assert(config?.system.includes("extraction-agent--extract")); + assertEquals(config?.system.includes("global-skill"), false); + + const childToolContext = veryfrontCloudAgentServiceInternals.buildHostedChildToolContext( + { + authToken: "token-1", + projectId: "project-1", + branchId: "branch-1", + agentId: "orchestrator", + }, + "extraction-agent", + config, + ); + const hostTools = veryfrontCloudAgentServiceInternals.buildHostedChildGlobalTools( + context, + { + childAgentId: "extraction-agent", + childConfig: config, + childToolContext, + }, + ); + + assertEquals("get_file" in hostTools, true); + assertEquals("load_skill" in hostTools, true); + } finally { + toolRegistry.clearAll(); + } +}); + Deno.test({ name: "createNodeVeryfrontCloudAgentServiceRuntime uses veryfront.config.ts discovery paths", // Code primitive discovery invokes the esbuild-backed transpiler, which starts diff --git a/src/agent/hosted/veryfront-cloud-agent-service.ts b/src/agent/hosted/veryfront-cloud-agent-service.ts index 3c159b608e..13220fa408 100644 --- a/src/agent/hosted/veryfront-cloud-agent-service.ts +++ b/src/agent/hosted/veryfront-cloud-agent-service.ts @@ -29,6 +29,7 @@ import { initializeNodeVeryfrontCloudAgentServiceContext, } from "./cloud-agent-config.ts"; import { + buildHostedChildGlobalTools, buildHostedChildToolContext, getDiscoveredHostTools, resolveHostedChildAgentExecutionConfig, @@ -138,6 +139,7 @@ export { getDiscoveredHostTools }; /** Internal test seams for hosted project-agent materialization. */ export const veryfrontCloudAgentServiceInternals = { + buildHostedChildGlobalTools, buildHostedChildToolContext, resolveHostedDelegationBinding, resolveHostedChildAgentExecutionConfig, diff --git a/src/agent/project/context.test.ts b/src/agent/project/context.test.ts index f1c2fca962..724a81ad02 100644 --- a/src/agent/project/context.test.ts +++ b/src/agent/project/context.test.ts @@ -11,6 +11,7 @@ Deno.test("applyAgentProjectContextChange updates project and resets branch and projectId: "project-1", branchId: "branch-1", availableSkillIds: ["skill-a"], + skillSelectorPolicy: { kind: "allowlist", entries: ["skill-a"] }, skillSourcePaths: { "skill-a": "skills/skill-a/SKILL.md" }, steeringRevision: 3, }; @@ -24,6 +25,7 @@ Deno.test("applyAgentProjectContextChange updates project and resets branch and runtimeTargetKind: "main_branch", runtimeTargetEnvironmentId: null, availableSkillIds: undefined, + skillSelectorPolicy: { kind: "allowlist", entries: ["skill-a"] }, skillSourcePaths: undefined, steeringRevision: 3, }); diff --git a/src/agent/project/context.ts b/src/agent/project/context.ts index c39daadf16..a023129872 100644 --- a/src/agent/project/context.ts +++ b/src/agent/project/context.ts @@ -5,6 +5,7 @@ export interface MutableAgentProjectContext { runtimeTargetKind?: "main_branch" | "environment" | "preview_branch" | null; runtimeTargetEnvironmentId?: string | null; availableSkillIds?: string[]; + skillSelectorPolicy?: import("#veryfront/skill/selector.ts").ResolvedSkillSelectorPolicy; /** Per-run skill id -> discovered SKILL.md source path (owner-aware catalog). */ skillSourcePaths?: Readonly>; } diff --git a/src/agent/runtime/agent-markdown-adapter.test.ts b/src/agent/runtime/agent-markdown-adapter.test.ts index 72c0d81d1d..df6052d594 100644 --- a/src/agent/runtime/agent-markdown-adapter.test.ts +++ b/src/agent/runtime/agent-markdown-adapter.test.ts @@ -70,7 +70,7 @@ Deno.test("createRuntimeAgentFromMarkdownDefinition preserves delegates and MCP }]); }); -Deno.test("createRuntimeAgentFromMarkdownDefinition preserves an empty catalog and binds skill tools", async () => { +Deno.test("createRuntimeAgentFromMarkdownDefinition preserves explicit empty skills and hides skill tools", async () => { skillRegistry.clearAll(); registerSkill("global-howto", { id: "global-howto", @@ -86,11 +86,7 @@ Deno.test("createRuntimeAgentFromMarkdownDefinition preserves an empty catalog a skills: [], }); - assertEquals(runtimeAgent.config.tools, { - load_skill: true, - load_skill_reference: true, - execute_skill_script: true, - }); + assertEquals(runtimeAgent.config.tools, undefined); const system = getEffectiveAgentSystem(runtimeAgent); const prompt = typeof system === "function" ? await system() : system; assertEquals(prompt, "Work alone."); diff --git a/src/agent/runtime/agent-runtime-step.test.ts b/src/agent/runtime/agent-runtime-step.test.ts index 0141784404..7ef32072da 100644 --- a/src/agent/runtime/agent-runtime-step.test.ts +++ b/src/agent/runtime/agent-runtime-step.test.ts @@ -51,6 +51,36 @@ describe("agent/runtime-step", () => { assertStrictEquals(prepared.toolContext.abortSignal, trustedAbort.signal); }); + it("does not let runtime context shadow trusted allowed skill ids", async () => { + const prepared = await prepareAgentRuntimeStep({ + agentId: "agent_1", + activeSkillPolicy: undefined, + activeSkillToolAvailability: undefined, + allowedRemoteToolNames: undefined, + config: { model: "auto", system: "Base", tools: true } as AgentConfig, + forwardedRemoteToolDefinitions: undefined, + getAvailableTools: async (_toolsConfig, options) => { + assertEquals(options?.remoteToolContext?.allowedSkillIds, ["selected"]); + return []; + }, + isLocalModel: false, + messages: [], + mode: "generate", + remoteToolSources: undefined, + resolveRuntimeState: async () => ({ + systemPrompt: "Base", + context: { allowedSkillIds: ["excluded"], keep: true }, + }), + runtimeContext: { allowedSkillIds: ["selected"], keep: true }, + step: 0, + systemPrompt: "Base", + toolContextBase: undefined, + }); + + assertEquals(prepared.toolContext.allowedSkillIds, ["selected"]); + assertEquals(prepared.runtimeContext, { allowedSkillIds: ["selected"], keep: true }); + }); + it("resolves runtime state, merges tool context, and applies active skill policy", async () => { const messages: Message[] = [{ id: "msg_1", @@ -158,6 +188,32 @@ describe("agent/runtime-step", () => { }); }); + it("does not include skill tools for the explicit none selector", async () => { + const prepared = await prepareAgentRuntimeStep({ + agentId: "agent_1", + activeSkillPolicy: undefined, + activeSkillToolAvailability: undefined, + allowedRemoteToolNames: undefined, + config: { model: "auto", system: "Base", tools: true, skills: [] } as AgentConfig, + forwardedRemoteToolDefinitions: undefined, + getAvailableTools: async (_toolsConfig, options) => { + assertEquals(options?.includeSkillTools, false); + return [toolDefinition("ordinary_tool")]; + }, + isLocalModel: false, + messages: [], + mode: "stream", + remoteToolSources: [], + runtimeContext: undefined, + step: 0, + systemPrompt: "Base", + toolContextBase: undefined, + resolveRuntimeState: async () => ({ systemPrompt: "Base", context: undefined }), + }); + + assertEquals(prepared.tools.map((tool) => tool.name), ["ordinary_tool"]); + }); + it("stamps the validated source policy into child-visible tool context", async () => { const sourceIntegrationPolicy = { schemaVersion: 1 as const, diff --git a/src/agent/runtime/agent-runtime-step.ts b/src/agent/runtime/agent-runtime-step.ts index 9d24704259..f91deb2c5a 100644 --- a/src/agent/runtime/agent-runtime-step.ts +++ b/src/agent/runtime/agent-runtime-step.ts @@ -65,10 +65,24 @@ export interface PreparedAgentRuntimeStep { tools: ToolDefinition[]; } +function shouldIncludeSkillTools(config: AgentConfig): boolean { + return config.skills !== false && (!Array.isArray(config.skills) || config.skills.length > 0); +} + +function getTrustedAllowedSkillIds( + input: PrepareAgentRuntimeStepInput, +): readonly string[] | undefined { + const value = input.toolContextBase?.allowedSkillIds ?? input.runtimeContext?.allowedSkillIds; + return Array.isArray(value) && value.every((entry): entry is string => typeof entry === "string") + ? value + : undefined; +} + /** Resolve per-step runtime state and the tools visible for that step. */ export async function prepareAgentRuntimeStep( input: PrepareAgentRuntimeStepInput, ): Promise { + const trustedAllowedSkillIds = getTrustedAllowedSkillIds(input); const runtimeState = await input.resolveRuntimeState( input.messages, input.runtimeContext, @@ -80,6 +94,9 @@ export async function prepareAgentRuntimeStep( if (input.toolContextBase?.abortSignal !== undefined) { toolContext.abortSignal = input.toolContextBase.abortSignal; } + if (trustedAllowedSkillIds !== undefined) { + toolContext.allowedSkillIds = [...trustedAllowedSkillIds]; + } delete toolContext[SOURCE_INTEGRATION_POLICY_CONTEXT_KEY]; if (input.sourceIntegrationPolicy !== undefined) { toolContext[SOURCE_INTEGRATION_POLICY_CONTEXT_KEY] = input.sourceIntegrationPolicy; @@ -93,7 +110,7 @@ export async function prepareAgentRuntimeStep( let tools = input.isLocalModel ? [] : await input.getAvailableTools(input.config.tools, { callerAgentId: input.agentId, - includeSkillTools: true, + includeSkillTools: shouldIncludeSkillTools(input.config), allowedRemoteToolNames: input.allowedRemoteToolNames, forwardedRemoteToolDefinitions: input.forwardedRemoteToolDefinitions, remoteToolSources: input.remoteToolSources, @@ -112,7 +129,9 @@ export async function prepareAgentRuntimeStep( tools = filterToolsAfterSubmittedFormInput(tools, input.messages, runtimeState.context); return { - runtimeContext: runtimeState.context, + runtimeContext: trustedAllowedSkillIds === undefined + ? runtimeState.context + : { ...runtimeState.context, allowedSkillIds: [...trustedAllowedSkillIds] }, systemPrompt: runtimeState.systemPrompt, toolContext, tools, diff --git a/src/agent/runtime/load-skill-tool.test.ts b/src/agent/runtime/load-skill-tool.test.ts index 694a1909dd..35598672d6 100644 --- a/src/agent/runtime/load-skill-tool.test.ts +++ b/src/agent/runtime/load-skill-tool.test.ts @@ -1067,6 +1067,53 @@ Deno.test("createRuntimeLoadSkillTool rejects invented skill IDs before tool exe ); }); +Deno.test("createRuntimeLoadSkillTool treats an empty availableSkillIds manifest as deny-all before storage reads", async () => { + let projectReads = 0; + let builtinReads = 0; + const tool = createRuntimeLoadSkillTool({ + context: createProjectContext({ + availableSkillIds: [], + }), + skillsDir: "/skills", + projectSkillLoader: { + listProjectSkillReferences: () => Promise.resolve([]), + loadProjectSkill: () => { + projectReads++; + return Promise.resolve({ instructions: "# Project plan", references: [] }); + }, + loadProjectSkillReference: () => Promise.resolve(null), + }, + builtinSkillIds: ["plan"], + builtinStore: { + readSkill: () => { + builtinReads++; + return "# Builtin plan"; + }, + readReferenceFile: () => { + builtinReads++; + return "Guide"; + }, + listReferences: () => { + builtinReads++; + return ["references/guide.md"]; + }, + }, + }); + + await assertRejects( + () => tool.execute({ skillId: "plan" }), + Error, + "input validation failed", + ); + await assertRejects( + () => tool.execute({ skillId: "plan", file: "references/guide.md" }), + Error, + "input validation failed", + ); + assertEquals(projectReads, 0); + assertEquals(builtinReads, 0); +}); + Deno.test("createRuntimeLoadSkillTool allows host copy overrides", async () => { const tool = createRuntimeLoadSkillTool({ context: createProjectContext(), diff --git a/src/agent/runtime/load-skill-tool.ts b/src/agent/runtime/load-skill-tool.ts index 537e1110c6..5f948fe8e1 100644 --- a/src/agent/runtime/load-skill-tool.ts +++ b/src/agent/runtime/load-skill-tool.ts @@ -27,6 +27,7 @@ import { type RuntimeLoadedSkillResponseMessages, type RuntimeSkillMetadataLogger, } from "./skill-metadata.ts"; +import type { ResolvedSkillSelectorPolicy } from "#veryfront/skill/selector.ts"; import { narrowPolicyAfterSubmittedForm } from "./skill-policy-enforcement.ts"; /** Legacy continuation-note fallback used when runtime tool inventory is unavailable. */ @@ -109,6 +110,7 @@ export type RuntimeLoadSkillToolContext = RuntimeProjectSkillContext & { /** Agent identity used to enforce owner-scoped skill visibility. */ agentId?: string; availableSkillIds?: readonly string[]; + skillSelectorPolicy?: ResolvedSkillSelectorPolicy; availableToolNames?: readonly string[]; loadedSkillResponses?: Record; loadedSkillReferenceResponses?: Record; @@ -254,10 +256,7 @@ function buildMissingSkillError( options: RuntimeLoadSkillToolOptions, skillId: string, ): RuntimeLoadSkillErrorOutput { - const knownIds = new Set([ - ...(options.context.availableSkillIds ?? []), - ...(options.builtinSkillIds ?? []), - ]); + const knownIds = new Set(getKnownRuntimeSkillIds(options) ?? []); const available = [...knownIds].sort().join(", "); return { error: `Skill not found: ${skillId}. Available skills: ${available}`, @@ -305,21 +304,22 @@ function buildRuntimeLoadSkillDescription(options: RuntimeLoadSkillToolOptions): return options.description; } - if (!options.context.availableSkillIds && !options.builtinSkillIds) { + if (options.context.availableSkillIds === undefined && !options.builtinSkillIds) { return RUNTIME_LOAD_SKILL_DESCRIPTION; } - const knownIds = new Set([ - ...(options.context.availableSkillIds ?? []), - ...(options.builtinSkillIds ?? []), - ]); + const knownIds = new Set(getKnownRuntimeSkillIds(options) ?? []); const available = [...knownIds].sort().join(", ") || "none"; return `${RUNTIME_LOAD_SKILL_DESCRIPTION} Available skill IDs: ${available}. Do not invent skill IDs. Only call load_skill with one of these IDs.`; } function getKnownRuntimeSkillIds(options: RuntimeLoadSkillToolOptions): string[] | null { - if (!options.context.availableSkillIds && !options.builtinSkillIds) { + if (options.context.availableSkillIds !== undefined) { + return [...new Set(options.context.availableSkillIds)].sort(); + } + + if (!options.builtinSkillIds) { return null; } @@ -385,10 +385,22 @@ function normalizeRuntimeLoadSkillInputSkillId( function buildRuntimeLoadSkillInputSchema(options: RuntimeLoadSkillToolOptions) { const knownIds = getKnownRuntimeSkillIds(options); - if (!knownIds || knownIds.length === 0) { + if (!knownIds) { return runtimeLoadSkillToolInputSchema; } + if (knownIds.length === 0) { + return defineSchema((v) => + v.object({ + skillId: v.string().refine( + () => false, + "No skills are available in this run.", + ).describe("No skills are available in this run."), + file: v.string().optional(), + }).strict() + )(); + } + const knownIdSet = new Set(knownIds); const loadedIds = getLoadedRuntimeSkillIds(options).filter((skillId) => knownIdSet.has(skillId)); const loadedIdSet = new Set(loadedIds); diff --git a/src/agent/runtime/skill-metadata.test.ts b/src/agent/runtime/skill-metadata.test.ts index 8642181571..fc4ba04623 100644 --- a/src/agent/runtime/skill-metadata.test.ts +++ b/src/agent/runtime/skill-metadata.test.ts @@ -5,6 +5,7 @@ import { buildRuntimeSkillDefinition, normalizeRuntimeSkillReferencePath, parseRuntimeSkillMetadata, + resolveRuntimeSkillSelectorForAgent, resolveRuntimeSkillsForAgent, } from "./skill-metadata.ts"; @@ -111,6 +112,167 @@ Deno.test("resolveRuntimeSkillsForAgent applies owner visibility and short-name ); }); +Deno.test("resolveRuntimeSkillSelectorForAgent returns a deterministic strict snapshot", () => { + const globalCite = buildRuntimeSkillDefinition({ + id: "cite", + content: "---\ndescription: Global citations\n---\nUse global citations.", + sourcePath: "skills/cite/SKILL.md", + })!; + const ownedCite = buildRuntimeSkillDefinition({ + id: "researcher--helper", + content: "---\ndescription: Research citations\n---\nUse research citations.", + ownerAgentId: "researcher", + shortName: "cite", + sourcePath: "agents/researcher/skills/cite/SKILL.md", + })!; + const otherOwned = buildRuntimeSkillDefinition({ + id: "writer--style", + content: "---\ndescription: Writer style\n---\nUse writer style.", + ownerAgentId: "writer", + shortName: "style", + sourcePath: "agents/writer/skills/style/SKILL.md", + })!; + + const selected = resolveRuntimeSkillSelectorForAgent({ + skills: [globalCite, ownedCite, otherOwned], + agentId: "researcher", + selector: ["cite", "cite"], + }); + + assertEquals(selected.policy, { kind: "allowlist", entries: ["cite", "cite"] }); + assertEquals(selected.allowedSkillIds, ["researcher--helper"]); + assertEquals(selected.skillSourcePaths, { + "researcher--helper": "agents/researcher/skills/cite/SKILL.md", + }); + assertEquals(selected.definitions.map((skill) => skill.id), ["researcher--helper"]); + + const none = resolveRuntimeSkillSelectorForAgent({ + skills: [globalCite, ownedCite, otherOwned], + agentId: "researcher", + selector: [], + }); + assertEquals(none.policy, { kind: "none" }); + assertEquals(none.allowedSkillIds, []); +}); + +Deno.test("resolveRuntimeSkillSelectorForAgent rejects unresolved explicit entries generically", () => { + const otherOwned = buildRuntimeSkillDefinition({ + id: "writer--style", + content: "---\ndescription: Writer style\n---\nUse writer style.", + ownerAgentId: "writer", + shortName: "style", + })!; + + let rejected = false; + try { + resolveRuntimeSkillSelectorForAgent({ + skills: [otherOwned], + agentId: "researcher", + selector: ["writer--style"], + }); + } catch (error) { + rejected = true; + const message = String(error); + assertEquals(message.includes("configured skills are not available"), true); + assertEquals(message.includes("writer--style"), false); + } + assertEquals(rejected, true); +}); + +Deno.test("resolveRuntimeSkillSelectorForAgent matches the canonical selector matrix", () => { + const global = buildRuntimeSkillDefinition({ + id: "global", + content: "---\ndescription: Global\n---\nGlobal.", + sourcePath: "skills/global/SKILL.md", + })!; + const bundled = buildRuntimeSkillDefinition({ + id: "bundled", + content: "---\ndescription: Bundled\n---\nBundled.", + sourcePath: "bundled/skills/bundled/SKILL.md", + })!; + const ownCite = buildRuntimeSkillDefinition({ + id: "agent--cite", + content: "---\ndescription: Own cite\n---\nCite.", + ownerAgentId: "agent", + shortName: "cite", + sourcePath: "agents/agent/skills/cite/SKILL.md", + })!; + const otherStyle = buildRuntimeSkillDefinition({ + id: "other--style", + content: "---\ndescription: Other style\n---\nStyle.", + ownerAgentId: "other", + shortName: "style", + sourcePath: "agents/other/skills/style/SKILL.md", + })!; + const globalCite = buildRuntimeSkillDefinition({ + id: "cite", + content: "---\ndescription: Global cite\n---\nGlobal cite.", + sourcePath: "skills/cite/SKILL.md", + })!; + + const skills = [global, bundled, ownCite, otherStyle, globalCite]; + const cases: Array<{ + selector: true | string[] | undefined; + expectedPolicy: object; + expectedIds: string[]; + }> = [ + { + selector: undefined, + expectedPolicy: { kind: "all-visible", source: "omitted" }, + expectedIds: ["global", "bundled", "agent--cite", "cite"], + }, + { + selector: true, + expectedPolicy: { kind: "all-visible", source: "true" }, + expectedIds: ["global", "bundled", "agent--cite", "cite"], + }, + { + selector: [], + expectedPolicy: { kind: "none" }, + expectedIds: [], + }, + { + selector: ["bundled", "cite", "global", "bundled"], + expectedPolicy: { kind: "allowlist", entries: ["bundled", "cite", "global", "bundled"] }, + expectedIds: ["bundled", "agent--cite", "global"], + }, + ]; + + for (const testCase of cases) { + const snapshot = resolveRuntimeSkillSelectorForAgent({ + skills, + agentId: "agent", + selector: testCase.selector, + }); + assertEquals(snapshot.policy, testCase.expectedPolicy); + assertEquals(snapshot.allowedSkillIds, testCase.expectedIds); + assertEquals(snapshot.definitions.map((skill) => skill.id), testCase.expectedIds); + } +}); + +Deno.test("resolveRuntimeSkillSelectorForAgent keeps the first visible duplicate id once", () => { + const projectSkill = buildRuntimeSkillDefinition({ + id: "create", + content: "---\ndescription: Project create\n---\nProject.", + sourcePath: "skills/create/SKILL.md", + })!; + const bundledSkill = buildRuntimeSkillDefinition({ + id: "create", + content: "---\ndescription: Bundled create\n---\nBundled.", + sourcePath: "bundled/skills/create/SKILL.md", + })!; + + const snapshot = resolveRuntimeSkillSelectorForAgent({ + skills: [projectSkill, bundledSkill], + agentId: "agent", + selector: ["create", "create"], + }); + + assertEquals(snapshot.allowedSkillIds, ["create"]); + assertEquals(snapshot.definitions[0], projectSkill); + assertEquals(snapshot.skillSourcePaths, { create: "skills/create/SKILL.md" }); +}); + Deno.test("buildRuntimeSkillDefinition includes optional runtime fields", () => { const content = `--- name: Skill diff --git a/src/agent/runtime/skill-metadata.ts b/src/agent/runtime/skill-metadata.ts index 04985302e3..8d46bc12c2 100644 --- a/src/agent/runtime/skill-metadata.ts +++ b/src/agent/runtime/skill-metadata.ts @@ -1,5 +1,10 @@ import { extract } from "#std/front-matter/yaml.ts"; import { defineSchema, lazySchema } from "#veryfront/schemas/index.ts"; +import { + assertResolvedSkillSelector, + type ResolvedSkillSelectorSnapshot, + resolveSkillSelector, +} from "#veryfront/skill/selector.ts"; function normalizeAllowedTools(value: string | string[] | undefined): string[] { if (value === undefined) { @@ -136,6 +141,34 @@ export function resolveRuntimeSkillsForAgent(input: { return [...selectedSkills.values()]; } +/** Resolve a presence-aware runtime skill selector snapshot without throwing on explicit misses. */ +export function resolveRuntimeSkillSelectorSnapshotForAgent(input: { + skills: readonly RuntimeSkillDefinition[]; + agentId: string; + selector: true | string[] | undefined; +}): ResolvedSkillSelectorSnapshot { + return resolveSkillSelector({ + definitions: input.skills, + selector: input.selector, + getId: (skill) => skill.id, + isVisible: (skill) => isRuntimeSkillVisibleTo(skill, { agentId: input.agentId }), + getShortName: (skill) => skill.shortName, + isOwnShortNameCandidate: (skill) => skill.ownerAgentId === input.agentId, + getSourcePath: (skill) => skill.sourcePath, + }); +} + +/** Resolve a presence-aware runtime skill selector snapshot and reject explicit misses. */ +export function resolveRuntimeSkillSelectorForAgent(input: { + skills: readonly RuntimeSkillDefinition[]; + agentId: string; + selector: true | string[] | undefined; +}): ResolvedSkillSelectorSnapshot { + const snapshot = resolveRuntimeSkillSelectorSnapshotForAgent(input); + assertResolvedSkillSelector(snapshot); + return snapshot; +} + /** Public API contract for runtime loaded skill response messages. */ export type RuntimeLoadedSkillResponseMessages = { allowedToolsNote: string; diff --git a/src/agent/service/runtime.test.ts b/src/agent/service/runtime.test.ts index eda81f2703..a940170c33 100644 --- a/src/agent/service/runtime.test.ts +++ b/src/agent/service/runtime.test.ts @@ -1,5 +1,6 @@ -import { assertEquals, assertRejects } from "#veryfront/testing/assert.ts"; +import { assert, assertEquals, assertRejects } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; +import { registerSkill, skillRegistry } from "#veryfront/skill/registry.ts"; import { combineAgentServiceLifecycle, createAgentServiceRuntime, @@ -83,6 +84,13 @@ describe("agent/agent-service-runtime", () => { }); it("preserves configured skills and tools on the service agent", () => { + skillRegistry.clearAll(); + registerSkill("support-triage", { + id: "support-triage", + metadata: { name: "support-triage", description: "Triage support requests" }, + rootPath: "/test/skills/support-triage", + }); + const bundle = createAgentServiceRuntime({ serviceName: "test-agent-service", getConfig: () => ({ @@ -108,13 +116,13 @@ describe("agent/agent-service-runtime", () => { const serviceAgent = bundle.runtime.contract.agents.assistant; assertEquals(serviceAgent?.config.skills, ["support-triage"]); - assertEquals(serviceAgent?.config.tools, { - search_knowledge: true, - get_file: true, - load_skill: true, - load_skill_reference: true, - execute_skill_script: true, - }); + const tools = serviceAgent?.config.tools; + assert(tools && tools !== true); + assertEquals(tools?.search_knowledge, true); + assertEquals(tools?.get_file, true); + assertEquals(typeof tools?.load_skill, "object"); + assertEquals(typeof tools?.load_skill_reference, "object"); + assertEquals(typeof tools?.execute_skill_script, "object"); }); it("starts the node agent service server from the assembled runtime", async () => { diff --git a/src/agent/types.ts b/src/agent/types.ts index 609eb58e72..1c2b0a82ad 100644 --- a/src/agent/types.ts +++ b/src/agent/types.ts @@ -222,15 +222,15 @@ export interface AgentConfig { */ onToolResult?: ToolExecutionResultHandler; /** - * Select the skills advertised in this agent's system prompt. + * Select visible skill IDs or this agent's own skill short names advertised + * in this agent's system prompt and authorized for `load_skill`. * - omitted or true: include every discovered skill visible to this agent - * - string[] or false: include only the listed skill IDs; use [] or false to advertise none + * - string[]: include and authorize only listed visible skill IDs or this + * agent's own skill short names + * - [] or false: advertise no skills and do not authorize project or + * configured skills for `load_skill` * - * This selects the prompt catalog only. It does not restrict which - * owner-visible skills `load_skill` can resolve by id. - * - * Discovery happens at startup via discoverAll(). `load_skill` remains - * available to every agent regardless of this catalog selection. + * Discovery happens at startup via discoverAll(). */ skills?: true | false | string[]; /** diff --git a/src/server/handlers/request/agent-stream.handler.test.ts b/src/server/handlers/request/agent-stream.handler.test.ts index 73681cb973..acb411339c 100644 --- a/src/server/handlers/request/agent-stream.handler.test.ts +++ b/src/server/handlers/request/agent-stream.handler.test.ts @@ -731,9 +731,9 @@ describe("server/handlers/request/agent-stream.handler", () => { agentConfig: { id: "assistant-1", name: "Project Assistant", - description: "Uses project-scoped skills and tools.", + description: "Uses project-scoped tools with skills disabled.", instructions: "Use project-scoped instructions.", - skills: ["support-triage"], + skills: [], tools: ["search_knowledge", "get_file"], }, }); @@ -759,7 +759,7 @@ describe("server/handlers/request/agent-stream.handler", () => { ? await (capturedSystem as () => Promise)() : capturedSystem; assertStringIncludes(String(resolvedSystem), "Use project-scoped instructions."); - assertEquals(capturedSkills, ["support-triage"]); + assertEquals(capturedSkills, []); assertEquals((capturedTools as Record).search_knowledge, true); assertEquals((capturedTools as Record).get_file, true); assertEquals(capturedAllowedRemoteTools, ["get_file", "search_knowledge"]); diff --git a/src/server/handlers/request/api/project-discovery.test.ts b/src/server/handlers/request/api/project-discovery.test.ts index ed90f95c33..772ea252f4 100644 --- a/src/server/handlers/request/api/project-discovery.test.ts +++ b/src/server/handlers/request/api/project-discovery.test.ts @@ -44,6 +44,19 @@ function createHandlerContext( } as HandlerContext; } +function assertConfiguredSkillInfrastructure( + tools: unknown, +): asserts tools is Record { + assertExists(tools); + if (tools === true || typeof tools !== "object") { + throw new Error("Expected a concrete agent tool map"); + } + const toolMap = tools as Record; + assertEquals(typeof toolMap.load_skill, "object"); + assertEquals(typeof toolMap.load_skill_reference, "object"); + assertEquals(typeof toolMap.execute_skill_script, "object"); +} + async function writeAgentFile( ctx: HandlerContext, agentId: string, @@ -978,12 +991,8 @@ describe( assertExists(discoveredAgent); assertEquals(toolRegistry.has("write-report"), true); assertEquals(toolRegistry.has("writeReport"), false); - assertEquals(discoveredAgent.config.tools, { - "write-report": true, - load_skill: true, - load_skill_reference: true, - execute_skill_script: true, - }); + assertConfiguredSkillInfrastructure(discoveredAgent.config.tools); + assertEquals(discoveredAgent.config.tools["write-report"], true); }); it("keeps explicit generated-looking tool ids available for request-time project-agent runs", async () => { @@ -1033,12 +1042,8 @@ describe( assertExists(discoveredAgent); assertEquals(toolRegistry.has("tool_2024_01"), true); assertEquals(toolRegistry.has("writeReport"), false); - assertEquals(discoveredAgent.config.tools, { - tool_2024_01: true, - load_skill: true, - load_skill_reference: true, - execute_skill_script: true, - }); + assertConfiguredSkillInfrastructure(discoveredAgent.config.tools); + assertEquals(discoveredAgent.config.tools.tool_2024_01, true); }); it("keeps object-spread overridden tool ids available for request-time project-agent runs", async () => { @@ -1089,12 +1094,8 @@ describe( assertExists(discoveredAgent); assertEquals(toolRegistry.has("my-tool"), true); assertEquals(toolRegistry.has("writeReport"), false); - assertEquals(discoveredAgent.config.tools, { - "my-tool": true, - load_skill: true, - load_skill_reference: true, - execute_skill_script: true, - }); + assertConfiguredSkillInfrastructure(discoveredAgent.config.tools); + assertEquals(discoveredAgent.config.tools["my-tool"], true); }); }, ); diff --git a/src/skill/registry.test.ts b/src/skill/registry.test.ts index eadb9c6af4..15a6523d7c 100644 --- a/src/skill/registry.test.ts +++ b/src/skill/registry.test.ts @@ -1,5 +1,5 @@ import "#veryfront/schemas/_test-setup.ts"; -import { assertEquals } from "#veryfront/testing/assert.ts"; +import { assertEquals, assertThrows } from "#veryfront/testing/assert.ts"; import { beforeEach, describe, it } from "#veryfront/testing/bdd.ts"; import { getAllSkills, getSkill, registerSkill, skillRegistry } from "./registry.ts"; import type { Skill } from "./types.ts"; @@ -12,6 +12,18 @@ function createTestSkill(id: string): Skill { }; } +function createScopedTestSkill(input: { + id: string; + ownerAgentId?: string; + shortName?: string; +}): Skill { + return { + ...createTestSkill(input.id), + ...(input.ownerAgentId === undefined ? {} : { ownerAgentId: input.ownerAgentId }), + ...(input.shortName === undefined ? {} : { shortName: input.shortName }), + }; +} + describe("src/skill/registry", () => { beforeEach(() => { skillRegistry.clearAll(); @@ -75,4 +87,102 @@ describe("src/skill/registry", () => { assertEquals(resolved.size, 0); }); }); + + describe("resolveSelectorForAgent", () => { + it("preserves omitted, true, empty, and allowlist selector policies", () => { + registerSkill("a", createTestSkill("a")); + registerSkill("b", createTestSkill("b")); + + const omitted = skillRegistry.resolveSelectorForAgent(undefined); + assertEquals(omitted.policy, { kind: "all-visible", source: "omitted" }); + assertEquals(omitted.allowedSkillIds, ["a", "b"]); + + const all = skillRegistry.resolveSelectorForAgent(true); + assertEquals(all.policy, { kind: "all-visible", source: "true" }); + assertEquals(all.allowedSkillIds, ["a", "b"]); + + const none = skillRegistry.resolveSelectorForAgent([]); + assertEquals(none.policy, { kind: "none" }); + assertEquals(none.allowedSkillIds, []); + + const selected = skillRegistry.resolveSelectorForAgent(["b"]); + assertEquals(selected.policy, { kind: "allowlist", entries: ["b"] }); + assertEquals(selected.allowedSkillIds, ["b"]); + }); + + it("deduplicates explicit selections in request order and exposes source paths", () => { + registerSkill("a", createTestSkill("a")); + registerSkill("b", createTestSkill("b")); + + const resolved = skillRegistry.resolveSelectorForAgent(["b", "a", "b"]); + assertEquals(resolved.allowedSkillIds, ["b", "a"]); + assertEquals(resolved.skillSourcePaths, { + b: "/test/skills/b/SKILL.md", + a: "/test/skills/a/SKILL.md", + }); + assertEquals(resolved.definitions.map((skill) => skill.id), ["b", "a"]); + }); + + it("rejects unresolved explicit entries without echoing requested ids", () => { + registerSkill("a", createTestSkill("a")); + + const error = assertThrows( + () => skillRegistry.resolveSelectorForAgent(["missing-skill"]), + Error, + "configured skills are not available", + ); + + assertEquals(String(error).includes("missing-skill"), false); + }); + + it("applies the canonical selector matrix for owner-visible skills", () => { + registerSkill("global", createScopedTestSkill({ id: "global" })); + registerSkill("bundled", createScopedTestSkill({ id: "bundled" })); + registerSkill( + "agent--cite", + createScopedTestSkill({ id: "agent--cite", ownerAgentId: "agent", shortName: "cite" }), + ); + registerSkill( + "other--style", + createScopedTestSkill({ id: "other--style", ownerAgentId: "other", shortName: "style" }), + ); + registerSkill("cite", createScopedTestSkill({ id: "cite" })); + + const cases: Array<{ + selector: true | string[] | undefined; + expectedPolicy: object; + expectedIds: string[]; + }> = [ + { + selector: undefined, + expectedPolicy: { kind: "all-visible", source: "omitted" }, + expectedIds: ["global", "bundled", "agent--cite", "cite"], + }, + { + selector: true, + expectedPolicy: { kind: "all-visible", source: "true" }, + expectedIds: ["global", "bundled", "agent--cite", "cite"], + }, + { + selector: [], + expectedPolicy: { kind: "none" }, + expectedIds: [], + }, + { + selector: ["bundled", "cite", "global", "bundled"], + expectedPolicy: { kind: "allowlist", entries: ["bundled", "cite", "global", "bundled"] }, + expectedIds: ["bundled", "agent--cite", "global"], + }, + ]; + + for (const testCase of cases) { + const snapshot = skillRegistry.resolveSelectorForAgent(testCase.selector, { + agentId: "agent", + }); + assertEquals(snapshot.policy, testCase.expectedPolicy); + assertEquals(snapshot.allowedSkillIds, testCase.expectedIds); + assertEquals(snapshot.definitions.map((skill) => skill.id), testCase.expectedIds); + } + }); + }); }); diff --git a/src/skill/registry.ts b/src/skill/registry.ts index 04474b20c3..60122f42ea 100644 --- a/src/skill/registry.ts +++ b/src/skill/registry.ts @@ -13,6 +13,11 @@ */ import type { Skill } from "./types.ts"; +import { + assertResolvedSkillSelector, + type ResolvedSkillSelectorSnapshot, + resolveSkillSelector, +} from "./selector.ts"; import { ScopedRegistryFacade } from "#veryfront/registry/scoped-registry-facade.ts"; import { ProjectScopedRegistryManager } from "#veryfront/registry/project-scoped-registry-manager.ts"; @@ -30,6 +35,31 @@ export function isSkillVisibleTo(skill: Skill, scope?: AgentCapabilityScope): bo } class SkillRegistryClass extends ScopedRegistryFacade { + /** + * Resolve a presence-aware, execution-facing skill selector snapshot. + * + * Omitted and `true` resolve to all visible skills, `[]` resolves to none, + * and explicit entries must resolve to this caller's own short names or + * exact visible ids. Explicit misses fail closed with a generic error. + */ + resolveSelectorForAgent( + skillsConfig: true | string[] | undefined, + scope?: AgentCapabilityScope, + ): ResolvedSkillSelectorSnapshot { + const snapshot = resolveSkillSelector({ + definitions: [...this.getAll().values()], + selector: skillsConfig, + getId: (skill) => skill.id, + isVisible: (skill) => isSkillVisibleTo(skill, scope), + getShortName: (skill) => skill.shortName, + isOwnShortNameCandidate: (skill) => + scope?.agentId !== undefined && skill.ownerAgentId === scope.agentId, + getSourcePath: (skill) => `${skill.rootPath}/SKILL.md`, + }); + assertResolvedSkillSelector(snapshot); + return snapshot; + } + /** * Resolve skills for an agent configuration. * diff --git a/src/skill/selector.ts b/src/skill/selector.ts new file mode 100644 index 0000000000..188795db01 --- /dev/null +++ b/src/skill/selector.ts @@ -0,0 +1,150 @@ +import { CONFIG_INVALID } from "#veryfront/errors"; + +/** Authored skill selector policy after preserving property presence. */ +export type ResolvedSkillSelectorPolicy = + | { kind: "all-visible"; source: "omitted" | "true" } + | { kind: "none" } + | { kind: "allowlist"; entries: string[] }; + +/** Sanitized unresolved explicit selector entry. */ +export type UnresolvedSkillSelectorEntry = { + index: number; +}; + +/** Deterministic resolved selector snapshot shared by skill catalog adapters. */ +export type ResolvedSkillSelectorSnapshot = { + policy: ResolvedSkillSelectorPolicy; + definitions: TDefinition[]; + allowedSkillIds: string[]; + skillSourcePaths: Record; + unresolvedEntries: UnresolvedSkillSelectorEntry[]; +}; + +/** Empty selector snapshot for explicit none policies. */ +export function createNoneSkillSelectorSnapshot( + policy: Extract = { kind: "none" }, +): ResolvedSkillSelectorSnapshot { + return { + policy, + definitions: [], + allowedSkillIds: [], + skillSourcePaths: {}, + unresolvedEntries: [], + }; +} + +type ResolveSkillSelectorInput = { + definitions: readonly TDefinition[]; + selector: true | readonly string[] | undefined; + getId: (definition: TDefinition) => string; + isVisible: (definition: TDefinition) => boolean; + getShortName?: (definition: TDefinition) => string | undefined; + isOwnShortNameCandidate?: (definition: TDefinition) => boolean; + getSourcePath?: (definition: TDefinition) => string | undefined; +}; + +const UNAVAILABLE_SKILLS_MESSAGE = + "One or more configured skills are not available to this agent. " + + "Update the skills selector to use visible skill IDs or this agent's own skill short names."; + +function buildSnapshot( + policy: ResolvedSkillSelectorPolicy, + definitions: TDefinition[], + getId: (definition: TDefinition) => string, + getSourcePath: ((definition: TDefinition) => string | undefined) | undefined, + unresolvedEntries: UnresolvedSkillSelectorEntry[], +): ResolvedSkillSelectorSnapshot { + return { + policy, + definitions, + allowedSkillIds: definitions.map((definition) => getId(definition)), + skillSourcePaths: Object.fromEntries( + definitions + .map((definition) => [getId(definition), getSourcePath?.(definition)] as const) + .filter((entry): entry is readonly [string, string] => entry[1] !== undefined), + ), + unresolvedEntries, + }; +} + +/** Resolve a presence-aware skill selector without throwing on explicit misses. */ +export function resolveSkillSelector( + input: ResolveSkillSelectorInput, +): ResolvedSkillSelectorSnapshot { + const visibleDefinitions = input.definitions.filter(input.isVisible); + + if (input.selector === undefined || input.selector === true) { + return buildSnapshot( + { + kind: "all-visible", + source: input.selector === true ? "true" : "omitted", + }, + visibleDefinitions, + input.getId, + input.getSourcePath, + [], + ); + } + + if (input.selector.length === 0) { + return createNoneSkillSelectorSnapshot(); + } + + const byId = new Map(); + const byOwnShortName = new Map(); + + for (const definition of visibleDefinitions) { + const id = input.getId(definition); + if (!byId.has(id)) { + byId.set(id, definition); + } + + const shortName = input.getShortName?.(definition); + if ( + shortName !== undefined && + (input.isOwnShortNameCandidate?.(definition) ?? true) && + !byOwnShortName.has(shortName) + ) { + byOwnShortName.set(shortName, definition); + } + } + + const selectedDefinitions: TDefinition[] = []; + const selectedIds = new Set(); + const unresolvedEntries: UnresolvedSkillSelectorEntry[] = []; + + input.selector.forEach((requested, index) => { + const definition = byOwnShortName.get(requested) ?? byId.get(requested); + if (!definition) { + unresolvedEntries.push({ index }); + return; + } + + const id = input.getId(definition); + if (!selectedIds.has(id)) { + selectedIds.add(id); + selectedDefinitions.push(definition); + } + }); + + return buildSnapshot( + { kind: "allowlist", entries: [...input.selector] }, + selectedDefinitions, + input.getId, + input.getSourcePath, + unresolvedEntries, + ); +} + +/** Throw the generic selector configuration error for unresolved explicit entries. */ +export function assertResolvedSkillSelector( + snapshot: ResolvedSkillSelectorSnapshot, +): void { + if (snapshot.unresolvedEntries.length === 0) { + return; + } + + throw CONFIG_INVALID.create({ + detail: UNAVAILABLE_SKILLS_MESSAGE, + }); +} diff --git a/src/skill/skill-owner-scope.test.ts b/src/skill/skill-owner-scope.test.ts index 47addbcfd9..043968812c 100644 --- a/src/skill/skill-owner-scope.test.ts +++ b/src/skill/skill-owner-scope.test.ts @@ -7,7 +7,7 @@ */ import "#veryfront/schemas/_test-setup.ts"; -import { assertEquals, assertRejects } from "#veryfront/testing/assert.ts"; +import { assertEquals, assertRejects, assertThrows } from "#veryfront/testing/assert.ts"; import { registerSkill, skillRegistry } from "./registry.ts"; import { createExecuteSkillScriptTool, @@ -93,6 +93,37 @@ Deno.test("explicit selector cannot reach another agent's owned skill by full id } }); +Deno.test("strict explicit selector rejects another agent's owned skill without owner-id leakage", () => { + setupRegistry(); + try { + const error = assertThrows( + () => skillRegistry.resolveSelectorForAgent(["researcher--cite"], { agentId: "writer" }), + Error, + "configured skills are not available", + ); + const message = String(error); + assertEquals(message.includes("researcher--cite"), false); + assertEquals(message.includes("writer--style"), false); + } finally { + skillRegistry.clearAll(); + } +}); + +Deno.test("strict explicit selector resolves own short name before exact visible id", () => { + setupRegistry(); + try { + registerSkill("cite", makeSkill({ id: "cite" })); + + const resolved = skillRegistry.resolveSelectorForAgent(["cite", "global-howto", "cite"], { + agentId: "researcher", + }); + + assertEquals(resolved.allowedSkillIds, ["researcher--cite", "global-howto"]); + } finally { + skillRegistry.clearAll(); + } +}); + Deno.test("getVisibleSkillIds excludes other agents' owned skills", () => { setupRegistry(); try { diff --git a/src/skill/tools.test.ts b/src/skill/tools.test.ts index eb3c9432ce..2ae15aa844 100644 --- a/src/skill/tools.test.ts +++ b/src/skill/tools.test.ts @@ -1,5 +1,5 @@ import "#veryfront/schemas/_test-setup.ts"; -import { assertEquals, assertRejects } from "#veryfront/testing/assert.ts"; +import { assert, assertEquals, assertRejects } from "#veryfront/testing/assert.ts"; import { beforeEach, describe, it } from "#veryfront/testing/bdd.ts"; import { registerSkill, skillRegistry } from "./registry.ts"; import { @@ -113,6 +113,88 @@ Review the asset files.`, assertEquals(result.references, ["assets/checklist.txt"]); }); + it("load_skill should reject skills outside the selector before reading storage", async () => { + let readCount = 0; + const fsAdapter = createSkillTestAdapter({ + "/project/skills/my-skill/SKILL.md": `--- +name: my-skill +description: Skill from adapter +--- +# Instructions +Do work.`, + }); + const countingAdapter: FileSystemAdapter = { + ...fsAdapter, + async readFile(path) { + readCount++; + return await fsAdapter.readFile(path); + }, + }; + registerSkill("my-skill", createTestSkill(countingAdapter)); + + const tool = createLoadSkillTool(); + + await assertRejects( + () => + tool.execute({ skillId: "my-skill" }, { + agentId: "agent", + allowedSkillIds: [], + }), + Error, + "not available to this agent", + ); + assertEquals(readCount, 0); + }); + + it("load_skill should not disclose selector-disallowed skills in unavailable errors", async () => { + const allowedAdapter = createSkillTestAdapter({ + "/project/skills/allowed-skill/SKILL.md": `--- +name: allowed-skill +description: Allowed skill +--- +# Instructions +Allowed work.`, + }); + const hiddenAdapter = createSkillTestAdapter({ + "/project/skills/hidden-skill/SKILL.md": `--- +name: hidden-skill +description: Hidden skill +--- +# Instructions +Hidden work.`, + }); + registerSkill("allowed-skill", createNamedTestSkill("allowed-skill", allowedAdapter)); + registerSkill("hidden-skill", createNamedTestSkill("hidden-skill", hiddenAdapter)); + + const tool = createLoadSkillTool(); + + const error = await assertRejects( + () => + tool.execute({ skillId: "missing-skill" }, { + agentId: "agent", + allowedSkillIds: ["allowed-skill"], + }), + Error, + ); + + assert(error instanceof Error); + assertEquals(error.message.includes("hidden-skill"), false); + assertEquals(error.message.includes("allowed-skill"), false); + + const hiddenError = await assertRejects( + () => + tool.execute({ skillId: "hidden-skill" }, { + agentId: "agent", + allowedSkillIds: ["allowed-skill"], + }), + Error, + ); + + assert(hiddenError instanceof Error); + assertEquals(hiddenError.message.includes("hidden-skill"), false); + assertEquals(hiddenError.message.includes("allowed-skill"), false); + }); + it("load_skill should omit prompt notes for unavailable file tools", async () => { const fsAdapter = createSkillTestAdapter({ "/project/skills/my-skill/SKILL.md": `--- @@ -291,6 +373,43 @@ Do work.`, ); }); + it("load_skill_reference should reject stale active skill state outside the selector", async () => { + let readCount = 0; + const fsAdapter = createSkillTestAdapter({ + "/project/skills/my-skill/references/guide.md": "Guide", + }); + const countingAdapter: FileSystemAdapter = { + ...fsAdapter, + async readFile(path) { + readCount++; + return await fsAdapter.readFile(path); + }, + }; + registerSkill("my-skill", createNamedTestSkill("my-skill", countingAdapter)); + + const tool = createLoadSkillReferenceTool(); + + await assertRejects( + () => + tool.execute({ + skillId: "my-skill", + reference: "references/guide.md", + }, { + agentId: "agent", + allowedSkillIds: [], + activeSkillId: "my-skill", + activeSkillToolAvailability: { + hasActiveSkill: true, + references: ["references/guide.md"], + scripts: [], + }, + }), + Error, + "not available to this agent", + ); + assertEquals(readCount, 0); + }); + it("execute_skill_script should run a local script from the skill directory", async () => { const tempDir = await Deno.makeTempDir({ prefix: "vf-skill-script-" }); diff --git a/src/skill/tools.ts b/src/skill/tools.ts index 659957f75e..4d994e69b5 100644 --- a/src/skill/tools.ts +++ b/src/skill/tools.ts @@ -32,6 +32,9 @@ import { const MAX_SCRIPT_TIMEOUT_MS = 300_000; type SkillFileKind = "reference" | "script"; +type SkillSelectorToolOptions = { + resolveAllowedSkillIds?: (context: ToolExecutionContext | undefined) => readonly string[]; +}; const getLoadSkillInputSchema = defineSchema((v) => v.object({ @@ -89,10 +92,16 @@ async function readSkillFile(skill: Skill, path: string): Promise { function resolveVisibleSkillOrThrow( skillId: string, context: ToolExecutionContext | undefined, + options: SkillSelectorToolOptions = {}, ): Skill { const scope = { agentId: context?.agentId }; + const allowedSkillIds = getSelectorAllowedSkillIds(context, options); const skill = skillRegistry.resolveVisibleSkill(skillId, scope); if (!skill) { + if (allowedSkillIds !== undefined) { + throw createSkillUnavailableError(); + } + const visible = skillRegistry.getVisibleSkillIds(scope).join(", "); throw toError( createError({ @@ -101,9 +110,47 @@ function resolveVisibleSkillOrThrow( }), ); } + assertSkillAllowedBySelector(skill, allowedSkillIds); return skill; } +function createSkillUnavailableError(): Error { + return toError( + createError({ + type: "agent", + message: "Skill is not available to this agent.", + }), + ); +} + +function getSelectorAllowedSkillIds( + context: ToolExecutionContext | undefined, + options: SkillSelectorToolOptions, +): readonly string[] | undefined { + const resolved = options.resolveAllowedSkillIds?.(context); + if (resolved !== undefined) return resolved; + + const contextAllowed = context?.allowedSkillIds; + if ( + Array.isArray(contextAllowed) && + contextAllowed.every((entry): entry is string => typeof entry === "string") + ) { + return contextAllowed; + } + return undefined; +} + +function assertSkillAllowedBySelector( + skill: Skill, + allowedSkillIds: readonly string[] | undefined, +): void { + if (allowedSkillIds === undefined || allowedSkillIds.includes(skill.id)) { + return; + } + + throw createSkillUnavailableError(); +} + function hasRuntimeSkillBoundary( context: ToolExecutionContext | undefined, ): context is ToolExecutionContext { @@ -162,14 +209,14 @@ function assertActiveSkillFileAvailable( * Create the load_skill tool. * Loads a skill's full instructions, available references, and scripts. */ -export function createLoadSkillTool(): Tool { +export function createLoadSkillTool(options: SkillSelectorToolOptions = {}): Tool { return tool({ id: "load_skill", description: "Load a skill's full instructions. Returns the skill's markdown instructions, " + "allowed tools policy, and lists of available reference files and scripts.", inputSchema: getLoadSkillInputSchema(), execute: async (input, context): Promise => { - const skill = resolveVisibleSkillOrThrow(input.skillId, context); + const skill = resolveVisibleSkillOrThrow(input.skillId, context, options); // Read SKILL.md const skillMdPath = join(skill.rootPath, SKILL_MD_FILENAME); @@ -218,14 +265,14 @@ export function createLoadSkillTool(): Tool { * Create the load_skill_reference tool. * Reads a reference file from a skill's references/, resources/, or assets/ directory. */ -export function createLoadSkillReferenceTool(): Tool { +export function createLoadSkillReferenceTool(options: SkillSelectorToolOptions = {}): Tool { return tool({ id: "load_skill_reference", description: "Read a reference file from a skill. Only files in the skill's " + "references/, resources/, and assets/ directories are accessible.", inputSchema: getLoadSkillReferenceInputSchema(), execute: async (input, context): Promise<{ content: string; path: string }> => { - const skill = resolveVisibleSkillOrThrow(input.skillId, context); + const skill = resolveVisibleSkillOrThrow(input.skillId, context, options); assertActiveSkillFileAvailable( { toolName: "load_skill_reference", @@ -256,7 +303,7 @@ export function createLoadSkillReferenceTool(): Tool { * Executes a script from a skill's scripts/ directory. */ export function createExecuteSkillScriptTool( - options: { executor?: SkillScriptExecutor } = {}, + options: { executor?: SkillScriptExecutor } & SkillSelectorToolOptions = {}, ): Tool { return tool({ id: "execute_skill_script", @@ -264,7 +311,7 @@ export function createExecuteSkillScriptTool( "Execute a script from a skill's scripts/ directory. Returns stdout, stderr, and exit code.", inputSchema: getExecuteSkillScriptInputSchema(), execute: async (input, context) => { - const skill = resolveVisibleSkillOrThrow(input.skillId, context); + const skill = resolveVisibleSkillOrThrow(input.skillId, context, options); assertActiveSkillFileAvailable( { toolName: "execute_skill_script",