@alejandroroman/agent-kit 0.1.4 → 0.2.1

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 (77) hide show
  1. package/dist/agent/loop.js +213 -111
  2. package/dist/agent/types.d.ts +2 -0
  3. package/dist/api/errors.d.ts +3 -0
  4. package/dist/api/errors.js +37 -0
  5. package/dist/api/events.d.ts +5 -0
  6. package/dist/api/events.js +28 -0
  7. package/dist/api/router.js +10 -0
  8. package/dist/api/traces.d.ts +3 -0
  9. package/dist/api/traces.js +35 -0
  10. package/dist/api/types.d.ts +2 -0
  11. package/dist/bootstrap.d.ts +3 -1
  12. package/dist/bootstrap.js +26 -7
  13. package/dist/cli/chat.js +3 -1
  14. package/dist/cli/claude-md-template.d.ts +5 -0
  15. package/dist/cli/claude-md-template.js +220 -0
  16. package/dist/cli/config-writer.js +3 -0
  17. package/dist/cli/env.d.ts +14 -0
  18. package/dist/cli/env.js +68 -0
  19. package/dist/cli/init.js +10 -0
  20. package/dist/cli/setup-agent/index.js +61 -18
  21. package/dist/cli/slack-setup.d.ts +6 -0
  22. package/dist/cli/slack-setup.js +234 -0
  23. package/dist/cli/start.js +65 -16
  24. package/dist/cli/ui.d.ts +2 -0
  25. package/dist/cli/ui.js +4 -1
  26. package/dist/cli/whats-new.d.ts +1 -0
  27. package/dist/cli/whats-new.js +69 -0
  28. package/dist/cli.js +14 -0
  29. package/dist/config/resolve.d.ts +1 -0
  30. package/dist/config/resolve.js +1 -0
  31. package/dist/config/schema.d.ts +2 -0
  32. package/dist/config/schema.js +1 -0
  33. package/dist/config/writer.d.ts +18 -0
  34. package/dist/config/writer.js +85 -0
  35. package/dist/cron/scheduler.d.ts +4 -1
  36. package/dist/cron/scheduler.js +99 -52
  37. package/dist/gateways/slack/client.d.ts +1 -0
  38. package/dist/gateways/slack/client.js +9 -0
  39. package/dist/gateways/slack/handler.js +2 -1
  40. package/dist/gateways/slack/index.js +75 -29
  41. package/dist/gateways/slack/listener.d.ts +8 -1
  42. package/dist/gateways/slack/listener.js +36 -10
  43. package/dist/heartbeat/runner.js +99 -82
  44. package/dist/llm/anthropic.d.ts +1 -0
  45. package/dist/llm/anthropic.js +11 -2
  46. package/dist/llm/fallback.js +34 -2
  47. package/dist/llm/openai.d.ts +2 -0
  48. package/dist/llm/openai.js +33 -2
  49. package/dist/llm/types.d.ts +16 -2
  50. package/dist/llm/types.js +9 -0
  51. package/dist/logger.d.ts +1 -0
  52. package/dist/logger.js +11 -0
  53. package/dist/media/sanitize.d.ts +5 -0
  54. package/dist/media/sanitize.js +53 -0
  55. package/dist/multi/spawn.js +29 -10
  56. package/dist/session/compaction.js +3 -1
  57. package/dist/session/prune-images.d.ts +9 -0
  58. package/dist/session/prune-images.js +42 -0
  59. package/dist/skills/activate.d.ts +6 -0
  60. package/dist/skills/activate.js +72 -27
  61. package/dist/skills/index.d.ts +1 -1
  62. package/dist/skills/index.js +1 -1
  63. package/dist/telemetry/db.d.ts +63 -0
  64. package/dist/telemetry/db.js +193 -0
  65. package/dist/telemetry/index.d.ts +17 -0
  66. package/dist/telemetry/index.js +82 -0
  67. package/dist/telemetry/sanitize.d.ts +6 -0
  68. package/dist/telemetry/sanitize.js +48 -0
  69. package/dist/telemetry/sqlite-processor.d.ts +11 -0
  70. package/dist/telemetry/sqlite-processor.js +108 -0
  71. package/dist/telemetry/types.d.ts +30 -0
  72. package/dist/telemetry/types.js +31 -0
  73. package/dist/tools/builtin/index.d.ts +2 -0
  74. package/dist/tools/builtin/index.js +2 -0
  75. package/dist/tools/builtin/self-config.d.ts +4 -0
  76. package/dist/tools/builtin/self-config.js +182 -0
  77. package/package.json +10 -2
