@swarmclawai/swarmclaw 1.7.0 → 1.7.2
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 +25 -9
- package/bin/swarmclaw.js +87 -0
- package/electron-dist/main.js +218 -0
- package/package.json +2 -2
- package/scripts/run-next-build.mjs +1 -1
- package/src/app/api/setup/check-provider/route.ts +5 -62
- package/src/app/api/setup/doctor/route.ts +19 -9
- package/src/app/home/page.tsx +19 -10
- package/src/cli/index.js +8 -2
- package/src/cli/index.ts +12 -3
- package/src/components/agents/inspector-panel.tsx +25 -3
- package/src/components/auth/setup-wizard/index.tsx +6 -2
- package/src/components/auth/setup-wizard/step-next.tsx +46 -39
- package/src/components/auth/setup-wizard/step-providers.tsx +113 -140
- package/src/components/auth/setup-wizard/types.ts +5 -2
- package/src/components/auth/setup-wizard/utils.test.ts +0 -19
- package/src/components/auth/setup-wizard/utils.ts +0 -69
- package/src/components/chat/chat-card.tsx +5 -0
- package/src/components/home/home-launchpad.tsx +123 -71
- package/src/components/layout/update-banner.tsx +43 -9
- package/src/lib/home-launchpad.test.ts +1 -31
- package/src/lib/home-launchpad.ts +0 -58
- package/src/lib/provider-sets.test.ts +19 -0
- package/src/lib/provider-sets.ts +8 -3
- package/src/lib/providers/cli-provider-metadata.test.ts +38 -0
- package/src/lib/providers/cli-provider-metadata.ts +208 -0
- package/src/lib/providers/cli-utils.test.ts +65 -1
- package/src/lib/providers/cli-utils.ts +26 -44
- package/src/lib/providers/codex-cli.ts +71 -75
- package/src/lib/providers/generic-cli.ts +2 -31
- package/src/lib/providers/index.ts +14 -44
- package/src/lib/server/chat-execution/chat-execution-session-sync.test.ts +189 -0
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +26 -19
- package/src/lib/server/cli-provider-readiness.test.ts +45 -0
- package/src/lib/server/cli-provider-readiness.ts +84 -0
- package/src/lib/server/provider-health.test.ts +6 -0
- package/src/lib/server/provider-health.ts +2 -2
- package/src/lib/setup-defaults.test.ts +8 -0
- package/src/lib/setup-defaults.ts +38 -178
- package/src/stores/slices/session-slice.test.ts +40 -2
- package/src/stores/slices/session-slice.ts +41 -1
- package/tsconfig.json +1 -0
|
@@ -3,33 +3,10 @@
|
|
|
3
3
|
* Isomorphic — no 'use client', no server imports.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
| 'opencode-web'
|
|
11
|
-
| 'gemini-cli'
|
|
12
|
-
| 'copilot-cli'
|
|
13
|
-
| 'droid-cli'
|
|
14
|
-
| 'cursor-cli'
|
|
15
|
-
| 'qwen-code-cli'
|
|
16
|
-
| 'goose'
|
|
17
|
-
| 'anthropic'
|
|
18
|
-
| 'openai'
|
|
19
|
-
| 'openrouter'
|
|
20
|
-
| 'google'
|
|
21
|
-
| 'deepseek'
|
|
22
|
-
| 'groq'
|
|
23
|
-
| 'together'
|
|
24
|
-
| 'mistral'
|
|
25
|
-
| 'xai'
|
|
26
|
-
| 'fireworks'
|
|
27
|
-
| 'nebius'
|
|
28
|
-
| 'deepinfra'
|
|
29
|
-
| 'ollama'
|
|
30
|
-
| 'openclaw'
|
|
31
|
-
| 'hermes'
|
|
32
|
-
| 'custom'
|
|
6
|
+
import { CLI_PROVIDER_METADATA, type CliProviderId, type CliProviderMetadata } from './providers/cli-provider-metadata.ts'
|
|
7
|
+
import type { ProviderType } from '../types/provider.ts'
|
|
8
|
+
|
|
9
|
+
export type SetupProvider = ProviderType | 'custom'
|
|
33
10
|
|
|
34
11
|
export interface SetupProviderOption {
|
|
35
12
|
id: SetupProvider
|
|
@@ -48,38 +25,27 @@ export interface SetupProviderOption {
|
|
|
48
25
|
icon: string
|
|
49
26
|
modelLibraryUrl?: string
|
|
50
27
|
cloudEndpoint?: string
|
|
28
|
+
category?: 'cli' | 'api' | 'gateway' | 'local' | 'custom'
|
|
51
29
|
}
|
|
52
30
|
|
|
31
|
+
const CLI_SETUP_PROVIDERS: SetupProviderOption[] = (CLI_PROVIDER_METADATA as readonly CliProviderMetadata[]).map((provider) => ({
|
|
32
|
+
id: provider.id,
|
|
33
|
+
name: provider.displayName,
|
|
34
|
+
description: provider.description,
|
|
35
|
+
requiresKey: false,
|
|
36
|
+
supportsEndpoint: false,
|
|
37
|
+
optionalKey: provider.optionalApiKey,
|
|
38
|
+
keyUrl: provider.keyUrl,
|
|
39
|
+
keyLabel: provider.keyLabel,
|
|
40
|
+
keyPlaceholder: provider.keyPlaceholder,
|
|
41
|
+
badge: provider.setupBadge,
|
|
42
|
+
icon: provider.icon,
|
|
43
|
+
modelLibraryUrl: provider.modelLibraryUrl,
|
|
44
|
+
category: 'cli',
|
|
45
|
+
}))
|
|
46
|
+
|
|
53
47
|
export const SETUP_PROVIDERS: SetupProviderOption[] = [
|
|
54
|
-
|
|
55
|
-
id: 'claude-cli',
|
|
56
|
-
name: 'Claude Code CLI',
|
|
57
|
-
description: 'Anthropic’s coding agent with native tools, strong edits, and first-class CLI workflows.',
|
|
58
|
-
requiresKey: false,
|
|
59
|
-
supportsEndpoint: false,
|
|
60
|
-
badge: 'CLI',
|
|
61
|
-
icon: 'C',
|
|
62
|
-
modelLibraryUrl: 'https://docs.anthropic.com/en/docs/about-claude/models',
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
id: 'codex-cli',
|
|
66
|
-
name: 'OpenAI Codex CLI',
|
|
67
|
-
description: 'OpenAI’s terminal coding agent with resume support and structured headless output.',
|
|
68
|
-
requiresKey: false,
|
|
69
|
-
supportsEndpoint: false,
|
|
70
|
-
badge: 'CLI',
|
|
71
|
-
icon: 'O',
|
|
72
|
-
modelLibraryUrl: 'https://platform.openai.com/docs/models',
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
id: 'opencode-cli',
|
|
76
|
-
name: 'OpenCode CLI',
|
|
77
|
-
description: 'A flexible coding CLI that can route across multiple model backends.',
|
|
78
|
-
requiresKey: false,
|
|
79
|
-
supportsEndpoint: false,
|
|
80
|
-
badge: 'CLI',
|
|
81
|
-
icon: 'O',
|
|
82
|
-
},
|
|
48
|
+
...CLI_SETUP_PROVIDERS,
|
|
83
49
|
{
|
|
84
50
|
id: 'opencode-web',
|
|
85
51
|
name: 'OpenCode Web',
|
|
@@ -92,66 +58,7 @@ export const SETUP_PROVIDERS: SetupProviderOption[] = [
|
|
|
92
58
|
keyPlaceholder: 'opencode:••••••• (or just the password)',
|
|
93
59
|
badge: 'HTTP',
|
|
94
60
|
icon: 'O',
|
|
95
|
-
|
|
96
|
-
{
|
|
97
|
-
id: 'gemini-cli',
|
|
98
|
-
name: 'Gemini CLI',
|
|
99
|
-
description: 'Google’s terminal coding agent with project-aware headless mode and resume support.',
|
|
100
|
-
requiresKey: false,
|
|
101
|
-
supportsEndpoint: false,
|
|
102
|
-
badge: 'CLI',
|
|
103
|
-
icon: 'G',
|
|
104
|
-
modelLibraryUrl: 'https://ai.google.dev/gemini-api/docs/models',
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
id: 'copilot-cli',
|
|
108
|
-
name: 'GitHub Copilot CLI',
|
|
109
|
-
description: 'GitHub’s multi-model terminal agent for coding and automation.',
|
|
110
|
-
requiresKey: false,
|
|
111
|
-
supportsEndpoint: false,
|
|
112
|
-
badge: 'CLI',
|
|
113
|
-
icon: 'P',
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
id: 'droid-cli',
|
|
117
|
-
name: 'Factory Droid CLI',
|
|
118
|
-
description: 'Factory.ai’s terminal coding agent with headless exec mode, session resume, and autonomy controls.',
|
|
119
|
-
requiresKey: false,
|
|
120
|
-
supportsEndpoint: false,
|
|
121
|
-
optionalKey: true,
|
|
122
|
-
keyUrl: 'https://app.factory.ai/settings/api-keys',
|
|
123
|
-
keyLabel: 'app.factory.ai',
|
|
124
|
-
keyPlaceholder: 'FACTORY_API_KEY (optional if signed in via `droid`)',
|
|
125
|
-
badge: 'CLI',
|
|
126
|
-
icon: 'F',
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
id: 'cursor-cli',
|
|
130
|
-
name: 'Cursor Agent CLI',
|
|
131
|
-
description: 'Cursor’s terminal agent with resume support, JSON output, and Cursor-native coding workflows.',
|
|
132
|
-
requiresKey: false,
|
|
133
|
-
supportsEndpoint: false,
|
|
134
|
-
badge: 'CLI',
|
|
135
|
-
icon: 'U',
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
id: 'qwen-code-cli',
|
|
139
|
-
name: 'Qwen Code CLI',
|
|
140
|
-
description: 'Qwen’s terminal coding agent with structured headless mode and multi-provider model config.',
|
|
141
|
-
requiresKey: false,
|
|
142
|
-
supportsEndpoint: false,
|
|
143
|
-
badge: 'CLI',
|
|
144
|
-
icon: 'Q',
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
id: 'goose',
|
|
148
|
-
name: 'Goose',
|
|
149
|
-
description: 'A runtime-managed terminal agent with extensions, session history, and ACP support.',
|
|
150
|
-
requiresKey: false,
|
|
151
|
-
supportsEndpoint: false,
|
|
152
|
-
optionalKey: true,
|
|
153
|
-
badge: 'Runtime',
|
|
154
|
-
icon: 'G',
|
|
61
|
+
category: 'cli',
|
|
155
62
|
},
|
|
156
63
|
{
|
|
157
64
|
id: 'openai',
|
|
@@ -190,6 +97,7 @@ export const SETUP_PROVIDERS: SetupProviderOption[] = [
|
|
|
190
97
|
optionalKey: true,
|
|
191
98
|
badge: 'First-Tier',
|
|
192
99
|
icon: 'C',
|
|
100
|
+
category: 'gateway',
|
|
193
101
|
},
|
|
194
102
|
{
|
|
195
103
|
id: 'hermes',
|
|
@@ -202,6 +110,7 @@ export const SETUP_PROVIDERS: SetupProviderOption[] = [
|
|
|
202
110
|
optionalKey: true,
|
|
203
111
|
badge: 'API Server',
|
|
204
112
|
icon: 'H',
|
|
113
|
+
category: 'gateway',
|
|
205
114
|
},
|
|
206
115
|
{
|
|
207
116
|
id: 'anthropic',
|
|
@@ -327,6 +236,7 @@ export const SETUP_PROVIDERS: SetupProviderOption[] = [
|
|
|
327
236
|
icon: 'L',
|
|
328
237
|
modelLibraryUrl: 'https://ollama.com/library',
|
|
329
238
|
cloudEndpoint: 'https://api.ollama.com',
|
|
239
|
+
category: 'local',
|
|
330
240
|
},
|
|
331
241
|
{
|
|
332
242
|
id: 'custom',
|
|
@@ -337,6 +247,7 @@ export const SETUP_PROVIDERS: SetupProviderOption[] = [
|
|
|
337
247
|
allowMultiple: true,
|
|
338
248
|
optionalKey: true,
|
|
339
249
|
icon: '+',
|
|
250
|
+
category: 'custom',
|
|
340
251
|
},
|
|
341
252
|
]
|
|
342
253
|
|
|
@@ -815,28 +726,19 @@ export interface DefaultAgentConfig {
|
|
|
815
726
|
tools: string[]
|
|
816
727
|
}
|
|
817
728
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
model: 'claude-sonnet-4-6',
|
|
824
|
-
tools: STARTER_AGENT_TOOLS,
|
|
825
|
-
},
|
|
826
|
-
'codex-cli': {
|
|
827
|
-
name: 'Codex CLI',
|
|
828
|
-
description: 'A helpful assistant powered by OpenAI Codex CLI.',
|
|
829
|
-
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
830
|
-
model: 'gpt-5.3-codex',
|
|
831
|
-
tools: STARTER_AGENT_TOOLS,
|
|
832
|
-
},
|
|
833
|
-
'opencode-cli': {
|
|
834
|
-
name: 'OpenCode',
|
|
835
|
-
description: 'A helpful assistant powered by OpenCode CLI.',
|
|
729
|
+
const CLI_DEFAULT_AGENTS = Object.fromEntries(CLI_PROVIDER_METADATA.map((provider) => [
|
|
730
|
+
provider.id,
|
|
731
|
+
{
|
|
732
|
+
name: provider.displayName.endsWith(' CLI') ? provider.displayName.slice(0, -4) : provider.displayName,
|
|
733
|
+
description: `A helpful assistant powered by ${provider.displayName}.`,
|
|
836
734
|
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
837
|
-
model:
|
|
735
|
+
model: provider.defaultModel,
|
|
838
736
|
tools: STARTER_AGENT_TOOLS,
|
|
839
737
|
},
|
|
738
|
+
])) as Record<CliProviderId, DefaultAgentConfig>
|
|
739
|
+
|
|
740
|
+
export const DEFAULT_AGENTS = {
|
|
741
|
+
...CLI_DEFAULT_AGENTS,
|
|
840
742
|
'opencode-web': {
|
|
841
743
|
name: 'OpenCode Web',
|
|
842
744
|
description: 'A helpful assistant powered by a remote OpenCode HTTP server.',
|
|
@@ -844,48 +746,6 @@ export const DEFAULT_AGENTS: Record<SetupProvider, DefaultAgentConfig> = {
|
|
|
844
746
|
model: 'anthropic/claude-sonnet-4-6',
|
|
845
747
|
tools: STARTER_AGENT_TOOLS,
|
|
846
748
|
},
|
|
847
|
-
'gemini-cli': {
|
|
848
|
-
name: 'Gemini CLI',
|
|
849
|
-
description: 'A helpful assistant powered by Gemini CLI.',
|
|
850
|
-
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
851
|
-
model: 'gemini-3.1-pro',
|
|
852
|
-
tools: STARTER_AGENT_TOOLS,
|
|
853
|
-
},
|
|
854
|
-
'copilot-cli': {
|
|
855
|
-
name: 'Copilot CLI',
|
|
856
|
-
description: 'A helpful assistant powered by GitHub Copilot CLI.',
|
|
857
|
-
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
858
|
-
model: 'claude-sonnet-4-6',
|
|
859
|
-
tools: STARTER_AGENT_TOOLS,
|
|
860
|
-
},
|
|
861
|
-
'droid-cli': {
|
|
862
|
-
name: 'Factory Droid',
|
|
863
|
-
description: 'A helpful assistant powered by Factory Droid CLI.',
|
|
864
|
-
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
865
|
-
model: 'default',
|
|
866
|
-
tools: STARTER_AGENT_TOOLS,
|
|
867
|
-
},
|
|
868
|
-
'cursor-cli': {
|
|
869
|
-
name: 'Cursor CLI',
|
|
870
|
-
description: 'A helpful assistant powered by Cursor Agent CLI.',
|
|
871
|
-
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
872
|
-
model: 'auto',
|
|
873
|
-
tools: STARTER_AGENT_TOOLS,
|
|
874
|
-
},
|
|
875
|
-
'qwen-code-cli': {
|
|
876
|
-
name: 'Qwen Code',
|
|
877
|
-
description: 'A helpful assistant powered by Qwen Code CLI.',
|
|
878
|
-
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
879
|
-
model: 'default',
|
|
880
|
-
tools: STARTER_AGENT_TOOLS,
|
|
881
|
-
},
|
|
882
|
-
goose: {
|
|
883
|
-
name: 'Goose',
|
|
884
|
-
description: 'A helpful assistant powered by Goose.',
|
|
885
|
-
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
886
|
-
model: 'default',
|
|
887
|
-
tools: STARTER_AGENT_TOOLS,
|
|
888
|
-
},
|
|
889
749
|
anthropic: {
|
|
890
750
|
name: 'Claude',
|
|
891
751
|
description: 'A helpful Claude-powered assistant.',
|
|
@@ -998,7 +858,7 @@ export const DEFAULT_AGENTS: Record<SetupProvider, DefaultAgentConfig> = {
|
|
|
998
858
|
model: '',
|
|
999
859
|
tools: STARTER_AGENT_TOOLS,
|
|
1000
860
|
},
|
|
1001
|
-
}
|
|
861
|
+
} satisfies Record<SetupProvider, DefaultAgentConfig>
|
|
1002
862
|
|
|
1003
863
|
export function getDefaultModelForProvider(provider: SetupProvider): string {
|
|
1004
864
|
return DEFAULT_AGENTS[provider].model
|
|
@@ -32,11 +32,49 @@ test('selectActiveSessionId prefers override when present', () => {
|
|
|
32
32
|
assert.equal(selectActiveSessionId(state), 'task-1')
|
|
33
33
|
})
|
|
34
34
|
|
|
35
|
-
test('selectActiveSessionId
|
|
35
|
+
test('selectActiveSessionId chooses most recently active session for current agent', () => {
|
|
36
36
|
const state = makeState({
|
|
37
37
|
currentAgentId: 'agent-1',
|
|
38
38
|
agents: { 'agent-1': makeAgent('agent-1', 'thread-1') },
|
|
39
|
-
sessions: {
|
|
39
|
+
sessions: {
|
|
40
|
+
'thread-1': { ...makeSession('thread-1'), agentId: 'agent-1', lastActiveAt: 100 } as unknown as Session,
|
|
41
|
+
'old-1': { ...makeSession('old-1'), agentId: 'agent-1', lastActiveAt: 90 } as unknown as Session,
|
|
42
|
+
'latest-1': { ...makeSession('latest-1'), agentId: 'agent-1', lastActiveAt: 200, messageCount: 1 } as unknown as Session,
|
|
43
|
+
'other-agent': { ...makeSession('other-agent'), agentId: 'agent-2', lastActiveAt: 999 } as unknown as Session,
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
assert.equal(selectActiveSessionId(state), 'latest-1')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('selectActiveSessionId prefers most recent session with content over newer empty thread session', () => {
|
|
50
|
+
const state = makeState({
|
|
51
|
+
currentAgentId: 'agent-1',
|
|
52
|
+
agents: { 'agent-1': makeAgent('agent-1', 'thread-1') },
|
|
53
|
+
sessions: {
|
|
54
|
+
'thread-1': { ...makeSession('thread-1'), agentId: 'agent-1', lastActiveAt: 300 } as unknown as Session,
|
|
55
|
+
'work-1': { ...makeSession('work-1'), agentId: 'agent-1', lastActiveAt: 200, messageCount: 2 } as unknown as Session,
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
assert.equal(selectActiveSessionId(state), 'work-1')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('selectActiveSessionId falls back to thread session when agent has no loaded sessions', () => {
|
|
62
|
+
const state = makeState({
|
|
63
|
+
currentAgentId: 'agent-1',
|
|
64
|
+
agents: { 'agent-1': makeAgent('agent-1', 'thread-1') },
|
|
65
|
+
sessions: { 'unrelated': { ...makeSession('unrelated'), agentId: 'agent-2' } as unknown as Session },
|
|
66
|
+
})
|
|
67
|
+
assert.equal(selectActiveSessionId(state), 'thread-1')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('selectActiveSessionId falls back to thread session when all loaded sessions are empty', () => {
|
|
71
|
+
const state = makeState({
|
|
72
|
+
currentAgentId: 'agent-1',
|
|
73
|
+
agents: { 'agent-1': makeAgent('agent-1', 'thread-1') },
|
|
74
|
+
sessions: {
|
|
75
|
+
'thread-1': { ...makeSession('thread-1'), agentId: 'agent-1', lastActiveAt: 120 } as unknown as Session,
|
|
76
|
+
'empty-newer': { ...makeSession('empty-newer'), agentId: 'agent-1', lastActiveAt: 220 } as unknown as Session,
|
|
77
|
+
},
|
|
40
78
|
})
|
|
41
79
|
assert.equal(selectActiveSessionId(state), 'thread-1')
|
|
42
80
|
})
|
|
@@ -8,6 +8,46 @@ import { createLoader, createInflightDeduplicator } from '../store-utils'
|
|
|
8
8
|
|
|
9
9
|
const sessionRefreshDedup = createInflightDeduplicator('sessionSlice_inflightRefreshes')
|
|
10
10
|
|
|
11
|
+
function getSessionSortScore(session: Session): number {
|
|
12
|
+
return session.lastAssistantAt
|
|
13
|
+
|| session.lastActiveAt
|
|
14
|
+
|| session.updatedAt
|
|
15
|
+
|| session.createdAt
|
|
16
|
+
|| 0
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function hasSessionContent(session: Session): boolean {
|
|
20
|
+
if (typeof session.messageCount === 'number' && Number.isFinite(session.messageCount) && session.messageCount > 0) {
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
if (session.lastMessageSummary) return true
|
|
24
|
+
return Array.isArray(session.messages) && session.messages.length > 0
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getLatestAgentSessionId(s: AppState, agentId: string, threadSessionId?: string | null): string | null {
|
|
28
|
+
let bestAnyId: string | null = null
|
|
29
|
+
let bestAnyScore = Number.NEGATIVE_INFINITY
|
|
30
|
+
let bestWithContentId: string | null = null
|
|
31
|
+
let bestWithContentScore = Number.NEGATIVE_INFINITY
|
|
32
|
+
|
|
33
|
+
for (const [sessionId, session] of Object.entries(s.sessions)) {
|
|
34
|
+
if (session.agentId !== agentId) continue
|
|
35
|
+
const score = getSessionSortScore(session)
|
|
36
|
+
if (score > bestAnyScore) {
|
|
37
|
+
bestAnyScore = score
|
|
38
|
+
bestAnyId = sessionId
|
|
39
|
+
}
|
|
40
|
+
if (hasSessionContent(session) && score > bestWithContentScore) {
|
|
41
|
+
bestWithContentScore = score
|
|
42
|
+
bestWithContentId = sessionId
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (bestWithContentId) return bestWithContentId
|
|
47
|
+
if (threadSessionId && s.sessions[threadSessionId]?.agentId === agentId) return threadSessionId
|
|
48
|
+
return bestAnyId
|
|
49
|
+
}
|
|
50
|
+
|
|
11
51
|
/** Derive the active session ID from the current agent — no stored `currentSessionId`. */
|
|
12
52
|
export function selectActiveSessionId(s: AppState): string | null {
|
|
13
53
|
if (s.activeSessionIdOverride && s.sessions[s.activeSessionIdOverride]) {
|
|
@@ -15,7 +55,7 @@ export function selectActiveSessionId(s: AppState): string | null {
|
|
|
15
55
|
}
|
|
16
56
|
if (!s.currentAgentId) return null
|
|
17
57
|
const agent = s.agents[s.currentAgentId]
|
|
18
|
-
return agent?.threadSessionId
|
|
58
|
+
return getLatestAgentSessionId(s, s.currentAgentId, agent?.threadSessionId) || agent?.threadSessionId || null
|
|
19
59
|
}
|
|
20
60
|
|
|
21
61
|
export interface SessionSlice {
|