loopengine 0.1.68 → 0.1.70

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.
@@ -2,9 +2,12 @@ import { agentDirFor, createAgentEnv } from '../core/agent-env.js';
2
2
  import { decidePath } from '../core/approval-links.js';
3
3
  import { describeNotifier } from '../core/http-notifier.js';
4
4
  import { sendTestSlackMessage } from '../core/http-notify-triggers/slack.js';
5
- import { sendTestWebhook } from '../core/http-notify-triggers/webhook.js';
5
+ import { postLifecycleWebhook, sendTestWebhook } from '../core/http-notify-triggers/webhook.js';
6
+ import { SlackNotifier } from '../core/http-notify-triggers/slack.js';
7
+ import { LarkNotifier } from '../core/http-notify-triggers/lark.js';
6
8
  import { listDeliveries } from '../core/notifier-deliveries.js';
7
- import { NOTIFIER_SECRET_VARS, notifierConfigFromFile, readNotifierFile, removeNotifierFile, writeNotifierFile } from '../core/notifier-file.js';
9
+ import { NOTIFIER_SECRET_VARS, NOTIFIER_SLACK_SIGNING_SECRET_VAR, notifierConfigFromFile, readNotifierFile, removeNotifierFile, writeNotifierFile } from '../core/notifier-file.js';
10
+ import { listSlackChannels, slackAppManifest } from '../core/slack-interactions.js';
8
11
  import { setAgentEnvVar } from './env-admin.js';
9
12
  export function describeAgentNotifier(config, defaultWebhook) {
10
13
  const described = describeNotifier(config);
@@ -18,6 +21,15 @@ export function describeAgentNotifier(config, defaultWebhook) {
18
21
  publicUrl: process.env.LOOPENGINE_PUBLIC_URL ?? null,
19
22
  secretVars: NOTIFIER_SECRET_VARS,
20
23
  secretsSet,
24
+ slack: {
25
+ signingSecretVar: NOTIFIER_SLACK_SIGNING_SECRET_VAR,
26
+ signingSecretSet: env.get(NOTIFIER_SLACK_SIGNING_SECRET_VAR) !== undefined,
27
+ // Where the Slack app's Interactivity Request URL should point.
28
+ interactivityPath: `/hooks/slack/${encodeURIComponent(config.name)}`,
29
+ // The console swaps __INTERACTIVITY_URL__ for the link address +
30
+ // interactivityPath as you type.
31
+ manifestTemplate: slackAppManifest({ agentName: config.name, interactivityUrl: '__INTERACTIVITY_URL__' }),
32
+ },
21
33
  deliveries: listDeliveries(config.name),
22
34
  };
23
35
  }
@@ -29,12 +41,75 @@ export async function saveAgentNotifier(config, body) {
29
41
  if (typeof body.secret === 'string' && body.secret.trim()) {
30
42
  await setAgentEnvVar(config.name, NOTIFIER_SECRET_VARS[file.channel], body.secret.trim());
31
43
  }
44
+ if (file.channel === 'slack' && typeof body.signingSecret === 'string' && body.signingSecret.trim()) {
45
+ await setAgentEnvVar(config.name, NOTIFIER_SLACK_SIGNING_SECRET_VAR, body.signingSecret.trim());
46
+ }
47
+ }
48
+ /** The bot token and signing secret behind an agent's Slack notifier —
49
+ * from AgentConfig.httpNotifier when it's Slack, else from the agent's
50
+ * env (the console-saved ones). The signing secret always comes from the
51
+ * agent env. */
52
+ export function slackCredentialsFor(config) {
53
+ const env = createAgentEnv(config.name, agentDirFor(config));
54
+ const botToken = config.httpNotifier?.channel === 'slack' ? config.httpNotifier.config.botToken : env.get(NOTIFIER_SECRET_VARS.slack);
55
+ return { botToken, signingSecret: env.get(NOTIFIER_SLACK_SIGNING_SECRET_VAR) };
56
+ }
57
+ /** Channels for the console's picker, with the token being typed in or,
58
+ * when none is given, the one already saved. */
59
+ export async function listAgentSlackChannels(config, botToken) {
60
+ const token = botToken?.trim() || slackCredentialsFor(config).botToken;
61
+ if (!token)
62
+ throw new Error('Enter the bot token first');
63
+ return listSlackChannels(token);
32
64
  }
