@alejandroroman/agent-kit 0.1.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.
Files changed (169) hide show
  1. package/dist/_memory/config.d.ts +14 -0
  2. package/dist/_memory/config.js +16 -0
  3. package/dist/_memory/db/client.d.ts +2 -0
  4. package/dist/_memory/db/client.js +15 -0
  5. package/dist/_memory/db/schema.d.ts +14 -0
  6. package/dist/_memory/db/schema.js +51 -0
  7. package/dist/_memory/embeddings/ollama.d.ts +12 -0
  8. package/dist/_memory/embeddings/ollama.js +22 -0
  9. package/dist/_memory/embeddings/provider.d.ts +4 -0
  10. package/dist/_memory/embeddings/provider.js +1 -0
  11. package/dist/_memory/index.d.ts +10 -0
  12. package/dist/_memory/index.js +6 -0
  13. package/dist/_memory/search.d.ts +30 -0
  14. package/dist/_memory/search.js +121 -0
  15. package/dist/_memory/server.d.ts +8 -0
  16. package/dist/_memory/server.js +126 -0
  17. package/dist/_memory/store.d.ts +51 -0
  18. package/dist/_memory/store.js +115 -0
  19. package/dist/agent/loop.d.ts +3 -0
  20. package/dist/agent/loop.js +195 -0
  21. package/dist/agent/setup.d.ts +6 -0
  22. package/dist/agent/setup.js +11 -0
  23. package/dist/agent/soul.d.ts +1 -0
  24. package/dist/agent/soul.js +8 -0
  25. package/dist/agent/types.d.ts +23 -0
  26. package/dist/agent/types.js +1 -0
  27. package/dist/api/agents.d.ts +2 -0
  28. package/dist/api/agents.js +43 -0
  29. package/dist/api/config.d.ts +2 -0
  30. package/dist/api/config.js +20 -0
  31. package/dist/api/cron.d.ts +2 -0
  32. package/dist/api/cron.js +15 -0
  33. package/dist/api/health.d.ts +2 -0
  34. package/dist/api/health.js +8 -0
  35. package/dist/api/logs.d.ts +5 -0
  36. package/dist/api/logs.js +28 -0
  37. package/dist/api/router.d.ts +6 -0
  38. package/dist/api/router.js +80 -0
  39. package/dist/api/sessions.d.ts +2 -0
  40. package/dist/api/sessions.js +67 -0
  41. package/dist/api/types.d.ts +12 -0
  42. package/dist/api/types.js +13 -0
  43. package/dist/api/usage.d.ts +3 -0
  44. package/dist/api/usage.js +50 -0
  45. package/dist/bootstrap.d.ts +51 -0
  46. package/dist/bootstrap.js +110 -0
  47. package/dist/cli/chat.d.ts +1 -0
  48. package/dist/cli/chat.js +102 -0
  49. package/dist/cli/config-writer.d.ts +40 -0
  50. package/dist/cli/config-writer.js +108 -0
  51. package/dist/cli/create.d.ts +1 -0
  52. package/dist/cli/create.js +37 -0
  53. package/dist/cli/init.d.ts +1 -0
  54. package/dist/cli/init.js +85 -0
  55. package/dist/cli/list.d.ts +1 -0
  56. package/dist/cli/list.js +36 -0
  57. package/dist/cli/ollama.d.ts +6 -0
  58. package/dist/cli/ollama.js +44 -0
  59. package/dist/cli/setup-agent/index.d.ts +9 -0
  60. package/dist/cli/setup-agent/index.js +100 -0
  61. package/dist/cli/setup-agent/soul.d.ts +2 -0
  62. package/dist/cli/setup-agent/soul.js +79 -0
  63. package/dist/cli/setup-agent/tools.d.ts +9 -0
  64. package/dist/cli/setup-agent/tools.js +362 -0
  65. package/dist/cli/start.d.ts +1 -0
  66. package/dist/cli/start.js +235 -0
  67. package/dist/cli/ui.d.ts +17 -0
  68. package/dist/cli/ui.js +79 -0
  69. package/dist/cli/validate.d.ts +1 -0
  70. package/dist/cli/validate.js +47 -0
  71. package/dist/cli.d.ts +4 -0
  72. package/dist/cli.js +59 -0
  73. package/dist/config/index.d.ts +4 -0
  74. package/dist/config/index.js +3 -0
  75. package/dist/config/loader.d.ts +2 -0
  76. package/dist/config/loader.js +10 -0
  77. package/dist/config/resolve.d.ts +22 -0
  78. package/dist/config/resolve.js +45 -0
  79. package/dist/config/schema.d.ts +217 -0
  80. package/dist/config/schema.js +159 -0
  81. package/dist/cron/scheduler.d.ts +22 -0
  82. package/dist/cron/scheduler.js +115 -0
  83. package/dist/gateways/slack/client.d.ts +13 -0
  84. package/dist/gateways/slack/client.js +44 -0
  85. package/dist/gateways/slack/format.d.ts +30 -0
  86. package/dist/gateways/slack/format.js +170 -0
  87. package/dist/gateways/slack/handler.d.ts +9 -0
  88. package/dist/gateways/slack/handler.js +95 -0
  89. package/dist/gateways/slack/index.d.ts +16 -0
  90. package/dist/gateways/slack/index.js +102 -0
  91. package/dist/gateways/slack/listener.d.ts +10 -0
  92. package/dist/gateways/slack/listener.js +35 -0
  93. package/dist/gateways/slack/sessions.d.ts +11 -0
  94. package/dist/gateways/slack/sessions.js +32 -0
  95. package/dist/gateways/slack/types.d.ts +13 -0
  96. package/dist/gateways/slack/types.js +7 -0
  97. package/dist/heartbeat/index.d.ts +2 -0
  98. package/dist/heartbeat/index.js +1 -0
  99. package/dist/heartbeat/runner.d.ts +31 -0
  100. package/dist/heartbeat/runner.js +215 -0
  101. package/dist/index.d.ts +1 -0
  102. package/dist/index.js +207 -0
  103. package/dist/llm/anthropic.d.ts +12 -0
  104. package/dist/llm/anthropic.js +89 -0
  105. package/dist/llm/fallback.d.ts +6 -0
  106. package/dist/llm/fallback.js +30 -0
  107. package/dist/llm/index.d.ts +9 -0
  108. package/dist/llm/index.js +40 -0
  109. package/dist/llm/openai.d.ts +12 -0
  110. package/dist/llm/openai.js +85 -0
  111. package/dist/llm/provider.d.ts +12 -0
  112. package/dist/llm/provider.js +9 -0
  113. package/dist/llm/types.d.ts +73 -0
  114. package/dist/llm/types.js +6 -0
  115. package/dist/logger.d.ts +2 -0
  116. package/dist/logger.js +15 -0
  117. package/dist/multi/registry.d.ts +15 -0
  118. package/dist/multi/registry.js +28 -0
  119. package/dist/multi/spawn.d.ts +14 -0
  120. package/dist/multi/spawn.js +14 -0
  121. package/dist/scripts/validate-agent-cli.d.ts +1 -0
  122. package/dist/scripts/validate-agent-cli.js +47 -0
  123. package/dist/scripts/validate-agent.d.ts +17 -0
  124. package/dist/scripts/validate-agent.js +242 -0
  125. package/dist/session/compaction.d.ts +4 -0
  126. package/dist/session/compaction.js +30 -0
  127. package/dist/session/manager.d.ts +9 -0
  128. package/dist/session/manager.js +41 -0
  129. package/dist/skills/activate.d.ts +11 -0
  130. package/dist/skills/activate.js +62 -0
  131. package/dist/skills/index.d.ts +3 -0
  132. package/dist/skills/index.js +3 -0
  133. package/dist/skills/loader.d.ts +3 -0
  134. package/dist/skills/loader.js +20 -0
  135. package/dist/skills/schema.d.ts +8 -0
  136. package/dist/skills/schema.js +7 -0
  137. package/dist/text.d.ts +8 -0
  138. package/dist/text.js +24 -0
  139. package/dist/tools/builtin/index.d.ts +21 -0
  140. package/dist/tools/builtin/index.js +21 -0
  141. package/dist/tools/builtin/memory.d.ts +8 -0
  142. package/dist/tools/builtin/memory.js +164 -0
  143. package/dist/tools/builtin/read-file.d.ts +3 -0
  144. package/dist/tools/builtin/read-file.js +30 -0
  145. package/dist/tools/builtin/run-command.d.ts +3 -0
  146. package/dist/tools/builtin/run-command.js +37 -0
  147. package/dist/tools/builtin/spawn.d.ts +15 -0
  148. package/dist/tools/builtin/spawn.js +59 -0
  149. package/dist/tools/builtin/web-search.d.ts +8 -0
  150. package/dist/tools/builtin/web-search.js +99 -0
  151. package/dist/tools/builtin/write-file.d.ts +3 -0
  152. package/dist/tools/builtin/write-file.js +34 -0
  153. package/dist/tools/registry.d.ts +10 -0
  154. package/dist/tools/registry.js +26 -0
  155. package/dist/tools/sandbox.d.ts +9 -0
  156. package/dist/tools/sandbox.js +74 -0
  157. package/dist/tools/types.d.ts +8 -0
  158. package/dist/tools/types.js +7 -0
  159. package/dist/usage/index.d.ts +4 -0
  160. package/dist/usage/index.js +3 -0
  161. package/dist/usage/pricing.d.ts +10 -0
  162. package/dist/usage/pricing.js +35 -0
  163. package/dist/usage/schema.d.ts +1 -0
  164. package/dist/usage/schema.js +45 -0
  165. package/dist/usage/store.d.ts +10 -0
  166. package/dist/usage/store.js +227 -0
  167. package/dist/usage/types.d.ts +61 -0
  168. package/dist/usage/types.js +1 -0
  169. package/package.json +53 -0