@@ -0,0 +1,234 @@
1
+ import * as path from "path";
2
+ import { execFileSync } from "child_process";
3
+ import * as p from "@clack/prompts";
4
+ import { isCancel } from "./ui.js";
5
+ import { upsertEnvKey, loadEnvFile } from "./env.js";
6
+ import { readRawConfig, writeConfig } from "./config-writer.js";
7
+ import { loadConfig } from "../config/index.js";
8
+ const SLACK_MANIFEST = `display_information:
9
+ name: Agent Kit
10
+ settings:
11
+ socket_mode_enabled: true
12
+ features:
13
+ bot_user:
14
+ display_name: Agent Kit
15
+ always_online: true
16
+ event_subscriptions:
17
+ bot_events:
18
+ - message.channels
19
+ - message.groups
20
+ oauth_config:
21
+ scopes:
22
+ bot:
23
+ - chat:write
24
+ - channels:history
25
+ - channels:read
26
+ - groups:history
27
+ - groups:read`;
28
+ function openBrowser(url) {
29
+ try {
30
+ const cmd = process.platform === "darwin" ? "open"
31
+ : process.platform === "win32" ? "start"
32
+ : "xdg-open";
33
+ execFileSync(cmd, [url], { stdio: "ignore" });
34
+ }
35
+ catch {
36
+ p.log.warn(`Could not open browser. Visit: ${url}`);
37
+ }
38
+ }
39
+ export async function setupSlack(opts) {
40
+ const envPath = path.join(process.cwd(), ".env");
41
+ // 1. Opt-in (only during init)
42
+ if (opts.fromInit) {
43
+ const wantSlack = await p.confirm({
44
+ message: "Would you like to connect Slack?",
45
+ });
46
+ if (isCancel(wantSlack) || !wantSlack)
47
+ return;
48
+ }
49
+ // Check for existing tokens
50
+ const existingEnv = loadEnvFile(envPath);
51
+ const hasAppToken = !!existingEnv.SLACK_APP_TOKEN;
52
+ const hasBotToken = !!existingEnv.SLACK_BOT_TOKEN;
53
+ let skipTokens = false;
54
+ if (hasAppToken && hasBotToken) {
55
+ const keep = await p.confirm({
56
+ message: "Slack tokens already exist in .env. Keep them?",
57
+ });
58
+ if (isCancel(keep))
59
+ return;
60
+ if (keep) {
61
+ skipTokens = true;
62
+ process.env.SLACK_APP_TOKEN ??= existingEnv.SLACK_APP_TOKEN;
63
+ process.env.SLACK_BOT_TOKEN ??= existingEnv.SLACK_BOT_TOKEN;
64
+ p.log.info("Using existing tokens from .env");
65
+ }
66
+ }
67
+ // 2. Instructions — Create App
68
+ if (!skipTokens) {
69
+ p.log.step("Step 1: Create Slack App");
70
+ console.log();
71
+ console.log(" Go to api.slack.com/apps → Create New App → From a manifest");
72
+ console.log();
73
+ const openPage = await p.confirm({
74
+ message: "Open api.slack.com in your browser?",
75
+ });
76
+ if (!isCancel(openPage) && openPage) {
77
+ openBrowser("https://api.slack.com/apps");
78
+ }
79
+ console.log();
80
+ console.log(" Select your workspace, then paste this manifest:");
81
+ console.log();
82
+ for (const line of SLACK_MANIFEST.split("\n")) {
83
+ console.log(` ${line}`);
84
+ }
85
+ console.log();
86
+ console.log(" Click Create.");
87
+ console.log();
88
+ // 3. Instructions — App Token
89
+ p.log.step("Step 2: Generate App-Level Token");
90
+ console.log();
91
+ console.log(" Settings → Basic Information → App-Level Tokens");
92
+ console.log(" Click 'Generate Token and Scopes'");
93
+ console.log(" Name: socket-mode | Scope: connections:write");
94
+ console.log(" Copy the xapp-... token");
95
+ console.log();
96
+ // 4. Collect App Token
97
+ const appToken = await p.password({
98
+ message: "Paste App Token (xapp-...):",
99
+ validate: (val) => {
100
+ if (!val?.trim())
101
+ return "App token is required";
102
+ if (!val.startsWith("xapp-"))
103
+ return "Token should start with xapp-";
104
+ },
105
+ });
106
+ if (isCancel(appToken))
107
+ return;
108
+ upsertEnvKey(envPath, "SLACK_APP_TOKEN", appToken.trim());
109
+ process.env.SLACK_APP_TOKEN = appToken.trim();
110
+ p.log.success("App token saved to .env");
111
+ // 5. Instructions — Bot Token
112
+ p.log.step("Step 3: Install to Workspace");
113
+ console.log();
114
+ console.log(" Settings → Install App → Install to Workspace");
115
+ console.log(" Authorize, then copy the Bot User OAuth Token (xoxb-...)");
116
+ console.log();
117
+ // 6. Collect Bot Token
118
+ const botToken = await p.password({
119
+ message: "Paste Bot Token (xoxb-...):",
120
+ validate: (val) => {
121
+ if (!val?.trim())
122
+ return "Bot token is required";
123
+ if (!val.startsWith("xoxb-"))
124
+ return "Token should start with xoxb-";
125
+ },
126
+ });
127
+ if (isCancel(botToken))
128
+ return;
129
+ upsertEnvKey(envPath, "SLACK_BOT_TOKEN", botToken.trim());
130
+ process.env.SLACK_BOT_TOKEN = botToken.trim();
131
+ p.log.success("Bot token saved to .env");
132
+ } // end if (!skipTokens)
133
+ // 7. Channel Binding
134
+ let config;
135
+ try {
136
+ config = loadConfig(opts.configPath);
137
+ }
138
+ catch (err) {
139
+ p.log.error(`Could not load config: ${err instanceof Error ? err.message : err}`);
140
+ p.log.info("Slack tokens were saved to .env. Run `agent-kit slack-setup` again after fixing your config.");
141
+ return;
142
+ }
143
+ const agentNames = Object.keys(config.agents);
144
+ if (agentNames.length === 0) {
145
+ p.log.info("No agents configured yet. Run `agent-kit create` first, then `agent-kit slack-setup` to bind channels.");
146
+ }
147
+ else {
148
+ p.log.step("Step 4: Connect agents to Slack channels");
149
+ console.log();
150
+ console.log(" Create channels in Slack, then invite the bot: /invite @Agent Kit");
151
+ console.log(" Get channel IDs: right-click channel → View channel details → copy ID (starts with C)");
152
+ console.log();
153
+ const raw = readRawConfig(opts.configPath);
154
+ if (!raw) {
155
+ p.log.error("Could not read agent-kit.json");
156
+ return;
157
+ }
158
+ const agents = raw.agents;
159
+ if (!agents) {
160
+ p.log.error("No agents section in config");
161
+ return;
162
+ }
163
+ const usedChannels = new Map(); // channelId → agentName
164
+ // Collect already-bound channels
165
+ for (const [name, def] of Object.entries(agents)) {
166
+ const slack = def.slack;
167
+ if (slack?.channelId) {
168
+ usedChannels.set(slack.channelId, name);
169
+ }
170
+ }
171
+ for (const name of agentNames) {
172
+ const agentDef = config.agents[name];
173
+ const emoji = agentDef.emoji ?? " ";
174
+ const display = agentDef.displayName ?? name;
175
+ // Skip if already bound
176
+ if (agentDef.slack) {
177
+ p.log.info(`${emoji} ${display} — already bound to ${agentDef.slack.channelName}`);
178
+ continue;
179
+ }
180
+ const bind = await p.confirm({
181
+ message: `Connect ${emoji} ${display} to a Slack channel?`,
182
+ });
183
+ if (isCancel(bind))
184
+ return;
185
+ if (!bind)
186
+ continue;
187
+ const channelId = await p.text({
188
+ message: "Channel ID (e.g. C0123456789):",
189
+ validate: (val) => {
190
+ if (!val?.trim())
191
+ return "Channel ID is required";
192
+ if (!/^[CG][A-Z0-9]{8,}$/.test(val.trim())) {
193
+ return "Channel ID should start with C or G followed by uppercase letters/numbers";
194
+ }
195
+ const existing = usedChannels.get(val.trim());
196
+ if (existing) {
197
+ return `Channel already bound to agent "${existing}"`;
198
+ }
199
+ },
200
+ });
201
+ if (isCancel(channelId))
202
+ return;
203
+ const channelName = await p.text({
204
+ message: "Channel name (e.g. #finances):",
205
+ validate: (val) => {
206
+ if (!val?.trim())
207
+ return "Channel name is required";
208
+ },
209
+ });
210
+ if (isCancel(channelName))
211
+ return;
212
+ const id = channelId.trim();
213
+ const cName = channelName.trim();
214
+ // Update raw config
215
+ agents[name] = {
216
+ ...agents[name],
217
+ slack: { channelId: id, channelName: cName },
218
+ };
219
+ usedChannels.set(id, name);
220
+ p.log.success(`${emoji} ${display} → ${cName}`);
221
+ }
222
+ try {
223
+ writeConfig(opts.configPath, raw);
224
+ }
225
+ catch (err) {
226
+ p.log.error(`Could not save config: ${err instanceof Error ? err.message : err}`);
227
+ p.log.info("Slack tokens were saved, but channel bindings were not persisted.");
228
+ return;
229
+ }
230
+ }
231
+ // 8. Done
232
+ console.log();
233
+ p.log.success("Slack configured! Start with `pnpm start` to connect.");
234
+ }
package/dist/cli/start.js CHANGED
@@ -7,13 +7,38 @@ import { createLogger } from "../logger.js";
7
7
  import { buildAgentRuntime, initUsageStore, createAgentExecutor, } from "../bootstrap.js";