33
65
  /** Removes notifier.yml; the stored secret is left alone, so turning the
34
66
  * notifier back on doesn't need it re-entered. */
35
67
  export function removeAgentNotifier(config) {
36
68
  removeNotifierFile(agentDirFor(config));
37
69
  }
70
+ /** The notifier settings that apply to an agent: its code httpNotifier,
71
+ * else its notifier.yml (once its secret is set), else the
72
+ * deployment-wide default webhook. */
73
+ function activeNotifierTarget(config, defaultWebhook) {
74
+ if (config.httpNotifier)
75
+ return config.httpNotifier;
76
+ const dir = agentDirFor(config);
77
+ const file = readNotifierFile(dir);
78
+ if (file) {
79
+ const { config: fromFile } = notifierConfigFromFile(file, createAgentEnv(config.name, dir));
80
+ if (fromFile)
81
+ return fromFile;
82
+ }
83
+ return defaultWebhook ? { channel: 'webhook', config: { webhookUrl: defaultWebhook.webhookUrl, webhookSecret: defaultWebhook.webhookSecret }, events: ['run_finish'] } : undefined;
84
+ }
85
+ /** Posts a plain announcement through the agent's notifier — a scheduled
86
+ * task's result, for one — regardless of which events it subscribed to.
87
+ * A webhook gets a signed `run_finish` payload with `data` merged in;
88
+ * Slack and Lark get `text`. Does nothing when no notifier applies. */
89
+ export function announceToNotifier(config, text, data, defaultWebhook) {
90
+ let target;
91
+ try {
92
+ target = activeNotifierTarget(config, defaultWebhook);
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ if (!target)
98
+ return false;
99
+ switch (target.channel) {
100
+ case 'webhook':
101
+ postLifecycleWebhook(target.config.webhookUrl, target.config.webhookSecret, 'run_finish', { agent: config.name, text, ...data });
102
+ return true;
103
+ case 'slack':
104
+ new SlackNotifier({ botToken: target.config.botToken, channelId: target.config.channelId }).postMessage(text);
105
+ return true;
106
+ case 'lark':
107
+ new LarkNotifier({ appId: target.config.appId, appSecret: target.config.appSecret, chatId: target.config.chatId }).postMessage(text);
108
+ return true;
109
+ default:
110
+ return false;
111
+ }
112
+ }
38
113
  /** Sends a test through whichever notifier applies to this agent —
39
114
  * webhook or Slack; reports what the receiver answered. */
40
115
  export async function testAgentNotifier(config, defaultWebhook) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopengine",
3
- "version": "0.1.68",
3
+ "version": "0.1.70",
4
4
  "type": "module",
5
5
  "description": "Runtime for tool-using AI agents with a transparent ReAct loop.",
6
6
  "license": "MIT",
@@ -50,6 +50,10 @@
50
50
  "dist/core/notifier-deliveries.d.ts",
51
51
  "dist/core/approval-links.js",
52
52
  "dist/core/approval-links.d.ts",
53
+ "dist/core/slack-interactions.js",
54
+ "dist/core/slack-interactions.d.ts",
55
+ "dist/core/scheduler.js",
56
+ "dist/core/scheduler.d.ts",
53
57
  "dist/core/session-store.js",
54
58
  "dist/core/session-store.d.ts",
55
59
  "dist/core/vector-index.js",
@@ -100,7 +104,7 @@
100
104
  "LICENSE"
101
105
  ],
102
106
  "scripts": {
103
- "build": "tsc; s=$?; chmod +x dist/bin/cli.js; cp -r core/system-skills dist/core/system-skills; tsx scripts/write-dist-package-json.ts; exit $s",
107
+ "build": "tsc; s=$?; chmod +x dist/bin/cli.js; rm -rf dist/core/system-skills; cp -r core/system-skills dist/core/system-skills; tsx scripts/write-dist-package-json.ts; exit $s",
104
108
  "prepublishOnly": "npm run build",
105
109
  "start": "node --env-file-if-exists=.env dist/adapters/http.js",
106
110
  "test": "vitest run"