@@ -0,0 +1,85 @@
1
+ import OpenAI from "openai";
2
+ import { defaultStream } from "./provider.js";
3
+ export class OpenAIProvider {
4
+ name = "openai";
5
+ client;
6
+ constructor(apiKey) {
7
+ this.client = new OpenAI({ apiKey: apiKey ?? process.env.OPENAI_API_KEY });
8
+ }
9
+ async complete(model, messages, options) {
10
+ const params = {
11
+ model,
12
+ max_completion_tokens: options?.maxTokens ?? 4096,
13
+ messages: this.toOpenAIMessages(messages, options?.systemPrompt),
14
+ };
15
+ if (options?.tools?.length) {
16
+ params.tools = options.tools.map((t) => ({
17
+ type: "function",
18
+ function: { name: t.name, description: t.description, parameters: t.parameters },
19
+ }));
20
+ }
21
+ const response = await this.client.chat.completions.create(params);
22
+ return this.fromOpenAIResponse(response);
23
+ }
24
+ async *stream(model, messages, options) {
25
+ yield* defaultStream(this, model, messages, options);
26
+ }
27
+ toOpenAIMessages(messages, systemPrompt) {
28
+ const result = [];
29
+ if (systemPrompt)
30
+ result.push({ role: "system", content: systemPrompt });
31
+ for (const msg of messages) {
32
+ if (msg.role === "user") {
33
+ result.push({ role: "user", content: msg.content });
34
+ }
35
+ else if (msg.role === "assistant") {
36
+ const textParts = msg.content.filter((b) => b.type === "text").map((b) => b.text).join("");
37
+ const toolCalls = msg.content.filter((b) => b.type === "tool_call").map((b) => ({
38
+ id: b.id, type: "function", function: { name: b.name, arguments: JSON.stringify(b.arguments) },
39
+ }));
40
+ const openaiMsg = { role: "assistant" };
41
+ if (textParts)
42
+ openaiMsg.content = textParts;
43
+ if (toolCalls.length)
44
+ openaiMsg.tool_calls = toolCalls;
45
+ result.push(openaiMsg);
46
+ }
47
+ else if (msg.role === "tool_result") {
48
+ result.push({ role: "tool", tool_call_id: msg.tool_call_id, content: msg.content });
49
+ }
50
+ }
51
+ return result;
52
+ }
53
+ fromOpenAIResponse(response) {
54
+ const choice = response.choices[0];
55
+ const content = [];
56
+ if (choice.message.content)
57
+ content.push({ type: "text", text: choice.message.content });
58
+ if (choice.message.tool_calls) {
59
+ for (const tc of choice.message.tool_calls) {
60
+ content.push({ type: "tool_call", id: tc.id, name: tc.function.name, arguments: JSON.parse(tc.function.arguments) });
61
+ }
62
+ }
63
+ return {
64
+ content,
65
+ stopReason: this.mapStopReason(choice.finish_reason),
66
+ usage: {
67
+ inputTokens: response.usage?.prompt_tokens ?? 0,
68
+ outputTokens: response.usage?.completion_tokens ?? 0,
69
+ cacheCreationTokens: 0,
70
+ cacheReadTokens: response.usage?.prompt_tokens_details?.cached_tokens ?? 0,
71
+ reasoningTokens: response.usage?.completion_tokens_details?.reasoning_tokens ?? 0,
72
+ },
73
+ model: "",
74
+ latencyMs: 0,
75
+ };
76
+ }
77
+ mapStopReason(reason) {
78
+ switch (reason) {
79
+ case "stop": return "end_turn";
80
+ case "tool_calls": return "tool_use";
81
+ case "length": return "max_tokens";
82
+ default: return "end_turn";
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,12 @@
1
+ import type { Message, ToolDefinition, LLMResponse, StreamEvent } from "./types.js";
2
+ export interface CompleteOptions {
3
+ systemPrompt?: string;
4
+ tools?: ToolDefinition[];
5
+ maxTokens?: number;
6
+ }
7
+ export interface LLMProvider {
8
+ name: string;
9
+ complete(model: string, messages: Message[], options?: CompleteOptions): Promise<LLMResponse>;
10
+ stream(model: string, messages: Message[], options?: CompleteOptions): AsyncIterable<StreamEvent>;
11
+ }
12
+ export declare function defaultStream(provider: LLMProvider, model: string, messages: Message[], options?: CompleteOptions): AsyncIterable<StreamEvent>;
@@ -0,0 +1,9 @@
1
+ export async function* defaultStream(provider, model, messages, options) {
2
+ const response = await provider.complete(model, messages, options);
3
+ for (const block of response.content) {
4
+ if (block.type === "text") {
5
+ yield { type: "text_delta", delta: block.text };
6
+ }
7
+ }
8
+ yield { type: "done", response };
9
+ }
@@ -0,0 +1,73 @@
1
+ export interface TextBlock {
2
+ type: "text";
3
+ text: string;
4
+ }
5
+ export interface ToolCallBlock {
6
+ type: "tool_call";
7
+ id: string;
8
+ name: string;
9
+ arguments: Record<string, unknown>;
10
+ }
11
+ export type ContentBlock = TextBlock | ToolCallBlock;
12
+ export interface UserMessage {
13
+ role: "user";
14
+ content: string;
15
+ }
16
+ export interface AssistantMessage {
17
+ role: "assistant";
18
+ content: ContentBlock[];
19
+ }
20
+ export interface ToolResultMessage {
21
+ role: "tool_result";
22
+ tool_call_id: string;
23
+ content: string;
24
+ }
25
+ export type Message = UserMessage | AssistantMessage | ToolResultMessage;
26
+ export interface ToolDefinition {
27
+ name: string;
28
+ description: string;
29
+ parameters: {
30
+ type: "object";
31
+ properties: Record<string, unknown>;
32
+ required?: string[];
33
+ };
34
+ }
35
+ export type StopReason = "end_turn" | "tool_use" | "max_tokens" | "error";
36
+ export interface TokenUsage {
37
+ inputTokens: number;
38
+ outputTokens: number;
39
+ cacheCreationTokens: number;
40
+ cacheReadTokens: number;
41
+ reasoningTokens: number;
42
+ }
43
+ export interface LLMResponse {
44
+ content: ContentBlock[];
45
+ stopReason: StopReason;
46
+ usage: TokenUsage;
47
+ model: string;
48
+ latencyMs: number;
49
+ }
50
+ export interface TextDeltaEvent {
51
+ type: "text_delta";
52
+ delta: string;
53
+ }
54
+ export interface ToolCallStartEvent {
55
+ type: "tool_call_start";
56
+ id: string;
57
+ name: string;
58
+ }
59
+ export interface ToolCallDeltaEvent {
60
+ type: "tool_call_delta";
61
+ id: string;
62
+ delta: string;
63
+ }
64
+ export interface DoneEvent {
65
+ type: "done";
66
+ response: LLMResponse;
67
+ }
68
+ export interface ErrorEvent {
69
+ type: "error";
70
+ error: string;
71
+ }
72
+ export type StreamEvent = TextDeltaEvent | ToolCallStartEvent | ToolCallDeltaEvent | DoneEvent | ErrorEvent;
73
+ export declare function extractText(content: ContentBlock[]): string;
@@ -0,0 +1,6 @@
1
+ export function extractText(content) {
2
+ return content
3
+ .filter((b) => b.type === "text")
4
+ .map((b) => b.text)
5
+ .join("");
6
+ }
@@ -0,0 +1,2 @@
1
+ import pino from "pino";
2
+ export declare function createLogger(name: string): pino.Logger<never, boolean>;
package/dist/logger.js ADDED
@@ -0,0 +1,15 @@
1
+ import pino from "pino";
2
+ const root = pino({
3
+ level: process.env.LOG_LEVEL ?? "info",
4
+ transport: {
5
+ target: "pino-pretty",
6
+ options: {
7
+ ignore: "pid,hostname",
8
+ translateTime: "HH:MM:ss",
9
+ colorize: true,
10
+ },
11
+ },
12
+ });
13
+ export function createLogger(name) {
14
+ return root.child({ component: name });
15
+ }
@@ -0,0 +1,15 @@
1
+ import type { Config } from "../config/schema.js";
2
+ export interface AgentEntry {
3
+ name: string;
4
+ model: string;
5
+ tools: string[];
6
+ soul?: string;
7
+ spawn_only: boolean;
8
+ }
9
+ export declare class AgentRegistry {
10
+ private agents;
11
+ constructor(config: Config, dataDir: string);
12
+ getAgent(name: string): AgentEntry | undefined;
13
+ listAgents(): string[];
14
+ listAll(): string[];
15
+ }
@@ -0,0 +1,28 @@
1
+ import { loadSoul } from "../agent/soul.js";
2
+ import * as path from "path";
3
+ export class AgentRegistry {
4
+ agents = new Map();
5
+ constructor(config, dataDir) {
6
+ for (const [name, agentDef] of Object.entries(config.agents)) {
7
+ const agentDir = path.join(dataDir, "agents", name);
8
+ this.agents.set(name, {
9
+ name,
10
+ model: agentDef.model ?? config.defaults.model,
11
+ tools: agentDef.tools,
12
+ soul: loadSoul(agentDir),
13
+ spawn_only: agentDef.spawn_only,
14
+ });
15
+ }
16
+ }
17
+ getAgent(name) {
18
+ return this.agents.get(name);
19
+ }
20
+ listAgents() {
21
+ return [...this.agents.entries()]
22
+ .filter(([, entry]) => !entry.spawn_only)
23
+ .map(([name]) => name);
24
+ }
25
+ listAll() {
26
+ return [...this.agents.keys()];
27
+ }
28
+ }
@@ -0,0 +1,14 @@
1
+ import type { Tool } from "../tools/types.js";
2
+ import type { AgentResult } from "../agent/types.js";
3
+ import type { UsageStore } from "../usage/store.js";
4
+ export interface SpawnOptions {
5
+ model: string;
6
+ fallbacks?: string[];
7
+ systemPrompt?: string;
8
+ task: string;
9
+ tools?: Tool[];
10
+ maxIterations?: number;
11
+ label?: string;
12
+ usageStore?: UsageStore;
13
+ }
14
+ export declare function spawnAgent(options: SpawnOptions): Promise<AgentResult>;
@@ -0,0 +1,14 @@
1
+ import { runAgentLoop } from "../agent/loop.js";
2
+ export async function spawnAgent(options) {
3
+ return runAgentLoop([{ role: "user", content: options.task }], {
4
+ model: options.model,
5
+ fallbacks: options.fallbacks,
6
+ systemPrompt: options.systemPrompt,
7
+ tools: options.tools,
8
+ maxIterations: options.maxIterations ?? 10,
9
+ label: options.label,
10
+ agentName: options.label,
11
+ usageStore: options.usageStore,
12
+ source: "spawn",
13
+ });
14
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ import * as path from "path";
2
+ import { loadConfig } from "../config/loader.js";
3
+ import { validateAgent } from "./validate-agent.js";
4
+ const PASS = "\u2713";
5
+ const FAIL = "\u2717";
6
+ const agentName = process.argv[2];
7
+ if (!agentName) {
8
+ console.error(`Usage: agent:validate <agent-name>`);
9
+ process.exit(1);
10
+ }
11
+ // Load config
12
+ let config;
13
+ try {
14
+ config = loadConfig(path.resolve("agent-kit.json"));
15
+ console.log(`${PASS} Config loaded`);
16
+ }
17
+ catch (err) {
18
+ const message = err instanceof Error ? err.message : String(err);
19
+ console.error(`${FAIL} Config failed to load: ${message}`);
20
+ process.exit(1);
21
+ }
22
+ // Validate agent
23
+ let result;
24
+ try {
25
+ result = validateAgent(agentName, config, {
26
+ skillsDir: path.resolve("skills"),
27
+ dataDir: path.resolve("data"),
28
+ });
29
+ }
30
+ catch (err) {
31
+ const message = err instanceof Error ? err.message : String(err);
32
+ console.error(`${FAIL} Validator crashed unexpectedly: ${message}`);
33
+ process.exit(2);
34
+ }
35
+ for (const check of result.checks) {
36
+ const icon = check.passed ? PASS : FAIL;
37
+ console.log(`${icon} [${check.name}] ${check.message}`);
38
+ }
39
+ if (result.success) {
40
+ console.log("\nAll checks passed.");
41
+ process.exit(0);
42
+ }
43
+ else {
44
+ const errorCount = result.checks.filter((c) => !c.passed).length;
45
+ console.log(`\n${errorCount} error(s) found.`);
46
+ process.exit(1);
47
+ }
@@ -0,0 +1,17 @@
1
+ import type { Config } from "../config/schema.js";
2
+ export declare const BUILTIN_TOOLS: readonly ["read_file", "write_file", "run_command", "web_search", "store_memory", "search_memory", "get_memory", "update_memory", "forget_memory", "list_memories"];
3
+ export interface Check {
4
+ name: string;
5
+ passed: boolean;
6
+ message: string;
7
+ }
8
+ export interface ValidationResult {
9
+ agentName: string;
10
+ success: boolean;
11
+ checks: Check[];
12
+ }
13
+ export interface ValidateOptions {
14
+ skillsDir: string;
15
+ dataDir: string;
16
+ }
17
+ export declare function validateAgent(agentName: string, config: Config, options: ValidateOptions): ValidationResult;
@@ -0,0 +1,242 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { loadSkillManifest } from "../skills/loader.js";
4
+ // NOTE: This list must be kept in sync with src/tools/builtin/index.ts.
5
+ // We hardcode it here to avoid importing the registry (which requires config to instantiate).
6
+ export const BUILTIN_TOOLS = [
7
+ "read_file",
8
+ "write_file",
9
+ "run_command",
10
+ "web_search",
11
+ "store_memory",
12
+ "search_memory",
13
+ "get_memory",
14
+ "update_memory",
15
+ "forget_memory",
16
+ "list_memories",
17
+ ];
18
+ function levenshtein(a, b) {
19
+ const m = a.length;
20
+ const n = b.length;
21
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
22
+ for (let i = 0; i <= m; i++)
23
+ dp[i][0] = i;
24
+ for (let j = 0; j <= n; j++)
25
+ dp[0][j] = j;
26
+ for (let i = 1; i <= m; i++) {
27
+ for (let j = 1; j <= n; j++) {
28
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
29
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
30
+ }
31
+ }
32
+ return dp[m][n];
33
+ }
34
+ function findClosestTool(name) {
35
+ let best;
36
+ let bestDist = Infinity;
37
+ for (const tool of BUILTIN_TOOLS) {
38
+ const dist = levenshtein(name, tool);
39
+ if (dist < bestDist) {
40
+ bestDist = dist;
41
+ best = tool;
42
+ }
43
+ }
44
+ return bestDist <= 3 ? best : undefined;
45
+ }
46
+ export function validateAgent(agentName, config, options) {
47
+ const checks = [];
48
+ // agent-exists
49
+ const agentDef = config.agents[agentName];
50
+ if (!agentDef) {
51
+ checks.push({
52
+ name: "agent-exists",
53
+ passed: false,
54
+ message: `Agent "${agentName}" not found in config`,
55
+ });
56
+ return { agentName, success: false, checks };
57
+ }
58
+ checks.push({
59
+ name: "agent-exists",
60
+ passed: true,
61
+ message: `Agent "${agentName}" found in config`,
62
+ });
63
+ // model-resolves
64
+ const model = agentDef.model ?? config.defaults.model;
65
+ if (config.models) {
66
+ const aliases = new Set(config.models.map((m) => m.alias));
67
+ const resolves = aliases.has(model);
68
+ checks.push({
69
+ name: "model-resolves",
70
+ passed: resolves,
71
+ message: resolves
72
+ ? `Model "${model}" resolves`
73
+ : `Model "${model}" not found in models allowlist`,
74
+ });
75
+ }
76
+ else {
77
+ checks.push({
78
+ name: "model-resolves",
79
+ passed: true,
80
+ message: `Model "${model}" (no allowlist, assumed valid)`,
81
+ });
82
+ }
83
+ // tools-resolve
84
+ const unknownTools = [];
85
+ const suggestions = [];
86
+ const builtinSet = new Set(BUILTIN_TOOLS);
87
+ for (const tool of agentDef.tools) {
88
+ if (!builtinSet.has(tool)) {
89
+ unknownTools.push(tool);
90
+ const suggestion = findClosestTool(tool);
91
+ if (suggestion) {
92
+ suggestions.push(`"${tool}" -> did you mean "${suggestion}"?`);
93
+ }
94
+ }
95
+ }
96
+ if (unknownTools.length === 0) {
97
+ checks.push({
98
+ name: "tools-resolve",
99
+ passed: true,
100
+ message: agentDef.tools.length > 0
101
+ ? `All ${agentDef.tools.length} tool(s) resolve`
102
+ : "No tools configured",
103
+ });
104
+ }
105
+ else {
106
+ const detail = suggestions.length > 0
107
+ ? ` (${suggestions.join(", ")})`
108
+ : "";
109
+ checks.push({
110
+ name: "tools-resolve",
111
+ passed: false,
112
+ message: `Unknown tools: ${unknownTools.join(", ")}${detail}`,
113
+ });
114
+ }
115
+ // skills-resolve
116
+ const skillErrors = [];
117
+ for (const skillName of agentDef.skills) {
118
+ const skillDir = path.join(options.skillsDir, skillName);
119
+ try {
120
+ const manifest = loadSkillManifest(skillDir);
121
+ // Verify tool files listed in manifest exist on disk
122
+ if (manifest.tools) {
123
+ for (const toolFile of manifest.tools) {
124
+ const toolPath = path.join(skillDir, "tools", `${toolFile}.ts`);
125
+ if (!fs.existsSync(toolPath)) {
126
+ skillErrors.push(`${skillName}: tool file missing: tools/${toolFile}.ts`);
127
+ }
128
+ }
129
+ }
130
+ }
131
+ catch (err) {
132
+ skillErrors.push(`${skillName}: ${err.message}`);
133
+ }
134
+ }
135
+ if (skillErrors.length === 0) {
136
+ checks.push({
137
+ name: "skills-resolve",
138
+ passed: true,
139
+ message: agentDef.skills.length > 0
140
+ ? `All ${agentDef.skills.length} skill(s) resolve`
141
+ : "No skills configured",
142
+ });
143
+ }
144
+ else {
145
+ checks.push({
146
+ name: "skills-resolve",
147
+ passed: false,
148
+ message: `Skill errors: ${skillErrors.join("; ")}`,
149
+ });
150
+ }
151
+ // soul-exists
152
+ const soulPath = path.join(options.dataDir, "agents", agentName, "SOUL.md");
153
+ let soulContent = null;
154
+ try {
155
+ soulContent = fs.readFileSync(soulPath, "utf-8");
156
+ }
157
+ catch {
158
+ // File doesn't exist or isn't readable
159
+ }
160
+ const soulNonEmpty = soulContent !== null && soulContent.trim().length > 0;
161
+ checks.push({
162
+ name: "soul-exists",
163
+ passed: soulNonEmpty,
164
+ message: soulNonEmpty
165
+ ? `SOUL.md found at ${soulPath}`
166
+ : soulContent !== null
167
+ ? `SOUL.md at ${soulPath} is empty`
168
+ : `SOUL.md not found at ${soulPath}`,
169
+ });
170
+ // sandbox-declared
171
+ const agentSandbox = agentDef.sandbox;
172
+ const defaultsSandbox = config.defaults.sandbox;
173
+ if (agentSandbox) {
174
+ checks.push({
175
+ name: "sandbox-declared",
176
+ passed: true,
177
+ message: "Sandbox declared on agent",
178
+ });
179
+ }
180
+ else if (defaultsSandbox) {
181
+ checks.push({
182
+ name: "sandbox-declared",
183
+ passed: true,
184
+ message: "Sandbox inherited from defaults",
185
+ });
186
+ }
187
+ else {
188
+ checks.push({
189
+ name: "sandbox-declared",
190
+ passed: false,
191
+ message: "No sandbox declared on agent or defaults",
192
+ });
193
+ }
194
+ // spawn-targets
195
+ const spawnErrors = [];
196
+ for (const target of agentDef.can_spawn) {
197
+ const targetAgent = config.agents[target.agent];
198
+ if (!targetAgent) {
199
+ spawnErrors.push(`Target "${target.agent}" not found in config`);
200
+ }
201
+ else if (!targetAgent.spawn_only) {
202
+ spawnErrors.push(`Target "${target.agent}" is missing spawn_only: true`);
203
+ }
204
+ }
205
+ if (spawnErrors.length === 0) {
206
+ checks.push({
207
+ name: "spawn-targets",
208
+ passed: true,
209
+ message: agentDef.can_spawn.length > 0
210
+ ? `All ${agentDef.can_spawn.length} spawn target(s) valid`
211
+ : "No spawn targets configured",
212
+ });
213
+ }
214
+ else {
215
+ checks.push({
216
+ name: "spawn-targets",
217
+ passed: false,
218
+ message: `Spawn target errors: ${spawnErrors.join("; ")}`,
219
+ });
220
+ }
221
+ // cron-refs
222
+ const cronJobs = config.cron.filter((job) => job.agent === agentName);
223
+ if (cronJobs.length > 0) {
224
+ const jobList = cronJobs
225
+ .map((j) => `${j.id} (${j.schedule}, ${j.enabled ? "enabled" : "disabled"})`)
226
+ .join(", ");
227
+ checks.push({
228
+ name: "cron-refs",
229
+ passed: true,
230
+ message: `Cron jobs: ${jobList}`,
231
+ });
232
+ }
233
+ else {
234
+ checks.push({
235
+ name: "cron-refs",
236
+ passed: true,
237
+ message: "No cron jobs reference this agent",
238
+ });
239
+ }
240
+ const success = checks.every((c) => c.passed);
241
+ return { agentName, success, checks };
242
+ }
@@ -0,0 +1,4 @@
1
+ import type { Message } from "../llm/types.js";
2
+ export declare function estimateTokens(message: Message): number;
3
+ export declare function estimateTotalTokens(messages: Message[]): number;
4
+ export declare function compactMessages(messages: Message[], model: string, tokenThreshold?: number): Promise<Message[]>;
@@ -0,0 +1,30 @@
1
+ import { complete } from "../llm/index.js";
2
+ import { extractText } from "../llm/types.js";
3
+ export function estimateTokens(message) {
4
+ return Math.ceil(JSON.stringify(message).length / 4);
5
+ }
6
+ export function estimateTotalTokens(messages) {
7
+ return messages.reduce((sum, msg) => sum + estimateTokens(msg), 0);
8
+ }
9
+ export async function compactMessages(messages, model, tokenThreshold = 100_000) {
10
+ const totalTokens = estimateTotalTokens(messages);
11
+ if (totalTokens < tokenThreshold)
12
+ return messages;
13
+ const splitPoint = Math.floor(messages.length / 2);
14
+ const oldMessages = messages.slice(0, splitPoint);
15
+ const recentMessages = messages.slice(splitPoint);
16
+ const summaryResponse = await complete(model, [{
17
+ role: "user",
18
+ content: "Summarize this conversation concisely. Preserve:\n" +
19
+ "- Key facts about the user (name, preferences)\n" +
20
+ "- Important decisions made\n" +
21
+ "- Open tasks or TODOs\n" +
22
+ "- File paths and code changes discussed\n\n" +
23
+ JSON.stringify(oldMessages, null, 2),
24
+ }]);
25
+ const summaryText = extractText(summaryResponse.content);
26
+ return [
27
+ { role: "user", content: `[Previous conversation summary]\n${summaryText}` },
28
+ ...recentMessages,
29
+ ];
30
+ }
@@ -0,0 +1,9 @@
1
+ import type { Message } from "../llm/types.js";
2
+ export declare class SessionManager {
3
+ private filePath;
4
+ constructor(sessionDir: string, sessionId: string);
5
+ getMessages(): Message[];
6
+ append(message: Message): void;
7
+ replaceAll(messages: Message[]): void;
8
+ static list(sessionDir: string): string[];
9
+ }