update agents skill design

This commit is contained in:
2026-08-20 18:18:10 +05:30
parent 161b237695
commit b2e6868824
75 changed files with 14586 additions and 98 deletions

View File

@@ -178,6 +178,33 @@ export function outOfScopeAnswer(context) {
);
}
/**
* The reply when the selected agent does not cover this page.
*
* Says three things, because leaving any of them out invites the reader to
* assume something untrue: which agent is active, that the *page* is what
* bounds the answer rather than the agent being broken, and which agent is the
* one for here.
*
* It deliberately does not answer from the requested agent's own pages. An
* agent is a lens on the page you are standing on, never a way to reach another
* one — reaching would make selecting an agent a way around the page boundary,
* which is the one thing the design does not permit.
*/
export function constrainedAgentAnswer(agent, context, suggestion) {
const covered = (agent?.pages || []).join(', ');
return doc(
text(`**${agent?.name || 'That agent'}** is selected, but it does not cover **${context?.page || 'this page'}**.`),
note(covered
? `It works on: ${covered}.`
: 'It covers no pages yet.'),
text(suggestion
? `On this page, **${suggestion.name}** is the one that answers. Switch to it, or open a page ${agent?.name || 'this agent'} covers.`
: 'Open a page it covers, or ask something this page can answer.'),
note('An agent narrows what can be asked here. It never widens it, so the records this page holds are the records any agent can read from it.')
);
}
/**
* Resolve a question to an action, ahead of the page responder.
*
@@ -185,6 +212,7 @@ export function outOfScopeAnswer(context) {
* { kind: 'answer' } — let this page respond
* { kind: 'navigate', destination, doc } — other page; go there
* { kind: 'outOfScope', doc } — nothing here can answer it
* { kind: 'constrained', doc } — the agent does not cover this page
*/
/* ── Workforce ──────────────────────────────────────────────────────────── */
@@ -820,6 +848,16 @@ function resolveDraftAction(question, workforce, positionId = null) {
export function resolveIntent({
question, contextId, disabledSkills = [], customSkills = [], roles = [], skillCategories = [],
courses = [], workforce = null, skillContext = null,
/**
* The active agent, and where the reader is.
*
* Both optional and both inert when absent, so a caller that knows nothing
* about agents resolves exactly as it always did. `agent` does not gate the
* skills considered here — that has already happened, in `disabledSkills`,
* which arrives carrying the agent's scoping. What it decides is whether this
* agent should be answering on this page at all.
*/
agent = null, agentCoversPage = true, agentSuggestion = null, owliverContext = null,
/* The record the control that raised this question was built from, when there
was one. Only the draft flow reads it; a typed question carries none and
resolves exactly as it always did. */
@@ -827,6 +865,18 @@ export function resolveIntent({
}) {
const context = ASSISTANT_CONTEXTS[contextId] ?? null;
/**
* 0. The selected agent does not belong here.
*
* Ahead of everything, because every matcher below would otherwise answer
* from this page while the header names an agent that does not cover it — an
* answer attributed to the wrong lens. The page still decides what is
* readable; this only declines to pretend the agent chose it.
*/
if (agent && !agentCoversPage) {
return { kind: 'constrained', agent, doc: constrainedAgentAnswer(agent, context, agentSuggestion) };
}
/**
* 1. Finishing a draft, wherever the reader is standing.
*
@@ -846,7 +896,10 @@ export function resolveIntent({
/* 2. Current page skills — specific triggers, ahead of the general reader. */
const skill = resolveSkill({
question, contextId, disabledSkills, customSkills, roles, skillCategories, courses,
skillContext,
/* The envelope travels beside the collections rather than replacing them:
a resolver reads records, and the envelope says where the reader is. A
source that needs a position still finds it exactly where it always was. */
skillContext: owliverContext ? { ...skillContext, owliver: owliverContext } : skillContext,
});
if (skill) return skill;