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.
- package/LICENSE +21 -0
- package/README.md +224 -0
- package/camelagi.mjs +2 -0
- package/config.example.yaml +107 -0
- package/dist/agent/agent-openai.js +206 -0
- package/dist/agent/agent-openai.js.map +1 -0
- package/dist/agent/agent-sdk.js +209 -0
- package/dist/agent/agent-sdk.js.map +1 -0
- package/dist/agent/tool-adapter.js +31 -0
- package/dist/agent/tool-adapter.js.map +1 -0
- package/dist/agent/types.js +3 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/agent.js +17 -0
- package/dist/agent.js.map +1 -0
- package/dist/approval-forward.js +42 -0
- package/dist/approval-forward.js.map +1 -0
- package/dist/approvals.js +151 -0
- package/dist/approvals.js.map +1 -0
- package/dist/boot.js +34 -0
- package/dist/boot.js.map +1 -0
- package/dist/bootstrap.js +451 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/camelagi-gateway.mjs +93611 -0
- package/dist/camelagi-gateway.mjs.map +7 -0
- package/dist/channels/adapter.js +10 -0
- package/dist/channels/adapter.js.map +1 -0
- package/dist/channels/discord.js +232 -0
- package/dist/channels/discord.js.map +1 -0
- package/dist/channels/handler.js +349 -0
- package/dist/channels/handler.js.map +1 -0
- package/dist/channels/index.js +19 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/channels/registry.js +71 -0
- package/dist/channels/registry.js.map +1 -0
- package/dist/channels/telegram.js +83 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/types.js +3 -0
- package/dist/channels/types.js.map +1 -0
- package/dist/chunker.js +102 -0
- package/dist/chunker.js.map +1 -0
- package/dist/cli/cmd-agents.js +65 -0
- package/dist/cli/cmd-agents.js.map +1 -0
- package/dist/cli/cmd-bootstrap.js +10 -0
- package/dist/cli/cmd-bootstrap.js.map +1 -0
- package/dist/cli/cmd-chat.js +32 -0
- package/dist/cli/cmd-chat.js.map +1 -0
- package/dist/cli/cmd-config.js +88 -0
- package/dist/cli/cmd-config.js.map +1 -0
- package/dist/cli/cmd-cron.js +120 -0
- package/dist/cli/cmd-cron.js.map +1 -0
- package/dist/cli/cmd-daemon.js +37 -0
- package/dist/cli/cmd-daemon.js.map +1 -0
- package/dist/cli/cmd-doctor.js +18 -0
- package/dist/cli/cmd-doctor.js.map +1 -0
- package/dist/cli/cmd-logs.js +30 -0
- package/dist/cli/cmd-logs.js.map +1 -0
- package/dist/cli/cmd-pairing.js +41 -0
- package/dist/cli/cmd-pairing.js.map +1 -0
- package/dist/cli/cmd-reset.js +39 -0
- package/dist/cli/cmd-reset.js.map +1 -0
- package/dist/cli/cmd-serve.js +30 -0
- package/dist/cli/cmd-serve.js.map +1 -0
- package/dist/cli/cmd-sessions.js +56 -0
- package/dist/cli/cmd-sessions.js.map +1 -0
- package/dist/cli/cmd-setup.js +11 -0
- package/dist/cli/cmd-setup.js.map +1 -0
- package/dist/cli/cmd-soul.js +43 -0
- package/dist/cli/cmd-soul.js.map +1 -0
- package/dist/cli/parse.js +50 -0
- package/dist/cli/parse.js.map +1 -0
- package/dist/cli/registry.js +15 -0
- package/dist/cli/registry.js.map +1 -0
- package/dist/cli.js +103 -0
- package/dist/cli.js.map +1 -0
- package/dist/compact.js +92 -0
- package/dist/compact.js.map +1 -0
- package/dist/config.js +153 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.js +21 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/config.js +212 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/constants.js +21 -0
- package/dist/core/constants.js.map +1 -0
- package/dist/core/errors.js +5 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/log.js +41 -0
- package/dist/core/log.js.map +1 -0
- package/dist/core/models.js +123 -0
- package/dist/core/models.js.map +1 -0
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/update-check.js +51 -0
- package/dist/core/update-check.js.map +1 -0
- package/dist/cron.js +81 -0
- package/dist/cron.js.map +1 -0
- package/dist/daemon.js +109 -0
- package/dist/daemon.js.map +1 -0
- package/dist/doctor.js +194 -0
- package/dist/doctor.js.map +1 -0
- package/dist/errors.js +5 -0
- package/dist/errors.js.map +1 -0
- package/dist/extensions/approval-forward.js +42 -0
- package/dist/extensions/approval-forward.js.map +1 -0
- package/dist/extensions/approvals.js +144 -0
- package/dist/extensions/approvals.js.map +1 -0
- package/dist/extensions/cron.js +306 -0
- package/dist/extensions/cron.js.map +1 -0
- package/dist/extensions/hooks.js +72 -0
- package/dist/extensions/hooks.js.map +1 -0
- package/dist/extensions/skills.js +97 -0
- package/dist/extensions/skills.js.map +1 -0
- package/dist/gateway/csrf.js +44 -0
- package/dist/gateway/csrf.js.map +1 -0
- package/dist/gateway/logger.js +81 -0
- package/dist/gateway/logger.js.map +1 -0
- package/dist/gateway/rate-limit.js +33 -0
- package/dist/gateway/rate-limit.js.map +1 -0
- package/dist/gateway/routes.js +315 -0
- package/dist/gateway/routes.js.map +1 -0
- package/dist/gateway/state.js +54 -0
- package/dist/gateway/state.js.map +1 -0
- package/dist/gateway/ws-handler.js +200 -0
- package/dist/gateway/ws-handler.js.map +1 -0
- package/dist/gateway-entry.js +16 -0
- package/dist/gateway-entry.js.map +1 -0
- package/dist/hooks.js +72 -0
- package/dist/hooks.js.map +1 -0
- package/dist/lanes.js +62 -0
- package/dist/lanes.js.map +1 -0
- package/dist/model.js +30 -0
- package/dist/model.js.map +1 -0
- package/dist/policy.js +22 -0
- package/dist/policy.js.map +1 -0
- package/dist/queue.js +45 -0
- package/dist/queue.js.map +1 -0
- package/dist/retry.js +96 -0
- package/dist/retry.js.map +1 -0
- package/dist/runs.js +83 -0
- package/dist/runs.js.map +1 -0
- package/dist/runtime/compact.js +99 -0
- package/dist/runtime/compact.js.map +1 -0
- package/dist/runtime/lanes.js +66 -0
- package/dist/runtime/lanes.js.map +1 -0
- package/dist/runtime/orchestrate.js +121 -0
- package/dist/runtime/orchestrate.js.map +1 -0
- package/dist/runtime/queue.js +50 -0
- package/dist/runtime/queue.js.map +1 -0
- package/dist/runtime/retry.js +127 -0
- package/dist/runtime/retry.js.map +1 -0
- package/dist/runtime/runs.js +105 -0
- package/dist/runtime/runs.js.map +1 -0
- package/dist/serve.js +209 -0
- package/dist/serve.js.map +1 -0
- package/dist/session.js +75 -0
- package/dist/session.js.map +1 -0
- package/dist/setup.js +254 -0
- package/dist/setup.js.map +1 -0
- package/dist/skills.js +89 -0
- package/dist/skills.js.map +1 -0
- package/dist/subagent.js +71 -0
- package/dist/subagent.js.map +1 -0
- package/dist/system-prompt.js +157 -0
- package/dist/system-prompt.js.map +1 -0
- package/dist/telegram/admin-bot.js +705 -0
- package/dist/telegram/admin-bot.js.map +1 -0
- package/dist/telegram/agent-bot.js +551 -0
- package/dist/telegram/agent-bot.js.map +1 -0
- package/dist/telegram/bot-approval.js +63 -0
- package/dist/telegram/bot-approval.js.map +1 -0
- package/dist/telegram/draft-stream.js +86 -0
- package/dist/telegram/draft-stream.js.map +1 -0
- package/dist/telegram/format.js +106 -0
- package/dist/telegram/format.js.map +1 -0
- package/dist/telegram/helpers.js +87 -0
- package/dist/telegram/helpers.js.map +1 -0
- package/dist/telegram/pairing-notify.js +52 -0
- package/dist/telegram/pairing-notify.js.map +1 -0
- package/dist/telegram/pairing.js +138 -0
- package/dist/telegram/pairing.js.map +1 -0
- package/dist/telegram/resolve.js +33 -0
- package/dist/telegram/resolve.js.map +1 -0
- package/dist/telegram/transcribe.js +77 -0
- package/dist/telegram/transcribe.js.map +1 -0
- package/dist/telegram/types.js +3 -0
- package/dist/telegram/types.js.map +1 -0
- package/dist/telegram/voice-wizard.js +84 -0
- package/dist/telegram/voice-wizard.js.map +1 -0
- package/dist/telegram/wizard.js +89 -0
- package/dist/telegram/wizard.js.map +1 -0
- package/dist/telegram/wizards.js +297 -0
- package/dist/telegram/wizards.js.map +1 -0
- package/dist/telegram-admin.js +800 -0
- package/dist/telegram-admin.js.map +1 -0
- package/dist/telegram.js +118 -0
- package/dist/telegram.js.map +1 -0
- package/dist/tools/cron.js +94 -0
- package/dist/tools/cron.js.map +1 -0
- package/dist/tools/edit.js +29 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/exec.js +38 -0
- package/dist/tools/exec.js.map +1 -0
- package/dist/tools/fetch.js +28 -0
- package/dist/tools/fetch.js.map +1 -0
- package/dist/tools/index.js +16 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/memory.js +164 -0
- package/dist/tools/memory.js.map +1 -0
- package/dist/tools/patch.js +284 -0
- package/dist/tools/patch.js.map +1 -0
- package/dist/tools/read.js +26 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/search.js +62 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/subagent.js +48 -0
- package/dist/tools/subagent.js.map +1 -0
- package/dist/tools/write.js +22 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/tui/commands.js +450 -0
- package/dist/tui/commands.js.map +1 -0
- package/dist/tui/components/assistant-message.js +26 -0
- package/dist/tui/components/assistant-message.js.map +1 -0
- package/dist/tui/components/chat-log.js +94 -0
- package/dist/tui/components/chat-log.js.map +1 -0
- package/dist/tui/components/custom-editor.js +40 -0
- package/dist/tui/components/custom-editor.js.map +1 -0
- package/dist/tui/components/hint-bar.js +13 -0
- package/dist/tui/components/hint-bar.js.map +1 -0
- package/dist/tui/components/tool-execution.js +73 -0
- package/dist/tui/components/tool-execution.js.map +1 -0
- package/dist/tui/components/user-message.js +19 -0
- package/dist/tui/components/user-message.js.map +1 -0
- package/dist/tui/components/welcome.js +147 -0
- package/dist/tui/components/welcome.js.map +1 -0
- package/dist/tui/context.js +3 -0
- package/dist/tui/context.js.map +1 -0
- package/dist/tui/theme.js +91 -0
- package/dist/tui/theme.js.map +1 -0
- package/dist/tui/tui.js +389 -0
- package/dist/tui/tui.js.map +1 -0
- package/dist/tui/ws-handler.js +154 -0
- package/dist/tui/ws-handler.js.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/usage.js +88 -0
- package/dist/usage.js.map +1 -0
- package/dist/workspace.js +245 -0
- package/dist/workspace.js.map +1 -0
- 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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"}
|