@hailer/mcp 1.1.11 → 1.1.12
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/.mcp.json +2 -2
- package/.opencode/agent/agent-helga-workflow-config.md +1 -2
- package/.opencode/opencode.json +7 -0
- package/CHANGELOG.md +7 -0
- package/SESSION-HANDOFF.md +68 -0
- package/dist/bot/bot-config.d.ts +2 -0
- package/dist/bot/bot-config.js +34 -11
- package/dist/bot/bot-manager.d.ts +11 -3
- package/dist/bot/bot-manager.js +73 -25
- package/dist/bot/bot.d.ts +13 -0
- package/dist/bot/bot.js +100 -25
- package/dist/cli.js +0 -0
- package/dist/mcp/hailer-clients.js +4 -1
- package/dist/mcp/signal-handler.js +10 -12
- package/dist/mcp/webhook-handler.d.ts +2 -0
- package/dist/mcp/webhook-handler.js +3 -0
- package/dist/mcp-server.js +16 -0
- package/inbox/2026-03-04-bot-config-patterns.md +24 -0
- package/package.json +1 -1
- package/.claude/.context-watchdog.json +0 -1
- package/.claude/.session-checked +0 -1
- package/.hailer-mcp-port +0 -1
- package/dist/CLAUDE.md +0 -370
- package/dist/lib/discussion-lock.d.ts +0 -42
- package/dist/lib/discussion-lock.js +0 -110
- package/dist/mcp/tools/bot-config/constants.d.ts +0 -23
- package/dist/mcp/tools/bot-config/constants.js +0 -94
- package/dist/mcp/tools/bot-config/core.d.ts +0 -253
- package/dist/mcp/tools/bot-config/core.js +0 -2456
- package/dist/mcp/tools/bot-config/index.d.ts +0 -10
- package/dist/mcp/tools/bot-config/index.js +0 -59
- package/dist/mcp/tools/bot-config/tools.d.ts +0 -7
- package/dist/mcp/tools/bot-config/tools.js +0 -15
- package/dist/mcp/tools/bot-config/types.d.ts +0 -50
- package/dist/mcp/tools/bot-config/types.js +0 -6
- package/dist/mcp/tools/bug-fixer-tools.d.ts +0 -45
- package/dist/mcp/tools/bug-fixer-tools.js +0 -1096
- package/dist/mcp/tools/document.d.ts +0 -11
- package/dist/mcp/tools/document.js +0 -741
- package/dist/mcp/tools/investigate.d.ts +0 -9
- package/dist/mcp/tools/investigate.js +0 -254
- package/dist/stdio-server.d.ts +0 -14
- package/dist/stdio-server.js +0 -101
- package/inbox/failures.log +0 -1
- package/inbox/usage.jsonl +0 -4
package/.mcp.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-helga-workflow-config
|
|
3
3
|
description: Manages Hailer workspace configuration as infrastructure-as-code using SDK v0.8.4.
|
|
4
|
-
model: sonnet
|
|
4
|
+
model: anthropic/claude-sonnet-4-5
|
|
5
5
|
tools:
|
|
6
6
|
bash: true
|
|
7
7
|
read: true
|
|
@@ -53,7 +53,6 @@ For on-demand skills, the orchestrator will say "Load skill X" — use the Skill
|
|
|
53
53
|
11. **ACTIVITYLINK FIELDS** - data must be plain string array: `["workflowId"]` NOT `[{workflowId: "..."}]`.
|
|
54
54
|
12. **NUMBER FIELDS** - Type: `numeric` (not number).
|
|
55
55
|
13. **FIELD TYPES ARE IMMUTABLE** - Cannot change field type via API. To change text→number: create new field, migrate data, delete old field. Or change in Hailer UI manually.
|
|
56
|
-
14. **CANNOT RENAME WORKFLOWS** - Workflow names cannot be changed via SDK. To "rename": create new workflow with desired name, migrate data, delete old workflow. Or rename in Hailer UI manually.
|
|
57
56
|
|
|
58
57
|
**Delegation to specialists:**
|
|
59
58
|
- For insight SQL query design → delegate to Viktor (after creating insight entry in insights.ts)
|
package/.opencode/opencode.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.1.10] - 25-02-2026
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **OpenCode MCP support:** Added `.opencode/opencode.json` with Hailer MCP server configuration
|
|
12
|
+
- **Dynamic port patching for OpenCode:** On server startup, `.opencode/opencode.json` is now auto-updated with the actual port (alongside the existing `.mcp.json` patching), so both Claude Code and OpenCode always connect to the right server
|
|
13
|
+
|
|
7
14
|
## [1.1.7] - 18-02-2026
|
|
8
15
|
|
|
9
16
|
### Fix
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Session Handoff
|
|
2
|
+
|
|
3
|
+
**Last Updated:** 2026-03-04
|
|
4
|
+
|
|
5
|
+
## What Was Done
|
|
6
|
+
|
|
7
|
+
### System Prompt — Full End-to-End Wiring
|
|
8
|
+
Wired `systemPrompt` field from Agent Directory through the entire bot pipeline:
|
|
9
|
+
|
|
10
|
+
- **`src/bot-config/constants.ts`** — added `FIELD_KEY_SYSTEM_PROMPT`
|
|
11
|
+
- **`src/bot-config/context.ts`** — `BotSchema.fields.systemPrompt`, `BotCredentials.systemPrompt`, schema discovery, `extractCredentials()`
|
|
12
|
+
- **`src/bot-config/webhooks.ts`** — extract systemPrompt in `extractCredentialsFromActivity()`
|
|
13
|
+
- **`src/bot-config/loader.ts`** — `BotConfigFile` types + `saveBotConfig()` includes systemPrompt
|
|
14
|
+
- **`src/bot/bot-config.ts`** — `BotConfig.systemPrompt`, reads from `orchestrator.systemPrompt`
|
|
15
|
+
- **`src/bot/bot-manager.ts`** — `BotUpdateEntry.systemPrompt`, passes to Bot; smart restart logic: if only systemPrompt changed (credentials same) → `bot.updateSystemPrompt()` without restart
|
|
16
|
+
- **`src/bot/bot.ts`** — `_systemPrompt` instance field (hot-swappable), `updateSystemPrompt()` public method, `buildSystemPrompt()` uses custom prompt + always appends `<bot-identity>` + `<workspace-context>`
|
|
17
|
+
- **`src/commands/seed-config.ts`** — `extractSystemPromptFromActivity()`, wired into orchestrator + specialist output
|
|
18
|
+
- **`src/mcp/webhook-handler.ts`** — `BotEntry.systemPrompt`, `WorkspaceConfig.orchestrator.systemPrompt`, extracts from webhook payload
|
|
19
|
+
|
|
20
|
+
### System Prompt Design
|
|
21
|
+
- Custom prompt from AI Hub used as full body; `{wsName}`, `{userId}`, `{botName}` substituted at runtime
|
|
22
|
+
- `<bot-identity>` and `<workspace-context>` always appended by bot — never stored
|
|
23
|
+
- Workspace-isolated: each `Bot` instance has own `_systemPrompt`, `BotManager.bots` Map keyed by workspaceId
|
|
24
|
+
- Phase change → full restart picks up new prompt from config file
|
|
25
|
+
- Field edit → webhook fires (with delay) → `handleBotConfigWebhook()` → `updateSystemPrompt()` (no restart)
|
|
26
|
+
|
|
27
|
+
## Current Work
|
|
28
|
+
|
|
29
|
+
### Uncommitted — All System Prompt Changes
|
|
30
|
+
10 files modified, compiles clean (`tsc --noEmit` passes). Not yet committed or pushed.
|
|
31
|
+
|
|
32
|
+
## Next Steps
|
|
33
|
+
|
|
34
|
+
1. **Commit the system prompt wiring** — all 10 files are ready
|
|
35
|
+
2. **Test end-to-end**: edit prompt in AI Hub → wait for webhook → verify bot uses new prompt
|
|
36
|
+
3. **Cloudflare** — user mentioned needing this (for remote MCP / public webhook URL). See `docs/prd-remote-mcp-server.md` and `docs/prd-remote-mcp-connector.md`
|
|
37
|
+
4. **`activityId` passthrough** (optional improvement) — pass orchestrator's `activityId` into Bot so it can subscribe to `activities.updated` socket signal for instant prompt updates (vs waiting for webhook)
|
|
38
|
+
|
|
39
|
+
## Key Decisions
|
|
40
|
+
|
|
41
|
+
- **Hot-swap without restart**: Only credentials change triggers bot restart; systemPrompt-only changes apply live via `updateSystemPrompt()`
|
|
42
|
+
- **Workspace isolation**: Guaranteed by `Map<workspaceId, Bot>` — webhook `cid` field routes to correct bot instance
|
|
43
|
+
- **Webhook-driven updates**: Phase change → webhook (requires public URL). Field-only changes → webhook fires with delay. Socket `activities.updated` would be instant but requires `activityId` passthrough (not yet done)
|
|
44
|
+
- **Prompt structure**: Custom prompt replaces the rules section; `<bot-identity>` + `<workspace-context>` always runtime-injected
|
|
45
|
+
|
|
46
|
+
## Files Modified (uncommitted)
|
|
47
|
+
|
|
48
|
+
- `src/bot-config/constants.ts` — FIELD_KEY_SYSTEM_PROMPT
|
|
49
|
+
- `src/bot-config/context.ts` — BotSchema, BotCredentials, discovery, extractCredentials
|
|
50
|
+
- `src/bot-config/loader.ts` — BotConfigFile types + saveBotConfig
|
|
51
|
+
- `src/bot-config/webhooks.ts` — extractCredentialsFromActivity
|
|
52
|
+
- `src/bot/bot-config.ts` — BotConfig + loadBotConfigs
|
|
53
|
+
- `src/bot/bot-manager.ts` — BotUpdateEntry, smart restart, updateSystemPrompt call
|
|
54
|
+
- `src/bot/bot.ts` — _systemPrompt field, updateSystemPrompt(), buildSystemPrompt(), email/password getters
|
|
55
|
+
- `src/commands/seed-config.ts` — extractSystemPromptFromActivity + wired into output
|
|
56
|
+
- `src/mcp/webhook-handler.ts` — BotEntry, WorkspaceConfig types + extraction
|
|
57
|
+
|
|
58
|
+
## Context to Preserve
|
|
59
|
+
|
|
60
|
+
- Agent Directory workflow ID: `69885261c432a499a35313b6`
|
|
61
|
+
- Deployed phase: `8fa543c69749ab4e8f9e3967`, Retired phase: `df66a80fe7cfde1c3b928038`
|
|
62
|
+
- AI Hub app: `695e3665701ef8e3824beebe`, marketplace targetId: `698351e8dda756e689368b60`
|
|
63
|
+
- `systemPrompt` field key must be exactly `systemPrompt` (camelCase) — field type: textarea
|
|
64
|
+
- **seed-config.ts is an independent copy** of field extraction — NOT shared with context.ts. Always update both when adding new Agent Directory fields
|
|
65
|
+
- **Two bot-config readers**: `src/bot-config/loader.ts` (BotContext/webhook path) AND `src/bot/bot-config.ts` (BotManager startup path) — both need updating
|
|
66
|
+
- **webhook-handler.ts has its own local types** (BotEntry, WorkspaceConfig) — update independently
|
|
67
|
+
- Webhook fires on phase changes; field edits may have delay before webhook triggers
|
|
68
|
+
- `app-edit-guard --agent-on` does NOT propagate to subagents — do ai-hub edits directly
|
package/dist/bot/bot-config.d.ts
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
* Extracts orchestrator credentials into a flat BotConfig interface.
|
|
6
6
|
*/
|
|
7
7
|
export interface BotConfig {
|
|
8
|
+
activityId: string;
|
|
8
9
|
workspaceId: string;
|
|
9
10
|
workspaceName: string;
|
|
10
11
|
email: string;
|
|
11
12
|
password: string;
|
|
12
13
|
displayName?: string;
|
|
14
|
+
systemPrompt?: string;
|
|
13
15
|
enabled: boolean;
|
|
14
16
|
apiHost: string;
|
|
15
17
|
}
|
package/dist/bot/bot-config.js
CHANGED
|
@@ -68,21 +68,43 @@ function loadBotConfigs() {
|
|
|
68
68
|
try {
|
|
69
69
|
const filePath = path.join(configDir, file);
|
|
70
70
|
const raw = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
71
|
+
// Load orchestrator
|
|
71
72
|
const orch = raw.orchestrator;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
73
|
+
if (orch?.email && orch?.password) {
|
|
74
|
+
configs.push({
|
|
75
|
+
activityId: orch.activityId || '',
|
|
76
|
+
workspaceId: raw.workspaceId,
|
|
77
|
+
workspaceName: raw.workspaceName,
|
|
78
|
+
email: orch.email,
|
|
79
|
+
password: orch.password,
|
|
80
|
+
displayName: orch.displayName,
|
|
81
|
+
systemPrompt: orch.systemPrompt,
|
|
82
|
+
enabled: true,
|
|
83
|
+
apiHost: getApiHost(),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// Load specialists
|
|
87
|
+
if (Array.isArray(raw.specialists)) {
|
|
88
|
+
for (const spec of raw.specialists) {
|
|
89
|
+
if (spec?.email && spec?.password && spec?.enabled !== false) {
|
|
90
|
+
configs.push({
|
|
91
|
+
activityId: spec.activityId || '',
|
|
92
|
+
workspaceId: raw.workspaceId,
|
|
93
|
+
workspaceName: raw.workspaceName,
|
|
94
|
+
email: spec.email,
|
|
95
|
+
password: spec.password,
|
|
96
|
+
displayName: spec.displayName,
|
|
97
|
+
systemPrompt: spec.systemPrompt,
|
|
98
|
+
enabled: true,
|
|
99
|
+
apiHost: getApiHost(),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
82
104
|
logger.debug('Loaded bot config', {
|
|
83
105
|
workspaceId: raw.workspaceId,
|
|
84
106
|
workspaceName: raw.workspaceName,
|
|
85
|
-
|
|
107
|
+
botCount: configs.filter(c => c.workspaceId === raw.workspaceId).length,
|
|
86
108
|
});
|
|
87
109
|
}
|
|
88
110
|
catch (error) {
|
|
@@ -124,6 +146,7 @@ function saveBotConfig(config) {
|
|
|
124
146
|
email: config.email,
|
|
125
147
|
password: config.password,
|
|
126
148
|
displayName: config.displayName,
|
|
149
|
+
systemPrompt: config.systemPrompt,
|
|
127
150
|
} : null,
|
|
128
151
|
specialists: existing.specialists ?? [],
|
|
129
152
|
lastSynced: new Date().toISOString(),
|
|
@@ -7,23 +7,31 @@
|
|
|
7
7
|
import { ToolRegistry } from '../mcp/tool-registry';
|
|
8
8
|
import { BotConfig } from './bot-config';
|
|
9
9
|
interface BotUpdateEntry {
|
|
10
|
+
activityId: string;
|
|
10
11
|
email: string;
|
|
11
12
|
password: string;
|
|
12
13
|
botType: string;
|
|
13
14
|
enabled: boolean;
|
|
14
15
|
displayName?: string;
|
|
16
|
+
systemPrompt?: string;
|
|
15
17
|
}
|
|
16
18
|
export declare class BotManager {
|
|
17
19
|
private toolRegistry;
|
|
18
20
|
private anthropicApiKey;
|
|
21
|
+
/** Key: activityId → Bot instance */
|
|
19
22
|
private bots;
|
|
23
|
+
/** Key: workspaceId → Set of activityIds (for cross-bot awareness) */
|
|
24
|
+
private workspaceBots;
|
|
20
25
|
private apiHost;
|
|
21
26
|
constructor(toolRegistry: ToolRegistry, anthropicApiKey: string);
|
|
27
|
+
/** Get all bot userIds in a workspace (for self-message guard) */
|
|
28
|
+
getBotUserIdsForWorkspace(workspaceId: string): Set<string>;
|
|
22
29
|
startAll(): Promise<void>;
|
|
23
30
|
startBot(config: BotConfig): Promise<void>;
|
|
24
|
-
stopBot(
|
|
31
|
+
stopBot(botKey: string, workspaceId?: string): Promise<void>;
|
|
25
32
|
stopAll(): Promise<void>;
|
|
26
33
|
getStatus(): Array<{
|
|
34
|
+
activityId: string;
|
|
27
35
|
workspaceId: string;
|
|
28
36
|
connected: boolean;
|
|
29
37
|
displayName: string;
|
|
@@ -31,8 +39,8 @@ export declare class BotManager {
|
|
|
31
39
|
getBotCount(): number;
|
|
32
40
|
/**
|
|
33
41
|
* Handle hot reload from webhook updates.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
42
|
+
* Supports all bot types. Routes by activityId so multiple bots
|
|
43
|
+
* can run in the same workspace independently.
|
|
36
44
|
*/
|
|
37
45
|
handleBotUpdate(workspaceId: string, bot: BotUpdateEntry, action: 'add' | 'update' | 'remove'): Promise<void>;
|
|
38
46
|
}
|
package/dist/bot/bot-manager.js
CHANGED
|
@@ -14,13 +14,29 @@ const logger = (0, logger_1.createLogger)({ component: 'bot-manager' });
|
|
|
14
14
|
class BotManager {
|
|
15
15
|
toolRegistry;
|
|
16
16
|
anthropicApiKey;
|
|
17
|
+
/** Key: activityId → Bot instance */
|
|
17
18
|
bots = new Map();
|
|
19
|
+
/** Key: workspaceId → Set of activityIds (for cross-bot awareness) */
|
|
20
|
+
workspaceBots = new Map();
|
|
18
21
|
apiHost;
|
|
19
22
|
constructor(toolRegistry, anthropicApiKey) {
|
|
20
23
|
this.toolRegistry = toolRegistry;
|
|
21
24
|
this.anthropicApiKey = anthropicApiKey;
|
|
22
25
|
this.apiHost = process.env.BOT_API_BASE_URL || 'https://api.hailer.com';
|
|
23
26
|
}
|
|
27
|
+
/** Get all bot userIds in a workspace (for self-message guard) */
|
|
28
|
+
getBotUserIdsForWorkspace(workspaceId) {
|
|
29
|
+
const ids = new Set();
|
|
30
|
+
const activityIds = this.workspaceBots.get(workspaceId);
|
|
31
|
+
if (activityIds) {
|
|
32
|
+
for (const actId of activityIds) {
|
|
33
|
+
const bot = this.bots.get(actId);
|
|
34
|
+
if (bot?.botUserId)
|
|
35
|
+
ids.add(bot.botUserId);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return ids;
|
|
39
|
+
}
|
|
24
40
|
async startAll() {
|
|
25
41
|
const configs = (0, bot_config_1.loadBotConfigs)();
|
|
26
42
|
const enabled = configs.filter(c => c.enabled);
|
|
@@ -37,11 +53,13 @@ class BotManager {
|
|
|
37
53
|
logger.debug('Bot startup complete', { succeeded, failed });
|
|
38
54
|
}
|
|
39
55
|
async startBot(config) {
|
|
40
|
-
|
|
41
|
-
|
|
56
|
+
const botKey = config.activityId || config.workspaceId; // fallback for legacy configs
|
|
57
|
+
if (this.bots.has(botKey)) {
|
|
58
|
+
logger.warn('Bot already running', { activityId: botKey, workspaceId: config.workspaceId });
|
|
42
59
|
return;
|
|
43
60
|
}
|
|
44
61
|
logger.debug('Starting bot', {
|
|
62
|
+
activityId: botKey,
|
|
45
63
|
workspaceId: config.workspaceId,
|
|
46
64
|
workspace: config.workspaceName,
|
|
47
65
|
});
|
|
@@ -52,32 +70,50 @@ class BotManager {
|
|
|
52
70
|
anthropicApiKey: this.anthropicApiKey,
|
|
53
71
|
toolRegistry: this.toolRegistry,
|
|
54
72
|
workspaceId: config.workspaceId,
|
|
73
|
+
systemPrompt: config.systemPrompt,
|
|
74
|
+
botManager: this,
|
|
55
75
|
});
|
|
56
76
|
await bot.start();
|
|
57
|
-
this.bots.set(
|
|
58
|
-
|
|
77
|
+
this.bots.set(botKey, bot);
|
|
78
|
+
// Track in workspace lookup
|
|
79
|
+
if (!this.workspaceBots.has(config.workspaceId)) {
|
|
80
|
+
this.workspaceBots.set(config.workspaceId, new Set());
|
|
81
|
+
}
|
|
82
|
+
this.workspaceBots.get(config.workspaceId).add(botKey);
|
|
83
|
+
logger.debug('Bot started', { activityId: botKey, workspaceId: config.workspaceId });
|
|
59
84
|
}
|
|
60
|
-
async stopBot(workspaceId) {
|
|
61
|
-
const bot = this.bots.get(
|
|
85
|
+
async stopBot(botKey, workspaceId) {
|
|
86
|
+
const bot = this.bots.get(botKey);
|
|
62
87
|
if (!bot)
|
|
63
88
|
return;
|
|
64
89
|
await bot.stop();
|
|
65
|
-
this.bots.delete(
|
|
66
|
-
|
|
90
|
+
this.bots.delete(botKey);
|
|
91
|
+
// Clean up workspace tracking
|
|
92
|
+
if (workspaceId) {
|
|
93
|
+
const wsSet = this.workspaceBots.get(workspaceId);
|
|
94
|
+
if (wsSet) {
|
|
95
|
+
wsSet.delete(botKey);
|
|
96
|
+
if (wsSet.size === 0)
|
|
97
|
+
this.workspaceBots.delete(workspaceId);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
logger.debug('Bot stopped', { activityId: botKey, workspaceId });
|
|
67
101
|
}
|
|
68
102
|
async stopAll() {
|
|
69
103
|
logger.debug('Stopping all bots', { count: this.bots.size });
|
|
70
|
-
await Promise.allSettled(Array.from(this.bots.entries()).map(async ([
|
|
104
|
+
await Promise.allSettled(Array.from(this.bots.entries()).map(async ([botKey, bot]) => {
|
|
71
105
|
await bot.stop();
|
|
72
|
-
logger.debug('Bot stopped', {
|
|
106
|
+
logger.debug('Bot stopped', { activityId: botKey });
|
|
73
107
|
}));
|
|
74
108
|
this.bots.clear();
|
|
109
|
+
this.workspaceBots.clear();
|
|
75
110
|
}
|
|
76
111
|
getStatus() {
|
|
77
|
-
return Array.from(this.bots.entries()).map(([
|
|
78
|
-
|
|
112
|
+
return Array.from(this.bots.entries()).map(([botKey, bot]) => ({
|
|
113
|
+
activityId: botKey,
|
|
114
|
+
workspaceId: bot.workspaceId || botKey,
|
|
79
115
|
connected: bot.connected,
|
|
80
|
-
displayName:
|
|
116
|
+
displayName: bot.workspaceId || botKey,
|
|
81
117
|
}));
|
|
82
118
|
}
|
|
83
119
|
getBotCount() {
|
|
@@ -85,48 +121,60 @@ class BotManager {
|
|
|
85
121
|
}
|
|
86
122
|
/**
|
|
87
123
|
* Handle hot reload from webhook updates.
|
|
88
|
-
*
|
|
89
|
-
*
|
|
124
|
+
* Supports all bot types. Routes by activityId so multiple bots
|
|
125
|
+
* can run in the same workspace independently.
|
|
90
126
|
*/
|
|
91
127
|
async handleBotUpdate(workspaceId, bot, action) {
|
|
92
|
-
|
|
93
|
-
|
|
128
|
+
const botKey = bot.activityId;
|
|
129
|
+
if (!botKey) {
|
|
130
|
+
logger.warn('Bot update missing activityId, skipping', { workspaceId });
|
|
94
131
|
return;
|
|
95
132
|
}
|
|
96
|
-
const running = this.bots.has(
|
|
133
|
+
const running = this.bots.has(botKey);
|
|
97
134
|
if (action === 'remove' || !bot.enabled) {
|
|
98
135
|
if (running) {
|
|
99
|
-
logger.info('Stopping bot (disabled via webhook)', { workspaceId });
|
|
100
|
-
await this.stopBot(workspaceId);
|
|
136
|
+
logger.info('Stopping bot (disabled via webhook)', { activityId: botKey, workspaceId });
|
|
137
|
+
await this.stopBot(botKey, workspaceId);
|
|
101
138
|
}
|
|
102
139
|
return;
|
|
103
140
|
}
|
|
104
141
|
// Validate credentials before proceeding
|
|
105
142
|
if (!/.+@.+\..+/.test(bot.email) || !bot.password || bot.password.length < 6) {
|
|
106
|
-
logger.warn('Invalid bot credentials, skipping', { workspaceId });
|
|
143
|
+
logger.warn('Invalid bot credentials, skipping', { activityId: botKey, workspaceId });
|
|
107
144
|
return;
|
|
108
145
|
}
|
|
109
146
|
// action is 'add' or 'update' with enabled=true
|
|
110
147
|
if (running) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
148
|
+
const runningBot = this.bots.get(botKey);
|
|
149
|
+
// If only the system prompt changed, update it live — no restart needed
|
|
150
|
+
const credentialsChanged = runningBot.email !== bot.email || runningBot.password !== bot.password;
|
|
151
|
+
if (!credentialsChanged) {
|
|
152
|
+
logger.info('Updating system prompt live (no restart needed)', { activityId: botKey, workspaceId });
|
|
153
|
+
runningBot.updateSystemPrompt(bot.systemPrompt);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// Credentials changed — full restart required
|
|
157
|
+
logger.info('Restarting bot (credentials updated via webhook)', { activityId: botKey, workspaceId });
|
|
158
|
+
await this.stopBot(botKey, workspaceId);
|
|
114
159
|
}
|
|
115
160
|
const config = {
|
|
161
|
+
activityId: botKey,
|
|
116
162
|
workspaceId,
|
|
117
163
|
workspaceName: workspaceId,
|
|
118
164
|
email: bot.email,
|
|
119
165
|
password: bot.password,
|
|
120
166
|
displayName: bot.displayName,
|
|
167
|
+
systemPrompt: bot.systemPrompt,
|
|
121
168
|
enabled: true,
|
|
122
169
|
apiHost: this.apiHost,
|
|
123
170
|
};
|
|
124
171
|
try {
|
|
125
172
|
await this.startBot(config);
|
|
126
|
-
logger.info('Bot started via webhook', { workspaceId, displayName: bot.displayName });
|
|
173
|
+
logger.info('Bot started via webhook', { activityId: botKey, workspaceId, displayName: bot.displayName });
|
|
127
174
|
}
|
|
128
175
|
catch (error) {
|
|
129
176
|
logger.error('Failed to start bot via webhook', {
|
|
177
|
+
activityId: botKey,
|
|
130
178
|
workspaceId,
|
|
131
179
|
error: error instanceof Error ? error.message : String(error),
|
|
132
180
|
});
|
package/dist/bot/bot.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export declare class Bot {
|
|
|
18
18
|
private workspaceOverview;
|
|
19
19
|
private userId;
|
|
20
20
|
private _workspaceId;
|
|
21
|
+
private _systemPrompt;
|
|
21
22
|
private conversationManager;
|
|
22
23
|
private messageClassifier;
|
|
23
24
|
private messageFormatter;
|
|
@@ -45,6 +46,7 @@ export declare class Bot {
|
|
|
45
46
|
private adminUserIds;
|
|
46
47
|
private ownerUserIds;
|
|
47
48
|
private config;
|
|
49
|
+
private botManager;
|
|
48
50
|
constructor(config: {
|
|
49
51
|
email: string;
|
|
50
52
|
password: string;
|
|
@@ -53,9 +55,20 @@ export declare class Bot {
|
|
|
53
55
|
model?: string;
|
|
54
56
|
toolRegistry: ToolRegistry;
|
|
55
57
|
workspaceId?: string;
|
|
58
|
+
systemPrompt?: string;
|
|
59
|
+
botManager?: any;
|
|
56
60
|
});
|
|
61
|
+
get email(): string;
|
|
62
|
+
get password(): string;
|
|
63
|
+
/**
|
|
64
|
+
* Hot-update the system prompt without restarting the bot.
|
|
65
|
+
* Takes effect on the next message processed.
|
|
66
|
+
*/
|
|
67
|
+
updateSystemPrompt(prompt: string | undefined): void;
|
|
57
68
|
get connected(): boolean;
|
|
58
69
|
get workspaceId(): string | undefined;
|
|
70
|
+
/** Exposed for BotManager cross-bot awareness */
|
|
71
|
+
get botUserId(): string;
|
|
59
72
|
start(): Promise<void>;
|
|
60
73
|
stop(): Promise<void>;
|
|
61
74
|
private handleSignal;
|