@swarmclawai/swarmclaw 1.2.3 → 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/bin/daemon-cmd.js +169 -0
- package/bin/server-cmd.js +3 -0
- package/bin/swarmclaw.js +11 -0
- package/package.json +17 -16
- package/src/app/api/agents/[id]/clone/route.ts +3 -32
- package/src/app/api/agents/[id]/route.ts +6 -158
- package/src/app/api/agents/[id]/status/route.ts +2 -3
- package/src/app/api/agents/[id]/thread/route.ts +4 -17
- package/src/app/api/agents/bulk/route.ts +5 -47
- package/src/app/api/agents/route.ts +5 -119
- package/src/app/api/agents/trash/route.ts +13 -24
- package/src/app/api/auth/route.ts +3 -9
- package/src/app/api/autonomy/estop/route.ts +5 -5
- package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
- package/src/app/api/chatrooms/[id]/route.ts +23 -2
- package/src/app/api/chatrooms/route.ts +13 -2
- package/src/app/api/chats/[id]/clear/route.ts +2 -13
- package/src/app/api/chats/[id]/deploy/route.ts +2 -3
- package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
- package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
- package/src/app/api/chats/[id]/queue/route.ts +17 -64
- package/src/app/api/chats/[id]/retry/route.ts +4 -22
- package/src/app/api/chats/[id]/route.ts +10 -138
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/migrate-messages/route.ts +7 -0
- package/src/app/api/chats/route.ts +13 -134
- package/src/app/api/connectors/[id]/access/route.ts +12 -229
- package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
- package/src/app/api/connectors/[id]/health/route.ts +12 -39
- package/src/app/api/connectors/[id]/route.ts +14 -122
- package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
- package/src/app/api/connectors/doctor/route.ts +1 -1
- package/src/app/api/connectors/route.ts +12 -70
- package/src/app/api/credentials/[id]/route.ts +2 -4
- package/src/app/api/credentials/route.ts +10 -19
- package/src/app/api/daemon/health-check/route.ts +3 -4
- package/src/app/api/daemon/route.ts +10 -8
- package/src/app/api/documents/route.ts +11 -10
- package/src/app/api/external-agents/route.ts +3 -3
- package/src/app/api/gateways/[id]/health/route.ts +2 -3
- package/src/app/api/gateways/[id]/route.ts +7 -122
- package/src/app/api/gateways/route.ts +3 -103
- package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
- package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
- package/src/app/api/openclaw/directory/route.ts +2 -2
- package/src/app/api/openclaw/history/route.ts +3 -5
- package/src/app/api/providers/[id]/models/route.test.ts +60 -0
- package/src/app/api/providers/[id]/models/route.ts +33 -1
- package/src/app/api/providers/[id]/route.test.ts +49 -0
- package/src/app/api/providers/[id]/route.ts +30 -1
- package/src/app/api/providers/ollama/route.ts +6 -5
- package/src/app/api/schedules/[id]/route.ts +14 -108
- package/src/app/api/schedules/[id]/run/route.ts +6 -67
- package/src/app/api/schedules/route.ts +9 -51
- package/src/app/api/settings/route.ts +4 -3
- package/src/app/api/setup/check-provider/route.ts +15 -1
- package/src/app/api/setup/openclaw-device/route.ts +2 -2
- package/src/app/api/system/status/route.ts +2 -2
- package/src/app/api/tasks/[id]/route.ts +16 -202
- package/src/app/api/tasks/bulk/route.ts +5 -86
- package/src/app/api/tasks/metrics/route.ts +2 -1
- package/src/app/api/tasks/route.ts +11 -171
- package/src/app/api/upload/route.ts +1 -1
- package/src/app/api/uploads/[filename]/route.ts +1 -1
- package/src/app/api/uploads/route.ts +1 -1
- package/src/app/api/webhooks/[id]/history/route.ts +2 -2
- package/src/app/layout.tsx +9 -6
- package/src/app/protocols/page.tsx +71 -89
- package/src/app/tasks/page.tsx +32 -32
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-sheet.tsx +51 -25
- package/src/components/agents/inspector-panel.tsx +15 -4
- package/src/components/auth/setup-wizard/index.tsx +27 -18
- package/src/components/auth/setup-wizard/shared.tsx +2 -2
- package/src/components/auth/setup-wizard/step-agents.tsx +51 -38
- package/src/components/auth/setup-wizard/step-connect.tsx +48 -17
- package/src/components/auth/setup-wizard/types.ts +6 -4
- package/src/components/auth/setup-wizard/utils.test.ts +38 -8
- package/src/components/auth/setup-wizard/utils.ts +14 -8
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
- package/src/components/connectors/connector-list.tsx +26 -40
- package/src/components/connectors/connector-sheet.tsx +95 -149
- package/src/components/gateways/gateway-sheet.tsx +61 -110
- package/src/components/layout/live-query-sync.tsx +121 -0
- package/src/components/protocols/structured-session-launcher.tsx +24 -45
- package/src/components/providers/app-query-provider.tsx +17 -0
- package/src/components/providers/provider-list.tsx +150 -77
- package/src/components/providers/provider-sheet.tsx +102 -77
- package/src/components/shared/model-combobox.tsx +5 -4
- package/src/components/skills/skill-list.tsx +5 -18
- package/src/components/skills/skill-sheet.tsx +21 -20
- package/src/components/skills/skills-workspace.tsx +48 -87
- package/src/components/tasks/task-card.tsx +20 -13
- package/src/components/tasks/task-column.tsx +22 -7
- package/src/components/tasks/task-list.tsx +8 -11
- package/src/components/tasks/task-sheet.tsx +111 -103
- package/src/features/agents/queries.ts +20 -0
- package/src/features/chatrooms/queries.ts +20 -0
- package/src/features/chats/queries.ts +27 -0
- package/src/features/connectors/queries.ts +145 -0
- package/src/features/credentials/queries.ts +37 -0
- package/src/features/extensions/queries.ts +26 -0
- package/src/features/external-agents/queries.ts +36 -0
- package/src/features/gateways/queries.ts +274 -0
- package/src/features/missions/queries.ts +23 -0
- package/src/features/projects/queries.ts +20 -0
- package/src/features/protocols/queries.ts +149 -0
- package/src/features/providers/queries.ts +142 -0
- package/src/features/settings/queries.ts +20 -0
- package/src/features/skills/queries.ts +182 -0
- package/src/features/tasks/queries.ts +189 -0
- package/src/hooks/use-ws.ts +3 -2
- package/src/lib/agent-provider-options.test.ts +152 -0
- package/src/lib/agent-provider-options.ts +84 -0
- package/src/lib/app/api-client.ts +2 -2
- package/src/lib/providers/index.test.ts +78 -0
- package/src/lib/providers/index.ts +13 -10
- package/src/lib/query/client.ts +17 -0
- package/src/lib/server/agents/agent-runtime-config.ts +6 -6
- package/src/lib/server/agents/agent-service.ts +429 -0
- package/src/lib/server/agents/agent-thread-session.ts +6 -5
- package/src/lib/server/agents/autonomy-contract.ts +1 -4
- package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
- package/src/lib/server/agents/delegation-advisory.ts +251 -0
- package/src/lib/server/agents/main-agent-loop.ts +98 -40
- package/src/lib/server/agents/subagent-runtime.ts +12 -0
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
- package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
- package/src/lib/server/build-llm.ts +7 -15
- package/src/lib/server/capability-router.test.ts +70 -1
- package/src/lib/server/capability-router.ts +24 -99
- package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
- package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
- package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
- package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
- package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
- package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
- package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
- package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
- package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
- package/src/lib/server/chat-execution/message-classifier.ts +74 -32
- package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
- package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
- package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
- package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
- package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
- package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
- package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
- package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
- package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
- package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
- package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
- package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
- package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
- package/src/lib/server/chats/chat-session-service.ts +410 -0
- package/src/lib/server/connectors/access.ts +1 -1
- package/src/lib/server/connectors/commands.ts +7 -6
- package/src/lib/server/connectors/connector-inbound.ts +14 -7
- package/src/lib/server/connectors/connector-outbound.ts +16 -11
- package/src/lib/server/connectors/connector-service.ts +453 -0
- package/src/lib/server/connectors/delivery.ts +17 -12
- package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
- package/src/lib/server/connectors/media.ts +1 -1
- package/src/lib/server/connectors/response-media.ts +1 -1
- package/src/lib/server/connectors/session-consolidation.ts +11 -7
- package/src/lib/server/connectors/session.ts +9 -7
- package/src/lib/server/connectors/voice-note.ts +2 -1
- package/src/lib/server/context-manager.ts +20 -1
- package/src/lib/server/cost.ts +2 -3
- package/src/lib/server/credentials/credential-repository.ts +43 -4
- package/src/lib/server/credentials/credential-service.ts +112 -0
- package/src/lib/server/daemon/admin-metadata.ts +64 -0
- package/src/lib/server/daemon/controller.ts +577 -0
- package/src/lib/server/daemon/daemon-runtime.ts +352 -0
- package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
- package/src/lib/server/daemon/types.ts +101 -0
- package/src/lib/server/embeddings.ts +3 -9
- package/src/lib/server/eval/agent-regression.ts +3 -2
- package/src/lib/server/eval/runner.ts +2 -2
- package/src/lib/server/execution-brief.test.ts +167 -0
- package/src/lib/server/execution-brief.ts +295 -0
- package/src/lib/server/execution-engine/chat-turn.ts +9 -0
- package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
- package/src/lib/server/execution-engine/index.ts +35 -0
- package/src/lib/server/execution-engine/task-attempt.ts +303 -0
- package/src/lib/server/execution-engine/types.ts +33 -0
- package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
- package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
- package/src/lib/server/memory/session-archive-memory.ts +12 -10
- package/src/lib/server/messages/message-repository.ts +330 -0
- package/src/lib/server/missions/mission-service/core.ts +8 -6
- package/src/lib/server/openclaw/agent-resolver.ts +2 -3
- package/src/lib/server/openclaw/doctor.ts +1 -1
- package/src/lib/server/openclaw/gateway.test.ts +10 -1
- package/src/lib/server/openclaw/gateway.ts +5 -14
- package/src/lib/server/openclaw/health.ts +3 -11
- package/src/lib/server/openclaw/sync.ts +8 -6
- package/src/lib/server/persistence/storage-context.ts +3 -0
- package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
- package/src/lib/server/protocols/protocol-normalization.ts +1 -1
- package/src/lib/server/protocols/protocol-queries.ts +13 -7
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
- package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
- package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
- package/src/lib/server/protocols/protocol-swarm.ts +8 -8
- package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
- package/src/lib/server/protocols/protocol-templates.ts +4 -2
- package/src/lib/server/protocols/protocol-types.ts +10 -7
- package/src/lib/server/provider-endpoint.ts +7 -12
- package/src/lib/server/provider-model-discovery.ts +2 -11
- package/src/lib/server/query-expansion.ts +5 -6
- package/src/lib/server/run-context.test.ts +365 -0
- package/src/lib/server/run-context.ts +367 -0
- package/src/lib/server/runtime/heartbeat-service.ts +7 -5
- package/src/lib/server/runtime/queue/core.ts +61 -190
- package/src/lib/server/runtime/run-ledger.ts +8 -0
- package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
- package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
- package/src/lib/server/schedules/schedule-route-service.ts +230 -0
- package/src/lib/server/service-result.ts +16 -0
- package/src/lib/server/session-note.ts +2 -3
- package/src/lib/server/session-reset-policy.ts +4 -3
- package/src/lib/server/session-tools/connector.ts +9 -6
- package/src/lib/server/session-tools/context-mgmt.ts +58 -9
- package/src/lib/server/session-tools/crud.ts +162 -10
- package/src/lib/server/session-tools/delegate.ts +1 -1
- package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
- package/src/lib/server/session-tools/memory.ts +6 -4
- package/src/lib/server/session-tools/session-info.test.ts +56 -0
- package/src/lib/server/session-tools/session-info.ts +119 -12
- package/src/lib/server/session-tools/skill-runtime.ts +3 -1
- package/src/lib/server/session-tools/skills.ts +15 -15
- package/src/lib/server/session-tools/subagent.test.ts +115 -1
- package/src/lib/server/session-tools/subagent.ts +125 -7
- package/src/lib/server/session-tools/team-context.ts +4 -3
- package/src/lib/server/session-tools/wallet.ts +0 -58
- package/src/lib/server/sessions/session-lineage.ts +55 -0
- package/src/lib/server/sessions/session-repository.ts +2 -2
- package/src/lib/server/skills/learned-skills.ts +24 -23
- package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
- package/src/lib/server/skills/skill-repository.ts +136 -13
- package/src/lib/server/skills/skill-suggestions.ts +25 -28
- package/src/lib/server/storage-normalization.test.ts +42 -215
- package/src/lib/server/storage-normalization.ts +98 -0
- package/src/lib/server/storage.ts +19 -0
- package/src/lib/server/structured-extract.ts +3 -14
- package/src/lib/server/tasks/task-followups.ts +16 -11
- package/src/lib/server/tasks/task-result.test.ts +25 -29
- package/src/lib/server/tasks/task-result.ts +5 -9
- package/src/lib/server/tasks/task-route-service.ts +449 -0
- package/src/lib/server/text-normalization.ts +41 -0
- package/src/lib/server/tool-planning.ts +6 -42
- package/src/lib/server/upload-path.ts +5 -0
- package/src/lib/server/working-state/extraction.ts +614 -0
- package/src/lib/server/working-state/normalization.ts +866 -0
- package/src/lib/server/working-state/prompt.ts +60 -0
- package/src/lib/server/working-state/repository.ts +38 -0
- package/src/lib/server/working-state/service.test.ts +253 -0
- package/src/lib/server/working-state/service.ts +293 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/ws-client.ts +3 -3
- package/src/stores/slices/task-slice.ts +1 -4
- package/src/stores/use-chatroom-store.ts +2 -2
- package/src/types/index.ts +288 -22
- package/src/views/settings/section-providers.tsx +2 -2
|
@@ -3,8 +3,10 @@ import path from 'path'
|
|
|
3
3
|
import { ImapFlow } from 'imapflow'
|
|
4
4
|
import { createTransport } from 'nodemailer'
|
|
5
5
|
import { simpleParser } from 'mailparser'
|
|
6
|
-
import {
|
|
6
|
+
import type { Connector } from '@/types'
|
|
7
|
+
import { loadConnectors } from '@/lib/server/connectors/connector-repository'
|
|
7
8
|
import { getExtensionManager } from '@/lib/server/extensions'
|
|
9
|
+
import { UPLOAD_DIR } from '@/lib/server/upload-path'
|
|
8
10
|
|
|
9
11
|
export interface MailboxConfig {
|
|
10
12
|
imapHost: string
|
|
@@ -79,7 +81,7 @@ export function getMailboxConfig(): MailboxConfig {
|
|
|
79
81
|
const emailSettings = extensionManager.getExtensionSettings('email') as Record<string, unknown>
|
|
80
82
|
const connectors = loadConnectors()
|
|
81
83
|
const emailConnector = Object.values(connectors)
|
|
82
|
-
.find((entry) => entry
|
|
84
|
+
.find((entry) => entry.platform === 'email') as Connector | undefined
|
|
83
85
|
const connectorConfig = emailConnector && typeof emailConnector.config === 'object' && emailConnector.config
|
|
84
86
|
? emailConnector.config as Record<string, unknown>
|
|
85
87
|
: {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { genId } from '@/lib/id'
|
|
2
2
|
import type { MailboxEnvelope } from '@/types'
|
|
3
|
-
import {
|
|
3
|
+
import { loadSession, patchSession } from '@/lib/server/sessions/session-repository'
|
|
4
4
|
import { requestMissionTicksForHumanReply } from '@/lib/server/missions/mission-service'
|
|
5
5
|
|
|
6
6
|
interface MailboxOptions {
|
|
@@ -83,9 +83,8 @@ function normalizeMailbox(target: { mailbox?: MailboxEnvelope[] | null }, now =
|
|
|
83
83
|
|
|
84
84
|
function findLatestPendingHumanRequestEnvelope(
|
|
85
85
|
sessionId: string,
|
|
86
|
-
|
|
86
|
+
target = loadSession(sessionId),
|
|
87
87
|
): MailboxEnvelope | null {
|
|
88
|
-
const target = sessions[sessionId]
|
|
89
88
|
if (!target) throw new Error(`Session not found: ${sessionId}`)
|
|
90
89
|
const envelopes = normalizeMailbox(target)
|
|
91
90
|
const repliedCorrelationIds = new Set(
|
|
@@ -108,8 +107,7 @@ export function findPendingHumanRequestEnvelope(params: {
|
|
|
108
107
|
fromSessionId?: string | null
|
|
109
108
|
fromAgentId?: string | null
|
|
110
109
|
}): MailboxEnvelope | null {
|
|
111
|
-
const
|
|
112
|
-
const target = sessions[params.sessionId]
|
|
110
|
+
const target = loadSession(params.sessionId)
|
|
113
111
|
if (!target) throw new Error(`Session not found: ${params.sessionId}`)
|
|
114
112
|
const expectedSignature = normalizeHumanRequestSignature(params)
|
|
115
113
|
const envelopes = normalizeMailbox(target)
|
|
@@ -139,8 +137,7 @@ export function sendMailboxEnvelope(input: {
|
|
|
139
137
|
correlationId?: string | null
|
|
140
138
|
ttlSec?: number | null
|
|
141
139
|
}): MailboxEnvelope {
|
|
142
|
-
const
|
|
143
|
-
const target = sessions[input.toSessionId]
|
|
140
|
+
const target = loadSession(input.toSessionId)
|
|
144
141
|
if (!target) throw new Error(`Target session not found: ${input.toSessionId}`)
|
|
145
142
|
|
|
146
143
|
const now = Date.now()
|
|
@@ -162,12 +159,14 @@ export function sendMailboxEnvelope(input: {
|
|
|
162
159
|
ackAt: null,
|
|
163
160
|
}
|
|
164
161
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
162
|
+
patchSession(input.toSessionId, (current) => {
|
|
163
|
+
if (!current) return null
|
|
164
|
+
return {
|
|
165
|
+
...current,
|
|
166
|
+
mailbox: [...normalizeMailbox(current, now), envelope],
|
|
167
|
+
lastActiveAt: now,
|
|
168
|
+
}
|
|
169
|
+
})
|
|
171
170
|
if (envelope.type === 'human_reply') {
|
|
172
171
|
requestMissionTicksForHumanReply({
|
|
173
172
|
sessionId: input.toSessionId,
|
|
@@ -188,8 +187,7 @@ export function sendMailboxEnvelope(input: {
|
|
|
188
187
|
}
|
|
189
188
|
|
|
190
189
|
export function listMailbox(sessionId: string, opts: MailboxOptions = {}): MailboxEnvelope[] {
|
|
191
|
-
const
|
|
192
|
-
const target = sessions[sessionId]
|
|
190
|
+
const target = loadSession(sessionId)
|
|
193
191
|
if (!target) throw new Error(`Session not found: ${sessionId}`)
|
|
194
192
|
const list = pruneExpired(normalizeMailboxList(target.mailbox || []))
|
|
195
193
|
const includeAcked = opts.includeAcked === true
|
|
@@ -201,36 +199,48 @@ export function listMailbox(sessionId: string, opts: MailboxOptions = {}): Mailb
|
|
|
201
199
|
}
|
|
202
200
|
|
|
203
201
|
export function ackMailboxEnvelope(sessionId: string, envelopeId: string): MailboxEnvelope | null {
|
|
204
|
-
const
|
|
205
|
-
const target = sessions[sessionId]
|
|
202
|
+
const target = loadSession(sessionId)
|
|
206
203
|
if (!target) throw new Error(`Session not found: ${sessionId}`)
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
204
|
+
const ackAt = Date.now()
|
|
205
|
+
let acked: MailboxEnvelope | null = null
|
|
206
|
+
patchSession(sessionId, (current) => {
|
|
207
|
+
if (!current) return null
|
|
208
|
+
const list = normalizeMailbox(current)
|
|
209
|
+
const idx = list.findIndex((env) => env.id === envelopeId)
|
|
210
|
+
if (idx === -1) return current
|
|
211
|
+
list[idx] = {
|
|
212
|
+
...list[idx],
|
|
213
|
+
status: 'ack',
|
|
214
|
+
ackAt,
|
|
215
|
+
}
|
|
216
|
+
acked = list[idx]
|
|
217
|
+
return {
|
|
218
|
+
...current,
|
|
219
|
+
mailbox: list,
|
|
220
|
+
lastActiveAt: ackAt,
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
return acked
|
|
220
224
|
}
|
|
221
225
|
|
|
222
226
|
export function clearMailbox(sessionId: string, includeAcked = true): { before: number; after: number } {
|
|
223
|
-
const
|
|
224
|
-
const target = sessions[sessionId]
|
|
227
|
+
const target = loadSession(sessionId)
|
|
225
228
|
if (!target) throw new Error(`Session not found: ${sessionId}`)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
229
|
+
let before = 0
|
|
230
|
+
let after = 0
|
|
231
|
+
patchSession(sessionId, (current) => {
|
|
232
|
+
if (!current) return null
|
|
233
|
+
const list = normalizeMailbox(current)
|
|
234
|
+
const afterList = includeAcked ? [] : list.filter((env) => env.status !== 'ack')
|
|
235
|
+
before = list.length
|
|
236
|
+
after = afterList.length
|
|
237
|
+
return {
|
|
238
|
+
...current,
|
|
239
|
+
mailbox: afterList,
|
|
240
|
+
lastActiveAt: Date.now(),
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
return { before, after }
|
|
234
244
|
}
|
|
235
245
|
|
|
236
246
|
export function bridgeHumanReplyFromChat(input: {
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import os from 'node:os'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { genId } from '@/lib/id'
|
|
5
|
+
import { normalizeCapabilitySelection } from '@/lib/capability-selection'
|
|
6
|
+
import { buildAgentDisabledMessage, isAgentDisabled } from '@/lib/server/agents/agent-availability'
|
|
7
|
+
import { loadAgent } from '@/lib/server/agents/agent-repository'
|
|
8
|
+
import { clearMainLoopStateForSession } from '@/lib/server/agents/main-agent-loop'
|
|
9
|
+
import { applyResolvedRoute, resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
|
|
10
|
+
import { enrichSessionWithMissionSummary } from '@/lib/server/missions/mission-service'
|
|
11
|
+
import { cleanupSessionProcesses } from '@/lib/server/runtime/process-manager'
|
|
12
|
+
import { stopActiveSessionProcess } from '@/lib/server/runtime/runtime-state'
|
|
13
|
+
import {
|
|
14
|
+
cancelQueuedRunById,
|
|
15
|
+
cancelQueuedRunsForSession,
|
|
16
|
+
enqueueSessionRun,
|
|
17
|
+
getSessionQueueSnapshot,
|
|
18
|
+
getSessionRunState,
|
|
19
|
+
} from '@/lib/server/runtime/session-run-manager'
|
|
20
|
+
import { deleteSession, getSession, listSessions, saveSession } from '@/lib/server/sessions/session-repository'
|
|
21
|
+
import {
|
|
22
|
+
clearMessages,
|
|
23
|
+
deleteSessionMessages,
|
|
24
|
+
getMessages,
|
|
25
|
+
truncateAfter,
|
|
26
|
+
} from '@/lib/server/messages/message-repository'
|
|
27
|
+
import { deleteSessionWorkingState } from '@/lib/server/working-state/service'
|
|
28
|
+
import { normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint'
|
|
29
|
+
import { serviceFail, serviceOk } from '@/lib/server/service-result'
|
|
30
|
+
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
31
|
+
import { buildSessionListSummary } from '@/lib/chat/session-summary'
|
|
32
|
+
import type { Session } from '@/types'
|
|
33
|
+
import type { ServiceResult } from '@/lib/server/service-result'
|
|
34
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
35
|
+
|
|
36
|
+
function normalizeCwd(value: unknown): string {
|
|
37
|
+
const raw = typeof value === 'string' ? value.trim() : ''
|
|
38
|
+
if (raw.startsWith('~/')) return path.join(os.homedir(), raw.slice(2))
|
|
39
|
+
if (raw === '~') return os.homedir()
|
|
40
|
+
if (!raw) return WORKSPACE_DIR
|
|
41
|
+
return raw
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function emptyDelegateResumeIds() {
|
|
45
|
+
return {
|
|
46
|
+
claudeCode: null,
|
|
47
|
+
codex: null,
|
|
48
|
+
opencode: null,
|
|
49
|
+
gemini: null,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function listChatsForApi(): Record<string, ReturnType<typeof buildSessionListSummary>> {
|
|
54
|
+
const sessions = listSessions()
|
|
55
|
+
for (const id of Object.keys(sessions)) {
|
|
56
|
+
const run = getSessionRunState(id)
|
|
57
|
+
const queue = getSessionQueueSnapshot(id)
|
|
58
|
+
sessions[id].active = !!run.runningRunId
|
|
59
|
+
sessions[id].queuedCount = queue.queueLength
|
|
60
|
+
sessions[id].currentRunId = run.runningRunId || null
|
|
61
|
+
}
|
|
62
|
+
return Object.fromEntries(
|
|
63
|
+
Object.entries(sessions).map(([id, session]) => [id, buildSessionListSummary(enrichSessionWithMissionSummary(session))]),
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getChatSessionForApi(sessionId: string): Session | null {
|
|
68
|
+
const session = getSession(sessionId)
|
|
69
|
+
if (!session) return null
|
|
70
|
+
const run = getSessionRunState(sessionId)
|
|
71
|
+
const queue = getSessionQueueSnapshot(sessionId)
|
|
72
|
+
session.active = !!run.runningRunId
|
|
73
|
+
session.queuedCount = queue.queueLength
|
|
74
|
+
session.currentRunId = run.runningRunId || null
|
|
75
|
+
return enrichSessionWithMissionSummary(session)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function createChatSession(input: Record<string, unknown>): ServiceResult<Session> {
|
|
79
|
+
const id = typeof input.id === 'string' && input.id.trim() ? input.id.trim() : genId()
|
|
80
|
+
const sessions = listSessions()
|
|
81
|
+
if (typeof input.id === 'string' && sessions[id]) {
|
|
82
|
+
return serviceOk(sessions[id])
|
|
83
|
+
}
|
|
84
|
+
const agent = typeof input.agentId === 'string' ? loadAgent(input.agentId) : null
|
|
85
|
+
if (isAgentDisabled(agent)) {
|
|
86
|
+
return serviceFail(409, buildAgentDisabledMessage(agent, 'start chats'))
|
|
87
|
+
}
|
|
88
|
+
const explicitOllamaMode = input.ollamaMode === 'cloud' ? 'cloud' : input.ollamaMode === 'local' ? 'local' : null
|
|
89
|
+
const routePreferredGatewayTags = Array.isArray(input.routePreferredGatewayTags)
|
|
90
|
+
? input.routePreferredGatewayTags.filter((tag): tag is string => typeof tag === 'string' && tag.trim().length > 0)
|
|
91
|
+
: []
|
|
92
|
+
const routePreferredGatewayUseCase = typeof input.routePreferredGatewayUseCase === 'string' && input.routePreferredGatewayUseCase.trim()
|
|
93
|
+
? input.routePreferredGatewayUseCase.trim()
|
|
94
|
+
: null
|
|
95
|
+
const resolvedRoute = agent ? resolvePrimaryAgentRoute(agent, undefined, {
|
|
96
|
+
preferredGatewayTags: routePreferredGatewayTags,
|
|
97
|
+
preferredGatewayUseCase: routePreferredGatewayUseCase,
|
|
98
|
+
}) : null
|
|
99
|
+
const resolvedCapabilities = normalizeCapabilitySelection({
|
|
100
|
+
tools: Array.isArray(input.tools) ? input.tools : agent?.tools,
|
|
101
|
+
extensions: Array.isArray(input.extensions) ? input.extensions : agent?.extensions,
|
|
102
|
+
})
|
|
103
|
+
const provider = (
|
|
104
|
+
typeof input.provider === 'string' && input.provider.trim()
|
|
105
|
+
? input.provider.trim()
|
|
106
|
+
: agent?.provider || 'claude-cli'
|
|
107
|
+
) as Session['provider']
|
|
108
|
+
const now = Date.now()
|
|
109
|
+
const baseSession: Session = {
|
|
110
|
+
id,
|
|
111
|
+
name: (input.name as string) || 'New Chat',
|
|
112
|
+
cwd: normalizeCwd(input.cwd),
|
|
113
|
+
user: (input.user as string) || 'user',
|
|
114
|
+
provider,
|
|
115
|
+
model: (input.model as string) || agent?.model || '',
|
|
116
|
+
ollamaMode: explicitOllamaMode ?? agent?.ollamaMode ?? (provider === 'ollama' ? 'local' : null),
|
|
117
|
+
credentialId: (input.credentialId as string | null | undefined) || agent?.credentialId || null,
|
|
118
|
+
fallbackCredentialIds: Array.isArray(input.fallbackCredentialIds) ? input.fallbackCredentialIds : agent?.fallbackCredentialIds || [],
|
|
119
|
+
apiEndpoint: normalizeProviderEndpoint(
|
|
120
|
+
provider,
|
|
121
|
+
(input.apiEndpoint as string | null | undefined) || agent?.apiEndpoint || null,
|
|
122
|
+
),
|
|
123
|
+
routePreferredGatewayTags,
|
|
124
|
+
routePreferredGatewayUseCase,
|
|
125
|
+
claudeSessionId: null,
|
|
126
|
+
codexThreadId: null,
|
|
127
|
+
opencodeSessionId: null,
|
|
128
|
+
delegateResumeIds: emptyDelegateResumeIds(),
|
|
129
|
+
messages: Array.isArray(input.messages) ? input.messages : [],
|
|
130
|
+
createdAt: now,
|
|
131
|
+
lastActiveAt: now,
|
|
132
|
+
sessionType: (input.sessionType as Session['sessionType']) || 'human',
|
|
133
|
+
agentId: (input.agentId as string | null | undefined) || null,
|
|
134
|
+
parentSessionId: (input.parentSessionId as string | null | undefined) || null,
|
|
135
|
+
tools: resolvedCapabilities.tools,
|
|
136
|
+
extensions: resolvedCapabilities.extensions,
|
|
137
|
+
heartbeatEnabled: (input.heartbeatEnabled as boolean | null | undefined) ?? null,
|
|
138
|
+
heartbeatIntervalSec: (input.heartbeatIntervalSec as number | null | undefined) ?? null,
|
|
139
|
+
sessionResetMode: (input.sessionResetMode as Session['sessionResetMode']) ?? agent?.sessionResetMode ?? null,
|
|
140
|
+
sessionIdleTimeoutSec: (input.sessionIdleTimeoutSec as number | null | undefined) ?? agent?.sessionIdleTimeoutSec ?? null,
|
|
141
|
+
sessionMaxAgeSec: (input.sessionMaxAgeSec as number | null | undefined) ?? agent?.sessionMaxAgeSec ?? null,
|
|
142
|
+
sessionDailyResetAt: (input.sessionDailyResetAt as string | null | undefined) ?? agent?.sessionDailyResetAt ?? null,
|
|
143
|
+
sessionResetTimezone: (input.sessionResetTimezone as string | null | undefined) ?? agent?.sessionResetTimezone ?? null,
|
|
144
|
+
thinkingLevel: (input.thinkingLevel as Session['thinkingLevel']) ?? null,
|
|
145
|
+
connectorThinkLevel: (input.connectorThinkLevel as Session['connectorThinkLevel']) ?? null,
|
|
146
|
+
connectorSessionScope: (input.connectorSessionScope as Session['connectorSessionScope']) ?? null,
|
|
147
|
+
connectorReplyMode: (input.connectorReplyMode as Session['connectorReplyMode']) ?? null,
|
|
148
|
+
connectorThreadBinding: (input.connectorThreadBinding as Session['connectorThreadBinding']) ?? null,
|
|
149
|
+
connectorGroupPolicy: (input.connectorGroupPolicy as Session['connectorGroupPolicy']) ?? null,
|
|
150
|
+
connectorIdleTimeoutSec: (input.connectorIdleTimeoutSec as number | null | undefined) ?? null,
|
|
151
|
+
connectorMaxAgeSec: (input.connectorMaxAgeSec as number | null | undefined) ?? null,
|
|
152
|
+
connectorContext: input.connectorContext === null
|
|
153
|
+
? undefined
|
|
154
|
+
: (input.connectorContext as Session['connectorContext']),
|
|
155
|
+
identityState: (input.identityState as Session['identityState']) ?? agent?.identityState ?? null,
|
|
156
|
+
sessionArchiveState: (input.sessionArchiveState as Session['sessionArchiveState']) ?? null,
|
|
157
|
+
}
|
|
158
|
+
const session: Session = (input.provider || input.model || input.credentialId || input.apiEndpoint)
|
|
159
|
+
? baseSession
|
|
160
|
+
: applyResolvedRoute(baseSession, resolvedRoute)
|
|
161
|
+
saveSession(id, session)
|
|
162
|
+
notify('sessions')
|
|
163
|
+
return serviceOk(session)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function deleteChats(ids: string[]): { deleted: number; requested: number } {
|
|
167
|
+
let deleted = 0
|
|
168
|
+
const sessions = listSessions()
|
|
169
|
+
for (const id of ids) {
|
|
170
|
+
if (!sessions[id]) continue
|
|
171
|
+
stopActiveSessionProcess(id)
|
|
172
|
+
deleteSessionWorkingState(id)
|
|
173
|
+
clearMainLoopStateForSession(id)
|
|
174
|
+
deleteSessionMessages(id)
|
|
175
|
+
deleteSession(id)
|
|
176
|
+
deleted += 1
|
|
177
|
+
}
|
|
178
|
+
if (deleted > 0) notify('sessions')
|
|
179
|
+
return { deleted, requested: ids.length }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function updateChatSession(sessionId: string, updates: Record<string, unknown>): Session | null {
|
|
183
|
+
const original = getSession(sessionId)
|
|
184
|
+
if (!original) return null
|
|
185
|
+
const session = original as unknown as Record<string, unknown>
|
|
186
|
+
|
|
187
|
+
if (updates.resetMainLoopState === true) {
|
|
188
|
+
clearMainLoopStateForSession(sessionId)
|
|
189
|
+
deleteSessionWorkingState(sessionId)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const agentIdUpdateProvided = updates.agentId !== undefined
|
|
193
|
+
let nextAgentId = session.agentId
|
|
194
|
+
if (agentIdUpdateProvided) {
|
|
195
|
+
session.agentId = updates.agentId
|
|
196
|
+
nextAgentId = updates.agentId
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const linkedAgent = nextAgentId ? loadAgent(String(nextAgentId)) : null
|
|
200
|
+
const routePreferredGatewayTags = updates.routePreferredGatewayTags !== undefined
|
|
201
|
+
? (Array.isArray(updates.routePreferredGatewayTags)
|
|
202
|
+
? updates.routePreferredGatewayTags.filter((tag): tag is string => typeof tag === 'string' && tag.trim().length > 0)
|
|
203
|
+
: [])
|
|
204
|
+
: ((session.routePreferredGatewayTags as string[]) || [])
|
|
205
|
+
const routePreferredGatewayUseCase = updates.routePreferredGatewayUseCase !== undefined
|
|
206
|
+
? (typeof updates.routePreferredGatewayUseCase === 'string' && updates.routePreferredGatewayUseCase.trim()
|
|
207
|
+
? updates.routePreferredGatewayUseCase.trim()
|
|
208
|
+
: null)
|
|
209
|
+
: ((session.routePreferredGatewayUseCase as string | null) || null)
|
|
210
|
+
const linkedRoute = linkedAgent ? resolvePrimaryAgentRoute(linkedAgent, undefined, {
|
|
211
|
+
preferredGatewayTags: routePreferredGatewayTags,
|
|
212
|
+
preferredGatewayUseCase: routePreferredGatewayUseCase,
|
|
213
|
+
}) : null
|
|
214
|
+
|
|
215
|
+
if (updates.name !== undefined) session.name = updates.name
|
|
216
|
+
if (updates.cwd !== undefined) session.cwd = normalizeCwd(updates.cwd)
|
|
217
|
+
if (updates.provider !== undefined) session.provider = updates.provider
|
|
218
|
+
else if (agentIdUpdateProvided && linkedAgent?.provider) session.provider = linkedAgent.provider
|
|
219
|
+
if (updates.model !== undefined) session.model = updates.model
|
|
220
|
+
else if (agentIdUpdateProvided && linkedRoute?.model) session.model = linkedRoute.model
|
|
221
|
+
else if (agentIdUpdateProvided && linkedAgent?.model !== undefined) session.model = linkedAgent.model
|
|
222
|
+
if (updates.ollamaMode !== undefined) session.ollamaMode = updates.ollamaMode
|
|
223
|
+
else if (updates.provider !== undefined && updates.provider !== 'ollama') session.ollamaMode = null
|
|
224
|
+
else if (agentIdUpdateProvided && linkedRoute) session.ollamaMode = linkedRoute.ollamaMode ?? null
|
|
225
|
+
else if (agentIdUpdateProvided && linkedAgent) session.ollamaMode = linkedAgent.ollamaMode ?? null
|
|
226
|
+
if (updates.credentialId !== undefined) session.credentialId = updates.credentialId
|
|
227
|
+
else if (agentIdUpdateProvided && linkedRoute) session.credentialId = linkedRoute.credentialId ?? null
|
|
228
|
+
else if (agentIdUpdateProvided && linkedAgent) session.credentialId = linkedAgent.credentialId ?? null
|
|
229
|
+
if (updates.fallbackCredentialIds !== undefined) session.fallbackCredentialIds = updates.fallbackCredentialIds
|
|
230
|
+
else if (agentIdUpdateProvided && linkedRoute) session.fallbackCredentialIds = [...linkedRoute.fallbackCredentialIds]
|
|
231
|
+
if (updates.gatewayProfileId !== undefined) session.gatewayProfileId = updates.gatewayProfileId
|
|
232
|
+
else if (agentIdUpdateProvided && linkedRoute) session.gatewayProfileId = linkedRoute.gatewayProfileId ?? null
|
|
233
|
+
if (updates.routePreferredGatewayTags !== undefined) session.routePreferredGatewayTags = routePreferredGatewayTags
|
|
234
|
+
if (updates.routePreferredGatewayUseCase !== undefined) session.routePreferredGatewayUseCase = routePreferredGatewayUseCase
|
|
235
|
+
|
|
236
|
+
if (updates.tools !== undefined || updates.extensions !== undefined || (agentIdUpdateProvided && linkedAgent)) {
|
|
237
|
+
const nextSelection = normalizeCapabilitySelection({
|
|
238
|
+
tools: Array.isArray(updates.tools)
|
|
239
|
+
? updates.tools
|
|
240
|
+
: (agentIdUpdateProvided && linkedAgent ? linkedAgent.tools : session.tools as string[] | undefined),
|
|
241
|
+
extensions: Array.isArray(updates.extensions)
|
|
242
|
+
? updates.extensions
|
|
243
|
+
: (agentIdUpdateProvided && linkedAgent ? linkedAgent.extensions : session.extensions as string[] | undefined),
|
|
244
|
+
})
|
|
245
|
+
session.tools = nextSelection.tools
|
|
246
|
+
session.extensions = nextSelection.extensions
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (updates.apiEndpoint !== undefined) {
|
|
250
|
+
session.apiEndpoint = normalizeProviderEndpoint(
|
|
251
|
+
(updates.provider || session.provider) as string,
|
|
252
|
+
updates.apiEndpoint as string | null | undefined,
|
|
253
|
+
)
|
|
254
|
+
} else if (agentIdUpdateProvided && linkedRoute) {
|
|
255
|
+
session.apiEndpoint = linkedRoute.apiEndpoint ?? null
|
|
256
|
+
} else if (agentIdUpdateProvided && linkedAgent) {
|
|
257
|
+
session.apiEndpoint = normalizeProviderEndpoint(linkedAgent.provider, linkedAgent.apiEndpoint ?? null)
|
|
258
|
+
}
|
|
259
|
+
if (updates.heartbeatEnabled !== undefined) session.heartbeatEnabled = updates.heartbeatEnabled
|
|
260
|
+
if (updates.heartbeatIntervalSec !== undefined) session.heartbeatIntervalSec = updates.heartbeatIntervalSec
|
|
261
|
+
if (updates.sessionResetMode !== undefined) session.sessionResetMode = updates.sessionResetMode
|
|
262
|
+
if (updates.sessionIdleTimeoutSec !== undefined) session.sessionIdleTimeoutSec = updates.sessionIdleTimeoutSec
|
|
263
|
+
if (updates.sessionMaxAgeSec !== undefined) session.sessionMaxAgeSec = updates.sessionMaxAgeSec
|
|
264
|
+
if (updates.sessionDailyResetAt !== undefined) session.sessionDailyResetAt = updates.sessionDailyResetAt
|
|
265
|
+
if (updates.sessionResetTimezone !== undefined) session.sessionResetTimezone = updates.sessionResetTimezone
|
|
266
|
+
if (updates.thinkingLevel !== undefined) session.thinkingLevel = updates.thinkingLevel
|
|
267
|
+
if (updates.connectorThinkLevel !== undefined) session.connectorThinkLevel = updates.connectorThinkLevel
|
|
268
|
+
if (updates.connectorSessionScope !== undefined) session.connectorSessionScope = updates.connectorSessionScope
|
|
269
|
+
if (updates.connectorReplyMode !== undefined) session.connectorReplyMode = updates.connectorReplyMode
|
|
270
|
+
if (updates.connectorThreadBinding !== undefined) session.connectorThreadBinding = updates.connectorThreadBinding
|
|
271
|
+
if (updates.connectorGroupPolicy !== undefined) session.connectorGroupPolicy = updates.connectorGroupPolicy
|
|
272
|
+
if (updates.connectorIdleTimeoutSec !== undefined) session.connectorIdleTimeoutSec = updates.connectorIdleTimeoutSec
|
|
273
|
+
if (updates.connectorMaxAgeSec !== undefined) session.connectorMaxAgeSec = updates.connectorMaxAgeSec
|
|
274
|
+
if (updates.connectorContext !== undefined) session.connectorContext = updates.connectorContext
|
|
275
|
+
if (updates.identityState !== undefined) session.identityState = updates.identityState
|
|
276
|
+
if (updates.sessionArchiveState !== undefined) session.sessionArchiveState = updates.sessionArchiveState
|
|
277
|
+
if (updates.lastSessionResetAt !== undefined) session.lastSessionResetAt = updates.lastSessionResetAt
|
|
278
|
+
if (updates.lastSessionResetReason !== undefined) session.lastSessionResetReason = updates.lastSessionResetReason
|
|
279
|
+
if (updates.pinned !== undefined) session.pinned = !!updates.pinned
|
|
280
|
+
if (updates.claudeSessionId !== undefined) session.claudeSessionId = updates.claudeSessionId
|
|
281
|
+
if (updates.codexThreadId !== undefined) session.codexThreadId = updates.codexThreadId
|
|
282
|
+
if (updates.opencodeSessionId !== undefined) session.opencodeSessionId = updates.opencodeSessionId
|
|
283
|
+
if (updates.delegateResumeIds !== undefined) session.delegateResumeIds = updates.delegateResumeIds
|
|
284
|
+
if (!Array.isArray(session.messages)) session.messages = []
|
|
285
|
+
|
|
286
|
+
saveSession(sessionId, original)
|
|
287
|
+
notify('sessions')
|
|
288
|
+
return enrichSessionWithMissionSummary(original)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function deleteChatSession(sessionId: string): boolean {
|
|
292
|
+
if (!getSession(sessionId)) return false
|
|
293
|
+
stopActiveSessionProcess(sessionId)
|
|
294
|
+
cleanupSessionProcesses(sessionId)
|
|
295
|
+
deleteSessionMessages(sessionId)
|
|
296
|
+
deleteSession(sessionId)
|
|
297
|
+
notify('sessions')
|
|
298
|
+
return true
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function getQueueSnapshot(sessionId: string) {
|
|
302
|
+
const session = getSession(sessionId)
|
|
303
|
+
if (!session) return null
|
|
304
|
+
return getSessionQueueSnapshot(sessionId)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function queueChatMessage(sessionId: string, body: Record<string, unknown>): ServiceResult<Record<string, unknown>> {
|
|
308
|
+
const session = getSession(sessionId)
|
|
309
|
+
if (!session) return serviceFail(404, 'Not found')
|
|
310
|
+
const message = typeof body.message === 'string' ? body.message : ''
|
|
311
|
+
const imagePath = typeof body.imagePath === 'string' ? body.imagePath : undefined
|
|
312
|
+
const imageUrl = typeof body.imageUrl === 'string' ? body.imageUrl : undefined
|
|
313
|
+
const attachedFiles = Array.isArray(body.attachedFiles)
|
|
314
|
+
? body.attachedFiles.filter((file): file is string => typeof file === 'string' && file.trim().length > 0)
|
|
315
|
+
: undefined
|
|
316
|
+
const replyToId = typeof body.replyToId === 'string' ? body.replyToId : undefined
|
|
317
|
+
const hasFiles = !!(imagePath || imageUrl || attachedFiles?.length)
|
|
318
|
+
if (!message.trim() && !hasFiles) {
|
|
319
|
+
return serviceFail(400, 'message or file is required')
|
|
320
|
+
}
|
|
321
|
+
const queued = enqueueSessionRun({
|
|
322
|
+
sessionId,
|
|
323
|
+
missionId: session.missionId || null,
|
|
324
|
+
message,
|
|
325
|
+
imagePath,
|
|
326
|
+
imageUrl,
|
|
327
|
+
attachedFiles,
|
|
328
|
+
source: 'chat',
|
|
329
|
+
mode: 'followup',
|
|
330
|
+
replyToId,
|
|
331
|
+
})
|
|
332
|
+
return serviceOk({
|
|
333
|
+
queued: {
|
|
334
|
+
runId: queued.runId,
|
|
335
|
+
position: queued.position,
|
|
336
|
+
},
|
|
337
|
+
snapshot: getSessionQueueSnapshot(sessionId),
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function cancelQueuedChatMessages(sessionId: string, runId?: string): ServiceResult<Record<string, unknown>> | null {
|
|
342
|
+
const session = getSession(sessionId)
|
|
343
|
+
if (!session) return null
|
|
344
|
+
const normalizedRunId = typeof runId === 'string' ? runId.trim() : ''
|
|
345
|
+
if (normalizedRunId) {
|
|
346
|
+
const snapshot = getSessionQueueSnapshot(sessionId)
|
|
347
|
+
if (!snapshot.items.some((item) => item.runId === normalizedRunId)) {
|
|
348
|
+
return serviceFail(404, 'Queued run not found')
|
|
349
|
+
}
|
|
350
|
+
cancelQueuedRunById(normalizedRunId, 'Removed from queue')
|
|
351
|
+
return serviceOk({ cancelled: 1, snapshot: getSessionQueueSnapshot(sessionId) })
|
|
352
|
+
}
|
|
353
|
+
const cancelled = cancelQueuedRunsForSession(sessionId, 'Cleared queued messages')
|
|
354
|
+
return serviceOk({ cancelled, snapshot: getSessionQueueSnapshot(sessionId) })
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function clearChatMessages(sessionId: string): boolean {
|
|
358
|
+
const session = getSession(sessionId)
|
|
359
|
+
if (!session) return false
|
|
360
|
+
clearMessages(sessionId)
|
|
361
|
+
session.messages = []
|
|
362
|
+
session.claudeSessionId = null
|
|
363
|
+
session.codexThreadId = null
|
|
364
|
+
session.opencodeSessionId = null
|
|
365
|
+
session.delegateResumeIds = emptyDelegateResumeIds()
|
|
366
|
+
saveSession(sessionId, session)
|
|
367
|
+
notify('sessions')
|
|
368
|
+
return true
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function retryChatTurn(sessionId: string): ServiceResult<{ message: string; imagePath: string | null }> {
|
|
372
|
+
const session = getSession(sessionId)
|
|
373
|
+
if (!session) return serviceFail(404, 'Session not found')
|
|
374
|
+
const msgs = getMessages(sessionId)
|
|
375
|
+
// Remove trailing assistant messages
|
|
376
|
+
while (msgs.length && msgs[msgs.length - 1].role === 'assistant') {
|
|
377
|
+
msgs.pop()
|
|
378
|
+
}
|
|
379
|
+
if (!msgs.length) {
|
|
380
|
+
clearMessages(sessionId)
|
|
381
|
+
return serviceOk({ message: '', imagePath: null })
|
|
382
|
+
}
|
|
383
|
+
const lastUser = msgs[msgs.length - 1]
|
|
384
|
+
const message = lastUser.text
|
|
385
|
+
const imagePath = lastUser.imagePath || null
|
|
386
|
+
msgs.pop()
|
|
387
|
+
// Truncate to the new length (keep seq 0..msgs.length-1)
|
|
388
|
+
if (msgs.length === 0) {
|
|
389
|
+
clearMessages(sessionId)
|
|
390
|
+
} else {
|
|
391
|
+
truncateAfter(sessionId, msgs.length - 1)
|
|
392
|
+
}
|
|
393
|
+
return serviceOk({ message, imagePath })
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export function editAndResendChatTurn(sessionId: string, messageIndex: number, newText: string): ServiceResult<{ message: string }> {
|
|
397
|
+
const session = getSession(sessionId)
|
|
398
|
+
if (!session) return serviceFail(404, 'Not found')
|
|
399
|
+
const msgCount = getMessages(sessionId).length
|
|
400
|
+
if (typeof messageIndex !== 'number' || messageIndex < 0 || messageIndex >= msgCount) {
|
|
401
|
+
return serviceFail(400, 'Invalid message index')
|
|
402
|
+
}
|
|
403
|
+
// Keep messages up to but not including messageIndex
|
|
404
|
+
if (messageIndex === 0) {
|
|
405
|
+
clearMessages(sessionId)
|
|
406
|
+
} else {
|
|
407
|
+
truncateAfter(sessionId, messageIndex - 1)
|
|
408
|
+
}
|
|
409
|
+
return serviceOk({ message: newText })
|
|
410
|
+
}
|
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
ConnectorAccessSnapshot,
|
|
6
6
|
WhatsAppApprovedContact,
|
|
7
7
|
} from '@/types'
|
|
8
|
-
import { loadSettings } from '../
|
|
8
|
+
import { loadSettings } from '../settings/settings-repository'
|
|
9
9
|
import {
|
|
10
10
|
createOrTouchPairingRequest,
|
|
11
11
|
getSenderAddressingOverride,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getProvider } from '@/lib/providers'
|
|
2
2
|
import type { Connector } from '@/types'
|
|
3
|
-
import { loadAgents } from '
|
|
3
|
+
import { loadAgents } from '@/lib/server/agents/agent-repository'
|
|
4
4
|
import { syncSessionArchiveMemory } from '@/lib/server/memory/session-archive-memory'
|
|
5
|
+
import { getMessages, replaceAllMessages } from '@/lib/server/messages/message-repository'
|
|
5
6
|
import { getEnabledCapabilityIds } from '@/lib/capability-selection'
|
|
6
7
|
import { resolvePairingAccess } from './access'
|
|
7
8
|
import {
|
|
@@ -189,9 +190,9 @@ export async function handleConnectorCommand(params: {
|
|
|
189
190
|
|
|
190
191
|
if (command.name === 'status') {
|
|
191
192
|
const policy = resolveConnectorSessionPolicy(connector, msg, session)
|
|
192
|
-
const all =
|
|
193
|
-
const userCount = all.filter((message
|
|
194
|
-
const assistantCount = all.filter((message
|
|
193
|
+
const all = getMessages(session.id)
|
|
194
|
+
const userCount = all.filter((message) => message?.role === 'user').length
|
|
195
|
+
const assistantCount = all.filter((message) => message?.role === 'assistant').length
|
|
195
196
|
const toolsCount = getEnabledCapabilityIds(session).length
|
|
196
197
|
const statusText = [
|
|
197
198
|
`Status for ${connector.platform} / ${connector.name}:`,
|
|
@@ -232,7 +233,7 @@ export async function handleConnectorCommand(params: {
|
|
|
232
233
|
if (command.name === 'compact') {
|
|
233
234
|
const keepParsed = Number.parseInt(command.args, 10)
|
|
234
235
|
const keepLastN = Number.isFinite(keepParsed) ? Math.max(4, Math.min(50, keepParsed)) : 10
|
|
235
|
-
const history =
|
|
236
|
+
const history = getMessages(session.id)
|
|
236
237
|
if (history.length <= keepLastN) {
|
|
237
238
|
const text = `Nothing to compact. Current history has ${history.length} message(s), keepLastN=${keepLastN}.`
|
|
238
239
|
pushSessionMessage(session, 'user', inboundText)
|
|
@@ -249,7 +250,7 @@ export async function handleConnectorCommand(params: {
|
|
|
249
250
|
time: Date.now(),
|
|
250
251
|
kind: 'system' as const,
|
|
251
252
|
}
|
|
252
|
-
session.
|
|
253
|
+
replaceAllMessages(session.id, [summaryMessage, ...recentMessages])
|
|
253
254
|
session.lastActiveAt = Date.now()
|
|
254
255
|
const text = `Compacted ${oldMessages.length} message(s). Kept ${recentMessages.length} recent message(s) plus a summary.`
|
|
255
256
|
pushSessionMessage(session, 'assistant', text)
|