8
8
  import { ensureOllama } from "./ollama.js";
9
9
  import { resolveApiKey } from "./ui.js";
10
+ import { showWhatsNew } from "./whats-new.js";
11
+ import { ConfigWriter } from "../config/writer.js";
10
12
  import { CONFIG_PATH, DATA_DIR, SKILLS_DIR } from "./paths.js";
11
13
  import { startRepl } from "./repl.js";
14
+ import * as path from "path";
15
+ import { loadEnvIntoProcess } from "./env.js";
16
+ import { initTelemetry, getTelemetryDb } from "../telemetry/index.js";
12
17
  const log = createLogger("start");
13
18
  const DEFAULT_AGENT = "default";
14
19
  export async function start() {
15
20
  await resolveApiKey({ save: false });
21
+ showWhatsNew();
22
+ // Telemetry (OpenTelemetry + Sentry)
23
+ const shutdownTelemetry = initTelemetry({
24
+ dbPath: path.join(DATA_DIR, "telemetry.db"),
25
+ });
26
+ // Prune telemetry data older than 30 days
27
+ const telDb = getTelemetryDb();
28
+ if (telDb) {
29
+ try {
30
+ telDb.cleanup(30);
31
+ }
32
+ catch (err) {
33
+ log.warn({ err }, "telemetry cleanup failed");
34
+ }
35
+ }
36
+ // Load Slack tokens from .env (if not already in process.env)
37
+ const envPath = path.join(process.cwd(), ".env");
38
+ loadEnvIntoProcess(envPath, ["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN"]);
16
39
  const config = loadConfig(CONFIG_PATH);
40
+ const configWriter = new ConfigWriter(CONFIG_PATH);
41
+ configWriter.watch();
17
42
  // Ollama check (warn only)
18
43
  if (config.defaults.memory) {
19
44
  const ollama = await ensureOllama();
@@ -27,6 +52,7 @@ export async function start() {
27
52
  configPath: CONFIG_PATH,
28
53
  usageStore,
29
54
  dataDir: DATA_DIR,
55
+ telemetryDb: getTelemetryDb(),
30
56
  }, apiPort);
31
57
  // Build primary agent runtime (for REPL)
32
58
  const agentNames = Object.keys(config.agents);
@@ -36,17 +62,18 @@ export async function start() {
36
62
  console.error(`Agent "${agentName}" not found. Available: ${names}`);
37
63
  process.exit(1);
38
64
  }
39
- const runtime = buildAgentRuntime(agentName, config, {
65
+ const runtime = await buildAgentRuntime(agentName, config, {
40
66
  dataDir: DATA_DIR,
41
67
  skillsDir: SKILLS_DIR,
42
68
  usageStore,
69
+ configPath: CONFIG_PATH,
43
70
  });
44
71
  const warnings = runtime.toolRegistry.validateAgents(config.agents);
45
72
  for (const w of warnings)
46
73
  log.warn(w);
47
74
  const { resolved, toolRegistry, agentRegistry, promptFragments, skillsIndex, soul, session } = runtime;
48
75
  // Cron scheduler
49
- const scheduler = new CronScheduler(config, toolRegistry, agentRegistry, DATA_DIR, SKILLS_DIR, usageStore);
76
+ const scheduler = new CronScheduler(config, toolRegistry, agentRegistry, DATA_DIR, SKILLS_DIR, usageStore, configWriter);
50
77
  const enabledJobs = scheduler.getJobs().filter((j) => j.enabled);
51
78
  // Agent executor for Slack
52
79
  const executeAgent = createAgentExecutor(config, {
@@ -55,10 +82,14 @@ export async function start() {
55
82
  agentRegistry,
56
83
  usageStore,
57
84
  source: "slack",
85
+ configPath: CONFIG_PATH,
58
86
  });
59
87
  // Slack gateway
60
88
  let gateway;
61
89
  const hasSlackBindings = Object.values(config.agents).some((a) => a.slack);
90
+ if (hasSlackBindings && (!process.env.SLACK_BOT_TOKEN || !process.env.SLACK_APP_TOKEN)) {
91
+ log.warn("Slack bindings configured but SLACK_BOT_TOKEN/SLACK_APP_TOKEN not found — run `agent-kit slack-setup`");
92
+ }
62
93
  if (hasSlackBindings && process.env.SLACK_BOT_TOKEN && process.env.SLACK_APP_TOKEN) {
63
94
  try {
64
95
  gateway = createSlackGateway(config, { onAgentRequest: executeAgent });
@@ -79,22 +110,35 @@ export async function start() {
79
110
  log.warn({ err }, "failed to start API server");
80
111
  }
81
112
  // Cron
113
+ const cronCallbacks = {
114
+ onResult: (jobId, agentName, result) => {
115
+ log.info({ jobId, tokens: result.usage.inputTokens + result.usage.outputTokens }, "cron job completed");
116
+ process.stdout.write("You: ");
117
+ const channelOverride = config.cron.find((j) => j.id === jobId)?.slack?.channelId;
118
+ gateway?.onJobResult(agentName, jobId, result, channelOverride).catch((err) => log.warn({ err, jobId }, "failed to post cron result to Slack"));
119
+ },
120
+ onError: (jobId, agentName, error) => {
121
+ log.error({ err: error, jobId }, "cron job failed");
122
+ process.stdout.write("You: ");
123
+ const channelOverride = config.cron.find((j) => j.id === jobId)?.slack?.channelId;
124
+ gateway?.onJobError(agentName, jobId, error, channelOverride).catch((err) => log.warn({ err, jobId }, "failed to post cron error to Slack"));
125
+ },
126
+ };
82
127
  if (enabledJobs.length > 0) {
83
- scheduler.start({
84
- onResult: (jobId, agentName, result) => {
85
- log.info({ jobId, tokens: result.usage.inputTokens + result.usage.outputTokens }, "cron job completed");
86
- process.stdout.write("You: ");
87
- const channelOverride = config.cron.find((j) => j.id === jobId)?.slack?.channelId;
88
- gateway?.onJobResult(agentName, jobId, result, channelOverride).catch((err) => log.warn({ err, jobId }, "failed to post cron result to Slack"));
89
- },
90
- onError: (jobId, agentName, error) => {
91
- log.error({ err: error, jobId }, "cron job failed");
92
- process.stdout.write("You: ");
93
- const channelOverride = config.cron.find((j) => j.id === jobId)?.slack?.channelId;
94
- gateway?.onJobError(agentName, jobId, error, channelOverride).catch((err) => log.warn({ err, jobId }, "failed to post cron error to Slack"));
95
- },
96
- });
128
+ scheduler.start(cronCallbacks);
97
129
  }
130
+ // Hot-reload: restart cron when config changes
131
+ configWriter.on("config:changed", () => {
132
+ try {
133
+ const newConfig = loadConfig(CONFIG_PATH);
134
+ scheduler.reload(newConfig);
135
+ scheduler.start(cronCallbacks);
136
+ log.info("hot-reload: cron scheduler restarted");
137
+ }
138
+ catch (err) {
139
+ log.error({ err }, "hot-reload: failed to reload config");
140
+ }
141
+ });
98
142
  // Heartbeat
99
143
  const heartbeat = new HeartbeatRunner(config, agentRegistry, DATA_DIR, SKILLS_DIR, usageStore);
100
144
  const heartbeatAgents = heartbeat.getHeartbeatAgents();
@@ -176,6 +220,7 @@ export async function start() {
176
220
  },
177
221
  },
178
222
  onQuit: async () => {
223
+ configWriter.stopWatching();
179
224
  scheduler.stop();
180
225
  heartbeat.stop();
181
226
  await gateway?.stop();
@@ -184,6 +229,10 @@ export async function start() {
184
229
  usageStore?.close();
185
230
  }
186
231
  catch { }
232
+ await Promise.race([
233
+ shutdownTelemetry(),
234
+ new Promise((r) => setTimeout(r, 5000)),
235
+ ]);
187
236
  },
188
237
  });
189
238
  }
package/dist/cli/ui.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ declare const VERSION: any;
2
+ export { VERSION };
1
3
  export declare function banner(): void;
2
4
  export declare function done(message: string): void;
3
5
  /**
package/dist/cli/ui.js CHANGED
@@ -1,7 +1,10 @@
1
+ import { createRequire } from "module";
1
2
  import * as fs from "fs";
2
3
  import * as path from "path";
3
4
  import * as p from "@clack/prompts";
4
- const VERSION = "0.1.0";
5
+ const require = createRequire(import.meta.url);
6
+ const { version: VERSION } = require("../../package.json");
7
+ export { VERSION };
5
8
  export function banner() {
6
9
  console.log();
7
10
  p.intro(`agent-kit v${VERSION}`);
@@ -0,0 +1 @@
1
+ export declare function showWhatsNew(): void;
@@ -0,0 +1,69 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as os from "os";
4
+ import { VERSION } from "./ui.js";
5
+ const RELEASE_NOTES = {
6
+ "0.1.6": [
7
+ "\"What's new\" notification after CLI updates",
8
+ "Auto-activate skills per agent",
9
+ "Hot-reload on external config file changes",
10
+ ],
11
+ "0.1.5": [
12
+ "Agent self-configuration tools",
13
+ "Bookkeeper: bump maxIterations, filter uncategorized by year",
14
+ "Generate CLAUDE.md and fix pnpm native deps on init",
15
+ ],
16
+ };
17
+ const VERSION_FILE = path.join(os.homedir(), ".agent-kit", ".last-version");
18
+ function getLastSeenVersion() {
19
+ try {
20
+ return fs.readFileSync(VERSION_FILE, "utf-8").trim() || null;
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ function setLastSeenVersion(version) {
27
+ try {
28
+ fs.mkdirSync(path.dirname(VERSION_FILE), { recursive: true });
29
+ fs.writeFileSync(VERSION_FILE, version);
30
+ }
31
+ catch {
32
+ // never crash startup
33
+ }
34
+ }
35
+ export function showWhatsNew() {
36
+ try {
37
+ const last = getLastSeenVersion();
38
+ // First run — just record the version, don't spam
39
+ if (last === null) {
40
+ setLastSeenVersion(VERSION);
41
+ return;
42
+ }
43
+ if (last === VERSION)
44
+ return;
45
+ // Collect notes for versions newer than last-seen
46
+ const entries = [];
47
+ for (const [ver, notes] of Object.entries(RELEASE_NOTES)) {
48
+ if (ver > last)
49
+ entries.push([ver, notes]);
50
+ }
51
+ if (entries.length > 0) {
52
+ // Sort descending
53
+ entries.sort((a, b) => (b[0] > a[0] ? 1 : -1));
54
+ console.log();
55
+ console.log(" \u2728 What's new in agent-kit");
56
+ for (const [ver, notes] of entries) {
57
+ console.log(`\n v${ver}`);
58
+ for (const note of notes) {
59
+ console.log(` • ${note}`);
60
+ }
61
+ }
62
+ console.log();
63
+ }
64
+ setLastSeenVersion(VERSION);
65
+ }
66
+ catch {
67
+ // never crash startup
68
+ }
69
+ }
package/dist/cli.js CHANGED
@@ -29,6 +29,19 @@ async function main() {
29
29
  await chat(args[0]);
30
30
  break;
31
31
  }
32
+ case "slack-setup": {
33
+ const fs = await import("fs");
34
+ const { setupSlack } = await import("./cli/slack-setup.js");
35
+ const { banner } = await import("./cli/ui.js");
36
+ const { CONFIG_PATH } = await import("./cli/paths.js");
37
+ banner();
38
+ if (!fs.existsSync(CONFIG_PATH)) {
39
+ console.error("No agent-kit.json found. Run `agent-kit init` first.");
40
+ process.exit(1);
41
+ }
42
+ await setupSlack({ configPath: CONFIG_PATH, fromInit: false });
43
+ break;
44
+ }
32
45
  case "validate": {
33
46
  const { validate } = await import("./cli/validate.js");
34
47
  validate(args[0]);
@@ -44,6 +57,7 @@ async function main() {
44
57
  agent-kit create AI-powered agent creation
45
58
  agent-kit list Show configured agents
46
59
  agent-kit chat <agent> Lightweight single-agent chat
60
+ agent-kit slack-setup Interactive Slack setup
47
61
  agent-kit validate [agent] Validate config
48
62
 
49
63
  Options:
@@ -8,6 +8,7 @@ export interface ResolvedAgent {
8
8
  tools: Tool[];
9
9
  skills: SkillManifest[];
10
10
  canSpawn: SpawnTarget[];
11
+ autoActivateSkills: boolean;
11
12
  maxIterations: number;
12
13
  maxTokens: number;
13
14
  compactionThreshold: number;
@@ -34,6 +34,7 @@ export function resolveAgent(name, config, toolRegistry, skillsDir) {
34
34
  tools: toolRegistry.resolve(agentDef.tools),
35
35
  skills,
36
36
  canSpawn: agentDef.can_spawn,
37
+ autoActivateSkills: agentDef.autoActivateSkills,
37
38
  maxIterations: agentDef.maxIterations ?? config.defaults.maxIterations,
38
39
  maxTokens: agentDef.maxTokens ?? config.defaults.maxTokens,
39
40
  compactionThreshold: config.defaults.compactionThreshold,
@@ -56,6 +56,7 @@ declare const AgentSchema: z.ZodObject<{
56
56
  model: z.ZodOptional<z.ZodString>;
57
57
  tools: z.ZodDefault<z.ZodArray<z.ZodString>>;
58
58
  skills: z.ZodDefault<z.ZodArray<z.ZodString>>;
59
+ autoActivateSkills: z.ZodDefault<z.ZodBoolean>;
59
60
  spawn_only: z.ZodDefault<z.ZodBoolean>;
60
61
  can_spawn: z.ZodDefault<z.ZodArray<z.ZodObject<{
61
62
  agent: z.ZodString;
@@ -150,6 +151,7 @@ export declare const ConfigSchema: z.ZodObject<{
150
151
  model: z.ZodOptional<z.ZodString>;
151
152
  tools: z.ZodDefault<z.ZodArray<z.ZodString>>;
152
153
  skills: z.ZodDefault<z.ZodArray<z.ZodString>>;
154
+ autoActivateSkills: z.ZodDefault<z.ZodBoolean>;
153
155
  spawn_only: z.ZodDefault<z.ZodBoolean>;
154
156
  can_spawn: z.ZodDefault<z.ZodArray<z.ZodObject<{
155
157
  agent: z.ZodString;
@@ -83,6 +83,7 @@ const AgentSchema = z.object({
83
83
  model: z.string().min(1).optional(),
84
84
  tools: z.array(z.string()).default([]),
85
85
  skills: z.array(z.string().min(1)).default([]),
86
+ autoActivateSkills: z.boolean().default(false),
86
87
  spawn_only: z.boolean().default(false),
87
88
  can_spawn: z.array(SpawnTargetSchema).default([]),
88
89
  maxIterations: z.number().positive().int().optional(),
@@ -0,0 +1,18 @@
1
+ import { EventEmitter } from "events";
2
+ export declare class ConfigWriter extends EventEmitter {
3
+ private configPath;
4
+ private lockPromise;
5
+ private watcher?;
6
+ private debounceTimer?;
7
+ private lastSelfWriteMs;
8
+ constructor(configPath: string);
9
+ /** Read the current config from disk (fresh read, no cache). */
10
+ read(): Record<string, any>;
11
+ /** Write config atomically (temp file + rename). Emits config:changed. */
12
+ write(config: Record<string, any>): void;
13
+ /** Watch the config file for external changes. Debounced, ignores self-writes. */
14
+ watch(): void;
15
+ stopWatching(): void;
16
+ /** Mutex-protected read-modify-write. The callback mutates config in place. */
17
+ mutate(fn: (config: Record<string, any>) => void): Promise<void>;
18
+ }
@@ -0,0 +1,85 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as os from "os";
4
+ import { EventEmitter } from "events";
5
+ import { createLogger } from "../logger.js";
6
+ const log = createLogger("config:writer");
7
+ export class ConfigWriter extends EventEmitter {
8
+ configPath;
9
+ lockPromise = Promise.resolve();
10
+ watcher;
11
+ debounceTimer;
12
+ lastSelfWriteMs = 0;
13
+ constructor(configPath) {
14
+ super();
15
+ this.configPath = configPath;
16
+ }
17
+ /** Read the current config from disk (fresh read, no cache). */
18
+ read() {
19
+ const raw = fs.readFileSync(this.configPath, "utf-8");
20
+ return JSON.parse(raw);
21
+ }
22
+ /** Write config atomically (temp file + rename). Emits config:changed. */
23
+ write(config) {
24
+ const tmp = path.join(path.dirname(this.configPath), `.agent-kit-${process.pid}-${Date.now()}.tmp`);
25
+ fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + os.EOL);
26
+ try {
27
+ fs.renameSync(tmp, this.configPath);
28
+ }
29
+ catch (err) {
30
+ try {
31
+ fs.unlinkSync(tmp);
32
+ }
33
+ catch { /* ignore cleanup error */ }
34
+ throw err;
35
+ }
36
+ log.info("config written");
37
+ this.lastSelfWriteMs = Date.now();
38
+ this.emit("config:changed", config);
39
+ }
40
+ /** Watch the config file for external changes. Debounced, ignores self-writes. */
41
+ watch() {
42
+ if (this.watcher)
43
+ return;
44
+ const DEBOUNCE_MS = 250;
45
+ const SELF_WRITE_WINDOW_MS = 1000;
46
+ this.watcher = fs.watch(this.configPath, () => {
47
+ if (this.debounceTimer)
48
+ clearTimeout(this.debounceTimer);
49
+ this.debounceTimer = setTimeout(() => {
50
+ // Ignore fs events triggered by our own write()
51
+ if (Date.now() - this.lastSelfWriteMs < SELF_WRITE_WINDOW_MS)
52
+ return;
53
+ try {
54
+ const config = this.read();
55
+ log.info("external config change detected");
56
+ this.emit("config:changed", config);
57
+ }
58
+ catch (err) {
59
+ log.error({ err }, "failed to read config after external change");
60
+ }
61
+ }, DEBOUNCE_MS);
62
+ });
63
+ }
64
+ stopWatching() {
65
+ if (this.debounceTimer)
66
+ clearTimeout(this.debounceTimer);
67
+ this.watcher?.close();
68
+ this.watcher = undefined;
69
+ }
70
+ /** Mutex-protected read-modify-write. The callback mutates config in place. */
71
+ async mutate(fn) {
72
+ const prev = this.lockPromise;
73
+ let resolve;
74
+ this.lockPromise = new Promise((r) => { resolve = r; });
75
+ await prev;
76
+ try {
77
+ const config = this.read();
78
+ fn(config);
79
+ this.write(config);
80
+ }
81
+ finally {
82
+ resolve();
83
+ }
84
+ }
85
+ }
@@ -2,6 +2,7 @@ import type { Config, CronJobDef } from "../config/schema.js";
2
2
  import type { ToolRegistry } from "../tools/registry.js";
