@swarmclawai/swarmclaw 1.2.1 → 1.2.3
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 +16 -85
- package/bin/server-cmd.js +64 -1
- package/package.json +2 -2
- package/skills/coding-agent/SKILL.md +111 -0
- package/skills/github/SKILL.md +140 -0
- package/skills/nano-banana-pro/SKILL.md +62 -0
- package/skills/nano-banana-pro/scripts/generate_image.py +235 -0
- package/skills/nano-pdf/SKILL.md +53 -0
- package/skills/openai-image-gen/SKILL.md +78 -0
- package/skills/openai-image-gen/scripts/gen.py +328 -0
- package/skills/resourceful-problem-solving/SKILL.md +49 -0
- package/skills/skill-creator/SKILL.md +147 -0
- package/skills/skill-creator/scripts/init_skill.py +378 -0
- package/skills/skill-creator/scripts/quick_validate.py +159 -0
- package/skills/summarize/SKILL.md +77 -0
- package/src/app/api/auth/route.ts +20 -5
- package/src/app/api/chats/[id]/devserver/route.ts +13 -19
- package/src/app/api/chats/[id]/messages/route.ts +13 -15
- package/src/app/api/chats/[id]/route.ts +9 -10
- package/src/app/api/chats/[id]/stop/route.ts +5 -7
- package/src/app/api/chats/messages-route.test.ts +8 -6
- package/src/app/api/chats/route.ts +9 -10
- package/src/app/api/ip/route.ts +2 -2
- package/src/app/api/preview-server/route.ts +1 -1
- package/src/app/api/projects/[id]/route.ts +7 -46
- package/src/cli/server-cmd.test.js +74 -0
- package/src/components/chat/chat-area.tsx +45 -23
- package/src/components/chat/message-bubble.test.ts +35 -0
- package/src/components/chat/message-bubble.tsx +19 -9
- package/src/components/chat/message-list.tsx +37 -3
- package/src/components/input/chat-input.tsx +34 -14
- package/src/components/openclaw/openclaw-deploy-panel.tsx +4 -0
- package/src/instrumentation.ts +1 -1
- package/src/lib/chat/assistant-render-id.ts +3 -0
- package/src/lib/chat/chat-streaming-state.test.ts +42 -3
- package/src/lib/chat/chat-streaming-state.ts +20 -8
- package/src/lib/chat/queued-message-queue.test.ts +23 -1
- package/src/lib/chat/queued-message-queue.ts +11 -2
- package/src/lib/providers/cli-utils.test.ts +124 -0
- package/src/lib/server/activity/activity-log.ts +21 -0
- package/src/lib/server/agents/agent-availability.test.ts +10 -5
- package/src/lib/server/agents/agent-cascade.ts +79 -59
- package/src/lib/server/agents/agent-registry.ts +3 -1
- package/src/lib/server/agents/agent-repository.ts +90 -0
- package/src/lib/server/agents/delegation-job-repository.ts +53 -0
- package/src/lib/server/agents/delegation-jobs.ts +11 -4
- package/src/lib/server/agents/guardian-checkpoint-repository.ts +35 -0
- package/src/lib/server/agents/guardian.ts +2 -2
- package/src/lib/server/agents/main-agent-loop.ts +10 -3
- package/src/lib/server/agents/main-loop-state-repository.ts +38 -0
- package/src/lib/server/agents/subagent-runtime.ts +9 -6
- package/src/lib/server/agents/subagent-swarm.ts +3 -2
- package/src/lib/server/agents/task-session.ts +3 -4
- package/src/lib/server/approvals/approval-repository.ts +30 -0
- package/src/lib/server/autonomy/supervisor-incident-repository.ts +42 -0
- package/src/lib/server/chat-execution/chat-execution-types.ts +38 -0
- package/src/lib/server/chat-execution/chat-execution-utils.ts +1 -1
- package/src/lib/server/chat-execution/chat-execution.ts +84 -1926
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +620 -0
- package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +221 -0
- package/src/lib/server/chat-execution/chat-turn-preflight.ts +133 -0
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +817 -0
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +296 -0
- package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +5 -5
- package/src/lib/server/chat-execution/message-classifier.test.ts +329 -0
- package/src/lib/server/chat-execution/post-stream-finalization.ts +1 -1
- package/src/lib/server/chat-execution/prompt-builder.ts +11 -0
- package/src/lib/server/chat-execution/prompt-sections.ts +5 -6
- package/src/lib/server/chat-execution/situational-awareness.ts +12 -7
- package/src/lib/server/chat-execution/stream-agent-chat.ts +16 -13
- package/src/lib/server/chatrooms/chatroom-repository.ts +32 -0
- package/src/lib/server/connectors/connector-repository.ts +58 -0
- package/src/lib/server/connectors/runtime-state.test.ts +117 -0
- package/src/lib/server/credentials/credential-repository.ts +7 -0
- package/src/lib/server/gateways/gateway-profile-repository.ts +4 -0
- package/src/lib/server/memory/memory-abstract.test.ts +59 -0
- package/src/lib/server/missions/mission-repository.ts +74 -0
- package/src/lib/server/missions/mission-service/actions.ts +6 -0
- package/src/lib/server/missions/mission-service/bindings.ts +9 -0
- package/src/lib/server/missions/mission-service/context.ts +4 -0
- package/src/lib/server/missions/mission-service/core.ts +2269 -0
- package/src/lib/server/missions/mission-service/queries.ts +12 -0
- package/src/lib/server/missions/mission-service/recovery.ts +5 -0
- package/src/lib/server/missions/mission-service/ticks.ts +9 -0
- package/src/lib/server/missions/mission-service.test.ts +9 -2
- package/src/lib/server/missions/mission-service.ts +6 -2266
- package/src/lib/server/openclaw/deploy.test.ts +42 -3
- package/src/lib/server/openclaw/deploy.ts +26 -12
- package/src/lib/server/persistence/repository-utils.ts +154 -0
- package/src/lib/server/persistence/storage-context.ts +51 -0
- package/src/lib/server/persistence/transaction.ts +1 -0
- package/src/lib/server/projects/project-repository.ts +36 -0
- package/src/lib/server/projects/project-service.ts +79 -0
- package/src/lib/server/protocols/protocol-normalization.test.ts +6 -4
- package/src/lib/server/runtime/alert-dispatch.ts +1 -1
- package/src/lib/server/runtime/daemon-policy.ts +1 -1
- package/src/lib/server/runtime/daemon-state/core.ts +1570 -0
- package/src/lib/server/runtime/daemon-state/health.ts +6 -0
- package/src/lib/server/runtime/daemon-state/policy.ts +7 -0
- package/src/lib/server/runtime/daemon-state/supervisor.ts +6 -0
- package/src/lib/server/runtime/daemon-state.test.ts +48 -0
- package/src/lib/server/runtime/daemon-state.ts +3 -1470
- package/src/lib/server/runtime/estop-repository.ts +4 -0
- package/src/lib/server/runtime/estop.ts +3 -1
- package/src/lib/server/runtime/heartbeat-service.test.ts +2 -2
- package/src/lib/server/runtime/heartbeat-service.ts +55 -34
- package/src/lib/server/runtime/heartbeat-wake.ts +6 -4
- package/src/lib/server/runtime/idle-window.ts +2 -2
- package/src/lib/server/runtime/network.ts +11 -0
- package/src/lib/server/runtime/orchestrator-events.ts +2 -2
- package/src/lib/server/runtime/queue/claims.ts +4 -0
- package/src/lib/server/runtime/queue/core.ts +2079 -0
- package/src/lib/server/runtime/queue/execution.ts +7 -0
- package/src/lib/server/runtime/queue/followups.ts +4 -0
- package/src/lib/server/runtime/queue/queries.ts +12 -0
- package/src/lib/server/runtime/queue/recovery.ts +7 -0
- package/src/lib/server/runtime/queue-recovery.test.ts +48 -13
- package/src/lib/server/runtime/queue-repository.ts +17 -0
- package/src/lib/server/runtime/queue.ts +5 -2061
- package/src/lib/server/runtime/run-ledger.ts +6 -5
- package/src/lib/server/runtime/run-repository.ts +73 -0
- package/src/lib/server/runtime/runtime-lock-repository.ts +8 -0
- package/src/lib/server/runtime/runtime-settings.ts +1 -1
- package/src/lib/server/runtime/runtime-state.ts +99 -0
- package/src/lib/server/runtime/scheduler.ts +4 -2
- package/src/lib/server/runtime/session-run-manager/cancellation.ts +157 -0
- package/src/lib/server/runtime/session-run-manager/drain.ts +246 -0
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +287 -0
- package/src/lib/server/runtime/session-run-manager/queries.ts +117 -0
- package/src/lib/server/runtime/session-run-manager/recovery.ts +238 -0
- package/src/lib/server/runtime/session-run-manager/state.ts +441 -0
- package/src/lib/server/runtime/session-run-manager/types.ts +74 -0
- package/src/lib/server/runtime/session-run-manager.ts +72 -1377
- package/src/lib/server/runtime/watch-job-repository.ts +35 -0
- package/src/lib/server/runtime/watch-jobs.ts +3 -1
- package/src/lib/server/schedules/schedule-repository.ts +42 -0
- package/src/lib/server/sessions/session-repository.ts +85 -0
- package/src/lib/server/settings/settings-repository.ts +25 -0
- package/src/lib/server/skills/skill-discovery.test.ts +2 -2
- package/src/lib/server/skills/skill-discovery.ts +2 -2
- package/src/lib/server/skills/skill-repository.ts +14 -0
- package/src/lib/server/storage.ts +13 -24
- package/src/lib/server/tasks/task-repository.ts +54 -0
- package/src/lib/server/usage/usage-repository.ts +30 -0
- package/src/lib/server/webhooks/webhook-repository.ts +10 -0
- package/src/lib/strip-internal-metadata.test.ts +42 -41
- package/src/stores/use-chat-store.test.ts +54 -0
- package/src/stores/use-chat-store.ts +21 -5
- /package/{bundled-skills → skills}/google-workspace/SKILL.md +0 -0
|
@@ -1,65 +1,26 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { ensureProjectWorkspace, normalizeProjectPatchInput } from '@/lib/server/project-utils'
|
|
5
|
-
import { notify } from '@/lib/server/ws-hub'
|
|
2
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
3
|
+
import { deleteProjectAndDetachReferences, getProject, updateProject } from '@/lib/server/projects/project-service'
|
|
6
4
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
7
5
|
|
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
-
const ops: CollectionOps<any> = { load: loadProjects, save: saveProjects, deleteFn: deleteProject, topic: 'projects' }
|
|
10
|
-
|
|
11
6
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
12
7
|
const { id } = await params
|
|
13
|
-
const
|
|
14
|
-
if (!
|
|
15
|
-
return NextResponse.json(
|
|
8
|
+
const project = getProject(id)
|
|
9
|
+
if (!project) return notFound()
|
|
10
|
+
return NextResponse.json(project)
|
|
16
11
|
}
|
|
17
12
|
|
|
18
13
|
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
19
14
|
const { id } = await params
|
|
20
15
|
const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
|
|
21
16
|
if (error) return error
|
|
22
|
-
const result =
|
|
23
|
-
const patch = normalizeProjectPatchInput(body && typeof body === 'object' ? body as Record<string, unknown> : {})
|
|
24
|
-
Object.assign(project, patch, { updatedAt: Date.now() })
|
|
25
|
-
delete (project as Record<string, unknown>).id
|
|
26
|
-
project.id = id
|
|
27
|
-
return project
|
|
28
|
-
})
|
|
17
|
+
const result = updateProject(id, body && typeof body === 'object' ? body : {})
|
|
29
18
|
if (!result) return notFound()
|
|
30
|
-
ensureProjectWorkspace(id, result.name)
|
|
31
19
|
return NextResponse.json(result)
|
|
32
20
|
}
|
|
33
21
|
|
|
34
22
|
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
35
23
|
const { id } = await params
|
|
36
|
-
if (!
|
|
37
|
-
|
|
38
|
-
// Clear projectId from referencing entities
|
|
39
|
-
const clearProjectId = (load: () => Record<string, Record<string, unknown>>, save: (d: Record<string, Record<string, unknown>>) => void, topic: string) => {
|
|
40
|
-
const items = load()
|
|
41
|
-
let changed = false
|
|
42
|
-
for (const item of Object.values(items)) {
|
|
43
|
-
if (item.projectId === id) {
|
|
44
|
-
item.projectId = undefined
|
|
45
|
-
changed = true
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
if (changed) {
|
|
49
|
-
save(items)
|
|
50
|
-
notify(topic)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
clearProjectId(
|
|
55
|
-
loadAgents as unknown as () => Record<string, Record<string, unknown>>,
|
|
56
|
-
saveAgents as unknown as (data: Record<string, Record<string, unknown>>) => void,
|
|
57
|
-
'agents',
|
|
58
|
-
)
|
|
59
|
-
clearProjectId(loadTasks as unknown as () => Record<string, Record<string, unknown>>, saveTasks as unknown as (data: Record<string, Record<string, unknown>>) => void, 'tasks')
|
|
60
|
-
clearProjectId(loadSchedules as unknown as () => Record<string, Record<string, unknown>>, saveSchedules as unknown as (data: Record<string, Record<string, unknown>>) => void, 'schedules')
|
|
61
|
-
clearProjectId(loadSkills as unknown as () => Record<string, Record<string, unknown>>, saveSkills as unknown as (data: Record<string, Record<string, unknown>>) => void, 'skills')
|
|
62
|
-
clearProjectId(loadSecrets as unknown as () => Record<string, Record<string, unknown>>, saveSecrets as unknown as (data: Record<string, Record<string, unknown>>) => void, 'secrets')
|
|
63
|
-
|
|
24
|
+
if (!deleteProjectAndDetachReferences(id)) return notFound()
|
|
64
25
|
return NextResponse.json({ ok: true })
|
|
65
26
|
}
|
|
@@ -136,6 +136,80 @@ test('prepareBuildWorkspace copies the package tree and links node_modules outsi
|
|
|
136
136
|
fs.rmSync(externalNodeModules, { recursive: true, force: true })
|
|
137
137
|
})
|
|
138
138
|
|
|
139
|
+
test('syncStandaloneRuntimeAssets copies .next/static and public into a direct standalone runtime', () => {
|
|
140
|
+
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-server-home-'))
|
|
141
|
+
const pkgRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-server-pkg-'))
|
|
142
|
+
const serverCmd = loadServerCmdForHome(homeDir)
|
|
143
|
+
const runtimeDir = path.join(pkgRoot, '.next', 'standalone')
|
|
144
|
+
|
|
145
|
+
fs.mkdirSync(path.join(pkgRoot, '.next', 'static', 'chunks'), { recursive: true })
|
|
146
|
+
fs.mkdirSync(path.join(pkgRoot, 'public', 'branding'), { recursive: true })
|
|
147
|
+
fs.writeFileSync(path.join(pkgRoot, '.next', 'static', 'chunks', 'app.js'), 'chunk\n', 'utf8')
|
|
148
|
+
fs.writeFileSync(path.join(pkgRoot, 'public', 'branding', 'logo.svg'), '<svg />\n', 'utf8')
|
|
149
|
+
|
|
150
|
+
const result = serverCmd.syncStandaloneRuntimeAssets({
|
|
151
|
+
sourceRoot: pkgRoot,
|
|
152
|
+
runtimeDir,
|
|
153
|
+
force: true,
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
assert.deepEqual(result, { staticCopied: true, publicCopied: true })
|
|
157
|
+
assert.equal(fs.readFileSync(path.join(runtimeDir, '.next', 'static', 'chunks', 'app.js'), 'utf8'), 'chunk\n')
|
|
158
|
+
assert.equal(fs.readFileSync(path.join(runtimeDir, 'public', 'branding', 'logo.svg'), 'utf8'), '<svg />\n')
|
|
159
|
+
|
|
160
|
+
fs.rmSync(homeDir, { recursive: true, force: true })
|
|
161
|
+
fs.rmSync(pkgRoot, { recursive: true, force: true })
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('syncStandaloneRuntimeAssets targets the resolved nested runtime directory', () => {
|
|
165
|
+
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-server-home-'))
|
|
166
|
+
const pkgRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-server-pkg-'))
|
|
167
|
+
const serverCmd = loadServerCmdForHome(homeDir)
|
|
168
|
+
const serverJs = path.join(pkgRoot, '.next', 'standalone', 'Users', 'wayde', 'Dev', 'swarmclaw', 'server.js')
|
|
169
|
+
const runtimeDir = serverCmd.resolveStandaloneRuntimeDir(serverJs)
|
|
170
|
+
|
|
171
|
+
fs.mkdirSync(path.dirname(serverJs), { recursive: true })
|
|
172
|
+
fs.writeFileSync(serverJs, 'console.log("ok")\n', 'utf8')
|
|
173
|
+
fs.mkdirSync(path.join(pkgRoot, '.next', 'static', 'css'), { recursive: true })
|
|
174
|
+
fs.writeFileSync(path.join(pkgRoot, '.next', 'static', 'css', 'app.css'), 'body{}\n', 'utf8')
|
|
175
|
+
|
|
176
|
+
const result = serverCmd.syncStandaloneRuntimeAssets({
|
|
177
|
+
sourceRoot: pkgRoot,
|
|
178
|
+
runtimeDir,
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
assert.deepEqual(result, { staticCopied: true, publicCopied: false })
|
|
182
|
+
assert.equal(fs.readFileSync(path.join(runtimeDir, '.next', 'static', 'css', 'app.css'), 'utf8'), 'body{}\n')
|
|
183
|
+
assert.equal(fs.existsSync(path.join(runtimeDir, 'public')), false)
|
|
184
|
+
|
|
185
|
+
fs.rmSync(homeDir, { recursive: true, force: true })
|
|
186
|
+
fs.rmSync(pkgRoot, { recursive: true, force: true })
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('syncStandaloneRuntimeAssets repairs missing assets without overwriting an existing target by default', () => {
|
|
190
|
+
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-server-home-'))
|
|
191
|
+
const pkgRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-server-pkg-'))
|
|
192
|
+
const serverCmd = loadServerCmdForHome(homeDir)
|
|
193
|
+
const runtimeDir = path.join(pkgRoot, '.next', 'standalone')
|
|
194
|
+
|
|
195
|
+
fs.mkdirSync(path.join(pkgRoot, '.next', 'static', 'chunks'), { recursive: true })
|
|
196
|
+
fs.writeFileSync(path.join(pkgRoot, '.next', 'static', 'chunks', 'main.js'), 'fresh\n', 'utf8')
|
|
197
|
+
fs.mkdirSync(path.join(runtimeDir, 'public'), { recursive: true })
|
|
198
|
+
fs.writeFileSync(path.join(runtimeDir, 'public', 'keep.txt'), 'keep\n', 'utf8')
|
|
199
|
+
|
|
200
|
+
const result = serverCmd.syncStandaloneRuntimeAssets({
|
|
201
|
+
sourceRoot: pkgRoot,
|
|
202
|
+
runtimeDir,
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
assert.deepEqual(result, { staticCopied: true, publicCopied: false })
|
|
206
|
+
assert.equal(fs.readFileSync(path.join(runtimeDir, '.next', 'static', 'chunks', 'main.js'), 'utf8'), 'fresh\n')
|
|
207
|
+
assert.equal(fs.readFileSync(path.join(runtimeDir, 'public', 'keep.txt'), 'utf8'), 'keep\n')
|
|
208
|
+
|
|
209
|
+
fs.rmSync(homeDir, { recursive: true, force: true })
|
|
210
|
+
fs.rmSync(pkgRoot, { recursive: true, force: true })
|
|
211
|
+
})
|
|
212
|
+
|
|
139
213
|
test('resolveReadyCheckHost maps wildcard bind hosts to loopback', () => {
|
|
140
214
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-server-home-'))
|
|
141
215
|
const serverCmd = loadServerCmdForHome(homeDir)
|
|
@@ -27,6 +27,7 @@ import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
|
27
27
|
import { speak } from '@/lib/tts'
|
|
28
28
|
import { api } from '@/lib/app/api-client'
|
|
29
29
|
import { messagesDiffer } from '@/lib/chat/chat-streaming-state'
|
|
30
|
+
import { createAssistantRenderId } from '@/lib/chat/assistant-render-id'
|
|
30
31
|
import { getSessionLastMessage } from '@/lib/chat/session-summary'
|
|
31
32
|
import { getEnabledCapabilityIds, getEnabledToolIds } from '@/lib/capability-selection'
|
|
32
33
|
|
|
@@ -71,6 +72,36 @@ export function ChatArea() {
|
|
|
71
72
|
const setPreviewContent = useChatStore((s) => s.setPreviewContent)
|
|
72
73
|
const isDesktop = useMediaQuery('(min-width: 768px)')
|
|
73
74
|
|
|
75
|
+
const markSessionLocallyIdle = useCallback((targetSessionId: string) => {
|
|
76
|
+
const appState = useAppStore.getState()
|
|
77
|
+
const existing = appState.sessions[targetSessionId]
|
|
78
|
+
if (!existing) return
|
|
79
|
+
appState.updateSessionInStore({
|
|
80
|
+
...existing,
|
|
81
|
+
active: false,
|
|
82
|
+
currentRunId: null,
|
|
83
|
+
})
|
|
84
|
+
}, [])
|
|
85
|
+
|
|
86
|
+
const startServerStreamingPlaceholder = useCallback((targetSessionId: string, phase: 'queued' | 'thinking' | 'connecting' = 'thinking') => {
|
|
87
|
+
useChatStore.setState((state) => {
|
|
88
|
+
const sameServerStream = state.streaming
|
|
89
|
+
&& state.streamSource === 'server'
|
|
90
|
+
&& state.streamingSessionId === targetSessionId
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
streaming: true,
|
|
94
|
+
streamingSessionId: targetSessionId,
|
|
95
|
+
streamSource: 'server',
|
|
96
|
+
streamPhase: sameServerStream ? state.streamPhase : phase,
|
|
97
|
+
streamText: '',
|
|
98
|
+
displayText: '',
|
|
99
|
+
assistantRenderId: sameServerStream && state.assistantRenderId ? state.assistantRenderId : createAssistantRenderId(),
|
|
100
|
+
thinkingStartTime: sameServerStream && state.thinkingStartTime > 0 ? state.thinkingStartTime : Date.now(),
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
}, [])
|
|
104
|
+
|
|
74
105
|
const currentAgent = useAppStore((s) => {
|
|
75
106
|
const agentId = session?.agentId
|
|
76
107
|
return agentId ? s.agents[agentId] ?? null : null
|
|
@@ -178,14 +209,12 @@ export function ChatArea() {
|
|
|
178
209
|
})
|
|
179
210
|
|
|
180
211
|
const sessionAtLoad = useAppStore.getState().sessions[requestedSessionId]
|
|
181
|
-
if (sessionAtLoad?.active)
|
|
182
|
-
useChatStore.setState({ streaming: true, streamingSessionId: requestedSessionId, streamSource: 'server', streamText: '' })
|
|
183
|
-
}
|
|
212
|
+
if (sessionAtLoad?.active) startServerStreamingPlaceholder(requestedSessionId)
|
|
184
213
|
|
|
185
214
|
return () => {
|
|
186
215
|
cancelled = true
|
|
187
216
|
}
|
|
188
|
-
}, [loadQueuedMessages, refreshSession, sessionId, setDevServer, setMessages])
|
|
217
|
+
}, [loadQueuedMessages, refreshSession, sessionId, setDevServer, setMessages, startServerStreamingPlaceholder])
|
|
189
218
|
|
|
190
219
|
useEffect(() => {
|
|
191
220
|
if (!sessionId || messagesLoading) return
|
|
@@ -195,9 +224,7 @@ export function ChatArea() {
|
|
|
195
224
|
void refreshSession(requestedSessionId).then(() => {
|
|
196
225
|
if (cancelled || selectActiveSessionId(useAppStore.getState()) !== requestedSessionId) return
|
|
197
226
|
const refreshed = useAppStore.getState().sessions[requestedSessionId]
|
|
198
|
-
if (refreshed?.active)
|
|
199
|
-
useChatStore.setState({ streaming: true, streamingSessionId: requestedSessionId, streamSource: 'server', streamText: '' })
|
|
200
|
-
}
|
|
227
|
+
if (refreshed?.active) startServerStreamingPlaceholder(requestedSessionId)
|
|
201
228
|
}).catch((err) => console.error('Failed to refresh session:', err))
|
|
202
229
|
|
|
203
230
|
void devServer(requestedSessionId, 'status').then((r) => {
|
|
@@ -213,7 +240,7 @@ export function ChatArea() {
|
|
|
213
240
|
cancelled = true
|
|
214
241
|
window.clearTimeout(timer)
|
|
215
242
|
}
|
|
216
|
-
}, [messagesLoading, refreshSession, sessionId, setDevServer])
|
|
243
|
+
}, [messagesLoading, refreshSession, sessionId, setDevServer, startServerStreamingPlaceholder])
|
|
217
244
|
|
|
218
245
|
useEffect(() => {
|
|
219
246
|
if (!sessionId || messagesLoading) return
|
|
@@ -292,19 +319,12 @@ export function ChatArea() {
|
|
|
292
319
|
&& !chatState.streaming
|
|
293
320
|
&& chatState.streamingSessionId !== sessionId
|
|
294
321
|
) {
|
|
295
|
-
|
|
296
|
-
streaming: true,
|
|
297
|
-
streamingSessionId: sessionId,
|
|
298
|
-
streamSource: 'server',
|
|
299
|
-
streamPhase: 'thinking',
|
|
300
|
-
streamText: '',
|
|
301
|
-
thinkingStartTime: Date.now(),
|
|
302
|
-
})
|
|
322
|
+
startServerStreamingPlaceholder(sessionId)
|
|
303
323
|
}
|
|
304
324
|
} catch (err) {
|
|
305
325
|
console.error('Failed to refresh queue:', err)
|
|
306
326
|
}
|
|
307
|
-
}, [loadQueuedMessages, sessionId])
|
|
327
|
+
}, [loadQueuedMessages, sessionId, startServerStreamingPlaceholder])
|
|
308
328
|
|
|
309
329
|
// Subscribe to WS messages for this session — always subscribe when session exists,
|
|
310
330
|
// only enable fallback polling when actively needed
|
|
@@ -316,7 +336,7 @@ export function ChatArea() {
|
|
|
316
336
|
useWs(
|
|
317
337
|
sessionId ? 'runs' : '',
|
|
318
338
|
refreshQueue,
|
|
319
|
-
sessionId && (isServerActive || queuedCount > 0) ?
|
|
339
|
+
sessionId && (isServerActive || queuedCount > 0) ? 2_500 : undefined,
|
|
320
340
|
)
|
|
321
341
|
|
|
322
342
|
// Listen for stream-end signal from the server — clears streaming state
|
|
@@ -325,11 +345,12 @@ export function ChatArea() {
|
|
|
325
345
|
if (!sessionId) return
|
|
326
346
|
const state = useChatStore.getState()
|
|
327
347
|
if (state.streamSource === 'server' && state.streamingSessionId === sessionId) {
|
|
328
|
-
|
|
348
|
+
markSessionLocallyIdle(sessionId)
|
|
349
|
+
useChatStore.setState({ streaming: false, streamingSessionId: null, streamSource: null, streamText: '', displayText: '', assistantRenderId: null, streamPhase: 'thinking', streamToolName: '', thinkingText: '', thinkingStartTime: 0 })
|
|
329
350
|
void refreshMessages()
|
|
330
351
|
void refreshSession(sessionId)
|
|
331
352
|
}
|
|
332
|
-
}, [sessionId, refreshMessages, refreshSession])
|
|
353
|
+
}, [markSessionLocallyIdle, sessionId, refreshMessages, refreshSession])
|
|
333
354
|
useWs(sessionId ? `stream-end:${sessionId}` : '', handleStreamEnd)
|
|
334
355
|
|
|
335
356
|
// Keep the local typing indicator aligned with the server's active state
|
|
@@ -338,7 +359,7 @@ export function ChatArea() {
|
|
|
338
359
|
const state = useChatStore.getState()
|
|
339
360
|
if (isServerActive) {
|
|
340
361
|
if (!state.streaming && !state.streamText) {
|
|
341
|
-
|
|
362
|
+
startServerStreamingPlaceholder(sessionId)
|
|
342
363
|
}
|
|
343
364
|
return
|
|
344
365
|
}
|
|
@@ -349,9 +370,10 @@ export function ChatArea() {
|
|
|
349
370
|
) {
|
|
350
371
|
// Server finished — clear all streaming state and fetch final messages
|
|
351
372
|
fetchMessages(sessionId).then(setMessages).catch(() => {})
|
|
352
|
-
|
|
373
|
+
markSessionLocallyIdle(sessionId)
|
|
374
|
+
useChatStore.setState({ streaming: false, streamingSessionId: null, streamSource: null, streamText: '', displayText: '', assistantRenderId: null, streamPhase: 'thinking', streamToolName: '', thinkingText: '', thinkingStartTime: 0 })
|
|
353
375
|
}
|
|
354
|
-
}, [isServerActive, sessionId, setMessages])
|
|
376
|
+
}, [isServerActive, markSessionLocallyIdle, sessionId, setMessages, startServerStreamingPlaceholder])
|
|
355
377
|
|
|
356
378
|
// Poll browser status while session has browser tools
|
|
357
379
|
const hasBrowserTool = getEnabledToolIds(session).includes('browser')
|
|
@@ -37,6 +37,41 @@ describe('MessageBubble', () => {
|
|
|
37
37
|
assert.doesNotMatch(html, /streaming-cursor/)
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
+
it('falls back to persisted streaming content when the live stream payload is temporarily empty', async () => {
|
|
41
|
+
const messageBubbleModule = await import('./message-bubble') as Record<string, unknown>
|
|
42
|
+
const MessageBubble = (
|
|
43
|
+
messageBubbleModule.MessageBubble
|
|
44
|
+
|| (messageBubbleModule.default as { MessageBubble?: unknown } | undefined)?.MessageBubble
|
|
45
|
+
|| (messageBubbleModule['module.exports'] as { MessageBubble?: unknown } | undefined)?.MessageBubble
|
|
46
|
+
) as typeof import('./message-bubble').MessageBubble | undefined
|
|
47
|
+
assert.ok(MessageBubble)
|
|
48
|
+
|
|
49
|
+
const html = renderToStaticMarkup(
|
|
50
|
+
React.createElement(MessageBubble, {
|
|
51
|
+
message: {
|
|
52
|
+
role: 'assistant',
|
|
53
|
+
text: 'Recovered persisted partial text',
|
|
54
|
+
time: Date.now(),
|
|
55
|
+
kind: 'chat',
|
|
56
|
+
streaming: true,
|
|
57
|
+
},
|
|
58
|
+
assistantName: 'Hal2k-3',
|
|
59
|
+
agentName: 'Hal2k-3',
|
|
60
|
+
liveStream: {
|
|
61
|
+
active: true,
|
|
62
|
+
phase: 'responding',
|
|
63
|
+
toolName: '',
|
|
64
|
+
text: '',
|
|
65
|
+
thinking: '',
|
|
66
|
+
toolEvents: [],
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
assert.match(html, /Recovered persisted partial text/)
|
|
72
|
+
assert.match(html, /streaming-cursor/)
|
|
73
|
+
})
|
|
74
|
+
|
|
40
75
|
it('renders upload-linked screenshots inline without duplicating them at the bottom', async () => {
|
|
41
76
|
const messageBubbleModule = await import('./message-bubble') as Record<string, unknown>
|
|
42
77
|
const MessageBubble = (
|
|
@@ -367,12 +367,14 @@ export const MessageBubble = memo(function MessageBubble({ message, assistantNam
|
|
|
367
367
|
const toolEvents = message.toolEvents ?? emptyToolEvents
|
|
368
368
|
const toolEventsForMedia = useMemo(
|
|
369
369
|
() => (liveStreamActive
|
|
370
|
-
? liveToolEvents.
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
370
|
+
? (liveToolEvents.length > 0
|
|
371
|
+
? liveToolEvents.map((event) => ({
|
|
372
|
+
name: event.name,
|
|
373
|
+
input: event.input,
|
|
374
|
+
output: event.output,
|
|
375
|
+
error: event.status === 'error' || undefined,
|
|
376
|
+
}))
|
|
377
|
+
: toolEvents)
|
|
376
378
|
: toolEvents),
|
|
377
379
|
[liveStreamActive, liveToolEvents, toolEvents],
|
|
378
380
|
)
|
|
@@ -383,7 +385,15 @@ export const MessageBubble = memo(function MessageBubble({ message, assistantNam
|
|
|
383
385
|
)
|
|
384
386
|
const displayToolEvents = useMemo(
|
|
385
387
|
() => (liveStreamActive
|
|
386
|
-
? liveToolEvents.
|
|
388
|
+
? (liveToolEvents.length > 0
|
|
389
|
+
? liveToolEvents.filter((ev) => ev.name !== 'send_file' || ev.status === 'error')
|
|
390
|
+
: persistedToolEvents.map((ev, i) => ({
|
|
391
|
+
id: ev.toolCallId || `${message.time}-${ev.name}-${i}`,
|
|
392
|
+
name: ev.name,
|
|
393
|
+
input: ev.input,
|
|
394
|
+
output: ev.output,
|
|
395
|
+
status: ev.error ? 'error' as const : 'done' as const,
|
|
396
|
+
})))
|
|
387
397
|
: persistedToolEvents.map((ev, i) => ({
|
|
388
398
|
id: ev.toolCallId || `${message.time}-${ev.name}-${i}`,
|
|
389
399
|
name: ev.name,
|
|
@@ -419,11 +429,11 @@ export const MessageBubble = memo(function MessageBubble({ message, assistantNam
|
|
|
419
429
|
? (liveStreamActive ? (liveStream?.thinking?.trim() ? liveStream.thinking : undefined) : message.thinking)
|
|
420
430
|
: undefined
|
|
421
431
|
|
|
422
|
-
const sourceText = liveStreamActive ? (liveStream?.text || '') : message.text
|
|
432
|
+
const sourceText = liveStreamActive ? (liveStream?.text || message.text || '') : message.text
|
|
423
433
|
const connectorDeliveryTranscript = !isUser && message.kind === 'connector-delivery'
|
|
424
434
|
? (message.source?.deliveryTranscript?.trim() || '')
|
|
425
435
|
: ''
|
|
426
|
-
const copySourceText = connectorDeliveryTranscript || (liveStreamActive ? (liveStream?.text || '') : message.text)
|
|
436
|
+
const copySourceText = connectorDeliveryTranscript || (liveStreamActive ? (liveStream?.text || message.text || '') : message.text)
|
|
427
437
|
|
|
428
438
|
// Extract ALL media from ALL tool events for inline display after the message text.
|
|
429
439
|
// Covers send_file, browser screenshots, file tool outputs — everything.
|
|
@@ -188,6 +188,7 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
188
188
|
const snapUntilRef = useRef(0)
|
|
189
189
|
const prevSessionIdRef = useRef<string | null>(null)
|
|
190
190
|
const assistantRenderId = useChatStore((s) => s.assistantRenderId)
|
|
191
|
+
const thinkingStartTime = useChatStore((s) => s.thinkingStartTime)
|
|
191
192
|
const hasLiveArtifacts = useChatStore(selectHasLiveArtifacts)
|
|
192
193
|
const setMessages = useChatStore((s) => s.setMessages)
|
|
193
194
|
const retryLastMessage = useChatStore((s) => s.retryLastMessage)
|
|
@@ -297,13 +298,46 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
297
298
|
return dedupeMessagesForDisplay(displayedMessages)
|
|
298
299
|
}, [messages, showAlerts, showOk])
|
|
299
300
|
|
|
301
|
+
const latestPersistedStreamingMessage = useMemo(() => {
|
|
302
|
+
for (let i = baseDisplayedMessages.length - 1; i >= 0; i -= 1) {
|
|
303
|
+
const candidate = baseDisplayedMessages[i]
|
|
304
|
+
if (candidate.role === 'assistant' && candidate.streaming === true) {
|
|
305
|
+
return candidate
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return null
|
|
309
|
+
}, [baseDisplayedMessages])
|
|
310
|
+
|
|
311
|
+
const currentRunHasCompletedAssistant = useMemo(
|
|
312
|
+
() => (
|
|
313
|
+
streaming
|
|
314
|
+
&& thinkingStartTime > 0
|
|
315
|
+
&& baseDisplayedMessages.some((message) => (
|
|
316
|
+
message.role === 'assistant'
|
|
317
|
+
&& message.streaming !== true
|
|
318
|
+
&& message.kind !== 'system'
|
|
319
|
+
&& message.kind !== 'heartbeat'
|
|
320
|
+
&& typeof message.time === 'number'
|
|
321
|
+
&& message.time >= thinkingStartTime
|
|
322
|
+
))
|
|
323
|
+
),
|
|
324
|
+
[baseDisplayedMessages, streaming, thinkingStartTime],
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
const showLiveStreamRow = streaming
|
|
328
|
+
&& !!assistantRenderId
|
|
329
|
+
&& !currentRunHasCompletedAssistant
|
|
330
|
+
&& (hasLiveArtifacts || !!latestPersistedStreamingMessage)
|
|
331
|
+
|
|
300
332
|
const streamingAwareMessages = useMemo(() => (
|
|
301
333
|
buildStreamingAwareMessageList(baseDisplayedMessages, {
|
|
302
334
|
localStreaming: streaming,
|
|
303
335
|
hasLiveArtifacts,
|
|
304
336
|
assistantRenderId,
|
|
337
|
+
showLiveRow: showLiveStreamRow,
|
|
338
|
+
syntheticAssistant: latestPersistedStreamingMessage,
|
|
305
339
|
})
|
|
306
|
-
), [assistantRenderId, baseDisplayedMessages, hasLiveArtifacts, streaming])
|
|
340
|
+
), [assistantRenderId, baseDisplayedMessages, hasLiveArtifacts, latestPersistedStreamingMessage, showLiveStreamRow, streaming])
|
|
307
341
|
|
|
308
342
|
const filteredMessages = useMemo(() => {
|
|
309
343
|
let nextMessages = bookmarkFilter
|
|
@@ -623,7 +657,7 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
623
657
|
}, [searchOpen])
|
|
624
658
|
|
|
625
659
|
return (
|
|
626
|
-
<div className="relative flex-1 min-h-0 min-w-0 flex flex-col overflow-hidden" data-testid="message-list">
|
|
660
|
+
<div className="relative flex-1 min-h-0 min-w-0 flex flex-col overflow-hidden isolate" data-testid="message-list">
|
|
627
661
|
<div className="shrink-0 px-4 md:px-12 lg:px-16 pt-3">
|
|
628
662
|
<div className="flex flex-wrap items-center gap-2 rounded-[14px] border border-white/[0.06] bg-surface/55 px-3 py-2 backdrop-blur-sm">
|
|
629
663
|
<button
|
|
@@ -853,7 +887,7 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
853
887
|
{transcriptNodes}
|
|
854
888
|
<ApprovalCards agentId={agent?.id} />
|
|
855
889
|
<LiveThinkingLane
|
|
856
|
-
show={streaming && !hasLiveArtifacts && !hasVisiblePersistedStreamingMessage}
|
|
890
|
+
show={streaming && !showLiveStreamRow && !hasLiveArtifacts && !hasVisiblePersistedStreamingMessage}
|
|
857
891
|
assistantName={assistantName}
|
|
858
892
|
agentAvatarSeed={agent?.avatarSeed}
|
|
859
893
|
agentAvatarUrl={agent?.avatarUrl}
|
|
@@ -50,6 +50,10 @@ export function ChatInput({ streaming, busy, onSend, onStop, extensionChatAction
|
|
|
50
50
|
const streamPhase = useChatStore((s) => s.streamPhase)
|
|
51
51
|
const streamToolName = useChatStore((s) => s.streamToolName)
|
|
52
52
|
const visibleQueuedMessages = listQueuedMessagesForSession(queuedMessages, sessionId)
|
|
53
|
+
const sendingQueuedMessages = visibleQueuedMessages.filter((item) => item.sending)
|
|
54
|
+
const pendingQueuedMessages = visibleQueuedMessages.filter((item) => !item.sending)
|
|
55
|
+
const displayedQueuedMessages = [...sendingQueuedMessages, ...pendingQueuedMessages]
|
|
56
|
+
const nextPendingRunId = pendingQueuedMessages[0]?.runId ?? null
|
|
53
57
|
const shouldQueue = !!sessionId && (busy || visibleQueuedMessages.length > 0)
|
|
54
58
|
|
|
55
59
|
useEffect(() => {
|
|
@@ -178,6 +182,8 @@ export function ChatInput({ streaming, busy, onSend, onStop, extensionChatAction
|
|
|
178
182
|
const hasContent = value.trim().length > 0 || pendingFiles.length > 0
|
|
179
183
|
const queueStatusLabel = !busy
|
|
180
184
|
? 'Queue ready'
|
|
185
|
+
: sendingQueuedMessages.length > 0 && pendingQueuedMessages.length === 0
|
|
186
|
+
? 'Sending now'
|
|
181
187
|
: streamPhase === 'queued'
|
|
182
188
|
? 'Queued'
|
|
183
189
|
: streamPhase === 'tool' && streamToolName
|
|
@@ -191,6 +197,8 @@ export function ChatInput({ streaming, busy, onSend, onStop, extensionChatAction
|
|
|
191
197
|
: 'Working'
|
|
192
198
|
const queueStatusDetail = !busy
|
|
193
199
|
? 'Queued messages are ready and will dispatch automatically.'
|
|
200
|
+
: sendingQueuedMessages.length > 0 && pendingQueuedMessages.length === 0
|
|
201
|
+
? 'The queued message has been accepted and should appear in the transcript shortly.'
|
|
194
202
|
: 'Queued messages will send automatically when the current turn finishes.'
|
|
195
203
|
|
|
196
204
|
return (
|
|
@@ -229,9 +237,16 @@ export function ChatInput({ streaming, busy, onSend, onStop, extensionChatAction
|
|
|
229
237
|
<span className={`relative inline-flex h-2.5 w-2.5 rounded-full ${busy ? 'bg-amber-300' : 'bg-white/[0.45]'}`} />
|
|
230
238
|
</span>
|
|
231
239
|
<span className="label-mono text-amber-300/80">Message queue</span>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
240
|
+
{pendingQueuedMessages.length > 0 && (
|
|
241
|
+
<span className="rounded-pill border border-amber-400/15 bg-amber-400/10 px-2 py-0.5 text-[10px] font-600 text-amber-200">
|
|
242
|
+
{pendingQueuedMessages.length}
|
|
243
|
+
</span>
|
|
244
|
+
)}
|
|
245
|
+
{sendingQueuedMessages.length > 0 && (
|
|
246
|
+
<span className="rounded-pill border border-sky-300/15 bg-sky-300/10 px-2 py-0.5 text-[10px] font-600 text-sky-200">
|
|
247
|
+
{sendingQueuedMessages.length} sending
|
|
248
|
+
</span>
|
|
249
|
+
)}
|
|
235
250
|
<span className={`rounded-pill border px-2 py-0.5 text-[10px] font-700 uppercase tracking-[0.12em] ${
|
|
236
251
|
busy
|
|
237
252
|
? 'border-amber-300/20 bg-amber-300/10 text-amber-100'
|
|
@@ -256,7 +271,7 @@ export function ChatInput({ streaming, busy, onSend, onStop, extensionChatAction
|
|
|
256
271
|
Stop
|
|
257
272
|
</button>
|
|
258
273
|
)}
|
|
259
|
-
{sessionId &&
|
|
274
|
+
{sessionId && pendingQueuedMessages.length > 0 && (
|
|
260
275
|
<button
|
|
261
276
|
type="button"
|
|
262
277
|
onClick={() => { void clearQueuedMessagesForSession(sessionId) }}
|
|
@@ -268,21 +283,26 @@ export function ChatInput({ streaming, busy, onSend, onStop, extensionChatAction
|
|
|
268
283
|
</div>
|
|
269
284
|
</div>
|
|
270
285
|
<div className="max-h-[184px] space-y-1.5 overflow-y-auto px-2.5 py-2.5">
|
|
271
|
-
{
|
|
286
|
+
{displayedQueuedMessages.map((item, index) => (
|
|
272
287
|
<div
|
|
273
288
|
key={item.runId}
|
|
274
289
|
className={`group flex items-start gap-3 rounded-[12px] border px-3 py-2.5 transition-all ${
|
|
275
|
-
|
|
276
|
-
? 'border-
|
|
277
|
-
:
|
|
290
|
+
item.sending
|
|
291
|
+
? 'border-sky-300/15 bg-sky-300/[0.06]'
|
|
292
|
+
: item.runId === nextPendingRunId
|
|
293
|
+
? 'border-amber-300/20 bg-amber-300/[0.07]'
|
|
294
|
+
: 'border-white/[0.05] bg-white/[0.02]'
|
|
278
295
|
}`}
|
|
279
296
|
>
|
|
280
297
|
<div className={`mt-0.5 flex h-6 min-w-6 items-center justify-center rounded-[8px] px-2 text-[10px] font-700 ${
|
|
281
|
-
|
|
282
|
-
? 'bg-
|
|
283
|
-
:
|
|
284
|
-
|
|
285
|
-
|
|
298
|
+
item.sending
|
|
299
|
+
? 'bg-sky-300/15 text-sky-100'
|
|
300
|
+
: item.runId === nextPendingRunId
|
|
301
|
+
? 'bg-amber-300/15 text-amber-100'
|
|
302
|
+
: 'border-white/[0.05] bg-white/[0.02]'
|
|
303
|
+
}`}
|
|
304
|
+
>
|
|
305
|
+
{item.sending ? '>' : pendingQueuedMessages.findIndex((candidate) => candidate.runId === item.runId) + 1}
|
|
286
306
|
</div>
|
|
287
307
|
<div className="min-w-0 flex-1">
|
|
288
308
|
<div className="flex flex-wrap items-center gap-2">
|
|
@@ -290,7 +310,7 @@ export function ChatInput({ streaming, busy, onSend, onStop, extensionChatAction
|
|
|
290
310
|
<span className="rounded-pill border border-sky-300/15 bg-sky-300/10 px-2 py-0.5 text-[10px] font-700 uppercase tracking-[0.12em] text-sky-200 animate-pulse">
|
|
291
311
|
Sending
|
|
292
312
|
</span>
|
|
293
|
-
) :
|
|
313
|
+
) : item.runId === nextPendingRunId && (
|
|
294
314
|
<span className="rounded-pill border border-amber-300/15 bg-amber-300/10 px-2 py-0.5 text-[10px] font-700 uppercase tracking-[0.12em] text-amber-100">
|
|
295
315
|
Next
|
|
296
316
|
</span>
|
|
@@ -851,6 +851,8 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
|
|
|
851
851
|
<label className="block text-[11px] font-700 uppercase tracking-[0.08em] text-text-3/70 mb-2">Port</label>
|
|
852
852
|
<input
|
|
853
853
|
type="number"
|
|
854
|
+
min={1024}
|
|
855
|
+
max={65535}
|
|
854
856
|
value={localPort}
|
|
855
857
|
onChange={(e) => setLocalPort(Number.parseInt(e.target.value, 10) || 18789)}
|
|
856
858
|
className="w-full rounded-[12px] border border-white/[0.08] bg-bg px-3 py-3 text-[13px] text-text outline-none focus:border-accent-bright/30"
|
|
@@ -1168,6 +1170,8 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
|
|
|
1168
1170
|
/>
|
|
1169
1171
|
<input
|
|
1170
1172
|
type="number"
|
|
1173
|
+
min={1}
|
|
1174
|
+
max={65535}
|
|
1171
1175
|
value={sshPort}
|
|
1172
1176
|
onChange={(e) => setSshPort(Number.parseInt(e.target.value, 10) || 22)}
|
|
1173
1177
|
placeholder="22"
|
package/src/instrumentation.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { hmrSingleton } from '@/lib/shared-utils'
|
|
2
|
-
import { log } from '@/lib/server/logger'
|
|
3
2
|
|
|
4
3
|
const TAG = 'instrumentation'
|
|
5
4
|
|
|
6
5
|
export async function register() {
|
|
7
6
|
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
7
|
+
const { log } = await import('@/lib/server/logger')
|
|
8
8
|
const isWorkerOnly = process.env.SWARMCLAW_WORKER_ONLY === '1'
|
|
9
9
|
const { initWsServer, closeWsServer } = await import('./lib/server/ws-hub')
|
|
10
10
|
const { ensureDaemonStarted } = await import('@/lib/server/runtime/daemon-state')
|