@swarmclawai/swarmclaw 1.2.6 → 1.2.8
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 +24 -17
- package/next.config.ts +1 -0
- package/package.json +3 -2
- package/scripts/easy-setup.mjs +1 -1
- package/scripts/postinstall.mjs +1 -1
- package/skills/swarmclaw.md +115 -0
- package/skills/tools/browser.md +131 -0
- package/skills/tools/execute.md +98 -0
- package/skills/tools/files.md +98 -0
- package/skills/tools/memory.md +104 -0
- package/skills/tools/platform.md +144 -0
- package/skills/tools/skills.md +83 -0
- package/src/app/api/chats/[id]/messages/route.ts +23 -19
- package/src/app/api/chats/messages-route.test.ts +105 -51
- package/src/app/api/mcp-servers/[id]/test/route.ts +3 -2
- package/src/app/api/openclaw/deploy/route.ts +2 -0
- package/src/app/api/setup/doctor/route.ts +4 -4
- package/src/components/agents/agent-chat-list.tsx +23 -1
- package/src/components/agents/inspector-panel.tsx +165 -48
- package/src/components/chat/chat-area.tsx +38 -9
- package/src/components/chat/message-list.tsx +33 -19
- package/src/components/gateways/gateway-sheet.tsx +5 -2
- package/src/lib/agent-execute-defaults.test.ts +24 -0
- package/src/lib/agent-execute-defaults.ts +62 -0
- package/src/lib/chat/queued-message-queue.test.ts +134 -1
- package/src/lib/chat/queued-message-queue.ts +77 -2
- package/src/lib/server/agents/agent-service.ts +5 -0
- package/src/lib/server/builtin-extensions.ts +1 -0
- package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +1 -1
- package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +1 -0
- package/src/lib/server/chat-execution/chat-execution-utils.ts +2 -2
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +79 -42
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -0
- package/src/lib/server/chat-execution/continuation-evaluator.ts +8 -0
- package/src/lib/server/chat-execution/memory-mutation-tools.ts +1 -1
- package/src/lib/server/chat-execution/message-classifier.ts +11 -1
- package/src/lib/server/chat-execution/prompt-builder.test.ts +28 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +14 -1
- package/src/lib/server/chat-execution/prompt-mode.test.ts +24 -0
- package/src/lib/server/chat-execution/prompt-mode.ts +5 -1
- package/src/lib/server/chat-execution/stream-agent-chat.test.ts +6 -4
- package/src/lib/server/chat-execution/stream-agent-chat.ts +45 -16
- package/src/lib/server/chatrooms/chatroom-routing.test.ts +4 -0
- package/src/lib/server/connectors/discord.ts +2 -2
- package/src/lib/server/connectors/matrix.ts +3 -2
- package/src/lib/server/connectors/signal.ts +5 -4
- package/src/lib/server/connectors/slack.ts +10 -9
- package/src/lib/server/connectors/teams.ts +3 -2
- package/src/lib/server/connectors/telegram.ts +4 -4
- package/src/lib/server/connectors/whatsapp.ts +2 -2
- package/src/lib/server/daemon/controller.ts +7 -0
- package/src/lib/server/gateways/gateway-profile-service.ts +19 -1
- package/src/lib/server/messages/message-repository.test.ts +70 -0
- package/src/lib/server/messages/message-repository.ts +11 -6
- package/src/lib/server/openclaw/deploy.ts +32 -2
- package/src/lib/server/plugins-advanced.test.ts +1 -2
- package/src/lib/server/provider-health.ts +1 -1
- package/src/lib/server/runtime/process-manager.ts +13 -9
- package/src/lib/server/runtime/session-run-manager/queries.ts +15 -0
- package/src/lib/server/runtime/session-run-manager.test.ts +58 -0
- package/src/lib/server/sandbox/session-runtime.test.ts +18 -1
- package/src/lib/server/sandbox/session-runtime.ts +40 -28
- package/src/lib/server/session-tools/autonomy-tools.test.ts +7 -9
- package/src/lib/server/session-tools/context.ts +1 -1
- package/src/lib/server/session-tools/credential-env.ts +109 -0
- package/src/lib/server/session-tools/crud.ts +3 -3
- package/src/lib/server/session-tools/edit_file.ts +3 -2
- package/src/lib/server/session-tools/execute.test.ts +58 -0
- package/src/lib/server/session-tools/execute.ts +334 -0
- package/src/lib/server/session-tools/files-tool.ts +635 -0
- package/src/lib/server/session-tools/index.ts +14 -4
- package/src/lib/server/session-tools/memory-tool.ts +242 -0
- package/src/lib/server/session-tools/memory.ts +1 -1
- package/src/lib/server/session-tools/openclaw-nodes.ts +3 -2
- package/src/lib/server/session-tools/openclaw-workspace.ts +3 -2
- package/src/lib/server/session-tools/platform-tool.ts +617 -0
- package/src/lib/server/session-tools/session-info.ts +3 -2
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +3 -4
- package/src/lib/server/session-tools/shell.ts +7 -122
- package/src/lib/server/session-tools/skills-tool.ts +396 -0
- package/src/lib/server/session-tools/web.ts +2 -2
- package/src/lib/server/storage-normalization.ts +2 -0
- package/src/lib/server/tool-aliases.ts +2 -1
- package/src/lib/server/tool-capability-policy-advanced.test.ts +9 -2
- package/src/lib/server/tool-capability-policy.test.ts +2 -1
- package/src/lib/server/tool-capability-policy.ts +60 -33
- package/src/lib/server/tool-planning.ts +11 -0
- package/src/lib/setup-defaults.ts +5 -0
- package/src/lib/tool-definitions.ts +1 -0
- package/src/lib/validation/schemas.test.ts +16 -0
- package/src/lib/validation/schemas.ts +16 -0
- package/src/stores/use-chat-store.test.ts +231 -0
- package/src/stores/use-chat-store.ts +62 -13
- package/src/types/agent.ts +348 -0
- package/src/types/app-settings.ts +175 -0
- package/src/types/approval.ts +27 -0
- package/src/types/connector.ts +187 -0
- package/src/types/extension.ts +386 -0
- package/src/types/index.ts +16 -3555
- package/src/types/message.ts +57 -0
- package/src/types/misc.ts +739 -0
- package/src/types/mission.ts +185 -0
- package/src/types/protocol.ts +422 -0
- package/src/types/provider.ts +52 -0
- package/src/types/run.ts +183 -0
- package/src/types/schedule.ts +59 -0
- package/src/types/session.ts +265 -0
- package/src/types/skill.ts +157 -0
- package/src/types/task.ts +140 -0
- package/src/types/working-state.ts +211 -0
- package/src/views/settings/section-heartbeat.tsx +2 -2
- package/src/lib/server/session-tools/sandbox.ts +0 -281
|
@@ -31,6 +31,7 @@ export const MessageClassificationSchema = z.object({
|
|
|
31
31
|
taskIntent: TaskIntentSchema,
|
|
32
32
|
isDeliverableTask: z.boolean(),
|
|
33
33
|
isBroadGoal: z.boolean(),
|
|
34
|
+
isLightweightDirectChat: z.boolean().optional().default(false),
|
|
34
35
|
walletIntent: z.enum(['none', 'read_only', 'transactional']),
|
|
35
36
|
hasHumanSignals: z.boolean(),
|
|
36
37
|
hasSignificantEvent: z.boolean(),
|
|
@@ -47,6 +48,7 @@ export interface MessageClassification {
|
|
|
47
48
|
taskIntent: MessageTaskIntent
|
|
48
49
|
isDeliverableTask: boolean
|
|
49
50
|
isBroadGoal: boolean
|
|
51
|
+
isLightweightDirectChat?: boolean
|
|
50
52
|
walletIntent: 'none' | 'read_only' | 'transactional'
|
|
51
53
|
hasHumanSignals: boolean
|
|
52
54
|
hasSignificantEvent: boolean
|
|
@@ -102,6 +104,7 @@ function buildClassificationPrompt(message: string, recentHistory: string): stri
|
|
|
102
104
|
'- taskIntent: The primary execution intent. Use exactly one of: "coding", "research", "browsing", "outreach", "scheduling", or "general". Choose "coding" for repo/code/build/debug/edit tasks. Choose "research" for gathering current info or synthesizing sources. Choose "browsing" for page navigation, rendered-page inspection, form work, or literal browser workflows. Choose "outreach" for sending/sharing/delivering updates to an external channel. Choose "scheduling" for reminders, recurring work, monitoring, or follow-up scheduling. Choose "general" when none of the above clearly fits.',
|
|
103
105
|
'- isDeliverableTask (bool): The user wants a concrete artifact produced — a document, report, plan, proposal, landing page, dashboard, HTML file, markdown file, brief, copy, screenshots, or similar deliverable. NOT simple Q&A, code fixes, or single-command tasks.',
|
|
104
106
|
'- isBroadGoal (bool): The message describes a broad, multi-step goal (50+ chars, no code blocks, no file paths, no numbered lists). Short questions ending with "?" are NOT broad goals.',
|
|
107
|
+
'- isLightweightDirectChat (bool): This is a low-signal direct chat turn that should get a natural lightweight reply, such as a greeting, acknowledgment, check-in, or simple social/direct question that does NOT require research, file work, planning, delegation, or tool execution.',
|
|
105
108
|
'- walletIntent: "none" if no crypto/wallet/trading context. "read_only" if mentioning wallet/crypto but only for checking balances, viewing transactions, or research. "transactional" if the user wants to swap, trade, buy, sell, mint, claim, deposit, withdraw, bridge, or execute a transaction.',
|
|
106
109
|
'- hasHumanSignals (bool): The message contains personal signals — preferences ("I prefer", "call me"), relationships ("my wife", "my partner", "my kid"), life events ("birthday", "wedding", "promotion", "moving", "graduation", "hospital"), or personal disclosures.',
|
|
107
110
|
'- hasSignificantEvent (bool): The message mentions a notable life/work event or milestone (birthday, anniversary, wedding, graduation, promotion, new job, relocation, illness, funeral, travel, house, deadline, launch).',
|
|
@@ -115,13 +118,14 @@ function buildClassificationPrompt(message: string, recentHistory: string): stri
|
|
|
115
118
|
'',
|
|
116
119
|
'Rules:',
|
|
117
120
|
'- Be conservative. When unsure, default to false/none/empty.',
|
|
121
|
+
'- Mark isLightweightDirectChat true only when a short natural reply is enough and escalating into planning, delegation, or tool execution would be unnecessary.',
|
|
118
122
|
'- A message can be both a deliverable task AND a broad goal.',
|
|
119
123
|
'- "walletIntent" should be "transactional" only if the user wants to execute a state-changing action, not just discuss crypto.',
|
|
120
124
|
'- For "explicitToolRequests", only include tools the user explicitly mentions by name or clear synonym. Do not infer tool needs from the task type.',
|
|
121
125
|
'- Prefer the most execution-relevant taskIntent. Example: "research this and send me a voice note" is "research", not "outreach".',
|
|
122
126
|
'',
|
|
123
127
|
'Output shape:',
|
|
124
|
-
'{"taskIntent":"coding|research|browsing|outreach|scheduling|general","isDeliverableTask":bool,"isBroadGoal":bool,"walletIntent":"none|read_only|transactional","hasHumanSignals":bool,"hasSignificantEvent":bool,"isResearchSynthesis":bool,"workType":"coding|research|writing|review|operations|general","wantsScreenshots":bool,"wantsOutboundDelivery":bool,"wantsVoiceDelivery":bool,"explicitToolRequests":[],"confidence":0.0-1.0}',
|
|
128
|
+
'{"taskIntent":"coding|research|browsing|outreach|scheduling|general","isDeliverableTask":bool,"isBroadGoal":bool,"isLightweightDirectChat":bool,"walletIntent":"none|read_only|transactional","hasHumanSignals":bool,"hasSignificantEvent":bool,"isResearchSynthesis":bool,"workType":"coding|research|writing|review|operations|general","wantsScreenshots":bool,"wantsOutboundDelivery":bool,"wantsVoiceDelivery":bool,"explicitToolRequests":[],"confidence":0.0-1.0}',
|
|
125
129
|
'',
|
|
126
130
|
recentHistory ? `Recent context:\n${recentHistory}\n` : '',
|
|
127
131
|
`User message: ${JSON.stringify(message)}`,
|
|
@@ -276,6 +280,7 @@ export function toMessageSemanticsSummary(classification: MessageClassification
|
|
|
276
280
|
isDeliverableTask: classification.isDeliverableTask,
|
|
277
281
|
isBroadGoal: classification.isBroadGoal,
|
|
278
282
|
isResearchSynthesis: classification.isResearchSynthesis,
|
|
283
|
+
isLightweightDirectChat: classification.isLightweightDirectChat === true,
|
|
279
284
|
hasHumanSignals: classification.hasHumanSignals,
|
|
280
285
|
hasSignificantEvent: classification.hasSignificantEvent,
|
|
281
286
|
wantsScreenshots: classification.wantsScreenshots === true,
|
|
@@ -324,3 +329,8 @@ export function isResearchSynthesis(classification: MessageClassification | null
|
|
|
324
329
|
void routingIntent
|
|
325
330
|
return classification?.isResearchSynthesis === true
|
|
326
331
|
}
|
|
332
|
+
|
|
333
|
+
export function isLightweightDirectChat(classification: MessageClassification | null, message?: string): boolean {
|
|
334
|
+
void message
|
|
335
|
+
return classification?.isLightweightDirectChat === true
|
|
336
|
+
}
|
|
@@ -26,4 +26,32 @@ describe('buildAgenticExecutionPolicy', () => {
|
|
|
26
26
|
assert.ok(prompt.includes('use the concrete tool now'))
|
|
27
27
|
assert.ok(prompt.includes('prefer the direct `manage_*` tool'))
|
|
28
28
|
})
|
|
29
|
+
|
|
30
|
+
it('adds lightweight direct-chat guidance when classification marks the turn as lightweight', () => {
|
|
31
|
+
const prompt = buildAgenticExecutionPolicy({
|
|
32
|
+
enabledExtensions: ['memory', 'files', 'delegate'],
|
|
33
|
+
loopMode: 'bounded',
|
|
34
|
+
heartbeatPrompt: 'HEARTBEAT',
|
|
35
|
+
heartbeatIntervalSec: 120,
|
|
36
|
+
userMessage: 'Hello',
|
|
37
|
+
history: [],
|
|
38
|
+
classification: {
|
|
39
|
+
taskIntent: 'general',
|
|
40
|
+
isDeliverableTask: false,
|
|
41
|
+
isBroadGoal: false,
|
|
42
|
+
isLightweightDirectChat: true,
|
|
43
|
+
walletIntent: 'none',
|
|
44
|
+
hasHumanSignals: false,
|
|
45
|
+
hasSignificantEvent: false,
|
|
46
|
+
isResearchSynthesis: false,
|
|
47
|
+
workType: 'general',
|
|
48
|
+
explicitToolRequests: [],
|
|
49
|
+
confidence: 0.98,
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
assert.ok(prompt.includes('## Lightweight Chat'))
|
|
54
|
+
assert.ok(prompt.includes('Reply naturally and briefly.'))
|
|
55
|
+
assert.ok(prompt.includes('prefer 1-3 short sentences'))
|
|
56
|
+
})
|
|
29
57
|
})
|
|
@@ -75,7 +75,6 @@ function buildExtensionCapabilityLines(enabledExtensions: string[], opts?: { del
|
|
|
75
75
|
|
|
76
76
|
const DISPLAY_TOOL_ALIASES: Record<string, string[]> = {
|
|
77
77
|
files: ['send_file'],
|
|
78
|
-
shell: ['sandbox_exec', 'sandbox_list_runtimes'],
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
function buildExactToolNameList(enabledExtensions: string[]): string[] {
|
|
@@ -158,6 +157,7 @@ export function buildToolDisciplineLines(enabledExtensions: string[]): string[]
|
|
|
158
157
|
...(researchSearchTools.length || researchFetchTools.length ? [...researchSearchTools, ...researchFetchTools] : []),
|
|
159
158
|
...httpTools,
|
|
160
159
|
...(uniqueTools.includes('shell') ? ['shell'] : []),
|
|
160
|
+
...(uniqueTools.includes('execute') ? ['execute'] : []),
|
|
161
161
|
...(uniqueTools.includes('browser') ? ['browser'] : []),
|
|
162
162
|
]))
|
|
163
163
|
if (alternateResearchTools.length >= 2) {
|
|
@@ -330,6 +330,7 @@ export function buildAgenticExecutionPolicy(opts: {
|
|
|
330
330
|
const hasManageSessions = opts.enabledExtensions.some((toolId) => (canonicalizeExtensionId(toolId) || toolId) === 'manage_sessions')
|
|
331
331
|
const hasManageTasks = opts.enabledExtensions.some((toolId) => (canonicalizeExtensionId(toolId) || toolId) === 'manage_tasks')
|
|
332
332
|
const hasManageSkills = opts.enabledExtensions.some((toolId) => (canonicalizeExtensionId(toolId) || toolId) === 'manage_skills')
|
|
333
|
+
const lightweightDirectChat = opts.classification?.isLightweightDirectChat === true && !opts.isDirectConnectorSession
|
|
333
334
|
const hasDelegationTools = opts.enabledExtensions.some((toolId) => {
|
|
334
335
|
const canonical = canonicalizeExtensionId(toolId) || toolId
|
|
335
336
|
return canonical === 'delegate' || canonical === 'spawn_subagent'
|
|
@@ -359,6 +360,15 @@ export function buildAgenticExecutionPolicy(opts: {
|
|
|
359
360
|
: 'Loop: BOUNDED — execute multiple steps but finish within recursion budget.',
|
|
360
361
|
)
|
|
361
362
|
|
|
363
|
+
if (lightweightDirectChat) {
|
|
364
|
+
parts.push(
|
|
365
|
+
'## Lightweight Chat',
|
|
366
|
+
'This turn is a lightweight direct chat. Reply naturally and briefly.',
|
|
367
|
+
'Do not delegate, create tasks, outline a workflow, or narrate tools unless the user adds a concrete task that actually requires that escalation.',
|
|
368
|
+
'For greetings, acknowledgements, and simple social questions, a short human-sounding answer is sufficient.',
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
|
|
362
372
|
if (hasTooling) {
|
|
363
373
|
parts.push(
|
|
364
374
|
'## Routing Matrix',
|
|
@@ -444,6 +454,9 @@ export function buildAgenticExecutionPolicy(opts: {
|
|
|
444
454
|
]),
|
|
445
455
|
'Keep responses concise. Bullet points over prose. After file operations, confirm the result briefly (path and status) without echoing the full file contents.',
|
|
446
456
|
'Do not end every reply with a question. Only ask when a specific missing detail blocks progress. When a task is done, state the result and stop.',
|
|
457
|
+
...(lightweightDirectChat
|
|
458
|
+
? ['For this turn, prefer 1-3 short sentences over bullets, planning, or process narration.']
|
|
459
|
+
: []),
|
|
447
460
|
opts.responseStyle === 'concise'
|
|
448
461
|
? `IMPORTANT: Be extremely concise.${opts.responseMaxChars ? ` Keep responses under ${opts.responseMaxChars} characters.` : ' Target under 500 characters.'} Lead with the answer, skip preamble.`
|
|
449
462
|
: opts.responseStyle === 'detailed'
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it } from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { resolvePromptMode } from '@/lib/server/chat-execution/prompt-mode'
|
|
5
|
+
|
|
6
|
+
describe('resolvePromptMode', () => {
|
|
7
|
+
it('returns full for root sessions by default', () => {
|
|
8
|
+
assert.equal(resolvePromptMode({ id: 'root' } as never), 'full')
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('prefers minimal mode for lightweight direct-chat turns', () => {
|
|
12
|
+
assert.equal(
|
|
13
|
+
resolvePromptMode({ id: 'root' } as never, { preferMinimalPrompt: true }),
|
|
14
|
+
'minimal',
|
|
15
|
+
)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('keeps delegated child sessions in minimal mode', () => {
|
|
19
|
+
assert.equal(
|
|
20
|
+
resolvePromptMode({ id: 'child', parentSessionId: 'parent' } as never, { preferMinimalPrompt: false }),
|
|
21
|
+
'minimal',
|
|
22
|
+
)
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -20,7 +20,11 @@ export type PromptMode = 'full' | 'minimal' | 'none'
|
|
|
20
20
|
* proactive memory, thinking guidance
|
|
21
21
|
* - `none` — reserved for bare identity (light heartbeat path)
|
|
22
22
|
*/
|
|
23
|
-
export function resolvePromptMode(
|
|
23
|
+
export function resolvePromptMode(
|
|
24
|
+
session: Session,
|
|
25
|
+
options?: { preferMinimalPrompt?: boolean },
|
|
26
|
+
): PromptMode {
|
|
24
27
|
if (session.parentSessionId) return 'minimal'
|
|
28
|
+
if (options?.preferMinimalPrompt) return 'minimal'
|
|
25
29
|
return 'full'
|
|
26
30
|
}
|
|
@@ -65,14 +65,13 @@ const streamContinuationSource = _readSibling('stream-continuation.ts')
|
|
|
65
65
|
const streamSources = `${streamAgentChatSource}\n${streamContinuationSource}`
|
|
66
66
|
|
|
67
67
|
describe('buildToolDisciplineLines', () => {
|
|
68
|
-
it('lists exact callable tool names for
|
|
68
|
+
it('lists exact callable tool names for legacy sandbox aliases and browser', () => {
|
|
69
69
|
const lines = buildToolAvailabilityLines(['sandbox', 'browser', 'manage_schedules'])
|
|
70
70
|
|
|
71
71
|
assert.equal(lines[0], 'Tool names are case-sensitive. Call tools exactly as listed.')
|
|
72
72
|
assert.ok(lines.includes('- `browser`'))
|
|
73
|
+
assert.ok(lines.includes('- `execute`'))
|
|
73
74
|
assert.ok(lines.includes('- `manage_schedules`'))
|
|
74
|
-
assert.ok(lines.includes('- `sandbox_exec`'))
|
|
75
|
-
assert.ok(lines.includes('- `sandbox_list_runtimes`'))
|
|
76
75
|
})
|
|
77
76
|
|
|
78
77
|
it('tells the agent to use direct platform tools when manage_platform is absent', () => {
|
|
@@ -1033,7 +1032,7 @@ describe('shouldForceDeliverableFollowthrough', () => {
|
|
|
1033
1032
|
{ name: 'web', input: '{"action":"fetch","url":"https://example.com/topic"}', output: '<html>topic</html>' },
|
|
1034
1033
|
],
|
|
1035
1034
|
history: [
|
|
1036
|
-
{ role: 'user', text: 'Research 3 topics, take screenshots, write markdown and PDF files, then build a site for each topic.' },
|
|
1035
|
+
{ role: 'user', text: 'Research 3 topics, take screenshots, write markdown and PDF files, then build a site for each topic.', time: Date.now() },
|
|
1037
1036
|
],
|
|
1038
1037
|
}),
|
|
1039
1038
|
true,
|
|
@@ -1275,6 +1274,7 @@ describe('parseClassificationResponse', () => {
|
|
|
1275
1274
|
|
|
1276
1275
|
describe('message classifier adapter functions', () => {
|
|
1277
1276
|
const deliverableClassification: MessageClassification = {
|
|
1277
|
+
taskIntent: 'general',
|
|
1278
1278
|
isDeliverableTask: true,
|
|
1279
1279
|
isBroadGoal: true,
|
|
1280
1280
|
walletIntent: 'none',
|
|
@@ -1286,6 +1286,7 @@ describe('message classifier adapter functions', () => {
|
|
|
1286
1286
|
}
|
|
1287
1287
|
|
|
1288
1288
|
const walletClassification: MessageClassification = {
|
|
1289
|
+
taskIntent: 'general',
|
|
1289
1290
|
isDeliverableTask: false,
|
|
1290
1291
|
isBroadGoal: false,
|
|
1291
1292
|
walletIntent: 'transactional',
|
|
@@ -1297,6 +1298,7 @@ describe('message classifier adapter functions', () => {
|
|
|
1297
1298
|
}
|
|
1298
1299
|
|
|
1299
1300
|
const humanSignalClassification: MessageClassification = {
|
|
1301
|
+
taskIntent: 'general',
|
|
1300
1302
|
isDeliverableTask: false,
|
|
1301
1303
|
isBroadGoal: false,
|
|
1302
1304
|
walletIntent: 'none',
|
|
@@ -190,6 +190,7 @@ interface StreamAgentChatOpts {
|
|
|
190
190
|
fallbackCredentialIds?: string[]
|
|
191
191
|
signal?: AbortSignal
|
|
192
192
|
promptMode?: PromptMode
|
|
193
|
+
classification?: MessageClassification | null
|
|
193
194
|
/** Run source (e.g. 'heartbeat', 'chat', 'scheduler') — used for heartbeat-specific tuning. */
|
|
194
195
|
source?: string
|
|
195
196
|
}
|
|
@@ -223,10 +224,23 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
223
224
|
|
|
224
225
|
async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAgentChatResult> {
|
|
225
226
|
const startTs = Date.now()
|
|
226
|
-
const {
|
|
227
|
+
const {
|
|
228
|
+
session,
|
|
229
|
+
message,
|
|
230
|
+
imagePath,
|
|
231
|
+
imageUrl,
|
|
232
|
+
attachedFiles,
|
|
233
|
+
apiKey,
|
|
234
|
+
systemPrompt,
|
|
235
|
+
executionBrief,
|
|
236
|
+
extraSystemContext,
|
|
237
|
+
write,
|
|
238
|
+
history,
|
|
239
|
+
fallbackCredentialIds,
|
|
240
|
+
signal,
|
|
241
|
+
classification: providedClassification,
|
|
242
|
+
} = opts
|
|
227
243
|
const isHeartbeat = isHeartbeatSource(opts.source)
|
|
228
|
-
const promptMode: PromptMode = opts.promptMode ?? resolvePromptMode(session)
|
|
229
|
-
const isMinimalPrompt = promptMode === 'minimal'
|
|
230
244
|
const isConnectorSession = isDirectConnectorSession(session)
|
|
231
245
|
const rawExtensions = getEnabledCapabilityIds(session)
|
|
232
246
|
const hasShellCapability = rawExtensions.some((toolId) => ['shell', 'execute_command'].includes(String(toolId)))
|
|
@@ -241,6 +255,23 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
241
255
|
|
|
242
256
|
const sessionAgent = session.agentId ? getAgent(session.agentId) : null
|
|
243
257
|
|
|
258
|
+
const classificationPromise = providedClassification !== undefined
|
|
259
|
+
? Promise.resolve(providedClassification)
|
|
260
|
+
: classifyMessage({
|
|
261
|
+
sessionId: session.id,
|
|
262
|
+
agentId: session.agentId,
|
|
263
|
+
message,
|
|
264
|
+
history,
|
|
265
|
+
}).catch(() => null as MessageClassification | null)
|
|
266
|
+
const classification = await classificationPromise
|
|
267
|
+
const lightweightDirectChat = classification?.isLightweightDirectChat === true
|
|
268
|
+
&& !isConnectorSession
|
|
269
|
+
&& !isHeartbeat
|
|
270
|
+
const promptMode: PromptMode = opts.promptMode ?? resolvePromptMode(session, {
|
|
271
|
+
preferMinimalPrompt: lightweightDirectChat,
|
|
272
|
+
})
|
|
273
|
+
const isMinimalPrompt = promptMode === 'minimal'
|
|
274
|
+
|
|
244
275
|
// Resolve agent's thinking level for provider-native params
|
|
245
276
|
let agentThinkingLevel: 'minimal' | 'low' | 'medium' | 'high' | undefined
|
|
246
277
|
if (session.thinkingLevel) {
|
|
@@ -248,6 +279,9 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
248
279
|
} else if (sessionAgent) {
|
|
249
280
|
agentThinkingLevel = sessionAgent.thinkingLevel
|
|
250
281
|
}
|
|
282
|
+
if (lightweightDirectChat) {
|
|
283
|
+
agentThinkingLevel = 'minimal'
|
|
284
|
+
}
|
|
251
285
|
|
|
252
286
|
const llm = buildChatModel({
|
|
253
287
|
provider: session.provider,
|
|
@@ -296,16 +330,6 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
296
330
|
return Math.max(0, Math.min(3600, Math.trunc(parsed)))
|
|
297
331
|
})()
|
|
298
332
|
|
|
299
|
-
// -------------------------------------------------------------------------
|
|
300
|
-
// Start message classification in the background (LLM-based, ~200-800ms)
|
|
301
|
-
// -------------------------------------------------------------------------
|
|
302
|
-
const classificationPromise = classifyMessage({
|
|
303
|
-
sessionId: session.id,
|
|
304
|
-
agentId: session.agentId,
|
|
305
|
-
message,
|
|
306
|
-
history,
|
|
307
|
-
}).catch(() => null as MessageClassification | null)
|
|
308
|
-
|
|
309
333
|
// -------------------------------------------------------------------------
|
|
310
334
|
// System prompt assembly (stays inline — many async calls + local state)
|
|
311
335
|
// -------------------------------------------------------------------------
|
|
@@ -426,8 +450,6 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
426
450
|
const suggestionsBlock = buildSuggestionsSection(settings.suggestionsEnabled, isMinimalPrompt)
|
|
427
451
|
if (suggestionsBlock) promptParts.push(suggestionsBlock)
|
|
428
452
|
|
|
429
|
-
// Await classification before building the agentic execution policy
|
|
430
|
-
const classification = await classificationPromise
|
|
431
453
|
const delegationAdvisory = sessionAgent && agentDelegationEnabled
|
|
432
454
|
? resolveDelegationAdvisory({
|
|
433
455
|
currentAgent: sessionAgent,
|
|
@@ -1138,7 +1160,14 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
|
|
|
1138
1160
|
}
|
|
1139
1161
|
|
|
1140
1162
|
// Async LLM-based incomplete-action check: catches "I'll run the deployment:" with no tool calls
|
|
1141
|
-
if (
|
|
1163
|
+
if (
|
|
1164
|
+
!shouldContinue
|
|
1165
|
+
&& outcome
|
|
1166
|
+
&& !state.hasToolCalls
|
|
1167
|
+
&& classification?.isLightweightDirectChat !== true
|
|
1168
|
+
&& state.fullText.trim().length > 0
|
|
1169
|
+
&& state.fullText.trim().length < 500
|
|
1170
|
+
) {
|
|
1142
1171
|
const completeness = await evaluateResponseCompleteness({
|
|
1143
1172
|
sessionId: session.id,
|
|
1144
1173
|
agentId: session.agentId,
|
|
@@ -16,6 +16,8 @@ const agents: Record<string, Agent> = {
|
|
|
16
16
|
model: 'gpt-test',
|
|
17
17
|
systemPrompt: '',
|
|
18
18
|
capabilities: ['deploy', 'infrastructure'],
|
|
19
|
+
createdAt: Date.now(),
|
|
20
|
+
updatedAt: Date.now(),
|
|
19
21
|
},
|
|
20
22
|
design: {
|
|
21
23
|
id: 'design',
|
|
@@ -25,6 +27,8 @@ const agents: Record<string, Agent> = {
|
|
|
25
27
|
model: 'gpt-test',
|
|
26
28
|
systemPrompt: '',
|
|
27
29
|
capabilities: ['design', 'ui'],
|
|
30
|
+
createdAt: Date.now(),
|
|
31
|
+
updatedAt: Date.now(),
|
|
28
32
|
},
|
|
29
33
|
}
|
|
30
34
|
|
|
@@ -217,8 +217,8 @@ const discord: PlatformConnector = {
|
|
|
217
217
|
return String(sent.id || '')
|
|
218
218
|
},
|
|
219
219
|
})
|
|
220
|
-
} catch (err:
|
|
221
|
-
log.error(TAG, 'Error handling message:', err
|
|
220
|
+
} catch (err: unknown) {
|
|
221
|
+
log.error(TAG, 'Error handling message:', errorMessage(err))
|
|
222
222
|
try {
|
|
223
223
|
await message.reply('Sorry, I encountered an error processing your message.')
|
|
224
224
|
} catch { /* ignore */ }
|
|
@@ -4,6 +4,7 @@ import path from 'path'
|
|
|
4
4
|
import { DATA_DIR } from '../data-dir'
|
|
5
5
|
import type { PlatformConnector, ConnectorInstance, InboundMessage } from './types'
|
|
6
6
|
import { resolveConnectorIngressReply } from './ingress-delivery'
|
|
7
|
+
import { errorMessage } from '@/lib/shared-utils'
|
|
7
8
|
|
|
8
9
|
const TAG = 'matrix'
|
|
9
10
|
|
|
@@ -54,8 +55,8 @@ const matrix: PlatformConnector = {
|
|
|
54
55
|
const reply = await resolveConnectorIngressReply(onMessage, inbound)
|
|
55
56
|
if (!reply) return
|
|
56
57
|
await client.sendText(roomId, reply.visibleText)
|
|
57
|
-
} catch (err:
|
|
58
|
-
log.error(TAG, 'Error handling message:', err
|
|
58
|
+
} catch (err: unknown) {
|
|
59
|
+
log.error(TAG, 'Error handling message:', errorMessage(err))
|
|
59
60
|
try {
|
|
60
61
|
await client.sendText(roomId, 'Sorry, I encountered an error processing your message.')
|
|
61
62
|
} catch { /* ignore */ }
|
|
@@ -4,6 +4,7 @@ import type { ChildProcess } from 'child_process'
|
|
|
4
4
|
import type { Connector } from '@/types'
|
|
5
5
|
import type { PlatformConnector, ConnectorInstance, InboundMessage, ConnectorIngressResult } from './types'
|
|
6
6
|
import { resolveConnectorIngressReply } from './ingress-delivery'
|
|
7
|
+
import { errorMessage } from '@/lib/shared-utils'
|
|
7
8
|
|
|
8
9
|
const TAG = 'signal'
|
|
9
10
|
|
|
@@ -107,8 +108,8 @@ const signal: PlatformConnector = {
|
|
|
107
108
|
`${cliPath} -u ${phoneNumber} send -m ${JSON.stringify(text)} ${channelId}`,
|
|
108
109
|
{ timeout: 15_000 },
|
|
109
110
|
)
|
|
110
|
-
} catch (err:
|
|
111
|
-
throw new Error(`Signal send failed: ${err
|
|
111
|
+
} catch (err: unknown) {
|
|
112
|
+
throw new Error(`Signal send failed: ${errorMessage(err)}`)
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
},
|
|
@@ -179,8 +180,8 @@ export async function handleSignalEvent(
|
|
|
179
180
|
{ timeout: 15_000 },
|
|
180
181
|
)
|
|
181
182
|
}
|
|
182
|
-
} catch (err:
|
|
183
|
-
log.error(TAG, 'Error handling message:', err
|
|
183
|
+
} catch (err: unknown) {
|
|
184
|
+
log.error(TAG, 'Error handling message:', errorMessage(err))
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
187
|
|
|
@@ -145,11 +145,12 @@ const slack: PlatformConnector = {
|
|
|
145
145
|
}
|
|
146
146
|
botUserId = auth.user_id as string
|
|
147
147
|
log.info(TAG, `Authenticated as @${auth.user} in workspace "${auth.team}"`)
|
|
148
|
-
} catch (err:
|
|
149
|
-
const hint = err.code
|
|
148
|
+
} catch (err: unknown) {
|
|
149
|
+
const hint = (err instanceof Error && 'code' in err) ? (err as { code: string }).code : undefined
|
|
150
|
+
const suffix = hint === 'slack_webapi_platform_error'
|
|
150
151
|
? '. Check that your Bot Token (xoxb-...) is correct and the app is installed to the workspace.'
|
|
151
152
|
: ''
|
|
152
|
-
throw new Error(`Slack auth failed: ${err
|
|
153
|
+
throw new Error(`Slack auth failed: ${errorMessage(err)}${suffix}`)
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
const app = new App({
|
|
@@ -215,8 +216,8 @@ const slack: PlatformConnector = {
|
|
|
215
216
|
media.push(stored)
|
|
216
217
|
continue
|
|
217
218
|
}
|
|
218
|
-
} catch (err:
|
|
219
|
-
log.warn(TAG, `Media download failed (${f?.name || 'file'}):`,
|
|
219
|
+
} catch (err: unknown) {
|
|
220
|
+
log.warn(TAG, `Media download failed (${f?.name || 'file'}):`, errorMessage(err))
|
|
220
221
|
}
|
|
221
222
|
}
|
|
222
223
|
media.push({
|
|
@@ -264,8 +265,8 @@ const slack: PlatformConnector = {
|
|
|
264
265
|
return sent.ts || undefined
|
|
265
266
|
},
|
|
266
267
|
})
|
|
267
|
-
} catch (err:
|
|
268
|
-
log.error(TAG, 'Error handling message:', err
|
|
268
|
+
} catch (err: unknown) {
|
|
269
|
+
log.error(TAG, 'Error handling message:', errorMessage(err))
|
|
269
270
|
try {
|
|
270
271
|
await say('Sorry, I encountered an error processing your message.')
|
|
271
272
|
} catch { /* ignore */ }
|
|
@@ -322,8 +323,8 @@ const slack: PlatformConnector = {
|
|
|
322
323
|
return sent.ts || undefined
|
|
323
324
|
},
|
|
324
325
|
})
|
|
325
|
-
} catch (err:
|
|
326
|
-
log.error(TAG, 'Error handling mention:', err
|
|
326
|
+
} catch (err: unknown) {
|
|
327
|
+
log.error(TAG, 'Error handling mention:', errorMessage(err))
|
|
327
328
|
}
|
|
328
329
|
})
|
|
329
330
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { log } from '@/lib/server/logger'
|
|
2
2
|
import type { PlatformConnector, ConnectorInstance, InboundMessage } from './types'
|
|
3
3
|
import { resolveConnectorIngressReply } from './ingress-delivery'
|
|
4
|
+
import { errorMessage } from '@/lib/shared-utils'
|
|
4
5
|
|
|
5
6
|
const TAG = 'teams'
|
|
6
7
|
|
|
@@ -51,8 +52,8 @@ const teams: PlatformConnector = {
|
|
|
51
52
|
const reply = await resolveConnectorIngressReply(onMessage, inbound)
|
|
52
53
|
if (!reply) return
|
|
53
54
|
await context.sendActivity(reply.visibleText)
|
|
54
|
-
} catch (err:
|
|
55
|
-
log.error(TAG, 'Error handling message:', err
|
|
55
|
+
} catch (err: unknown) {
|
|
56
|
+
log.error(TAG, 'Error handling message:', errorMessage(err))
|
|
56
57
|
try {
|
|
57
58
|
await context.sendActivity('Sorry, I encountered an error processing your message.')
|
|
58
59
|
} catch { /* ignore */ }
|
|
@@ -127,8 +127,8 @@ const telegram: PlatformConnector = {
|
|
|
127
127
|
mimeType: m.mimeType,
|
|
128
128
|
})
|
|
129
129
|
if (stored) media.push(stored)
|
|
130
|
-
} catch (err:
|
|
131
|
-
log.warn(TAG, `Failed to fetch media ${m.fileId}:`,
|
|
130
|
+
} catch (err: unknown) {
|
|
131
|
+
log.warn(TAG, `Failed to fetch media ${m.fileId}:`, errorMessage(err))
|
|
132
132
|
media.push({
|
|
133
133
|
type: m.type,
|
|
134
134
|
fileName: m.fileName,
|
|
@@ -177,8 +177,8 @@ const telegram: PlatformConnector = {
|
|
|
177
177
|
return String(sent.message_id)
|
|
178
178
|
},
|
|
179
179
|
})
|
|
180
|
-
} catch (err:
|
|
181
|
-
log.error(TAG, 'Error handling message:', err
|
|
180
|
+
} catch (err: unknown) {
|
|
181
|
+
log.error(TAG, 'Error handling message:', errorMessage(err))
|
|
182
182
|
try {
|
|
183
183
|
await ctx.reply('Sorry, I encountered an error processing your message.')
|
|
184
184
|
} catch { /* ignore */ }
|
|
@@ -714,8 +714,8 @@ const whatsapp: PlatformConnector = {
|
|
|
714
714
|
fileName: mediaCandidate.payload?.fileName || undefined,
|
|
715
715
|
})
|
|
716
716
|
media.push(saved)
|
|
717
|
-
} catch (err:
|
|
718
|
-
log.error(TAG, `Failed to decode media: ${
|
|
717
|
+
} catch (err: unknown) {
|
|
718
|
+
log.error(TAG, `Failed to decode media: ${errorMessage(err)}`)
|
|
719
719
|
media.push({
|
|
720
720
|
type: mediaCandidate.kind,
|
|
721
721
|
fileName: mediaCandidate.payload?.fileName || undefined,
|
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
} from '@/lib/server/daemon/types'
|
|
26
26
|
import { DATA_DIR } from '@/lib/server/data-dir'
|
|
27
27
|
import { loadEstopState } from '@/lib/server/runtime/estop'
|
|
28
|
+
import { getDaemonStatus } from '@/lib/server/runtime/daemon-state/core'
|
|
28
29
|
import { daemonAutostartEnvEnabled } from '@/lib/server/runtime/daemon-policy'
|
|
29
30
|
import {
|
|
30
31
|
releaseRuntimeLock,
|
|
@@ -344,6 +345,12 @@ export async function ensureDaemonProcessRunning(
|
|
|
344
345
|
source: string,
|
|
345
346
|
opts?: { manualStart?: boolean },
|
|
346
347
|
): Promise<boolean> {
|
|
348
|
+
// In dev mode, the daemon may already be running in-process (same Next.js server)
|
|
349
|
+
// without a daemon-admin.json file. Check in-process state first to avoid spawning
|
|
350
|
+
// a subprocess that fails to acquire the already-held lease.
|
|
351
|
+
const inProcessStatus = getDaemonStatus()
|
|
352
|
+
if (inProcessStatus.running) return false
|
|
353
|
+
|
|
347
354
|
const manualStart = opts?.manualStart === true
|
|
348
355
|
const record = loadDaemonStatusRecord()
|
|
349
356
|
if (loadEstopState().level !== 'none') return false
|
|
@@ -4,6 +4,7 @@ import { genId } from '@/lib/id'
|
|
|
4
4
|
import { normalizeOpenClawEndpoint } from '@/lib/openclaw/openclaw-endpoint'
|
|
5
5
|
import { listAgents, saveAgentMany } from '@/lib/server/agents/agent-repository'
|
|
6
6
|
import { getGatewayProfiles } from '@/lib/server/agents/agent-runtime-config'
|
|
7
|
+
import { deleteCredentialRecord } from '@/lib/server/credentials/credential-service'
|
|
7
8
|
import {
|
|
8
9
|
loadGatewayProfile,
|
|
9
10
|
loadGatewayProfiles,
|
|
@@ -161,7 +162,9 @@ export function updateGatewayProfile(id: string, input: Record<string, unknown>)
|
|
|
161
162
|
|
|
162
163
|
export function deleteGatewayProfileAndDetachAgents(id: string): boolean {
|
|
163
164
|
const gateways = loadGatewayProfiles()
|
|
164
|
-
|
|
165
|
+
const deleted = gateways[id]
|
|
166
|
+
if (!deleted) return false
|
|
167
|
+
const orphanCredentialId = deleted.credentialId || null
|
|
165
168
|
delete gateways[id]
|
|
166
169
|
saveGatewayProfiles(gateways)
|
|
167
170
|
|
|
@@ -195,6 +198,21 @@ export function deleteGatewayProfileAndDetachAgents(id: string): boolean {
|
|
|
195
198
|
}
|
|
196
199
|
|
|
197
200
|
if (changed.length > 0) saveAgentMany(changed)
|
|
201
|
+
|
|
202
|
+
// Clean up orphaned credential if no other gateway or agent references it
|
|
203
|
+
if (orphanCredentialId) {
|
|
204
|
+
const stillReferencedByGateway = Object.values(gateways).some(
|
|
205
|
+
(gw) => gw && gw.credentialId === orphanCredentialId,
|
|
206
|
+
)
|
|
207
|
+
const stillReferencedByAgent = !stillReferencedByGateway && Object.values(agents).some(
|
|
208
|
+
(a) => a.credentialId === orphanCredentialId
|
|
209
|
+
|| (Array.isArray(a.fallbackCredentialIds) && a.fallbackCredentialIds.includes(orphanCredentialId)),
|
|
210
|
+
)
|
|
211
|
+
if (!stillReferencedByGateway && !stillReferencedByAgent) {
|
|
212
|
+
deleteCredentialRecord(orphanCredentialId)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
198
216
|
notify('gateways')
|
|
199
217
|
return true
|
|
200
218
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test from 'node:test'
|
|
3
|
+
import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
|
|
4
|
+
|
|
5
|
+
test('appendMessage notifies both generic and per-session message topics', () => {
|
|
6
|
+
const output = runWithTempDataDir<{
|
|
7
|
+
genericTopics: string[]
|
|
8
|
+
sessionTopics: string[]
|
|
9
|
+
}>(`
|
|
10
|
+
const { WebSocket } = await import('ws')
|
|
11
|
+
const storageMod = await import('@/lib/server/storage')
|
|
12
|
+
const repoMod = await import('@/lib/server/messages/message-repository')
|
|
13
|
+
const storage = storageMod.default || storageMod
|
|
14
|
+
const repo = repoMod.default || repoMod
|
|
15
|
+
|
|
16
|
+
storage.saveSessions({
|
|
17
|
+
'sess-notify': {
|
|
18
|
+
id: 'sess-notify',
|
|
19
|
+
name: 'Notify Session',
|
|
20
|
+
cwd: process.env.WORKSPACE_DIR,
|
|
21
|
+
user: 'tester',
|
|
22
|
+
provider: 'openai',
|
|
23
|
+
model: 'gpt-5',
|
|
24
|
+
claudeSessionId: null,
|
|
25
|
+
codexThreadId: null,
|
|
26
|
+
opencodeSessionId: null,
|
|
27
|
+
delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
|
|
28
|
+
messages: [],
|
|
29
|
+
createdAt: Date.now(),
|
|
30
|
+
lastActiveAt: Date.now(),
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const genericPayloads = []
|
|
35
|
+
const sessionPayloads = []
|
|
36
|
+
globalThis.__swarmclaw_ws__ = {
|
|
37
|
+
wss: null,
|
|
38
|
+
clients: new Set([
|
|
39
|
+
{
|
|
40
|
+
ws: {
|
|
41
|
+
readyState: WebSocket.OPEN,
|
|
42
|
+
send(payload) { genericPayloads.push(JSON.parse(payload)) },
|
|
43
|
+
},
|
|
44
|
+
topics: new Set(['messages']),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
ws: {
|
|
48
|
+
readyState: WebSocket.OPEN,
|
|
49
|
+
send(payload) { sessionPayloads.push(JSON.parse(payload)) },
|
|
50
|
+
},
|
|
51
|
+
topics: new Set(['messages:sess-notify']),
|
|
52
|
+
},
|
|
53
|
+
]),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
repo.appendMessage('sess-notify', {
|
|
57
|
+
role: 'user',
|
|
58
|
+
text: 'hello',
|
|
59
|
+
time: 1,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
console.log(JSON.stringify({
|
|
63
|
+
genericTopics: genericPayloads.map((payload) => payload.topic),
|
|
64
|
+
sessionTopics: sessionPayloads.map((payload) => payload.topic),
|
|
65
|
+
}))
|
|
66
|
+
`, { prefix: 'swarmclaw-message-repo-notify-' })
|
|
67
|
+
|
|
68
|
+
assert.deepEqual(output.genericTopics, ['messages'])
|
|
69
|
+
assert.deepEqual(output.sessionTopics, ['messages:sess-notify'])
|
|
70
|
+
})
|