@anvia/cli 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/dist/chunk-ERCP4EIZ.js +607 -0
- package/dist/chunk-ERCP4EIZ.js.map +1 -0
- package/dist/cli.js +278 -74
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +54 -1
- package/dist/index.js +21 -3
- package/dist/skills/anvia-agent/SKILL.md +59 -0
- package/dist/skills/anvia-agent/references/agent-options.md +83 -0
- package/dist/skills/anvia-agent/references/providers.md +22 -0
- package/dist/skills/anvia-agent/references/teams.md +84 -0
- package/dist/skills/anvia-agent/references/tools.md +71 -0
- package/dist/skills/anvia-agent/scripts/check-agent.sh +99 -0
- package/dist/skills/anvia-channels/SKILL.md +59 -0
- package/dist/skills/anvia-channels/references/adapters.md +53 -0
- package/dist/skills/anvia-channels/references/channel-agent.md +52 -0
- package/dist/skills/anvia-channels/references/delivery.md +53 -0
- package/dist/skills/anvia-channels/scripts/check-channels.sh +79 -0
- package/dist/skills/anvia-chat/SKILL.md +61 -0
- package/dist/skills/anvia-chat/references/react-ui.md +104 -0
- package/dist/skills/anvia-chat/references/server-protocol.md +53 -0
- package/dist/skills/anvia-chat/references/transports-state.md +71 -0
- package/dist/skills/anvia-chat/scripts/check-chat-boundary.sh +70 -0
- package/dist/skills/anvia-evals/SKILL.md +51 -0
- package/dist/skills/anvia-evals/references/judges.md +61 -0
- package/dist/skills/anvia-evals/references/metrics.md +45 -0
- package/dist/skills/anvia-evals/references/running.md +62 -0
- package/dist/skills/anvia-evals/scripts/check-evals.sh +73 -0
- package/dist/skills/anvia-mcp/SKILL.md +49 -0
- package/dist/skills/anvia-mcp/references/clients.md +73 -0
- package/dist/skills/anvia-mcp/references/safety.md +25 -0
- package/dist/skills/anvia-mcp/scripts/check-mcp.sh +73 -0
- package/dist/skills/anvia-pipeline/SKILL.md +52 -0
- package/dist/skills/anvia-pipeline/references/agents-extract.md +62 -0
- package/dist/skills/anvia-pipeline/references/steps-compose.md +63 -0
- package/dist/skills/anvia-pipeline/scripts/check-pipeline.sh +69 -0
- package/dist/skills/anvia-rag/SKILL.md +50 -0
- package/dist/skills/anvia-rag/references/graph-rag.md +115 -0
- package/dist/skills/anvia-rag/references/pipeline.md +81 -0
- package/dist/skills/anvia-rag/references/rag-tool.md +43 -0
- package/dist/skills/anvia-rag/references/stores.md +68 -0
- package/dist/skills/anvia-rag/scripts/check-rag.sh +75 -0
- package/dist/skills/anvia-studio/SKILL.md +45 -0
- package/dist/skills/anvia-studio/references/inspect.md +33 -0
- package/dist/skills/anvia-studio/references/observe.md +34 -0
- package/dist/skills/anvia-studio/references/serve.md +65 -0
- package/dist/skills/anvia-studio/scripts/check-studio.sh +59 -0
- package/dist/skills/release-notes/SKILL.md +18 -0
- package/dist/skills/release-notes/references/style.md +6 -0
- package/dist/skills/release-notes/scripts/draft.sh +22 -0
- package/package.json +2 -2
- package/dist/chunk-IH4ZNWHB.js +0 -285
- package/dist/chunk-IH4ZNWHB.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,21 +1,39 @@
|
|
|
1
1
|
import {
|
|
2
2
|
addRegistryItem,
|
|
3
|
+
bundledSkillsDirectory,
|
|
3
4
|
closestRegistryItemName,
|
|
5
|
+
collectSkillFiles,
|
|
4
6
|
createRegistryItem,
|
|
7
|
+
initSkills,
|
|
5
8
|
initializeProject,
|
|
6
9
|
inspectInstalledItems,
|
|
10
|
+
inspectInstalledSkills,
|
|
7
11
|
isRegistryItemName,
|
|
12
|
+
isSkillsTarget,
|
|
8
13
|
registryItemNames,
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
skillNames,
|
|
15
|
+
skillsTargetDirectory,
|
|
16
|
+
skillsTargetNames,
|
|
17
|
+
updateInstalledItems,
|
|
18
|
+
updateSkills
|
|
19
|
+
} from "./chunk-ERCP4EIZ.js";
|
|
11
20
|
export {
|
|
12
21
|
addRegistryItem,
|
|
22
|
+
bundledSkillsDirectory,
|
|
13
23
|
closestRegistryItemName,
|
|
24
|
+
collectSkillFiles,
|
|
14
25
|
createRegistryItem,
|
|
26
|
+
initSkills,
|
|
15
27
|
initializeProject,
|
|
16
28
|
inspectInstalledItems,
|
|
29
|
+
inspectInstalledSkills,
|
|
17
30
|
isRegistryItemName,
|
|
31
|
+
isSkillsTarget,
|
|
18
32
|
registryItemNames,
|
|
19
|
-
|
|
33
|
+
skillNames,
|
|
34
|
+
skillsTargetDirectory,
|
|
35
|
+
skillsTargetNames,
|
|
36
|
+
updateInstalledItems,
|
|
37
|
+
updateSkills
|
|
20
38
|
};
|
|
21
39
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: anvia-agent
|
|
3
|
+
description: Build Anvia agents and tools — Agent options, tool definitions, approvals, memory, streaming, multi-agent teams, and provider wiring.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Anvia Agent Skill
|
|
7
|
+
|
|
8
|
+
Use this skill when the user wants to build or change an Anvia agent: defining
|
|
9
|
+
tools, configuring the `Agent`, handling approvals and streaming, adding memory,
|
|
10
|
+
composing specialists, or wiring a provider model.
|
|
11
|
+
|
|
12
|
+
## Process
|
|
13
|
+
|
|
14
|
+
1. Define tools first (`references/tools.md`) — zod schemas, approvals.
|
|
15
|
+
2. Create the `Agent` (`references/agent-options.md`) — smallest option set that works.
|
|
16
|
+
3. Wire the provider model (`references/providers.md`) — keep vendor SDKs in provider packages.
|
|
17
|
+
4. Compose specialists when needed (`references/teams.md`) — subagents as tools, or `AgentTeam`.
|
|
18
|
+
5. Run `scripts/check-agent.sh` from the app root before claiming done.
|
|
19
|
+
|
|
20
|
+
## Minimal slice
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { Agent } from "@anvia/core/agent";
|
|
24
|
+
import { createTool } from "@anvia/core/tool";
|
|
25
|
+
import { OpenAIClient } from "@anvia/openai";
|
|
26
|
+
import { z } from "zod";
|
|
27
|
+
|
|
28
|
+
const addTool = createTool({
|
|
29
|
+
name: "add",
|
|
30
|
+
description: "Add two numbers together.",
|
|
31
|
+
inputSchema: z.object({
|
|
32
|
+
x: z.number().describe("The first number."),
|
|
33
|
+
y: z.number().describe("The second number."),
|
|
34
|
+
}),
|
|
35
|
+
outputSchema: z.number(),
|
|
36
|
+
execute: (args) => args.x + args.y,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const client = new OpenAIClient({
|
|
40
|
+
baseUrl: process.env.OPENAI_BASEURL,
|
|
41
|
+
apiKey: process.env.OPENAI_API_KEY ?? "",
|
|
42
|
+
});
|
|
43
|
+
const agent = new Agent({
|
|
44
|
+
id: "agent",
|
|
45
|
+
model: client.completionModel({ modelId: "gpt-6-astra", api: "responses" }),
|
|
46
|
+
instructions: "You are a concise assistant. Use tools when useful.",
|
|
47
|
+
maxTurns: 2,
|
|
48
|
+
tools: [addTool],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const response = await agent.generate({ prompt: "What is 12 + 30? Use the add tool." });
|
|
52
|
+
if (response.type !== "response") throw new Error("Unexpected tool approval request.");
|
|
53
|
+
console.log(response.output);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Output
|
|
57
|
+
|
|
58
|
+
Prefer one agent, few tools, explicit zod schemas. Point to the relevant
|
|
59
|
+
reference file instead of pasting its contents into chat.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Agent Options
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
const agent = new Agent({
|
|
5
|
+
id: "agent", // required, stable across restarts
|
|
6
|
+
model, // required CompletionModel from a provider package
|
|
7
|
+
instructions: "...", // system guidance; keep it task-specific
|
|
8
|
+
tools: [...], // AnyTool | ProviderTool | ToolIndex
|
|
9
|
+
mcpServers: [...], // MCP servers (the only path for MCP tools)
|
|
10
|
+
skills: skillSet, // SkillSet from loadSkills() — Agent merges its tools
|
|
11
|
+
context: [...], // static Document or VectorContext entries
|
|
12
|
+
memory: { store }, // MemoryStore for session continuity
|
|
13
|
+
outputSchema: schema, // zod schema for structured final output
|
|
14
|
+
toolChoice, // constrain or force tool use
|
|
15
|
+
maxTurns, // bound runaway loops (e.g. 2-4 for demos)
|
|
16
|
+
middlewares: [...], // AgentMiddleware for cross-cutting tool behavior
|
|
17
|
+
guardrails, // policy input for safety constraints
|
|
18
|
+
temperature, // maxTokens, retries, controls, providerOptions as needed
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## generate vs stream
|
|
23
|
+
|
|
24
|
+
- `agent.generate({ prompt, session? })` — one shot. Returns `{ type: "response" }`,
|
|
25
|
+
`{ type: "interaction" }` (approval/question), or `{ type: "blocked" }` (guardrail
|
|
26
|
+
stop); never throws for those.
|
|
27
|
+
- `agent.stream({ prompt, toolConcurrency? })` — events: `tool_call`,
|
|
28
|
+
`tool_result`, `text_delta`, `response`. Use `toolConcurrency` when parallel
|
|
29
|
+
specialist calls are safe. Requires a streaming-capable model — it throws
|
|
30
|
+
otherwise, so fall back to `generate` for chat-only models.
|
|
31
|
+
|
|
32
|
+
## Memory
|
|
33
|
+
|
|
34
|
+
`memory: { store }` with a `MemoryStore` (`load` / `append` / `clear` over
|
|
35
|
+
`Message[]`), plus a `session` (`{ sessionId, userId }`) per call:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const agent = new Agent({ id: "agent", model, instructions: "...", memory: { store } });
|
|
39
|
+
await agent.generate({ prompt: "Remember my project is named Anvia.", session });
|
|
40
|
+
await agent.generate({ prompt: "What is my project named?", session });
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Compaction (`afterTokens` / `recentTokens` + token counter + compactor) is opt-in
|
|
44
|
+
for long sessions — add it when context growth is the problem, not upfront.
|
|
45
|
+
`agent.compactMemory({ session })` forces that compaction on demand.
|
|
46
|
+
|
|
47
|
+
## Multi-agent
|
|
48
|
+
|
|
49
|
+
Specialists route by their `name` + `description` text — both optional on
|
|
50
|
+
`Agent`, but give them explicitly (`asTool` falls back to a generic description
|
|
51
|
+
when omitted). `asTool` itself requires a tool `name` and an explicit
|
|
52
|
+
`suspension` policy:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const coordinator = new Agent({
|
|
56
|
+
id: "coordinator",
|
|
57
|
+
model,
|
|
58
|
+
instructions: "Coordinate specialist agents through tools. ...",
|
|
59
|
+
maxTurns: 4,
|
|
60
|
+
tools: [supportAgent.asTool({ name: "ask_support_agent", suspension: "reject" })],
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Keep the coordinator's instructions to routing + synthesis. Give each specialist
|
|
65
|
+
a short task built only from user facts. Prefer `stream` with `toolConcurrency`
|
|
66
|
+
for parallel delegation. When members must talk, wait, and spawn each other,
|
|
67
|
+
use `AgentTeam` (`references/teams.md`); for static teams/pipelines, check the
|
|
68
|
+
cookbook (`07_multi_agent`, `05_pipelines`) before inventing a new pattern.
|
|
69
|
+
|
|
70
|
+
## Skills wiring
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { loadSkills, skill } from "@anvia/core/skills";
|
|
74
|
+
|
|
75
|
+
const skills = await loadSkills(skill.local("./skills"));
|
|
76
|
+
const agent = new Agent({ id: "agent", model, instructions: "...", skills });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`skill.local(path)` treats the path as one skill when it holds `SKILL.md`,
|
|
80
|
+
otherwise as a folder of skills. `loadSkills` builds the system instructions
|
|
81
|
+
(name + description + reference/script listing) and the four lazy
|
|
82
|
+
`get_skill_*` / `run_skill_script` tools — the Agent only pulls full
|
|
83
|
+
instructions, references, or scripts when relevant.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Providers
|
|
2
|
+
|
|
3
|
+
Provider behavior lives in provider packages, never in `@anvia/core`:
|
|
4
|
+
|
|
5
|
+
- `@anvia/openai`, `@anvia/anthropic`, `@anvia/gemini`, `@anvia/mistral`,
|
|
6
|
+
`@anvia/grok` — check the owning package README for client names and model options.
|
|
7
|
+
- Typical shape: `new OpenAIClient({ baseUrl, apiKey })`, then
|
|
8
|
+
`client.completionModel({ modelId, api: "responses" })` for the Agent model.
|
|
9
|
+
- Embedding, transcription, and image/audio generation follow the same split:
|
|
10
|
+
core contracts, provider adapters in `packages/provider-*`, local embeddings
|
|
11
|
+
in `packages/embedding-transformers`.
|
|
12
|
+
|
|
13
|
+
## Rules
|
|
14
|
+
|
|
15
|
+
- Never import a vendor SDK in `@anvia/core` (or in app code that claims to be
|
|
16
|
+
provider-agnostic) — add a provider adapter instead.
|
|
17
|
+
- Read credentials from the environment at the boundary
|
|
18
|
+
(`process.env.OPENAI_API_KEY ?? ""`); never commit keys or trace payloads.
|
|
19
|
+
- Tests must mock or fake provider SDKs. Ordinary package tests must not need
|
|
20
|
+
network access or real API keys.
|
|
21
|
+
- Match the package's import style: some packages use extensionless local
|
|
22
|
+
imports, many adapters use `.js` suffixes in source imports.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Subagents and Teams
|
|
2
|
+
|
|
3
|
+
Two composition shapes, chosen by who decides the control flow: when you can
|
|
4
|
+
name the delegation graph, wire agents together yourself; when the model must
|
|
5
|
+
decide who to call, give it subagent tools; when instances must talk, wait, and
|
|
6
|
+
spawn each other, use `AgentTeam`.
|
|
7
|
+
|
|
8
|
+
## Subagents as tools (you decide)
|
|
9
|
+
|
|
10
|
+
`agent.asTool({ name, suspension })` wraps an Agent as a callable tool of
|
|
11
|
+
another (see `references/agent-options.md` for the coordinator example).
|
|
12
|
+
Prefer this when the delegation graph is static and shallow — one coordinator
|
|
13
|
+
calling named specialists, optionally fanned out in parallel via
|
|
14
|
+
`toolConcurrency`. Each call is independent; subagents share nothing but the
|
|
15
|
+
model.
|
|
16
|
+
|
|
17
|
+
## AgentTeam (the model decides)
|
|
18
|
+
|
|
19
|
+
`AgentTeam` from `@anvia/core/agent` runs a coordinator plus member agents as
|
|
20
|
+
one supervised runtime:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { Agent, AgentTeam } from "@anvia/core/agent";
|
|
24
|
+
|
|
25
|
+
const researcher = new Agent({ id: "researcher", model, instructions: "Find facts only." });
|
|
26
|
+
const writer = new Agent({ id: "writer", model, instructions: "Draft from findings only." });
|
|
27
|
+
|
|
28
|
+
const team = new AgentTeam({
|
|
29
|
+
id: "research-team", // coordinator options (id, model, instructions, tools, memory...)
|
|
30
|
+
model,
|
|
31
|
+
instructions: "Coordinate research and drafting through your member tools.",
|
|
32
|
+
members: [researcher, writer], // required; Agent instances, unique ids
|
|
33
|
+
communication: { siblings: true }, // default false
|
|
34
|
+
limits: { maxConcurrentAgents: 4 },
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const outcome = await team.generate({ prompt: "Brief the team and produce a memo." });
|
|
38
|
+
console.log(
|
|
39
|
+
outcome.output,
|
|
40
|
+
outcome.usage,
|
|
41
|
+
outcome.members.map((m) => `${m.name}:${m.status}`),
|
|
42
|
+
);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Constructor options are coordinator `AgentOptions` plus:
|
|
46
|
+
|
|
47
|
+
- `members` — required array of `Agent` instances. Member ids must be unique
|
|
48
|
+
and match `^[a-zA-Z0-9_-]{1,58}$` because they become spawn tool names.
|
|
49
|
+
- `spawning: [{ from, to }]` — additional spawn permissions between members;
|
|
50
|
+
the coordinator can always spawn every member.
|
|
51
|
+
- `communication: { siblings: true }` — allow messaging and waiting between
|
|
52
|
+
instances with the same parent (default false).
|
|
53
|
+
- `limits` — `maxDepth` (3), `maxConcurrentAgents` (4), `maxAgentInstances`
|
|
54
|
+
(12), `maxTotalTurns` (100), `maxBufferedEvents` (1024). Breaching a limit
|
|
55
|
+
raises `AgentTeamLimitError`; unread stream-event overflow cancels the run.
|
|
56
|
+
|
|
57
|
+
The runtime injects reserved tools — `spawn_<memberId>`, `send_message`,
|
|
58
|
+
`wait_for_agent`, `list_agents`, `cancel_agent`. A member tool with one of
|
|
59
|
+
those names throws at construction.
|
|
60
|
+
|
|
61
|
+
## Running teams
|
|
62
|
+
|
|
63
|
+
- `generate({ prompt })` or `{ messages }` — exactly one. Returns the
|
|
64
|
+
coordinator outcome (`response` or `blocked`) plus `teamRunId`, aggregate
|
|
65
|
+
`usage`, and per-instance `members` summaries (status, usage, outcome).
|
|
66
|
+
- `stream()` — async events: `agent_queued` / `agent_started` /
|
|
67
|
+
`agent_waiting` / `agent_idle` / `agent_failed` / `agent_cancelled`,
|
|
68
|
+
`message_queued` / `message_delivered`, `interaction`, and nested
|
|
69
|
+
`agent_event`s (each tagged with the emitting instance). Also exposes
|
|
70
|
+
`textStream`, `text`, `result`, `steer(input)`, and `cancel(reason?)`.
|
|
71
|
+
- Interactions from any member (tool approvals, questions) must be resolved at
|
|
72
|
+
the team level: pass `resolveInteraction(request) => response` on the run.
|
|
73
|
+
Without it, an interaction fails the run with `AgentTeamInteractionError`.
|
|
74
|
+
|
|
75
|
+
## Rules
|
|
76
|
+
|
|
77
|
+
- Keep members narrow: one task each, explicit `name` + `description`, facts
|
|
78
|
+
in, facts out — the coordinator routes by that text.
|
|
79
|
+
- One coordinator; do not nest teams inside teams. Escalating depth is what
|
|
80
|
+
`limits.maxDepth` is there to stop.
|
|
81
|
+
- A fixed sequence of agent steps is a pipeline (`.agent()` stage — see the
|
|
82
|
+
`anvia-pipeline` skill), not a team.
|
|
83
|
+
- Studio renders team runs with steer, cancel, and interaction cards — see the
|
|
84
|
+
`anvia-studio` skill.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Tools
|
|
2
|
+
|
|
3
|
+
Tools are created with `createTool` from `@anvia/core/tool`. Every tool needs a
|
|
4
|
+
`name`, a routing `description`, a zod `inputSchema`, and an `execute`
|
|
5
|
+
implementation. Input and output are validated at the boundary — describe every
|
|
6
|
+
schema field so the model fills it correctly.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
const tool = createTool({
|
|
10
|
+
name: "add",
|
|
11
|
+
description: "Add two numbers together.",
|
|
12
|
+
inputSchema: z.object({
|
|
13
|
+
x: z.number().describe("The first number."),
|
|
14
|
+
y: z.number().describe("The second number."),
|
|
15
|
+
}),
|
|
16
|
+
outputSchema: z.number(), // optional; when present, execute results are parsed against it
|
|
17
|
+
execute: (args) => args.x + args.y,
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Approvals
|
|
22
|
+
|
|
23
|
+
Side-effecting tools declare `requiresApproval` — a boolean, a static
|
|
24
|
+
`{ reason }`, or a function of the args. Approval requests suspend the run;
|
|
25
|
+
resume it explicitly:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
const tool = createTool({
|
|
29
|
+
name: "delete_account",
|
|
30
|
+
description: "Delete a customer account permanently.",
|
|
31
|
+
inputSchema: z.object({ accountId: z.string() }),
|
|
32
|
+
outputSchema: z.string(),
|
|
33
|
+
requiresApproval: ({ accountId }) => ({ reason: `Permanently delete ${accountId}?` }),
|
|
34
|
+
execute: ({ accountId }) => `Deleted account ${accountId}`,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
let result = await agent.generate({ prompt: "..." });
|
|
38
|
+
while (result.type === "interaction") {
|
|
39
|
+
// result.interaction is e.g. { type: "tool-approval", toolName, reason }
|
|
40
|
+
result = await agent.resume(result.continuation, {
|
|
41
|
+
type: "tool-approval",
|
|
42
|
+
approved: false,
|
|
43
|
+
reason: "Reject sensitive operations in this demo.",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Always handle the `interaction` result shape — `generate` does not throw for
|
|
49
|
+
approvals. The same interaction loop applies to `createQuestionTool` prompts.
|
|
50
|
+
|
|
51
|
+
## Built-in and dynamic tools
|
|
52
|
+
|
|
53
|
+
- `createThinkTool()` — a scratchpad reasoning tool for agents that need visible
|
|
54
|
+
intermediate thought.
|
|
55
|
+
- `createQuestionTool()` — ask the caller a question mid-run (resolves through
|
|
56
|
+
the interaction loop like approvals).
|
|
57
|
+
- `ToolIndex` — expose a large or dynamic tool set behind one entry so the Agent
|
|
58
|
+
resolves tools at call time instead of listing them all in the prompt.
|
|
59
|
+
- `createMiddleware()` — observe or map around requests, responses, and tool
|
|
60
|
+
input/output; prefer it over re-implementing retry or mapping inside
|
|
61
|
+
`execute`.
|
|
62
|
+
- `createHook()` (from `@anvia/core/internal/agent`) — lifecycle actions
|
|
63
|
+
(continue, skip, terminate, approval). Per-run tool concurrency is the
|
|
64
|
+
`toolConcurrency` setting, not a middleware or hook concern.
|
|
65
|
+
|
|
66
|
+
## MCP tools
|
|
67
|
+
|
|
68
|
+
MCP tools must be registered through `Agent.mcpServers`, never smuggled in via
|
|
69
|
+
`Agent.skills`. Skill tooling (`get_skill_instructions`, `get_skill_reference`,
|
|
70
|
+
`get_skill_script`, `run_skill_script`) is created by `loadSkills` and merged by
|
|
71
|
+
the Agent itself — pass the `SkillSet`, not its individual tools.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Heuristic checks for Anvia agent/tool code in an app directory.
|
|
3
|
+
# Usage: sh scripts/check-agent.sh [--dir <app-root>]
|
|
4
|
+
# Fails with a list of violations; passes silently with "agent OK".
|
|
5
|
+
|
|
6
|
+
DIR="."
|
|
7
|
+
if [ "$1" = "--dir" ] && [ -n "$2" ]; then
|
|
8
|
+
DIR="$2"
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
ROOTS_FOUND=0
|
|
12
|
+
for root in "$DIR/src" "$DIR/app" "$DIR/lib" "$DIR/server"; do
|
|
13
|
+
if [ -d "$root" ]; then
|
|
14
|
+
ROOTS_FOUND=1
|
|
15
|
+
break
|
|
16
|
+
fi
|
|
17
|
+
done
|
|
18
|
+
if [ "$ROOTS_FOUND" -eq 0 ]; then
|
|
19
|
+
echo "ERROR: no src/app/lib/server directory under '$DIR' — nothing was checked."
|
|
20
|
+
echo "Run from the app root or pass --dir <app-root>."
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
fail=0
|
|
25
|
+
violation() {
|
|
26
|
+
echo "VIOLATION: $1"
|
|
27
|
+
fail=1
|
|
28
|
+
}
|
|
29
|
+
warning() {
|
|
30
|
+
echo "WARNING: $1"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
scan() {
|
|
34
|
+
# $1 = include pattern; prints matching files under the usual roots.
|
|
35
|
+
grep -rln --include="$1" -e 'new Agent(' -e 'new AgentTeam(' -e 'createTool(' -e '.asTool(' \
|
|
36
|
+
"$DIR/src" "$DIR/app" "$DIR/lib" "$DIR/server" 2>/dev/null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
FILES=$( { scan '*.ts'; scan '*.tsx'; } | sort -u)
|
|
40
|
+
|
|
41
|
+
if [ -z "$FILES" ]; then
|
|
42
|
+
echo "agent OK (no Agent/tool code found)"
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# 1. createTool requires a zod inputSchema at the boundary.
|
|
47
|
+
violations=$(echo "$FILES" | while IFS= read -r f; do
|
|
48
|
+
awk -v file="$f" '
|
|
49
|
+
/createTool\(/ { in_tool=1; buf=""; depth=0 }
|
|
50
|
+
in_tool { buf=buf "\n" $0; depth+=gsub(/\(/,"(")-gsub(/\)/,")") }
|
|
51
|
+
in_tool && depth<=0 && length(buf)>0 {
|
|
52
|
+
if (buf !~ /inputSchema/) print "VIOLATION: " file ": createTool without inputSchema (see references/tools.md)."
|
|
53
|
+
in_tool=0
|
|
54
|
+
}
|
|
55
|
+
' "$f"
|
|
56
|
+
done)
|
|
57
|
+
if [ -n "$violations" ]; then
|
|
58
|
+
echo "$violations"
|
|
59
|
+
fail=1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# 2. asTool requires an explicit suspension policy.
|
|
63
|
+
if echo "$FILES" | xargs grep -h -A4 '.asTool(' 2>/dev/null | grep -q 'asTool('; then
|
|
64
|
+
echo "$FILES" | xargs grep -h -A4 '.asTool(' 2>/dev/null | grep -q 'suspension' || {
|
|
65
|
+
violation "agent.asTool without suspension policy (see references/agent-options.md)."
|
|
66
|
+
}
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# 3. new Agent requires id and model.
|
|
70
|
+
if echo "$FILES" | xargs grep -h -B1 -A8 'new Agent(' 2>/dev/null | grep -q 'new Agent('; then
|
|
71
|
+
echo "$FILES" | xargs grep -h -B1 -A8 'new Agent(' 2>/dev/null | grep -q 'id:' || {
|
|
72
|
+
violation "new Agent without id (see references/agent-options.md)."
|
|
73
|
+
}
|
|
74
|
+
echo "$FILES" | xargs grep -h -B1 -A8 'new Agent(' 2>/dev/null | grep -q 'model' || {
|
|
75
|
+
violation "new Agent without model (see references/providers.md)."
|
|
76
|
+
}
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# 4. Sensitive tool names without requiresApproval (warning only).
|
|
80
|
+
if echo "$FILES" | xargs grep -l -i -e 'name: *"[^"]*\(delete\|refund\|payment\|payroll\)' 2>/dev/null | grep -q .; then
|
|
81
|
+
hit=$(echo "$FILES" | xargs grep -l -i -e 'name: *"[^"]*\(delete\|refund\|payment\|payroll\)' 2>/dev/null)
|
|
82
|
+
echo "$hit" | xargs grep -L 'requiresApproval' 2>/dev/null | grep -q . && {
|
|
83
|
+
warning "sensitive tool without requiresApproval (see references/tools.md)."
|
|
84
|
+
}
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# 5. AgentTeam requires a members array.
|
|
88
|
+
if echo "$FILES" | xargs grep -l 'new AgentTeam(' 2>/dev/null | grep -q .; then
|
|
89
|
+
echo "$FILES" | xargs grep -h -A10 'new AgentTeam(' 2>/dev/null | grep -q 'members' || {
|
|
90
|
+
violation "new AgentTeam without members (see references/teams.md)."
|
|
91
|
+
}
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
if [ "$fail" -eq 0 ]; then
|
|
95
|
+
echo "agent OK"
|
|
96
|
+
exit 0
|
|
97
|
+
fi
|
|
98
|
+
echo "See skills/anvia-agent/references/ for fixes."
|
|
99
|
+
exit 1
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: anvia-channels
|
|
3
|
+
description: Connect Discord, Slack, and Telegram to Anvia agents — channel adapters, agent-backed bots, proactive delivery, sessions, streaming, and platform actions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Anvia Channels Skill
|
|
7
|
+
|
|
8
|
+
Use this skill when the user wants an Anvia agent reachable from Discord, Slack,
|
|
9
|
+
or Telegram, needs proactive delivery into a channel from a worker or monitor,
|
|
10
|
+
or wants to handle raw platform events. The five libraries are private workspace
|
|
11
|
+
packages — depend on them from the channels workspace (`workspace:*`), not npm.
|
|
12
|
+
|
|
13
|
+
## Process
|
|
14
|
+
|
|
15
|
+
1. Wire the platform adapter (`references/adapters.md`) — one factory call per
|
|
16
|
+
platform; credentials from the environment, never literals.
|
|
17
|
+
2. Run an agent behind it (`references/channel-agent.md`) — sessions and memory,
|
|
18
|
+
streaming edits, acknowledgements, slash commands, paused approvals.
|
|
19
|
+
3. Send or receive without an agent (`references/delivery.md`) —
|
|
20
|
+
`sendChannelMessage`, long-text splitting, validation, raw event handling.
|
|
21
|
+
4. Run `scripts/check-channels.sh` from the app root before claiming done.
|
|
22
|
+
|
|
23
|
+
## Minimal slice
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { createChannelAgent } from "@anvia/channel-agent";
|
|
27
|
+
import { Agent } from "@anvia/core/agent";
|
|
28
|
+
import { OpenAIClient } from "@anvia/openai";
|
|
29
|
+
import { telegram } from "@anvia/telegram";
|
|
30
|
+
|
|
31
|
+
const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY ?? "" });
|
|
32
|
+
const agent = new Agent({
|
|
33
|
+
id: "telegram-assistant",
|
|
34
|
+
model: openai.completionModel({ modelId: "gpt-6-astra", api: "responses" }),
|
|
35
|
+
instructions: "You are a concise assistant. Answer in the sender's language.",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const channel = telegram({
|
|
39
|
+
token: process.env.TELEGRAM_BOT_TOKEN ?? "",
|
|
40
|
+
onError: (error, context) => console.error(`[telegram:${context.operation}]`, error),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const service = createChannelAgent({
|
|
44
|
+
channel,
|
|
45
|
+
agent,
|
|
46
|
+
streaming: { placeholder: "Thinking…", editIntervalMs: 750 },
|
|
47
|
+
acknowledge: "👀",
|
|
48
|
+
onError: (error, context) => console.error(`[channel-agent:${context.stage}]`, error),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await service.start();
|
|
52
|
+
process.once("SIGTERM", () => void service.stop());
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Output
|
|
56
|
+
|
|
57
|
+
Prefer one adapter, one agent, environment-read credentials, and a shutdown path
|
|
58
|
+
for every started service. Point to the relevant reference file instead of
|
|
59
|
+
pasting its contents into chat.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Channel adapters
|
|
2
|
+
|
|
3
|
+
Three first-party adapters, each a single factory call. All factories read
|
|
4
|
+
credentials from the environment at the call site — never a literal.
|
|
5
|
+
|
|
6
|
+
| Platform | Factory | Input transport | Delivery |
|
|
7
|
+
| -------- | --------------------------------------------------- | ----------------------- | -------- |
|
|
8
|
+
| Telegram | `telegram({ token })` from `@anvia/telegram` | Long polling or webhook | Bot API |
|
|
9
|
+
| Discord | `discord({ token })` from `@anvia/discord` | Gateway | REST |
|
|
10
|
+
| Slack | `slack({ appToken, botToken })` from `@anvia/slack` | Socket Mode | Web API |
|
|
11
|
+
|
|
12
|
+
## Option shapes
|
|
13
|
+
|
|
14
|
+
Each factory accepts either live credentials or an injected low-level client for
|
|
15
|
+
offline tests — the unions are exclusive (`token` and `api` are never both set):
|
|
16
|
+
|
|
17
|
+
- `telegram({ token, baseUrl?, fetch?, maximumAttachmentBytes?, polling?, webhook?, onError? })`
|
|
18
|
+
or `telegram({ api })`. `onError(error, context)` receives
|
|
19
|
+
`context.operation`.
|
|
20
|
+
- `discord({ token, messageContentIntent?, fetch?, maximumAttachmentBytes?, onError? })`
|
|
21
|
+
or `discord({ gateway })`. Set `messageContentIntent` when the bot needs
|
|
22
|
+
message content (privileged intent on Discord's side).
|
|
23
|
+
- `slack({ appToken, botToken, fetch?, maximumAttachmentBytes? })` or
|
|
24
|
+
`slack({ transport })` — app-level + bot-level tokens are both required.
|
|
25
|
+
|
|
26
|
+
Adapters also expose the classes directly (`TelegramChannel`, `DiscordChannel`,
|
|
27
|
+
`SlackChannel`) if you need the type; prefer the factories.
|
|
28
|
+
|
|
29
|
+
## Capabilities
|
|
30
|
+
|
|
31
|
+
Adapters advertise what the platform supports through `channel.capabilities` —
|
|
32
|
+
gate optional behavior on it instead of hard-coding per-platform branches.
|
|
33
|
+
All three support message edits, deletion, and reactions; files and
|
|
34
|
+
replies/threads everywhere; native actions differ (Discord buttons, Slack Block
|
|
35
|
+
Kit buttons, Telegram inline keyboard); typing indicators exist on Discord and
|
|
36
|
+
Telegram but not Slack.
|
|
37
|
+
|
|
38
|
+
## Addresses and events
|
|
39
|
+
|
|
40
|
+
Every conversation is a `ChannelAddress`: `{ platform, conversationId }`.
|
|
41
|
+
`platform` is a plain string — custom adapters choose their own.
|
|
42
|
+
|
|
43
|
+
Inbound payloads are validated at runtime and normalized into a `ChannelEvent`
|
|
44
|
+
union: message, message-edited, message-deleted, reaction, action, or command
|
|
45
|
+
events. You rarely touch raw platform payloads unless you write an adapter.
|
|
46
|
+
|
|
47
|
+
## Custom adapters
|
|
48
|
+
|
|
49
|
+
Implement `Channel<RawEvent>` from `@anvia/channel`: a `platform` string,
|
|
50
|
+
optional `capabilities`, send/edit/delete/react operations, and an event
|
|
51
|
+
subscription that normalizes raw payloads into `ChannelEvent` values at the
|
|
52
|
+
boundary. Wrap any adapter with `createRateLimitedChannel(channel, options)`
|
|
53
|
+
to throttle outbound delivery.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Channel agent bridge
|
|
2
|
+
|
|
3
|
+
`createChannelAgent({ channel, agent, ... })` from `@anvia/channel-agent` runs
|
|
4
|
+
an Anvia `Agent` behind any adapter and returns a `ChannelAgentService` with
|
|
5
|
+
`start()` / `stop()`. `serveChannelAgent(options)` is the shorthand that also
|
|
6
|
+
starts it. The executor contract is structural — anything with `generate` /
|
|
7
|
+
`stream` (plus optional `resume`), i.e. an `Agent` — so tests can pass a fake.
|
|
8
|
+
|
|
9
|
+
## Options that matter
|
|
10
|
+
|
|
11
|
+
- `shouldHandle(event)` — filter inbound messages; the default
|
|
12
|
+
(`defaultShouldHandleChannelEvent`) drops bot/self messages and non-text
|
|
13
|
+
noise. Override for allowlists or group-chat rules.
|
|
14
|
+
- `createSession(event)` — return a `MemoryScope` to scope agent memory per
|
|
15
|
+
conversation or per user. Defaults to `defaultChannelAgentSession`
|
|
16
|
+
(per-conversation); use `channelConversationUserSession` when each sender
|
|
17
|
+
needs their own thread of memory, `channelConversationSession` /
|
|
18
|
+
`channelConversationKey` for custom keys.
|
|
19
|
+
- `createPrompt({ event, context })` — reshape the prompt before the agent runs
|
|
20
|
+
(`channelMessagePrompt` is the default builder).
|
|
21
|
+
- `renderOutcome({ outcome, event })` — turn the agent outcome into the reply
|
|
22
|
+
text or a full `ChannelMessage` (actions, attachments).
|
|
23
|
+
- `streaming` — `{ enabled?, editIntervalMs?, placeholder? }`; the bridge edits
|
|
24
|
+
one message in place as the agent streams. `placeholder: false` disables the
|
|
25
|
+
"Thinking…" stub.
|
|
26
|
+
- `acknowledge` — shorthand for a receipt reaction: `"👀"` or
|
|
27
|
+
`{ reaction, completeReaction?, ... }`; `false` disables.
|
|
28
|
+
- `commands` — handle platform slash commands; `false` (default) ignores them.
|
|
29
|
+
- `multimodal` — inbound attachments become prompt parts when enabled.
|
|
30
|
+
- `errorMessage`, `emptyResponseMessage` — user-facing fallbacks.
|
|
31
|
+
- `onError(error, context)` — always set it. `context.stage` is one of
|
|
32
|
+
`"filter" | "prepare" | "acknowledge" | "interaction" | "agent" | "delivery"`,
|
|
33
|
+
which tells you where a failure happened.
|
|
34
|
+
|
|
35
|
+
## Approvals and questions
|
|
36
|
+
|
|
37
|
+
Tool approvals and agent questions pause instead of failing: the bridge stores
|
|
38
|
+
the pending interaction and renders platform actions (buttons/inline keyboards)
|
|
39
|
+
so the conversation can resume when the user taps. `interactions` controls this
|
|
40
|
+
— pass `false` to disable, or options with a store:
|
|
41
|
+
|
|
42
|
+
- `MemoryChannelAgentInteractionStore` — in-memory, fine for dev.
|
|
43
|
+
- `SqliteChannelAgentInteractionStore` — durable; pending interactions survive
|
|
44
|
+
restarts.
|
|
45
|
+
- Helpers: `renderChannelAgentInteraction`, `parseChannelAgentActionResponse`,
|
|
46
|
+
`channelInteractionActions`, `channelInteractionKey`.
|
|
47
|
+
|
|
48
|
+
## Lifecycle
|
|
49
|
+
|
|
50
|
+
`start()` begins receiving; `stop()` drains and disconnects. Own the shutdown
|
|
51
|
+
path: stop the service, then close memory/interaction-store clients. Guard
|
|
52
|
+
against double-shutdown when both SIGINT and SIGTERM arrive.
|