@harbinger-ai/harbinger 0.1.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 +406 -0
- package/agents/README.md +76 -0
- package/agents/_template/CONFIG.yaml +7 -0
- package/agents/_template/HEARTBEAT.md +59 -0
- package/agents/_template/IDENTITY.md +4 -0
- package/agents/_template/SKILLS.md +1 -0
- package/agents/_template/SOUL.md +25 -0
- package/agents/_template/TOOLS.md +3 -0
- package/agents/binary-reverser/CONFIG.yaml +21 -0
- package/agents/binary-reverser/HEARTBEAT.md +65 -0
- package/agents/binary-reverser/IDENTITY.md +1 -0
- package/agents/binary-reverser/SKILLS.md +1 -0
- package/agents/binary-reverser/SOUL.md +23 -0
- package/agents/binary-reverser/TOOLS.md +99 -0
- package/agents/browser-agent/CONFIG.yaml +20 -0
- package/agents/browser-agent/HEARTBEAT.md +79 -0
- package/agents/browser-agent/IDENTITY.md +5 -0
- package/agents/browser-agent/SKILLS.md +86 -0
- package/agents/browser-agent/SOUL.md +23 -0
- package/agents/browser-agent/TOOLS.md +186 -0
- package/agents/cloud-infiltrator/CONFIG.yaml +22 -0
- package/agents/cloud-infiltrator/HEARTBEAT.md +78 -0
- package/agents/cloud-infiltrator/IDENTITY.md +1 -0
- package/agents/cloud-infiltrator/SKILLS.md +1 -0
- package/agents/cloud-infiltrator/SOUL.md +23 -0
- package/agents/cloud-infiltrator/TOOLS.md +68 -0
- package/agents/coding-assistant/CONFIG.yaml +22 -0
- package/agents/coding-assistant/HEARTBEAT.md +57 -0
- package/agents/coding-assistant/IDENTITY.md +5 -0
- package/agents/coding-assistant/SKILLS.md +69 -0
- package/agents/coding-assistant/SOUL.md +60 -0
- package/agents/coding-assistant/TOOLS.md +168 -0
- package/agents/learning-agent/CONFIG.yaml +21 -0
- package/agents/learning-agent/HEARTBEAT.md +63 -0
- package/agents/learning-agent/IDENTITY.md +5 -0
- package/agents/learning-agent/SKILLS.md +86 -0
- package/agents/learning-agent/SOUL.md +77 -0
- package/agents/learning-agent/TOOLS.md +145 -0
- package/agents/maintainer/CONFIG.yaml +31 -0
- package/agents/maintainer/HEARTBEAT.md +28 -0
- package/agents/maintainer/IDENTITY.md +33 -0
- package/agents/maintainer/SKILLS.md +24 -0
- package/agents/maintainer/SOUL.md +61 -0
- package/agents/maintainer/TOOLS.md +29 -0
- package/agents/maintainer/lib/engine.js +279 -0
- package/agents/maintainer/lib/safe-fixer.js +183 -0
- package/agents/morning-brief/CONFIG.yaml +22 -0
- package/agents/morning-brief/HEARTBEAT.md +60 -0
- package/agents/morning-brief/IDENTITY.md +5 -0
- package/agents/morning-brief/SKILLS.md +56 -0
- package/agents/morning-brief/SOUL.md +64 -0
- package/agents/morning-brief/TOOLS.md +112 -0
- package/agents/osint-detective/CONFIG.yaml +24 -0
- package/agents/osint-detective/HEARTBEAT.md +66 -0
- package/agents/osint-detective/IDENTITY.md +1 -0
- package/agents/osint-detective/SKILLS.md +1 -0
- package/agents/osint-detective/SOUL.md +23 -0
- package/agents/osint-detective/TOOLS.md +81 -0
- package/agents/recon-scout/CONFIG.yaml +22 -0
- package/agents/recon-scout/HEARTBEAT.md +79 -0
- package/agents/recon-scout/IDENTITY.md +1 -0
- package/agents/recon-scout/SKILLS.md +1 -0
- package/agents/recon-scout/SOUL.md +23 -0
- package/agents/recon-scout/TOOLS.md +93 -0
- package/agents/report-writer/CONFIG.yaml +21 -0
- package/agents/report-writer/HEARTBEAT.md +63 -0
- package/agents/report-writer/IDENTITY.md +1 -0
- package/agents/report-writer/SKILLS.md +1 -0
- package/agents/report-writer/SOUL.md +23 -0
- package/agents/report-writer/TOOLS.md +69 -0
- package/agents/shared/README.md +13 -0
- package/agents/web-hacker/CONFIG.yaml +24 -0
- package/agents/web-hacker/HEARTBEAT.md +78 -0
- package/agents/web-hacker/IDENTITY.md +1 -0
- package/agents/web-hacker/SKILLS.md +1 -0
- package/agents/web-hacker/SOUL.md +23 -0
- package/agents/web-hacker/TOOLS.md +86 -0
- package/api/CLAUDE.md +19 -0
- package/api/index.js +274 -0
- package/bin/cli.js +620 -0
- package/bin/local.sh +31 -0
- package/bin/postinstall.js +63 -0
- package/config/index.js +24 -0
- package/config/instrumentation.js +93 -0
- package/drizzle/0000_initial.sql +52 -0
- package/drizzle/0001_bounty_and_registry.sql +82 -0
- package/drizzle/0002_sync_columns.sql +7 -0
- package/drizzle/0003_graceful_bloodscream.sql +86 -0
- package/drizzle/meta/0000_snapshot.json +321 -0
- package/drizzle/meta/0003_snapshot.json +878 -0
- package/drizzle/meta/_journal.json +34 -0
- package/drizzle/relations.ts +3 -0
- package/drizzle/schema.ts +145 -0
- package/lib/actions.js +47 -0
- package/lib/agents.js +166 -0
- package/lib/ai/agent.js +96 -0
- package/lib/ai/autonomous-engine.js +261 -0
- package/lib/ai/index.js +359 -0
- package/lib/ai/model-router.js +254 -0
- package/lib/ai/model.js +73 -0
- package/lib/ai/tools.js +84 -0
- package/lib/auth/actions.js +28 -0
- package/lib/auth/config.js +27 -0
- package/lib/auth/edge-config.js +27 -0
- package/lib/auth/index.js +27 -0
- package/lib/auth/middleware.js +53 -0
- package/lib/bounty/actions.js +119 -0
- package/lib/bounty/findings.js +64 -0
- package/lib/bounty/programs.js +34 -0
- package/lib/bounty/sync-targets.js +267 -0
- package/lib/bounty/targets.js +33 -0
- package/lib/channels/base.js +56 -0
- package/lib/channels/index.js +15 -0
- package/lib/channels/telegram.js +148 -0
- package/lib/chat/actions.js +288 -0
- package/lib/chat/api.js +135 -0
- package/lib/chat/components/app-sidebar.js +237 -0
- package/lib/chat/components/app-sidebar.jsx +289 -0
- package/lib/chat/components/chat-header.js +27 -0
- package/lib/chat/components/chat-header.jsx +37 -0
- package/lib/chat/components/chat-input.js +230 -0
- package/lib/chat/components/chat-input.jsx +228 -0
- package/lib/chat/components/chat-nav-context.js +11 -0
- package/lib/chat/components/chat-nav-context.jsx +11 -0
- package/lib/chat/components/chat-page.js +81 -0
- package/lib/chat/components/chat-page.jsx +100 -0
- package/lib/chat/components/chat.js +150 -0
- package/lib/chat/components/chat.jsx +182 -0
- package/lib/chat/components/chats-page.js +302 -0
- package/lib/chat/components/chats-page.jsx +330 -0
- package/lib/chat/components/crons-page.js +172 -0
- package/lib/chat/components/crons-page.jsx +244 -0
- package/lib/chat/components/enhanced-tool-call.js +103 -0
- package/lib/chat/components/enhanced-tool-call.jsx +139 -0
- package/lib/chat/components/findings-page.js +175 -0
- package/lib/chat/components/findings-page.jsx +214 -0
- package/lib/chat/components/greeting.js +22 -0
- package/lib/chat/components/greeting.jsx +26 -0
- package/lib/chat/components/icons.js +777 -0
- package/lib/chat/components/icons.jsx +741 -0
- package/lib/chat/components/index.js +26 -0
- package/lib/chat/components/mcp-page.js +260 -0
- package/lib/chat/components/mcp-page.jsx +355 -0
- package/lib/chat/components/message.js +289 -0
- package/lib/chat/components/message.jsx +315 -0
- package/lib/chat/components/messages.js +66 -0
- package/lib/chat/components/messages.jsx +77 -0
- package/lib/chat/components/notifications-page.js +56 -0
- package/lib/chat/components/notifications-page.jsx +87 -0
- package/lib/chat/components/page-layout.js +21 -0
- package/lib/chat/components/page-layout.jsx +28 -0
- package/lib/chat/components/registry-page.js +222 -0
- package/lib/chat/components/registry-page.jsx +255 -0
- package/lib/chat/components/settings-layout.js +40 -0
- package/lib/chat/components/settings-layout.jsx +54 -0
- package/lib/chat/components/settings-secrets-page.js +216 -0
- package/lib/chat/components/settings-secrets-page.jsx +264 -0
- package/lib/chat/components/sidebar-history-item.js +132 -0
- package/lib/chat/components/sidebar-history-item.jsx +113 -0
- package/lib/chat/components/sidebar-history.js +115 -0
- package/lib/chat/components/sidebar-history.jsx +157 -0
- package/lib/chat/components/sidebar-user-nav.js +63 -0
- package/lib/chat/components/sidebar-user-nav.jsx +73 -0
- package/lib/chat/components/status-bar.js +39 -0
- package/lib/chat/components/status-bar.jsx +51 -0
- package/lib/chat/components/swarm-page.js +157 -0
- package/lib/chat/components/swarm-page.jsx +210 -0
- package/lib/chat/components/targets-page.js +376 -0
- package/lib/chat/components/targets-page.jsx +389 -0
- package/lib/chat/components/tool-call.js +86 -0
- package/lib/chat/components/tool-call.jsx +104 -0
- package/lib/chat/components/tool-panel.js +107 -0
- package/lib/chat/components/tool-panel.jsx +145 -0
- package/lib/chat/components/triggers-page.js +153 -0
- package/lib/chat/components/triggers-page.jsx +221 -0
- package/lib/chat/components/ui/confirm-dialog.js +53 -0
- package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
- package/lib/chat/components/ui/dropdown-menu.js +98 -0
- package/lib/chat/components/ui/dropdown-menu.jsx +116 -0
- package/lib/chat/components/ui/rename-dialog.js +74 -0
- package/lib/chat/components/ui/rename-dialog.jsx +72 -0
- package/lib/chat/components/ui/scroll-area.js +13 -0
- package/lib/chat/components/ui/scroll-area.jsx +17 -0
- package/lib/chat/components/ui/separator.js +21 -0
- package/lib/chat/components/ui/separator.jsx +18 -0
- package/lib/chat/components/ui/sheet.js +75 -0
- package/lib/chat/components/ui/sheet.jsx +95 -0
- package/lib/chat/components/ui/sidebar.js +227 -0
- package/lib/chat/components/ui/sidebar.jsx +245 -0
- package/lib/chat/components/ui/tooltip.js +56 -0
- package/lib/chat/components/ui/tooltip.jsx +66 -0
- package/lib/chat/components/upgrade-dialog.js +151 -0
- package/lib/chat/components/upgrade-dialog.jsx +170 -0
- package/lib/chat/utils.js +11 -0
- package/lib/cron.js +246 -0
- package/lib/db/api-keys.js +163 -0
- package/lib/db/chats.js +145 -0
- package/lib/db/index.js +52 -0
- package/lib/db/notifications.js +99 -0
- package/lib/db/schema.js +145 -0
- package/lib/db/update-check.js +96 -0
- package/lib/db/users.js +89 -0
- package/lib/mcp/actions.js +104 -0
- package/lib/mcp/client.js +79 -0
- package/lib/mcp/handler.js +57 -0
- package/lib/mcp/server.js +165 -0
- package/lib/paths.js +46 -0
- package/lib/registry/actions.js +164 -0
- package/lib/registry/catalog.js +137 -0
- package/lib/registry/tools.js +71 -0
- package/lib/tools/create-job.js +99 -0
- package/lib/tools/github.js +217 -0
- package/lib/tools/openai.js +35 -0
- package/lib/tools/telegram.js +292 -0
- package/lib/triggers.js +118 -0
- package/lib/utils/render-md.js +102 -0
- package/package.json +103 -0
- package/setup/lib/auth.mjs +81 -0
- package/setup/lib/env.mjs +21 -0
- package/setup/lib/fs-utils.mjs +20 -0
- package/setup/lib/github.mjs +149 -0
- package/setup/lib/prerequisites.mjs +155 -0
- package/setup/lib/prompts.mjs +267 -0
- package/setup/lib/providers.mjs +48 -0
- package/setup/lib/sync.mjs +125 -0
- package/setup/lib/targets.mjs +45 -0
- package/setup/lib/telegram-verify.mjs +63 -0
- package/setup/lib/telegram.mjs +76 -0
- package/setup/setup-telegram.mjs +264 -0
- package/setup/setup.mjs +842 -0
- package/templates/.dockerignore +5 -0
- package/templates/.env.example +63 -0
- package/templates/.github/workflows/auto-merge.yml +117 -0
- package/templates/.github/workflows/build-image.yml +36 -0
- package/templates/.github/workflows/notify-job-failed.yml +64 -0
- package/templates/.github/workflows/notify-pr-complete.yml +119 -0
- package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
- package/templates/.github/workflows/run-job.yml +89 -0
- package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
- package/templates/.gitignore.template +45 -0
- package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
- package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
- package/templates/CLAUDE.md +29 -0
- package/templates/CLAUDE.md.template +307 -0
- package/templates/app/api/[...thepopebot]/route.js +1 -0
- package/templates/app/api/auth/[...nextauth]/route.js +1 -0
- package/templates/app/chat/[chatId]/page.js +8 -0
- package/templates/app/chats/page.js +7 -0
- package/templates/app/components/ascii-logo.jsx +10 -0
- package/templates/app/components/login-form.jsx +92 -0
- package/templates/app/components/setup-form.jsx +82 -0
- package/templates/app/components/theme-provider.jsx +11 -0
- package/templates/app/components/theme-toggle.jsx +38 -0
- package/templates/app/components/ui/button.jsx +21 -0
- package/templates/app/components/ui/card.jsx +23 -0
- package/templates/app/components/ui/input.jsx +10 -0
- package/templates/app/components/ui/label.jsx +10 -0
- package/templates/app/crons/page.js +5 -0
- package/templates/app/findings/page.js +7 -0
- package/templates/app/globals.css +90 -0
- package/templates/app/layout.js +19 -0
- package/templates/app/login/page.js +15 -0
- package/templates/app/notifications/page.js +7 -0
- package/templates/app/page.js +7 -0
- package/templates/app/settings/crons/page.js +5 -0
- package/templates/app/settings/layout.js +7 -0
- package/templates/app/settings/mcp/page.js +5 -0
- package/templates/app/settings/page.js +5 -0
- package/templates/app/settings/secrets/page.js +5 -0
- package/templates/app/settings/triggers/page.js +5 -0
- package/templates/app/stream/chat/route.js +1 -0
- package/templates/app/swarm/page.js +7 -0
- package/templates/app/targets/page.js +7 -0
- package/templates/app/toolbox/page.js +7 -0
- package/templates/app/triggers/page.js +5 -0
- package/templates/config/AGENT.md +34 -0
- package/templates/config/CRONS.json +56 -0
- package/templates/config/EVENT_HANDLER.md +224 -0
- package/templates/config/HEARTBEAT.md +3 -0
- package/templates/config/JOB_SUMMARY.md +130 -0
- package/templates/config/MCP_SERVERS.json +1 -0
- package/templates/config/SKILL_BUILDING_GUIDE.md +90 -0
- package/templates/config/SOUL.md +17 -0
- package/templates/config/TRIGGERS.json +58 -0
- package/templates/docker/event-handler/Dockerfile +20 -0
- package/templates/docker/event-handler/ecosystem.config.cjs +8 -0
- package/templates/docker/job-claude-code/Dockerfile +34 -0
- package/templates/docker/job-claude-code/entrypoint.sh +139 -0
- package/templates/docker/job-pi-coding-agent/Dockerfile +44 -0
- package/templates/docker/job-pi-coding-agent/entrypoint.sh +163 -0
- package/templates/docker-compose.yml +63 -0
- package/templates/instrumentation.js +6 -0
- package/templates/middleware.js +1 -0
- package/templates/next.config.mjs +3 -0
- package/templates/postcss.config.mjs +5 -0
- package/templates/skills/LICENSE +21 -0
- package/templates/skills/README.md +119 -0
- package/templates/skills/brave-search/SKILL.md +79 -0
- package/templates/skills/brave-search/content.js +86 -0
- package/templates/skills/brave-search/package-lock.json +621 -0
- package/templates/skills/brave-search/package.json +14 -0
- package/templates/skills/brave-search/search.js +199 -0
- package/templates/skills/browser-tools/SKILL.md +196 -0
- package/templates/skills/browser-tools/browser-content.js +103 -0
- package/templates/skills/browser-tools/browser-cookies.js +35 -0
- package/templates/skills/browser-tools/browser-eval.js +53 -0
- package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/templates/skills/browser-tools/browser-nav.js +44 -0
- package/templates/skills/browser-tools/browser-pick.js +162 -0
- package/templates/skills/browser-tools/browser-screenshot.js +34 -0
- package/templates/skills/browser-tools/browser-start.js +87 -0
- package/templates/skills/browser-tools/package-lock.json +2556 -0
- package/templates/skills/browser-tools/package.json +19 -0
- package/templates/skills/llm-secrets/SKILL.md +34 -0
- package/templates/skills/llm-secrets/llm-secrets.js +33 -0
- package/templates/skills/modify-self/SKILL.md +12 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutonomousEngine — Background thinking loop for agents.
|
|
3
|
+
*
|
|
4
|
+
* Every agent runs this engine to continuously analyze context, identify
|
|
5
|
+
* improvements, calculate efficiency, and propose automations. Thoughts are
|
|
6
|
+
* stored in-memory and logged (no external API needed).
|
|
7
|
+
*
|
|
8
|
+
* Adapted from Harbinger's agents/shared/autonomous-engine.js.
|
|
9
|
+
* Key change: stores thoughts locally instead of POSTing to swarm API.
|
|
10
|
+
*
|
|
11
|
+
* COST_BENEFIT = (TIME_SAVED * FREQUENCY) / (IMPL_COST + RUNNING_COST)
|
|
12
|
+
* Only proposals with cost_benefit > 1.0 get surfaced.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { discoverAgents } from '../agents.js';
|
|
16
|
+
|
|
17
|
+
const MAX_THOUGHTS = 200; // Rolling buffer size
|
|
18
|
+
|
|
19
|
+
export class AutonomousEngine {
|
|
20
|
+
constructor(opts = {}) {
|
|
21
|
+
this.agentId = opts.agentId || 'unknown';
|
|
22
|
+
this.agentName = opts.agentName || 'AGENT';
|
|
23
|
+
this.agentType = opts.agentType || 'general';
|
|
24
|
+
this.interval = opts.interval || 60000; // 60-second thinking cycle
|
|
25
|
+
this._timer = null;
|
|
26
|
+
this._running = false;
|
|
27
|
+
this._cycleCount = 0;
|
|
28
|
+
this._lastContext = null;
|
|
29
|
+
this._thoughts = []; // in-memory thought storage
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
start() {
|
|
33
|
+
if (this._running) return;
|
|
34
|
+
this._running = true;
|
|
35
|
+
console.log(`[${this.agentName}] Autonomous engine started (${this.interval / 1000}s cycle)`);
|
|
36
|
+
|
|
37
|
+
// Run first cycle immediately, then on interval
|
|
38
|
+
this._think();
|
|
39
|
+
this._timer = setInterval(() => this._think(), this.interval);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
stop() {
|
|
43
|
+
this._running = false;
|
|
44
|
+
if (this._timer) {
|
|
45
|
+
clearInterval(this._timer);
|
|
46
|
+
this._timer = null;
|
|
47
|
+
}
|
|
48
|
+
console.log(`[${this.agentName}] Autonomous engine stopped after ${this._cycleCount} cycles`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async _think() {
|
|
52
|
+
this._cycleCount++;
|
|
53
|
+
try {
|
|
54
|
+
// 1. Gather context — what agents are available locally?
|
|
55
|
+
const context = this._gatherContext();
|
|
56
|
+
this._lastContext = context;
|
|
57
|
+
|
|
58
|
+
// 2. Identify enhancements — scan 5 dimensions
|
|
59
|
+
const enhancements = this._identifyEnhancements(context);
|
|
60
|
+
|
|
61
|
+
// 3. For each enhancement, calculate efficiency and classify
|
|
62
|
+
for (const enhancement of enhancements) {
|
|
63
|
+
const efficiency = this._calculateEfficiency(enhancement);
|
|
64
|
+
|
|
65
|
+
// Only store if cost-benefit ratio is positive
|
|
66
|
+
if (efficiency.cost_benefit >= 1.0) {
|
|
67
|
+
enhancement.efficiency = efficiency;
|
|
68
|
+
enhancement.efficiency.automation_type = this._classifyAutomation(enhancement);
|
|
69
|
+
this._storeThought(enhancement);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} catch (err) {
|
|
73
|
+
// Silent fail — don't crash the agent over thinking errors
|
|
74
|
+
if (this._cycleCount <= 3) {
|
|
75
|
+
console.error(`[${this.agentName}] Think cycle error:`, err.message);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Gather local context — discover agents and their statuses.
|
|
82
|
+
* No HTTP calls — everything is in-process.
|
|
83
|
+
*/
|
|
84
|
+
_gatherContext() {
|
|
85
|
+
try {
|
|
86
|
+
const agents = discoverAgents();
|
|
87
|
+
return {
|
|
88
|
+
agents,
|
|
89
|
+
agentCount: agents.length,
|
|
90
|
+
self: agents.find(a => a.id === this.agentId) || null,
|
|
91
|
+
thoughtCount: this._thoughts.length,
|
|
92
|
+
cycleCount: this._cycleCount,
|
|
93
|
+
};
|
|
94
|
+
} catch {
|
|
95
|
+
return { agents: [], agentCount: 0, self: null, thoughtCount: 0, cycleCount: this._cycleCount };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Scan 5 dimensions for potential enhancements
|
|
100
|
+
_identifyEnhancements(context) {
|
|
101
|
+
const enhancements = [];
|
|
102
|
+
|
|
103
|
+
// Dimension 1: Performance — detect high thought volume
|
|
104
|
+
if (context.thoughtCount > 50) {
|
|
105
|
+
enhancements.push({
|
|
106
|
+
type: 'enhancement',
|
|
107
|
+
category: 'performance',
|
|
108
|
+
title: `${this.agentName} generating high thought volume`,
|
|
109
|
+
content: `Engine has ${context.thoughtCount} thoughts. Consider batch processing or reducing scan frequency.`,
|
|
110
|
+
priority: 2,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Dimension 2: Accuracy — check cycle health
|
|
115
|
+
if (context.cycleCount > 100 && context.thoughtCount === 0) {
|
|
116
|
+
enhancements.push({
|
|
117
|
+
type: 'observation',
|
|
118
|
+
category: 'accuracy',
|
|
119
|
+
title: 'No actionable thoughts after many cycles',
|
|
120
|
+
content: `${context.cycleCount} cycles completed with no thoughts. Consider adjusting thresholds.`,
|
|
121
|
+
priority: 3,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Dimension 3: Cost — monitor cycle count
|
|
126
|
+
if (context.cycleCount > 1000) {
|
|
127
|
+
enhancements.push({
|
|
128
|
+
type: 'alert',
|
|
129
|
+
category: 'cost',
|
|
130
|
+
title: 'High cycle count — consider throttling',
|
|
131
|
+
content: `Engine has run ${context.cycleCount} cycles. Consider increasing interval to reduce resource usage.`,
|
|
132
|
+
priority: 4,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Dimension 4: Automation — detect idle potential
|
|
137
|
+
if (context.agentCount > 0) {
|
|
138
|
+
enhancements.push({
|
|
139
|
+
type: 'proposal',
|
|
140
|
+
category: 'automation',
|
|
141
|
+
title: `${context.agentCount} agent profiles available`,
|
|
142
|
+
content: `Agent profiles detected: ${context.agents.map(a => a.codename || a.id).join(', ')}. Could schedule proactive scans.`,
|
|
143
|
+
priority: 2,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Dimension 5: Collaboration — multi-agent coordination
|
|
148
|
+
if (context.agentCount > 3) {
|
|
149
|
+
enhancements.push({
|
|
150
|
+
type: 'proposal',
|
|
151
|
+
category: 'collaboration',
|
|
152
|
+
title: 'Multiple agents available — suggest coordinated workflow',
|
|
153
|
+
content: `${context.agentCount} agent profiles loaded. Consider launching coordinated assessments.`,
|
|
154
|
+
priority: 3,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return enhancements;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Calculate cost-benefit ratio for an enhancement
|
|
162
|
+
_calculateEfficiency(enhancement) {
|
|
163
|
+
const baseTimeSaved = { performance: 2, accuracy: 1, cost: 3, automation: 4, collaboration: 2 };
|
|
164
|
+
const baseFrequency = { performance: 7, accuracy: 3, cost: 1, automation: 14, collaboration: 2 };
|
|
165
|
+
const baseCost = { performance: 4, accuracy: 2, cost: 1, automation: 8, collaboration: 6 };
|
|
166
|
+
|
|
167
|
+
const cat = enhancement.category || 'automation';
|
|
168
|
+
const timeSaved = (baseTimeSaved[cat] || 1) * (enhancement.priority / 3);
|
|
169
|
+
const frequency = baseFrequency[cat] || 1;
|
|
170
|
+
const implCost = baseCost[cat] || 4;
|
|
171
|
+
const runningCost = implCost * 0.1;
|
|
172
|
+
|
|
173
|
+
const costBenefit = (timeSaved * frequency) / (implCost + runningCost);
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
time_saved: Math.round(timeSaved * 100) / 100,
|
|
177
|
+
frequency,
|
|
178
|
+
implementation_cost: implCost,
|
|
179
|
+
running_cost: Math.round(runningCost * 100) / 100,
|
|
180
|
+
cost_benefit: Math.round(costBenefit * 100) / 100,
|
|
181
|
+
automation_type: 'script',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_classifyAutomation(enhancement) {
|
|
186
|
+
const cat = enhancement.category;
|
|
187
|
+
if (cat === 'automation') return 'script';
|
|
188
|
+
if (cat === 'performance') return 'code_change';
|
|
189
|
+
if (cat === 'collaboration') return 'workflow';
|
|
190
|
+
if (cat === 'accuracy') return 'skill';
|
|
191
|
+
return 'script';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Store thought in the local rolling buffer.
|
|
196
|
+
*/
|
|
197
|
+
_storeThought(thought) {
|
|
198
|
+
const entry = {
|
|
199
|
+
timestamp: new Date().toISOString(),
|
|
200
|
+
agent_id: this.agentId,
|
|
201
|
+
agent_name: this.agentName,
|
|
202
|
+
type: thought.type || 'observation',
|
|
203
|
+
category: thought.category || '',
|
|
204
|
+
title: thought.title || 'Untitled thought',
|
|
205
|
+
content: thought.content || '',
|
|
206
|
+
priority: thought.priority || 3,
|
|
207
|
+
efficiency: thought.efficiency || null,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
this._thoughts.push(entry);
|
|
211
|
+
|
|
212
|
+
// Rolling buffer — trim oldest
|
|
213
|
+
if (this._thoughts.length > MAX_THOUGHTS) {
|
|
214
|
+
this._thoughts = this._thoughts.slice(-MAX_THOUGHTS);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Get all stored thoughts. */
|
|
219
|
+
getThoughts() {
|
|
220
|
+
return [...this._thoughts];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Get the last gathered context. */
|
|
224
|
+
getLastContext() {
|
|
225
|
+
return this._lastContext;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Get engine status. */
|
|
229
|
+
getStatus() {
|
|
230
|
+
return {
|
|
231
|
+
running: this._running,
|
|
232
|
+
cycles: this._cycleCount,
|
|
233
|
+
agent: this.agentName,
|
|
234
|
+
interval: this.interval,
|
|
235
|
+
thoughtCount: this._thoughts.length,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let _engine = null;
|
|
241
|
+
|
|
242
|
+
/** Get the AutonomousEngine singleton. */
|
|
243
|
+
export function getAutonomousEngine() {
|
|
244
|
+
return _engine;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Start the autonomous engine with the given options. */
|
|
248
|
+
export function startAutonomousEngine(opts = {}) {
|
|
249
|
+
if (_engine) return _engine;
|
|
250
|
+
_engine = new AutonomousEngine(opts);
|
|
251
|
+
_engine.start();
|
|
252
|
+
return _engine;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Stop the autonomous engine. */
|
|
256
|
+
export function stopAutonomousEngine() {
|
|
257
|
+
if (_engine) {
|
|
258
|
+
_engine.stop();
|
|
259
|
+
_engine = null;
|
|
260
|
+
}
|
|
261
|
+
}
|
package/lib/ai/index.js
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { HumanMessage, AIMessage } from '@langchain/core/messages';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getAgent, getAgentForProfile } from './agent.js';
|
|
4
|
+
import { createModel } from './model.js';
|
|
5
|
+
import { jobSummaryMd } from '../paths.js';
|
|
6
|
+
import { render_md } from '../utils/render-md.js';
|
|
7
|
+
import { getChatById, createChat, saveMessage, updateChatTitle } from '../db/chats.js';
|
|
8
|
+
import { discoverAgents } from '../agents.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detect @AGENT_NAME mentions at the start of a message.
|
|
12
|
+
* Returns { agentId, cleanMessage } or null if no mention found.
|
|
13
|
+
* Matches @codename or @id (case-insensitive) against discovered agent profiles.
|
|
14
|
+
*/
|
|
15
|
+
let _agentMap = null;
|
|
16
|
+
function detectAgentMention(message) {
|
|
17
|
+
if (!message) return null;
|
|
18
|
+
const match = message.match(/^@(\w[\w-]*)\s+/i);
|
|
19
|
+
if (!match) return null;
|
|
20
|
+
|
|
21
|
+
const mention = match[1].toLowerCase();
|
|
22
|
+
|
|
23
|
+
// Lazy-load agent map
|
|
24
|
+
if (!_agentMap) {
|
|
25
|
+
try {
|
|
26
|
+
const agents = discoverAgents();
|
|
27
|
+
_agentMap = new Map();
|
|
28
|
+
for (const a of agents) {
|
|
29
|
+
if (a.codename) _agentMap.set(a.codename.toLowerCase(), a.id);
|
|
30
|
+
if (a.id) _agentMap.set(a.id.toLowerCase(), a.id);
|
|
31
|
+
if (a.name) _agentMap.set(a.name.toLowerCase(), a.id);
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
_agentMap = new Map();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const agentId = _agentMap.get(mention);
|
|
39
|
+
if (!agentId) return null;
|
|
40
|
+
return { agentId, cleanMessage: message.slice(match[0].length) };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Ensure a chat exists in the DB and save a message.
|
|
45
|
+
* Centralized so every channel gets persistence automatically.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} threadId - Chat/thread ID
|
|
48
|
+
* @param {string} role - 'user' or 'assistant'
|
|
49
|
+
* @param {string} text - Message text
|
|
50
|
+
* @param {object} [options] - { userId, chatTitle }
|
|
51
|
+
*/
|
|
52
|
+
function persistMessage(threadId, role, text, options = {}) {
|
|
53
|
+
try {
|
|
54
|
+
if (!getChatById(threadId)) {
|
|
55
|
+
createChat(options.userId || 'unknown', options.chatTitle || 'New Chat', threadId);
|
|
56
|
+
}
|
|
57
|
+
saveMessage(threadId, role, text);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
// DB persistence is best-effort — don't break chat if DB fails
|
|
60
|
+
console.error('Failed to persist message:', err);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Process a chat message through the LangGraph agent.
|
|
66
|
+
* Saves user and assistant messages to the DB automatically.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} threadId - Conversation thread ID (from channel adapter)
|
|
69
|
+
* @param {string} message - User's message text
|
|
70
|
+
* @param {Array} [attachments=[]] - Normalized attachments from adapter
|
|
71
|
+
* @param {object} [options] - { userId, chatTitle } for DB persistence
|
|
72
|
+
* @returns {Promise<string>} AI response text
|
|
73
|
+
*/
|
|
74
|
+
async function chat(threadId, message, attachments = [], options = {}) {
|
|
75
|
+
// Detect @AGENT_NAME mention or use explicit agentId option
|
|
76
|
+
let effectiveMessage = message;
|
|
77
|
+
let agentId = options.agentId || null;
|
|
78
|
+
if (!agentId && message) {
|
|
79
|
+
const mention = detectAgentMention(message);
|
|
80
|
+
if (mention) {
|
|
81
|
+
agentId = mention.agentId;
|
|
82
|
+
effectiveMessage = mention.cleanMessage;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const agent = agentId ? await getAgentForProfile(agentId) : await getAgent();
|
|
86
|
+
|
|
87
|
+
// Save user message to DB (save original message with @mention)
|
|
88
|
+
persistMessage(threadId, 'user', message || '[attachment]', options);
|
|
89
|
+
|
|
90
|
+
// Build content blocks: text + any image attachments as base64 vision
|
|
91
|
+
// Use effectiveMessage (with @mention stripped) for the LLM
|
|
92
|
+
const content = [];
|
|
93
|
+
|
|
94
|
+
if (effectiveMessage) {
|
|
95
|
+
content.push({ type: 'text', text: effectiveMessage });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const att of attachments) {
|
|
99
|
+
if (att.category === 'image') {
|
|
100
|
+
content.push({
|
|
101
|
+
type: 'image_url',
|
|
102
|
+
image_url: {
|
|
103
|
+
url: `data:${att.mimeType};base64,${att.data.toString('base64')}`,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
// Documents: future handling
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// If only text and no attachments, simplify to a string
|
|
111
|
+
const messageContent = content.length === 1 && content[0].type === 'text'
|
|
112
|
+
? content[0].text
|
|
113
|
+
: content;
|
|
114
|
+
|
|
115
|
+
const result = await agent.invoke(
|
|
116
|
+
{ messages: [new HumanMessage({ content: messageContent })] },
|
|
117
|
+
{ configurable: { thread_id: threadId } }
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const lastMessage = result.messages[result.messages.length - 1];
|
|
121
|
+
|
|
122
|
+
// LangChain message content can be a string or an array of content blocks
|
|
123
|
+
let response;
|
|
124
|
+
if (typeof lastMessage.content === 'string') {
|
|
125
|
+
response = lastMessage.content;
|
|
126
|
+
} else {
|
|
127
|
+
response = lastMessage.content
|
|
128
|
+
.filter((block) => block.type === 'text')
|
|
129
|
+
.map((block) => block.text)
|
|
130
|
+
.join('\n');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Save assistant response to DB
|
|
134
|
+
persistMessage(threadId, 'assistant', response, options);
|
|
135
|
+
|
|
136
|
+
// Auto-generate title for new chats
|
|
137
|
+
if (options.userId && message) {
|
|
138
|
+
autoTitle(threadId, message).catch(() => {});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return response;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Process a chat message with streaming (for channels that support it).
|
|
146
|
+
* Saves user and assistant messages to the DB automatically.
|
|
147
|
+
*
|
|
148
|
+
* @param {string} threadId - Conversation thread ID
|
|
149
|
+
* @param {string} message - User's message text
|
|
150
|
+
* @param {Array} [attachments=[]] - Image/PDF attachments: { category, mimeType, dataUrl }
|
|
151
|
+
* @param {object} [options] - { userId, chatTitle, skipUserPersist } for DB persistence
|
|
152
|
+
* @returns {AsyncIterableIterator<string>} Stream of text chunks
|
|
153
|
+
*/
|
|
154
|
+
async function* chatStream(threadId, message, attachments = [], options = {}) {
|
|
155
|
+
// Detect @AGENT_NAME mention or use explicit agentId option
|
|
156
|
+
let effectiveMessage = message;
|
|
157
|
+
let agentId = options.agentId || null;
|
|
158
|
+
if (!agentId && message) {
|
|
159
|
+
const mention = detectAgentMention(message);
|
|
160
|
+
if (mention) {
|
|
161
|
+
agentId = mention.agentId;
|
|
162
|
+
effectiveMessage = mention.cleanMessage;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const agent = agentId ? await getAgentForProfile(agentId) : await getAgent();
|
|
166
|
+
|
|
167
|
+
// Save user message to DB (skip on regeneration — message already exists)
|
|
168
|
+
if (!options.skipUserPersist) {
|
|
169
|
+
persistMessage(threadId, 'user', message || '[attachment]', options);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Build content blocks: text + any image/PDF attachments as vision
|
|
173
|
+
// Use effectiveMessage (with @mention stripped) for the LLM
|
|
174
|
+
const content = [];
|
|
175
|
+
|
|
176
|
+
if (effectiveMessage) {
|
|
177
|
+
content.push({ type: 'text', text: effectiveMessage });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (const att of attachments) {
|
|
181
|
+
if (att.category === 'image') {
|
|
182
|
+
// Support both dataUrl (web) and Buffer (Telegram) formats
|
|
183
|
+
const url = att.dataUrl
|
|
184
|
+
? att.dataUrl
|
|
185
|
+
: `data:${att.mimeType};base64,${att.data.toString('base64')}`;
|
|
186
|
+
content.push({
|
|
187
|
+
type: 'image_url',
|
|
188
|
+
image_url: { url },
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// If only text and no attachments, simplify to a string
|
|
194
|
+
const messageContent = content.length === 1 && content[0].type === 'text'
|
|
195
|
+
? content[0].text
|
|
196
|
+
: content;
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const stream = await agent.stream(
|
|
200
|
+
{ messages: [new HumanMessage({ content: messageContent })] },
|
|
201
|
+
{ configurable: { thread_id: threadId }, streamMode: 'messages' }
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
let fullText = '';
|
|
205
|
+
|
|
206
|
+
for await (const event of stream) {
|
|
207
|
+
// streamMode: 'messages' yields [message, metadata] tuples
|
|
208
|
+
const msg = Array.isArray(event) ? event[0] : event;
|
|
209
|
+
const msgType = msg._getType?.();
|
|
210
|
+
|
|
211
|
+
if (msgType === 'ai') {
|
|
212
|
+
// Tool calls — AIMessage.tool_calls is an array of { id, name, args }
|
|
213
|
+
if (msg.tool_calls?.length > 0) {
|
|
214
|
+
for (const tc of msg.tool_calls) {
|
|
215
|
+
yield {
|
|
216
|
+
type: 'tool-call',
|
|
217
|
+
toolCallId: tc.id,
|
|
218
|
+
toolName: tc.name,
|
|
219
|
+
args: tc.args,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Text content (wrapped in structured object)
|
|
225
|
+
let text = '';
|
|
226
|
+
if (typeof msg.content === 'string') {
|
|
227
|
+
text = msg.content;
|
|
228
|
+
} else if (Array.isArray(msg.content)) {
|
|
229
|
+
text = msg.content
|
|
230
|
+
.filter((b) => b.type === 'text' && b.text)
|
|
231
|
+
.map((b) => b.text)
|
|
232
|
+
.join('');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (text) {
|
|
236
|
+
fullText += text;
|
|
237
|
+
yield { type: 'text', text };
|
|
238
|
+
}
|
|
239
|
+
} else if (msgType === 'tool') {
|
|
240
|
+
// Tool result — ToolMessage has tool_call_id and content
|
|
241
|
+
yield {
|
|
242
|
+
type: 'tool-result',
|
|
243
|
+
toolCallId: msg.tool_call_id,
|
|
244
|
+
result: msg.content,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
// Skip other message types (human, system)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Save assistant response to DB
|
|
251
|
+
if (fullText) {
|
|
252
|
+
persistMessage(threadId, 'assistant', fullText, options);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Auto-generate title for new chats
|
|
256
|
+
if (options.userId && message) {
|
|
257
|
+
autoTitle(threadId, message).catch(() => {});
|
|
258
|
+
}
|
|
259
|
+
} catch (err) {
|
|
260
|
+
console.error('[chatStream] error:', err);
|
|
261
|
+
throw err;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Auto-generate a chat title from the first user message (fire-and-forget).
|
|
267
|
+
* Uses structured output to avoid thinking-token leaks with extended-thinking models.
|
|
268
|
+
*/
|
|
269
|
+
async function autoTitle(threadId, firstMessage) {
|
|
270
|
+
try {
|
|
271
|
+
const chat = getChatById(threadId);
|
|
272
|
+
if (!chat || chat.title !== 'New Chat') return;
|
|
273
|
+
|
|
274
|
+
const model = await createModel({ maxTokens: 250 });
|
|
275
|
+
const response = await model.withStructuredOutput(z.object({ title: z.string() })).invoke([
|
|
276
|
+
['system', 'Generate a descriptive (8-12 word) title for this chat based on the user\'s first message.'],
|
|
277
|
+
['human', firstMessage],
|
|
278
|
+
]);
|
|
279
|
+
if (response.title.trim()) {
|
|
280
|
+
updateChatTitle(threadId, response.title.trim());
|
|
281
|
+
}
|
|
282
|
+
} catch (err) {
|
|
283
|
+
console.error('[autoTitle] Failed to generate title:', err.message);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* One-shot summarization with a different system prompt and no memory.
|
|
289
|
+
* Used for job completion summaries sent via GitHub webhook.
|
|
290
|
+
*
|
|
291
|
+
* @param {object} results - Job results from webhook payload
|
|
292
|
+
* @returns {Promise<string>} Summary text
|
|
293
|
+
*/
|
|
294
|
+
async function summarizeJob(results) {
|
|
295
|
+
try {
|
|
296
|
+
const model = await createModel({ maxTokens: 1024 });
|
|
297
|
+
const systemPrompt = render_md(jobSummaryMd);
|
|
298
|
+
|
|
299
|
+
if (!systemPrompt) {
|
|
300
|
+
console.error(`[summarizeJob] Empty system prompt — JOB_SUMMARY.md not found or empty at: ${jobSummaryMd}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const userMessage = [
|
|
304
|
+
results.job ? `## Task\n${results.job}` : '',
|
|
305
|
+
results.commit_message ? `## Commit Message\n${results.commit_message}` : '',
|
|
306
|
+
results.changed_files?.length ? `## Changed Files\n${results.changed_files.join('\n')}` : '',
|
|
307
|
+
results.status ? `## Status\n${results.status}` : '',
|
|
308
|
+
results.merge_result ? `## Merge Result\n${results.merge_result}` : '',
|
|
309
|
+
results.pr_url ? `## PR URL\n${results.pr_url}` : '',
|
|
310
|
+
results.run_url ? `## Run URL\n${results.run_url}` : '',
|
|
311
|
+
results.log ? `## Agent Log\n${results.log}` : '',
|
|
312
|
+
]
|
|
313
|
+
.filter(Boolean)
|
|
314
|
+
.join('\n\n');
|
|
315
|
+
|
|
316
|
+
console.log(`[summarizeJob] System prompt: ${systemPrompt.length} chars, user message: ${userMessage.length} chars`);
|
|
317
|
+
|
|
318
|
+
const response = await model.invoke([
|
|
319
|
+
['system', systemPrompt],
|
|
320
|
+
['human', userMessage],
|
|
321
|
+
]);
|
|
322
|
+
|
|
323
|
+
const text =
|
|
324
|
+
typeof response.content === 'string'
|
|
325
|
+
? response.content
|
|
326
|
+
: response.content
|
|
327
|
+
.filter((block) => block.type === 'text')
|
|
328
|
+
.map((block) => block.text)
|
|
329
|
+
.join('\n');
|
|
330
|
+
|
|
331
|
+
console.log(`[summarizeJob] Result: ${text.length} chars — ${text.slice(0, 200)}`);
|
|
332
|
+
|
|
333
|
+
return text.trim() || 'Job finished.';
|
|
334
|
+
} catch (err) {
|
|
335
|
+
console.error('[summarizeJob] Failed to summarize job:', err);
|
|
336
|
+
return 'Job finished.';
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Inject a message into a thread's memory so the agent has context
|
|
342
|
+
* for future conversations (e.g., job completion summaries).
|
|
343
|
+
*
|
|
344
|
+
* @param {string} threadId - Conversation thread ID
|
|
345
|
+
* @param {string} text - Message text to inject as an assistant message
|
|
346
|
+
*/
|
|
347
|
+
async function addToThread(threadId, text) {
|
|
348
|
+
try {
|
|
349
|
+
const agent = await getAgent();
|
|
350
|
+
await agent.updateState(
|
|
351
|
+
{ configurable: { thread_id: threadId } },
|
|
352
|
+
{ messages: [new AIMessage(text)] }
|
|
353
|
+
);
|
|
354
|
+
} catch (err) {
|
|
355
|
+
console.error('Failed to add message to thread:', err);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export { chat, chatStream, summarizeJob, addToThread, persistMessage };
|