3
3
  import type { AgentResult } from "../agent/types.js";
4
4
  import type { AgentRegistry } from "../multi/registry.js";
5
+ import type { ConfigWriter } from "../config/writer.js";
5
6
  import type { UsageStore } from "../usage/store.js";
6
7
  export declare class CronScheduler {
7
8
  private tasks;
@@ -11,12 +12,14 @@ export declare class CronScheduler {
11
12
  private dataDir;
12
13
  private skillsDir;
13
14
  private usageStore?;
14
- constructor(config: Config, toolRegistry: ToolRegistry, agentRegistry: AgentRegistry, dataDir: string, skillsDir?: string, usageStore?: UsageStore);
15
+ private configWriter?;
16
+ constructor(config: Config, toolRegistry: ToolRegistry, agentRegistry: AgentRegistry, dataDir: string, skillsDir?: string, usageStore?: UsageStore, configWriter?: ConfigWriter);
15
17
  getJobs(): CronJobDef[];
16
18
  runJob(jobId: string): Promise<AgentResult | undefined>;
17
19
  start(callbacks?: {
18
20
  onResult?: (jobId: string, agentName: string, result: AgentResult) => void;
19
21
  onError?: (jobId: string, agentName: string, error: Error) => void;
20
22
  }): void;
23
+ reload(newConfig: Config): void;
21
24
  stop(): void;
22
25
  }