@swarmclawai/swarmclaw 0.7.1 → 0.7.2
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/README.md +85 -139
- package/package.json +1 -1
- package/src/app/api/agents/[id]/thread/route.ts +1 -2
- package/src/app/api/agents/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/main-loop/route.ts +2 -2
- package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/route.ts +4 -52
- package/src/app/api/{sessions → chats}/route.ts +5 -7
- package/src/app/api/plugins/route.ts +3 -0
- package/src/app/api/plugins/settings/route.ts +35 -0
- package/src/app/api/usage/route.ts +30 -0
- package/src/cli/index.js +35 -33
- package/src/cli/index.ts +40 -39
- package/src/cli/spec.js +29 -27
- package/src/components/agents/agent-card.tsx +1 -1
- package/src/components/agents/agent-chat-list.tsx +3 -3
- package/src/components/agents/agent-list.tsx +8 -13
- package/src/components/agents/agent-sheet.tsx +2 -2
- package/src/components/agents/cron-job-form.tsx +3 -3
- package/src/components/agents/inspector-panel.tsx +2 -2
- package/src/components/auth/setup-wizard.tsx +5 -38
- package/src/components/chat/chat-area.tsx +10 -14
- package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +3 -3
- package/src/components/chat/chat-header.tsx +156 -73
- package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +4 -5
- package/src/components/chat/chat-tool-toggles.tsx +26 -17
- package/src/components/chat/checkpoint-timeline.tsx +4 -4
- package/src/components/chat/message-bubble.tsx +4 -1
- package/src/components/chat/message-list.tsx +2 -2
- package/src/components/{sessions/new-session-sheet.tsx → chat/new-chat-sheet.tsx} +6 -6
- package/src/components/chat/session-debug-panel.tsx +1 -1
- package/src/components/chat/tool-request-banner.tsx +3 -3
- package/src/components/chatrooms/agent-hover-card.tsx +3 -3
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
- package/src/components/connectors/connector-sheet.tsx +1 -1
- package/src/components/home/home-view.tsx +1 -1
- package/src/components/layout/app-layout.tsx +23 -2
- package/src/components/plugins/plugin-list.tsx +475 -254
- package/src/components/plugins/plugin-sheet.tsx +124 -10
- package/src/components/settings/gateway-connection-panel.tsx +1 -1
- package/src/components/shared/command-palette.tsx +0 -1
- package/src/components/shared/settings/section-heartbeat.tsx +1 -1
- package/src/components/shared/settings/section-providers.tsx +1 -1
- package/src/components/shared/settings/settings-page.tsx +1 -12
- package/src/components/usage/metrics-dashboard.tsx +73 -0
- package/src/components/webhooks/webhook-sheet.tsx +1 -1
- package/src/lib/chat.ts +1 -1
- package/src/lib/{sessions.ts → chats.ts} +28 -18
- package/src/lib/providers/claude-cli.ts +1 -1
- package/src/lib/server/approvals.ts +4 -4
- package/src/lib/server/capability-router.ts +10 -8
- package/src/lib/server/chat-execution.ts +36 -105
- package/src/lib/server/chatroom-helpers.ts +3 -3
- package/src/lib/server/connectors/manager.ts +4 -4
- package/src/lib/server/cost.ts +34 -1
- package/src/lib/server/daemon-state.ts +2 -2
- package/src/lib/server/heartbeat-service.ts +1 -1
- package/src/lib/server/main-agent-loop.ts +25 -160
- package/src/lib/server/main-session.ts +6 -13
- package/src/lib/server/orchestrator-lg.ts +3 -3
- package/src/lib/server/orchestrator.ts +5 -5
- package/src/lib/server/plugins.ts +112 -4
- package/src/lib/server/provider-health.ts +5 -3
- package/src/lib/server/queue.ts +12 -10
- package/src/lib/server/session-run-manager.test.ts +9 -6
- package/src/lib/server/session-run-manager.ts +1 -3
- package/src/lib/server/session-tools/calendar.ts +376 -0
- package/src/lib/server/session-tools/canvas.ts +1 -1
- package/src/lib/server/session-tools/chatroom.ts +4 -2
- package/src/lib/server/session-tools/connector.ts +5 -2
- package/src/lib/server/session-tools/context.ts +7 -3
- package/src/lib/server/session-tools/crud.ts +14 -6
- package/src/lib/server/session-tools/delegate.ts +95 -8
- package/src/lib/server/session-tools/discovery.ts +2 -2
- package/src/lib/server/session-tools/edit_file.ts +4 -2
- package/src/lib/server/session-tools/email.ts +322 -0
- package/src/lib/server/session-tools/file.ts +5 -2
- package/src/lib/server/session-tools/git.ts +1 -1
- package/src/lib/server/session-tools/http.ts +1 -1
- package/src/lib/server/session-tools/image-gen.ts +382 -0
- package/src/lib/server/session-tools/index.ts +74 -49
- package/src/lib/server/session-tools/memory.ts +139 -2
- package/src/lib/server/session-tools/monitor.ts +1 -1
- package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
- package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
- package/src/lib/server/session-tools/platform.ts +6 -3
- package/src/lib/server/session-tools/plugin-creator.ts +3 -3
- package/src/lib/server/session-tools/replicate.ts +303 -0
- package/src/lib/server/session-tools/sample-ui.ts +1 -1
- package/src/lib/server/session-tools/sandbox.ts +4 -2
- package/src/lib/server/session-tools/schedule.ts +4 -2
- package/src/lib/server/session-tools/session-info.ts +7 -4
- package/src/lib/server/session-tools/shell.ts +5 -2
- package/src/lib/server/session-tools/subagent.ts +2 -2
- package/src/lib/server/session-tools/wallet.ts +29 -2
- package/src/lib/server/session-tools/web.ts +44 -5
- package/src/lib/server/storage.ts +29 -9
- package/src/lib/server/stream-agent-chat.ts +72 -249
- package/src/lib/server/tool-aliases.ts +26 -15
- package/src/lib/server/tool-capability-policy.test.ts +9 -9
- package/src/lib/server/tool-capability-policy.ts +32 -27
- package/src/lib/tool-definitions.ts +4 -0
- package/src/lib/validation/schemas.ts +3 -1
- package/src/stores/use-app-store.ts +5 -5
- package/src/stores/use-chat-store.ts +7 -7
- package/src/types/index.ts +65 -3
- /package/src/app/api/{sessions → chats}/[id]/browser/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/chat/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/messages/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/stop/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
|
@@ -23,12 +23,12 @@ import { buildSessionTools } from './session-tools'
|
|
|
23
23
|
import type { StructuredToolInterface } from '@langchain/core/tools'
|
|
24
24
|
import type { Session } from '@/types'
|
|
25
25
|
import { stripMainLoopMetaForPersistence } from './main-agent-loop'
|
|
26
|
+
import { getPluginManager } from './plugins'
|
|
26
27
|
import { normalizeProviderEndpoint } from '@/lib/openclaw-endpoint'
|
|
27
|
-
import { getMemoryDb } from './memory-db'
|
|
28
28
|
import { routeTaskIntent } from './capability-router'
|
|
29
29
|
import { notify } from './ws-hub'
|
|
30
30
|
import { resolveConcreteToolPolicyBlock, resolveSessionToolPolicy } from './tool-capability-policy'
|
|
31
|
-
import {
|
|
31
|
+
import { pluginIdMatches } from './tool-aliases'
|
|
32
32
|
import { buildCurrentDateTimePromptContext } from './prompt-runtime-context'
|
|
33
33
|
import {
|
|
34
34
|
getCachedLlmResponse,
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
import type { Message, MessageToolEvent, SSEEvent, UsageRecord } from '@/types'
|
|
40
40
|
import { markProviderFailure, markProviderSuccess, rankDelegatesByHealth } from './provider-health'
|
|
41
41
|
import { NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
|
|
42
|
-
type DelegateTool = 'delegate_to_claude_code' | 'delegate_to_codex_cli' | 'delegate_to_opencode_cli'
|
|
42
|
+
type DelegateTool = 'delegate_to_claude_code' | 'delegate_to_codex_cli' | 'delegate_to_opencode_cli' | 'delegate_to_gemini_cli'
|
|
43
43
|
|
|
44
44
|
/** Slice history from the most recent context-clear marker forward */
|
|
45
45
|
function applyContextClearBoundary(messages: Message[]): Message[] {
|
|
@@ -50,6 +50,8 @@ function applyContextClearBoundary(messages: Message[]): Message[] {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
interface SessionWithTools {
|
|
53
|
+
plugins?: string[] | null
|
|
54
|
+
/** @deprecated Use plugins */
|
|
53
55
|
tools?: string[] | null
|
|
54
56
|
}
|
|
55
57
|
|
|
@@ -144,6 +146,7 @@ function requestedToolNamesFromMessage(message: string): string[] {
|
|
|
144
146
|
'delegate_to_claude_code',
|
|
145
147
|
'delegate_to_codex_cli',
|
|
146
148
|
'delegate_to_opencode_cli',
|
|
149
|
+
'delegate_to_gemini_cli',
|
|
147
150
|
'connector_message_tool',
|
|
148
151
|
'sessions_tool',
|
|
149
152
|
'whoami_tool',
|
|
@@ -326,7 +329,7 @@ function extractDelegationTask(message: string, toolName: string): string | null
|
|
|
326
329
|
}
|
|
327
330
|
|
|
328
331
|
function hasToolEnabled(session: SessionWithTools, toolName: string): boolean {
|
|
329
|
-
return
|
|
332
|
+
return pluginIdMatches(session?.plugins || session?.tools || [], toolName)
|
|
330
333
|
}
|
|
331
334
|
|
|
332
335
|
function enabledDelegationTools(session: SessionWithTools): DelegateTool[] {
|
|
@@ -334,6 +337,7 @@ function enabledDelegationTools(session: SessionWithTools): DelegateTool[] {
|
|
|
334
337
|
if (hasToolEnabled(session, 'claude_code') || hasToolEnabled(session, 'delegate')) tools.push('delegate_to_claude_code')
|
|
335
338
|
if (hasToolEnabled(session, 'codex_cli')) tools.push('delegate_to_codex_cli')
|
|
336
339
|
if (hasToolEnabled(session, 'opencode_cli')) tools.push('delegate_to_opencode_cli')
|
|
340
|
+
if (hasToolEnabled(session, 'gemini_cli')) tools.push('delegate_to_gemini_cli')
|
|
337
341
|
return tools
|
|
338
342
|
}
|
|
339
343
|
|
|
@@ -401,8 +405,8 @@ function syncSessionFromAgent(sessionId: string): void {
|
|
|
401
405
|
const normalized = normalizeProviderEndpoint(agent.provider, agent.apiEndpoint ?? null)
|
|
402
406
|
if (normalized !== session.apiEndpoint) { session.apiEndpoint = normalized; changed = true }
|
|
403
407
|
}
|
|
404
|
-
if (!Array.isArray(session.
|
|
405
|
-
session.
|
|
408
|
+
if (!Array.isArray(session.plugins)) {
|
|
409
|
+
session.plugins = Array.isArray(agent.plugins) ? [...agent.plugins] : []
|
|
406
410
|
changed = true
|
|
407
411
|
}
|
|
408
412
|
|
|
@@ -529,75 +533,6 @@ function estimateConversationTone(text: string): string {
|
|
|
529
533
|
return 'neutral'
|
|
530
534
|
}
|
|
531
535
|
|
|
532
|
-
const AUTO_MEMORY_MIN_INTERVAL_MS = 45 * 60 * 1000
|
|
533
|
-
|
|
534
|
-
function normalizeMemoryText(value: string): string {
|
|
535
|
-
return (value || '').replace(/\s+/g, ' ').trim()
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
function shouldStoreAutoMemoryNote(opts: {
|
|
539
|
-
session: Session
|
|
540
|
-
source: string
|
|
541
|
-
internal: boolean
|
|
542
|
-
message: string
|
|
543
|
-
response: string
|
|
544
|
-
now: number
|
|
545
|
-
}): boolean {
|
|
546
|
-
const { session, source, internal, message, response, now } = opts
|
|
547
|
-
if (internal) return false
|
|
548
|
-
if (source !== 'chat' && source !== 'connector') return false
|
|
549
|
-
if (!session?.agentId) return false
|
|
550
|
-
if (!Array.isArray(session.tools) || !session.tools.includes('memory')) return false
|
|
551
|
-
const msg = (message || '').trim()
|
|
552
|
-
const resp = (response || '').trim()
|
|
553
|
-
if (msg.length < 20 || resp.length < 40) return false
|
|
554
|
-
if (/^(ok|okay|cool|thanks|thx|got it|nice)[.! ]*$/i.test(msg)) return false
|
|
555
|
-
if (resp === 'HEARTBEAT_OK') return false
|
|
556
|
-
const last = typeof session.lastAutoMemoryAt === 'number' ? session.lastAutoMemoryAt : 0
|
|
557
|
-
if (last > 0 && now - last < AUTO_MEMORY_MIN_INTERVAL_MS) return false
|
|
558
|
-
return true
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
function storeAutoMemoryNote(opts: {
|
|
562
|
-
session: Session
|
|
563
|
-
message: string
|
|
564
|
-
response: string
|
|
565
|
-
source: string
|
|
566
|
-
now: number
|
|
567
|
-
}): string | null {
|
|
568
|
-
const { session, message, response, source, now } = opts
|
|
569
|
-
try {
|
|
570
|
-
const db = getMemoryDb()
|
|
571
|
-
const compactMessage = message.replace(/\s+/g, ' ').trim().slice(0, 220)
|
|
572
|
-
const compactResponse = response.replace(/\s+/g, ' ').trim().slice(0, 700)
|
|
573
|
-
const title = `[auto] ${compactMessage.slice(0, 90)}`
|
|
574
|
-
const content = [
|
|
575
|
-
`source: ${source}`,
|
|
576
|
-
`user_request: ${compactMessage}`,
|
|
577
|
-
`assistant_outcome: ${compactResponse}`,
|
|
578
|
-
].join('\n')
|
|
579
|
-
const latest = db.getLatestBySessionCategory?.(session.id, 'execution')
|
|
580
|
-
if (latest) {
|
|
581
|
-
const sameTitle = normalizeMemoryText(latest.title) === normalizeMemoryText(title)
|
|
582
|
-
const sameContent = normalizeMemoryText(latest.content) === normalizeMemoryText(content)
|
|
583
|
-
if (sameTitle && sameContent) {
|
|
584
|
-
session.lastAutoMemoryAt = now
|
|
585
|
-
return latest.id
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
const created = db.add({
|
|
589
|
-
agentId: session.agentId as string,
|
|
590
|
-
sessionId: session.id as string,
|
|
591
|
-
category: 'execution',
|
|
592
|
-
title,
|
|
593
|
-
content,
|
|
594
|
-
})
|
|
595
|
-
session.lastAutoMemoryAt = now
|
|
596
|
-
return created?.id || null
|
|
597
|
-
} catch {
|
|
598
|
-
return null
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
536
|
|
|
602
537
|
export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promise<ExecuteChatTurnResult> {
|
|
603
538
|
const { message } = input
|
|
@@ -620,29 +555,29 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
620
555
|
if (!session) throw new Error(`Session not found: ${sessionId}`)
|
|
621
556
|
|
|
622
557
|
const appSettings = loadSettings()
|
|
623
|
-
const toolPolicy = resolveSessionToolPolicy(session.
|
|
558
|
+
const toolPolicy = resolveSessionToolPolicy(session.plugins, appSettings)
|
|
624
559
|
const isHeartbeatRun = internal && source === 'heartbeat'
|
|
625
560
|
const isAutoRunNoHistory = isHeartbeatRun || (internal && source === 'main-loop-followup')
|
|
626
561
|
const heartbeatStatus = session.mainLoopState?.status || 'idle'
|
|
627
|
-
const mainLoopIdle = session.
|
|
562
|
+
const mainLoopIdle = session.id.startsWith('agent-thread-')
|
|
628
563
|
&& (heartbeatStatus === 'ok' || heartbeatStatus === 'idle')
|
|
629
564
|
&& !(session.mainLoopState?.pendingEvents?.length > 0)
|
|
630
565
|
const heartbeatStatusOnly = isHeartbeatRun && mainLoopIdle
|
|
631
|
-
const
|
|
632
|
-
let sessionForRun =
|
|
566
|
+
const pluginsForRun = heartbeatStatusOnly ? [] : toolPolicy.enabledPlugins
|
|
567
|
+
let sessionForRun = pluginsForRun === session.plugins
|
|
633
568
|
? session
|
|
634
|
-
: { ...session,
|
|
569
|
+
: { ...session, plugins: pluginsForRun }
|
|
635
570
|
|
|
636
571
|
// Apply model override for heartbeat runs (cheaper model)
|
|
637
572
|
if (isHeartbeatRun && input.modelOverride) {
|
|
638
573
|
sessionForRun = { ...sessionForRun, model: input.modelOverride }
|
|
639
574
|
}
|
|
640
575
|
|
|
641
|
-
if (!heartbeatStatusOnly && toolPolicy.
|
|
642
|
-
const blockedSummary = toolPolicy.
|
|
576
|
+
if (!heartbeatStatusOnly && toolPolicy.blockedPlugins.length > 0) {
|
|
577
|
+
const blockedSummary = toolPolicy.blockedPlugins
|
|
643
578
|
.map((entry) => `${entry.tool} (${entry.reason})`)
|
|
644
579
|
.join(', ')
|
|
645
|
-
onEvent?.({ t: 'err', text: `Capability policy blocked
|
|
580
|
+
onEvent?.({ t: 'err', text: `Capability policy blocked plugins for this run: ${blockedSummary}` })
|
|
646
581
|
}
|
|
647
582
|
|
|
648
583
|
// --- Agent spend-limit enforcement (hourly/daily/monthly) ---
|
|
@@ -861,7 +796,7 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
861
796
|
const responseCacheConfig = resolveLlmResponseCacheConfig(appSettings)
|
|
862
797
|
let responseCacheHit = false
|
|
863
798
|
let responseCacheInput: LlmResponseCacheKeyInput | null = null
|
|
864
|
-
const
|
|
799
|
+
const hasPlugins = !!(sessionForRun.plugins?.length || sessionForRun.tools?.length) && !NON_LANGGRAPH_PROVIDER_IDS.has(providerType)
|
|
865
800
|
|
|
866
801
|
let durationMs = 0
|
|
867
802
|
const startTs = Date.now()
|
|
@@ -873,8 +808,8 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
873
808
|
? getSessionMessages(sessionId).slice(-6)
|
|
874
809
|
: undefined
|
|
875
810
|
|
|
876
|
-
console.log(`[chat-execution] provider=${providerType},
|
|
877
|
-
if (
|
|
811
|
+
console.log(`[chat-execution] provider=${providerType}, hasPlugins=${hasPlugins}, imagePath=${imagePath || 'none'}, attachedFiles=${attachedFiles?.length || 0}, plugins=${(sessionForRun.plugins || sessionForRun.tools || []).length}`)
|
|
812
|
+
if (hasPlugins) {
|
|
878
813
|
fullResponse = (await streamAgentChat({
|
|
879
814
|
session: sessionForRun,
|
|
880
815
|
message: message,
|
|
@@ -967,7 +902,7 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
967
902
|
|
|
968
903
|
// Record usage for the direct (non-tools) streamChat path.
|
|
969
904
|
// streamAgentChat already calls appendUsage internally for the tools path.
|
|
970
|
-
if (!
|
|
905
|
+
if (!hasPlugins && fullResponse && !errorMessage && !responseCacheHit) {
|
|
971
906
|
const inputTokens = directUsage.received ? directUsage.inputTokens : Math.ceil(message.length / 4)
|
|
972
907
|
const outputTokens = directUsage.received ? directUsage.outputTokens : Math.ceil(fullResponse.length / 4)
|
|
973
908
|
const totalTokens = inputTokens + outputTokens
|
|
@@ -998,7 +933,7 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
998
933
|
? requestedToolNamesFromMessage(message)
|
|
999
934
|
: []
|
|
1000
935
|
const routingDecision = (!internal && source === 'chat')
|
|
1001
|
-
? routeTaskIntent(message,
|
|
936
|
+
? routeTaskIntent(message, pluginsForRun, appSettings)
|
|
1002
937
|
: null
|
|
1003
938
|
const calledNames = new Set((toolEvents || []).map((t) => t.name))
|
|
1004
939
|
|
|
@@ -1034,6 +969,9 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1034
969
|
if (requestedName === 'delegate_to_opencode_cli') {
|
|
1035
970
|
return { toolName: 'delegate', args: { ...rawArgs, backend: 'opencode' } }
|
|
1036
971
|
}
|
|
972
|
+
if (requestedName === 'delegate_to_gemini_cli') {
|
|
973
|
+
return { toolName: 'delegate', args: { ...rawArgs, backend: 'gemini' } }
|
|
974
|
+
}
|
|
1037
975
|
|
|
1038
976
|
const managePrefix = 'manage_'
|
|
1039
977
|
if (requestedName.startsWith(managePrefix) && requestedName !== 'manage_platform') {
|
|
@@ -1065,7 +1003,7 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1065
1003
|
return false
|
|
1066
1004
|
}
|
|
1067
1005
|
const agent = session.agentId ? loadAgents()[session.agentId] : null
|
|
1068
|
-
const { tools, cleanup } = await buildSessionTools(session.cwd, sessionForRun.tools || [], {
|
|
1006
|
+
const { tools, cleanup } = await buildSessionTools(session.cwd, sessionForRun.plugins || sessionForRun.tools || [], {
|
|
1069
1007
|
agentId: session.agentId || null,
|
|
1070
1008
|
sessionId,
|
|
1071
1009
|
platformAssignScope: agent?.platformAssignScope || 'self',
|
|
@@ -1109,10 +1047,11 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1109
1047
|
}
|
|
1110
1048
|
}
|
|
1111
1049
|
|
|
1112
|
-
const forcedDelegationTools:
|
|
1050
|
+
const forcedDelegationTools: DelegateTool[] = [
|
|
1113
1051
|
'delegate_to_claude_code',
|
|
1114
1052
|
'delegate_to_codex_cli',
|
|
1115
1053
|
'delegate_to_opencode_cli',
|
|
1054
|
+
'delegate_to_gemini_cli',
|
|
1116
1055
|
]
|
|
1117
1056
|
for (const toolName of forcedDelegationTools) {
|
|
1118
1057
|
if (!requestedToolNames.includes(toolName)) continue
|
|
@@ -1220,7 +1159,7 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1220
1159
|
}
|
|
1221
1160
|
|
|
1222
1161
|
const finalText = (fullResponse || '').trim() || (!internal && errorMessage ? `Error: ${errorMessage}` : '')
|
|
1223
|
-
const textForPersistence = stripMainLoopMetaForPersistence(finalText
|
|
1162
|
+
const textForPersistence = stripMainLoopMetaForPersistence(finalText)
|
|
1224
1163
|
|
|
1225
1164
|
// Emit status SSE event from [MAIN_LOOP_META] if present
|
|
1226
1165
|
if (internal && finalText) {
|
|
@@ -1391,24 +1330,16 @@ export async function executeSessionChatTurn(input: ExecuteChatTurnInput): Promi
|
|
|
1391
1330
|
}
|
|
1392
1331
|
}
|
|
1393
1332
|
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
internal,
|
|
1398
|
-
message: message,
|
|
1399
|
-
response: textForPersistence,
|
|
1400
|
-
now: Date.now(),
|
|
1401
|
-
})
|
|
1402
|
-
if (autoMemoryEligible) {
|
|
1403
|
-
const storedId = storeAutoMemoryNote({
|
|
1333
|
+
// Fire afterChatTurn hook for all enabled plugins (memory auto-save, logging, etc.)
|
|
1334
|
+
try {
|
|
1335
|
+
await getPluginManager().runHook('afterChatTurn', {
|
|
1404
1336
|
session: current,
|
|
1405
|
-
message
|
|
1337
|
+
message,
|
|
1406
1338
|
response: textForPersistence,
|
|
1407
1339
|
source,
|
|
1408
|
-
|
|
1340
|
+
internal,
|
|
1409
1341
|
})
|
|
1410
|
-
|
|
1411
|
-
}
|
|
1342
|
+
} catch { /* afterChatTurn hooks are non-critical */ }
|
|
1412
1343
|
|
|
1413
1344
|
// Don't extend idle timeout for heartbeat runs — only user-initiated activity counts
|
|
1414
1345
|
if (source !== 'heartbeat' && source !== 'heartbeat-wake' && source !== 'main-loop-followup') {
|
|
@@ -126,9 +126,9 @@ export function buildChatroomSystemPrompt(chatroom: Chatroom, agents: Record<str
|
|
|
126
126
|
.map((id) => {
|
|
127
127
|
const a = agents[id]
|
|
128
128
|
if (!a) return null
|
|
129
|
-
const
|
|
129
|
+
const plugins = (a.plugins || a.tools)?.length ? `Plugins: ${(a.plugins || a.tools)!.join(', ')}` : 'No specialized plugins'
|
|
130
130
|
const desc = a.description || a.soul || 'No description'
|
|
131
|
-
return `- **${a.name}**: ${desc}\n ${
|
|
131
|
+
return `- **${a.name}**: ${desc}\n ${plugins}`
|
|
132
132
|
})
|
|
133
133
|
.filter(Boolean)
|
|
134
134
|
.join('\n')
|
|
@@ -187,7 +187,7 @@ export function buildSyntheticSession(agent: Agent, chatroomId: string): Session
|
|
|
187
187
|
messages: [],
|
|
188
188
|
createdAt: Date.now(),
|
|
189
189
|
lastActiveAt: Date.now(),
|
|
190
|
-
|
|
190
|
+
plugins: agent.plugins || agent.tools || [],
|
|
191
191
|
agentId: agent.id,
|
|
192
192
|
}
|
|
193
193
|
}
|
|
@@ -622,7 +622,7 @@ async function handleConnectorCommand(params: {
|
|
|
622
622
|
const all = Array.isArray(session.messages) ? session.messages : []
|
|
623
623
|
const userCount = all.filter((m: { role?: string }) => m?.role === 'user').length
|
|
624
624
|
const assistantCount = all.filter((m: { role?: string }) => m?.role === 'assistant').length
|
|
625
|
-
const toolsCount = Array.isArray(session.
|
|
625
|
+
const toolsCount = Array.isArray(session.plugins) ? session.plugins.length : 0
|
|
626
626
|
const statusText = [
|
|
627
627
|
`Status for ${connector.platform} / ${connector.name}:`,
|
|
628
628
|
`- Agent: ${agentName}`,
|
|
@@ -645,7 +645,7 @@ async function handleConnectorCommand(params: {
|
|
|
645
645
|
session.claudeSessionId = null
|
|
646
646
|
session.codexThreadId = null
|
|
647
647
|
session.opencodeSessionId = null
|
|
648
|
-
session.delegateResumeIds = { claudeCode: null, codex: null, opencode: null }
|
|
648
|
+
session.delegateResumeIds = { claudeCode: null, codex: null, opencode: null, gemini: null }
|
|
649
649
|
session.lastActiveAt = Date.now()
|
|
650
650
|
persistSession(session)
|
|
651
651
|
return `Reset complete for ${connector.platform} channel thread. Cleared ${cleared} message(s).`
|
|
@@ -1000,7 +1000,7 @@ async function routeMessage(connector: Connector, msg: InboundMessage): Promise<
|
|
|
1000
1000
|
lastActiveAt: Date.now(),
|
|
1001
1001
|
sessionType: 'human' as const,
|
|
1002
1002
|
agentId: agent.id,
|
|
1003
|
-
|
|
1003
|
+
plugins: agent.plugins || agent.tools || [],
|
|
1004
1004
|
}
|
|
1005
1005
|
sessions[id] = session
|
|
1006
1006
|
saveSessions(sessions)
|
|
@@ -1148,7 +1148,7 @@ If media sending fails, report the exact error and retry with a corrected path/t
|
|
|
1148
1148
|
let fullText = ''
|
|
1149
1149
|
let mediaExtractionText = ''
|
|
1150
1150
|
let connectorToolDeliveredCurrentChannel = false
|
|
1151
|
-
const hasTools = session.
|
|
1151
|
+
const hasTools = session.plugins?.length && session.provider !== 'claude-cli'
|
|
1152
1152
|
console.log(`[connector] Routing message to agent "${agent.name}" (${agent.provider}/${agent.model}), hasTools=${!!hasTools}`)
|
|
1153
1153
|
|
|
1154
1154
|
if (hasTools) {
|
package/src/lib/server/cost.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Agent, UsageRecord } from '@/types'
|
|
1
|
+
import type { Agent, UsageRecord, PluginDefinitionCost } from '@/types'
|
|
2
|
+
import type { StructuredToolInterface } from '@langchain/core/tools'
|
|
2
3
|
import { loadSessions, loadUsage } from './storage'
|
|
3
4
|
|
|
4
5
|
// Model cost table: [inputCostPer1M, outputCostPer1M] in USD
|
|
@@ -65,6 +66,38 @@ export function getModelCosts(): Record<string, [number, number]> {
|
|
|
65
66
|
return { ...MODEL_COSTS }
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Estimate the number of tokens a tool definition occupies in the LLM context.
|
|
71
|
+
* Uses ~4 chars per token as a rough approximation.
|
|
72
|
+
*/
|
|
73
|
+
export function estimateToolDefinitionTokens(t: StructuredToolInterface): number {
|
|
74
|
+
let chars = (t.name || '').length + (t.description || '').length
|
|
75
|
+
try {
|
|
76
|
+
const schema = typeof t.schema === 'object' ? JSON.stringify(t.schema) : ''
|
|
77
|
+
chars += schema.length
|
|
78
|
+
} catch { /* ignore */ }
|
|
79
|
+
return Math.ceil(chars / 4)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Build per-plugin definition cost estimates from a set of tools and their plugin mapping.
|
|
84
|
+
*/
|
|
85
|
+
export function buildPluginDefinitionCosts(
|
|
86
|
+
tools: StructuredToolInterface[],
|
|
87
|
+
toolToPluginMap: Record<string, string>,
|
|
88
|
+
): PluginDefinitionCost[] {
|
|
89
|
+
const totals = new Map<string, number>()
|
|
90
|
+
for (const t of tools) {
|
|
91
|
+
const pluginId = toolToPluginMap[t.name] || '_unknown'
|
|
92
|
+
const tokens = estimateToolDefinitionTokens(t)
|
|
93
|
+
totals.set(pluginId, (totals.get(pluginId) || 0) + tokens)
|
|
94
|
+
}
|
|
95
|
+
return Array.from(totals.entries()).map(([pluginId, estimatedTokens]) => ({
|
|
96
|
+
pluginId,
|
|
97
|
+
estimatedTokens,
|
|
98
|
+
}))
|
|
99
|
+
}
|
|
100
|
+
|
|
68
101
|
export interface AgentSpendWindows {
|
|
69
102
|
hourly: number
|
|
70
103
|
daily: number
|
|
@@ -413,14 +413,14 @@ async function processWebhookRetries() {
|
|
|
413
413
|
claudeSessionId: null,
|
|
414
414
|
codexThreadId: null,
|
|
415
415
|
opencodeSessionId: null,
|
|
416
|
-
delegateResumeIds: { claudeCode: null, codex: null, opencode: null },
|
|
416
|
+
delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
|
|
417
417
|
messages: [],
|
|
418
418
|
createdAt: ts,
|
|
419
419
|
lastActiveAt: ts,
|
|
420
420
|
sessionType: 'orchestrated',
|
|
421
421
|
agentId: agent.id,
|
|
422
422
|
parentSessionId: null,
|
|
423
|
-
|
|
423
|
+
plugins: agent.plugins || agent.tools || [],
|
|
424
424
|
heartbeatEnabled: (agent.heartbeatEnabled as boolean | undefined) ?? true,
|
|
425
425
|
heartbeatIntervalSec: (agent.heartbeatIntervalSec as number | null | undefined) ?? null,
|
|
426
426
|
}
|
|
@@ -377,7 +377,7 @@ async function tickHeartbeats() {
|
|
|
377
377
|
|
|
378
378
|
for (const session of Object.values(sessions) as any[]) {
|
|
379
379
|
if (!session?.id) continue
|
|
380
|
-
if (!Array.isArray(session.
|
|
380
|
+
if (!Array.isArray(session.plugins) || session.plugins.length === 0) continue
|
|
381
381
|
if (session.sessionType && session.sessionType !== 'human' && session.sessionType !== 'orchestrated') continue
|
|
382
382
|
|
|
383
383
|
// Check if this session or its agent has explicit heartbeat opt-in
|