camelagi 0.5.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 (249) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +224 -0
  3. package/camelagi.mjs +2 -0
  4. package/config.example.yaml +107 -0
  5. package/dist/agent/agent-openai.js +206 -0
  6. package/dist/agent/agent-openai.js.map +1 -0
  7. package/dist/agent/agent-sdk.js +209 -0
  8. package/dist/agent/agent-sdk.js.map +1 -0
  9. package/dist/agent/tool-adapter.js +31 -0
  10. package/dist/agent/tool-adapter.js.map +1 -0
  11. package/dist/agent/types.js +3 -0
  12. package/dist/agent/types.js.map +1 -0
  13. package/dist/agent.js +17 -0
  14. package/dist/agent.js.map +1 -0
  15. package/dist/approval-forward.js +42 -0
  16. package/dist/approval-forward.js.map +1 -0
  17. package/dist/approvals.js +151 -0
  18. package/dist/approvals.js.map +1 -0
  19. package/dist/boot.js +34 -0
  20. package/dist/boot.js.map +1 -0
  21. package/dist/bootstrap.js +451 -0
  22. package/dist/bootstrap.js.map +1 -0
  23. package/dist/camelagi-gateway.mjs +93611 -0
  24. package/dist/camelagi-gateway.mjs.map +7 -0
  25. package/dist/channels/adapter.js +10 -0
  26. package/dist/channels/adapter.js.map +1 -0
  27. package/dist/channels/discord.js +232 -0
  28. package/dist/channels/discord.js.map +1 -0
  29. package/dist/channels/handler.js +349 -0
  30. package/dist/channels/handler.js.map +1 -0
  31. package/dist/channels/index.js +19 -0
  32. package/dist/channels/index.js.map +1 -0
  33. package/dist/channels/registry.js +71 -0
  34. package/dist/channels/registry.js.map +1 -0
  35. package/dist/channels/telegram.js +83 -0
  36. package/dist/channels/telegram.js.map +1 -0
  37. package/dist/channels/types.js +3 -0
  38. package/dist/channels/types.js.map +1 -0
  39. package/dist/chunker.js +102 -0
  40. package/dist/chunker.js.map +1 -0
  41. package/dist/cli/cmd-agents.js +65 -0
  42. package/dist/cli/cmd-agents.js.map +1 -0
  43. package/dist/cli/cmd-bootstrap.js +10 -0
  44. package/dist/cli/cmd-bootstrap.js.map +1 -0
  45. package/dist/cli/cmd-chat.js +32 -0
  46. package/dist/cli/cmd-chat.js.map +1 -0
  47. package/dist/cli/cmd-config.js +88 -0
  48. package/dist/cli/cmd-config.js.map +1 -0
  49. package/dist/cli/cmd-cron.js +120 -0
  50. package/dist/cli/cmd-cron.js.map +1 -0
  51. package/dist/cli/cmd-daemon.js +37 -0
  52. package/dist/cli/cmd-daemon.js.map +1 -0
  53. package/dist/cli/cmd-doctor.js +18 -0
  54. package/dist/cli/cmd-doctor.js.map +1 -0
  55. package/dist/cli/cmd-logs.js +30 -0
  56. package/dist/cli/cmd-logs.js.map +1 -0
  57. package/dist/cli/cmd-pairing.js +41 -0
  58. package/dist/cli/cmd-pairing.js.map +1 -0
  59. package/dist/cli/cmd-reset.js +39 -0
  60. package/dist/cli/cmd-reset.js.map +1 -0
  61. package/dist/cli/cmd-serve.js +30 -0
  62. package/dist/cli/cmd-serve.js.map +1 -0
  63. package/dist/cli/cmd-sessions.js +56 -0
  64. package/dist/cli/cmd-sessions.js.map +1 -0
  65. package/dist/cli/cmd-setup.js +11 -0
  66. package/dist/cli/cmd-setup.js.map +1 -0
  67. package/dist/cli/cmd-soul.js +43 -0
  68. package/dist/cli/cmd-soul.js.map +1 -0
  69. package/dist/cli/parse.js +50 -0
  70. package/dist/cli/parse.js.map +1 -0
  71. package/dist/cli/registry.js +15 -0
  72. package/dist/cli/registry.js.map +1 -0
  73. package/dist/cli.js +103 -0
  74. package/dist/cli.js.map +1 -0
  75. package/dist/compact.js +92 -0
  76. package/dist/compact.js.map +1 -0
  77. package/dist/config.js +153 -0
  78. package/dist/config.js.map +1 -0
  79. package/dist/constants.js +21 -0
  80. package/dist/constants.js.map +1 -0
  81. package/dist/core/config.js +212 -0
  82. package/dist/core/config.js.map +1 -0
  83. package/dist/core/constants.js +21 -0
  84. package/dist/core/constants.js.map +1 -0
  85. package/dist/core/errors.js +5 -0
  86. package/dist/core/errors.js.map +1 -0
  87. package/dist/core/log.js +41 -0
  88. package/dist/core/log.js.map +1 -0
  89. package/dist/core/models.js +123 -0
  90. package/dist/core/models.js.map +1 -0
  91. package/dist/core/types.js +3 -0
  92. package/dist/core/types.js.map +1 -0
  93. package/dist/core/update-check.js +51 -0
  94. package/dist/core/update-check.js.map +1 -0
  95. package/dist/cron.js +81 -0
  96. package/dist/cron.js.map +1 -0
  97. package/dist/daemon.js +109 -0
  98. package/dist/daemon.js.map +1 -0
  99. package/dist/doctor.js +194 -0
  100. package/dist/doctor.js.map +1 -0
  101. package/dist/errors.js +5 -0
  102. package/dist/errors.js.map +1 -0
  103. package/dist/extensions/approval-forward.js +42 -0
  104. package/dist/extensions/approval-forward.js.map +1 -0
  105. package/dist/extensions/approvals.js +144 -0
  106. package/dist/extensions/approvals.js.map +1 -0
  107. package/dist/extensions/cron.js +306 -0
  108. package/dist/extensions/cron.js.map +1 -0
  109. package/dist/extensions/hooks.js +72 -0
  110. package/dist/extensions/hooks.js.map +1 -0
  111. package/dist/extensions/skills.js +97 -0
  112. package/dist/extensions/skills.js.map +1 -0
  113. package/dist/gateway/csrf.js +44 -0
  114. package/dist/gateway/csrf.js.map +1 -0
  115. package/dist/gateway/logger.js +81 -0
  116. package/dist/gateway/logger.js.map +1 -0
  117. package/dist/gateway/rate-limit.js +33 -0
  118. package/dist/gateway/rate-limit.js.map +1 -0
  119. package/dist/gateway/routes.js +315 -0
  120. package/dist/gateway/routes.js.map +1 -0
  121. package/dist/gateway/state.js +54 -0
  122. package/dist/gateway/state.js.map +1 -0
  123. package/dist/gateway/ws-handler.js +200 -0
  124. package/dist/gateway/ws-handler.js.map +1 -0
  125. package/dist/gateway-entry.js +16 -0
  126. package/dist/gateway-entry.js.map +1 -0
  127. package/dist/hooks.js +72 -0
  128. package/dist/hooks.js.map +1 -0
  129. package/dist/lanes.js +62 -0
  130. package/dist/lanes.js.map +1 -0
  131. package/dist/model.js +30 -0
  132. package/dist/model.js.map +1 -0
  133. package/dist/policy.js +22 -0
  134. package/dist/policy.js.map +1 -0
  135. package/dist/queue.js +45 -0
  136. package/dist/queue.js.map +1 -0
  137. package/dist/retry.js +96 -0
  138. package/dist/retry.js.map +1 -0
  139. package/dist/runs.js +83 -0
  140. package/dist/runs.js.map +1 -0
  141. package/dist/runtime/compact.js +99 -0
  142. package/dist/runtime/compact.js.map +1 -0
  143. package/dist/runtime/lanes.js +66 -0
  144. package/dist/runtime/lanes.js.map +1 -0
  145. package/dist/runtime/orchestrate.js +121 -0
  146. package/dist/runtime/orchestrate.js.map +1 -0
  147. package/dist/runtime/queue.js +50 -0
  148. package/dist/runtime/queue.js.map +1 -0
  149. package/dist/runtime/retry.js +127 -0
  150. package/dist/runtime/retry.js.map +1 -0
  151. package/dist/runtime/runs.js +105 -0
  152. package/dist/runtime/runs.js.map +1 -0
  153. package/dist/serve.js +209 -0
  154. package/dist/serve.js.map +1 -0
  155. package/dist/session.js +75 -0
  156. package/dist/session.js.map +1 -0
  157. package/dist/setup.js +254 -0
  158. package/dist/setup.js.map +1 -0
  159. package/dist/skills.js +89 -0
  160. package/dist/skills.js.map +1 -0
  161. package/dist/subagent.js +71 -0
  162. package/dist/subagent.js.map +1 -0
  163. package/dist/system-prompt.js +157 -0
  164. package/dist/system-prompt.js.map +1 -0
  165. package/dist/telegram/admin-bot.js +705 -0
  166. package/dist/telegram/admin-bot.js.map +1 -0
  167. package/dist/telegram/agent-bot.js +551 -0
  168. package/dist/telegram/agent-bot.js.map +1 -0
  169. package/dist/telegram/bot-approval.js +63 -0
  170. package/dist/telegram/bot-approval.js.map +1 -0
  171. package/dist/telegram/draft-stream.js +86 -0
  172. package/dist/telegram/draft-stream.js.map +1 -0
  173. package/dist/telegram/format.js +106 -0
  174. package/dist/telegram/format.js.map +1 -0
  175. package/dist/telegram/helpers.js +87 -0
  176. package/dist/telegram/helpers.js.map +1 -0
  177. package/dist/telegram/pairing-notify.js +52 -0
  178. package/dist/telegram/pairing-notify.js.map +1 -0
  179. package/dist/telegram/pairing.js +138 -0
  180. package/dist/telegram/pairing.js.map +1 -0
  181. package/dist/telegram/resolve.js +33 -0
  182. package/dist/telegram/resolve.js.map +1 -0
  183. package/dist/telegram/transcribe.js +77 -0
  184. package/dist/telegram/transcribe.js.map +1 -0
  185. package/dist/telegram/types.js +3 -0
  186. package/dist/telegram/types.js.map +1 -0
  187. package/dist/telegram/voice-wizard.js +84 -0
  188. package/dist/telegram/voice-wizard.js.map +1 -0
  189. package/dist/telegram/wizard.js +89 -0
  190. package/dist/telegram/wizard.js.map +1 -0
  191. package/dist/telegram/wizards.js +297 -0
  192. package/dist/telegram/wizards.js.map +1 -0
  193. package/dist/telegram-admin.js +800 -0
  194. package/dist/telegram-admin.js.map +1 -0
  195. package/dist/telegram.js +118 -0
  196. package/dist/telegram.js.map +1 -0
  197. package/dist/tools/cron.js +94 -0
  198. package/dist/tools/cron.js.map +1 -0
  199. package/dist/tools/edit.js +29 -0
  200. package/dist/tools/edit.js.map +1 -0
  201. package/dist/tools/exec.js +38 -0
  202. package/dist/tools/exec.js.map +1 -0
  203. package/dist/tools/fetch.js +28 -0
  204. package/dist/tools/fetch.js.map +1 -0
  205. package/dist/tools/index.js +16 -0
  206. package/dist/tools/index.js.map +1 -0
  207. package/dist/tools/memory.js +164 -0
  208. package/dist/tools/memory.js.map +1 -0
  209. package/dist/tools/patch.js +284 -0
  210. package/dist/tools/patch.js.map +1 -0
  211. package/dist/tools/read.js +26 -0
  212. package/dist/tools/read.js.map +1 -0
  213. package/dist/tools/search.js +62 -0
  214. package/dist/tools/search.js.map +1 -0
  215. package/dist/tools/subagent.js +48 -0
  216. package/dist/tools/subagent.js.map +1 -0
  217. package/dist/tools/write.js +22 -0
  218. package/dist/tools/write.js.map +1 -0
  219. package/dist/tui/commands.js +450 -0
  220. package/dist/tui/commands.js.map +1 -0
  221. package/dist/tui/components/assistant-message.js +26 -0
  222. package/dist/tui/components/assistant-message.js.map +1 -0
  223. package/dist/tui/components/chat-log.js +94 -0
  224. package/dist/tui/components/chat-log.js.map +1 -0
  225. package/dist/tui/components/custom-editor.js +40 -0
  226. package/dist/tui/components/custom-editor.js.map +1 -0
  227. package/dist/tui/components/hint-bar.js +13 -0
  228. package/dist/tui/components/hint-bar.js.map +1 -0
  229. package/dist/tui/components/tool-execution.js +73 -0
  230. package/dist/tui/components/tool-execution.js.map +1 -0
  231. package/dist/tui/components/user-message.js +19 -0
  232. package/dist/tui/components/user-message.js.map +1 -0
  233. package/dist/tui/components/welcome.js +147 -0
  234. package/dist/tui/components/welcome.js.map +1 -0
  235. package/dist/tui/context.js +3 -0
  236. package/dist/tui/context.js.map +1 -0
  237. package/dist/tui/theme.js +91 -0
  238. package/dist/tui/theme.js.map +1 -0
  239. package/dist/tui/tui.js +389 -0
  240. package/dist/tui/tui.js.map +1 -0
  241. package/dist/tui/ws-handler.js +154 -0
  242. package/dist/tui/ws-handler.js.map +1 -0
  243. package/dist/types.js +3 -0
  244. package/dist/types.js.map +1 -0
  245. package/dist/usage.js +88 -0
  246. package/dist/usage.js.map +1 -0
  247. package/dist/workspace.js +245 -0
  248. package/dist/workspace.js.map +1 -0
  249. package/package.json +74 -0
