@swarmclawai/swarmclaw 1.2.4 → 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 +14 -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]/route.test.ts +49 -0
- 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 +5 -5
- package/src/components/auth/setup-wizard/index.tsx +4 -4
- package/src/components/auth/setup-wizard/step-agents.tsx +1 -1
- package/src/components/auth/setup-wizard/step-connect.tsx +1 -1
- package/src/components/auth/setup-wizard/utils.ts +1 -1
- 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 +60 -61
- package/src/components/providers/provider-sheet.tsx +74 -56
- 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/app/api-client.ts +2 -2
- package/src/lib/query/client.ts +17 -0
- package/src/lib/server/agents/agent-runtime-config.ts +1 -1
- 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 +44 -267
- package/src/lib/server/storage-normalization.ts +75 -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 +277 -12
|
@@ -34,8 +34,16 @@ import { isHeartbeatSource } from '@/lib/server/runtime/heartbeat-source'
|
|
|
34
34
|
import { perf } from '@/lib/server/runtime/perf'
|
|
35
35
|
import { getAgent } from '@/lib/server/agents/agent-repository'
|
|
36
36
|
import { isDirectConnectorSession } from '@/lib/server/connectors/session-kind'
|
|
37
|
-
import { getSession,
|
|
37
|
+
import { getSession, saveSession } from '@/lib/server/sessions/session-repository'
|
|
38
|
+
import {
|
|
39
|
+
getMessages,
|
|
40
|
+
getMessageCount,
|
|
41
|
+
appendMessage,
|
|
42
|
+
replaceMessageAt,
|
|
43
|
+
replaceAllMessages,
|
|
44
|
+
} from '@/lib/server/messages/message-repository'
|
|
38
45
|
import { appendUsage } from '@/lib/server/usage/usage-repository'
|
|
46
|
+
import { synchronizeWorkingStateForTurn } from '@/lib/server/working-state/service'
|
|
39
47
|
import { notify } from '@/lib/server/ws-hub'
|
|
40
48
|
|
|
41
49
|
import type { ExecuteChatTurnInput, ExecuteChatTurnResult } from './chat-execution-types'
|
|
@@ -193,7 +201,7 @@ export async function finalizeChatTurn(params: {
|
|
|
193
201
|
const totalTokens = inputTokens + outputTokens
|
|
194
202
|
if (totalTokens > 0) {
|
|
195
203
|
const cost = estimateCost(sessionForRun.model, inputTokens, outputTokens)
|
|
196
|
-
const history =
|
|
204
|
+
const history = getMessages(sessionId)
|
|
197
205
|
const usageRecord: UsageRecord = {
|
|
198
206
|
sessionId,
|
|
199
207
|
messageIndex: history.length,
|
|
@@ -354,15 +362,17 @@ export async function finalizeChatTurn(params: {
|
|
|
354
362
|
const current = getSession(sessionId)
|
|
355
363
|
let assistantPersisted = false
|
|
356
364
|
if (current) {
|
|
357
|
-
|
|
365
|
+
// Load messages from relational table (lazy-migrates from blob on first access)
|
|
366
|
+
const messages = getMessages(sessionId)
|
|
367
|
+
let messagesPruned = false
|
|
358
368
|
if (!isDirectConnectorSession(current) && current.connectorContext) {
|
|
359
369
|
current.connectorContext = undefined
|
|
360
370
|
}
|
|
361
371
|
const currentAgent = current.agentId ? getAgent(current.agentId) : null
|
|
362
|
-
pruneStreamingAssistantArtifacts(
|
|
372
|
+
if (pruneStreamingAssistantArtifacts(messages, {
|
|
363
373
|
minIndex: runMessageStartIndex,
|
|
364
374
|
minTime: runStartedAt,
|
|
365
|
-
})
|
|
375
|
+
})) messagesPruned = true
|
|
366
376
|
const persistField = (key: string, value: unknown) => {
|
|
367
377
|
const normalized = normalizeResumeId(value)
|
|
368
378
|
if ((current as unknown as Record<string, unknown>)[key] !== normalized) {
|
|
@@ -392,6 +402,8 @@ export async function finalizeChatTurn(params: {
|
|
|
392
402
|
}
|
|
393
403
|
}
|
|
394
404
|
|
|
405
|
+
let persistedAssistantMsg: Message | null = null
|
|
406
|
+
let replacedLast = false
|
|
395
407
|
if (shouldPersistAssistant) {
|
|
396
408
|
const persistedKind = isHeartbeatRun ? 'heartbeat' : 'chat'
|
|
397
409
|
const nowTs = Date.now()
|
|
@@ -410,7 +422,7 @@ export async function finalizeChatTurn(params: {
|
|
|
410
422
|
runId: lifecycleRunId,
|
|
411
423
|
})
|
|
412
424
|
if (nextAssistantMessage) {
|
|
413
|
-
const previous =
|
|
425
|
+
const previous = messages.at(-1)
|
|
414
426
|
const nextToolEvents = nextAssistantMessage.toolEvents || []
|
|
415
427
|
const nextKind = nextAssistantMessage.kind || persistedKind
|
|
416
428
|
if (shouldSuppressRedundantConnectorDeliveryFollowup({
|
|
@@ -433,11 +445,14 @@ export async function finalizeChatTurn(params: {
|
|
|
433
445
|
nextKind,
|
|
434
446
|
now: nowTs,
|
|
435
447
|
})) {
|
|
436
|
-
|
|
448
|
+
messages[messages.length - 1] = nextAssistantMessage
|
|
437
449
|
assistantPersisted = true
|
|
450
|
+
replacedLast = true
|
|
451
|
+
persistedAssistantMsg = nextAssistantMessage
|
|
438
452
|
} else {
|
|
439
|
-
|
|
453
|
+
messages.push(nextAssistantMessage)
|
|
440
454
|
assistantPersisted = true
|
|
455
|
+
persistedAssistantMsg = nextAssistantMessage
|
|
441
456
|
}
|
|
442
457
|
persistedResponseForHooks = nextAssistantMessage.text
|
|
443
458
|
if (assistantPersisted) {
|
|
@@ -534,16 +549,28 @@ export async function finalizeChatTurn(params: {
|
|
|
534
549
|
}
|
|
535
550
|
}
|
|
536
551
|
if (isHeartbeatRun && heartbeatClassification === 'suppress') {
|
|
537
|
-
pruneSuppressedHeartbeatStreamMessage(
|
|
552
|
+
if (pruneSuppressedHeartbeatStreamMessage(messages)) messagesPruned = true
|
|
538
553
|
}
|
|
539
554
|
|
|
540
555
|
if (isHeartbeatRun) {
|
|
541
|
-
const pruned = pruneOldHeartbeatMessages(
|
|
556
|
+
const pruned = pruneOldHeartbeatMessages(messages)
|
|
542
557
|
if (pruned > 0) {
|
|
558
|
+
messagesPruned = true
|
|
543
559
|
log.info('heartbeat', `Pruned ${pruned} old heartbeat message(s) from session ${sessionId}`)
|
|
544
560
|
}
|
|
545
561
|
}
|
|
546
562
|
|
|
563
|
+
// Persist messages: use O(1) append/replace when no pruning, O(n) replaceAll when pruned
|
|
564
|
+
if (messagesPruned) {
|
|
565
|
+
replaceAllMessages(sessionId, messages)
|
|
566
|
+
} else if (assistantPersisted && persistedAssistantMsg) {
|
|
567
|
+
if (replacedLast) {
|
|
568
|
+
replaceMessageAt(sessionId, getMessageCount(sessionId) - 1, persistedAssistantMsg)
|
|
569
|
+
} else {
|
|
570
|
+
appendMessage(sessionId, persistedAssistantMsg)
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
547
574
|
try {
|
|
548
575
|
await runCapabilityHook('afterChatTurn', {
|
|
549
576
|
session: current,
|
|
@@ -563,8 +590,9 @@ export async function finalizeChatTurn(params: {
|
|
|
563
590
|
|
|
564
591
|
refreshSessionIdentityState(current, currentAgent)
|
|
565
592
|
let resolvedMissionId = mission?.id || current.missionId || null
|
|
593
|
+
let updatedMission = mission || null
|
|
566
594
|
if (resolvedMissionId) {
|
|
567
|
-
|
|
595
|
+
updatedMission = await applyMissionOutcomeForTurn({
|
|
568
596
|
session: current,
|
|
569
597
|
missionId: resolvedMissionId,
|
|
570
598
|
source,
|
|
@@ -579,6 +607,43 @@ export async function finalizeChatTurn(params: {
|
|
|
579
607
|
current.missionId = updatedMission.id
|
|
580
608
|
}
|
|
581
609
|
}
|
|
610
|
+
const missionStateChanged = Boolean(
|
|
611
|
+
updatedMission
|
|
612
|
+
&& (
|
|
613
|
+
updatedMission.id !== mission?.id
|
|
614
|
+
|| updatedMission.updatedAt !== mission?.updatedAt
|
|
615
|
+
|| updatedMission.status !== mission?.status
|
|
616
|
+
|| updatedMission.phase !== mission?.phase
|
|
617
|
+
|| updatedMission.currentStep !== mission?.currentStep
|
|
618
|
+
|| updatedMission.waitState?.reason !== mission?.waitState?.reason
|
|
619
|
+
)
|
|
620
|
+
)
|
|
621
|
+
const shouldSyncWorkingState = (
|
|
622
|
+
(!isHeartbeatRun && (assistantPersisted || persistedToolEvents.length > 0 || Boolean(errorMessage)))
|
|
623
|
+
|| (isHeartbeatRun && (persistedToolEvents.length > 0 || Boolean(errorMessage) || missionStateChanged))
|
|
624
|
+
)
|
|
625
|
+
if (shouldSyncWorkingState) {
|
|
626
|
+
try {
|
|
627
|
+
await synchronizeWorkingStateForTurn({
|
|
628
|
+
sessionId,
|
|
629
|
+
agentId: current.agentId || null,
|
|
630
|
+
mission: updatedMission,
|
|
631
|
+
message,
|
|
632
|
+
assistantText: hiddenControlOnly ? '' : textForPersistence,
|
|
633
|
+
error: errorMessage || null,
|
|
634
|
+
toolEvents: persistedToolEvents,
|
|
635
|
+
runId: lifecycleRunId,
|
|
636
|
+
source,
|
|
637
|
+
})
|
|
638
|
+
} catch (workingStateError: unknown) {
|
|
639
|
+
log.warn('chat-run', `Working-state sync failed for session ${sessionId}`, {
|
|
640
|
+
runId: lifecycleRunId,
|
|
641
|
+
error: typeof workingStateError === 'object' && workingStateError !== null && 'message' in workingStateError
|
|
642
|
+
? (workingStateError as Error).message
|
|
643
|
+
: String(workingStateError),
|
|
644
|
+
})
|
|
645
|
+
}
|
|
646
|
+
}
|
|
582
647
|
try {
|
|
583
648
|
syncSessionArchiveMemory(current, { agent: currentAgent })
|
|
584
649
|
} catch {
|
|
@@ -591,7 +656,7 @@ export async function finalizeChatTurn(params: {
|
|
|
591
656
|
isHeartbeatRun,
|
|
592
657
|
agentAutoDraftSetting: currentAgent?.autoDraftSkillSuggestions === true,
|
|
593
658
|
toolEventCount: persistedToolEvents.length,
|
|
594
|
-
messageCount:
|
|
659
|
+
messageCount: messages.length,
|
|
595
660
|
})) {
|
|
596
661
|
try {
|
|
597
662
|
const { createSkillSuggestionFromSession } = await import('@/lib/server/skills/skill-suggestions')
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getSession,
|
|
3
|
-
saveSession,
|
|
4
3
|
} from '@/lib/server/sessions/session-repository'
|
|
4
|
+
import { getMessages, replaceAllMessages } from '@/lib/server/messages/message-repository'
|
|
5
5
|
import { notify } from '@/lib/server/ws-hub'
|
|
6
6
|
import type { MessageToolEvent, SSEEvent } from '@/types'
|
|
7
7
|
import { upsertStreamingAssistantArtifact } from '@/lib/chat/chat-streaming-state'
|
|
@@ -72,7 +72,7 @@ export function createPartialAssistantPersistence(input: {
|
|
|
72
72
|
try {
|
|
73
73
|
const current = getSession(prepared.sessionId)
|
|
74
74
|
if (!current) return
|
|
75
|
-
|
|
75
|
+
const currentMessages = getMessages(prepared.sessionId)
|
|
76
76
|
const partialMsg = await applyMessageLifecycleHooks({
|
|
77
77
|
session: current,
|
|
78
78
|
message: {
|
|
@@ -98,11 +98,11 @@ export function createPartialAssistantPersistence(input: {
|
|
|
98
98
|
if (snapshotKey === lastPartialSnapshotKey) return
|
|
99
99
|
lastPartialSnapshotKey = snapshotKey
|
|
100
100
|
lastPartialSaveAt = Date.now()
|
|
101
|
-
upsertStreamingAssistantArtifact(
|
|
101
|
+
upsertStreamingAssistantArtifact(currentMessages, partialMsg, {
|
|
102
102
|
minIndex: prepared.runMessageStartIndex,
|
|
103
103
|
minTime: prepared.runStartedAt,
|
|
104
104
|
})
|
|
105
|
-
|
|
105
|
+
replaceAllMessages(prepared.sessionId, currentMessages)
|
|
106
106
|
notify(`messages:${prepared.sessionId}`)
|
|
107
107
|
} catch {
|
|
108
108
|
// Partial persistence is best-effort.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { notify } from '@/lib/server/ws-hub'
|
|
2
2
|
import { getSession, saveSession } from '@/lib/server/sessions/session-repository'
|
|
3
|
+
import { appendMessage } from '@/lib/server/messages/message-repository'
|
|
3
4
|
import type { MessageToolEvent, SSEEvent } from '@/types'
|
|
4
5
|
import type { ExecuteChatTurnResult } from './chat-execution-types'
|
|
5
6
|
import {
|
|
@@ -47,8 +48,7 @@ async function completeSyntheticAssistantTurn(params: {
|
|
|
47
48
|
isSynthetic: true,
|
|
48
49
|
})
|
|
49
50
|
if (nextAssistantMessage) {
|
|
50
|
-
|
|
51
|
-
session.messages.push(nextAssistantMessage)
|
|
51
|
+
appendMessage(params.sessionId, nextAssistantMessage)
|
|
52
52
|
session.lastActiveAt = Date.now()
|
|
53
53
|
saveSession(params.sessionId, session)
|
|
54
54
|
if (params.notifyMessages) notify(`messages:${params.sessionId}`)
|
|
@@ -2,13 +2,14 @@ import fs from 'fs'
|
|
|
2
2
|
import os from 'os'
|
|
3
3
|
|
|
4
4
|
import { getProvider } from '@/lib/providers'
|
|
5
|
-
import type { Message, Session } from '@/types'
|
|
5
|
+
import type { ExecutionBrief, Message, Session } from '@/types'
|
|
6
6
|
import {
|
|
7
7
|
decryptKey,
|
|
8
8
|
loadCredentials,
|
|
9
9
|
} from '@/lib/server/credentials/credential-repository'
|
|
10
10
|
import { getAgent } from '@/lib/server/agents/agent-repository'
|
|
11
11
|
import { getSession, saveSession } from '@/lib/server/sessions/session-repository'
|
|
12
|
+
import { getMessages, getMessageCount, appendMessage } from '@/lib/server/messages/message-repository'
|
|
12
13
|
import { loadSettings } from '@/lib/server/settings/settings-repository'
|
|
13
14
|
import { loadSkills } from '@/lib/server/skills/skill-repository'
|
|
14
15
|
import { resolveImagePath } from '@/lib/server/resolve-image'
|
|
@@ -45,7 +46,6 @@ import {
|
|
|
45
46
|
import { normalizeProviderEndpoint, isLocalOpenClawEndpoint } from '@/lib/openclaw/openclaw-endpoint'
|
|
46
47
|
import { NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
|
|
47
48
|
import {
|
|
48
|
-
buildMissionContextBlock,
|
|
49
49
|
resolveMissionForTurn,
|
|
50
50
|
} from '@/lib/server/missions/mission-service'
|
|
51
51
|
import {
|
|
@@ -69,7 +69,15 @@ import {
|
|
|
69
69
|
resetSessionRuntime,
|
|
70
70
|
resolveSessionResetPolicy,
|
|
71
71
|
} from '@/lib/server/session-reset-policy'
|
|
72
|
+
import {
|
|
73
|
+
buildExecutionBrief,
|
|
74
|
+
buildExecutionBriefContextBlock,
|
|
75
|
+
} from '@/lib/server/execution-brief'
|
|
72
76
|
import { checkAgentBudgetLimits } from '@/lib/server/cost'
|
|
77
|
+
import {
|
|
78
|
+
classifyMessage,
|
|
79
|
+
toMessageSemanticsSummary,
|
|
80
|
+
} from '@/lib/server/chat-execution/message-classifier'
|
|
73
81
|
import {
|
|
74
82
|
filterRuntimeCapabilityIds,
|
|
75
83
|
getTodaySpendUsd,
|
|
@@ -408,13 +416,13 @@ function resolveApiKeyForSession(session: SessionWithCredentials, provider: Prov
|
|
|
408
416
|
if (!session.credentialId) throw new Error('No API key configured for this session')
|
|
409
417
|
const creds = loadCredentials()
|
|
410
418
|
const cred = creds[session.credentialId]
|
|
411
|
-
if (!cred) throw new Error('API key not found. Please add one in Settings.')
|
|
419
|
+
if (!cred?.encryptedKey) throw new Error('API key not found. Please add one in Settings.')
|
|
412
420
|
return decryptKey(cred.encryptedKey)
|
|
413
421
|
}
|
|
414
422
|
if (provider.optionalApiKey && session.credentialId) {
|
|
415
423
|
const creds = loadCredentials()
|
|
416
424
|
const cred = creds[session.credentialId]
|
|
417
|
-
if (cred) {
|
|
425
|
+
if (cred?.encryptedKey) {
|
|
418
426
|
try { return decryptKey(cred.encryptedKey) } catch { return null }
|
|
419
427
|
}
|
|
420
428
|
}
|
|
@@ -445,7 +453,8 @@ export interface PreparedExecutableChatTurn {
|
|
|
445
453
|
lifecycleRunId: string
|
|
446
454
|
agentForSession: ReturnType<typeof getAgent>
|
|
447
455
|
mission: Awaited<ReturnType<typeof resolveMissionForTurn>>
|
|
448
|
-
|
|
456
|
+
executionBrief: ExecutionBrief
|
|
457
|
+
executionBriefContextBlock?: string
|
|
449
458
|
extensionsForRun: string[]
|
|
450
459
|
effectiveMessage: string
|
|
451
460
|
providerType: string
|
|
@@ -492,9 +501,8 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
|
|
|
492
501
|
|
|
493
502
|
const session = getSession(sessionId)
|
|
494
503
|
if (!session) throw new Error(`Session not found: ${sessionId}`)
|
|
495
|
-
session.messages = Array.isArray(session.messages) ? session.messages : []
|
|
496
504
|
const runStartedAt = Date.now()
|
|
497
|
-
const runMessageStartIndex =
|
|
505
|
+
const runMessageStartIndex = getMessageCount(sessionId)
|
|
498
506
|
|
|
499
507
|
const appSettings = loadSettings()
|
|
500
508
|
const lifecycleRunId = runId || `${sessionId}:${runStartedAt}`
|
|
@@ -542,7 +550,7 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
|
|
|
542
550
|
{
|
|
543
551
|
sessionId: session.id,
|
|
544
552
|
session,
|
|
545
|
-
messageCount:
|
|
553
|
+
messageCount: getMessageCount(sessionId),
|
|
546
554
|
durationMs: Date.now() - (session.createdAt || runStartedAt),
|
|
547
555
|
reason: freshness.reason || 'session_reset',
|
|
548
556
|
},
|
|
@@ -615,12 +623,16 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
|
|
|
615
623
|
if (isHeartbeatRun && input.modelOverride) {
|
|
616
624
|
sessionForRun = { ...sessionForRun, model: input.modelOverride }
|
|
617
625
|
}
|
|
618
|
-
const
|
|
626
|
+
const executionBrief = buildExecutionBrief({
|
|
627
|
+
session: sessionForRun,
|
|
628
|
+
mission,
|
|
629
|
+
})
|
|
630
|
+
const executionBriefContextBlock = buildExecutionBriefContextBlock(executionBrief)
|
|
619
631
|
|
|
620
632
|
if (extensionsForRun.length > 0) {
|
|
621
633
|
const modelResolvePrompt = heartbeatLightContext
|
|
622
|
-
? (joinSystemPromptBlocks(buildLightHeartbeatSystemPrompt(sessionForRun),
|
|
623
|
-
: (joinSystemPromptBlocks(buildAgentSystemPrompt(sessionForRun),
|
|
634
|
+
? (joinSystemPromptBlocks(buildLightHeartbeatSystemPrompt(sessionForRun), executionBriefContextBlock) || '')
|
|
635
|
+
: (joinSystemPromptBlocks(buildAgentSystemPrompt(sessionForRun), executionBriefContextBlock) || '')
|
|
624
636
|
const modelResolve = await runCapabilityBeforeModelResolve(
|
|
625
637
|
{
|
|
626
638
|
session: sessionForRun,
|
|
@@ -710,7 +722,17 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
|
|
|
710
722
|
|
|
711
723
|
const shouldPersistUserMessage = shouldPersistInboundUserMessage(internal, source)
|
|
712
724
|
if (shouldPersistUserMessage) {
|
|
713
|
-
const linkAnalysis =
|
|
725
|
+
const [linkAnalysis, semantics] = await Promise.all([
|
|
726
|
+
!internal ? runLinkUnderstanding(message) : Promise.resolve([]),
|
|
727
|
+
classifyMessage({
|
|
728
|
+
sessionId,
|
|
729
|
+
agentId: session.agentId || null,
|
|
730
|
+
message,
|
|
731
|
+
history: getMessages(sessionId),
|
|
732
|
+
})
|
|
733
|
+
.then((classification) => toMessageSemanticsSummary(classification))
|
|
734
|
+
.catch(() => undefined),
|
|
735
|
+
])
|
|
714
736
|
const guardedUserText = guardUntrustedText({
|
|
715
737
|
text: message,
|
|
716
738
|
source,
|
|
@@ -727,13 +749,14 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
|
|
|
727
749
|
imageUrl: imageUrl || undefined,
|
|
728
750
|
attachedFiles: attachedFiles?.length ? attachedFiles : undefined,
|
|
729
751
|
replyToId: input.replyToId || undefined,
|
|
752
|
+
...(semantics ? { semantics } : {}),
|
|
730
753
|
},
|
|
731
754
|
enabledIds: extensionsForRun,
|
|
732
755
|
phase: 'user',
|
|
733
756
|
runId: lifecycleRunId,
|
|
734
757
|
})
|
|
735
758
|
if (nextUserMessage) {
|
|
736
|
-
|
|
759
|
+
appendMessage(sessionId, nextUserMessage)
|
|
737
760
|
if (linkAnalysis.length > 0) {
|
|
738
761
|
const linkAnalysisMessage = await applyMessageLifecycleHooks({
|
|
739
762
|
session,
|
|
@@ -749,7 +772,7 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
|
|
|
749
772
|
isSynthetic: true,
|
|
750
773
|
})
|
|
751
774
|
if (linkAnalysisMessage) {
|
|
752
|
-
|
|
775
|
+
appendMessage(sessionId, linkAnalysisMessage)
|
|
753
776
|
}
|
|
754
777
|
}
|
|
755
778
|
session.lastActiveAt = Date.now()
|
|
@@ -781,8 +804,8 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
|
|
|
781
804
|
&& !useLocalOpenClawNativeRuntime
|
|
782
805
|
|
|
783
806
|
const systemPrompt = heartbeatLightContext
|
|
784
|
-
? joinSystemPromptBlocks(buildLightHeartbeatSystemPrompt(sessionForRun),
|
|
785
|
-
: (hasExtensions ? undefined : joinSystemPromptBlocks(buildAgentSystemPrompt(sessionForRun),
|
|
807
|
+
? joinSystemPromptBlocks(buildLightHeartbeatSystemPrompt(sessionForRun), executionBriefContextBlock)
|
|
808
|
+
: (hasExtensions ? undefined : joinSystemPromptBlocks(buildAgentSystemPrompt(sessionForRun), executionBriefContextBlock))
|
|
786
809
|
|
|
787
810
|
return {
|
|
788
811
|
kind: 'ready',
|
|
@@ -797,7 +820,8 @@ export async function prepareChatTurn(input: ExecuteChatTurnInput): Promise<Prep
|
|
|
797
820
|
lifecycleRunId,
|
|
798
821
|
agentForSession,
|
|
799
822
|
mission,
|
|
800
|
-
|
|
823
|
+
executionBrief,
|
|
824
|
+
executionBriefContextBlock: executionBriefContextBlock || undefined,
|
|
801
825
|
extensionsForRun,
|
|
802
826
|
effectiveMessage,
|
|
803
827
|
providerType,
|
|
@@ -74,7 +74,8 @@ export async function executePreparedChatTurn(params: {
|
|
|
74
74
|
resolvedImagePath,
|
|
75
75
|
heartbeatLightContext,
|
|
76
76
|
isAutoRunNoHistory,
|
|
77
|
-
|
|
77
|
+
executionBrief,
|
|
78
|
+
executionBriefContextBlock,
|
|
78
79
|
} = prepared
|
|
79
80
|
|
|
80
81
|
const emit = partialPersistence.emit
|
|
@@ -144,7 +145,8 @@ export async function executePreparedChatTurn(params: {
|
|
|
144
145
|
attachedFiles,
|
|
145
146
|
apiKey,
|
|
146
147
|
systemPrompt,
|
|
147
|
-
|
|
148
|
+
executionBrief,
|
|
149
|
+
extraSystemContext: [executionBriefContextBlock].filter((value): value is string => typeof value === 'string' && value.trim().length > 0),
|
|
148
150
|
write: (raw) => parseAndEmit(raw),
|
|
149
151
|
history: heartbeatHistory ?? applyContextClearBoundary(getSessionMessages(sessionId)),
|
|
150
152
|
signal: abortController.signal,
|
|
@@ -439,6 +439,51 @@ describe('chat-turn-tool-routing', () => {
|
|
|
439
439
|
assert.equal(result.calledNames.has('memory_search'), true)
|
|
440
440
|
})
|
|
441
441
|
|
|
442
|
+
it('uses classifier-backed memory list fallback for broad memory inventory requests', async () => {
|
|
443
|
+
const invocations: Array<{ toolName: string; args: Record<string, unknown> }> = []
|
|
444
|
+
const result = await runPostLlmToolRouting({
|
|
445
|
+
session: {
|
|
446
|
+
cwd: process.cwd(),
|
|
447
|
+
tools: ['memory'],
|
|
448
|
+
},
|
|
449
|
+
sessionId: 'session-memory-list',
|
|
450
|
+
message: 'List everything you remember about me.',
|
|
451
|
+
effectiveMessage: 'List everything you remember about me.',
|
|
452
|
+
enabledExtensions: ['memory'],
|
|
453
|
+
toolPolicy: resolveSessionToolPolicy(['memory'], {}),
|
|
454
|
+
appSettings: {},
|
|
455
|
+
internal: false,
|
|
456
|
+
source: 'chat',
|
|
457
|
+
toolEvents: [],
|
|
458
|
+
emit: () => {},
|
|
459
|
+
}, '', undefined, {
|
|
460
|
+
classifyDirectMemoryIntent: async () => ({
|
|
461
|
+
action: 'list',
|
|
462
|
+
confidence: 0.91,
|
|
463
|
+
}),
|
|
464
|
+
invokeTool: async (_ctx, toolName, args, _failurePrefix, calledNames) => {
|
|
465
|
+
invocations.push({ toolName, args })
|
|
466
|
+
calledNames.add(toolName)
|
|
467
|
+
return {
|
|
468
|
+
invoked: true,
|
|
469
|
+
responseOverride: null,
|
|
470
|
+
toolOutputText: '[mem_1] favorite editor: Neovim\n[mem_2] timezone: Europe/Isle_of_Man',
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
assert.equal(invocations.length, 1)
|
|
476
|
+
assert.equal(invocations[0].toolName, 'memory_tool')
|
|
477
|
+
assert.deepEqual(invocations[0].args, {
|
|
478
|
+
action: 'list',
|
|
479
|
+
key: '',
|
|
480
|
+
scope: 'auto',
|
|
481
|
+
})
|
|
482
|
+
assert.equal(result.fullResponse, '[mem_1] favorite editor: Neovim\n[mem_2] timezone: Europe/Isle_of_Man')
|
|
483
|
+
assert.equal(result.errorMessage, undefined)
|
|
484
|
+
assert.equal(result.calledNames.has('memory_tool'), true)
|
|
485
|
+
})
|
|
486
|
+
|
|
442
487
|
it('fails open when post-LLM memory classification times out', async () => {
|
|
443
488
|
let invoked = false
|
|
444
489
|
const started = Date.now()
|
|
@@ -22,6 +22,7 @@ import { log } from '@/lib/server/logger'
|
|
|
22
22
|
import { rankDelegatesByHealth } from '@/lib/server/provider-health'
|
|
23
23
|
import { routeTaskIntent, type CapabilityRoutingDecision } from '@/lib/server/capability-router'
|
|
24
24
|
import { canonicalizeExtensionId, extensionIdMatches } from '@/lib/server/tool-aliases'
|
|
25
|
+
import { classifyMessage, type MessageClassification } from '@/lib/server/chat-execution/message-classifier'
|
|
25
26
|
import {
|
|
26
27
|
buildDirectMemoryRecallResponse,
|
|
27
28
|
classifyDirectMemoryIntent,
|
|
@@ -37,7 +38,6 @@ import {
|
|
|
37
38
|
findFirstUrl,
|
|
38
39
|
hasToolEnabled,
|
|
39
40
|
hasDirectLocalCodingTools,
|
|
40
|
-
isMemoryListIntent,
|
|
41
41
|
requestedToolNamesFromMessage,
|
|
42
42
|
translateRequestedToolInvocation,
|
|
43
43
|
} from '@/lib/server/chat-execution/chat-execution-utils'
|
|
@@ -61,6 +61,7 @@ export interface ToolRoutingContext {
|
|
|
61
61
|
source: string
|
|
62
62
|
toolEvents: MessageToolEvent[]
|
|
63
63
|
emit: (ev: SSEEvent) => void
|
|
64
|
+
classification?: MessageClassification | null
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
export interface ToolRoutingResult {
|
|
@@ -126,6 +127,16 @@ async function resolveDirectMemoryIntentWithTimeout(
|
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
async function resolveTurnClassification(ctx: ToolRoutingContext): Promise<MessageClassification | null> {
|
|
131
|
+
if (ctx.classification !== undefined) return ctx.classification ?? null
|
|
132
|
+
if (ctx.internal || ctx.source !== 'chat') return null
|
|
133
|
+
return classifyMessage({
|
|
134
|
+
sessionId: ctx.sessionId,
|
|
135
|
+
agentId: ctx.session.agentId || null,
|
|
136
|
+
message: ctx.message,
|
|
137
|
+
}).catch(() => null)
|
|
138
|
+
}
|
|
139
|
+
|
|
129
140
|
export async function runExclusiveDirectMemoryPreflight(
|
|
130
141
|
ctx: ToolRoutingContext,
|
|
131
142
|
hooks?: ToolRoutingHooks,
|
|
@@ -148,14 +159,18 @@ export async function runExclusiveDirectMemoryPreflight(
|
|
|
148
159
|
? 'memory_store'
|
|
149
160
|
: directMemoryIntent.action === 'update'
|
|
150
161
|
? 'memory_update'
|
|
151
|
-
: '
|
|
162
|
+
: directMemoryIntent.action === 'list'
|
|
163
|
+
? 'memory_tool'
|
|
164
|
+
: 'memory_search'
|
|
152
165
|
|
|
153
166
|
const args: Record<string, unknown> = directMemoryIntent.action === 'recall'
|
|
154
167
|
? { query: directMemoryIntent.query, scope: 'auto' }
|
|
155
|
-
:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
168
|
+
: directMemoryIntent.action === 'list'
|
|
169
|
+
? { action: 'list', key: '', scope: 'auto' }
|
|
170
|
+
: {
|
|
171
|
+
value: directMemoryIntent.value,
|
|
172
|
+
...(directMemoryIntent.title ? { title: directMemoryIntent.title } : {}),
|
|
173
|
+
}
|
|
159
174
|
|
|
160
175
|
const result = await invokeTool(
|
|
161
176
|
ctx,
|
|
@@ -192,6 +207,15 @@ export async function runExclusiveDirectMemoryPreflight(
|
|
|
192
207
|
}
|
|
193
208
|
}
|
|
194
209
|
|
|
210
|
+
if (directMemoryIntent.action === 'list') {
|
|
211
|
+
return {
|
|
212
|
+
calledNames,
|
|
213
|
+
fullResponse: String(result.toolOutputText || '').trim() || 'No memories found.',
|
|
214
|
+
errorMessage: undefined,
|
|
215
|
+
missedRequestedTools: [],
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
195
219
|
if (directMemoryIntent.action === 'recall') {
|
|
196
220
|
const recallResponse = result.toolOutputText
|
|
197
221
|
? buildDirectMemoryRecallResponse(directMemoryIntent, result.toolOutputText)
|
|
@@ -494,12 +518,13 @@ export async function runPostLlmToolRouting(
|
|
|
494
518
|
const unavailableRequestedTools = new Map<string, string>()
|
|
495
519
|
let fullResponse = currentResponse
|
|
496
520
|
let errorMessage = currentError
|
|
521
|
+
const classification = await resolveTurnClassification(ctx)
|
|
497
522
|
|
|
498
523
|
const requestedToolNames = (!ctx.internal && ctx.source === 'chat')
|
|
499
524
|
? requestedToolNamesFromMessage(ctx.message)
|
|
500
525
|
: []
|
|
501
526
|
const routingDecision: CapabilityRoutingDecision | null = (!ctx.internal && ctx.source === 'chat')
|
|
502
|
-
? routeTaskIntent(ctx.message, ctx.enabledExtensions, ctx.appSettings)
|
|
527
|
+
? routeTaskIntent(ctx.message, ctx.enabledExtensions, ctx.appSettings, classification)
|
|
503
528
|
: null
|
|
504
529
|
|
|
505
530
|
// --- Forced connector_message_tool ---
|
|
@@ -609,6 +634,22 @@ export async function runPostLlmToolRouting(
|
|
|
609
634
|
}
|
|
610
635
|
}
|
|
611
636
|
|
|
637
|
+
if (directMemoryIntent?.action === 'list') {
|
|
638
|
+
const result = await invokeTool(
|
|
639
|
+
ctx,
|
|
640
|
+
'memory_tool',
|
|
641
|
+
{ action: 'list', key: '', scope: 'auto' },
|
|
642
|
+
'Forced memory list invocation failed',
|
|
643
|
+
calledNames,
|
|
644
|
+
)
|
|
645
|
+
if (result.blockedReason) policyBlockedTools.set('memory_tool', result.blockedReason)
|
|
646
|
+
if (result.unavailableReason) unavailableRequestedTools.set('memory_tool', result.unavailableReason)
|
|
647
|
+
if (result.invoked) {
|
|
648
|
+
fullResponse = String(result.toolOutputText || '').trim() || 'No memories found.'
|
|
649
|
+
errorMessage = undefined
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
612
653
|
// --- Auto-delegation for coding intent ---
|
|
613
654
|
const hasDelegationCall = FORCED_DELEGATION_TOOLS.some((t) => calledNames.has(t))
|
|
614
655
|
const enabledDelegates = enabledDelegationTools(ctx.session)
|
|
@@ -690,16 +731,6 @@ export async function runPostLlmToolRouting(
|
|
|
690
731
|
}
|
|
691
732
|
}
|
|
692
733
|
|
|
693
|
-
if (canAutoRoute && calledNames.size === 0 && hasToolEnabled(ctx.session, 'memory') && isMemoryListIntent(ctx.message)) {
|
|
694
|
-
const result = await invokeTool(
|
|
695
|
-
ctx, 'memory_tool',
|
|
696
|
-
{ action: 'list', key: '', scope: 'auto' },
|
|
697
|
-
'Auto memory listing failed',
|
|
698
|
-
calledNames,
|
|
699
|
-
)
|
|
700
|
-
if (result.responseOverride) fullResponse = result.responseOverride
|
|
701
|
-
}
|
|
702
|
-
|
|
703
734
|
const explicitArtifactTarget = extractExplicitArtifactTarget(ctx.message)
|
|
704
735
|
const canAutoSaveArtifact = (!ctx.internal && ctx.source === 'chat')
|
|
705
736
|
&& !!explicitArtifactTarget
|
|
@@ -42,6 +42,8 @@ export interface ContinuationContext {
|
|
|
42
42
|
sessionExtensions: string[]
|
|
43
43
|
isConnectorSession: boolean
|
|
44
44
|
isCoordinatorAgent: boolean
|
|
45
|
+
delegationEnabled: boolean
|
|
46
|
+
delegationPreferenceActive: boolean
|
|
45
47
|
history: Message[]
|
|
46
48
|
session: { cwd: string }
|
|
47
49
|
write: (data: string) => void
|
|
@@ -279,6 +281,7 @@ function checkAttachmentFollowthrough(ctx: ContinuationContext): ContinuationDec
|
|
|
279
281
|
enabledExtensions: ctx.sessionExtensions,
|
|
280
282
|
hasToolCalls: ctx.state.hasToolCalls,
|
|
281
283
|
hasAttachmentContext: ctx.hasAttachmentContext,
|
|
284
|
+
classification: ctx.classification,
|
|
282
285
|
})) return null
|
|
283
286
|
const count = ctx.limits.increment('attachment_followthrough')
|
|
284
287
|
const { max } = ctx.limits.getStatus('attachment_followthrough')
|
|
@@ -335,7 +338,7 @@ function checkToolErrorFollowthrough(ctx: ContinuationContext): ContinuationDeci
|
|
|
335
338
|
}
|
|
336
339
|
|
|
337
340
|
function checkCoordinatorDelegation(ctx: ContinuationContext): ContinuationDecision | null {
|
|
338
|
-
if (!ctx.
|
|
341
|
+
if (!ctx.delegationEnabled || !ctx.delegationPreferenceActive) return null
|
|
339
342
|
if (!ctx.limits.canContinue('coordinator_delegation_nudge')) return null
|
|
340
343
|
// Skip if already delegated
|
|
341
344
|
const delegationTools = ['spawn_subagent', 'manage_protocols']
|
|
@@ -46,6 +46,15 @@ describe('direct-memory-intent', () => {
|
|
|
46
46
|
})
|
|
47
47
|
})
|
|
48
48
|
|
|
49
|
+
it('parses a list classification payload', () => {
|
|
50
|
+
const parsed = parseDirectMemoryIntentResponse('{"action":"list","confidence":0.84}')
|
|
51
|
+
|
|
52
|
+
assert.deepEqual(parsed, {
|
|
53
|
+
action: 'list',
|
|
54
|
+
confidence: 0.84,
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
49
58
|
it('parses exclusive completion for pure memory turns', () => {
|
|
50
59
|
const parsed = parseDirectMemoryIntentResponse('{"action":"store","confidence":0.98,"title":"Launch marker","value":"My launch marker is ALPHA-9","acknowledgement":"I\\u2019ll remember that.","exclusiveCompletion":true}')
|
|
51
60
|
|