@agent-native/core 0.224.0 → 0.225.0-nightly-20261105110131
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action.d.ts +28 -0
- package/dist/action.js +47 -0
- package/dist/agent/production-agent.d.ts +2 -0
- package/dist/agent/tool-search.d.ts +2 -0
- package/dist/agent/tool-search.js +287 -75
- package/dist/framework-package-versions.json +7 -7
- package/dist/mcp-client/index.js +22 -0
- package/dist/server/action-discovery.js +28 -9
- package/dist/server/agent-chat-plugin.js +21 -9
- package/dist/server/filesystem-action-entry.d.ts +9 -1
- package/dist/server/filesystem-action-entry.js +26 -2
- package/dist/server/prompts/framework-core-compact.js +1 -1
- package/dist/server/prompts/framework-core.js +1 -1
- package/dist/templates/chat/.agents/skills/actions/SKILL.md +1 -1
- package/dist/templates/default/.agents/skills/actions/SKILL.md +1 -1
- package/dist/templates/headless/.agents/skills/actions/SKILL.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +1 -1
- package/docs/content/actions-defining.mdx +1 -0
- package/package.json +4 -4
- package/src/templates/chat/.agents/skills/actions/SKILL.md +1 -1
- package/src/templates/default/.agents/skills/actions/SKILL.md +1 -1
- package/src/templates/headless/.agents/skills/actions/SKILL.md +1 -1
- package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +1 -1
package/dist/action.d.ts
CHANGED
|
@@ -488,6 +488,13 @@ interface DefineActionWithSchema<TSchema extends StandardSchemaV1, TReturn = any
|
|
|
488
488
|
mcpTool?: boolean;
|
|
489
489
|
mcpAnnotations?: ActionMcpToolAnnotations;
|
|
490
490
|
deferLoading?: boolean;
|
|
491
|
+
/** Words and short phrases a user says for this action that its name and
|
|
492
|
+
* description do not: "auto-archive rule", "canned reply", "reschedule".
|
|
493
|
+
* `tool-search` ranks them just below the name. They are never shown to a
|
|
494
|
+
* model, so they add no context cost; keep them factual, not a list of
|
|
495
|
+
* every request the action might serve. At most 12, each up to 60
|
|
496
|
+
* characters. */
|
|
497
|
+
searchHints?: readonly string[];
|
|
491
498
|
eagerInputStreaming?: boolean;
|
|
492
499
|
/** An empty list on this action sets an option and erases nothing saved, so
|
|
493
500
|
* a call that pads unused fields with `null` and `[]` can run as sent. Leave
|
|
@@ -600,6 +607,8 @@ interface DefineActionWithParams<TParams extends Record<string, ParameterSchema>
|
|
|
600
607
|
mcpTool?: boolean;
|
|
601
608
|
mcpAnnotations?: ActionMcpToolAnnotations;
|
|
602
609
|
deferLoading?: boolean;
|
|
610
|
+
/** See `searchHints` on the schema form above. */
|
|
611
|
+
searchHints?: readonly string[];
|
|
603
612
|
eagerInputStreaming?: boolean;
|
|
604
613
|
emptyListsEraseNothing?: boolean;
|
|
605
614
|
readOnly?: boolean;
|
|
@@ -643,6 +652,7 @@ export interface ActionDefinition<TInput, TReturn> {
|
|
|
643
652
|
readonly mcpTool?: boolean;
|
|
644
653
|
readonly mcpAnnotations?: ActionMcpToolAnnotations;
|
|
645
654
|
readonly deferLoading?: boolean;
|
|
655
|
+
readonly searchHints?: readonly string[];
|
|
646
656
|
readonly eagerInputStreaming?: boolean;
|
|
647
657
|
readonly emptyListsEraseNothing?: boolean;
|
|
648
658
|
readonly readOnly?: boolean;
|
|
@@ -679,6 +689,24 @@ export interface ActionDefinition<TInput, TReturn> {
|
|
|
679
689
|
readonly audit?: ActionAuditConfig;
|
|
680
690
|
readonly access?: ActionAccessConfig;
|
|
681
691
|
}
|
|
692
|
+
/**
|
|
693
|
+
* An action's own definition is invalid. Loaders never skip such an action or
|
|
694
|
+
* fall back to wrapping it as a CLI script: the app fails to load, naming the
|
|
695
|
+
* action, until the definition is fixed.
|
|
696
|
+
*/
|
|
697
|
+
export declare class ActionDefinitionError extends TypeError {
|
|
698
|
+
readonly actionDefinitionError = true;
|
|
699
|
+
readonly actionName: string | undefined;
|
|
700
|
+
constructor(message: string, actionName?: string);
|
|
701
|
+
}
|
|
702
|
+
export declare function isActionDefinitionError(error: unknown): error is ActionDefinitionError;
|
|
703
|
+
/**
|
|
704
|
+
* The same error naming `actionName`, for a loader that knows the name the
|
|
705
|
+
* definition itself could not report.
|
|
706
|
+
*/
|
|
707
|
+
export declare function namedActionDefinitionError(error: ActionDefinitionError, actionName: string): ActionDefinitionError;
|
|
708
|
+
/** Throws unless `value` is absent or a valid hint list. */
|
|
709
|
+
export declare function validateSearchHints(value: unknown, actionName?: string): readonly string[] | undefined;
|
|
682
710
|
export declare function defineAction<TSchema extends StandardSchemaV1, TReturn>(options: DefineActionWithSchema<TSchema, TReturn, undefined>): ActionDefinition<StandardSchemaV1.InferInput<TSchema>, TReturn>;
|
|
683
711
|
export declare function defineAction<TParams extends Record<string, ParameterSchema> | undefined, TReturn>(options: DefineActionWithParams<TParams, TReturn, undefined>): ActionDefinition<InferParams<TParams>, TReturn>;
|
|
684
712
|
export declare function defineAction<TParams extends Record<string, ParameterSchema> | undefined, TOutputSchema extends StandardSchemaV1, TStrategy extends ActionOutputErrorStrategy = "strict">(options: DefineActionWithParams<TParams, StandardSchemaV1.InferInput<TOutputSchema>, TOutputSchema, TStrategy> & {
|
package/dist/action.js
CHANGED
|
@@ -472,6 +472,51 @@ function withCommittedEffect(error) {
|
|
|
472
472
|
export const MCP_APP_EXTENSION_ID = "io.modelcontextprotocol/ui";
|
|
473
473
|
export const MCP_APP_MIME_TYPE = "text/html;profile=mcp-app";
|
|
474
474
|
export const MCP_APP_RESOURCE_URI_META_KEY = "ui/resourceUri";
|
|
475
|
+
const MAX_SEARCH_HINTS = 12;
|
|
476
|
+
const MAX_SEARCH_HINT_CHARS = 60;
|
|
477
|
+
/**
|
|
478
|
+
* An action's own definition is invalid. Loaders never skip such an action or
|
|
479
|
+
* fall back to wrapping it as a CLI script: the app fails to load, naming the
|
|
480
|
+
* action, until the definition is fixed.
|
|
481
|
+
*/
|
|
482
|
+
export class ActionDefinitionError extends TypeError {
|
|
483
|
+
actionDefinitionError = true;
|
|
484
|
+
actionName;
|
|
485
|
+
constructor(message, actionName) {
|
|
486
|
+
super(actionName ? `Action "${actionName}": ${message}` : message);
|
|
487
|
+
this.name = "ActionDefinitionError";
|
|
488
|
+
this.actionName = actionName;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
export function isActionDefinitionError(error) {
|
|
492
|
+
return (error instanceof ActionDefinitionError ||
|
|
493
|
+
(!!error &&
|
|
494
|
+
typeof error === "object" &&
|
|
495
|
+
error.actionDefinitionError ===
|
|
496
|
+
true));
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* The same error naming `actionName`, for a loader that knows the name the
|
|
500
|
+
* definition itself could not report.
|
|
501
|
+
*/
|
|
502
|
+
export function namedActionDefinitionError(error, actionName) {
|
|
503
|
+
return error.actionName
|
|
504
|
+
? error
|
|
505
|
+
: new ActionDefinitionError(error.message, actionName);
|
|
506
|
+
}
|
|
507
|
+
/** Throws unless `value` is absent or a valid hint list. */
|
|
508
|
+
export function validateSearchHints(value, actionName) {
|
|
509
|
+
if (value === undefined)
|
|
510
|
+
return undefined;
|
|
511
|
+
if (!Array.isArray(value) ||
|
|
512
|
+
value.length > MAX_SEARCH_HINTS ||
|
|
513
|
+
value.some((hint) => typeof hint !== "string" ||
|
|
514
|
+
hint.trim().length === 0 ||
|
|
515
|
+
hint.length > MAX_SEARCH_HINT_CHARS)) {
|
|
516
|
+
throw new ActionDefinitionError(`searchHints must be at most ${MAX_SEARCH_HINTS} non-empty strings of up to ${MAX_SEARCH_HINT_CHARS} characters.`, actionName);
|
|
517
|
+
}
|
|
518
|
+
return Object.freeze([...value]);
|
|
519
|
+
}
|
|
475
520
|
export function defineAction(options) {
|
|
476
521
|
const hasSchema = options.schema && "~standard" in options.schema;
|
|
477
522
|
const tool = {
|
|
@@ -716,6 +761,7 @@ export function defineAction(options) {
|
|
|
716
761
|
const deferLoading = typeof options.deferLoading === "boolean"
|
|
717
762
|
? options.deferLoading
|
|
718
763
|
: undefined;
|
|
764
|
+
const searchHints = validateSearchHints(options.searchHints);
|
|
719
765
|
const parallelSafe = typeof options.parallelSafe === "boolean"
|
|
720
766
|
? options.parallelSafe
|
|
721
767
|
: undefined;
|
|
@@ -783,6 +829,7 @@ export function defineAction(options) {
|
|
|
783
829
|
...(typeof mcpTool === "boolean" ? { mcpTool } : {}),
|
|
784
830
|
...(mcpAnnotations ? { mcpAnnotations } : {}),
|
|
785
831
|
...(typeof deferLoading === "boolean" ? { deferLoading } : {}),
|
|
832
|
+
...(searchHints ? { searchHints } : {}),
|
|
786
833
|
...(typeof options.eagerInputStreaming === "boolean"
|
|
787
834
|
? { eagerInputStreaming: options.eagerInputStreaming }
|
|
788
835
|
: {}),
|
|
@@ -193,6 +193,8 @@ export interface ActionEntry {
|
|
|
193
193
|
* decision, so they never count as the action declaring its hints. */
|
|
194
194
|
mcpAnnotationDefaults?: import("../action.js").ActionMcpToolAnnotations;
|
|
195
195
|
deferLoading?: boolean;
|
|
196
|
+
/** See `searchHints` in `defineAction`. Read only by `tool-search`. */
|
|
197
|
+
searchHints?: readonly string[];
|
|
196
198
|
eagerInputStreaming?: boolean;
|
|
197
199
|
/** See `emptyListsEraseNothing` in `defineAction`. */
|
|
198
200
|
emptyListsEraseNothing?: boolean;
|
|
@@ -23,6 +23,8 @@ type ToolSearchResult = {
|
|
|
23
23
|
score: number;
|
|
24
24
|
callable: boolean;
|
|
25
25
|
planAvailability: "read" | "conditional" | "act-only";
|
|
26
|
+
/** Present on a tool that deletes or cancels; absent on every other. */
|
|
27
|
+
destructive?: true;
|
|
26
28
|
parameters: ToolParameterSummary[];
|
|
27
29
|
};
|
|
28
30
|
type ToolSearchOptions = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isActionHiddenFromEveryAgentSurface } from "../action.js";
|
|
2
2
|
import { parseMcpToolName } from "../mcp-client/manager.js";
|
|
3
3
|
import { isMcpToolAllowedForRequest } from "../mcp-client/visibility.js";
|
|
4
|
-
import { frameworkMcpTool } from "../mcp/framework-tools.js";
|
|
4
|
+
import { frameworkMcpTool, isFrameworkMcpTool, } from "../mcp/framework-tools.js";
|
|
5
5
|
import { getRequestRunContext } from "../server/request-context.js";
|
|
6
6
|
import { clipHead } from "./clip-text.js";
|
|
7
7
|
export const TOOL_SEARCH_ACTION_NAME = "tool-search";
|
|
@@ -23,19 +23,19 @@ export function createToolSearchEntry(getRegistry, options = {}) {
|
|
|
23
23
|
const defaultLimit = Math.max(1, Math.min(options.defaultLimit ?? DEFAULT_LIMIT, maxLimit));
|
|
24
24
|
return frameworkMcpTool({
|
|
25
25
|
tool: {
|
|
26
|
-
description: "Find actions and connected MCP tools by
|
|
26
|
+
description: "Find actions and connected MCP tools by what they do or by exact name. The system prompt's Tool index lists every deferred tool name grouped by topic; loading several exact names in one call is the fastest path. A targeted search returns concise details and adds matches allowed in the current mode, with their full schemas, to the NEXT model step, not this one, so request every tool you need in one call (`queries` and/or `names`) and call them on the next step. If every match is already callable, call it directly instead of searching again. Results rank by words, not intent: before calling one marked `destructive`, confirm the user wants that change. Omit query for a bounded alphabetical menu; menu entries are informational until you search for a capability or name.",
|
|
27
27
|
parameters: {
|
|
28
28
|
type: "object",
|
|
29
29
|
properties: {
|
|
30
30
|
query: {
|
|
31
31
|
type: "string",
|
|
32
|
-
description:
|
|
32
|
+
description: 'The task in a few plain words ("edit auto-archive rules") or an exact tool name; the user\'s own words work. Omit for a bounded alphabetical inventory.',
|
|
33
33
|
},
|
|
34
34
|
queries: {
|
|
35
35
|
type: "array",
|
|
36
36
|
items: { type: "string" },
|
|
37
37
|
maxItems: MAX_QUERIES,
|
|
38
|
-
description: `Up to ${MAX_QUERIES}
|
|
38
|
+
description: `Up to ${MAX_QUERIES} searches run together, one per task step; all matches load on the next step.`,
|
|
39
39
|
},
|
|
40
40
|
names: {
|
|
41
41
|
type: "array",
|
|
@@ -195,6 +195,7 @@ function searchOnce(registry, target, args, options) {
|
|
|
195
195
|
score: 0,
|
|
196
196
|
callable: registry[name]?.allowInPlanMode !== false,
|
|
197
197
|
planAvailability: getPlanAvailability(name, registry[name]),
|
|
198
|
+
...destructiveMark(name, registry[name]),
|
|
198
199
|
description: "Already returned by an earlier identical tool-search call.",
|
|
199
200
|
parameters: [],
|
|
200
201
|
})),
|
|
@@ -233,6 +234,7 @@ function searchOnce(registry, target, args, options) {
|
|
|
233
234
|
score: 0,
|
|
234
235
|
callable,
|
|
235
236
|
planAvailability,
|
|
237
|
+
...destructiveMark(name, entry),
|
|
236
238
|
parameters: [],
|
|
237
239
|
});
|
|
238
240
|
continue;
|
|
@@ -246,6 +248,7 @@ function searchOnce(registry, target, args, options) {
|
|
|
246
248
|
score: 0,
|
|
247
249
|
callable,
|
|
248
250
|
planAvailability,
|
|
251
|
+
...destructiveMark(name, entry),
|
|
249
252
|
parameters: parameters
|
|
250
253
|
.slice(0, MAX_PARAMETER_COUNT)
|
|
251
254
|
.map(boundParameterSummary),
|
|
@@ -258,7 +261,14 @@ function searchOnce(registry, target, args, options) {
|
|
|
258
261
|
}
|
|
259
262
|
rankable.push({
|
|
260
263
|
candidate,
|
|
261
|
-
|
|
264
|
+
prior: isFrameworkMcpTool(entry) ? FRAMEWORK_TOOL_PRIOR : 1,
|
|
265
|
+
fields: searchDocumentFields({
|
|
266
|
+
name,
|
|
267
|
+
source,
|
|
268
|
+
hints: entry.searchHints,
|
|
269
|
+
description,
|
|
270
|
+
parameters,
|
|
271
|
+
}),
|
|
262
272
|
});
|
|
263
273
|
}
|
|
264
274
|
if (!listAll && !exactNames) {
|
|
@@ -519,67 +529,230 @@ function summarizeType(value) {
|
|
|
519
529
|
const BM25_K1 = 1.2;
|
|
520
530
|
const BM25_B = 0.75;
|
|
521
531
|
const NAME_PHRASE_BONUS = 5;
|
|
522
|
-
|
|
523
|
-
//
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
532
|
+
const SYNONYM_WEIGHT = 0.5;
|
|
533
|
+
// BM25F: each field is length-normalised on its own, then weighted, so a long
|
|
534
|
+
// description cannot drown a one-word name match. Search hints are words users
|
|
535
|
+
// say for the action, so they rank just below the name; the first sentence of
|
|
536
|
+
// a description says what the tool is for and outranks the caveats after it.
|
|
537
|
+
const FIELD_WEIGHTS = {
|
|
538
|
+
name: 5,
|
|
539
|
+
hints: 4,
|
|
540
|
+
source: 2,
|
|
541
|
+
summary: 2,
|
|
542
|
+
details: 1,
|
|
543
|
+
parameters: 0.5,
|
|
544
|
+
};
|
|
545
|
+
const SEARCH_FIELDS = Object.keys(FIELD_WEIGHTS);
|
|
546
|
+
// Framework tools (sharing, review comments, usage, settings) ship in every
|
|
547
|
+
// app and share its vocabulary: in Mail, "email" also names transactional
|
|
548
|
+
// email logs. A request made in an app is usually about that app's own work,
|
|
549
|
+
// so on similar text the app's actions rank first. Exact names still win.
|
|
550
|
+
const FRAMEWORK_TOOL_PRIOR = 0.8;
|
|
551
|
+
// Stemming would fold these nouns into an unrelated word ("news" into "new",
|
|
552
|
+
// which then expands to the create synonyms; "setting" into "set"), so they
|
|
553
|
+
// are indexed as themselves. Declared before the stemmed set below, which
|
|
554
|
+
// calls `stem` as the module loads.
|
|
555
|
+
const PROTECTED_TERMS = new Map([
|
|
556
|
+
["news", "news"],
|
|
557
|
+
["setting", "setting"],
|
|
558
|
+
["settings", "setting"],
|
|
559
|
+
["recording", "recording"],
|
|
560
|
+
["recordings", "recording"],
|
|
561
|
+
["mapping", "mapping"],
|
|
562
|
+
["mappings", "mapping"],
|
|
563
|
+
["heading", "heading"],
|
|
564
|
+
["headings", "heading"],
|
|
565
|
+
]);
|
|
566
|
+
// Verbs that lose data. "Remove" and "clear" also name reversible edits
|
|
567
|
+
// (removing a tag, clearing a filter), so they count only through a declared
|
|
568
|
+
// `destructiveHint`, as does "permanently remove" spelled any other way.
|
|
569
|
+
// Verbs that lose data or undo something another person relies on. "Remove"
|
|
570
|
+
// and "clear" also name reversible edits (removing a tag, clearing a filter),
|
|
571
|
+
// so they count only through a declared `destructiveHint`.
|
|
572
|
+
const DESTRUCTIVE_VERBS = new Set(["delete", "destroy", "trash", "purge", "cancel"].map(stem));
|
|
573
|
+
/** "delete", "deleteNode", "delete-node", "delete_node": the value leads with one. */
|
|
574
|
+
function leadsWithDestructiveVerb(value) {
|
|
575
|
+
const verb = searchTerms(value).at(0);
|
|
576
|
+
return verb !== undefined && DESTRUCTIVE_VERBS.has(verb);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* The `destructive` mark every result carries, on every path that builds one.
|
|
580
|
+
* Ranking ignores it: whether the user wants the change ("delete it" or "don't
|
|
581
|
+
* delete it") is the agent's call, so a search finds the tool either way and
|
|
582
|
+
* the mark tells the agent to check.
|
|
583
|
+
*
|
|
584
|
+
* Declared annotations decide, `destructiveHint: false` included. Without
|
|
585
|
+
* them, a read-only tool is never destructive (a `status: "cancelled"` filter
|
|
586
|
+
* on a list is not a write), and any other tool is when an `enum` or `const`
|
|
587
|
+
* value anywhere in its input schema leads with a destructive verb
|
|
588
|
+
* (`operation: "delete"`, `op: "delete"`, `kind: "deleteNode"`) or its own
|
|
589
|
+
* name does, since most delete actions declare no annotations.
|
|
590
|
+
*/
|
|
591
|
+
function destructiveMark(name, entry) {
|
|
592
|
+
if (!entry)
|
|
593
|
+
return {};
|
|
594
|
+
if (entry.mcpAnnotations) {
|
|
595
|
+
return entry.mcpAnnotations.destructiveHint ? { destructive: true } : {};
|
|
596
|
+
}
|
|
597
|
+
if (entry.readOnly === true)
|
|
598
|
+
return {};
|
|
599
|
+
return schemaHasDestructiveValue(entry.tool.parameters, 0) ||
|
|
600
|
+
leadsWithDestructiveVerb(parseMcpToolName(name)?.toolName ?? name)
|
|
601
|
+
? { destructive: true }
|
|
602
|
+
: {};
|
|
603
|
+
}
|
|
604
|
+
// Deep enough for a list of tagged operations (properties → items → oneOf →
|
|
605
|
+
// properties → op), shallow enough that a huge or cyclic schema cannot stall a
|
|
606
|
+
// search.
|
|
607
|
+
const MAX_SCHEMA_DEPTH = 6;
|
|
608
|
+
function schemaHasDestructiveValue(schema, depth) {
|
|
609
|
+
if (depth > MAX_SCHEMA_DEPTH || !schema || typeof schema !== "object") {
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
const node = schema;
|
|
613
|
+
const literals = [
|
|
614
|
+
...(Array.isArray(node.enum) ? node.enum : []),
|
|
615
|
+
node.const,
|
|
616
|
+
].filter((value) => typeof value === "string");
|
|
617
|
+
if (literals.some(leadsWithDestructiveVerb))
|
|
618
|
+
return true;
|
|
619
|
+
const children = [
|
|
620
|
+
...(node.properties && typeof node.properties === "object"
|
|
621
|
+
? Object.values(node.properties)
|
|
622
|
+
: []),
|
|
623
|
+
...(Array.isArray(node.items) ? node.items : [node.items]),
|
|
624
|
+
...[node.oneOf, node.anyOf, node.allOf].flatMap((list) => Array.isArray(list) ? list : []),
|
|
537
625
|
];
|
|
626
|
+
return children.some((child) => schemaHasDestructiveValue(child, depth + 1));
|
|
627
|
+
}
|
|
628
|
+
function fieldTerms(value) {
|
|
629
|
+
const terms = searchTerms(value);
|
|
630
|
+
const counts = new Map();
|
|
631
|
+
for (const term of terms)
|
|
632
|
+
counts.set(term, (counts.get(term) ?? 0) + 1);
|
|
633
|
+
return { counts, length: terms.length };
|
|
634
|
+
}
|
|
635
|
+
function searchDocumentFields(doc) {
|
|
636
|
+
const sentenceEnd = doc.description.search(/[.!?](\s|$)/);
|
|
637
|
+
const summaryEnd = sentenceEnd < 0 ? doc.description.length : sentenceEnd + 1;
|
|
638
|
+
return {
|
|
639
|
+
name: fieldTerms(doc.name),
|
|
640
|
+
hints: fieldTerms((doc.hints ?? []).join(" ")),
|
|
641
|
+
source: fieldTerms(doc.source ?? ""),
|
|
642
|
+
summary: fieldTerms(doc.description.slice(0, summaryEnd)),
|
|
643
|
+
details: fieldTerms(doc.description.slice(summaryEnd)),
|
|
644
|
+
parameters: fieldTerms(doc.parameters.map((p) => `${p.name} ${p.description ?? ""}`).join(" ")),
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
// Common verbs and nouns users say in place of the words tool names use. They
|
|
648
|
+
// count for half of a literal match, so the user's own word still wins.
|
|
649
|
+
const SYNONYM_GROUPS = [
|
|
650
|
+
["create", "make", "new", "add"],
|
|
651
|
+
["delete", "remove", "trash", "erase", "clear"],
|
|
652
|
+
["update", "change", "edit", "modify", "rename", "adjust"],
|
|
653
|
+
["search", "find", "look", "lookup"],
|
|
654
|
+
["list", "show"],
|
|
655
|
+
["view", "see", "open"],
|
|
656
|
+
["restore", "undo", "revert", "recover"],
|
|
657
|
+
["export", "download"],
|
|
658
|
+
["duplicate", "copy", "clone"],
|
|
659
|
+
["reply", "respond", "answer"],
|
|
660
|
+
["image", "picture", "photo"],
|
|
661
|
+
["document", "doc", "page"],
|
|
662
|
+
["email", "mail", "message"],
|
|
663
|
+
["setting", "preference"],
|
|
664
|
+
];
|
|
665
|
+
const SYNONYMS = (() => {
|
|
666
|
+
const map = new Map();
|
|
667
|
+
for (const group of SYNONYM_GROUPS) {
|
|
668
|
+
const stems = group.map(stem);
|
|
669
|
+
for (const word of stems) {
|
|
670
|
+
const others = stems.filter((other) => other !== word);
|
|
671
|
+
map.set(word, [...(map.get(word) ?? []), ...others]);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return map;
|
|
675
|
+
})();
|
|
676
|
+
function weightedQueryTerms(queryTokens) {
|
|
677
|
+
const weights = new Map(queryTokens.map((term) => [term, 1]));
|
|
678
|
+
for (const term of queryTokens) {
|
|
679
|
+
for (const synonym of SYNONYMS.get(term) ?? []) {
|
|
680
|
+
if (!weights.has(synonym))
|
|
681
|
+
weights.set(synonym, SYNONYM_WEIGHT);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return weights;
|
|
538
685
|
}
|
|
539
686
|
// Corpus statistics come from every tool that survived this call's filters, so
|
|
540
687
|
// a word most tools share ("get", "list", "app") ranks below a rare one.
|
|
541
688
|
function rankByBm25(query, queryTokens, docs) {
|
|
542
689
|
if (queryTokens.length === 0 || docs.length === 0)
|
|
543
690
|
return [];
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
691
|
+
const queryTerms = weightedQueryTerms(queryTokens);
|
|
692
|
+
// Averaged over the tools that fill the field: most tools have no search
|
|
693
|
+
// hints, and counting them would make every hint list look too long.
|
|
694
|
+
const averageLength = Object.fromEntries(SEARCH_FIELDS.map((field) => {
|
|
695
|
+
const lengths = docs
|
|
696
|
+
.map((doc) => doc.fields[field].length)
|
|
697
|
+
.filter((length) => length > 0);
|
|
698
|
+
return [
|
|
699
|
+
field,
|
|
700
|
+
lengths.reduce((sum, length) => sum + length, 0) / lengths.length || 1,
|
|
701
|
+
];
|
|
702
|
+
}));
|
|
703
|
+
const idf = new Map([...queryTerms.keys()].map((term) => {
|
|
704
|
+
const frequency = docs.filter((doc) => SEARCH_FIELDS.some((field) => doc.fields[field].counts.has(term))).length;
|
|
554
705
|
return [
|
|
555
706
|
term,
|
|
556
707
|
Math.log(1 + (docs.length - frequency + 0.5) / (frequency + 0.5)),
|
|
557
708
|
];
|
|
558
709
|
}));
|
|
559
710
|
const wholeQuery = query.trim().toLowerCase();
|
|
560
|
-
|
|
561
|
-
return docs.flatMap((doc, index) => {
|
|
711
|
+
return docs.flatMap((doc) => {
|
|
562
712
|
const { candidate } = doc;
|
|
563
713
|
if (candidate.name.toLowerCase() === wholeQuery) {
|
|
564
714
|
return [{ ...candidate, score: EXACT_NAME_SCORE }];
|
|
565
715
|
}
|
|
566
|
-
const norm = BM25_K1 * (1 - BM25_B + (BM25_B * doc.terms.length) / averageLength);
|
|
567
716
|
let score = 0;
|
|
568
|
-
for (const term of
|
|
569
|
-
|
|
570
|
-
|
|
717
|
+
for (const [term, queryWeight] of queryTerms) {
|
|
718
|
+
let weightedFrequency = 0;
|
|
719
|
+
for (const field of SEARCH_FIELDS) {
|
|
720
|
+
const count = doc.fields[field].counts.get(term);
|
|
721
|
+
if (!count)
|
|
722
|
+
continue;
|
|
723
|
+
weightedFrequency +=
|
|
724
|
+
(FIELD_WEIGHTS[field] * count) /
|
|
725
|
+
(1 -
|
|
726
|
+
BM25_B +
|
|
727
|
+
(BM25_B * doc.fields[field].length) / averageLength[field]);
|
|
728
|
+
}
|
|
729
|
+
if (weightedFrequency === 0)
|
|
571
730
|
continue;
|
|
572
731
|
score +=
|
|
573
|
-
|
|
732
|
+
queryWeight *
|
|
733
|
+
(idf.get(term) ?? 0) *
|
|
734
|
+
((weightedFrequency * (BM25_K1 + 1)) / (weightedFrequency + BM25_K1));
|
|
574
735
|
}
|
|
575
736
|
if (score <= 0)
|
|
576
737
|
return [];
|
|
577
|
-
if (
|
|
738
|
+
if (queryTokens.length > 1 &&
|
|
739
|
+
containsTokenRun(tokenize(candidate.name), queryTokens)) {
|
|
578
740
|
score += NAME_PHRASE_BONUS;
|
|
579
741
|
}
|
|
742
|
+
score *= doc.prior;
|
|
580
743
|
return [{ ...candidate, score: Math.round(score * 100) / 100 }];
|
|
581
744
|
});
|
|
582
745
|
}
|
|
746
|
+
// Whole tokens in order, so "view email" never matches inside "preview-email"
|
|
747
|
+
// and "archive email" never matches inside "unarchive-email".
|
|
748
|
+
function containsTokenRun(tokens, run) {
|
|
749
|
+
for (let start = 0; start + run.length <= tokens.length; start++) {
|
|
750
|
+
if (run.every((token, offset) => tokens[start + offset] === token)) {
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
583
756
|
// Suggestions are never loaded, so a query with no exact term still points the
|
|
584
757
|
// model at real names instead of an empty result.
|
|
585
758
|
function suggestCloseTools(docs, queryTokens) {
|
|
@@ -589,8 +762,13 @@ function suggestCloseTools(docs, queryTokens) {
|
|
|
589
762
|
if (prefixes.length === 0)
|
|
590
763
|
return [];
|
|
591
764
|
return docs
|
|
592
|
-
.map(({ candidate }) => {
|
|
593
|
-
const terms =
|
|
765
|
+
.map(({ candidate, fields }) => {
|
|
766
|
+
const terms = [
|
|
767
|
+
...fields.name.counts.keys(),
|
|
768
|
+
...fields.hints.counts.keys(),
|
|
769
|
+
...fields.summary.counts.keys(),
|
|
770
|
+
...fields.details.counts.keys(),
|
|
771
|
+
];
|
|
594
772
|
const overlap = prefixes.filter((prefix) => terms.some((term) => term.startsWith(prefix))).length;
|
|
595
773
|
return { candidate, overlap };
|
|
596
774
|
})
|
|
@@ -603,45 +781,79 @@ function suggestCloseTools(docs, queryTokens) {
|
|
|
603
781
|
description: truncate(candidate.description, MAX_SUGGESTION_DESCRIPTION_CHARS),
|
|
604
782
|
}));
|
|
605
783
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
"
|
|
611
|
-
"
|
|
612
|
-
"
|
|
613
|
-
"
|
|
614
|
-
"
|
|
615
|
-
"
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
"on",
|
|
622
|
-
"or",
|
|
623
|
-
"that",
|
|
624
|
-
"the",
|
|
625
|
-
"this",
|
|
626
|
-
"to",
|
|
627
|
-
"with",
|
|
628
|
-
]);
|
|
784
|
+
// Function words carry no capability, and conversational requests are full of
|
|
785
|
+
// them ("can you show me what I have"). Indexed text drops them too, so a
|
|
786
|
+
// description's "you can" never matches a request's.
|
|
787
|
+
const STOP_WORDS = new Set(("a about after again also am an and any are as at be been before being " +
|
|
788
|
+
"but by can could did do does doing done for from had has have having " +
|
|
789
|
+
"he her here hers him his how i if in into is it its just let lets me " +
|
|
790
|
+
"mine my myself no not now of off on once only or our ours please she " +
|
|
791
|
+
"should so some such than thank thanks that the their theirs them then " +
|
|
792
|
+
"there these they this those to too up us very want was we were what " +
|
|
793
|
+
"when where which while who whom why will with would you your yours").split(" "));
|
|
794
|
+
// Light, inflection-only stemming, the same on both sides of the match:
|
|
795
|
+
// plurals, "-ed" and "-ing" (undoubling "running" to "run"), and a final
|
|
796
|
+
// "-e", so "archived", "archiving" and "archive" meet at "archiv" and "saved"
|
|
797
|
+
// meets "save". Full Porter stemming was measured on the benchmark and lost a
|
|
798
|
+
// held-out case: it folds derivations together ("engineering" into "engine").
|
|
629
799
|
function stem(term) {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
if (
|
|
635
|
-
|
|
636
|
-
|
|
800
|
+
const protectedTerm = PROTECTED_TERMS.get(term);
|
|
801
|
+
if (protectedTerm)
|
|
802
|
+
return protectedTerm;
|
|
803
|
+
let stemmed = term;
|
|
804
|
+
if (stemmed.length > 4 && stemmed.endsWith("ies")) {
|
|
805
|
+
stemmed = `${stemmed.slice(0, -3)}y`;
|
|
806
|
+
}
|
|
807
|
+
else if (stemmed.length > 4 && /(ches|shes|sses|xes|zes)$/.test(stemmed)) {
|
|
808
|
+
stemmed = stemmed.slice(0, -2);
|
|
809
|
+
}
|
|
810
|
+
else if (stemmed.length > 3 &&
|
|
811
|
+
stemmed.endsWith("s") &&
|
|
812
|
+
!/(ss|us|is)$/.test(stemmed)) {
|
|
813
|
+
stemmed = stemmed.slice(0, -1);
|
|
814
|
+
}
|
|
815
|
+
if (/^[aeiou][^aeiouy](?:ing|ed)$/u.test(stemmed)) {
|
|
816
|
+
// "using" and "used" lost the "e" of "use", as "owing" did of "owe".
|
|
817
|
+
stemmed = `${stemmed.slice(0, 2)}e`;
|
|
818
|
+
}
|
|
819
|
+
else if (stemmed.length > 5 && stemmed.endsWith("ing")) {
|
|
820
|
+
stemmed = undoubleFinalConsonant(stemmed.slice(0, -3));
|
|
821
|
+
}
|
|
822
|
+
else if (stemmed.length > 4 && stemmed.endsWith("ied")) {
|
|
823
|
+
stemmed = `${stemmed.slice(0, -3)}y`;
|
|
824
|
+
}
|
|
825
|
+
else if (stemmed.length > 4 && stemmed.endsWith("ed")) {
|
|
826
|
+
stemmed = undoubleFinalConsonant(stemmed.slice(0, -2));
|
|
827
|
+
}
|
|
828
|
+
if (stemmed.length >= 4 && stemmed.endsWith("e")) {
|
|
829
|
+
stemmed = stemmed.slice(0, -1);
|
|
830
|
+
}
|
|
831
|
+
return stemmed;
|
|
832
|
+
}
|
|
833
|
+
// "running" and "stopped" double the consonant before the suffix, so their
|
|
834
|
+
// stems must drop it to meet "run" and "stop". Base words that end in a double
|
|
835
|
+
// letter ("fill", "dress", "buzz", "stuff") keep it, as do stems that would
|
|
836
|
+
// shrink to two letters ("adding" stays "add"). A long "-ell" stem is the
|
|
837
|
+
// British doubling of "cancelled" and "labelled"; "spell" and "sell" are short.
|
|
838
|
+
function undoubleFinalConsonant(stemmed) {
|
|
839
|
+
return (stemmed.length >= 4 && /([bcdgkmnprt])\1$/.test(stemmed)) ||
|
|
840
|
+
(stemmed.length >= 6 && stemmed.endsWith("ell"))
|
|
841
|
+
? stemmed.slice(0, -1)
|
|
842
|
+
: stemmed;
|
|
637
843
|
}
|
|
638
844
|
function searchTerms(value) {
|
|
639
845
|
return value
|
|
640
|
-
.
|
|
641
|
-
.
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
846
|
+
.split(/[^A-Za-z0-9]+/)
|
|
847
|
+
.flatMap((word) => {
|
|
848
|
+
const parts = word
|
|
849
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
850
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
851
|
+
.toLowerCase()
|
|
852
|
+
.split(" ");
|
|
853
|
+
// Users type "PowerPoint" and "HubSpot" as one word, too.
|
|
854
|
+
return parts.length > 1 ? [...parts, word.toLowerCase()] : parts;
|
|
855
|
+
})
|
|
856
|
+
.filter((term) => (term.length > 1 || /\d/.test(term)) && !STOP_WORDS.has(term))
|
|
645
857
|
.map(stem);
|
|
646
858
|
}
|
|
647
859
|
function tokenize(value) {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
|
-
"@agent-native/agentkit": "0.
|
|
2
|
+
"@agent-native/agentkit": "0.225.0-nightly-20261105110131",
|
|
3
3
|
"@agent-native/code-agents-ui": "0.1.75",
|
|
4
|
-
"@agent-native/core": "0.
|
|
5
|
-
"@agent-native/core-corpus": "0.
|
|
4
|
+
"@agent-native/core": "0.225.0-nightly-20261105110131",
|
|
5
|
+
"@agent-native/core-corpus": "0.225.0-nightly-20261105110131",
|
|
6
6
|
"@agent-native/creative-context": "0.8.65",
|
|
7
|
-
"@agent-native/dispatch": "0.42.
|
|
7
|
+
"@agent-native/dispatch": "0.42.1-nightly-20261105110131",
|
|
8
8
|
"@agent-native/embedding": "0.2.0",
|
|
9
9
|
"@agent-native/migrate": "0.0.7",
|
|
10
10
|
"@agent-native/otel": "0.1.32",
|
|
11
11
|
"@agent-native/pinpoint": "0.1.108",
|
|
12
12
|
"@agent-native/recap-cli": "0.5.96",
|
|
13
|
-
"@agent-native/scheduling": "0.2.
|
|
13
|
+
"@agent-native/scheduling": "0.2.53-nightly-20261105110131",
|
|
14
14
|
"@agent-native/shared-app-config": "0.1.2",
|
|
15
|
-
"@agent-native/skills": "0.4.
|
|
16
|
-
"@agent-native/toolkit": "0.
|
|
15
|
+
"@agent-native/skills": "0.4.1-nightly-20261105110131",
|
|
16
|
+
"@agent-native/toolkit": "0.225.0-nightly-20261105110131"
|
|
17
17
|
}
|
package/dist/mcp-client/index.js
CHANGED
|
@@ -44,6 +44,27 @@ export function syncMcpActionEntries(manager, target, options = {}) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* The upstream server's hints, so `tool-search` marks a destructive tool
|
|
49
|
+
* whatever it is called. Hints the server leaves out take the MCP spec's
|
|
50
|
+
* defaults: "destructiveHint: If true, the tool may perform destructive
|
|
51
|
+
* updates to its environment. ... Default: true", meaningful only when the
|
|
52
|
+
* tool is not read-only. Our own actions do not get this default.
|
|
53
|
+
*/
|
|
54
|
+
function upstreamMcpAnnotations(tool) {
|
|
55
|
+
const hints = tool.annotations ?? {};
|
|
56
|
+
const readOnlyHint = hints.readOnlyHint === true;
|
|
57
|
+
return {
|
|
58
|
+
mcpAnnotations: {
|
|
59
|
+
readOnlyHint,
|
|
60
|
+
destructiveHint: !readOnlyHint && hints.destructiveHint !== false,
|
|
61
|
+
openWorldHint: hints.openWorldHint !== false,
|
|
62
|
+
...(typeof hints.idempotentHint === "boolean"
|
|
63
|
+
? { idempotentHint: hints.idempotentHint }
|
|
64
|
+
: {}),
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
47
68
|
function mcpToolToActionEntry(manager, tool, options = {}) {
|
|
48
69
|
const { authorize, ...resolvedEntry } = options.resolveActionEntry?.(tool) ?? {};
|
|
49
70
|
return {
|
|
@@ -58,6 +79,7 @@ function mcpToolToActionEntry(manager, tool, options = {}) {
|
|
|
58
79
|
description: "Plan mode allows MCP calls classified as read-only from annotations, operation names, and runtime arguments.",
|
|
59
80
|
},
|
|
60
81
|
...(tool.annotations?.readOnlyHint === true ? { readOnly: true } : {}),
|
|
82
|
+
...upstreamMcpAnnotations(tool),
|
|
61
83
|
...resolvedEntry,
|
|
62
84
|
run: async (args, context) => {
|
|
63
85
|
// Defense-in-depth: even if a cross-scope MCP tool somehow makes it
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import nodePath from "node:path";
|
|
2
2
|
import "../authorization/check-action.js";
|
|
3
|
+
import { isActionDefinitionError, namedActionDefinitionError, } from "../action.js";
|
|
3
4
|
import { CORE_ACTION_GROUPS } from "../framework-tools.js";
|
|
4
5
|
import { frameworkMcpTool } from "../mcp/framework-tools.js";
|
|
5
6
|
import { normalizeShellArgs, serializeCliArgs } from "../scripts/parse-args.js";
|
|
@@ -167,7 +168,7 @@ async function loadActionsIntoRegistry(actionsDir, registry, skipExisting) {
|
|
|
167
168
|
tool: mod.tool,
|
|
168
169
|
run: mod.run,
|
|
169
170
|
...(mod.http !== undefined ? { http: mod.http } : {}),
|
|
170
|
-
...preserveActionFlags(mod),
|
|
171
|
+
...preserveActionFlags(mod, name),
|
|
171
172
|
};
|
|
172
173
|
}
|
|
173
174
|
else if (mod.default &&
|
|
@@ -178,7 +179,7 @@ async function loadActionsIntoRegistry(actionsDir, registry, skipExisting) {
|
|
|
178
179
|
tool: mod.default.tool,
|
|
179
180
|
run: mod.default.run,
|
|
180
181
|
...(mod.default.http !== undefined ? { http: mod.default.http } : {}),
|
|
181
|
-
...preserveActionFlags(mod.default),
|
|
182
|
+
...preserveActionFlags(mod.default, name),
|
|
182
183
|
};
|
|
183
184
|
}
|
|
184
185
|
else if (typeof mod.default === "function") {
|
|
@@ -186,6 +187,11 @@ async function loadActionsIntoRegistry(actionsDir, registry, skipExisting) {
|
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
catch (err) {
|
|
190
|
+
// A module that imports but defines its action wrongly is a bug in the
|
|
191
|
+
// action, not a CLI script: fail the load instead of skipping it.
|
|
192
|
+
if (isActionDefinitionError(err)) {
|
|
193
|
+
throw namedActionDefinitionError(err, name);
|
|
194
|
+
}
|
|
189
195
|
const msg = err instanceof Error ? (err.stack ?? err.message) : String(err);
|
|
190
196
|
console.warn(`[action-discovery] Skipped "${file}" — failed to import. If this is an ` +
|
|
191
197
|
`agent action (not a CLI script), it will be missing from the agent's tools:\n${msg}`);
|
|
@@ -203,7 +209,7 @@ export function loadActionsFromStaticRegistry(modules) {
|
|
|
203
209
|
tool: mod.tool,
|
|
204
210
|
run: mod.run,
|
|
205
211
|
...(mod.http !== undefined ? { http: mod.http } : {}),
|
|
206
|
-
...preserveActionFlags(mod),
|
|
212
|
+
...preserveActionFlags(mod, name),
|
|
207
213
|
};
|
|
208
214
|
continue;
|
|
209
215
|
}
|
|
@@ -216,7 +222,7 @@ export function loadActionsFromStaticRegistry(modules) {
|
|
|
216
222
|
tool: def.tool,
|
|
217
223
|
run: def.run,
|
|
218
224
|
...(def.http !== undefined ? { http: def.http } : {}),
|
|
219
|
-
...preserveActionFlags(def),
|
|
225
|
+
...preserveActionFlags(def, name),
|
|
220
226
|
};
|
|
221
227
|
continue;
|
|
222
228
|
}
|
|
@@ -233,6 +239,8 @@ export async function autoDiscoverActions(from) {
|
|
|
233
239
|
await loadActionsIntoRegistry(actionsDir, registry, false);
|
|
234
240
|
}
|
|
235
241
|
catch (err) {
|
|
242
|
+
if (isActionDefinitionError(err))
|
|
243
|
+
throw err;
|
|
236
244
|
console.warn(`[autoDiscoverActions] Could not read actions directory: ${actionsDir} — ${err?.message}`);
|
|
237
245
|
}
|
|
238
246
|
if (Object.keys(registry).length === 0 && from) {
|
|
@@ -253,7 +261,11 @@ export async function autoDiscoverActions(from) {
|
|
|
253
261
|
`Consider switching to loadActionsFromStaticRegistry(actionsRegistry) for production reliability.`);
|
|
254
262
|
}
|
|
255
263
|
}
|
|
256
|
-
catch {
|
|
264
|
+
catch (error) {
|
|
265
|
+
// An action in the registry that defines itself wrongly is not "no
|
|
266
|
+
// registry": the app would boot with no template tools at all.
|
|
267
|
+
if (isActionDefinitionError(error))
|
|
268
|
+
throw error;
|
|
257
269
|
// No generated registry available — registry stays empty.
|
|
258
270
|
}
|
|
259
271
|
}
|
|
@@ -271,13 +283,17 @@ export async function autoDiscoverActions(from) {
|
|
|
271
283
|
await loadActionsIntoRegistry(ws.actionsDir, registry, true);
|
|
272
284
|
}
|
|
273
285
|
}
|
|
274
|
-
catch {
|
|
286
|
+
catch (error) {
|
|
287
|
+
if (isActionDefinitionError(error))
|
|
288
|
+
throw error;
|
|
275
289
|
// workspace-core discovery unavailable (e.g. edge runtime) — skip.
|
|
276
290
|
}
|
|
277
291
|
try {
|
|
278
292
|
await mergeCoreSharingActions(registry);
|
|
279
293
|
}
|
|
280
|
-
catch {
|
|
294
|
+
catch (error) {
|
|
295
|
+
if (isActionDefinitionError(error))
|
|
296
|
+
throw error;
|
|
281
297
|
// Ignore — templates without sharing still work.
|
|
282
298
|
}
|
|
283
299
|
return registry;
|
|
@@ -882,14 +898,17 @@ export async function mergeCoreSharingActions(registry) {
|
|
|
882
898
|
// mcpApp) plus readOnly/parallelSafe/dedupe. Without this, the sharing
|
|
883
899
|
// actions' `toolCallable: false` (audit-H5) is dropped and the
|
|
884
900
|
// tools-iframe bridge 403 in action-routes.ts never fires.
|
|
885
|
-
...preserveActionFlags(def),
|
|
901
|
+
...preserveActionFlags(def, name),
|
|
886
902
|
...(CORE_ACTION_GROUPS[name]
|
|
887
903
|
? { frameworkGroup: CORE_ACTION_GROUPS[name] }
|
|
888
904
|
: {}),
|
|
889
905
|
});
|
|
890
906
|
}
|
|
891
907
|
}
|
|
892
|
-
catch {
|
|
908
|
+
catch (error) {
|
|
909
|
+
if (isActionDefinitionError(error)) {
|
|
910
|
+
throw namedActionDefinitionError(error, name);
|
|
911
|
+
}
|
|
893
912
|
// Skip any sharing action that fails to import.
|
|
894
913
|
}
|
|
895
914
|
}
|
|
@@ -8,6 +8,7 @@ import { hasConfiguredA2ASecret, isLoopbackAddress, isTrustedLocalRuntime, } fro
|
|
|
8
8
|
import { sanitizeA2ACorrelationId, sanitizeA2ACorrelationMetadata, } from "../a2a/correlation.js";
|
|
9
9
|
import { a2aFilePartsToAttachments } from "../a2a/file-attachments.js";
|
|
10
10
|
import { createA2AApproval, a2aTaskLeaseThreadId, A2A_TASK_CLAIM_LOST_ERROR_CODE, updateTaskStatusMessage, } from "../a2a/task-store.js";
|
|
11
|
+
import { ActionDefinitionError, isActionDefinitionError } from "../action.js";
|
|
11
12
|
import { clientAbortReason, EXECUTION_LEASE_LOST_ABORT_REASON, } from "../agent/abort-reasons.js";
|
|
12
13
|
import { canUpdateAgentAppModelDefaultSettings, normalizeAgentAppModelDefaultAppId, readAgentAppModelDefaultSettings, resetAgentAppModelDefaultSettings, writeAgentAppModelDefaultSettings, } from "../agent/app-model-defaults.js";
|
|
13
14
|
import { approvalKeyWithArguments } from "../agent/approval-key.js";
|
|
@@ -77,7 +78,7 @@ import { AGENT_TEAM_PROCESS_RUN_PATH, getCurrentDelegationDepth, processAgentTea
|
|
|
77
78
|
import { getSession, isLoopbackRequest, registerAuthPublicPaths, } from "./auth.js";
|
|
78
79
|
import { captureError } from "./capture-error.js";
|
|
79
80
|
import { chatTitleRequestFromBody, generateChatTitle } from "./chat-title.js";
|
|
80
|
-
import {
|
|
81
|
+
import { discoveredActionMcpAnnotations, importDevActionEntry, } from "./filesystem-action-entry.js";
|
|
81
82
|
import { getH3App, markDefaultPluginProvided, trackPluginInit, } from "./framework-request-handler.js";
|
|
82
83
|
import { publicFrameworkPath } from "./framework-route-prefix.js";
|
|
83
84
|
import { getOrigin } from "./google-oauth.js";
|
|
@@ -1078,7 +1079,9 @@ export function createAgentChatPlugin(options) {
|
|
|
1078
1079
|
const { autoDiscoverActions } = await import("./action-discovery.js");
|
|
1079
1080
|
templateScriptsAll = await autoDiscoverActions("auto");
|
|
1080
1081
|
}
|
|
1081
|
-
catch {
|
|
1082
|
+
catch (error) {
|
|
1083
|
+
if (isActionDefinitionError(error))
|
|
1084
|
+
throw error;
|
|
1082
1085
|
// Filesystem discovery unavailable (serverless bundle) — skip.
|
|
1083
1086
|
}
|
|
1084
1087
|
}
|
|
@@ -1169,6 +1172,7 @@ export function createAgentChatPlugin(options) {
|
|
|
1169
1172
|
// Auto-discover template action files and register as bash-based tools.
|
|
1170
1173
|
// This ensures templates without a custom agent-chat plugin (e.g., analytics)
|
|
1171
1174
|
// still have their domain actions available as tools.
|
|
1175
|
+
const definitionErrors = [];
|
|
1172
1176
|
try {
|
|
1173
1177
|
const pathMod = await import("path");
|
|
1174
1178
|
const cwd = process.cwd();
|
|
@@ -1184,17 +1188,22 @@ export function createAgentChatPlugin(options) {
|
|
|
1184
1188
|
// run function (not a shell wrapper). This makes HTTP endpoints
|
|
1185
1189
|
// work correctly. Only fall back to shell wrapper if the import
|
|
1186
1190
|
// fails (e.g., CLI-style scripts that throw at top level).
|
|
1191
|
+
// A definition error is rethrown below, past the catch that
|
|
1192
|
+
// skips unreadable discovery, so it fails the load instead.
|
|
1187
1193
|
try {
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
discoveredActionsAll[name] = filesystemActionEntry(def);
|
|
1194
|
+
const entry = await importDevActionEntry(filePath, name);
|
|
1195
|
+
if (entry) {
|
|
1196
|
+
discoveredActionsAll[name] = entry;
|
|
1192
1197
|
continue;
|
|
1193
1198
|
}
|
|
1194
1199
|
}
|
|
1195
|
-
catch {
|
|
1196
|
-
|
|
1197
|
-
|
|
1200
|
+
catch (error) {
|
|
1201
|
+
if (isActionDefinitionError(error)) {
|
|
1202
|
+
definitionErrors.push(error);
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1205
|
+
// Anything else falls through to the shell wrapper, as an
|
|
1206
|
+
// unimportable CLI script does.
|
|
1198
1207
|
}
|
|
1199
1208
|
// Static-parse the source for `http`, `agentTool: false`,
|
|
1200
1209
|
// `needsApproval`, `persistInBrowser`, `uiOnly` and
|
|
@@ -1290,6 +1299,9 @@ export function createAgentChatPlugin(options) {
|
|
|
1290
1299
|
console.log(`[agent-chat] Auto-discovered ${Object.keys(discoveredActionsAll).length} action(s): ${Object.keys(discoveredActionsAll).join(", ")}`);
|
|
1291
1300
|
}
|
|
1292
1301
|
catch { }
|
|
1302
|
+
if (definitionErrors.length > 0) {
|
|
1303
|
+
throw new ActionDefinitionError(definitionErrors.map((error) => error.message).join("\n"));
|
|
1304
|
+
}
|
|
1293
1305
|
}
|
|
1294
1306
|
// Agent-facing views of the discovered actions: actions that opted out of
|
|
1295
1307
|
// agent exposure with `agentTool: false` are dropped from every tool
|
|
@@ -14,7 +14,7 @@ export declare function discoveredActionMcpAnnotations(readOnly: boolean): Reado
|
|
|
14
14
|
* validates, asks for approval, and checks access the same way however it was
|
|
15
15
|
* loaded. Each flag is kept only when its value has the right shape.
|
|
16
16
|
*/
|
|
17
|
-
export declare function preserveActionFlags(entry: Record<string, unknown
|
|
17
|
+
export declare function preserveActionFlags(entry: Record<string, unknown>, actionName?: string): Partial<ActionEntry>;
|
|
18
18
|
/**
|
|
19
19
|
* The object a loaded action module defines its action on. Named `tool` and
|
|
20
20
|
* `run` exports come first, as the built registry and the directory loader
|
|
@@ -28,3 +28,11 @@ export declare function actionDefinition(mod: Record<string, unknown>): Record<s
|
|
|
28
28
|
* annotations when the module declares no valid ones.
|
|
29
29
|
*/
|
|
30
30
|
export declare function filesystemActionEntry(def: Record<string, any>): ActionEntry;
|
|
31
|
+
/**
|
|
32
|
+
* The real entry for a dev action file, or `null` when the file should run as
|
|
33
|
+
* a CLI-script wrapper: it does not import (top-level CLI code, syntax Node
|
|
34
|
+
* cannot parse) or defines no action. A file that imports but defines its
|
|
35
|
+
* action wrongly throws an `ActionDefinitionError` naming it instead, so an
|
|
36
|
+
* invalid definition never silently changes how the action runs.
|
|
37
|
+
*/
|
|
38
|
+
export declare function importDevActionEntry(filePath: string, actionName: string): Promise<ActionEntry | null>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isMcpReadOnlyTool } from "../action-call-classification.js";
|
|
2
|
-
import { ACTION_EXECUTION_KEY } from "../action.js";
|
|
2
|
+
import { ACTION_EXECUTION_KEY, isActionDefinitionError, namedActionDefinitionError, validateSearchHints, } from "../action.js";
|
|
3
3
|
import { carryFrameworkMcpTool } from "../mcp/framework-tools.js";
|
|
4
4
|
// Without the key an app wrapper around `run` cannot be recognized as the same
|
|
5
5
|
// action, and every validation failure reads as an unknown write.
|
|
@@ -44,7 +44,7 @@ function isMcpAnnotations(value) {
|
|
|
44
44
|
* validates, asks for approval, and checks access the same way however it was
|
|
45
45
|
* loaded. Each flag is kept only when its value has the right shape.
|
|
46
46
|
*/
|
|
47
|
-
export function preserveActionFlags(entry) {
|
|
47
|
+
export function preserveActionFlags(entry, actionName) {
|
|
48
48
|
const out = {};
|
|
49
49
|
carryFrameworkMcpTool(entry, out);
|
|
50
50
|
if (entry.schema &&
|
|
@@ -73,6 +73,11 @@ export function preserveActionFlags(entry) {
|
|
|
73
73
|
if (typeof entry.deferLoading === "boolean") {
|
|
74
74
|
out.deferLoading = entry.deferLoading;
|
|
75
75
|
}
|
|
76
|
+
// Not dropped like the flags above: a malformed list would silently leave
|
|
77
|
+
// the action harder to find, so loading it fails instead.
|
|
78
|
+
const searchHints = validateSearchHints(entry.searchHints, actionName);
|
|
79
|
+
if (searchHints)
|
|
80
|
+
out.searchHints = searchHints;
|
|
76
81
|
if (typeof entry.eagerInputStreaming === "boolean") {
|
|
77
82
|
out.eagerInputStreaming = entry.eagerInputStreaming;
|
|
78
83
|
}
|
|
@@ -204,3 +209,22 @@ export function filesystemActionEntry(def) {
|
|
|
204
209
|
carryExecutionKey(def, out);
|
|
205
210
|
return out;
|
|
206
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* The real entry for a dev action file, or `null` when the file should run as
|
|
214
|
+
* a CLI-script wrapper: it does not import (top-level CLI code, syntax Node
|
|
215
|
+
* cannot parse) or defines no action. A file that imports but defines its
|
|
216
|
+
* action wrongly throws an `ActionDefinitionError` naming it instead, so an
|
|
217
|
+
* invalid definition never silently changes how the action runs.
|
|
218
|
+
*/
|
|
219
|
+
export async function importDevActionEntry(filePath, actionName) {
|
|
220
|
+
try {
|
|
221
|
+
const def = actionDefinition(await import(/* @vite-ignore */ filePath));
|
|
222
|
+
return def ? filesystemActionEntry(def) : null;
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
if (isActionDefinitionError(error)) {
|
|
226
|
+
throw namedActionDefinitionError(error, actionName);
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -66,7 +66,7 @@ ${sharedRule8(examples, options)}
|
|
|
66
66
|
${SHARED_RULE_9}
|
|
67
67
|
10. **Native widgets** — For table/chart/graph/report requests, prefer actions labeled \`Native chat widget\`; use \`render-data-widget\` for already-summarized data (≤50 rows) instead of markdown tables. Above that, give the total plus the top rows — never retype a full result set as widget arguments.
|
|
68
68
|
11. **Downloadable files** — Deliver files in chat, never just a path.
|
|
69
|
-
12. **Your tool list is not the whole surface** — Most app actions and connected MCP tools load on demand; find
|
|
69
|
+
12. **Your tool list is not the whole surface** — Most app actions and connected MCP tools load on demand; find names in the Tool index or search \`tool-search\` for the task in plain words before concluding a capability doesn't exist.
|
|
70
70
|
${sharedRule13(options)}
|
|
71
71
|
${SHARED_RULE_14}
|
|
72
72
|
${SHARED_RULE_15}
|
|
@@ -95,7 +95,7 @@ ${SHARED_RULE_9}
|
|
|
95
95
|
${ATTACHMENT_READ_RULE}
|
|
96
96
|
10. **Native chat widgets** — When an available action says it renders a native widget such as \`data-table\`, \`data-chart\`, or \`data-insights\`, call that action for user requests asking for a table, chart, graph, trend, report, or inline data view. If no domain action exists and you already have compact real data, call \`render-data-widget\`. Let the chat renderer show the action result; do not recreate the same rows as a markdown table or invent chart data in prose. Add only a short human summary or next-step link around the widget. Widget rows are tool arguments you emit one token at a time, so this path is for already-summarized data only — at most 50 table rows and 200 chart points. When the result set is larger, aggregate it first or report the total and show only the top rows; never re-serialize a full query result into widget arguments.
|
|
97
97
|
11. **Downloadable files** — When the user asks you to create or export a file, deliver it in the same chat turn. For compact tabular results, prefer \`render-data-widget\` so the user gets the native Download CSV action without a second stored copy. For a complete or durable file written through \`workspaceWrite\`, use a normal non-\`scratch/\` path and then call \`show-workspace-file\` with that path so chat renders a direct download card. Never finish by giving filesystem paths or telling the user to navigate elsewhere to find the file.
|
|
98
|
-
12. **Your tool list is not the whole surface** — Most app actions and connected MCP tools load on demand; find
|
|
98
|
+
12. **Your tool list is not the whole surface** — Most app actions and connected MCP tools load on demand; find names in the Tool index or search \`tool-search\` for the task in plain words before concluding a capability doesn't exist.
|
|
99
99
|
${sharedRule13(options)}
|
|
100
100
|
${SHARED_RULE_14}
|
|
101
101
|
${SHARED_RULE_15}
|
|
@@ -66,7 +66,7 @@ Every agent-exposed action is a tool in the model's context window; more tools d
|
|
|
66
66
|
|
|
67
67
|
## Key Actions — One Index, Every Surface
|
|
68
68
|
|
|
69
|
-
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`.
|
|
69
|
+
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`. When users name an action's job in words its name and description lack ("auto-archive rule", "canned reply"), add those words as `searchHints`: `tool-search` ranks them just below the name and never shows them to the model.
|
|
70
70
|
|
|
71
71
|
When MCP is enabled, its `Key tools` line is generated from `mcp.keyToolNames ?? initialToolNames` and filtered to the actions served on that surface. A configured `mcp.keyToolNames` overrides the advertised MCP/WebMCP key list; it does not change the in-app agent's first-turn list. Keep the advertised subset consistent with the primary actions in the `AGENTS.md` table and the external catalog; the table may also document other in-app actions. Do not hand-write another action list in `mcp.instructions`, a skill, or an external skill; describe when to use actions there.
|
|
72
72
|
|
|
@@ -66,7 +66,7 @@ Every agent-exposed action is a tool in the model's context window; more tools d
|
|
|
66
66
|
|
|
67
67
|
## Key Actions — One Index, Every Surface
|
|
68
68
|
|
|
69
|
-
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`.
|
|
69
|
+
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`. When users name an action's job in words its name and description lack ("auto-archive rule", "canned reply"), add those words as `searchHints`: `tool-search` ranks them just below the name and never shows them to the model.
|
|
70
70
|
|
|
71
71
|
When MCP is enabled, its `Key tools` line is generated from `mcp.keyToolNames ?? initialToolNames` and filtered to the actions served on that surface. A configured `mcp.keyToolNames` overrides the advertised MCP/WebMCP key list; it does not change the in-app agent's first-turn list. Keep the advertised subset consistent with the primary actions in the `AGENTS.md` table and the external catalog; the table may also document other in-app actions. Do not hand-write another action list in `mcp.instructions`, a skill, or an external skill; describe when to use actions there.
|
|
72
72
|
|
|
@@ -66,7 +66,7 @@ Every agent-exposed action is a tool in the model's context window; more tools d
|
|
|
66
66
|
|
|
67
67
|
## Key Actions — One Index, Every Surface
|
|
68
68
|
|
|
69
|
-
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`.
|
|
69
|
+
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`. When users name an action's job in words its name and description lack ("auto-archive rule", "canned reply"), add those words as `searchHints`: `tool-search` ranks them just below the name and never shows them to the model.
|
|
70
70
|
|
|
71
71
|
When MCP is enabled, its `Key tools` line is generated from `mcp.keyToolNames ?? initialToolNames` and filtered to the actions served on that surface. A configured `mcp.keyToolNames` overrides the advertised MCP/WebMCP key list; it does not change the in-app agent's first-turn list. Keep the advertised subset consistent with the primary actions in the `AGENTS.md` table and the external catalog; the table may also document other in-app actions. Do not hand-write another action list in `mcp.instructions`, a skill, or an external skill; describe when to use actions there.
|
|
72
72
|
|
|
@@ -66,7 +66,7 @@ Every agent-exposed action is a tool in the model's context window; more tools d
|
|
|
66
66
|
|
|
67
67
|
## Key Actions — One Index, Every Surface
|
|
68
68
|
|
|
69
|
-
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`.
|
|
69
|
+
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`. When users name an action's job in words its name and description lack ("auto-archive rule", "canned reply"), add those words as `searchHints`: `tool-search` ranks them just below the name and never shows them to the model.
|
|
70
70
|
|
|
71
71
|
When MCP is enabled, its `Key tools` line is generated from `mcp.keyToolNames ?? initialToolNames` and filtered to the actions served on that surface. A configured `mcp.keyToolNames` overrides the advertised MCP/WebMCP key list; it does not change the in-app agent's first-turn list. Keep the advertised subset consistent with the primary actions in the `AGENTS.md` table and the external catalog; the table may also document other in-app actions. Do not hand-write another action list in `mcp.instructions`, a skill, or an external skill; describe when to use actions there.
|
|
72
72
|
|
|
@@ -446,6 +446,7 @@ Every action the agent can see takes up a slot in the model's tool list, and a l
|
|
|
446
446
|
- Before adding a new read action for every query or filter, consider a more general option first: the [provider API trio](/docs/template-dispatch-developers#calling-a-provider-directly) (`provider-api-catalog`, `provider-api-docs`, and `provider-api-request`) for provider data, or the dev `db-query` tool for app data.
|
|
447
447
|
- Mark UI-only or programmatic actions [`agentTool: false`](/docs/actions-access-control#agent-tool). They stay callable from the frontend or over HTTP, without taking a slot in the model's tool list.
|
|
448
448
|
- Mark the handful of actions the agent reaches for constantly `deferLoading: false`. Those are the schemas sent on the first model request; everything else loads on demand through `tool-search` when the agent needs it. As soon as one action opts out this way, every unmarked action is deferred, so mark the whole starter set at once rather than one action at a time. This is about what the first turn costs, not about access: a deferred action is callable the moment `tool-search` returns it. Deferred actions still appear by name in the prompt's tool index, grouped by the noun after the leading verb, so `verb-noun` names such as `list-dashboards` and `create-dashboard` are what let the agent find them. It is the per-action form of the plugin's `initialToolNames` array, and an app can use either.
|
|
449
|
+
- Give an action `searchHints` when users call its job something its name and description do not, such as `["auto-archive rule", "inbox rule"]` on an inbox-rules action. `tool-search` ranks these words just below the name, and they are never sent to the model, so they cost no context. Keep them short and factual (at most 12, each up to 60 characters), not a list of every request the action might serve.
|
|
449
450
|
- Delete or hide actions the UI no longer uses, instead of leaving them exposed to the model.
|
|
450
451
|
|
|
451
452
|
The repo includes an advisory helper, `node scripts/audit-template-actions.mjs [template ...]` (alias `pnpm actions:audit`). It scans a template's `actions/` folder and flags actions the UI may no longer use, along with groups of per-field actions that could be combined into one. It always exits with code 0 and never fails CI, and its heuristics are conservative, so treat its output as a suggestion, not an error.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.225.0-nightly-20261105110131",
|
|
4
4
|
"description": "The agentic application framework for building autonomous agents with intuitive UIs",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -760,8 +760,8 @@
|
|
|
760
760
|
"yaml": "^2.9.0",
|
|
761
761
|
"yjs": "^13.6.33",
|
|
762
762
|
"zod": "^4.3.6",
|
|
763
|
-
"@agent-native/
|
|
764
|
-
"@agent-native/
|
|
763
|
+
"@agent-native/agentkit": "^0.225.0-nightly-20261105110131",
|
|
764
|
+
"@agent-native/recap-cli": "^0.5.96"
|
|
765
765
|
},
|
|
766
766
|
"devDependencies": {
|
|
767
767
|
"@ai-sdk/anthropic": "^3.0.71",
|
|
@@ -833,7 +833,7 @@
|
|
|
833
833
|
"xlsx": "^0.18.5"
|
|
834
834
|
},
|
|
835
835
|
"peerDependencies": {
|
|
836
|
-
"@agent-native/core-corpus": "
|
|
836
|
+
"@agent-native/core-corpus": "0.225.0-nightly-20261105110131",
|
|
837
837
|
"@ai-sdk/anthropic": ">=3",
|
|
838
838
|
"@ai-sdk/cohere": ">=3",
|
|
839
839
|
"@ai-sdk/google": ">=3",
|
|
@@ -66,7 +66,7 @@ Every agent-exposed action is a tool in the model's context window; more tools d
|
|
|
66
66
|
|
|
67
67
|
## Key Actions — One Index, Every Surface
|
|
68
68
|
|
|
69
|
-
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`.
|
|
69
|
+
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`. When users name an action's job in words its name and description lack ("auto-archive rule", "canned reply"), add those words as `searchHints`: `tool-search` ranks them just below the name and never shows them to the model.
|
|
70
70
|
|
|
71
71
|
When MCP is enabled, its `Key tools` line is generated from `mcp.keyToolNames ?? initialToolNames` and filtered to the actions served on that surface. A configured `mcp.keyToolNames` overrides the advertised MCP/WebMCP key list; it does not change the in-app agent's first-turn list. Keep the advertised subset consistent with the primary actions in the `AGENTS.md` table and the external catalog; the table may also document other in-app actions. Do not hand-write another action list in `mcp.instructions`, a skill, or an external skill; describe when to use actions there.
|
|
72
72
|
|
|
@@ -66,7 +66,7 @@ Every agent-exposed action is a tool in the model's context window; more tools d
|
|
|
66
66
|
|
|
67
67
|
## Key Actions — One Index, Every Surface
|
|
68
68
|
|
|
69
|
-
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`.
|
|
69
|
+
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`. When users name an action's job in words its name and description lack ("auto-archive rule", "canned reply"), add those words as `searchHints`: `tool-search` ranks them just below the name and never shows them to the model.
|
|
70
70
|
|
|
71
71
|
When MCP is enabled, its `Key tools` line is generated from `mcp.keyToolNames ?? initialToolNames` and filtered to the actions served on that surface. A configured `mcp.keyToolNames` overrides the advertised MCP/WebMCP key list; it does not change the in-app agent's first-turn list. Keep the advertised subset consistent with the primary actions in the `AGENTS.md` table and the external catalog; the table may also document other in-app actions. Do not hand-write another action list in `mcp.instructions`, a skill, or an external skill; describe when to use actions there.
|
|
72
72
|
|
|
@@ -66,7 +66,7 @@ Every agent-exposed action is a tool in the model's context window; more tools d
|
|
|
66
66
|
|
|
67
67
|
## Key Actions — One Index, Every Surface
|
|
68
68
|
|
|
69
|
-
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`.
|
|
69
|
+
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`. When users name an action's job in words its name and description lack ("auto-archive rule", "canned reply"), add those words as `searchHints`: `tool-search` ranks them just below the name and never shows them to the model.
|
|
70
70
|
|
|
71
71
|
When MCP is enabled, its `Key tools` line is generated from `mcp.keyToolNames ?? initialToolNames` and filtered to the actions served on that surface. A configured `mcp.keyToolNames` overrides the advertised MCP/WebMCP key list; it does not change the in-app agent's first-turn list. Keep the advertised subset consistent with the primary actions in the `AGENTS.md` table and the external catalog; the table may also document other in-app actions. Do not hand-write another action list in `mcp.instructions`, a skill, or an external skill; describe when to use actions there.
|
|
72
72
|
|
|
@@ -66,7 +66,7 @@ Every agent-exposed action is a tool in the model's context window; more tools d
|
|
|
66
66
|
|
|
67
67
|
## Key Actions — One Index, Every Surface
|
|
68
68
|
|
|
69
|
-
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`.
|
|
69
|
+
Document the app's primary actions in the `AGENTS.md` action table. For first-turn loading, add those action names to the plugin's `initialToolNames` (`INITIAL_TOOL_NAMES` in some templates), or mark the full starter set `deferLoading: false` on the actions. When one action opts into per-action eager loading, unmarked actions are deferred; the remaining actions stay discoverable through `tool-search`. When users name an action's job in words its name and description lack ("auto-archive rule", "canned reply"), add those words as `searchHints`: `tool-search` ranks them just below the name and never shows them to the model.
|
|
70
70
|
|
|
71
71
|
When MCP is enabled, its `Key tools` line is generated from `mcp.keyToolNames ?? initialToolNames` and filtered to the actions served on that surface. A configured `mcp.keyToolNames` overrides the advertised MCP/WebMCP key list; it does not change the in-app agent's first-turn list. Keep the advertised subset consistent with the primary actions in the `AGENTS.md` table and the external catalog; the table may also document other in-app actions. Do not hand-write another action list in `mcp.instructions`, a skill, or an external skill; describe when to use actions there.
|
|
72
72
|
|