@bryti/agent 0.0.1 → 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 (228) hide show
  1. package/Dockerfile +27 -0
  2. package/README.md +77 -50
  3. package/config.example.yml +265 -0
  4. package/dist/active-hours.d.ts +23 -0
  5. package/dist/active-hours.d.ts.map +1 -0
  6. package/dist/active-hours.js +68 -0
  7. package/dist/active-hours.js.map +1 -0
  8. package/dist/agent.d.ts +84 -0
  9. package/dist/agent.d.ts.map +1 -0
  10. package/dist/agent.js +383 -0
  11. package/dist/agent.js.map +1 -0
  12. package/dist/channels/markdown/ir.d.ts +79 -0
  13. package/dist/channels/markdown/ir.d.ts.map +1 -0
  14. package/dist/channels/markdown/ir.js +824 -0
  15. package/dist/channels/markdown/ir.js.map +1 -0
  16. package/dist/channels/markdown/render.d.ts +35 -0
  17. package/dist/channels/markdown/render.d.ts.map +1 -0
  18. package/dist/channels/markdown/render.js +178 -0
  19. package/dist/channels/markdown/render.js.map +1 -0
  20. package/dist/channels/telegram-network-errors.d.ts +27 -0
  21. package/dist/channels/telegram-network-errors.d.ts.map +1 -0
  22. package/dist/channels/telegram-network-errors.js +156 -0
  23. package/dist/channels/telegram-network-errors.js.map +1 -0
  24. package/dist/channels/telegram.d.ts +76 -0
  25. package/dist/channels/telegram.d.ts.map +1 -0
  26. package/dist/channels/telegram.js +814 -0
  27. package/dist/channels/telegram.js.map +1 -0
  28. package/dist/channels/types.d.ts +59 -0
  29. package/dist/channels/types.d.ts.map +1 -0
  30. package/dist/channels/types.js +9 -0
  31. package/dist/channels/types.js.map +1 -0
  32. package/dist/channels/whatsapp.d.ts +45 -0
  33. package/dist/channels/whatsapp.d.ts.map +1 -0
  34. package/dist/channels/whatsapp.js +310 -0
  35. package/dist/channels/whatsapp.js.map +1 -0
  36. package/dist/cli.d.ts +13 -0
  37. package/dist/cli.d.ts.map +1 -0
  38. package/dist/cli.js +635 -0
  39. package/dist/cli.js.map +1 -0
  40. package/dist/commands.d.ts +35 -0
  41. package/dist/commands.d.ts.map +1 -0
  42. package/dist/commands.js +113 -0
  43. package/dist/commands.js.map +1 -0
  44. package/dist/compaction/history.d.ts +17 -0
  45. package/dist/compaction/history.d.ts.map +1 -0
  46. package/dist/compaction/history.js +35 -0
  47. package/dist/compaction/history.js.map +1 -0
  48. package/dist/compaction/index.d.ts +3 -0
  49. package/dist/compaction/index.d.ts.map +1 -0
  50. package/dist/compaction/index.js +3 -0
  51. package/dist/compaction/index.js.map +1 -0
  52. package/dist/compaction/proactive.d.ts +25 -0
  53. package/dist/compaction/proactive.d.ts.map +1 -0
  54. package/dist/compaction/proactive.js +87 -0
  55. package/dist/compaction/proactive.js.map +1 -0
  56. package/dist/compaction/transcript-repair.d.ts +55 -0
  57. package/dist/compaction/transcript-repair.d.ts.map +1 -0
  58. package/dist/compaction/transcript-repair.js +215 -0
  59. package/dist/compaction/transcript-repair.js.map +1 -0
  60. package/dist/config.d.ts +128 -0
  61. package/dist/config.d.ts.map +1 -0
  62. package/dist/config.js +317 -0
  63. package/dist/config.js.map +1 -0
  64. package/dist/crash-recovery.d.ts +23 -0
  65. package/dist/crash-recovery.d.ts.map +1 -0
  66. package/dist/crash-recovery.js +96 -0
  67. package/dist/crash-recovery.js.map +1 -0
  68. package/dist/defaults/extensions/EXTENSIONS.md +158 -0
  69. package/dist/defaults/extensions/documents-hedgedoc.ts +153 -0
  70. package/dist/history.d.ts +31 -0
  71. package/dist/history.d.ts.map +1 -0
  72. package/dist/history.js +49 -0
  73. package/dist/history.js.map +1 -0
  74. package/dist/index.d.ts +19 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +673 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/logger.d.ts +39 -0
  79. package/dist/logger.d.ts.map +1 -0
  80. package/dist/logger.js +143 -0
  81. package/dist/logger.js.map +1 -0
  82. package/dist/memory/conversation-search.d.ts +15 -0
  83. package/dist/memory/conversation-search.d.ts.map +1 -0
  84. package/dist/memory/conversation-search.js +60 -0
  85. package/dist/memory/conversation-search.js.map +1 -0
  86. package/dist/memory/core-memory.d.ts +28 -0
  87. package/dist/memory/core-memory.d.ts.map +1 -0
  88. package/dist/memory/core-memory.js +102 -0
  89. package/dist/memory/core-memory.js.map +1 -0
  90. package/dist/memory/embeddings.d.ts +44 -0
  91. package/dist/memory/embeddings.d.ts.map +1 -0
  92. package/dist/memory/embeddings.js +139 -0
  93. package/dist/memory/embeddings.js.map +1 -0
  94. package/dist/memory/search.d.ts +49 -0
  95. package/dist/memory/search.d.ts.map +1 -0
  96. package/dist/memory/search.js +97 -0
  97. package/dist/memory/search.js.map +1 -0
  98. package/dist/memory/store.d.ts +32 -0
  99. package/dist/memory/store.d.ts.map +1 -0
  100. package/dist/memory/store.js +205 -0
  101. package/dist/memory/store.js.map +1 -0
  102. package/dist/message-queue.d.ts +73 -0
  103. package/dist/message-queue.d.ts.map +1 -0
  104. package/dist/message-queue.js +188 -0
  105. package/dist/message-queue.js.map +1 -0
  106. package/dist/model-infra.d.ts +64 -0
  107. package/dist/model-infra.d.ts.map +1 -0
  108. package/dist/model-infra.js +202 -0
  109. package/dist/model-infra.js.map +1 -0
  110. package/dist/projection/format.d.ts +10 -0
  111. package/dist/projection/format.d.ts.map +1 -0
  112. package/dist/projection/format.js +30 -0
  113. package/dist/projection/format.js.map +1 -0
  114. package/dist/projection/index.d.ts +11 -0
  115. package/dist/projection/index.d.ts.map +1 -0
  116. package/dist/projection/index.js +9 -0
  117. package/dist/projection/index.js.map +1 -0
  118. package/dist/projection/reflection.d.ts +94 -0
  119. package/dist/projection/reflection.d.ts.map +1 -0
  120. package/dist/projection/reflection.js +334 -0
  121. package/dist/projection/reflection.js.map +1 -0
  122. package/dist/projection/store.d.ts +144 -0
  123. package/dist/projection/store.d.ts.map +1 -0
  124. package/dist/projection/store.js +519 -0
  125. package/dist/projection/store.js.map +1 -0
  126. package/dist/projection/tools.d.ts +11 -0
  127. package/dist/projection/tools.d.ts.map +1 -0
  128. package/dist/projection/tools.js +237 -0
  129. package/dist/projection/tools.js.map +1 -0
  130. package/dist/scheduler.d.ts +36 -0
  131. package/dist/scheduler.d.ts.map +1 -0
  132. package/dist/scheduler.js +286 -0
  133. package/dist/scheduler.js.map +1 -0
  134. package/dist/system-prompt.d.ts +41 -0
  135. package/dist/system-prompt.d.ts.map +1 -0
  136. package/dist/system-prompt.js +162 -0
  137. package/dist/system-prompt.js.map +1 -0
  138. package/dist/time.d.ts +52 -0
  139. package/dist/time.d.ts.map +1 -0
  140. package/dist/time.js +138 -0
  141. package/dist/time.js.map +1 -0
  142. package/dist/tools/archival-memory-tool.d.ts +8 -0
  143. package/dist/tools/archival-memory-tool.d.ts.map +1 -0
  144. package/dist/tools/archival-memory-tool.js +68 -0
  145. package/dist/tools/archival-memory-tool.js.map +1 -0
  146. package/dist/tools/conversation-search-tool.d.ts +6 -0
  147. package/dist/tools/conversation-search-tool.d.ts.map +1 -0
  148. package/dist/tools/conversation-search-tool.js +28 -0
  149. package/dist/tools/conversation-search-tool.js.map +1 -0
  150. package/dist/tools/core-memory-tool.d.ts +7 -0
  151. package/dist/tools/core-memory-tool.d.ts.map +1 -0
  152. package/dist/tools/core-memory-tool.js +59 -0
  153. package/dist/tools/core-memory-tool.js.map +1 -0
  154. package/dist/tools/fetch-url.d.ts +15 -0
  155. package/dist/tools/fetch-url.d.ts.map +1 -0
  156. package/dist/tools/fetch-url.js +76 -0
  157. package/dist/tools/fetch-url.js.map +1 -0
  158. package/dist/tools/files.d.ts +10 -0
  159. package/dist/tools/files.d.ts.map +1 -0
  160. package/dist/tools/files.js +127 -0
  161. package/dist/tools/files.js.map +1 -0
  162. package/dist/tools/index.d.ts +17 -0
  163. package/dist/tools/index.d.ts.map +1 -0
  164. package/dist/tools/index.js +118 -0
  165. package/dist/tools/index.js.map +1 -0
  166. package/dist/tools/result.d.ts +21 -0
  167. package/dist/tools/result.d.ts.map +1 -0
  168. package/dist/tools/result.js +36 -0
  169. package/dist/tools/result.js.map +1 -0
  170. package/dist/tools/skill-install.d.ts +17 -0
  171. package/dist/tools/skill-install.d.ts.map +1 -0
  172. package/dist/tools/skill-install.js +148 -0
  173. package/dist/tools/skill-install.js.map +1 -0
  174. package/dist/tools/web-search.d.ts +42 -0
  175. package/dist/tools/web-search.d.ts.map +1 -0
  176. package/dist/tools/web-search.js +237 -0
  177. package/dist/tools/web-search.js.map +1 -0
  178. package/dist/trust/guardrail.d.ts +60 -0
  179. package/dist/trust/guardrail.d.ts.map +1 -0
  180. package/dist/trust/guardrail.js +171 -0
  181. package/dist/trust/guardrail.js.map +1 -0
  182. package/dist/trust/index.d.ts +12 -0
  183. package/dist/trust/index.d.ts.map +1 -0
  184. package/dist/trust/index.js +12 -0
  185. package/dist/trust/index.js.map +1 -0
  186. package/dist/trust/store.d.ts +118 -0
  187. package/dist/trust/store.d.ts.map +1 -0
  188. package/dist/trust/store.js +209 -0
  189. package/dist/trust/store.js.map +1 -0
  190. package/dist/trust/wrapper.d.ts +36 -0
  191. package/dist/trust/wrapper.d.ts.map +1 -0
  192. package/dist/trust/wrapper.js +142 -0
  193. package/dist/trust/wrapper.js.map +1 -0
  194. package/dist/usage.d.ts +53 -0
  195. package/dist/usage.d.ts.map +1 -0
  196. package/dist/usage.js +124 -0
  197. package/dist/usage.js.map +1 -0
  198. package/dist/util/math.d.ts +9 -0
  199. package/dist/util/math.d.ts.map +1 -0
  200. package/dist/util/math.js +22 -0
  201. package/dist/util/math.js.map +1 -0
  202. package/dist/util/ssrf.d.ts +21 -0
  203. package/dist/util/ssrf.d.ts.map +1 -0
  204. package/dist/util/ssrf.js +77 -0
  205. package/dist/util/ssrf.js.map +1 -0
  206. package/dist/workers/index.d.ts +8 -0
  207. package/dist/workers/index.d.ts.map +1 -0
  208. package/dist/workers/index.js +7 -0
  209. package/dist/workers/index.js.map +1 -0
  210. package/dist/workers/registry.d.ts +53 -0
  211. package/dist/workers/registry.d.ts.map +1 -0
  212. package/dist/workers/registry.js +38 -0
  213. package/dist/workers/registry.js.map +1 -0
  214. package/dist/workers/scoped-tools.d.ts +21 -0
  215. package/dist/workers/scoped-tools.d.ts.map +1 -0
  216. package/dist/workers/scoped-tools.js +111 -0
  217. package/dist/workers/scoped-tools.js.map +1 -0
  218. package/dist/workers/spawn.d.ts +62 -0
  219. package/dist/workers/spawn.d.ts.map +1 -0
  220. package/dist/workers/spawn.js +314 -0
  221. package/dist/workers/spawn.js.map +1 -0
  222. package/dist/workers/tools.d.ts +26 -0
  223. package/dist/workers/tools.d.ts.map +1 -0
  224. package/dist/workers/tools.js +380 -0
  225. package/dist/workers/tools.js.map +1 -0
  226. package/docker-compose.yml +72 -0
  227. package/package.json +16 -1
  228. package/run.sh +27 -0
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Config loading and validation.
3
+ *
4
+ * Loads config.yml from the data directory, substitutes ${ENV_VAR} references,
5
+ * and validates required fields at startup so misconfigurations fail early.
6
+ */
7
+ import type { ActiveHoursConfig } from "./active-hours.js";
8
+ export type { ActiveHoursConfig };
9
+ export interface ProviderConfig {
10
+ name: string;
11
+ base_url: string;
12
+ api: string;
13
+ api_key: string;
14
+ models: ModelEntry[];
15
+ }
16
+ export interface ModelEntry {
17
+ id: string;
18
+ name?: string;
19
+ api?: string;
20
+ context_window?: number;
21
+ max_tokens?: number;
22
+ input?: ("text" | "image")[];
23
+ cost?: {
24
+ input: number;
25
+ output: number;
26
+ };
27
+ compat?: Record<string, unknown>;
28
+ }
29
+ export interface WorkerTypeConfig {
30
+ /** Description shown in the system prompt so the agent knows when to use this type. */
31
+ description?: string;
32
+ /** Model override for this worker type. */
33
+ model?: string;
34
+ /** Tools available to this worker type. Default: ["web_search", "fetch_url"]. */
35
+ tools?: string[];
36
+ /** Timeout in seconds. Default: 3600. */
37
+ timeout_seconds?: number;
38
+ }
39
+ export interface CronJob {
40
+ schedule: string;
41
+ message: string;
42
+ }
43
+ export interface Config {
44
+ agent: {
45
+ name: string;
46
+ /** Additional content prepended to the system prompt. Persona, standing
47
+ * instructions, or context about the user. The framework adds memory,
48
+ * tool descriptions, and all other sections automatically. */
49
+ system_prompt: string;
50
+ model: string;
51
+ /** Ordered list of fallback model strings to try when the primary fails. */
52
+ fallback_models: string[];
53
+ /** IANA timezone string, e.g. "Europe/Amsterdam". Injected into the system
54
+ * prompt so the agent resolves relative time expressions correctly.
55
+ * Defaults to UTC when omitted. */
56
+ timezone?: string;
57
+ /** Model to use for the reflection pass. Defaults to the primary model.
58
+ * Use this to pick a cheaper model for background reflection without
59
+ * affecting the main agent. Format: "provider/model-id". */
60
+ reflection_model?: string;
61
+ };
62
+ telegram: {
63
+ token: string;
64
+ allowed_users: number[];
65
+ };
66
+ whatsapp: {
67
+ enabled: boolean;
68
+ /** Phone numbers in international format without +, e.g. ["31612345678"] */
69
+ allowed_users: string[];
70
+ };
71
+ models: {
72
+ providers: ProviderConfig[];
73
+ };
74
+ tools: {
75
+ web_search: {
76
+ enabled: boolean;
77
+ /** SearXNG instance URL (no trailing slash). Workers only. */
78
+ searxng_url: string;
79
+ /** Brave Search API key. If set, used instead of SearXNG. Workers only. */
80
+ brave_api_key?: string;
81
+ };
82
+ fetch_url: {
83
+ timeout_ms: number;
84
+ };
85
+ files: {
86
+ base_dir: string;
87
+ };
88
+ workers: {
89
+ /** Maximum number of workers that may run concurrently. Default: 3. */
90
+ max_concurrent: number;
91
+ /** Default model for workers. Falls back to first fallback model, then primary. */
92
+ model?: string;
93
+ /** Named worker types with preset defaults. The agent can select a type
94
+ * when dispatching to get its model, tools, and timeout without
95
+ * specifying them individually. */
96
+ types?: Record<string, WorkerTypeConfig>;
97
+ };
98
+ };
99
+ /**
100
+ * Optional integrations. Each entry is injected into process.env at startup
101
+ * so extensions can read them without needing separate .env entries.
102
+ *
103
+ * Convention: integrations.<name>.<key> → env var NAME_KEY (uppercased, dots to underscores).
104
+ * Example: integrations.hedgedoc.url → HEDGEDOC_URL
105
+ */
106
+ integrations: Record<string, Record<string, string>>;
107
+ cron: CronJob[];
108
+ /** Optional active hours window. Scheduler callbacks skip firing outside it. */
109
+ active_hours?: ActiveHoursConfig;
110
+ /** Trust and permission settings. */
111
+ trust: {
112
+ /** Tools pre-approved for elevated access (skip permission prompts). */
113
+ approved_tools: string[];
114
+ };
115
+ data_dir: string;
116
+ }
117
+ /**
118
+ * Inject integration config values into process.env.
119
+ * Convention: integrations.<name>.<key> becomes <NAME>_<KEY> (uppercased).
120
+ * Existing env vars are never overwritten; .env always wins.
121
+ */
122
+ export declare function applyIntegrationEnvVars(config: Config): void;
123
+ export declare function loadConfig(configPath?: string): Config;
124
+ /**
125
+ * Ensure data directories exist.
126
+ */
127
+ export declare function ensureDataDirs(config: Config): void;
128
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/config.js ADDED
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Config loading and validation.
3
+ *
4
+ * Loads config.yml from the data directory, substitutes ${ENV_VAR} references,
5
+ * and validates required fields at startup so misconfigurations fail early.
6
+ */
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { parse as parseYaml } from "yaml";
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+ function toFiniteNumber(value) {
14
+ if (typeof value === "number" && Number.isFinite(value)) {
15
+ return value;
16
+ }
17
+ if (typeof value === "string" && value.trim()) {
18
+ const parsed = Number(value);
19
+ if (Number.isFinite(parsed)) {
20
+ return parsed;
21
+ }
22
+ }
23
+ return undefined;
24
+ }
25
+ function normalizeModelCost(cost) {
26
+ if (!cost || typeof cost !== "object") {
27
+ return undefined;
28
+ }
29
+ const raw = cost;
30
+ const hasAny = raw.input !== undefined || raw.output !== undefined;
31
+ if (!hasAny) {
32
+ return undefined;
33
+ }
34
+ return {
35
+ input: toFiniteNumber(raw.input) ?? 0,
36
+ output: toFiniteNumber(raw.output) ?? 0,
37
+ };
38
+ }
39
+ /**
40
+ * Replace ${VAR} references with values from process.env.
41
+ */
42
+ function substituteEnvVars(text) {
43
+ // Only substitute uppercase env-style names (e.g. ${TELEGRAM_BOT_TOKEN}).
44
+ // This avoids clobbering template literals in prompt/examples like ${city}.
45
+ return text.replace(/\$\{([A-Z_][A-Z0-9_]*)\}/g, (_match, varName) => {
46
+ const value = process.env[varName];
47
+ if (value === undefined) {
48
+ throw new Error(`Environment variable ${varName} is not set`);
49
+ }
50
+ return value;
51
+ });
52
+ }
53
+ /**
54
+ * Recursively substitute env vars in all string values of an object.
55
+ */
56
+ function substituteDeep(obj) {
57
+ if (typeof obj === "string") {
58
+ return substituteEnvVars(obj);
59
+ }
60
+ if (Array.isArray(obj)) {
61
+ return obj.map(substituteDeep);
62
+ }
63
+ if (obj !== null && typeof obj === "object") {
64
+ const result = {};
65
+ for (const [key, value] of Object.entries(obj)) {
66
+ result[key] = substituteDeep(value);
67
+ }
68
+ return result;
69
+ }
70
+ return obj;
71
+ }
72
+ /**
73
+ * Parse the integrations section, extracting string key-value pairs.
74
+ *
75
+ * Only string values are kept — nested objects, booleans, and numbers are
76
+ * ignored. Each entry's values can use ${ENV_VAR} substitution (already
77
+ * applied by substituteDeep before this is called).
78
+ */
79
+ function integrationsFromConfig(substituted) {
80
+ const raw = (substituted.integrations ?? {});
81
+ const result = {};
82
+ for (const [name, entry] of Object.entries(raw)) {
83
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
84
+ continue;
85
+ const values = {};
86
+ for (const [key, value] of Object.entries(entry)) {
87
+ if (typeof value === "string") {
88
+ values[key] = value;
89
+ }
90
+ }
91
+ if (Object.keys(values).length > 0) {
92
+ result[name] = values;
93
+ }
94
+ }
95
+ return result;
96
+ }
97
+ /**
98
+ * Inject integration config values into process.env.
99
+ * Convention: integrations.<name>.<key> becomes <NAME>_<KEY> (uppercased).
100
+ * Existing env vars are never overwritten; .env always wins.
101
+ */
102
+ export function applyIntegrationEnvVars(config) {
103
+ for (const [name, values] of Object.entries(config.integrations)) {
104
+ for (const [key, value] of Object.entries(values)) {
105
+ const envKey = `${name}_${key}`.toUpperCase().replace(/[^A-Z0-9]/g, "_");
106
+ if (process.env[envKey] === undefined) {
107
+ process.env[envKey] = value;
108
+ }
109
+ }
110
+ }
111
+ }
112
+ /**
113
+ * Parse the tools section of the substituted config, applying defaults.
114
+ */
115
+ function toolsFromConfig(substituted, dataDir) {
116
+ const raw = (substituted.tools ?? {});
117
+ const webRaw = (raw.web_search ?? {});
118
+ return {
119
+ web_search: {
120
+ enabled: webRaw.enabled !== false,
121
+ searxng_url: webRaw.searxng_url ?? "https://searx.be",
122
+ brave_api_key: webRaw.brave_api_key ?? undefined,
123
+ },
124
+ fetch_url: {
125
+ timeout_ms: 10000,
126
+ ...raw.fetch_url,
127
+ },
128
+ files: {
129
+ base_dir: path.join(dataDir, "files"),
130
+ ...raw.files,
131
+ },
132
+ workers: {
133
+ max_concurrent: 3,
134
+ ...raw.workers,
135
+ },
136
+ };
137
+ }
138
+ export function loadConfig(configPath) {
139
+ const dataDir = path.resolve(process.env.BRYTI_DATA_DIR || "./data");
140
+ const cfgPath = configPath || path.join(dataDir, "config.yml");
141
+ if (!fs.existsSync(cfgPath)) {
142
+ throw new Error(`Config file not found: ${cfgPath}`);
143
+ }
144
+ const raw = fs.readFileSync(cfgPath, "utf-8");
145
+ const parsed = parseYaml(raw);
146
+ const substituted = substituteDeep(parsed);
147
+ // Defaults
148
+ const config = {
149
+ agent: {
150
+ name: "Bryti",
151
+ system_prompt: "You are a helpful personal assistant.",
152
+ model: "",
153
+ fallback_models: [],
154
+ ...substituted.agent,
155
+ },
156
+ telegram: {
157
+ token: "",
158
+ allowed_users: [],
159
+ ...substituted.telegram,
160
+ },
161
+ whatsapp: {
162
+ enabled: substituted.whatsapp?.enabled ?? false,
163
+ allowed_users: (substituted.whatsapp?.allowed_users ?? []).map(String),
164
+ },
165
+ models: {
166
+ providers: [],
167
+ ...substituted.models,
168
+ },
169
+ tools: toolsFromConfig(substituted, dataDir),
170
+ integrations: integrationsFromConfig(substituted),
171
+ cron: substituted.cron || [],
172
+ active_hours: substituted.active_hours ?? undefined,
173
+ trust: {
174
+ approved_tools: (substituted.trust?.approved_tools) ?? [],
175
+ },
176
+ data_dir: dataDir,
177
+ };
178
+ for (const provider of config.models.providers) {
179
+ provider.models = provider.models.map((model) => ({
180
+ ...model,
181
+ cost: normalizeModelCost(model.cost),
182
+ }));
183
+ }
184
+ // Validate
185
+ validateConfig(config);
186
+ return config;
187
+ }
188
+ /**
189
+ * Validate config at startup so bad config doesn't surface 30 minutes later
190
+ * when a cron job fires.
191
+ */
192
+ function validateConfig(config) {
193
+ const errors = [];
194
+ const warnings = [];
195
+ // --- Required fields ---
196
+ if (!config.telegram.token && !config.whatsapp.enabled) {
197
+ errors.push("telegram.token is required (or enable whatsapp)");
198
+ }
199
+ if (!config.agent.model) {
200
+ errors.push("agent.model is required");
201
+ }
202
+ if (config.models.providers.length === 0) {
203
+ errors.push("at least one model provider is required");
204
+ }
205
+ // --- Primary model must be resolvable ---
206
+ if (config.agent.model) {
207
+ const [providerName] = config.agent.model.includes("/")
208
+ ? config.agent.model.split("/", 2)
209
+ : [config.agent.model];
210
+ const provider = config.models.providers.find((p) => p.name === providerName);
211
+ if (!provider) {
212
+ errors.push(`agent.model "${config.agent.model}" references provider "${providerName}" ` +
213
+ `which is not in models.providers. Available: ${config.models.providers.map((p) => p.name).join(", ")}`);
214
+ }
215
+ }
216
+ // --- Fallback models must reference known providers ---
217
+ for (const fb of config.agent.fallback_models ?? []) {
218
+ const [providerName] = fb.includes("/") ? fb.split("/", 2) : [fb];
219
+ const provider = config.models.providers.find((p) => p.name === providerName);
220
+ if (!provider) {
221
+ warnings.push(`fallback_model "${fb}" references unknown provider "${providerName}"`);
222
+ }
223
+ }
224
+ // --- WhatsApp needs allowed_users if enabled ---
225
+ if (config.whatsapp.enabled && config.whatsapp.allowed_users.length === 0) {
226
+ warnings.push("whatsapp is enabled but allowed_users is empty. " +
227
+ "Anyone who finds the bot's number can message it.");
228
+ }
229
+ // --- Web search needs a URL ---
230
+ if (config.tools.web_search.enabled && !config.tools.web_search.searxng_url) {
231
+ warnings.push("web_search is enabled but searxng_url is empty. Workers won't be able to search.");
232
+ }
233
+ // --- Emit ---
234
+ for (const w of warnings) {
235
+ console.warn(`Config warning: ${w}`);
236
+ }
237
+ if (errors.length > 0) {
238
+ throw new Error(`Config errors:\n - ${errors.join("\n - ")}`);
239
+ }
240
+ }
241
+ /**
242
+ * Ensure data directories exist.
243
+ */
244
+ export function ensureDataDirs(config) {
245
+ const dirs = [
246
+ config.data_dir,
247
+ path.join(config.data_dir, "history"),
248
+ path.join(config.data_dir, "files"),
249
+ path.join(config.data_dir, "files", "extensions"),
250
+ path.join(config.data_dir, "usage"),
251
+ path.join(config.data_dir, "logs"),
252
+ path.join(config.data_dir, ".pi"),
253
+ path.join(config.data_dir, "pending"),
254
+ path.join(config.data_dir, "skills"),
255
+ ];
256
+ // WhatsApp auth directory (always create in case user adds WhatsApp later)
257
+ dirs.push(path.join(config.data_dir, "whatsapp-auth"));
258
+ for (const dir of dirs) {
259
+ fs.mkdirSync(dir, { recursive: true });
260
+ }
261
+ // Write .pi/settings.json so pi discovers agent-written extensions.
262
+ // Pi's discoverAndLoadExtensions reads configuredPaths from settings and
263
+ // scans directories for .ts/.js files.
264
+ writeExtensionSettings(config);
265
+ // Seed default extensions into the user's extensions directory.
266
+ // Only copies when the target file does not exist.
267
+ // An empty file signals intentional deletion by the agent — never overwrite it.
268
+ seedDefaultExtensions(path.join(config.data_dir, "files", "extensions"));
269
+ }
270
+ /**
271
+ * Seed default extensions into the user's extensions directory.
272
+ *
273
+ * Only copies when the target file doesn't exist. Never overwrites. An empty
274
+ * file (0 bytes) is a tombstone meaning the agent intentionally deleted it;
275
+ * never replace those either.
276
+ */
277
+ function seedDefaultExtensions(extensionsDir) {
278
+ // In production (dist/): defaults are copied alongside the compiled output.
279
+ // In development (src/ via ts-node or tsx): walk up to project root.
280
+ const defaultsDir = path.join(__dirname, "defaults", "extensions");
281
+ if (!fs.existsSync(defaultsDir)) {
282
+ return;
283
+ }
284
+ for (const filename of fs.readdirSync(defaultsDir)) {
285
+ const target = path.join(extensionsDir, filename);
286
+ // File already exists (including empty tombstone) — leave it alone.
287
+ if (fs.existsSync(target)) {
288
+ continue;
289
+ }
290
+ const source = path.join(defaultsDir, filename);
291
+ fs.copyFileSync(source, target);
292
+ console.log(`[extensions] seeded default: ${filename}`);
293
+ }
294
+ }
295
+ /**
296
+ * Write pi settings pointing to the agent's extension directory so pi
297
+ * discovers extensions the agent writes via file_write.
298
+ */
299
+ function writeExtensionSettings(config) {
300
+ const settingsPath = path.join(config.data_dir, ".pi", "settings.json");
301
+ const extensionsDir = path.resolve(config.data_dir, "files", "extensions");
302
+ let settings = {};
303
+ if (fs.existsSync(settingsPath)) {
304
+ try {
305
+ settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
306
+ }
307
+ catch {
308
+ // Corrupted settings, overwrite
309
+ }
310
+ }
311
+ const currentPaths = Array.isArray(settings.extensions) ? settings.extensions : [];
312
+ if (!currentPaths.includes(extensionsDir)) {
313
+ settings.extensions = [...currentPaths, extensionsDir];
314
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
315
+ }
316
+ }
317
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Crash recovery: pending-message checkpoints.
3
+ *
4
+ * Before processing a user message, write a checkpoint to disk. If the process
5
+ * crashes mid-response, the next startup finds the checkpoint and notifies the
6
+ * user. Checkpoints are deleted after successful processing.
7
+ */
8
+ import type { Config } from "./config.js";
9
+ import type { IncomingMessage } from "./channels/types.js";
10
+ export interface PendingCheckpoint {
11
+ text: string;
12
+ channelId: string;
13
+ platform: string;
14
+ timestamp: number;
15
+ }
16
+ export declare function writePendingCheckpoint(config: Config, msg: IncomingMessage): void;
17
+ export declare function deletePendingCheckpoint(config: Config, userId: string): void;
18
+ /**
19
+ * Scan for leftover pending files from a previous crash. Files between
20
+ * 2 min and 1 hour old get a notification; older ones are silently discarded.
21
+ */
22
+ export declare function recoverPendingCheckpoints(config: Config, sendNotification: (checkpoint: PendingCheckpoint, userId: string) => Promise<void>): Promise<void>;
23
+ //# sourceMappingURL=crash-recovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crash-recovery.d.ts","sourceRoot":"","sources":["../src/crash-recovery.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAUD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,GAAG,IAAI,CAYjF;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAM5E;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,CAAC,UAAU,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GACjF,OAAO,CAAC,IAAI,CAAC,CAyDf"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Crash recovery: pending-message checkpoints.
3
+ *
4
+ * Before processing a user message, write a checkpoint to disk. If the process
5
+ * crashes mid-response, the next startup finds the checkpoint and notifies the
6
+ * user. Checkpoints are deleted after successful processing.
7
+ */
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ function pendingDir(config) {
11
+ return path.join(config.data_dir, "pending");
12
+ }
13
+ function pendingPath(config, userId) {
14
+ return path.join(pendingDir(config), `${userId}.json`);
15
+ }
16
+ export function writePendingCheckpoint(config, msg) {
17
+ const checkpoint = {
18
+ text: msg.text,
19
+ channelId: msg.channelId,
20
+ platform: msg.platform,
21
+ timestamp: Date.now(),
22
+ };
23
+ try {
24
+ fs.writeFileSync(pendingPath(config, msg.userId), JSON.stringify(checkpoint), "utf8");
25
+ }
26
+ catch (err) {
27
+ console.warn("[pending] Failed to write checkpoint:", err.message);
28
+ }
29
+ }
30
+ export function deletePendingCheckpoint(config, userId) {
31
+ try {
32
+ fs.rmSync(pendingPath(config, userId), { force: true });
33
+ }
34
+ catch (err) {
35
+ console.warn("[pending] Failed to delete checkpoint:", err.message);
36
+ }
37
+ }
38
+ /**
39
+ * Scan for leftover pending files from a previous crash. Files between
40
+ * 2 min and 1 hour old get a notification; older ones are silently discarded.
41
+ */
42
+ export async function recoverPendingCheckpoints(config, sendNotification) {
43
+ const dir = pendingDir(config);
44
+ let entries;
45
+ try {
46
+ entries = fs.readdirSync(dir).filter((f) => f.endsWith(".json") && f !== "restart.json");
47
+ }
48
+ catch {
49
+ return;
50
+ }
51
+ if (entries.length === 0)
52
+ return;
53
+ const now = Date.now();
54
+ const MIN_AGE_MS = 2 * 60 * 1000; // 2 minutes: ignore files written moments before a clean restart
55
+ const MAX_AGE_MS = 60 * 60 * 1000; // 1 hour: too stale to be useful
56
+ // Group by userId (filename = <userId>.json), keep most recent per user
57
+ const byUser = new Map();
58
+ for (const entry of entries) {
59
+ const filePath = path.join(dir, entry);
60
+ let checkpoint;
61
+ try {
62
+ checkpoint = JSON.parse(fs.readFileSync(filePath, "utf8"));
63
+ }
64
+ catch {
65
+ fs.rmSync(filePath, { force: true });
66
+ continue;
67
+ }
68
+ const userId = entry.slice(0, -5); // strip .json
69
+ const existing = byUser.get(userId);
70
+ if (!existing || checkpoint.timestamp > existing.checkpoint.timestamp) {
71
+ byUser.set(userId, { checkpoint, filePath });
72
+ }
73
+ }
74
+ for (const [userId, { checkpoint, filePath }] of byUser) {
75
+ const age = now - checkpoint.timestamp;
76
+ // Always delete the file first to prevent repeat notifications on the next restart
77
+ try {
78
+ fs.rmSync(filePath, { force: true });
79
+ }
80
+ catch {
81
+ // ignore
82
+ }
83
+ if (age < MIN_AGE_MS || age > MAX_AGE_MS) {
84
+ console.log(`[pending] Skipping stale checkpoint for ${userId} (age ${Math.round(age / 1000)}s)`);
85
+ continue;
86
+ }
87
+ console.log(`[pending] Crash recovery: notifying ${userId} (age ${Math.round(age / 1000)}s)`);
88
+ try {
89
+ await sendNotification(checkpoint, userId);
90
+ }
91
+ catch (err) {
92
+ console.warn(`[pending] Failed to notify ${userId}:`, err.message);
93
+ }
94
+ }
95
+ }
96
+ //# sourceMappingURL=crash-recovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crash-recovery.js","sourceRoot":"","sources":["../src/crash-recovery.ts"],"names":[],"mappings":"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"}