@swarmclawai/swarmclaw 1.2.4 → 1.2.6
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 +23 -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/providers/index.test.ts +108 -0
- package/src/lib/providers/index.ts +38 -15
- 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
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { checkAgentBudgetLimits } from '@/lib/server/cost'
|
|
2
|
+
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
3
|
+
import { log } from '@/lib/server/logger'
|
|
4
|
+
import { loadSettings } from '@/lib/server/settings/settings-repository'
|
|
5
|
+
import { loadSessions } from '@/lib/server/sessions/session-repository'
|
|
6
|
+
import { appendPersistedRunEvent, persistRun } from '@/lib/server/runtime/run-ledger'
|
|
7
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
8
|
+
import { captureGuardianCheckpoint } from '@/lib/server/agents/guardian'
|
|
9
|
+
import {
|
|
10
|
+
assessAutonomyRun,
|
|
11
|
+
executeSupervisorAutoActions,
|
|
12
|
+
} from '@/lib/server/autonomy/supervisor-reflection'
|
|
13
|
+
import { errorMessage, hmrSingleton } from '@/lib/shared-utils'
|
|
14
|
+
import type {
|
|
15
|
+
Agent,
|
|
16
|
+
BoardTask,
|
|
17
|
+
Session,
|
|
18
|
+
SessionRunRecord,
|
|
19
|
+
SessionRunStatus,
|
|
20
|
+
} from '@/types'
|
|
21
|
+
import type { ExecuteChatTurnResult } from '@/lib/server/chat-execution/chat-execution-types'
|
|
22
|
+
import type {
|
|
23
|
+
EnqueueTaskAttemptExecutionRequest,
|
|
24
|
+
ExecutionHandle,
|
|
25
|
+
} from '@/lib/server/execution-engine/types'
|
|
26
|
+
import { executeExecutionChatTurn } from '@/lib/server/execution-engine/chat-turn'
|
|
27
|
+
|
|
28
|
+
const TAG = 'execution-engine'
|
|
29
|
+
|
|
30
|
+
interface TaskAttemptState {
|
|
31
|
+
runningByTaskId: Map<string, ExecutionHandle<ExecuteChatTurnResult>>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const taskAttemptState = hmrSingleton<TaskAttemptState>(
|
|
35
|
+
'__swarmclaw_execution_engine_task_attempt__',
|
|
36
|
+
() => ({
|
|
37
|
+
runningByTaskId: new Map<string, ExecutionHandle<ExecuteChatTurnResult>>(),
|
|
38
|
+
}),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
function messagePreview(text: string): string {
|
|
42
|
+
return (text || '').replace(/\s+/g, ' ').trim().slice(0, 140)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function looksIncomplete(text: string): boolean {
|
|
46
|
+
if (!text) return false
|
|
47
|
+
const trimmed = text.trim()
|
|
48
|
+
if (trimmed.endsWith('...') || trimmed.endsWith('…')) return true
|
|
49
|
+
if (/(?:^|\n)#{1,3}\s+(?:Step|Phase|Next)\s+\d/i.test(trimmed.slice(-200))) return true
|
|
50
|
+
const lastChunk = trimmed.slice(-300).toLowerCase()
|
|
51
|
+
return /\b(?:next i(?:'ll| will)|now i(?:'ll| will)|let me (?:now|next)|moving on to|proceeding to)\b/.test(lastChunk)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function chainCallerSignal(callerSignal: AbortSignal | undefined, controller: AbortController): void {
|
|
55
|
+
if (!callerSignal) return
|
|
56
|
+
if (callerSignal.aborted) {
|
|
57
|
+
controller.abort()
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
const onAbort = () => controller.abort()
|
|
61
|
+
callerSignal.addEventListener('abort', onAbort, { once: true })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function notifyExecutionState(sessionId: string): void {
|
|
65
|
+
notify('runs')
|
|
66
|
+
notify('sessions')
|
|
67
|
+
notify(`session:${sessionId}`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function emitStatus(run: SessionRunRecord, status: SessionRunStatus, extra?: Record<string, unknown>): void {
|
|
71
|
+
appendPersistedRunEvent({
|
|
72
|
+
runId: run.id,
|
|
73
|
+
sessionId: run.sessionId,
|
|
74
|
+
kind: run.kind,
|
|
75
|
+
ownerType: run.ownerType,
|
|
76
|
+
ownerId: run.ownerId,
|
|
77
|
+
parentExecutionId: run.parentExecutionId,
|
|
78
|
+
phase: 'status',
|
|
79
|
+
status,
|
|
80
|
+
summary: run.resultPreview || run.error || undefined,
|
|
81
|
+
event: {
|
|
82
|
+
t: 'md',
|
|
83
|
+
text: JSON.stringify({
|
|
84
|
+
run: {
|
|
85
|
+
id: run.id,
|
|
86
|
+
sessionId: run.sessionId,
|
|
87
|
+
kind: run.kind,
|
|
88
|
+
ownerType: run.ownerType,
|
|
89
|
+
ownerId: run.ownerId,
|
|
90
|
+
status,
|
|
91
|
+
source: run.source,
|
|
92
|
+
internal: run.internal,
|
|
93
|
+
...extra,
|
|
94
|
+
},
|
|
95
|
+
}),
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
notifyExecutionState(run.sessionId)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function executeTaskAttemptTurn(
|
|
102
|
+
task: BoardTask,
|
|
103
|
+
agent: Agent,
|
|
104
|
+
sessionId: string,
|
|
105
|
+
signal: AbortSignal,
|
|
106
|
+
): Promise<ExecuteChatTurnResult> {
|
|
107
|
+
if (agent.autoRecovery) {
|
|
108
|
+
const cwd = task.projectId
|
|
109
|
+
? `${WORKSPACE_DIR}/projects/${task.projectId}`
|
|
110
|
+
: WORKSPACE_DIR
|
|
111
|
+
captureGuardianCheckpoint(cwd, `task:${task.id}`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const settings = loadSettings()
|
|
115
|
+
const basePrompt = task.description || task.title
|
|
116
|
+
const prompt = [
|
|
117
|
+
basePrompt,
|
|
118
|
+
'',
|
|
119
|
+
'Completion requirements:',
|
|
120
|
+
'- Execute the task before replying; do not reply with only a plan.',
|
|
121
|
+
'- Include concrete evidence in your final summary: changed file paths, commands run, and verification results.',
|
|
122
|
+
'- If blocked, state the blocker explicitly and what input or permission is missing.',
|
|
123
|
+
].join('\n')
|
|
124
|
+
|
|
125
|
+
let latestRun = await executeExecutionChatTurn({
|
|
126
|
+
sessionId,
|
|
127
|
+
message: prompt,
|
|
128
|
+
internal: false,
|
|
129
|
+
source: 'task',
|
|
130
|
+
runId: task.id,
|
|
131
|
+
signal,
|
|
132
|
+
})
|
|
133
|
+
let text = typeof latestRun.text === 'string' ? latestRun.text.trim() : ''
|
|
134
|
+
let previousSummary: string | null = null
|
|
135
|
+
let totalInputTokens = latestRun.inputTokens || 0
|
|
136
|
+
let totalOutputTokens = latestRun.outputTokens || 0
|
|
137
|
+
let totalEstimatedCost = Number(latestRun.estimatedCost || 0)
|
|
138
|
+
|
|
139
|
+
if (latestRun.error) {
|
|
140
|
+
return {
|
|
141
|
+
...latestRun,
|
|
142
|
+
text,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const maxSupervisorFollowups = 2
|
|
147
|
+
for (let followupIndex = 0; followupIndex < maxSupervisorFollowups; followupIndex += 1) {
|
|
148
|
+
if (signal.aborted) break
|
|
149
|
+
|
|
150
|
+
const sessions = loadSessions()
|
|
151
|
+
const session = sessions[sessionId] as Session | undefined
|
|
152
|
+
const assessment = assessAutonomyRun({
|
|
153
|
+
runId: `${task.id}:attempt-${(task.attempts || 0) + 1}:step-${followupIndex + 1}`,
|
|
154
|
+
sessionId,
|
|
155
|
+
taskId: task.id,
|
|
156
|
+
agentId: agent.id,
|
|
157
|
+
source: 'task',
|
|
158
|
+
status: latestRun.error ? 'failed' : 'completed',
|
|
159
|
+
resultText: text,
|
|
160
|
+
error: latestRun.error,
|
|
161
|
+
toolEvents: latestRun.toolEvents,
|
|
162
|
+
mainLoopState: {
|
|
163
|
+
followupChainCount: followupIndex + 1,
|
|
164
|
+
summary: previousSummary,
|
|
165
|
+
missionCostUsd: totalEstimatedCost,
|
|
166
|
+
},
|
|
167
|
+
session: session || null,
|
|
168
|
+
settings,
|
|
169
|
+
})
|
|
170
|
+
if (assessment.shouldBlock) break
|
|
171
|
+
if (assessment.autoActions?.length) {
|
|
172
|
+
const result = await executeSupervisorAutoActions({
|
|
173
|
+
actions: assessment.autoActions,
|
|
174
|
+
sessionId,
|
|
175
|
+
agentId: agent.id,
|
|
176
|
+
})
|
|
177
|
+
if (result.blocked) break
|
|
178
|
+
}
|
|
179
|
+
const followupMessage = assessment.interventionPrompt
|
|
180
|
+
|| (text && looksIncomplete(text)
|
|
181
|
+
? 'Continue and complete the remaining steps. Provide a final summary when done.'
|
|
182
|
+
: null)
|
|
183
|
+
if (!followupMessage) break
|
|
184
|
+
|
|
185
|
+
if (agent.monthlyBudget || agent.dailyBudget || agent.hourlyBudget) {
|
|
186
|
+
try {
|
|
187
|
+
const followupBudget = checkAgentBudgetLimits(agent)
|
|
188
|
+
if (!followupBudget.ok) {
|
|
189
|
+
log.warn(TAG, `[task_attempt] Budget exceeded for "${agent.name}" during follow-up, stopping.`)
|
|
190
|
+
break
|
|
191
|
+
}
|
|
192
|
+
} catch {
|
|
193
|
+
// Best-effort safety check only.
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
previousSummary = text || previousSummary
|
|
198
|
+
const followUp = await executeExecutionChatTurn({
|
|
199
|
+
sessionId,
|
|
200
|
+
message: followupMessage,
|
|
201
|
+
internal: false,
|
|
202
|
+
source: 'task',
|
|
203
|
+
signal,
|
|
204
|
+
})
|
|
205
|
+
totalInputTokens += followUp.inputTokens || 0
|
|
206
|
+
totalOutputTokens += followUp.outputTokens || 0
|
|
207
|
+
totalEstimatedCost += Number(followUp.estimatedCost || 0)
|
|
208
|
+
text = typeof followUp.text === 'string' ? followUp.text.trim() : ''
|
|
209
|
+
latestRun = {
|
|
210
|
+
...followUp,
|
|
211
|
+
text,
|
|
212
|
+
inputTokens: totalInputTokens,
|
|
213
|
+
outputTokens: totalOutputTokens,
|
|
214
|
+
estimatedCost: totalEstimatedCost,
|
|
215
|
+
}
|
|
216
|
+
if (latestRun.error) break
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
...latestRun,
|
|
221
|
+
text,
|
|
222
|
+
inputTokens: totalInputTokens,
|
|
223
|
+
outputTokens: totalOutputTokens,
|
|
224
|
+
estimatedCost: totalEstimatedCost,
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function enqueueTaskAttemptExecution(
|
|
229
|
+
input: EnqueueTaskAttemptExecutionRequest,
|
|
230
|
+
): ExecutionHandle<ExecuteChatTurnResult> {
|
|
231
|
+
const existing = taskAttemptState.runningByTaskId.get(input.task.id)
|
|
232
|
+
if (existing) return { ...existing, deduped: true }
|
|
233
|
+
|
|
234
|
+
const executionId = input.executionId || `${input.task.id}:attempt-${(input.task.attempts || 0) + 1}`
|
|
235
|
+
const controller = new AbortController()
|
|
236
|
+
chainCallerSignal(input.callerSignal, controller)
|
|
237
|
+
|
|
238
|
+
const run: SessionRunRecord = {
|
|
239
|
+
id: executionId,
|
|
240
|
+
sessionId: input.sessionId,
|
|
241
|
+
missionId: input.task.missionId || null,
|
|
242
|
+
kind: 'task_attempt',
|
|
243
|
+
ownerType: 'task',
|
|
244
|
+
ownerId: input.task.id,
|
|
245
|
+
parentExecutionId: null,
|
|
246
|
+
recoveryPolicy: 'restart_recoverable',
|
|
247
|
+
source: 'task',
|
|
248
|
+
internal: false,
|
|
249
|
+
mode: 'task_attempt',
|
|
250
|
+
status: 'queued',
|
|
251
|
+
messagePreview: messagePreview(input.task.description || input.task.title),
|
|
252
|
+
queuedAt: Date.now(),
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
persistRun(run)
|
|
256
|
+
emitStatus(run, 'queued')
|
|
257
|
+
|
|
258
|
+
const promise = (async () => {
|
|
259
|
+
run.status = 'running'
|
|
260
|
+
run.startedAt = Date.now()
|
|
261
|
+
persistRun(run)
|
|
262
|
+
emitStatus(run, 'running')
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const result = await executeTaskAttemptTurn(input.task, input.agent, input.sessionId, controller.signal)
|
|
266
|
+
run.status = controller.signal.aborted
|
|
267
|
+
? 'cancelled'
|
|
268
|
+
: (result.error ? 'failed' : 'completed')
|
|
269
|
+
run.endedAt = Date.now()
|
|
270
|
+
run.error = controller.signal.aborted ? (run.error || 'Cancelled') : result.error
|
|
271
|
+
run.resultPreview = result.text?.slice(0, 280)
|
|
272
|
+
if (typeof result.inputTokens === 'number') run.totalInputTokens = result.inputTokens
|
|
273
|
+
if (typeof result.outputTokens === 'number') run.totalOutputTokens = result.outputTokens
|
|
274
|
+
if (typeof result.estimatedCost === 'number') run.estimatedCost = result.estimatedCost
|
|
275
|
+
persistRun(run)
|
|
276
|
+
emitStatus(run, run.status, {
|
|
277
|
+
hasText: !!result.text,
|
|
278
|
+
error: run.error || null,
|
|
279
|
+
})
|
|
280
|
+
return result
|
|
281
|
+
} catch (err: unknown) {
|
|
282
|
+
run.status = controller.signal.aborted ? 'cancelled' : 'failed'
|
|
283
|
+
run.endedAt = Date.now()
|
|
284
|
+
run.error = errorMessage(err)
|
|
285
|
+
persistRun(run)
|
|
286
|
+
emitStatus(run, run.status, { error: run.error })
|
|
287
|
+
throw err
|
|
288
|
+
} finally {
|
|
289
|
+
const latest = taskAttemptState.runningByTaskId.get(input.task.id)
|
|
290
|
+
if (latest?.executionId === executionId) {
|
|
291
|
+
taskAttemptState.runningByTaskId.delete(input.task.id)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
})()
|
|
295
|
+
|
|
296
|
+
const handle: ExecutionHandle<ExecuteChatTurnResult> = {
|
|
297
|
+
executionId,
|
|
298
|
+
promise,
|
|
299
|
+
abort: () => controller.abort(),
|
|
300
|
+
}
|
|
301
|
+
taskAttemptState.runningByTaskId.set(input.task.id, handle)
|
|
302
|
+
return handle
|
|
303
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ExecuteChatTurnResult } from '@/lib/server/chat-execution/chat-execution-types'
|
|
2
|
+
import type { EnqueueSessionRunInput } from '@/lib/server/runtime/session-run-manager/types'
|
|
3
|
+
import type { Agent, BoardTask } from '@/types'
|
|
4
|
+
|
|
5
|
+
export interface ExecutionHandle<TResult> {
|
|
6
|
+
executionId: string
|
|
7
|
+
promise: Promise<TResult>
|
|
8
|
+
abort: () => void
|
|
9
|
+
position?: number
|
|
10
|
+
deduped?: boolean
|
|
11
|
+
coalesced?: boolean
|
|
12
|
+
unsubscribe?: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface EnqueueSessionTurnExecutionRequest {
|
|
16
|
+
kind: 'session_turn'
|
|
17
|
+
input: EnqueueSessionRunInput
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface EnqueueTaskAttemptExecutionRequest {
|
|
21
|
+
kind: 'task_attempt'
|
|
22
|
+
task: BoardTask
|
|
23
|
+
agent: Agent
|
|
24
|
+
sessionId: string
|
|
25
|
+
executionId?: string
|
|
26
|
+
callerSignal?: AbortSignal
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type EnqueueExecutionRequest =
|
|
30
|
+
| EnqueueSessionTurnExecutionRequest
|
|
31
|
+
| EnqueueTaskAttemptExecutionRequest
|
|
32
|
+
|
|
33
|
+
export type ExecutionResult = ExecuteChatTurnResult
|
|
@@ -1,4 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { GatewayProfile } from '@/types'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
deleteStoredItem,
|
|
5
|
+
loadGatewayProfiles as loadStoredGatewayProfiles,
|
|
6
|
+
loadStoredItem,
|
|
7
|
+
saveGatewayProfiles as saveStoredGatewayProfiles,
|
|
8
|
+
upsertStoredItem,
|
|
4
9
|
} from '@/lib/server/storage'
|
|
10
|
+
import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
|
|
11
|
+
|
|
12
|
+
export const gatewayProfileRepository = createRecordRepository<GatewayProfile>(
|
|
13
|
+
'gatewayProfiles',
|
|
14
|
+
{
|
|
15
|
+
get(id) {
|
|
16
|
+
return loadStoredItem('gateway_profiles', id) as GatewayProfile | null
|
|
17
|
+
},
|
|
18
|
+
list() {
|
|
19
|
+
return loadStoredGatewayProfiles() as Record<string, GatewayProfile>
|
|
20
|
+
},
|
|
21
|
+
upsert(id, value) {
|
|
22
|
+
upsertStoredItem('gateway_profiles', id, value)
|
|
23
|
+
},
|
|
24
|
+
replace(data) {
|
|
25
|
+
saveStoredGatewayProfiles(data as Record<string, GatewayProfile>)
|
|
26
|
+
},
|
|
27
|
+
patch(id, updater) {
|
|
28
|
+
const current = loadStoredItem('gateway_profiles', id) as GatewayProfile | null
|
|
29
|
+
const next = updater(current)
|
|
30
|
+
if (next === null) {
|
|
31
|
+
deleteStoredItem('gateway_profiles', id)
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
upsertStoredItem('gateway_profiles', id, next)
|
|
35
|
+
return next
|
|
36
|
+
},
|
|
37
|
+
delete(id) {
|
|
38
|
+
deleteStoredItem('gateway_profiles', id)
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
export const loadGatewayProfiles = () => gatewayProfileRepository.list()
|
|
44
|
+
export const loadGatewayProfile = (id: string) => gatewayProfileRepository.get(id)
|
|
45
|
+
export const saveGatewayProfiles = (items: Record<string, GatewayProfile | Record<string, unknown>>) => gatewayProfileRepository.replace(items as Record<string, GatewayProfile>)
|
|
46
|
+
export const saveGatewayProfile = (id: string, value: GatewayProfile | Record<string, unknown>) => gatewayProfileRepository.upsert(id, value as GatewayProfile)
|
|
47
|
+
export const patchGatewayProfile = (id: string, updater: (current: GatewayProfile | null) => GatewayProfile | null) => gatewayProfileRepository.patch(id, updater)
|
|
48
|
+
export const deleteGatewayProfile = (id: string) => gatewayProfileRepository.delete(id)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { Agent, AgentRoutingTarget, GatewayProfile, OpenClawDeploymentConfig, OpenClawGatewayStats } from '@/types'
|
|
2
|
+
|
|
3
|
+
import { genId } from '@/lib/id'
|
|
4
|
+
import { normalizeOpenClawEndpoint } from '@/lib/openclaw/openclaw-endpoint'
|
|
5
|
+
import { listAgents, saveAgentMany } from '@/lib/server/agents/agent-repository'
|
|
6
|
+
import { getGatewayProfiles } from '@/lib/server/agents/agent-runtime-config'
|
|
7
|
+
import {
|
|
8
|
+
loadGatewayProfile,
|
|
9
|
+
loadGatewayProfiles,
|
|
10
|
+
saveGatewayProfiles,
|
|
11
|
+
} from '@/lib/server/gateways/gateway-profile-repository'
|
|
12
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
13
|
+
|
|
14
|
+
function normalizeTags(value: unknown): string[] {
|
|
15
|
+
if (!Array.isArray(value)) return []
|
|
16
|
+
return value
|
|
17
|
+
.map((entry) => (typeof entry === 'string' ? entry.trim() : ''))
|
|
18
|
+
.filter(Boolean)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeText(value: unknown): string | null {
|
|
22
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeNullableNumber(value: unknown): number | null {
|
|
26
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeDeployment(value: unknown): OpenClawDeploymentConfig | null {
|
|
30
|
+
if (!value || typeof value !== 'object') return null
|
|
31
|
+
const deployment = value as Record<string, unknown>
|
|
32
|
+
return {
|
|
33
|
+
method: normalizeText(deployment.method) as OpenClawDeploymentConfig['method'],
|
|
34
|
+
provider: normalizeText(deployment.provider) as OpenClawDeploymentConfig['provider'],
|
|
35
|
+
remoteTarget: normalizeText(deployment.remoteTarget) as OpenClawDeploymentConfig['remoteTarget'],
|
|
36
|
+
useCase: normalizeText(deployment.useCase) as OpenClawDeploymentConfig['useCase'],
|
|
37
|
+
exposure: normalizeText(deployment.exposure) as OpenClawDeploymentConfig['exposure'],
|
|
38
|
+
managedBy: normalizeText(deployment.managedBy) as OpenClawDeploymentConfig['managedBy'],
|
|
39
|
+
localInstanceId: normalizeText(deployment.localInstanceId),
|
|
40
|
+
localPort: normalizeNullableNumber(deployment.localPort),
|
|
41
|
+
targetHost: normalizeText(deployment.targetHost),
|
|
42
|
+
sshHost: normalizeText(deployment.sshHost),
|
|
43
|
+
sshUser: normalizeText(deployment.sshUser),
|
|
44
|
+
sshPort: normalizeNullableNumber(deployment.sshPort),
|
|
45
|
+
sshKeyPath: normalizeText(deployment.sshKeyPath),
|
|
46
|
+
sshTargetDir: normalizeText(deployment.sshTargetDir),
|
|
47
|
+
image: normalizeText(deployment.image),
|
|
48
|
+
version: normalizeText(deployment.version),
|
|
49
|
+
lastDeployAt: normalizeNullableNumber(deployment.lastDeployAt),
|
|
50
|
+
lastDeployAction: normalizeText(deployment.lastDeployAction),
|
|
51
|
+
lastDeployProcessId: normalizeText(deployment.lastDeployProcessId),
|
|
52
|
+
lastDeploySummary: normalizeText(deployment.lastDeploySummary),
|
|
53
|
+
lastVerifiedAt: normalizeNullableNumber(deployment.lastVerifiedAt),
|
|
54
|
+
lastVerifiedOk: typeof deployment.lastVerifiedOk === 'boolean' ? deployment.lastVerifiedOk : null,
|
|
55
|
+
lastVerifiedMessage: normalizeText(deployment.lastVerifiedMessage),
|
|
56
|
+
lastBackupPath: normalizeText(deployment.lastBackupPath),
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeStats(value: unknown): OpenClawGatewayStats | null {
|
|
61
|
+
if (!value || typeof value !== 'object') return null
|
|
62
|
+
const stats = value as Record<string, unknown>
|
|
63
|
+
return {
|
|
64
|
+
nodeCount: normalizeNullableNumber(stats.nodeCount) ?? undefined,
|
|
65
|
+
connectedNodeCount: normalizeNullableNumber(stats.connectedNodeCount) ?? undefined,
|
|
66
|
+
pendingNodePairings: normalizeNullableNumber(stats.pendingNodePairings) ?? undefined,
|
|
67
|
+
pairedDeviceCount: normalizeNullableNumber(stats.pairedDeviceCount) ?? undefined,
|
|
68
|
+
pendingDevicePairings: normalizeNullableNumber(stats.pendingDevicePairings) ?? undefined,
|
|
69
|
+
externalRuntimeCount: normalizeNullableNumber(stats.externalRuntimeCount) ?? undefined,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function listOpenClawGatewayProfiles(): GatewayProfile[] {
|
|
74
|
+
return getGatewayProfiles('openclaw')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getGatewayProfileById(id: string): GatewayProfile | null {
|
|
78
|
+
return loadGatewayProfile(id)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function createGatewayProfile(input: Record<string, unknown>): GatewayProfile {
|
|
82
|
+
const now = Date.now()
|
|
83
|
+
const gateways = loadGatewayProfiles()
|
|
84
|
+
const id = typeof input.id === 'string' && input.id.trim() ? input.id : `gateway-${genId()}`
|
|
85
|
+
const isDefault = input.isDefault === true
|
|
86
|
+
|
|
87
|
+
if (isDefault) {
|
|
88
|
+
for (const gateway of Object.values(gateways)) {
|
|
89
|
+
if (gateway) gateway.isDefault = false
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const profile: GatewayProfile = {
|
|
94
|
+
id,
|
|
95
|
+
name: typeof input.name === 'string' && input.name.trim() ? input.name.trim() : 'OpenClaw Gateway',
|
|
96
|
+
provider: 'openclaw',
|
|
97
|
+
endpoint: normalizeOpenClawEndpoint(typeof input.endpoint === 'string' ? input.endpoint : undefined),
|
|
98
|
+
wsUrl: normalizeText(input.wsUrl),
|
|
99
|
+
credentialId: normalizeText(input.credentialId),
|
|
100
|
+
status: typeof input.status === 'string' && input.status.trim() ? input.status as GatewayProfile['status'] : 'unknown',
|
|
101
|
+
notes: typeof input.notes === 'string' ? input.notes : null,
|
|
102
|
+
tags: normalizeTags(input.tags),
|
|
103
|
+
lastError: null,
|
|
104
|
+
lastCheckedAt: null,
|
|
105
|
+
lastModelCount: null,
|
|
106
|
+
discoveredHost: typeof input.discoveredHost === 'string' ? input.discoveredHost : null,
|
|
107
|
+
discoveredPort: typeof input.discoveredPort === 'number' ? input.discoveredPort : null,
|
|
108
|
+
deployment: normalizeDeployment(input.deployment),
|
|
109
|
+
stats: normalizeStats(input.stats),
|
|
110
|
+
isDefault,
|
|
111
|
+
createdAt: now,
|
|
112
|
+
updatedAt: now,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
gateways[id] = profile
|
|
116
|
+
saveGatewayProfiles(gateways)
|
|
117
|
+
notify('gateways')
|
|
118
|
+
return profile
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function updateGatewayProfile(id: string, input: Record<string, unknown>): GatewayProfile | null {
|
|
122
|
+
const gateways = loadGatewayProfiles()
|
|
123
|
+
const gateway = gateways[id]
|
|
124
|
+
if (!gateway) return null
|
|
125
|
+
|
|
126
|
+
// Clear isDefault on other gateways if this one is becoming default
|
|
127
|
+
if (input.isDefault === true) {
|
|
128
|
+
for (const [candidateId, g] of Object.entries(gateways)) {
|
|
129
|
+
if (candidateId !== id && g) g.isDefault = false
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Apply all field updates to the target gateway
|
|
134
|
+
if (input.name !== undefined) gateway.name = String(input.name || '').trim() || gateway.name
|
|
135
|
+
if (input.endpoint !== undefined) gateway.endpoint = normalizeOpenClawEndpoint(typeof input.endpoint === 'string' ? input.endpoint : undefined)
|
|
136
|
+
if (input.wsUrl !== undefined) gateway.wsUrl = normalizeText(input.wsUrl)
|
|
137
|
+
if (input.credentialId !== undefined) gateway.credentialId = normalizeText(input.credentialId)
|
|
138
|
+
if (input.status !== undefined) {
|
|
139
|
+
const nextStatus = typeof input.status === 'string' && input.status.trim()
|
|
140
|
+
? input.status.trim() as GatewayProfile['status']
|
|
141
|
+
: 'unknown'
|
|
142
|
+
gateway.status = nextStatus
|
|
143
|
+
}
|
|
144
|
+
if (input.notes !== undefined) gateway.notes = typeof input.notes === 'string' ? input.notes : null
|
|
145
|
+
if (input.tags !== undefined) gateway.tags = normalizeTags(input.tags)
|
|
146
|
+
if (input.lastError !== undefined) gateway.lastError = typeof input.lastError === 'string' ? input.lastError : null
|
|
147
|
+
if (input.lastCheckedAt !== undefined) gateway.lastCheckedAt = normalizeNullableNumber(input.lastCheckedAt)
|
|
148
|
+
if (input.lastModelCount !== undefined) gateway.lastModelCount = normalizeNullableNumber(input.lastModelCount)
|
|
149
|
+
if (input.discoveredHost !== undefined) gateway.discoveredHost = typeof input.discoveredHost === 'string' ? input.discoveredHost : null
|
|
150
|
+
if (input.discoveredPort !== undefined) gateway.discoveredPort = normalizeNullableNumber(input.discoveredPort)
|
|
151
|
+
if (input.deployment !== undefined) gateway.deployment = { ...(gateway.deployment || {}), ...(normalizeDeployment(input.deployment) || {}) }
|
|
152
|
+
if (input.stats !== undefined) gateway.stats = { ...(gateway.stats || {}), ...(normalizeStats(input.stats) || {}) }
|
|
153
|
+
if (input.isDefault !== undefined) gateway.isDefault = input.isDefault === true
|
|
154
|
+
gateway.updatedAt = Date.now()
|
|
155
|
+
|
|
156
|
+
gateways[id] = gateway
|
|
157
|
+
saveGatewayProfiles(gateways)
|
|
158
|
+
notify('gateways')
|
|
159
|
+
return gateway
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function deleteGatewayProfileAndDetachAgents(id: string): boolean {
|
|
163
|
+
const gateways = loadGatewayProfiles()
|
|
164
|
+
if (!gateways[id]) return false
|
|
165
|
+
delete gateways[id]
|
|
166
|
+
saveGatewayProfiles(gateways)
|
|
167
|
+
|
|
168
|
+
const agents = listAgents({ includeTrashed: true })
|
|
169
|
+
const changed: Array<[string, Agent]> = []
|
|
170
|
+
for (const agent of Object.values(agents)) {
|
|
171
|
+
let nextAgent: Agent | null = null
|
|
172
|
+
|
|
173
|
+
if (agent.gatewayProfileId === id) {
|
|
174
|
+
nextAgent = {
|
|
175
|
+
...agent,
|
|
176
|
+
gatewayProfileId: null,
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (Array.isArray(agent.routingTargets)) {
|
|
181
|
+
const nextTargets = agent.routingTargets.map((target: AgentRoutingTarget) => (
|
|
182
|
+
target.gatewayProfileId === id
|
|
183
|
+
? { ...target, gatewayProfileId: null }
|
|
184
|
+
: target
|
|
185
|
+
))
|
|
186
|
+
if (JSON.stringify(nextTargets) !== JSON.stringify(agent.routingTargets)) {
|
|
187
|
+
nextAgent = {
|
|
188
|
+
...(nextAgent || agent),
|
|
189
|
+
routingTargets: nextTargets,
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (nextAgent) changed.push([nextAgent.id, nextAgent])
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (changed.length > 0) saveAgentMany(changed)
|
|
198
|
+
notify('gateways')
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { createHash } from 'crypto'
|
|
2
2
|
import fs from 'fs'
|
|
3
3
|
import path from 'path'
|
|
4
|
-
import type { Agent, MemoryEntry, MemoryReference, Session } from '@/types'
|
|
4
|
+
import type { Agent, MemoryEntry, MemoryReference, Message, Session } from '@/types'
|
|
5
5
|
import { getMemoryDb } from '@/lib/server/memory/memory-db'
|
|
6
6
|
import { loadAgents, loadSessions, saveSessions } from '@/lib/server/storage'
|
|
7
7
|
import { DATA_DIR } from '@/lib/server/data-dir'
|
|
8
8
|
import { isDirectConnectorSession } from '@/lib/server/connectors/session-kind'
|
|
9
|
+
import { getMessageCount, getRecentMessages } from '@/lib/server/messages/message-repository'
|
|
9
10
|
|
|
10
11
|
const MAX_ARCHIVE_MESSAGES = 36
|
|
11
12
|
const MAX_ARCHIVE_LINE_CHARS = 320
|
|
@@ -15,7 +16,7 @@ function toOneLine(value: unknown, maxChars: number): string {
|
|
|
15
16
|
return String(value || '').replace(/\s+/g, ' ').trim().slice(0, maxChars)
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
function messageSpeaker(session: Session, agent: Partial<Agent> | null | undefined, message:
|
|
19
|
+
function messageSpeaker(session: Session, agent: Partial<Agent> | null | undefined, message: Message): string {
|
|
19
20
|
if (message.role === 'assistant') return agent?.name || 'assistant'
|
|
20
21
|
return (isDirectConnectorSession(session) ? session.connectorContext?.senderName : null) || session.user || 'user'
|
|
21
22
|
}
|
|
@@ -38,9 +39,10 @@ export function buildSessionArchivePayload(
|
|
|
38
39
|
references: MemoryReference[]
|
|
39
40
|
hash: string
|
|
40
41
|
} | null {
|
|
41
|
-
|
|
42
|
+
const messageCount = getMessageCount(session.id)
|
|
43
|
+
if (messageCount < 2) return null
|
|
42
44
|
|
|
43
|
-
const excerpt = session.
|
|
45
|
+
const excerpt = getRecentMessages(session.id, MAX_ARCHIVE_MESSAGES).map((message) => {
|
|
44
46
|
const speaker = messageSpeaker(session, agent, message)
|
|
45
47
|
const kind = message.kind && message.kind !== 'chat' ? ` [${message.kind}]` : ''
|
|
46
48
|
const text = toOneLine(message.text, MAX_ARCHIVE_LINE_CHARS)
|
|
@@ -57,7 +59,7 @@ export function buildSessionArchivePayload(
|
|
|
57
59
|
`session_type: ${toOneLine(session.sessionType || 'human', 32)}`,
|
|
58
60
|
`agent_name: ${toOneLine(agent?.name || '', 80)}`,
|
|
59
61
|
`last_active_iso: ${new Date(session.lastActiveAt || Date.now()).toISOString()}`,
|
|
60
|
-
`message_count: ${
|
|
62
|
+
`message_count: ${messageCount}`,
|
|
61
63
|
session.identityState?.personaLabel ? `persona_label: ${toOneLine(session.identityState.personaLabel, 120)}` : '',
|
|
62
64
|
'',
|
|
63
65
|
'Transcript excerpt:',
|
|
@@ -73,7 +75,7 @@ export function buildSessionArchivePayload(
|
|
|
73
75
|
archiveHash: hash,
|
|
74
76
|
sessionName: session.name,
|
|
75
77
|
sessionType: session.sessionType || 'human',
|
|
76
|
-
messageCount
|
|
78
|
+
messageCount,
|
|
77
79
|
lastActiveAt: session.lastActiveAt || Date.now(),
|
|
78
80
|
personaLabel: session.identityState?.personaLabel || null,
|
|
79
81
|
},
|
|
@@ -93,7 +95,7 @@ export function buildSessionArchiveMarkdown(
|
|
|
93
95
|
payload: NonNullable<ReturnType<typeof buildSessionArchivePayload>>,
|
|
94
96
|
agent?: Partial<Agent> | null,
|
|
95
97
|
): string {
|
|
96
|
-
const transcriptLines = session.
|
|
98
|
+
const transcriptLines = getRecentMessages(session.id, MAX_ARCHIVE_MESSAGES).map((message) => {
|
|
97
99
|
const speaker = messageSpeaker(session, agent, message)
|
|
98
100
|
const kind = message.kind && message.kind !== 'chat' ? ` (${message.kind})` : ''
|
|
99
101
|
const toolSummary = Array.isArray(message.toolEvents) && message.toolEvents.length > 0
|
|
@@ -110,7 +112,7 @@ export function buildSessionArchiveMarkdown(
|
|
|
110
112
|
`- Session Type: ${toOneLine(session.sessionType || 'human', 32)}`,
|
|
111
113
|
`- Agent: ${toOneLine(agent?.name || session.agentId || 'unknown', 80)}`,
|
|
112
114
|
`- Last Active: ${new Date(session.lastActiveAt || Date.now()).toISOString()}`,
|
|
113
|
-
`- Messages: ${session.
|
|
115
|
+
`- Messages: ${getMessageCount(session.id)}`,
|
|
114
116
|
session.identityState?.personaLabel ? `- Persona: ${toOneLine(session.identityState.personaLabel, 120)}` : '',
|
|
115
117
|
'',
|
|
116
118
|
'## Archive Snapshot',
|
|
@@ -168,7 +170,7 @@ export function syncSessionArchiveMemory(
|
|
|
168
170
|
memoryId: session.sessionArchiveState?.memoryId || existing?.id || null,
|
|
169
171
|
lastHash: payload.hash,
|
|
170
172
|
lastSyncedAt: session.sessionArchiveState?.lastSyncedAt || existing?.updatedAt || null,
|
|
171
|
-
messageCount: session.
|
|
173
|
+
messageCount: getMessageCount(session.id),
|
|
172
174
|
exportPath: session.sessionArchiveState?.exportPath || null,
|
|
173
175
|
}
|
|
174
176
|
return { stored: false, memoryId: existing?.id || session.sessionArchiveState.memoryId || undefined, reason: 'unchanged' }
|
|
@@ -199,7 +201,7 @@ export function syncSessionArchiveMemory(
|
|
|
199
201
|
memoryId: entry.id,
|
|
200
202
|
lastHash: payload.hash,
|
|
201
203
|
lastSyncedAt: Date.now(),
|
|
202
|
-
messageCount: session.
|
|
204
|
+
messageCount: getMessageCount(session.id),
|
|
203
205
|
exportPath,
|
|
204
206
|
}
|
|
205
207
|
|