hive-am 1.4.0 → 1.6.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/CHANGELOG.md +52 -1
- package/README.md +6 -9
- package/bin/hive-am.mjs +242 -31
- package/bin/run.mjs +135 -0
- package/package.json +1 -1
- package/server/dist/connections/format.js +26 -0
- package/server/dist/connections/index.js +2 -0
- package/server/dist/connections/manager.js +18 -6
- package/server/dist/connections/router.js +43 -13
- package/server/dist/connections/secrets.js +9 -1
- package/server/dist/connections/slack.js +488 -0
- package/server/dist/connections/store.js +2 -0
- package/server/dist/db/migrations.js +80 -0
- package/server/dist/db.js +26 -13
- package/server/dist/dispatches.js +126 -0
- package/server/dist/efforts.js +105 -0
- package/server/dist/http/routes/agents.js +4 -3
- package/server/dist/http/routes/meta.js +12 -0
- package/server/dist/http/routes/notebook.js +10 -2
- package/server/dist/http/routes/orchestration.js +13 -2
- package/server/dist/http/routes/resources.js +3 -0
- package/server/dist/http/routes/types.js +18 -1
- package/server/dist/http/validate.js +27 -4
- package/server/dist/index.js +6 -1
- package/server/dist/instructions.js +11 -3
- package/server/dist/objects/agent-db.js +258 -0
- package/server/dist/objects/database.js +496 -34
- package/server/dist/objects/index.js +16 -8
- package/server/dist/providers/kiro.js +7 -1
- package/server/dist/providers/opencode-server.js +11 -1
- package/server/dist/providers/opencode.js +4 -3
- package/server/dist/providers/spawn.js +38 -4
- package/server/dist/retention.js +52 -0
- package/server/dist/runtime.js +69 -27
- package/server/dist/services/agents.js +53 -1
- package/server/dist/services/connections.js +18 -3
- package/server/dist/services/notebook.js +7 -3
- package/server/dist/skills/defaults.js +41 -5
- package/server/dist/skills/notebook.js +57 -13
- package/server/dist/watch.js +1 -1
- package/server/mcp/dispatch.mjs +116 -5
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/app-build-manifest.json +98 -82
- package/web/.next/app-path-routes-manifest.json +7 -7
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +51 -51
- package/web/.next/server/app/_not-found/page.js +2 -2
- package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/server/app/_not-found.rsc +3 -3
- package/web/.next/server/app/agents/[id]/page.js +4 -4
- package/web/.next/server/app/agents/[id]/page.js.nft.json +1 -1
- package/web/.next/server/app/agents/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/agents/page.js +2 -2
- package/web/.next/server/app/agents/page.js.nft.json +1 -1
- package/web/.next/server/app/agents/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/agents.html +1 -1
- package/web/.next/server/app/agents.rsc +21 -18
- package/web/.next/server/app/chat/page.js +2 -2
- package/web/.next/server/app/chat/page.js.nft.json +1 -1
- package/web/.next/server/app/chat/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/chat.html +1 -1
- package/web/.next/server/app/chat.rsc +4 -4
- package/web/.next/server/app/connections/page.js +2 -2
- package/web/.next/server/app/connections/page.js.nft.json +1 -1
- package/web/.next/server/app/connections/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/connections.html +1 -1
- package/web/.next/server/app/connections.rsc +4 -4
- package/web/.next/server/app/index.html +1 -1
- package/web/.next/server/app/index.rsc +4 -4
- package/web/.next/server/app/page.js +2 -2
- package/web/.next/server/app/page.js.nft.json +1 -1
- package/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/relations/page.js +2 -2
- package/web/.next/server/app/relations/page.js.nft.json +1 -1
- package/web/.next/server/app/relations/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/relations.html +1 -1
- package/web/.next/server/app/relations.rsc +4 -4
- package/web/.next/server/app/resources/[id]/page.js +2 -2
- package/web/.next/server/app/resources/[id]/page.js.nft.json +1 -1
- package/web/.next/server/app/resources/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/resources/page.js +2 -2
- package/web/.next/server/app/resources/page.js.nft.json +1 -1
- package/web/.next/server/app/resources/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/resources.html +1 -1
- package/web/.next/server/app/resources.rsc +4 -4
- package/web/.next/server/app/schedules/page.js +2 -2
- package/web/.next/server/app/schedules/page.js.nft.json +1 -1
- package/web/.next/server/app/schedules/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/schedules.html +1 -1
- package/web/.next/server/app/schedules.rsc +4 -4
- package/web/.next/server/app/sessions/page.js +2 -2
- package/web/.next/server/app/sessions/page.js.nft.json +1 -1
- package/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/sessions.html +1 -1
- package/web/.next/server/app/sessions.rsc +4 -4
- package/web/.next/server/app/skills/page.js +2 -2
- package/web/.next/server/app/skills/page.js.nft.json +1 -1
- package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/skills.html +1 -1
- package/web/.next/server/app/skills.rsc +4 -4
- package/web/.next/server/app/types/page.js +2 -2
- package/web/.next/server/app/types/page.js.nft.json +1 -1
- package/web/.next/server/app/types/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/types.html +1 -1
- package/web/.next/server/app/types.rsc +4 -4
- package/web/.next/server/app-paths-manifest.json +7 -7
- package/web/.next/server/chunks/{505.js → 111.js} +1 -1
- package/web/.next/server/chunks/143.js +1 -1
- package/web/.next/server/chunks/213.js +1 -1
- package/web/.next/server/chunks/368.js +1 -1
- package/web/.next/server/chunks/403.js +1 -1
- package/web/.next/server/chunks/612.js +1 -1
- package/web/.next/server/chunks/741.js +2 -0
- package/web/.next/server/chunks/837.js +1 -0
- package/web/.next/server/chunks/{435.js → 939.js} +3 -3
- package/web/.next/server/pages/404.html +1 -1
- package/web/.next/server/pages/500.html +1 -1
- package/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/static/chunks/162-68ba190086f5ad39.js +1 -0
- package/web/.next/static/chunks/214-35b88db02113ae02.js +1 -0
- package/web/.next/static/chunks/281-4506bc98b7111729.js +1 -0
- package/web/.next/static/chunks/491-196bcb3dd11bb460.js +1 -0
- package/web/.next/static/chunks/517-8f2db464d10a4624.js +1 -0
- package/web/.next/static/chunks/{586-f7017d585a84738d.js → 586-4f67b823df060b46.js} +1 -1
- package/web/.next/static/chunks/587-3c53d91a1c4f99d6.js +1 -0
- package/web/.next/static/chunks/606-14fb5aaa4d0ff22d.js +1 -0
- package/web/.next/static/chunks/642-dc874c8caf203ee1.js +1 -0
- package/web/.next/static/chunks/{705-f81face97b1bb1af.js → 705-ce86175dea00e6a9.js} +1 -1
- package/web/.next/static/chunks/757-03361e7a97a605a2.js +1 -0
- package/web/.next/static/chunks/771-db19da9b1a24820c.js +1 -0
- package/web/.next/static/chunks/app/agents/[id]/page-803d53aedd10c8e5.js +1 -0
- package/web/.next/static/chunks/app/agents/layout-71dd98d696935e29.js +1 -0
- package/web/.next/static/chunks/app/agents/page-9da499db6d22731f.js +1 -0
- package/web/.next/static/chunks/app/chat/{page-5261b12b7d50f5d1.js → page-383bc24e72dab7fa.js} +1 -1
- package/web/.next/static/chunks/app/connections/page-b0ea2eae62cef74e.js +1 -0
- package/web/.next/static/chunks/app/layout-cb5af29b0742c513.js +1 -0
- package/web/.next/static/chunks/app/page-5e547e87de23d9fa.js +1 -0
- package/web/.next/static/chunks/app/relations/page-02710fe1f8a670a9.js +1 -0
- package/web/.next/static/chunks/app/resources/[id]/page-1e2afddcb1f04884.js +1 -0
- package/web/.next/static/chunks/app/resources/{page-61cccd8fdcc2f6c8.js → page-195b0af83b8ace0d.js} +1 -1
- package/web/.next/static/chunks/app/sessions/page-85638705fdc18174.js +1 -0
- package/web/.next/static/chunks/app/types/page-c4c2b2217c4667e8.js +1 -0
- package/web/.next/static/css/c7faf690373117bb.css +1 -0
- package/web/.next/trace +30 -30
- package/web/.next/types/routes.d.ts +2 -1
- package/web/.next/types/validator.ts +9 -0
- package/web/.next/server/chunks/274.js +0 -1
- package/web/.next/static/chunks/281-113274e705940a55.js +0 -1
- package/web/.next/static/chunks/316-26da9cc233ba7557.js +0 -1
- package/web/.next/static/chunks/391-18768499e741112e.js +0 -1
- package/web/.next/static/chunks/517-373a80c8ba432bdc.js +0 -1
- package/web/.next/static/chunks/562-7c2e33d838e869bf.js +0 -1
- package/web/.next/static/chunks/587-8e4d2be01ba7d48f.js +0 -1
- package/web/.next/static/chunks/606-c9a19918b02a98d0.js +0 -1
- package/web/.next/static/chunks/656-f537149fe28f79aa.js +0 -1
- package/web/.next/static/chunks/app/agents/[id]/page-6ed6edb3e74b0ebe.js +0 -1
- package/web/.next/static/chunks/app/agents/page-9bb8cba69776c719.js +0 -1
- package/web/.next/static/chunks/app/connections/page-eb11cfb1ffeab3ca.js +0 -1
- package/web/.next/static/chunks/app/layout-fad968a635f9a4b3.js +0 -1
- package/web/.next/static/chunks/app/page-99e0351e6ccaa384.js +0 -1
- package/web/.next/static/chunks/app/relations/page-a44fd515a3b0525e.js +0 -1
- package/web/.next/static/chunks/app/resources/[id]/page-fc494e85b01e8cb7.js +0 -1
- package/web/.next/static/chunks/app/sessions/page-a7534ebd90e982d6.js +0 -1
- package/web/.next/static/chunks/app/types/page-ebd37e220f77c1f3.js +0 -1
- package/web/.next/static/css/582723fcea1f9887.css +0 -1
- /package/web/.next/static/{YT6HM6qmegMJaBMX5QvuP → tdnUXVa01m25EjOobxVd1}/_buildManifest.js +0 -0
- /package/web/.next/static/{YT6HM6qmegMJaBMX5QvuP → tdnUXVa01m25EjOobxVd1}/_ssgManifest.js +0 -0
|
@@ -2,6 +2,7 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import { agents } from '../db.js';
|
|
3
3
|
import { liveOrigin, notifyAgentsChanged } from '../runtime.js';
|
|
4
4
|
import { cleanInbox } from './files.js';
|
|
5
|
+
import { retentionPass, retentionTouched } from '../retention.js';
|
|
5
6
|
import { resolveSendable } from './outbound.js';
|
|
6
7
|
import { syncChannelSkill } from './channel-skill.js';
|
|
7
8
|
import { handleInbound } from './router.js';
|
|
@@ -59,10 +60,21 @@ export async function stopConnection(id) {
|
|
|
59
60
|
catch { /* already down */ }
|
|
60
61
|
}
|
|
61
62
|
let cleaner;
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
/** Received files are kept for a while, then removed; and what hive-am keeps of its own record is aged out (retention.ts). */
|
|
64
|
+
function housekeeping() {
|
|
64
65
|
cleanInbox();
|
|
65
|
-
|
|
66
|
+
try {
|
|
67
|
+
const r = retentionPass();
|
|
68
|
+
if (retentionTouched(r))
|
|
69
|
+
console.log(`[retention] removed ${r.messages} messages, ${r.dispatches} dispatches, ${r.sessions} sessions; emptied ${r.files} file records`);
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
console.error('[retention]', e);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export const startAll = () => {
|
|
76
|
+
housekeeping();
|
|
77
|
+
cleaner ??= setInterval(housekeeping, 6 * 3_600_000).unref();
|
|
66
78
|
return Promise.all(connections.list().filter((c) => c.enabled).map((c) => startConnection(c.id)));
|
|
67
79
|
};
|
|
68
80
|
export const stopAll = () => Promise.all([...running.keys()].map(stopConnection));
|
|
@@ -114,7 +126,7 @@ export async function channelReply(agentId, text) {
|
|
|
114
126
|
console.warn(`[connections] blocked a reply of agent ${agentId}: it seemed to contain ${leak}`);
|
|
115
127
|
throw new ChannelError(SECRET_REFUSAL(leak));
|
|
116
128
|
}
|
|
117
|
-
const { externalId } = await adapter.send(thread.target, text);
|
|
129
|
+
const { externalId } = await adapter.send(origin.target ?? thread.target, text);
|
|
118
130
|
threadMessages.record(thread.id, 'out', externalId, { name: agents.get(agentId)?.name }, text);
|
|
119
131
|
origin.replied = true;
|
|
120
132
|
return { sent: true, parts: 1 };
|
|
@@ -136,7 +148,7 @@ export async function channelReact(agentId, emoji) {
|
|
|
136
148
|
if (!thread)
|
|
137
149
|
throw new ChannelError('The thread no longer exists.');
|
|
138
150
|
try {
|
|
139
|
-
await adapter.react(thread.target, origin.messageId, e);
|
|
151
|
+
await adapter.react(origin.target ?? thread.target, origin.messageId, e);
|
|
140
152
|
}
|
|
141
153
|
catch (err) {
|
|
142
154
|
throw new ChannelError(err instanceof Error ? err.message : 'The reaction failed');
|
|
@@ -164,7 +176,7 @@ export async function channelSendFile(agentId, path, caption) {
|
|
|
164
176
|
console.warn(`[connections] blocked a file of agent ${agentId}: it seemed to contain ${leak}`);
|
|
165
177
|
throw new ChannelError(SECRET_REFUSAL(leak).replace('the message', `the ${caption && findSecret(caption) ? 'caption' : 'file'}`));
|
|
166
178
|
}
|
|
167
|
-
const { externalId } = await adapter.sendFile(thread.target, file, caption?.trim() || undefined);
|
|
179
|
+
const { externalId } = await adapter.sendFile(origin.target ?? thread.target, file, caption?.trim() || undefined);
|
|
168
180
|
threadMessages.record(thread.id, 'out', externalId, { name: agent.name }, `${caption?.trim() ? `${caption.trim()} ` : ''}[sent file: ${file.name}]`);
|
|
169
181
|
origin.replied = true;
|
|
170
182
|
return { sent: true, name: file.name, kind: file.kind };
|
|
@@ -10,9 +10,11 @@ import { connections, threadMessages, threads } from './store.js';
|
|
|
10
10
|
const MAX_INPUT = 8000;
|
|
11
11
|
const DEFAULT_RATE = 20; // messages per user per minute
|
|
12
12
|
const DEFAULT_CHATTER_PER_MINUTE = 10; // times per minute that chatter nobody aimed at the agent may wake it, per thread
|
|
13
|
+
const DENIED_PER_MINUTE = 5; // access notices per person and chat per minute, so a stranger can't make the bot spam a chat
|
|
14
|
+
const DENIED_CHAT_PER_MINUTE = 20; // access notices per chat per minute, whoever asks
|
|
13
15
|
/** Sent once when a turn aimed at the agent ended without a reply. */
|
|
14
16
|
const NUDGE = '[hive:channel] System reminder: your last turn ended without calling `channel_reply`, so the person received nothing. If you had an answer, send it now with `channel_reply` (if the tool says it is unknown, call it again: it was still connecting). If you chose not to answer, say so in one short sentence with `channel_reply`.';
|
|
15
|
-
|
|
17
|
+
import { validEffort } from '../efforts.js';
|
|
16
18
|
const QUIET_MS = 4000; // group chatter is handed over once it pauses this long, so a burst is one turn
|
|
17
19
|
const LONG_TURN_MS = 45_000; // an unaddressed turn this long did real work: if it never replied, remind it
|
|
18
20
|
const QUIET_RETRIES = 15; // while the agent is busy the hand-over waits (about a minute); after this it is left as context for the next turn
|
|
@@ -34,8 +36,15 @@ const T = {
|
|
|
34
36
|
newDone: { es: '🆕 Conversación nueva.', en: '🆕 New conversation.' },
|
|
35
37
|
muted: { es: '🔇 Me quedo callado en este hilo. Menciónenme o respondan a un mensaje mío para que vuelva a hablar.', en: '🔇 I\'ll stay quiet in this thread. Mention me or reply to one of my messages to bring me back.' },
|
|
36
38
|
unmuted: { es: '🔊 Listo, vuelvo a participar en este hilo.', en: '🔊 Done, I\'m back in this thread.' },
|
|
39
|
+
help: { es: 'Comandos: /new (o /clear, /reset) empieza una conversación nueva (solo administradores) · /stop detiene el turno en curso · /status estado del agente · /mute y /unmute me callan o me reactivan en este hilo · /id tu id y el de este chat · /help esta ayuda.', en: 'Commands: /new (or /clear, /reset) starts a new conversation (admins only) · /stop stops the turn in progress · /status the agent\'s state · /mute and /unmute silence me or bring me back in this thread · /id your id and this chat\'s · /help this help.' },
|
|
40
|
+
ids: (user, chat) => ({ es: `Tu id: ${user} · este chat: ${chat}`, en: `Your id: ${user} · this chat: ${chat}` }),
|
|
37
41
|
notAllowedChat: (user, chat) => ({ es: `No tienes acceso. Para usarme aquí, añade tu id (${user}) o este chat (${chat}) en la conexión.`, en: `You don't have access. To use me here, add your id (${user}) or this chat (${chat}) to the connection.` }),
|
|
42
|
+
notAllowedMember: (user, chat, slack) => slack
|
|
43
|
+
? { es: `Este canal solo admite a personas de su lista. Para usarme aquí, añade tu id (${user}) a los miembros de este canal (${chat}).`, en: `This channel only admits the people on its list. To use me here, add your id (${user}) to this channel's members (${chat}).` }
|
|
44
|
+
: { es: `Este chat solo admite a personas de su lista. Para usarme aquí, añade tu id (${user}) a los miembros de este chat (${chat}).`, en: `This chat only admits the people on its list. To use me here, add your id (${user}) to this chat's members (${chat}).` },
|
|
38
45
|
};
|
|
46
|
+
/** The same commands on every platform; these are other names for them. */
|
|
47
|
+
const COMMAND_ALIASES = { clear: 'new', reset: 'new' };
|
|
39
48
|
const say = (c, m) => (c.config.lang === 'en' ? m.en : m.es);
|
|
40
49
|
const hits = new Map();
|
|
41
50
|
function tooFast(connId, user, limit) {
|
|
@@ -45,8 +54,20 @@ function tooFast(connId, user, limit) {
|
|
|
45
54
|
hits.set(key, arr);
|
|
46
55
|
return arr.length > limit;
|
|
47
56
|
}
|
|
48
|
-
const told = new Set(); // users already told how to get access, so strangers can't make us spam them
|
|
49
57
|
const chatsOf = (c) => (Array.isArray(c.config.chats) ? c.config.chats : []);
|
|
58
|
+
/** Who may talk: in a DM and in a group with no entry of its own, the connection's people list (with their admin flag); in a listed group, that group's own rule: everyone in it, or only its members (the people list does not open it, and an empty list admits nobody). A member keeps admin only if the people list gives it. The chat is `target.chat`, so a rule covers every topic and thread in it. */
|
|
59
|
+
export function senderOf(c, m) {
|
|
60
|
+
const listed = c.allowed.find((u) => u.id === m.userId);
|
|
61
|
+
if (!m.group)
|
|
62
|
+
return listed;
|
|
63
|
+
const chat = chatsOf(c).find((x) => x.id === m.target.chat);
|
|
64
|
+
if (!chat)
|
|
65
|
+
return listed;
|
|
66
|
+
if (chat.mode !== 'members')
|
|
67
|
+
return listed ?? { id: m.userId, name: m.userName };
|
|
68
|
+
const member = (Array.isArray(chat.members) ? chat.members : []).find((x) => x.id === m.userId);
|
|
69
|
+
return member ? { id: m.userId, name: member.name ?? m.userName, ...(listed?.admin ? { admin: true } : {}) } : undefined;
|
|
70
|
+
}
|
|
50
71
|
const aliasesOf = (c) => (Array.isArray(c.config.aliases) ? c.config.aliases : []).map((a) => String(a).trim()).filter(Boolean);
|
|
51
72
|
/** Group chatter that speaks TO the agent by one of its aliases counts as aimed at it, even without @mention. */
|
|
52
73
|
const selfNames = (c) => { const n = (c.agent_id ? agents.get(c.agent_id)?.name : '') ?? ''; return n ? [n, n.split(/\s+/)[0]] : []; };
|
|
@@ -72,16 +93,16 @@ export async function handleInbound(connectionId, adapter, m) {
|
|
|
72
93
|
// Group chatter is only heard when the connection is open to it.
|
|
73
94
|
if (group && !addressed && conn.config.group_mode !== 'open')
|
|
74
95
|
return;
|
|
75
|
-
const user = conn
|
|
76
|
-
?? (group && chatsOf(conn).some((c) => c.id === m.target.chat) ? { id: m.userId, name: m.userName } : undefined);
|
|
96
|
+
const user = senderOf(conn, m);
|
|
77
97
|
if (!user) {
|
|
78
98
|
if (!addressed)
|
|
79
99
|
return; // never answer strangers' chatter
|
|
80
|
-
|
|
81
|
-
if (!
|
|
82
|
-
|
|
83
|
-
await reply(say(conn, group ? T.notAllowedChat(m.userId, m.target.chat) : T.notAllowed(m.userId)));
|
|
100
|
+
// Told at every message, so the bot never looks dead; capped per person and per chat (topics and threads share it) so nobody can make it flood a chat.
|
|
101
|
+
if (!allow(`denied:${connectionId}:${m.userId}:${m.target.chat}`, DENIED_PER_MINUTE) || !allow(`denied-chat:${connectionId}:${m.target.chat}`, DENIED_CHAT_PER_MINUTE)) {
|
|
102
|
+
return;
|
|
84
103
|
}
|
|
104
|
+
const restricted = group && chatsOf(conn).some((c) => c.id === m.target.chat && c.mode === 'members');
|
|
105
|
+
await reply(say(conn, restricted ? T.notAllowedMember(m.userId, m.target.chat, conn.kind === 'slack') : group ? T.notAllowedChat(m.userId, m.target.chat) : T.notAllowed(m.userId)));
|
|
85
106
|
return;
|
|
86
107
|
}
|
|
87
108
|
const thread = threads.touch(connectionId, m.externalKey, { title: m.place, target: m.target, user: m.userName });
|
|
@@ -117,7 +138,16 @@ export async function handleInbound(connectionId, adapter, m) {
|
|
|
117
138
|
} // only files, and none could be saved: nothing to hand over
|
|
118
139
|
}
|
|
119
140
|
if (m.command) {
|
|
120
|
-
const {
|
|
141
|
+
const { args } = m.command;
|
|
142
|
+
const name = COMMAND_ALIASES[m.command.name] ?? m.command.name;
|
|
143
|
+
if (name === 'help') {
|
|
144
|
+
await reply(say(conn, T.help));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (name === 'id') {
|
|
148
|
+
await reply(say(conn, T.ids(m.userId, m.target.chat)));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
121
151
|
if (name === 'stop') {
|
|
122
152
|
await reply(say(conn, stopAgent(agent.id) ? T.stopped : T.nothingToStop));
|
|
123
153
|
return;
|
|
@@ -190,7 +220,7 @@ async function deliver(conn, adapter, thread, m, msgId, addressed, scheduled = f
|
|
|
190
220
|
return;
|
|
191
221
|
const reply = (text) => adapter.send(m.target, text).catch(() => undefined);
|
|
192
222
|
const fresh = threads.get(thread.id) ?? thread;
|
|
193
|
-
if (m.text.length > MAX_INPUT) {
|
|
223
|
+
if (!scheduled && m.text.length > MAX_INPUT) {
|
|
194
224
|
if (addressed)
|
|
195
225
|
await reply(say(conn, T.tooLong));
|
|
196
226
|
return;
|
|
@@ -205,7 +235,7 @@ async function deliver(conn, adapter, thread, m, msgId, addressed, scheduled = f
|
|
|
205
235
|
await reply(say(conn, T.queued(ahead)));
|
|
206
236
|
const unseen = threadMessages.unseen(thread.id, fresh.seen_id, msgId).map((r) => ({ name: r.user_name ?? r.user_id ?? '?', text: r.text, files: r.files ? parseJson(r.files, undefined, `thread message ${r.id} files`) : undefined }));
|
|
207
237
|
threads.markSeen(thread.id, msgId);
|
|
208
|
-
const origin = { connectionId: conn.id, threadId: thread.id, externalKey: m.externalKey, platform: conn.kind, place: m.place, userName: m.userName, userId: m.userId, group: !!m.group, mention: m.mention, addressed, replied: false, messageId: m.externalId, effort:
|
|
238
|
+
const origin = { connectionId: conn.id, threadId: thread.id, externalKey: m.externalKey, platform: conn.kind, place: m.place, userName: m.userName, userId: m.userId, group: !!m.group, mention: m.mention, addressed, replied: false, messageId: m.externalId, target: m.target, effort: validEffort(conn.config.effort) ? conn.config.effort : undefined };
|
|
209
239
|
if (addressed)
|
|
210
240
|
await adapter.busy(m.target, 'working', m.externalId).catch(() => undefined);
|
|
211
241
|
const startedAt = Date.now();
|
|
@@ -248,10 +278,10 @@ async function deliver(conn, adapter, thread, m, msgId, addressed, scheduled = f
|
|
|
248
278
|
await adapter.busy(m.target, 'done', m.externalId).catch(() => undefined);
|
|
249
279
|
}
|
|
250
280
|
}
|
|
251
|
-
/** A message the agent asked to receive later (a wake-up, a schedule): it reaches the agent like a message of that thread, so it can answer there. */
|
|
281
|
+
/** A message the agent asked to receive later (a wake-up, a schedule): it reaches the agent like a message of that thread, so it can answer there (on Slack, in the channel: see `ChannelAdapter.proactive`). */
|
|
252
282
|
export async function deliverScheduled(conn, adapter, thread, text, mention) {
|
|
253
283
|
const m = {
|
|
254
|
-
externalId: `sched-${Date.now()}`, externalKey: thread.external_key, userId: '', userName: 'hive-am (scheduled)', place: thread.title || 'chat', target: thread.target, addressed: true, text, mention,
|
|
284
|
+
externalId: `sched-${Date.now()}`, externalKey: thread.external_key, userId: '', userName: 'hive-am (scheduled)', place: thread.title || 'chat', target: adapter.proactive?.(thread.target) ?? thread.target, addressed: true, text, mention,
|
|
255
285
|
};
|
|
256
286
|
await deliver(conn, adapter, thread, m, threadMessages.lastId(thread.id), true, true);
|
|
257
287
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { connections } from './store.js';
|
|
2
|
+
import { objectsStore } from '../objects/store.js';
|
|
2
3
|
/**
|
|
3
4
|
* Last line of defence for text an agent sends to a chat. The instructions tell the agent never to share secrets, but a model can be
|
|
4
5
|
* talked into it (by anyone in a group, or by an admin who does not realise what the output holds), so the text is checked here
|
|
@@ -10,7 +11,7 @@ const SHAPES = [
|
|
|
10
11
|
[/\b\d{8,10}:[A-Za-z0-9_-]{30,}\b/, 'a Telegram bot token'],
|
|
11
12
|
[/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/, 'an AWS access key'],
|
|
12
13
|
[/\bgh[pousr]_[A-Za-z0-9]{30,}\b|\bgithub_pat_[A-Za-z0-9_]{30,}\b/, 'a GitHub token'],
|
|
13
|
-
[/\bxox[abprs]-[A-Za-z0-9-]{10,}\b/, 'a Slack token'],
|
|
14
|
+
[/\bxox[abprs]-[A-Za-z0-9-]{10,}\b|\bxapp-\d+-[A-Za-z0-9-]{10,}\b/, 'a Slack token'],
|
|
14
15
|
[/\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b/, 'a Stripe key'],
|
|
15
16
|
[/\bsk-[A-Za-z0-9_-]{32,}\b/, 'an API key'],
|
|
16
17
|
[/\bAIza[0-9A-Za-z_-]{35}\b/, 'a Google API key'],
|
|
@@ -31,6 +32,13 @@ function knownSecrets() {
|
|
|
31
32
|
if (typeof v === 'string' && v.length >= 8)
|
|
32
33
|
out.add(v);
|
|
33
34
|
}
|
|
35
|
+
// The password of a database resource: an agent can use it to connect, but it must never be repeated in a chat.
|
|
36
|
+
for (const o of objectsStore.list())
|
|
37
|
+
if (o.kind === 'database') {
|
|
38
|
+
const pw = o.config.password;
|
|
39
|
+
if (typeof pw === 'string' && pw.length >= 6)
|
|
40
|
+
out.add(pw);
|
|
41
|
+
}
|
|
34
42
|
for (const [k, v] of Object.entries(process.env))
|
|
35
43
|
if (v && v.length >= 12 && SECRET_ENV.test(k))
|
|
36
44
|
out.add(v);
|