@swarmclawai/swarmclaw 1.2.3 → 1.2.5
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 +20 -0
- package/bin/daemon-cmd.js +169 -0
- package/bin/server-cmd.js +3 -0
- package/bin/swarmclaw.js +11 -0
- package/package.json +17 -16
- package/src/app/api/agents/[id]/clone/route.ts +3 -32
- package/src/app/api/agents/[id]/route.ts +6 -158
- package/src/app/api/agents/[id]/status/route.ts +2 -3
- package/src/app/api/agents/[id]/thread/route.ts +4 -17
- package/src/app/api/agents/bulk/route.ts +5 -47
- package/src/app/api/agents/route.ts +5 -119
- package/src/app/api/agents/trash/route.ts +13 -24
- package/src/app/api/auth/route.ts +3 -9
- package/src/app/api/autonomy/estop/route.ts +5 -5
- package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
- package/src/app/api/chatrooms/[id]/route.ts +23 -2
- package/src/app/api/chatrooms/route.ts +13 -2
- package/src/app/api/chats/[id]/clear/route.ts +2 -13
- package/src/app/api/chats/[id]/deploy/route.ts +2 -3
- package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
- package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
- package/src/app/api/chats/[id]/queue/route.ts +17 -64
- package/src/app/api/chats/[id]/retry/route.ts +4 -22
- package/src/app/api/chats/[id]/route.ts +10 -138
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/migrate-messages/route.ts +7 -0
- package/src/app/api/chats/route.ts +13 -134
- package/src/app/api/connectors/[id]/access/route.ts +12 -229
- package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
- package/src/app/api/connectors/[id]/health/route.ts +12 -39
- package/src/app/api/connectors/[id]/route.ts +14 -122
- package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
- package/src/app/api/connectors/doctor/route.ts +1 -1
- package/src/app/api/connectors/route.ts +12 -70
- package/src/app/api/credentials/[id]/route.ts +2 -4
- package/src/app/api/credentials/route.ts +10 -19
- package/src/app/api/daemon/health-check/route.ts +3 -4
- package/src/app/api/daemon/route.ts +10 -8
- package/src/app/api/documents/route.ts +11 -10
- package/src/app/api/external-agents/route.ts +3 -3
- package/src/app/api/gateways/[id]/health/route.ts +2 -3
- package/src/app/api/gateways/[id]/route.ts +7 -122
- package/src/app/api/gateways/route.ts +3 -103
- package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
- package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
- package/src/app/api/openclaw/directory/route.ts +2 -2
- package/src/app/api/openclaw/history/route.ts +3 -5
- package/src/app/api/providers/[id]/models/route.test.ts +60 -0
- package/src/app/api/providers/[id]/models/route.ts +33 -1
- package/src/app/api/providers/[id]/route.test.ts +49 -0
- package/src/app/api/providers/[id]/route.ts +30 -1
- package/src/app/api/providers/ollama/route.ts +6 -5
- package/src/app/api/schedules/[id]/route.ts +14 -108
- package/src/app/api/schedules/[id]/run/route.ts +6 -67
- package/src/app/api/schedules/route.ts +9 -51
- package/src/app/api/settings/route.ts +4 -3
- package/src/app/api/setup/check-provider/route.ts +15 -1
- package/src/app/api/setup/openclaw-device/route.ts +2 -2
- package/src/app/api/system/status/route.ts +2 -2
- package/src/app/api/tasks/[id]/route.ts +16 -202
- package/src/app/api/tasks/bulk/route.ts +5 -86
- package/src/app/api/tasks/metrics/route.ts +2 -1
- package/src/app/api/tasks/route.ts +11 -171
- package/src/app/api/upload/route.ts +1 -1
- package/src/app/api/uploads/[filename]/route.ts +1 -1
- package/src/app/api/uploads/route.ts +1 -1
- package/src/app/api/webhooks/[id]/history/route.ts +2 -2
- package/src/app/layout.tsx +9 -6
- package/src/app/protocols/page.tsx +71 -89
- package/src/app/tasks/page.tsx +32 -32
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-sheet.tsx +51 -25
- package/src/components/agents/inspector-panel.tsx +15 -4
- package/src/components/auth/setup-wizard/index.tsx +27 -18
- package/src/components/auth/setup-wizard/shared.tsx +2 -2
- package/src/components/auth/setup-wizard/step-agents.tsx +51 -38
- package/src/components/auth/setup-wizard/step-connect.tsx +48 -17
- package/src/components/auth/setup-wizard/types.ts +6 -4
- package/src/components/auth/setup-wizard/utils.test.ts +38 -8
- package/src/components/auth/setup-wizard/utils.ts +14 -8
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
- package/src/components/connectors/connector-list.tsx +26 -40
- package/src/components/connectors/connector-sheet.tsx +95 -149
- package/src/components/gateways/gateway-sheet.tsx +61 -110
- package/src/components/layout/live-query-sync.tsx +121 -0
- package/src/components/protocols/structured-session-launcher.tsx +24 -45
- package/src/components/providers/app-query-provider.tsx +17 -0
- package/src/components/providers/provider-list.tsx +150 -77
- package/src/components/providers/provider-sheet.tsx +102 -77
- package/src/components/shared/model-combobox.tsx +5 -4
- package/src/components/skills/skill-list.tsx +5 -18
- package/src/components/skills/skill-sheet.tsx +21 -20
- package/src/components/skills/skills-workspace.tsx +48 -87
- package/src/components/tasks/task-card.tsx +20 -13
- package/src/components/tasks/task-column.tsx +22 -7
- package/src/components/tasks/task-list.tsx +8 -11
- package/src/components/tasks/task-sheet.tsx +111 -103
- package/src/features/agents/queries.ts +20 -0
- package/src/features/chatrooms/queries.ts +20 -0
- package/src/features/chats/queries.ts +27 -0
- package/src/features/connectors/queries.ts +145 -0
- package/src/features/credentials/queries.ts +37 -0
- package/src/features/extensions/queries.ts +26 -0
- package/src/features/external-agents/queries.ts +36 -0
- package/src/features/gateways/queries.ts +274 -0
- package/src/features/missions/queries.ts +23 -0
- package/src/features/projects/queries.ts +20 -0
- package/src/features/protocols/queries.ts +149 -0
- package/src/features/providers/queries.ts +142 -0
- package/src/features/settings/queries.ts +20 -0
- package/src/features/skills/queries.ts +182 -0
- package/src/features/tasks/queries.ts +189 -0
- package/src/hooks/use-ws.ts +3 -2
- package/src/lib/agent-provider-options.test.ts +152 -0
- package/src/lib/agent-provider-options.ts +84 -0
- package/src/lib/app/api-client.ts +2 -2
- package/src/lib/providers/index.test.ts +78 -0
- package/src/lib/providers/index.ts +13 -10
- package/src/lib/query/client.ts +17 -0
- package/src/lib/server/agents/agent-runtime-config.ts +6 -6
- package/src/lib/server/agents/agent-service.ts +429 -0
- package/src/lib/server/agents/agent-thread-session.ts +6 -5
- package/src/lib/server/agents/autonomy-contract.ts +1 -4
- package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
- package/src/lib/server/agents/delegation-advisory.ts +251 -0
- package/src/lib/server/agents/main-agent-loop.ts +98 -40
- package/src/lib/server/agents/subagent-runtime.ts +12 -0
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
- package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
- package/src/lib/server/build-llm.ts +7 -15
- package/src/lib/server/capability-router.test.ts +70 -1
- package/src/lib/server/capability-router.ts +24 -99
- package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
- package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
- package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
- package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
- package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
- package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
- package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
- package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
- package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
- package/src/lib/server/chat-execution/message-classifier.ts +74 -32
- package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
- package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
- package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
- package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
- package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
- package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
- package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
- package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
- package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
- package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
- package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
- package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
- package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
- package/src/lib/server/chats/chat-session-service.ts +410 -0
- package/src/lib/server/connectors/access.ts +1 -1
- package/src/lib/server/connectors/commands.ts +7 -6
- package/src/lib/server/connectors/connector-inbound.ts +14 -7
- package/src/lib/server/connectors/connector-outbound.ts +16 -11
- package/src/lib/server/connectors/connector-service.ts +453 -0
- package/src/lib/server/connectors/delivery.ts +17 -12
- package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
- package/src/lib/server/connectors/media.ts +1 -1
- package/src/lib/server/connectors/response-media.ts +1 -1
- package/src/lib/server/connectors/session-consolidation.ts +11 -7
- package/src/lib/server/connectors/session.ts +9 -7
- package/src/lib/server/connectors/voice-note.ts +2 -1
- package/src/lib/server/context-manager.ts +20 -1
- package/src/lib/server/cost.ts +2 -3
- package/src/lib/server/credentials/credential-repository.ts +43 -4
- package/src/lib/server/credentials/credential-service.ts +112 -0
- package/src/lib/server/daemon/admin-metadata.ts +64 -0
- package/src/lib/server/daemon/controller.ts +577 -0
- package/src/lib/server/daemon/daemon-runtime.ts +352 -0
- package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
- package/src/lib/server/daemon/types.ts +101 -0
- package/src/lib/server/embeddings.ts +3 -9
- package/src/lib/server/eval/agent-regression.ts +3 -2
- package/src/lib/server/eval/runner.ts +2 -2
- package/src/lib/server/execution-brief.test.ts +167 -0
- package/src/lib/server/execution-brief.ts +295 -0
- package/src/lib/server/execution-engine/chat-turn.ts +9 -0
- package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
- package/src/lib/server/execution-engine/index.ts +35 -0
- package/src/lib/server/execution-engine/task-attempt.ts +303 -0
- package/src/lib/server/execution-engine/types.ts +33 -0
- package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
- package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
- package/src/lib/server/memory/session-archive-memory.ts +12 -10
- package/src/lib/server/messages/message-repository.ts +330 -0
- package/src/lib/server/missions/mission-service/core.ts +8 -6
- package/src/lib/server/openclaw/agent-resolver.ts +2 -3
- package/src/lib/server/openclaw/doctor.ts +1 -1
- package/src/lib/server/openclaw/gateway.test.ts +10 -1
- package/src/lib/server/openclaw/gateway.ts +5 -14
- package/src/lib/server/openclaw/health.ts +3 -11
- package/src/lib/server/openclaw/sync.ts +8 -6
- package/src/lib/server/persistence/storage-context.ts +3 -0
- package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
- package/src/lib/server/protocols/protocol-normalization.ts +1 -1
- package/src/lib/server/protocols/protocol-queries.ts +13 -7
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
- package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
- package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
- package/src/lib/server/protocols/protocol-swarm.ts +8 -8
- package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
- package/src/lib/server/protocols/protocol-templates.ts +4 -2
- package/src/lib/server/protocols/protocol-types.ts +10 -7
- package/src/lib/server/provider-endpoint.ts +7 -12
- package/src/lib/server/provider-model-discovery.ts +2 -11
- package/src/lib/server/query-expansion.ts +5 -6
- package/src/lib/server/run-context.test.ts +365 -0
- package/src/lib/server/run-context.ts +367 -0
- package/src/lib/server/runtime/heartbeat-service.ts +7 -5
- package/src/lib/server/runtime/queue/core.ts +61 -190
- package/src/lib/server/runtime/run-ledger.ts +8 -0
- package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
- package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
- package/src/lib/server/schedules/schedule-route-service.ts +230 -0
- package/src/lib/server/service-result.ts +16 -0
- package/src/lib/server/session-note.ts +2 -3
- package/src/lib/server/session-reset-policy.ts +4 -3
- package/src/lib/server/session-tools/connector.ts +9 -6
- package/src/lib/server/session-tools/context-mgmt.ts +58 -9
- package/src/lib/server/session-tools/crud.ts +162 -10
- package/src/lib/server/session-tools/delegate.ts +1 -1
- package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
- package/src/lib/server/session-tools/memory.ts +6 -4
- package/src/lib/server/session-tools/session-info.test.ts +56 -0
- package/src/lib/server/session-tools/session-info.ts +119 -12
- package/src/lib/server/session-tools/skill-runtime.ts +3 -1
- package/src/lib/server/session-tools/skills.ts +15 -15
- package/src/lib/server/session-tools/subagent.test.ts +115 -1
- package/src/lib/server/session-tools/subagent.ts +125 -7
- package/src/lib/server/session-tools/team-context.ts +4 -3
- package/src/lib/server/session-tools/wallet.ts +0 -58
- package/src/lib/server/sessions/session-lineage.ts +55 -0
- package/src/lib/server/sessions/session-repository.ts +2 -2
- package/src/lib/server/skills/learned-skills.ts +24 -23
- package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
- package/src/lib/server/skills/skill-repository.ts +136 -13
- package/src/lib/server/skills/skill-suggestions.ts +25 -28
- package/src/lib/server/storage-normalization.test.ts +42 -215
- package/src/lib/server/storage-normalization.ts +98 -0
- package/src/lib/server/storage.ts +19 -0
- package/src/lib/server/structured-extract.ts +3 -14
- package/src/lib/server/tasks/task-followups.ts +16 -11
- package/src/lib/server/tasks/task-result.test.ts +25 -29
- package/src/lib/server/tasks/task-result.ts +5 -9
- package/src/lib/server/tasks/task-route-service.ts +449 -0
- package/src/lib/server/text-normalization.ts +41 -0
- package/src/lib/server/tool-planning.ts +6 -42
- package/src/lib/server/upload-path.ts +5 -0
- package/src/lib/server/working-state/extraction.ts +614 -0
- package/src/lib/server/working-state/normalization.ts +866 -0
- package/src/lib/server/working-state/prompt.ts +60 -0
- package/src/lib/server/working-state/repository.ts +38 -0
- package/src/lib/server/working-state/service.test.ts +253 -0
- package/src/lib/server/working-state/service.ts +293 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/ws-client.ts +3 -3
- package/src/stores/slices/task-slice.ts +1 -4
- package/src/stores/use-chatroom-store.ts +2 -2
- package/src/types/index.ts +288 -22
- package/src/views/settings/section-providers.tsx +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hmrSingleton } from '@/lib/shared-utils'
|
|
2
|
+
import { getMessages } from '@/lib/server/messages/message-repository'
|
|
2
3
|
import type { GoalContract, Message, MessageToolEvent, Session } from '@/types'
|
|
3
4
|
import { mergeGoalContracts, parseGoalContractFromText, parseMainLoopPlan, parseMainLoopReview } from '@/lib/server/agents/autonomy-contract'
|
|
4
5
|
import {
|
|
@@ -12,6 +13,10 @@ import { enqueueSystemEvent } from '@/lib/server/runtime/system-events'
|
|
|
12
13
|
import { buildMissionHeartbeatPrompt as buildMissionHeartbeatPromptFromMission, getMissionForSession } from '@/lib/server/missions/mission-service'
|
|
13
14
|
import { loadSettings } from '@/lib/server/settings/settings-repository'
|
|
14
15
|
import { getSession, loadSessions } from '@/lib/server/sessions/session-repository'
|
|
16
|
+
import { deleteSessionWorkingState, loadSessionWorkingState, syncWorkingStateFromMainLoopState } from '@/lib/server/working-state/service'
|
|
17
|
+
import { syncMainLoopToRunContext } from '@/lib/server/run-context'
|
|
18
|
+
import { buildExecutionBrief, buildExecutionBriefContextBlock } from '@/lib/server/execution-brief'
|
|
19
|
+
import { cleanText, cleanMultiline } from '@/lib/server/text-normalization'
|
|
15
20
|
|
|
16
21
|
const LEGACY_META_LINE_RE = /\[(?:MAIN_LOOP_META|MAIN_LOOP_PLAN|MAIN_LOOP_REVIEW|AGENT_HEARTBEAT_META)\]\s*(\{[^\n]*\})?/i
|
|
17
22
|
const HEARTBEAT_META_RE = /\[AGENT_HEARTBEAT_META\]\s*(\{[^\n]*\})/i
|
|
@@ -19,7 +24,7 @@ const MAX_PENDING_EVENTS = 16
|
|
|
19
24
|
const MAX_TIMELINE_ITEMS = 40
|
|
20
25
|
const MAX_WORKING_MEMORY_NOTES = 12
|
|
21
26
|
const DEFAULT_FOLLOWUP_DELAY_MS = 1500
|
|
22
|
-
const DEFAULT_MAX_FOLLOWUP_CHAIN =
|
|
27
|
+
const DEFAULT_MAX_FOLLOWUP_CHAIN = 3
|
|
23
28
|
const MAX_LIFETIME_ITERATIONS = 200
|
|
24
29
|
|
|
25
30
|
export interface MainLoopState {
|
|
@@ -111,24 +116,6 @@ function asSession(session: unknown): MainSessionLike | null {
|
|
|
111
116
|
return session as MainSessionLike
|
|
112
117
|
}
|
|
113
118
|
|
|
114
|
-
function cleanText(value: unknown, maxChars = 320): string | null {
|
|
115
|
-
if (typeof value !== 'string') return null
|
|
116
|
-
const normalized = value.replace(/\s+/g, ' ').trim()
|
|
117
|
-
return normalized ? normalized.slice(0, maxChars) : null
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function cleanMultiline(value: unknown, maxChars = 1400): string | null {
|
|
121
|
-
if (typeof value !== 'string') return null
|
|
122
|
-
const normalized = value
|
|
123
|
-
.split('\n')
|
|
124
|
-
.map((line) => line.trim())
|
|
125
|
-
.filter(Boolean)
|
|
126
|
-
.join('\n')
|
|
127
|
-
.slice(0, maxChars)
|
|
128
|
-
.trim()
|
|
129
|
-
return normalized || null
|
|
130
|
-
}
|
|
131
|
-
|
|
132
119
|
function normalizeConfidence(value: unknown): number | null {
|
|
133
120
|
const raw = typeof value === 'number'
|
|
134
121
|
? value
|
|
@@ -403,7 +390,7 @@ function hydrateStateFromSession(sessionId: string): MainLoopState | null {
|
|
|
403
390
|
const session = sessions[sessionId]
|
|
404
391
|
if (!session || !isMainSession(session)) return null
|
|
405
392
|
|
|
406
|
-
const messages =
|
|
393
|
+
const messages = getMessages(sessionId)
|
|
407
394
|
const hydrated = defaultState()
|
|
408
395
|
hydrated.autonomyMode = session.heartbeatEnabled === true ? 'autonomous' : 'assist'
|
|
409
396
|
hydrated.updatedAt = typeof session.lastActiveAt === 'number' ? session.lastActiveAt : now()
|
|
@@ -440,21 +427,48 @@ function hydrateStateFromSession(sessionId: string): MainLoopState | null {
|
|
|
440
427
|
}
|
|
441
428
|
}
|
|
442
429
|
|
|
443
|
-
return normalizeState(hydrated)
|
|
430
|
+
return mergeWorkingStateIntoMainLoopState(sessionId, normalizeState(hydrated))
|
|
444
431
|
}
|
|
445
432
|
|
|
446
433
|
function persistState(sessionId: string, state: MainLoopState): void {
|
|
447
|
-
|
|
434
|
+
const normalized = clampState(state)
|
|
435
|
+
upsertPersistedMainLoopState(sessionId, normalized as unknown as Record<string, unknown>)
|
|
436
|
+
const session = getSession(sessionId)
|
|
437
|
+
if (!session) return
|
|
438
|
+
const mission = getMissionForSession(session)
|
|
439
|
+
void syncWorkingStateFromMainLoopState({
|
|
440
|
+
sessionId,
|
|
441
|
+
mission,
|
|
442
|
+
goal: normalized.goal,
|
|
443
|
+
summary: normalized.summary,
|
|
444
|
+
status: normalized.status === 'ok'
|
|
445
|
+
? 'completed'
|
|
446
|
+
: normalized.status === 'blocked'
|
|
447
|
+
? 'blocked'
|
|
448
|
+
: normalized.status === 'progress'
|
|
449
|
+
? 'progress'
|
|
450
|
+
: 'idle',
|
|
451
|
+
nextAction: normalized.nextAction,
|
|
452
|
+
planSteps: normalized.planSteps,
|
|
453
|
+
blockers: normalized.skillBlocker ? [{
|
|
454
|
+
summary: normalized.skillBlocker.summary,
|
|
455
|
+
kind: normalized.skillBlocker.status === 'approval_requested' ? 'approval' : 'other',
|
|
456
|
+
}] : undefined,
|
|
457
|
+
})
|
|
448
458
|
}
|
|
449
459
|
|
|
450
460
|
function getOrCreateState(sessionId: string): MainLoopState | null {
|
|
451
461
|
const existing = stateMap.get(sessionId)
|
|
452
|
-
if (existing)
|
|
462
|
+
if (existing) {
|
|
463
|
+
const merged = mergeWorkingStateIntoMainLoopState(sessionId, existing)
|
|
464
|
+
stateMap.set(sessionId, merged)
|
|
465
|
+
return merged
|
|
466
|
+
}
|
|
453
467
|
|
|
454
468
|
// Try disk (survives full restart)
|
|
455
469
|
const persisted = loadPersistedMainLoopState(sessionId) as Partial<MainLoopState> | null
|
|
456
470
|
if (persisted) {
|
|
457
|
-
const restored = normalizeState(persisted)
|
|
471
|
+
const restored = mergeWorkingStateIntoMainLoopState(sessionId, normalizeState(persisted))
|
|
458
472
|
stateMap.set(sessionId, restored)
|
|
459
473
|
return restored
|
|
460
474
|
}
|
|
@@ -467,6 +481,43 @@ function getOrCreateState(sessionId: string): MainLoopState | null {
|
|
|
467
481
|
return hydrated
|
|
468
482
|
}
|
|
469
483
|
|
|
484
|
+
function mergeWorkingStateIntoMainLoopState(sessionId: string, current: MainLoopState): MainLoopState {
|
|
485
|
+
const workingState = loadSessionWorkingState(sessionId)
|
|
486
|
+
if (!workingState) return clampState(current)
|
|
487
|
+
const next = normalizeState(current)
|
|
488
|
+
if (workingState.objective) next.goal = cleanMultiline(workingState.objective, 900)
|
|
489
|
+
if (workingState.summary) next.summary = cleanText(workingState.summary, 1000)
|
|
490
|
+
if (workingState.nextAction) next.nextAction = cleanText(workingState.nextAction, 240)
|
|
491
|
+
if (workingState.status === 'completed') next.status = 'ok'
|
|
492
|
+
else if (workingState.status === 'blocked' || workingState.status === 'waiting') next.status = 'blocked'
|
|
493
|
+
else if (workingState.status === 'progress') next.status = 'progress'
|
|
494
|
+
|
|
495
|
+
const planSteps = (workingState.planSteps || [])
|
|
496
|
+
.map((step) => cleanText(step.text, 240))
|
|
497
|
+
.filter((step): step is string => Boolean(step))
|
|
498
|
+
if (planSteps.length > 0) {
|
|
499
|
+
next.planSteps = uniqueStrings(planSteps, 8)
|
|
500
|
+
next.completedPlanSteps = uniqueStrings(
|
|
501
|
+
(workingState.planSteps || [])
|
|
502
|
+
.filter((step) => step.status === 'resolved')
|
|
503
|
+
.map((step) => cleanText(step.text, 240))
|
|
504
|
+
.filter((step): step is string => Boolean(step)),
|
|
505
|
+
16,
|
|
506
|
+
)
|
|
507
|
+
const activeStep = (workingState.planSteps || []).find((step) => step.status === 'active')
|
|
508
|
+
if (activeStep?.text) next.currentPlanStep = cleanText(activeStep.text, 240)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const noteCandidates = [
|
|
512
|
+
...(workingState.confirmedFacts || []).filter((item) => item.status === 'active').map((item) => `Fact: ${item.statement}`),
|
|
513
|
+
...(workingState.blockers || []).filter((item) => item.status === 'active').map((item) => `Blocker: ${item.summary}`),
|
|
514
|
+
]
|
|
515
|
+
if (noteCandidates.length > 0) {
|
|
516
|
+
next.workingMemoryNotes = uniqueStrings([...(next.workingMemoryNotes || []), ...noteCandidates], MAX_WORKING_MEMORY_NOTES)
|
|
517
|
+
}
|
|
518
|
+
return clampState(next)
|
|
519
|
+
}
|
|
520
|
+
|
|
470
521
|
function summarizePendingEvents(events: MainLoopState['pendingEvents']): string {
|
|
471
522
|
if (!events.length) return ''
|
|
472
523
|
return events
|
|
@@ -754,39 +805,38 @@ export function buildMainLoopHeartbeatPrompt(session: unknown, fallbackPrompt: s
|
|
|
754
805
|
const state = getOrCreateState(String(candidate.id))
|
|
755
806
|
if (!state) return fallbackPrompt
|
|
756
807
|
const latestExternalGoal = extractLatestGoal(Array.isArray(candidate.messages) ? candidate.messages as Message[] : [])
|
|
757
|
-
const effectiveGoal = state.goal || latestExternalGoal.goal
|
|
758
808
|
const effectiveGoalContract = latestExternalGoal.goalContract
|
|
759
809
|
? mergeGoalContracts(state.goalContract, latestExternalGoal.goalContract)
|
|
760
810
|
: state.goalContract
|
|
761
811
|
|
|
762
|
-
const
|
|
763
|
-
const
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
: ''
|
|
812
|
+
const heartbeatSession = (persistedSession || candidate as Session)
|
|
813
|
+
const executionBrief = buildExecutionBrief({
|
|
814
|
+
session: heartbeatSession,
|
|
815
|
+
mission: getMissionForSession(heartbeatSession),
|
|
816
|
+
})
|
|
817
|
+
const executionBriefBlock = buildExecutionBriefContextBlock(executionBrief)
|
|
769
818
|
const boundedFallbackPrompt = cleanMultiline(fallbackPrompt, 500)
|
|
770
|
-
const
|
|
819
|
+
const workingState = loadSessionWorkingState(String(candidate.id))
|
|
820
|
+
const activeWorkingBlockers = (workingState?.blockers || [])
|
|
821
|
+
.filter((item) => item.status === 'active')
|
|
822
|
+
.map((item) => item.nextAction ? `${item.summary} | next: ${item.nextAction}` : item.summary)
|
|
823
|
+
.slice(0, 4)
|
|
824
|
+
.join('\n')
|
|
771
825
|
|
|
772
826
|
return [
|
|
773
827
|
'MAIN_AGENT_HEARTBEAT_TICK',
|
|
774
828
|
`Time: ${new Date().toISOString()}`,
|
|
775
|
-
|
|
829
|
+
executionBriefBlock,
|
|
776
830
|
formatGoalContract(effectiveGoalContract),
|
|
777
|
-
`Current status: ${state.status}`,
|
|
778
|
-
state.nextAction ? `Planned next action: ${state.nextAction}` : '',
|
|
779
|
-
state.currentPlanStep ? `Current plan step: ${state.currentPlanStep}` : '',
|
|
780
|
-
planLines ? `Plan:\n${planLines}` : '',
|
|
781
831
|
state.pendingEvents.length > 0 ? `Pending external events:\n${summarizePendingEvents(state.pendingEvents)}` : '',
|
|
832
|
+
activeWorkingBlockers ? `Active blockers:\n${activeWorkingBlockers}` : '',
|
|
782
833
|
state.skillBlocker ? `Active skill blocker:\n${summarizeSkillBlocker(state.skillBlocker)}` : '',
|
|
783
834
|
summarizeSelectedSkillRuntime(candidate),
|
|
784
|
-
boundedSummary ? `Latest summary:\n${boundedSummary}` : '',
|
|
785
835
|
boundedFallbackPrompt ? `Base heartbeat instructions:\n${boundedFallbackPrompt}` : '',
|
|
786
836
|
'',
|
|
787
837
|
'You are checking the durable main mission thread for this agent.',
|
|
788
838
|
'Keep this status check brief — 5-10 tool calls maximum. Read key state, summarize progress, and report. Do not attempt fixes or deep investigation during heartbeats.',
|
|
789
|
-
'Use
|
|
839
|
+
'Use the execution brief and pending external events shown above as the authoritative state for this tick.',
|
|
790
840
|
'Do not infer or repeat old tasks from prior heartbeats.',
|
|
791
841
|
'Prefer taking the single highest-value next step over restating the plan. Do not repeat completed work.',
|
|
792
842
|
'If you revise the plan, emit exactly one line like:',
|
|
@@ -827,6 +877,7 @@ export function getMainLoopStateForSession(sessionId: string): MainLoopState | n
|
|
|
827
877
|
|
|
828
878
|
export function clearMainLoopStateForSession(sessionId: string): boolean {
|
|
829
879
|
deletePersistedMainLoopState(sessionId)
|
|
880
|
+
deleteSessionWorkingState(sessionId)
|
|
830
881
|
return stateMap.delete(sessionId)
|
|
831
882
|
}
|
|
832
883
|
|
|
@@ -840,6 +891,7 @@ export function pruneMainLoopState(liveSessionIds: Set<string>): number {
|
|
|
840
891
|
if (!liveSessionIds.has(sessionId)) {
|
|
841
892
|
stateMap.delete(sessionId)
|
|
842
893
|
deletePersistedMainLoopState(sessionId)
|
|
894
|
+
deleteSessionWorkingState(sessionId)
|
|
843
895
|
removed++
|
|
844
896
|
}
|
|
845
897
|
}
|
|
@@ -1096,5 +1148,11 @@ export function handleMainLoopRunResult(input: HandleMainLoopRunResultInput): Ma
|
|
|
1096
1148
|
const finalClamped = clampState(state)
|
|
1097
1149
|
stateMap.set(input.sessionId, finalClamped)
|
|
1098
1150
|
persistState(input.sessionId, finalClamped)
|
|
1151
|
+
|
|
1152
|
+
// Project orchestrator state into session RunContext (non-critical)
|
|
1153
|
+
try {
|
|
1154
|
+
syncMainLoopToRunContext(input.sessionId, finalClamped)
|
|
1155
|
+
} catch { /* non-critical — main loop continues even if sync fails */ }
|
|
1156
|
+
|
|
1099
1157
|
return followup
|
|
1100
1158
|
}
|
|
@@ -49,6 +49,8 @@ import { logExecution } from '@/lib/server/execution-log'
|
|
|
49
49
|
import { enqueueSystemEvent } from '@/lib/server/runtime/system-events'
|
|
50
50
|
import { getEnabledCapabilityIds, splitCapabilityIds } from '@/lib/capability-selection'
|
|
51
51
|
import { getSession, loadSessions, saveSession } from '@/lib/server/sessions/session-repository'
|
|
52
|
+
import { ensureRunContext } from '@/lib/server/run-context'
|
|
53
|
+
import { buildExecutionBrief, serializeExecutionBriefForDelegation } from '@/lib/server/execution-brief'
|
|
52
54
|
|
|
53
55
|
// ---------------------------------------------------------------------------
|
|
54
56
|
// Types
|
|
@@ -277,6 +279,16 @@ async function spawnSubagentImpl(
|
|
|
277
279
|
browserProfileId,
|
|
278
280
|
}
|
|
279
281
|
sessions[sid] = applyResolvedRoute(nextSession, resolvePrimaryAgentRoute(agent))
|
|
282
|
+
|
|
283
|
+
// Enrich child session with parent's RunContext for delegation handoff
|
|
284
|
+
const delegationContext = parent ? serializeExecutionBriefForDelegation(buildExecutionBrief({ sessionId: context.sessionId })) : null
|
|
285
|
+
if (delegationContext) {
|
|
286
|
+
const childCtx = ensureRunContext(null)
|
|
287
|
+
childCtx.parentContext = delegationContext
|
|
288
|
+
childCtx.objective = input.message.slice(0, 900)
|
|
289
|
+
sessions[sid].runContext = childCtx
|
|
290
|
+
}
|
|
291
|
+
|
|
280
292
|
saveSession(sid, sessions[sid])
|
|
281
293
|
|
|
282
294
|
log.info('subagent', 'Spawning', { agentId: agent.id, agentName: agent.name, depth: depth + 1, jobId: job.id, sessionId: sid })
|
|
@@ -228,7 +228,26 @@ describe('supervisor-reflection', () => {
|
|
|
228
228
|
model: 'gpt-test',
|
|
229
229
|
claudeSessionId: null,
|
|
230
230
|
messages: [
|
|
231
|
-
{
|
|
231
|
+
{
|
|
232
|
+
role: 'user',
|
|
233
|
+
text: 'I am moving to Lisbon next month and prefer short check-ins while I am juggling the move.',
|
|
234
|
+
time: 1,
|
|
235
|
+
semantics: {
|
|
236
|
+
taskIntent: 'general',
|
|
237
|
+
workType: 'general',
|
|
238
|
+
walletIntent: 'none',
|
|
239
|
+
isDeliverableTask: false,
|
|
240
|
+
isBroadGoal: false,
|
|
241
|
+
isResearchSynthesis: false,
|
|
242
|
+
hasHumanSignals: true,
|
|
243
|
+
hasSignificantEvent: true,
|
|
244
|
+
wantsScreenshots: false,
|
|
245
|
+
wantsOutboundDelivery: false,
|
|
246
|
+
wantsVoiceDelivery: false,
|
|
247
|
+
explicitToolRequests: [],
|
|
248
|
+
confidence: 0.98,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
232
251
|
{ role: 'assistant', text: 'Understood. I will keep updates tight and remember the move timing.', time: 2 },
|
|
233
252
|
],
|
|
234
253
|
createdAt: 1,
|
|
@@ -33,14 +33,16 @@ import { log } from '@/lib/server/logger'
|
|
|
33
33
|
import { logExecution } from '@/lib/server/execution-log'
|
|
34
34
|
import { logActivity } from '@/lib/server/storage'
|
|
35
35
|
import { createNotification } from '@/lib/server/create-notification'
|
|
36
|
+
import { foldReflectionIntoRunContext } from '@/lib/server/run-context'
|
|
37
|
+
import { getSession, saveSession } from '@/lib/server/sessions/session-repository'
|
|
38
|
+
import { cleanText } from '@/lib/server/text-normalization'
|
|
39
|
+
import { getMessages, getMessageCount, getRecentMessages } from '@/lib/server/messages/message-repository'
|
|
36
40
|
|
|
37
41
|
const TAG = 'supervisor-reflection'
|
|
38
42
|
|
|
39
43
|
const MAIN_LOOP_META_LINE_RE = /\[(?:MAIN_LOOP_META|MAIN_LOOP_PLAN|MAIN_LOOP_REVIEW|AGENT_HEARTBEAT_META)\]\s*(\{[^\n]*\})?/i
|
|
40
44
|
const DEFAULT_TRANSCRIPT_MESSAGES = 12
|
|
41
45
|
const DEFAULT_SNIPPET_CHARS = 800
|
|
42
|
-
const HUMAN_SIGNAL_RE = /\b(?:prefer|please|call me|don't call me|do not call me|i like|i dislike|i hate|i love|my pronouns|my partner|my wife|my husband|my kid|my child|my mom|my dad|my sister|my brother|birthday|anniversary|wedding|married|divorc|pregnan|baby|moved|moving|relocat|promotion|promoted|laid off|new job|job change|graduat|hospital|sick|illness|diagnos|passed away|funeral|grief|bereave|deadline|launch|fundraising|closing|house|home|travel)\b/i
|
|
43
|
-
const SIGNIFICANT_EVENT_RE = /\b(?:birthday|anniversary|wedding|married|divorc|pregnan|baby|moved|moving|relocat|promotion|promoted|laid off|new job|job change|graduat|hospital|sick|illness|diagnos|passed away|funeral|grief|bereave|deadline|launch|fundraising|closing|house|home|travel)\b/i
|
|
44
46
|
|
|
45
47
|
export interface SupervisorStateSnapshot {
|
|
46
48
|
followupChainCount?: number | null
|
|
@@ -81,13 +83,6 @@ function now(): number {
|
|
|
81
83
|
return Date.now()
|
|
82
84
|
}
|
|
83
85
|
|
|
84
|
-
function cleanText(value: unknown, max = 320): string | null {
|
|
85
|
-
if (typeof value !== 'string') return null
|
|
86
|
-
const compact = value.replace(/\s+/g, ' ').trim()
|
|
87
|
-
if (!compact) return null
|
|
88
|
-
return compact.slice(0, max)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
86
|
function looksLikeHtmlErrorPayload(value: string): boolean {
|
|
92
87
|
const normalized = value.toLowerCase()
|
|
93
88
|
let matches = 0
|
|
@@ -189,9 +184,11 @@ function buildIncident(
|
|
|
189
184
|
}
|
|
190
185
|
|
|
191
186
|
function sessionContextPressure(session: Session | null): boolean {
|
|
192
|
-
if (!session
|
|
193
|
-
|
|
194
|
-
|
|
187
|
+
if (!session) return false
|
|
188
|
+
const msgCount = getMessageCount(session.id)
|
|
189
|
+
if (msgCount >= 60) return true
|
|
190
|
+
const messages = getMessages(session.id)
|
|
191
|
+
const totalChars = messages.reduce((sum, message) => sum + String(message?.text || '').length, 0)
|
|
195
192
|
return totalChars >= 18_000
|
|
196
193
|
}
|
|
197
194
|
|
|
@@ -428,7 +425,7 @@ export async function executeSupervisorAutoActions(params: {
|
|
|
428
425
|
}
|
|
429
426
|
|
|
430
427
|
function buildSessionTranscript(session: Session, maxMessages = DEFAULT_TRANSCRIPT_MESSAGES): string {
|
|
431
|
-
const messages =
|
|
428
|
+
const messages = getRecentMessages(session.id, maxMessages)
|
|
432
429
|
const lines: string[] = []
|
|
433
430
|
for (const message of messages) {
|
|
434
431
|
if (!message || message.suppressed) continue
|
|
@@ -543,10 +540,21 @@ function normalizeNoteArray(value: unknown, limit = 4): string[] {
|
|
|
543
540
|
return out
|
|
544
541
|
}
|
|
545
542
|
|
|
543
|
+
function transcriptHasSemanticSignal(
|
|
544
|
+
session: Session | null,
|
|
545
|
+
signal: 'hasHumanSignals' | 'hasSignificantEvent',
|
|
546
|
+
): boolean {
|
|
547
|
+
if (!session) return false
|
|
548
|
+
const recentMessages = getRecentMessages(session.id, 8)
|
|
549
|
+
return recentMessages.some((message) => message?.role === 'user' && message?.semantics?.[signal] === true)
|
|
550
|
+
}
|
|
551
|
+
|
|
546
552
|
function transcriptHasHumanSignals(session: Session | null): boolean {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
553
|
+
return transcriptHasSemanticSignal(session, 'hasHumanSignals')
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function transcriptHasSignificantEvents(session: Session | null): boolean {
|
|
557
|
+
return transcriptHasSemanticSignal(session, 'hasSignificantEvent')
|
|
550
558
|
}
|
|
551
559
|
|
|
552
560
|
function parseReflectionResponse(raw: string): {
|
|
@@ -658,10 +666,11 @@ function shouldReflectRun(params: {
|
|
|
658
666
|
if (!surface || !runtimeScopeIncludes(params.runtimeScope, surface)) return false
|
|
659
667
|
if (params.status === 'cancelled') return false
|
|
660
668
|
if (surface === 'task') return Boolean(params.resultText.trim() || params.incidents.length > 0)
|
|
661
|
-
const meaningfulMessages =
|
|
662
|
-
? params.session.
|
|
669
|
+
const meaningfulMessages = params.session
|
|
670
|
+
? getMessages(params.session.id).filter((message) => message && !message.suppressed && (message.text || message.toolEvents?.length)).length
|
|
663
671
|
: 0
|
|
664
672
|
if (transcriptHasHumanSignals(params.session)) return true
|
|
673
|
+
if (transcriptHasSignificantEvents(params.session)) return true
|
|
665
674
|
if (params.incidents.length > 0) return true
|
|
666
675
|
if (params.toolEvents.length > 0) return true
|
|
667
676
|
if (params.resultText.trim().length >= 180) return true
|
|
@@ -788,7 +797,7 @@ function writeReflectionMemories(params: {
|
|
|
788
797
|
}
|
|
789
798
|
if (group.kind === 'significant_event') {
|
|
790
799
|
metadata.memoryFacet = 'event'
|
|
791
|
-
metadata.eventSalience =
|
|
800
|
+
metadata.eventSalience = 'high'
|
|
792
801
|
}
|
|
793
802
|
if (group.kind === 'open_loop') {
|
|
794
803
|
metadata.memoryFacet = 'followup'
|
|
@@ -1095,6 +1104,17 @@ export async function observeAutonomyRunOutcome(
|
|
|
1095
1104
|
reflections[reflection.id] = reflection
|
|
1096
1105
|
saveRunReflections(reflections)
|
|
1097
1106
|
|
|
1107
|
+
// Fold reflection notes into session RunContext (non-critical)
|
|
1108
|
+
try {
|
|
1109
|
+
const freshSession = getSession(input.sessionId) as Session | undefined
|
|
1110
|
+
if (freshSession) {
|
|
1111
|
+
freshSession.runContext = foldReflectionIntoRunContext(freshSession.runContext, reflection)
|
|
1112
|
+
saveSession(input.sessionId, freshSession)
|
|
1113
|
+
}
|
|
1114
|
+
} catch (err: unknown) {
|
|
1115
|
+
log.warn(TAG, 'RunContext reflection folding failed:', err instanceof Error ? err.message : String(err))
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1098
1118
|
// Quality degradation alert — if recent quality trend drops below 0.5
|
|
1099
1119
|
if (typeof reflection.qualityScore === 'number' && input.agentId) {
|
|
1100
1120
|
checkQualityDegradation(input.agentId).catch(() => {})
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { ChatAnthropic } from '@langchain/anthropic'
|
|
2
2
|
import { ChatOpenAI } from '@langchain/openai'
|
|
3
|
-
import { loadCredentials, decryptKey, loadAgents, loadSessions } from './storage'
|
|
4
3
|
import { getProviderList } from '../providers'
|
|
5
4
|
import { normalizeOpenClawEndpoint } from '@/lib/openclaw/openclaw-endpoint'
|
|
6
5
|
import { NON_LANGGRAPH_PROVIDER_IDS } from '../provider-sets'
|
|
7
6
|
import { resolveOllamaRuntimeConfig } from './ollama-runtime'
|
|
8
7
|
import { resolveProviderApiEndpoint, resolveProviderCredentialId } from './provider-endpoint'
|
|
8
|
+
import { getAgent } from './agents/agent-repository'
|
|
9
|
+
import { resolveCredentialSecret } from './credentials/credential-service'
|
|
10
|
+
import { getSession } from './sessions/session-repository'
|
|
9
11
|
import type { Agent } from '@/types'
|
|
10
12
|
|
|
11
13
|
const OLLAMA_CLOUD_URL = 'https://ollama.com/v1'
|
|
@@ -135,15 +137,7 @@ export function buildChatModel(opts: {
|
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
function resolveApiKeyFromCredential(credentialId: string | null | undefined): string | null {
|
|
138
|
-
|
|
139
|
-
const creds = loadCredentials()
|
|
140
|
-
const cred = creds[credentialId]
|
|
141
|
-
if (!cred?.encryptedKey) return null
|
|
142
|
-
try {
|
|
143
|
-
return decryptKey(cred.encryptedKey)
|
|
144
|
-
} catch {
|
|
145
|
-
return null
|
|
146
|
-
}
|
|
140
|
+
return resolveCredentialSecret(credentialId)
|
|
147
141
|
}
|
|
148
142
|
|
|
149
143
|
function normalizePreferenceValue(value: string | null | undefined): string {
|
|
@@ -222,12 +216,10 @@ export function resolveGenerationModelConfig(options?: {
|
|
|
222
216
|
excludeProviders?: string[]
|
|
223
217
|
}): ResolvedGenerationModelConfig {
|
|
224
218
|
const providers = getProviderList()
|
|
225
|
-
const agents = loadAgents()
|
|
226
|
-
const sessions = loadSessions()
|
|
227
219
|
const excludeProviders = new Set((options?.excludeProviders || []).map((value) => normalizePreferenceValue(value)).filter(Boolean))
|
|
228
|
-
const session = options?.sessionId ?
|
|
229
|
-
const sessionAgent = session?.agentId ?
|
|
230
|
-
const directAgent = options?.agentId ?
|
|
220
|
+
const session = options?.sessionId ? getSession(options.sessionId) : null
|
|
221
|
+
const sessionAgent = session?.agentId ? getAgent(session.agentId) as Agent | null : null
|
|
222
|
+
const directAgent = options?.agentId ? getAgent(options.agentId) as Agent | null : null
|
|
231
223
|
const resolved = resolvePreferredGenerationConfig(providers, [
|
|
232
224
|
...(Array.isArray(options?.preferred) ? options?.preferred : options?.preferred ? [options.preferred] : []),
|
|
233
225
|
...(session ? [{
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import assert from 'node:assert/strict'
|
|
2
2
|
import { test } from 'node:test'
|
|
3
3
|
import { routeTaskIntent } from './capability-router'
|
|
4
|
+
import type { MessageClassification } from '@/lib/server/chat-execution/message-classifier'
|
|
4
5
|
|
|
5
6
|
test('routeTaskIntent keeps recall-style prompts as general intent', () => {
|
|
6
7
|
const decision = routeTaskIntent(
|
|
7
8
|
'What token did we store earlier as e2e_validation_token? Reply only with the token.',
|
|
8
9
|
['memory', 'web_search'],
|
|
9
10
|
null,
|
|
11
|
+
makeClassification({ taskIntent: 'general' }),
|
|
10
12
|
)
|
|
11
13
|
assert.equal(decision.intent, 'general')
|
|
12
14
|
})
|
|
@@ -16,6 +18,7 @@ test('routeTaskIntent keeps coding prompts prioritized over memory keywords', ()
|
|
|
16
18
|
'Build and test a calculator app, then remember the final path in memory.',
|
|
17
19
|
['memory', 'shell', 'files'],
|
|
18
20
|
null,
|
|
21
|
+
makeClassification({ taskIntent: 'coding', workType: 'coding' }),
|
|
19
22
|
)
|
|
20
23
|
assert.equal(decision.intent, 'coding')
|
|
21
24
|
})
|
|
@@ -25,6 +28,14 @@ test('routeTaskIntent keeps hybrid research-plus-media prompts in research inten
|
|
|
25
28
|
'Can you tell me more if there is any news related to the US-Iran war, and can you send me some screenshots and give me a summary and maybe send me a voice note about it?',
|
|
26
29
|
['web_search', 'web_fetch', 'browser', 'manage_connectors'],
|
|
27
30
|
null,
|
|
31
|
+
makeClassification({
|
|
32
|
+
taskIntent: 'research',
|
|
33
|
+
workType: 'research',
|
|
34
|
+
wantsScreenshots: true,
|
|
35
|
+
wantsVoiceDelivery: true,
|
|
36
|
+
wantsOutboundDelivery: true,
|
|
37
|
+
isResearchSynthesis: true,
|
|
38
|
+
}),
|
|
28
39
|
)
|
|
29
40
|
|
|
30
41
|
assert.equal(decision.intent, 'research')
|
|
@@ -36,6 +47,12 @@ test('routeTaskIntent treats direct voice-note delivery as outreach', () => {
|
|
|
36
47
|
'Send me a voice note over WhatsApp summarizing what changed.',
|
|
37
48
|
['manage_connectors'],
|
|
38
49
|
null,
|
|
50
|
+
makeClassification({
|
|
51
|
+
taskIntent: 'outreach',
|
|
52
|
+
workType: 'writing',
|
|
53
|
+
wantsVoiceDelivery: true,
|
|
54
|
+
wantsOutboundDelivery: true,
|
|
55
|
+
}),
|
|
39
56
|
)
|
|
40
57
|
|
|
41
58
|
assert.equal(decision.intent, 'outreach')
|
|
@@ -45,10 +62,62 @@ test('routeTaskIntent treats direct voice-note delivery as outreach', () => {
|
|
|
45
62
|
test('routeTaskIntent treats keep-watching update requests as research even without explicit news keywords', () => {
|
|
46
63
|
const decision = routeTaskIntent(
|
|
47
64
|
'Tell me about the Iran war, keep watching for meaningful updates, and avoid duplicate reminders.',
|
|
48
|
-
['web_search', 'manage_schedules'],
|
|
65
|
+
['web_search', 'web_fetch', 'manage_schedules'],
|
|
49
66
|
null,
|
|
67
|
+
makeClassification({
|
|
68
|
+
taskIntent: 'research',
|
|
69
|
+
workType: 'research',
|
|
70
|
+
isResearchSynthesis: true,
|
|
71
|
+
}),
|
|
50
72
|
)
|
|
51
73
|
|
|
52
74
|
assert.equal(decision.intent, 'research')
|
|
53
75
|
assert.deepEqual(decision.preferredTools, ['web_search', 'web_fetch'])
|
|
54
76
|
})
|
|
77
|
+
|
|
78
|
+
test('routeTaskIntent uses structured classification when available', () => {
|
|
79
|
+
const classification: MessageClassification = {
|
|
80
|
+
taskIntent: 'browsing',
|
|
81
|
+
isDeliverableTask: true,
|
|
82
|
+
isBroadGoal: false,
|
|
83
|
+
walletIntent: 'none',
|
|
84
|
+
hasHumanSignals: false,
|
|
85
|
+
hasSignificantEvent: false,
|
|
86
|
+
isResearchSynthesis: true,
|
|
87
|
+
workType: 'research',
|
|
88
|
+
wantsScreenshots: true,
|
|
89
|
+
wantsOutboundDelivery: false,
|
|
90
|
+
wantsVoiceDelivery: false,
|
|
91
|
+
explicitToolRequests: ['browser'],
|
|
92
|
+
confidence: 0.92,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const decision = routeTaskIntent(
|
|
96
|
+
'Review this story and show me screenshots.',
|
|
97
|
+
['web_search', 'web_fetch', 'browser'],
|
|
98
|
+
null,
|
|
99
|
+
classification,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
assert.equal(decision.intent, 'browsing')
|
|
103
|
+
assert.deepEqual(decision.preferredTools, ['browser', 'web_fetch'])
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
function makeClassification(overrides: Partial<MessageClassification>): MessageClassification {
|
|
107
|
+
return {
|
|
108
|
+
taskIntent: 'general',
|
|
109
|
+
isDeliverableTask: false,
|
|
110
|
+
isBroadGoal: false,
|
|
111
|
+
walletIntent: 'none',
|
|
112
|
+
hasHumanSignals: false,
|
|
113
|
+
hasSignificantEvent: false,
|
|
114
|
+
isResearchSynthesis: false,
|
|
115
|
+
workType: 'general',
|
|
116
|
+
wantsScreenshots: false,
|
|
117
|
+
wantsOutboundDelivery: false,
|
|
118
|
+
wantsVoiceDelivery: false,
|
|
119
|
+
explicitToolRequests: [],
|
|
120
|
+
confidence: 0.9,
|
|
121
|
+
...overrides,
|
|
122
|
+
}
|
|
123
|
+
}
|