@@ -0,0 +1,144 @@
1
+ // Approval system: gate dangerous tool calls behind user confirmation
2
+ //
3
+ // Modes:
4
+ // off — bypass all (current default, zero friction)
5
+ // smart — auto-approve reads, ask for writes/exec
6
+ // always — ask for every tool call
7
+ //
8
+ // Flow:
9
+ // PreToolUse hook → checkApproval() → auto or ask
10
+ // If ask: emit approval_request event → waitForDecision() → user responds → submitDecision()
11
+ import { randomUUID } from "node:crypto";
12
+ import { loadConfig, saveConfig } from "../core/config.js";
13
+ // Tools that "smart" mode auto-approves (read-only, no side effects)
14
+ const READ_ONLY_TOOLS = new Set([
15
+ "Read", "Glob", "Grep",
16
+ "WebSearch", "WebFetch",
17
+ "memory_search", "memory_get",
18
+ ]);
19
+ // --- Allowlist matching ---
20
+ function matchesAllowlist(toolName, args, allowlist) {
21
+ for (const entry of allowlist) {
22
+ const colonIdx = entry.indexOf(":");
23
+ if (colonIdx === -1) {
24
+ // Bare tool name: "Read", "Glob" — matches all calls to that tool
25
+ if (toolName === entry)
26
+ return true;
27
+ continue;
28
+ }
29
+ const entryTool = entry.slice(0, colonIdx);
30
+ const entryPattern = entry.slice(colonIdx + 1);
31
+ if (entryTool !== toolName)
32
+ continue;
33
+ // For Bash, match against the command string
34
+ if (toolName === "Bash") {
35
+ const cmd = String(args.command ?? "");
36
+ if (globMatch(entryPattern, cmd))
37
+ return true;
38
+ }
39
+ // For Write/Edit, match file path
40
+ else if (toolName === "Write" || toolName === "Edit") {
41
+ const filePath = String(args.file_path ?? "");
42
+ if (globMatch(entryPattern, filePath))
43
+ return true;
44
+ }
45
+ // For apply_patch, match if the pattern is "*" (blanket allow)
46
+ else if (toolName === "apply_patch") {
47
+ if (entryPattern === "*")
48
+ return true;
49
+ }
50
+ }
51
+ return false;
52
+ }
53
+ /** Simple glob matching — supports * as wildcard */
54
+ function globMatch(pattern, text) {
55
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
56
+ return new RegExp("^" + escaped + "$").test(text);
57
+ }
58
+ // --- Preview builder ---
59
+ function buildPreview(toolName, args) {
60
+ if (toolName === "Bash")
61
+ return String(args.command ?? "").slice(0, 200);
62
+ if (toolName === "Write")
63
+ return `write → ${args.file_path ?? "?"}`;
64
+ if (toolName === "Edit")
65
+ return `edit → ${args.file_path ?? "?"}`;
66
+ if (toolName === "Agent")
67
+ return `spawn agent: ${String(args.prompt ?? "").slice(0, 100)}`;
68
+ if (toolName === "apply_patch")
69
+ return `patch (${String(args.patch ?? "").split("\n").length} lines)`;
70
+ return `${toolName}(${JSON.stringify(args).slice(0, 120)})`;
71
+ }
72
+ // --- Factory ---
73
+ export function createApprovalManager() {
74
+ const pending = new Map();
75
+ function checkApproval(toolName, args, mode, allowlist) {
76
+ if (mode === "off")
77
+ return null;
78
+ if (matchesAllowlist(toolName, args, allowlist))
79
+ return null;
80
+ if (mode === "smart" && READ_ONLY_TOOLS.has(toolName))
81
+ return null;
82
+ return {
83
+ id: randomUUID(),
84
+ toolName,
85
+ args,
86
+ preview: buildPreview(toolName, args),
87
+ };
88
+ }
89
+ function waitForDecision(id, timeoutMs, fallback) {
90
+ return new Promise((resolve) => {
91
+ const timer = setTimeout(() => {
92
+ pending.delete(id);
93
+ resolve(fallback === "allow" ? "allow-once" : "deny");
94
+ }, timeoutMs);
95
+ pending.set(id, { resolve, timer });
96
+ });
97
+ }
98
+ function submitDecision(id, decision) {
99
+ const p = pending.get(id);
100
+ if (!p)
101
+ return false;
102
+ clearTimeout(p.timer);
103
+ pending.delete(id);
104
+ p.resolve(decision);
105
+ return true;
106
+ }
107
+ function addToAllowlist(toolName, args) {
108
+ const config = loadConfig();
109
+ const current = [...(config.approvals.allowlist ?? [])];
110
+ let entry;
111
+ if (toolName === "Bash") {
112
+ const cmd = String(args.command ?? "").trim();
113
+ const baseCmd = cmd.split(/[\s|;&]/)[0]; // first word before space/pipe/chain
114
+ entry = baseCmd ? `Bash:${baseCmd} *` : "Bash";
115
+ }
116
+ else if (toolName === "Write" || toolName === "Edit") {
117
+ const filePath = String(args.file_path ?? "");
118
+ entry = filePath ? `${toolName}:${filePath}` : toolName;
119
+ }
120
+ else {
121
+ entry = toolName;
122
+ }
123
+ if (!current.includes(entry)) {
124
+ current.push(entry);
125
+ saveConfig({
126
+ approvals: { ...config.approvals, allowlist: current },
127
+ });
128
+ }
129
+ }
130
+ function pendingCount() {
131
+ return pending.size;
132
+ }
133
+ function reset() {
134
+ for (const [, p] of pending) {
135
+ clearTimeout(p.timer);
136
+ }
137
+ pending.clear();
138
+ }
139
+ return { checkApproval, waitForDecision, submitDecision, addToAllowlist, pendingCount, reset };
140
+ }
141
+ // Backward-compat singleton
142
+ const defaultManager = createApprovalManager();
143
+ export const { checkApproval, waitForDecision, submitDecision, addToAllowlist, pendingCount } = defaultManager;
144
+ //# sourceMappingURL=approvals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approvals.js","sourceRoot":"","sources":["../../src/extensions/approvals.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,306 @@
1
+ // Cron: scheduled agent runs with runtime management
2
+ //
3
+ // Two sources of jobs:
4
+ // 1. Config-defined (config.yaml `cron:` array) — read-only from here
5
+ // 2. Runtime-defined (~/.camelagi/cron/jobs.json) — CRUD via tool/CLI
6
+ //
7
+ // Schedule formats:
8
+ // "5m", "1h", "1d" — repeating interval
9
+ // "*/5 * * * *" — cron expression (interval extracted from minute field)
10
+ // "+20m", "+2h" — one-shot relative (runs once, then auto-deletes)
11
+ // "2026-03-14T09:00:00Z" — one-shot absolute ISO timestamp
12
+ import { runAgent } from "../agent.js";
13
+ import { loadMessages, saveMessage } from "../session.js";
14
+ import { paths } from "../core/config.js";
15
+ import fs from "node:fs";
16
+ import path from "node:path";
17
+ // --- Module state ---
18
+ const activeJobs = new Map();
19
+ let serverConfig = null;
20
+ let serverSystemPrompt = "";
21
+ const cronDir = path.join(paths.configDir, "cron");
22
+ const storeFile = path.join(cronDir, "jobs.json");
23
+ /** Set server context so runtime-added jobs can auto-start */
24
+ export function setCronContext(config, systemPrompt) {
25
+ serverConfig = config;
26
+ serverSystemPrompt = systemPrompt;
27
+ }
28
+ // --- Schedule parsing ---
29
+ function parseInterval(schedule) {
30
+ const match = schedule.match(/^(\d+)(s|m|h|d)$/);
31
+ if (!match)
32
+ return null;
33
+ const value = parseInt(match[1], 10);
34
+ switch (match[2]) {
35
+ case "s": return value * 1000;
36
+ case "m": return value * 60_000;
37
+ case "h": return value * 3_600_000;
38
+ case "d": return value * 86_400_000;
39
+ default: return null;
40
+ }
41
+ }
42
+ function isOneShot(schedule) {
43
+ if (schedule.startsWith("+"))
44
+ return true;
45
+ const d = new Date(schedule);
46
+ return !isNaN(d.getTime()) && schedule.length > 8;
47
+ }
48
+ function parseOneShotDelay(schedule) {
49
+ if (schedule.startsWith("+")) {
50
+ const interval = parseInterval(schedule.slice(1));
51
+ return interval ?? null;
52
+ }
53
+ const d = new Date(schedule);
54
+ if (isNaN(d.getTime()))
55
+ return null;
56
+ return Math.max(0, d.getTime() - Date.now());
57
+ }
58
+ function parseCronInterval(schedule) {
59
+ const interval = parseInterval(schedule);
60
+ if (interval)
61
+ return interval;
62
+ const parts = schedule.split(/\s+/);
63
+ if (parts.length === 5) {
64
+ const [min] = parts;
65
+ if (min.startsWith("*/"))
66
+ return parseInt(min.slice(2), 10) * 60_000;
67
+ }
68
+ return 60_000;
69
+ }
70
+ // --- Backoff (30s → 1m → 5m → 15m → 60m) ---
71
+ const BACKOFF = [30_000, 60_000, 300_000, 900_000, 3_600_000];
72
+ function backoffDelay(errors) {
73
+ return BACKOFF[Math.min(errors - 1, BACKOFF.length - 1)] ?? 30_000;
74
+ }
75
+ // --- Runtime store (~/.camelagi/cron/jobs.json) ---
76
+ export function loadRuntimeJobs() {
77
+ try {
78
+ if (!fs.existsSync(storeFile))
79
+ return [];
80
+ const raw = JSON.parse(fs.readFileSync(storeFile, "utf-8"));
81
+ return (raw.jobs ?? []);
82
+ }
83
+ catch {
84
+ return [];
85
+ }
86
+ }
87
+ function saveRuntimeJobs(jobs) {
88
+ fs.mkdirSync(cronDir, { recursive: true });
89
+ fs.writeFileSync(storeFile, JSON.stringify({ version: 1, jobs }, null, 2));
90
+ }
91
+ // --- Core: start/stop jobs ---
92
+ export function startCronJob(job, config, systemPrompt, opts) {
93
+ if (activeJobs.has(job.id))
94
+ stopCronJob(job.id);
95
+ const sid = job.session ?? `cron-${job.id}`;
96
+ const oneShot = isOneShot(job.schedule);
97
+ const active = {
98
+ job,
99
+ timer: null,
100
+ consecutiveErrors: 0,
101
+ running: false,
102
+ };
103
+ const run = async () => {
104
+ if (active.running)
105
+ return;
106
+ active.running = true;
107
+ try {
108
+ const history = loadMessages(sid);
109
+ const result = await runAgent(config.apiKey, config.model, systemPrompt, history, job.prompt, {
110
+ maxTurns: opts?.maxTurns ?? 10,
111
+ timeoutMs: opts?.timeoutMs ?? 120_000,
112
+ provider: config.provider,
113
+ baseUrl: config.baseUrl,
114
+ });
115
+ saveMessage(sid, { role: "user", content: job.prompt }, "cron");
116
+ if (result.response)
117
+ saveMessage(sid, { role: "assistant", content: result.response }, "cron");
118
+ active.lastRunAt = Date.now();
119
+ active.lastStatus = "ok";
120
+ active.lastError = undefined;
121
+ active.consecutiveErrors = 0;
122
+ active.running = false;
123
+ opts?.onRun?.(job.id, result.response);
124
+ // One-shot: auto-remove after success
125
+ if (oneShot && job.deleteAfterRun !== false) {
126
+ stopCronJob(job.id);
127
+ removeRuntimeJob(job.id);
128
+ }
129
+ }
130
+ catch (err) {
131
+ const error = err instanceof Error ? err : new Error(String(err));
132
+ active.lastRunAt = Date.now();
133
+ active.lastStatus = "error";
134
+ active.lastError = error.message;
135
+ active.consecutiveErrors++;
136
+ active.running = false;
137
+ opts?.onError?.(job.id, error);
138
+ }
139
+ };
140
+ // Schedule using setTimeout chains (allows dynamic backoff)
141
+ const scheduleNext = () => {
142
+ if (!activeJobs.has(job.id))
143
+ return;
144
+ const delay = active.consecutiveErrors > 0
145
+ ? backoffDelay(active.consecutiveErrors)
146
+ : parseCronInterval(job.schedule);
147
+ active.timer = setTimeout(async () => {
148
+ await run();
149
+ if (!oneShot)
150
+ scheduleNext();
151
+ }, delay);
152
+ };
153
+ activeJobs.set(job.id, active);
154
+ if (oneShot) {
155
+ const delay = parseOneShotDelay(job.schedule);
156
+ if (delay === null) {
157
+ activeJobs.delete(job.id);
158
+ return;
159
+ }
160
+ active.timer = setTimeout(run, delay);
161
+ }
162
+ else {
163
+ // Run immediately, then schedule repeating
164
+ void (async () => {
165
+ await run();
166
+ scheduleNext();
167
+ })();
168
+ }
169
+ }
170
+ export function stopCronJob(id) {
171
+ const active = activeJobs.get(id);
172
+ if (!active)
173
+ return false;
174
+ clearTimeout(active.timer);
175
+ activeJobs.delete(id);
176
+ return true;
177
+ }
178
+ export function stopAllCronJobs() {
179
+ for (const [id] of activeJobs)
180
+ stopCronJob(id);
181
+ }
182
+ export function listCronJobs() {
183
+ return Array.from(activeJobs.values()).map((a) => a.job);
184
+ }
185
+ export function isCronRunning(id) {
186
+ return activeJobs.has(id);
187
+ }
188
+ // --- Runtime management ---
189
+ /** Get status of all jobs (active + inactive runtime jobs) */
190
+ export function getAllJobStatuses() {
191
+ const statuses = [];
192
+ const seen = new Set();
193
+ for (const [, a] of activeJobs) {
194
+ seen.add(a.job.id);
195
+ statuses.push({
196
+ id: a.job.id,
197
+ name: a.job.name,
198
+ schedule: a.job.schedule,
199
+ prompt: a.job.prompt,
200
+ enabled: true,
201
+ source: a.job.source ?? "config",
202
+ lastRunAt: a.lastRunAt,
203
+ lastStatus: a.lastStatus,
204
+ lastError: a.lastError,
205
+ running: a.running,
206
+ });
207
+ }
208
+ // Include inactive runtime jobs
209
+ for (const job of loadRuntimeJobs()) {
210
+ if (!seen.has(job.id)) {
211
+ statuses.push({
212
+ id: job.id,
213
+ name: job.name,
214
+ schedule: job.schedule,
215
+ prompt: job.prompt,
216
+ enabled: job.enabled,
217
+ source: "runtime",
218
+ running: false,
219
+ });
220
+ }
221
+ }
222
+ return statuses;
223
+ }
224
+ /** Add a runtime job (persisted to jobs.json, auto-started if server running) */
225
+ export function addRuntimeJob(job, autoStart = true) {
226
+ const jobs = loadRuntimeJobs();
227
+ if (jobs.some((j) => j.id === job.id))
228
+ throw new Error(`Job "${job.id}" already exists`);
229
+ if (activeJobs.has(job.id))
230
+ throw new Error(`Job "${job.id}" already exists (active)`);
231
+ const full = { ...job, source: "runtime", createdAt: Date.now() };
232
+ // Convert relative "+20m" to absolute ISO so it survives server restarts
233
+ if (full.schedule.startsWith("+")) {
234
+ const interval = parseInterval(full.schedule.slice(1));
235
+ if (interval) {
236
+ full.schedule = new Date(Date.now() + interval).toISOString();
237
+ }
238
+ }
239
+ if (isOneShot(full.schedule) && full.deleteAfterRun === undefined)
240
+ full.deleteAfterRun = true;
241
+ jobs.push(full);
242
+ saveRuntimeJobs(jobs);
243
+ if (autoStart && full.enabled && serverConfig) {
244
+ startCronJob(full, serverConfig, serverSystemPrompt);
245
+ }
246
+ return full;
247
+ }
248
+ /** Remove a runtime job (stops if active, removes from store) */
249
+ export function removeRuntimeJob(id) {
250
+ const jobs = loadRuntimeJobs();
251
+ const filtered = jobs.filter((j) => j.id !== id);
252
+ if (filtered.length === jobs.length)
253
+ return false;
254
+ saveRuntimeJobs(filtered);
255
+ stopCronJob(id);
256
+ return true;
257
+ }
258
+ /** Trigger a job immediately (runs synchronously, returns response) */
259
+ export async function runJobNow(id) {
260
+ if (!serverConfig)
261
+ throw new Error("Server not running");
262
+ const active = activeJobs.get(id);
263
+ const job = active?.job ?? loadRuntimeJobs().find((j) => j.id === id);
264
+ if (!job) {
265
+ // Also check config jobs
266
+ const configJob = serverConfig.cron.find((j) => j.id === id);
267
+ if (!configJob)
268
+ throw new Error(`Job "${id}" not found`);
269
+ return runJobWithConfig(configJob);
270
+ }
271
+ return runJobWithConfig(job);
272
+ }
273
+ async function runJobWithConfig(job) {
274
+ if (!serverConfig)
275
+ throw new Error("Server not running");
276
+ const sid = job.session ?? `cron-${job.id}`;
277
+ const history = loadMessages(sid);
278
+ const result = await runAgent(serverConfig.apiKey, serverConfig.model, serverSystemPrompt, history, job.prompt, {
279
+ maxTurns: 10,
280
+ timeoutMs: 120_000,
281
+ provider: serverConfig.provider,
282
+ baseUrl: serverConfig.baseUrl,
283
+ });
284
+ saveMessage(sid, { role: "user", content: job.prompt }, "cron");
285
+ if (result.response)
286
+ saveMessage(sid, { role: "assistant", content: result.response }, "cron");
287
+ const active = activeJobs.get(job.id);
288
+ if (active) {
289
+ active.lastRunAt = Date.now();
290
+ active.lastStatus = "ok";
291
+ active.lastError = undefined;
292
+ active.consecutiveErrors = 0;
293
+ }
294
+ return result.response;
295
+ }
296
+ /** Start all enabled runtime jobs (call from serve.ts alongside config jobs) */
297
+ export function startRuntimeJobs(config, systemPrompt, opts) {
298
+ const jobs = loadRuntimeJobs().filter((j) => j.enabled);
299
+ for (const job of jobs) {
300
+ if (!activeJobs.has(job.id)) {
301
+ startCronJob(job, config, systemPrompt, opts);
302
+ }
303
+ }
304
+ return jobs.length;
305
+ }
306
+ //# sourceMappingURL=cron.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cron.js","sourceRoot":"","sources":["../../src/extensions/cron.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ // Lifecycle hooks: shell scripts or JS handlers from ~/.camelagi/hooks/
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { execSync } from "node:child_process";
5
+ import { paths } from "../core/config.js";
6
+ import { HOOK_TIMEOUT_MS, MAX_STDERR_CHARS } from "../core/constants.js";
7
+ const hooksDir = path.join(paths.configDir, "hooks");
8
+ /**
9
+ * Load all hook scripts from ~/.camelagi/hooks/
10
+ * Naming convention: {point}.{name}.sh or {point}.{name}.js
11
+ * Examples: before_tool.log.sh, after_response.notify.sh
12
+ */
13
+ export function loadHooks() {
14
+ if (!fs.existsSync(hooksDir))
15
+ return [];
16
+ const entries = [];
17
+ const files = fs.readdirSync(hooksDir);
18
+ for (const file of files) {
19
+ if (!file.endsWith(".sh") && !file.endsWith(".js"))
20
+ continue;
21
+ const parts = file.split(".");
22
+ if (parts.length < 3)
23
+ continue;
24
+ const point = parts[0];
25
+ if (!["before_prompt", "after_response", "before_tool", "after_tool"].includes(point))
26
+ continue;
27
+ const name = parts.slice(1, -1).join(".");
28
+ entries.push({
29
+ name,
30
+ point,
31
+ script: path.join(hooksDir, file),
32
+ });
33
+ }
34
+ return entries;
35
+ }
36
+ /**
37
+ * Run all hooks for a given point.
38
+ * Context is passed via environment variables (CAMELAGI_HOOK_*).
39
+ */
40
+ export async function runHooks(point, context, hooks) {
41
+ const all = hooks ?? loadHooks();
42
+ const matching = all.filter((h) => h.point === point);
43
+ if (matching.length === 0)
44
+ return;
45
+ const env = {
46
+ ...process.env,
47
+ CAMELAGI_HOOK_POINT: point,
48
+ ...(context.sessionId && { CAMELAGI_HOOK_SESSION: context.sessionId }),
49
+ ...(context.message && { CAMELAGI_HOOK_MESSAGE: context.message }),
50
+ ...(context.response && { CAMELAGI_HOOK_RESPONSE: context.response.slice(0, MAX_STDERR_CHARS) }),
51
+ ...(context.toolName && { CAMELAGI_HOOK_TOOL: context.toolName }),
52
+ ...(context.toolArgs && { CAMELAGI_HOOK_TOOL_ARGS: JSON.stringify(context.toolArgs) }),
53
+ ...(context.toolResult && { CAMELAGI_HOOK_TOOL_RESULT: context.toolResult.slice(0, MAX_STDERR_CHARS) }),
54
+ };
55
+ for (const hook of matching) {
56
+ try {
57
+ execSync(hook.script, {
58
+ env,
59
+ timeout: HOOK_TIMEOUT_MS,
60
+ stdio: "pipe",
61
+ });
62
+ }
63
+ catch (err) {
64
+ const msg = err instanceof Error ? err.message : String(err);
65
+ process.stderr.write(`Hook ${hook.name} failed: ${msg}\n`);
66
+ }
67
+ }
68
+ }
69
+ export function ensureHooksDir() {
70
+ fs.mkdirSync(hooksDir, { recursive: true });
71
+ }
72
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/extensions/hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ // Skills system: load skill definitions from ~/.camelagi/skills/
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { paths } from "../core/config.js";
5
+ import { MAX_SKILLS_TOTAL_CHARS } from "../core/constants.js";
6
+ const skillsDir = path.join(paths.configDir, "skills");
7
+ /**
8
+ * Load all skills from ~/.camelagi/skills/
9
+ * Each skill is a directory containing SKILL.md with optional frontmatter.
10
+ *
11
+ * Format:
12
+ * ~/.camelagi/skills/my-skill/SKILL.md
13
+ *
14
+ * Frontmatter (optional):
15
+ * ---
16
+ * name: my-skill
17
+ * description: Does something useful
18
+ * ---
19
+ * # Skill content here...
20
+ */
21
+ export function loadSkills() {
22
+ if (!fs.existsSync(skillsDir))
23
+ return [];
24
+ const skills = [];
25
+ const dirs = fs.readdirSync(skillsDir, { withFileTypes: true });
26
+ for (const dir of dirs) {
27
+ if (!dir.isDirectory())
28
+ continue;
29
+ const skillFile = path.join(skillsDir, dir.name, "SKILL.md");
30
+ if (!fs.existsSync(skillFile))
31
+ continue;
32
+ const raw = fs.readFileSync(skillFile, "utf-8").trim();
33
+ if (!raw)
34
+ continue;
35
+ const { frontmatter, content } = parseFrontmatter(raw);
36
+ skills.push({
37
+ name: frontmatter.name ?? dir.name,
38
+ description: frontmatter.description ?? "",
39
+ content,
40
+ path: skillFile,
41
+ });
42
+ }
43
+ return skills;
44
+ }
45
+ /**
46
+ * Format skills for injection into the system prompt.
47
+ * Uses progressive disclosure: only metadata is injected upfront.
48
+ * The model reads the full SKILL.md on demand using the read tool.
49
+ */
50
+ export function formatSkillsForPrompt(skills, _maxChars = MAX_SKILLS_TOTAL_CHARS) {
51
+ if (skills.length === 0)
52
+ return "";
53
+ const lines = [
54
+ "## Skills",
55
+ "",
56
+ "The following skills are available. When a user's request matches a skill,",
57
+ "read its SKILL.md file to get detailed instructions, then follow them.",
58
+ "",
59
+ "<available_skills>",
60
+ ];
61
+ for (const skill of skills) {
62
+ const desc = skill.description ? `: ${skill.description}` : "";
63
+ lines.push(` <skill name="${skill.name}" path="${skill.path}"${desc} />`);
64
+ }
65
+ lines.push("</available_skills>");
66
+ lines.push("");
67
+ lines.push("To use a skill: read its SKILL.md path above, then follow the instructions inside.");
68
+ return lines.join("\n");
69
+ }
70
+ function parseFrontmatter(raw) {
71
+ const frontmatter = {};
72
+ if (!raw.startsWith("---")) {
73
+ return { frontmatter, content: raw };
74
+ }
75
+ const endIdx = raw.indexOf("---", 3);
76
+ if (endIdx === -1) {
77
+ return { frontmatter, content: raw };
78
+ }
79
+ const yaml = raw.slice(3, endIdx).trim();
80
+ const content = raw.slice(endIdx + 3).trim();
81
+ for (const line of yaml.split("\n")) {
82
+ const colonIdx = line.indexOf(":");
83
+ if (colonIdx === -1)
84
+ continue;
85
+ const key = line.slice(0, colonIdx).trim();
86
+ const value = line.slice(colonIdx + 1).trim();
87
+ frontmatter[key] = value;
88
+ }
89
+ return { frontmatter, content };
90
+ }
91
+ export function ensureSkillsDir() {
92
+ fs.mkdirSync(skillsDir, { recursive: true });
93
+ }
94
+ export function listSkillNames() {
95
+ return loadSkills().map((s) => s.name);
96
+ }
97
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/extensions/skills.ts"],"names":[],"mappings":"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"}