@swarmclawai/swarmclaw 1.2.0 → 1.2.1
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 +10 -0
- package/package.json +4 -1
- package/src/app/api/chats/[id]/deploy/route.ts +11 -6
- package/src/app/api/chats/[id]/devserver/route.ts +5 -2
- package/src/app/api/chats/[id]/messages/route.ts +7 -1
- package/src/app/api/credentials/[id]/route.ts +4 -1
- package/src/app/api/extensions/marketplace/route.ts +5 -2
- package/src/app/api/memory/maintenance/route.ts +5 -2
- package/src/app/api/preview-server/route.ts +14 -11
- package/src/app/api/system/status/route.ts +11 -0
- package/src/app/api/upload/route.ts +4 -1
- package/src/cli/index.js +7 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-files-editor.tsx +44 -32
- package/src/components/agents/personality-builder.tsx +13 -7
- package/src/components/agents/trash-list.tsx +1 -1
- package/src/components/chat/message-bubble.tsx +1 -0
- package/src/components/chat/message-list.tsx +25 -39
- package/src/components/chat/swarm-status-card.tsx +10 -3
- package/src/components/layout/daemon-indicator.tsx +7 -8
- package/src/components/layout/update-banner.tsx +8 -13
- package/src/components/logs/log-list.tsx +1 -1
- package/src/components/memory/memory-card.tsx +3 -1
- package/src/components/org-chart/org-chart-view.tsx +4 -0
- package/src/components/projects/project-list.tsx +4 -2
- package/src/components/projects/tabs/overview-tab.tsx +3 -2
- package/src/components/secrets/secret-sheet.tsx +1 -1
- package/src/components/secrets/secrets-list.tsx +1 -1
- package/src/components/shared/agent-switch-dialog.tsx +12 -6
- package/src/components/shared/dir-browser.tsx +22 -18
- package/src/components/skills/skill-sheet.tsx +2 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +1 -1
- package/src/hooks/use-openclaw-gateway.ts +46 -27
- package/src/instrumentation.ts +10 -7
- package/src/lib/chat/chat.ts +18 -2
- package/src/lib/providers/anthropic.ts +6 -3
- package/src/lib/providers/claude-cli.ts +9 -3
- package/src/lib/providers/cli-utils.ts +15 -0
- package/src/lib/providers/codex-cli.ts +9 -3
- package/src/lib/providers/gemini-cli.ts +6 -2
- package/src/lib/providers/index.ts +4 -1
- package/src/lib/providers/ollama.ts +5 -2
- package/src/lib/providers/openai.ts +8 -5
- package/src/lib/providers/opencode-cli.ts +6 -2
- package/src/lib/server/agents/agent-registry.ts +20 -3
- package/src/lib/server/agents/main-agent-loop.ts +4 -3
- package/src/lib/server/autonomy/supervisor-reflection.ts +14 -1
- package/src/lib/server/chat-execution/chat-execution.ts +14 -2
- package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -3
- package/src/lib/server/chat-execution/continuation-limits.ts +6 -3
- package/src/lib/server/chat-execution/message-classifier.ts +5 -2
- package/src/lib/server/chat-execution/post-stream-finalization.ts +4 -1
- package/src/lib/server/chat-execution/prompt-builder.ts +11 -1
- package/src/lib/server/chat-execution/prompt-sections.ts +52 -9
- package/src/lib/server/chat-execution/response-completeness.ts +5 -2
- package/src/lib/server/chat-execution/stream-agent-chat.ts +42 -12
- package/src/lib/server/chatrooms/chatroom-memory-bridge.ts +6 -3
- package/src/lib/server/connectors/bluebubbles.ts +7 -4
- package/src/lib/server/connectors/connector-inbound.ts +16 -13
- package/src/lib/server/connectors/connector-lifecycle.ts +11 -8
- package/src/lib/server/connectors/connector-outbound.ts +6 -3
- package/src/lib/server/connectors/discord.ts +10 -7
- package/src/lib/server/connectors/email.ts +17 -14
- package/src/lib/server/connectors/googlechat.ts +7 -4
- package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -2
- package/src/lib/server/connectors/matrix.ts +6 -3
- package/src/lib/server/connectors/openclaw.ts +20 -17
- package/src/lib/server/connectors/outbox.ts +4 -1
- package/src/lib/server/connectors/runtime-state.ts +19 -0
- package/src/lib/server/connectors/session-consolidation.ts +5 -2
- package/src/lib/server/connectors/signal.ts +9 -6
- package/src/lib/server/connectors/slack.ts +13 -10
- package/src/lib/server/connectors/teams.ts +8 -5
- package/src/lib/server/connectors/telegram.ts +15 -12
- package/src/lib/server/connectors/whatsapp.ts +32 -29
- package/src/lib/server/embeddings.ts +4 -1
- package/src/lib/server/link-understanding.ts +4 -1
- package/src/lib/server/memory/memory-abstract.ts +59 -0
- package/src/lib/server/memory/memory-db.ts +40 -14
- package/src/lib/server/missions/mission-service.ts +6 -3
- package/src/lib/server/openclaw/gateway.ts +8 -5
- package/src/lib/server/project-utils.ts +13 -0
- package/src/lib/server/protocols/protocol-agent-turn.ts +5 -2
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +5 -2
- package/src/lib/server/protocols/protocol-step-helpers.ts +4 -1
- package/src/lib/server/provider-health.ts +18 -0
- package/src/lib/server/query-expansion.ts +4 -1
- package/src/lib/server/runtime/alert-dispatch.ts +7 -6
- package/src/lib/server/runtime/daemon-state.ts +189 -50
- package/src/lib/server/runtime/heartbeat-service.ts +23 -0
- package/src/lib/server/runtime/idle-window.ts +4 -1
- package/src/lib/server/runtime/perf.ts +4 -1
- package/src/lib/server/runtime/process-manager.ts +7 -4
- package/src/lib/server/runtime/queue.ts +31 -28
- package/src/lib/server/runtime/scheduler.ts +9 -6
- package/src/lib/server/runtime/session-run-manager.ts +3 -0
- package/src/lib/server/sandbox/bridge-auth-registry.ts +6 -0
- package/src/lib/server/sandbox/novnc-auth.ts +10 -0
- package/src/lib/server/session-tools/context.ts +14 -0
- package/src/lib/server/session-tools/discovery.ts +9 -6
- package/src/lib/server/session-tools/index.ts +3 -1
- package/src/lib/server/session-tools/platform.ts +1 -1
- package/src/lib/server/session-tools/subagent.ts +23 -2
- package/src/lib/server/session-tools/wallet.ts +4 -1
- package/src/lib/server/skills/clawhub-client.ts +4 -1
- package/src/lib/server/skills/runtime-skill-resolver.ts +8 -2
- package/src/lib/server/skills/skill-eligibility.ts +6 -0
- package/src/lib/server/solana.ts +6 -0
- package/src/lib/server/storage-auth.ts +5 -5
- package/src/lib/server/storage-normalization.ts +4 -0
- package/src/lib/server/storage.ts +19 -8
- package/src/lib/server/tasks/task-followups.ts +4 -1
- package/src/lib/server/tool-loop-detection.ts +8 -3
- package/src/lib/server/tool-planning.ts +226 -0
- package/src/lib/server/tool-retry.ts +4 -3
- package/src/lib/server/wallet/wallet-portfolio.ts +29 -0
- package/src/lib/server/ws-hub.ts +5 -2
- package/src/lib/strip-internal-metadata.test.ts +44 -4
- package/src/lib/strip-internal-metadata.ts +20 -6
- package/src/stores/use-approval-store.ts +7 -1
- package/src/stores/use-chat-store.ts +5 -1
- package/src/types/index.ts +6 -0
package/README.md
CHANGED
|
@@ -190,6 +190,16 @@ The building blocks are the same: **agents, tools, memory, delegation, schedules
|
|
|
190
190
|
|
|
191
191
|
## Release Notes
|
|
192
192
|
|
|
193
|
+
### v1.2.1 Highlights
|
|
194
|
+
|
|
195
|
+
- **System health endpoint**: new `/api/system/status` route returns lightweight health summary for external monitoring and uptime checks.
|
|
196
|
+
- **Memory abstracts**: ~100-token LLM summaries attached to memories for efficient proactive recall without loading full content.
|
|
197
|
+
- **Structured logging**: migrated 40+ files from `console.*` to the `log` module for consistent, level-aware logging across the codebase.
|
|
198
|
+
- **Lint baseline improvements**: reduced lint violations from 440 to 414 (-26) through targeted fixes across server and UI code.
|
|
199
|
+
- **Daemon housekeeping**: pruning for subagent processes, orchestrator state, connector sessions, and usage records to prevent resource leaks.
|
|
200
|
+
- **SKILL.md v2.0.0**: comprehensive CLI documentation covering 40+ command groups with examples and usage patterns.
|
|
201
|
+
- **New dev scripts**: added `type-check`, `test`, and `format` scripts to `package.json` for streamlined development workflows.
|
|
202
|
+
|
|
193
203
|
### v1.1.9 Highlights
|
|
194
204
|
|
|
195
205
|
- **Docker build stability**: limit Next.js page data workers to 1 in build mode to prevent `SQLITE_BUSY` contention.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Self-hosted AI runtime for OpenClaw, delegation, autonomy, runtime skills, crypto wallets, and chat platform connectors.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -61,6 +61,9 @@
|
|
|
61
61
|
"sandbox:build:browser": "docker build -f Dockerfile.sandbox-browser -t swarmclaw-sandbox-browser:bookworm-slim .",
|
|
62
62
|
"benchmark:autonomy": "node ./scripts/benchmark-autonomy-harness.mjs",
|
|
63
63
|
"benchmark:agent-regression": "node --import tsx ./scripts/run-agent-regression-suite.ts",
|
|
64
|
+
"type-check": "tsc --noEmit",
|
|
65
|
+
"test": "npm run test:cli && npm run test:setup && npm run test:openclaw",
|
|
66
|
+
"format": "eslint --fix",
|
|
64
67
|
"lint": "eslint",
|
|
65
68
|
"lint:fix": "eslint --fix",
|
|
66
69
|
"lint:baseline": "node ./scripts/lint-baseline.mjs check",
|
|
@@ -3,6 +3,9 @@ import { execSync } from 'child_process'
|
|
|
3
3
|
import { loadSessions } from '@/lib/server/storage'
|
|
4
4
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
5
5
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
6
|
+
import { log } from '@/lib/server/logger'
|
|
7
|
+
|
|
8
|
+
const TAG = 'api-deploy'
|
|
6
9
|
|
|
7
10
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
11
|
const { id } = await params
|
|
@@ -21,16 +24,18 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
21
24
|
try {
|
|
22
25
|
execSync(`git commit -m "${msg.replace(/"/g, '\\"')}"`, opts)
|
|
23
26
|
committed = true
|
|
24
|
-
} catch (ce:
|
|
25
|
-
|
|
27
|
+
} catch (ce: unknown) {
|
|
28
|
+
const ex = ce as { stdout?: string; stderr?: string }
|
|
29
|
+
if (!(ex.stdout || ex.stderr || '').includes('nothing to commit')) throw ce
|
|
26
30
|
}
|
|
27
31
|
execSync('git push 2>&1', opts)
|
|
28
|
-
|
|
32
|
+
log.info(TAG, `deployed: ${msg}`)
|
|
29
33
|
return NextResponse.json({ ok: true, output: committed ? 'Committed and pushed!' : 'Already committed — pushed to remote!' })
|
|
30
|
-
} catch (e:
|
|
31
|
-
|
|
34
|
+
} catch (e: unknown) {
|
|
35
|
+
const ex = e as { stderr?: string; stdout?: string; message?: string }
|
|
36
|
+
log.error(TAG, `deploy error:`, ex.message)
|
|
32
37
|
return NextResponse.json(
|
|
33
|
-
{ ok: false, error: (
|
|
38
|
+
{ ok: false, error: (ex.stderr || ex.stdout || ex.message || 'Unknown error').toString().slice(0, 300) },
|
|
34
39
|
{ status: 500 },
|
|
35
40
|
)
|
|
36
41
|
}
|
|
@@ -6,6 +6,9 @@ import { resolveDevServerLaunchDir } from '@/lib/server/runtime/devserver-launch
|
|
|
6
6
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
7
7
|
import { sleep } from '@/lib/shared-utils'
|
|
8
8
|
import net from 'net'
|
|
9
|
+
import { log } from '@/lib/server/logger'
|
|
10
|
+
|
|
11
|
+
const TAG = 'api-devserver'
|
|
9
12
|
|
|
10
13
|
interface DevServerStartResult {
|
|
11
14
|
status?: number
|
|
@@ -61,11 +64,11 @@ async function startDevServer(id: string, session: { cwd: string }): Promise<Dev
|
|
|
61
64
|
|
|
62
65
|
proc.stdout!.on('data', onData)
|
|
63
66
|
proc.stderr!.on('data', onData)
|
|
64
|
-
proc.on('close', () => { devServers.delete(id);
|
|
67
|
+
proc.on('close', () => { devServers.delete(id); log.info(TAG, `dev server stopped for ${id}`) })
|
|
65
68
|
proc.on('error', () => devServers.delete(id))
|
|
66
69
|
|
|
67
70
|
devServers.set(id, { proc, url: `http://${localIP()}:${port}` })
|
|
68
|
-
|
|
71
|
+
log.info(TAG, `starting dev server in ${launch.launchDir} (session cwd=${session.cwd})`)
|
|
69
72
|
|
|
70
73
|
await sleep(4000)
|
|
71
74
|
const ds = devServers.get(id)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import { loadStoredItem, upsertStoredItem } from '@/lib/server/storage'
|
|
2
|
+
import { loadStoredItem, upsertStoredItem, active } from '@/lib/server/storage'
|
|
3
3
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
4
|
import { materializeStreamingAssistantArtifacts } from '@/lib/chat/chat-streaming-state'
|
|
5
5
|
import { appendSessionNote } from '@/lib/server/session-note'
|
|
6
|
+
import { getSessionRunState } from '@/lib/server/runtime/session-run-manager'
|
|
6
7
|
import type { Message, Session } from '@/types'
|
|
7
8
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
8
9
|
|
|
@@ -12,8 +13,13 @@ export async function GET(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
12
13
|
if (!session) return notFound()
|
|
13
14
|
session.messages = Array.isArray(session.messages) ? session.messages : []
|
|
14
15
|
|
|
16
|
+
// Check both persisted fields AND in-memory runtime state.
|
|
17
|
+
// The persisted session doesn't have active/currentRunId set during runs —
|
|
18
|
+
// those are only computed at runtime from the active map and run ledger.
|
|
15
19
|
const sessionClaimsActive = session.active === true
|
|
16
20
|
|| (typeof session.currentRunId === 'string' && session.currentRunId.trim().length > 0)
|
|
21
|
+
|| active.has(id)
|
|
22
|
+
|| !!getSessionRunState(id).runningRunId
|
|
17
23
|
if (!sessionClaimsActive && materializeStreamingAssistantArtifacts(session.messages)) {
|
|
18
24
|
upsertStoredItem('sessions', id, session)
|
|
19
25
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { loadCredentials, deleteCredential } from '@/lib/server/storage'
|
|
3
3
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
|
+
import { log } from '@/lib/server/logger'
|
|
5
|
+
|
|
6
|
+
const TAG = 'api-credentials'
|
|
4
7
|
|
|
5
8
|
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
9
|
const { id: credId } = await params
|
|
@@ -9,6 +12,6 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
9
12
|
return notFound()
|
|
10
13
|
}
|
|
11
14
|
deleteCredential(credId)
|
|
12
|
-
|
|
15
|
+
log.info(TAG, `deleted ${credId}`)
|
|
13
16
|
return new NextResponse('OK')
|
|
14
17
|
}
|
|
@@ -3,6 +3,9 @@ import { inferExtensionPublisherSourceFromUrl } from '@/lib/extension-sources'
|
|
|
3
3
|
import { searchClawHub } from '@/lib/server/skills/clawhub-client'
|
|
4
4
|
import type { ExtensionCatalogSource } from '@/types'
|
|
5
5
|
import { errorMessage } from '@/lib/shared-utils'
|
|
6
|
+
import { log } from '@/lib/server/logger'
|
|
7
|
+
|
|
8
|
+
const TAG = 'api-extensions-marketplace'
|
|
6
9
|
|
|
7
10
|
export const dynamic = 'force-dynamic'
|
|
8
11
|
|
|
@@ -71,7 +74,7 @@ export async function GET(req: Request) {
|
|
|
71
74
|
})
|
|
72
75
|
}
|
|
73
76
|
} catch (err: unknown) {
|
|
74
|
-
|
|
77
|
+
log.warn(TAG, 'Registry failed:', {
|
|
75
78
|
registryUrl: registry.url,
|
|
76
79
|
error: errorMessage(err),
|
|
77
80
|
})
|
|
@@ -93,7 +96,7 @@ export async function GET(req: Request) {
|
|
|
93
96
|
catalogSource: 'clawhub',
|
|
94
97
|
})))
|
|
95
98
|
} catch (err: unknown) {
|
|
96
|
-
|
|
99
|
+
log.warn(TAG, 'ClawHub failed:', errorMessage(err))
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
allExtensions.sort((a, b) => {
|
|
@@ -4,6 +4,9 @@ import { getMemoryDb } from '@/lib/server/memory/memory-db'
|
|
|
4
4
|
import { loadSettings } from '@/lib/server/storage'
|
|
5
5
|
import { syncAllSessionArchiveMemories } from '@/lib/server/memory/session-archive-memory'
|
|
6
6
|
import { DATA_DIR } from '@/lib/server/data-dir'
|
|
7
|
+
import { log } from '@/lib/server/logger'
|
|
8
|
+
|
|
9
|
+
const TAG = 'api-memory-maintenance'
|
|
7
10
|
|
|
8
11
|
function parseBool(value: unknown, fallback: boolean): boolean {
|
|
9
12
|
if (typeof value === 'boolean') return value
|
|
@@ -46,7 +49,7 @@ export async function GET(req: Request) {
|
|
|
46
49
|
archiveExportDir: path.join(DATA_DIR, 'session-archives'),
|
|
47
50
|
})
|
|
48
51
|
} catch (err: unknown) {
|
|
49
|
-
|
|
52
|
+
log.error(TAG, 'GET failed:', err)
|
|
50
53
|
return NextResponse.json({ ok: false, error: String((err as Error)?.message || err) }, { status: 500 })
|
|
51
54
|
}
|
|
52
55
|
}
|
|
@@ -77,7 +80,7 @@ export async function POST(req: Request) {
|
|
|
77
80
|
...result,
|
|
78
81
|
})
|
|
79
82
|
} catch (err: unknown) {
|
|
80
|
-
|
|
83
|
+
log.error(TAG, 'POST failed:', err)
|
|
81
84
|
return NextResponse.json({ ok: false, error: String((err as Error)?.message || err) }, { status: 500 })
|
|
82
85
|
}
|
|
83
86
|
}
|
|
@@ -8,6 +8,9 @@ import { resolveDevServerLaunchDir } from '@/lib/server/runtime/devserver-launch
|
|
|
8
8
|
import { resolvePathWithinBaseDir } from '@/lib/server/path-utils'
|
|
9
9
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
10
10
|
import { hmrSingleton, sleep } from '@/lib/shared-utils'
|
|
11
|
+
import { log } from '@/lib/server/logger'
|
|
12
|
+
|
|
13
|
+
const TAG = 'api-preview-server'
|
|
11
14
|
|
|
12
15
|
// ---------------------------------------------------------------------------
|
|
13
16
|
// MIME types for static server
|
|
@@ -181,7 +184,7 @@ function createStaticServer(dir: string): http.Server {
|
|
|
181
184
|
async function startNpmServer(dir: string, command: string[], port: number, framework?: string): Promise<PreviewServer> {
|
|
182
185
|
// Install deps if node_modules missing
|
|
183
186
|
if (!fs.existsSync(path.join(dir, 'node_modules'))) {
|
|
184
|
-
|
|
187
|
+
log.info(TAG, `Installing dependencies in ${dir}`)
|
|
185
188
|
await new Promise<void>((resolve, reject) => {
|
|
186
189
|
const install = spawn('npm', ['install'], { cwd: dir, stdio: 'pipe' })
|
|
187
190
|
install.on('close', (code) => code === 0 ? resolve() : reject(new Error(`npm install exited ${code}`)))
|
|
@@ -206,14 +209,14 @@ async function startNpmServer(dir: string, command: string[], port: number, fram
|
|
|
206
209
|
env,
|
|
207
210
|
})
|
|
208
211
|
|
|
209
|
-
let
|
|
212
|
+
let processOutput = ''
|
|
210
213
|
let detectedPort = port
|
|
211
214
|
const urlRe = /https?:\/\/(?:localhost|0\.0\.0\.0|127\.0\.0\.1|[\d.]+):(\d+)/
|
|
212
215
|
|
|
213
216
|
const onData = (chunk: Buffer) => {
|
|
214
217
|
const text = chunk.toString()
|
|
215
|
-
|
|
216
|
-
if (
|
|
218
|
+
processOutput += text
|
|
219
|
+
if (processOutput.length > 10000) processOutput = processOutput.slice(-5000)
|
|
217
220
|
const match = text.match(urlRe)
|
|
218
221
|
if (match) {
|
|
219
222
|
detectedPort = parseInt(match[1], 10)
|
|
@@ -236,7 +239,7 @@ async function startNpmServer(dir: string, command: string[], port: number, fram
|
|
|
236
239
|
|
|
237
240
|
proc.on('close', () => {
|
|
238
241
|
servers.delete(dirKey(dir))
|
|
239
|
-
|
|
242
|
+
log.info(TAG, `npm server stopped for ${dir}`)
|
|
240
243
|
})
|
|
241
244
|
proc.on('error', () => servers.delete(dirKey(dir)))
|
|
242
245
|
|
|
@@ -246,10 +249,10 @@ async function startNpmServer(dir: string, command: string[], port: number, fram
|
|
|
246
249
|
await sleep(5000)
|
|
247
250
|
if (proc.exitCode !== null) {
|
|
248
251
|
servers.delete(dirKey(dir))
|
|
249
|
-
throw new Error(`npm dev server exited early with code ${proc.exitCode}\n${
|
|
252
|
+
throw new Error(`npm dev server exited early with code ${proc.exitCode}\n${processOutput.slice(-4000)}`)
|
|
250
253
|
}
|
|
251
254
|
entry.port = detectedPort
|
|
252
|
-
entry.log =
|
|
255
|
+
entry.log = processOutput
|
|
253
256
|
|
|
254
257
|
return entry
|
|
255
258
|
}
|
|
@@ -295,7 +298,7 @@ export async function POST(req: Request) {
|
|
|
295
298
|
const port = await findFreePort()
|
|
296
299
|
|
|
297
300
|
if (project.type === 'npm' && project.devCommand) {
|
|
298
|
-
|
|
301
|
+
log.info(TAG, `Detected ${project.framework} project in ${launch.launchDir}, running: ${project.devCommand.join(' ')}`)
|
|
299
302
|
try {
|
|
300
303
|
const entry = await startNpmServer(launch.launchDir, project.devCommand, port, project.framework)
|
|
301
304
|
return NextResponse.json({
|
|
@@ -305,7 +308,7 @@ export async function POST(req: Request) {
|
|
|
305
308
|
launchDir: launch.launchDir,
|
|
306
309
|
})
|
|
307
310
|
} catch (err: unknown) {
|
|
308
|
-
|
|
311
|
+
log.error(TAG, 'npm server failed, falling back to static:', err)
|
|
309
312
|
// Fall through to static server
|
|
310
313
|
}
|
|
311
314
|
}
|
|
@@ -319,7 +322,7 @@ export async function POST(req: Request) {
|
|
|
319
322
|
|
|
320
323
|
const entry: PreviewServer = { type: 'static', server, port, dir, startedAt: Date.now(), log: '' }
|
|
321
324
|
servers.set(key, entry)
|
|
322
|
-
|
|
325
|
+
log.info(TAG, `Started static server for ${dir} on port ${port}`)
|
|
323
326
|
|
|
324
327
|
return NextResponse.json(buildResponse(entry))
|
|
325
328
|
|
|
@@ -332,7 +335,7 @@ export async function POST(req: Request) {
|
|
|
332
335
|
}
|
|
333
336
|
if (srv.server) srv.server.close()
|
|
334
337
|
servers.delete(key)
|
|
335
|
-
|
|
338
|
+
log.info(TAG, `Stopped server for ${launch.launchDir}`)
|
|
336
339
|
}
|
|
337
340
|
return NextResponse.json({ running: false, dir: launch.launchDir })
|
|
338
341
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getDaemonHealthSummary } from '@/lib/server/runtime/daemon-state'
|
|
3
|
+
import packageJson from '../../../../../package.json'
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const summary = getDaemonHealthSummary()
|
|
7
|
+
return NextResponse.json({
|
|
8
|
+
...summary,
|
|
9
|
+
version: packageJson.version,
|
|
10
|
+
})
|
|
11
|
+
}
|
|
@@ -3,6 +3,9 @@ import fs from 'fs'
|
|
|
3
3
|
import path from 'path'
|
|
4
4
|
import { genId } from '@/lib/id'
|
|
5
5
|
import { UPLOAD_DIR } from '@/lib/server/storage'
|
|
6
|
+
import { log } from '@/lib/server/logger'
|
|
7
|
+
|
|
8
|
+
const TAG = 'api-upload'
|
|
6
9
|
|
|
7
10
|
export async function POST(req: Request) {
|
|
8
11
|
const filename = req.headers.get('x-filename') || 'image.png'
|
|
@@ -12,7 +15,7 @@ export async function POST(req: Request) {
|
|
|
12
15
|
|
|
13
16
|
if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true })
|
|
14
17
|
fs.writeFileSync(filePath, buf)
|
|
15
|
-
|
|
18
|
+
log.info(TAG, `saved ${buf.length} bytes to ${filePath}`)
|
|
16
19
|
|
|
17
20
|
return NextResponse.json({ path: filePath, size: buf.length, url: `/api/uploads/${name}` })
|
|
18
21
|
}
|
package/src/cli/index.js
CHANGED
|
@@ -730,6 +730,13 @@ const COMMAND_GROUPS = [
|
|
|
730
730
|
cmd('delete-many', 'DELETE', '/uploads', 'Delete uploads by filter/body (filenames, olderThanDays, category, or all)', { expectsJsonBody: true }),
|
|
731
731
|
],
|
|
732
732
|
},
|
|
733
|
+
{
|
|
734
|
+
name: 'system-status',
|
|
735
|
+
description: 'Lightweight system health summary',
|
|
736
|
+
commands: [
|
|
737
|
+
cmd('get', 'GET', '/system/status', 'Get system health summary (safe for external monitors)'),
|
|
738
|
+
],
|
|
739
|
+
},
|
|
733
740
|
{
|
|
734
741
|
name: 'usage',
|
|
735
742
|
description: 'Usage and cost summary',
|
package/src/cli/spec.js
CHANGED
|
@@ -496,6 +496,7 @@ const COMMAND_GROUPS = {
|
|
|
496
496
|
description: 'System and version endpoints',
|
|
497
497
|
commands: {
|
|
498
498
|
ip: { description: 'Get local bind IP/port', method: 'GET', path: '/ip' },
|
|
499
|
+
status: { description: 'Get lightweight system health summary (safe for external monitors)', method: 'GET', path: '/system/status' },
|
|
499
500
|
usage: { description: 'Get usage summary', method: 'GET', path: '/usage' },
|
|
500
501
|
version: { description: 'Get local/remote git version info', method: 'GET', path: '/version' },
|
|
501
502
|
update: { description: 'Update to latest stable release tag (fallback: main)', method: 'POST', path: '/version/update' },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
4
|
import { api } from '@/lib/app/api-client'
|
|
5
5
|
import { errorMessage } from '@/lib/shared-utils'
|
|
6
6
|
import { PersonalityBuilder } from './personality-builder'
|
|
@@ -8,6 +8,14 @@ import { PersonalityBuilder } from './personality-builder'
|
|
|
8
8
|
const FILES = ['SOUL.md', 'IDENTITY.md', 'USER.md', 'TOOLS.md', 'HEARTBEAT.md', 'MEMORY.md', 'AGENTS.md'] as const
|
|
9
9
|
const GUIDED_FILES = new Set(['SOUL.md', 'IDENTITY.md', 'USER.md'])
|
|
10
10
|
|
|
11
|
+
function makeInitialFiles(): Record<string, FileState> {
|
|
12
|
+
const initial: Record<string, FileState> = {}
|
|
13
|
+
for (const f of FILES) {
|
|
14
|
+
initial[f] = { content: '', original: '', loading: true, saving: false }
|
|
15
|
+
}
|
|
16
|
+
return initial
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
interface FileState {
|
|
12
20
|
content: string
|
|
13
21
|
original: string
|
|
@@ -22,45 +30,49 @@ interface Props {
|
|
|
22
30
|
|
|
23
31
|
export function AgentFilesEditor({ agentId }: Props) {
|
|
24
32
|
const [activeTab, setActiveTab] = useState<string>(FILES[0])
|
|
25
|
-
const [files, setFiles] = useState<Record<string, FileState>>(
|
|
33
|
+
const [files, setFiles] = useState<Record<string, FileState>>(makeInitialFiles)
|
|
26
34
|
const [guidedMode, setGuidedMode] = useState(false)
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
// Reset to loading state when agentId changes
|
|
37
|
+
const [prevAgentId, setPrevAgentId] = useState(agentId)
|
|
38
|
+
if (agentId !== prevAgentId) {
|
|
39
|
+
setPrevAgentId(agentId)
|
|
40
|
+
setFiles(makeInitialFiles())
|
|
41
|
+
}
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
let cancelled = false
|
|
45
|
+
api<Record<string, { content: string; error?: string }>>('GET', `/openclaw/agent-files?agentId=${agentId}`)
|
|
46
|
+
.then((result) => {
|
|
47
|
+
if (cancelled) return
|
|
48
|
+
setFiles((prev) => {
|
|
49
|
+
const next = { ...prev }
|
|
50
|
+
for (const [name, data] of Object.entries(result)) {
|
|
51
|
+
next[name] = {
|
|
52
|
+
content: data.content,
|
|
53
|
+
original: data.content,
|
|
54
|
+
loading: false,
|
|
55
|
+
saving: false,
|
|
56
|
+
error: data.error,
|
|
57
|
+
}
|
|
46
58
|
}
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
return next
|
|
60
|
+
})
|
|
49
61
|
})
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
.catch((err: unknown) => {
|
|
63
|
+
if (cancelled) return
|
|
64
|
+
const message = errorMessage(err)
|
|
65
|
+
setFiles((prev) => {
|
|
66
|
+
const next = { ...prev }
|
|
67
|
+
for (const f of FILES) {
|
|
68
|
+
next[f] = { ...next[f], loading: false, error: message }
|
|
69
|
+
}
|
|
70
|
+
return next
|
|
71
|
+
})
|
|
58
72
|
})
|
|
59
|
-
}
|
|
73
|
+
return () => { cancelled = true }
|
|
60
74
|
}, [agentId])
|
|
61
75
|
|
|
62
|
-
useEffect(() => { loadFiles() }, [loadFiles])
|
|
63
|
-
|
|
64
76
|
const handleContentChange = (filename: string, content: string) => {
|
|
65
77
|
setFiles((prev) => ({
|
|
66
78
|
...prev,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useMemo, useState } from 'react'
|
|
4
4
|
import type { PersonalityDraft } from '@/types'
|
|
5
|
-
import { api } from '@/lib/app/api-client'
|
|
6
5
|
import {
|
|
7
6
|
parseIdentityMd, serializeIdentityMd,
|
|
8
7
|
parseUserMd, serializeUserMd,
|
|
@@ -19,12 +18,12 @@ interface Props {
|
|
|
19
18
|
const inputClass = 'w-full px-3 py-2 rounded-[10px] border border-white/[0.06] bg-black/20 text-[13px] text-text outline-none placeholder:text-text-3/40 focus:border-white/[0.12] transition-colors'
|
|
20
19
|
const labelClass = 'block text-[11px] font-600 uppercase tracking-wider text-text-3/50 mb-1'
|
|
21
20
|
|
|
22
|
-
export function PersonalityBuilder({
|
|
21
|
+
export function PersonalityBuilder({ fileType, content, onSave }: Props) {
|
|
23
22
|
const [draft, setDraft] = useState<Record<string, string>>({})
|
|
24
23
|
const [initialDraft, setInitialDraft] = useState<Record<string, string>>({})
|
|
25
24
|
const [saveState, setSaveState] = useState<'idle' | 'saved'>('idle')
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
const parsedContent = useMemo(() => {
|
|
28
27
|
let parsed: Record<string, string> = {}
|
|
29
28
|
if (fileType === 'IDENTITY.md') {
|
|
30
29
|
const p = parseIdentityMd(content)
|
|
@@ -36,11 +35,18 @@ export function PersonalityBuilder({ agentId: _agentId, fileType, content, onSav
|
|
|
36
35
|
const p = parseSoulMd(content)
|
|
37
36
|
parsed = { coreTruths: p.coreTruths || '', boundaries: p.boundaries || '', vibe: p.vibe || '', continuity: p.continuity || '' }
|
|
38
37
|
}
|
|
39
|
-
|
|
40
|
-
setInitialDraft(parsed)
|
|
41
|
-
setSaveState('idle')
|
|
38
|
+
return parsed
|
|
42
39
|
}, [content, fileType])
|
|
43
40
|
|
|
41
|
+
// Reset form when content/fileType changes (render-time state adjustment)
|
|
42
|
+
const [syncKey, setSyncKey] = useState({ content, fileType })
|
|
43
|
+
if (content !== syncKey.content || fileType !== syncKey.fileType) {
|
|
44
|
+
setSyncKey({ content, fileType })
|
|
45
|
+
setDraft(parsedContent)
|
|
46
|
+
setInitialDraft(parsedContent)
|
|
47
|
+
setSaveState('idle')
|
|
48
|
+
}
|
|
49
|
+
|
|
44
50
|
const isDirty = useMemo(() => {
|
|
45
51
|
return Object.keys(draft).some((k) => draft[k] !== (initialDraft[k] ?? ''))
|
|
46
52
|
}, [draft, initialDraft])
|
|
@@ -12,7 +12,7 @@ export function TrashList() {
|
|
|
12
12
|
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
13
13
|
const [confirmPermanent, setConfirmPermanent] = useState<Agent | null>(null)
|
|
14
14
|
|
|
15
|
-
useEffect(() => { loadTrashedAgents() }, [])
|
|
15
|
+
useEffect(() => { loadTrashedAgents() }, [loadTrashedAgents])
|
|
16
16
|
|
|
17
17
|
const handleRestore = async (id: string) => {
|
|
18
18
|
await api('POST', '/agents/trash', { id })
|
|
@@ -418,6 +418,7 @@ export const MessageBubble = memo(function MessageBubble({ message, assistantNam
|
|
|
418
418
|
const effectiveThinking = !isUser
|
|
419
419
|
? (liveStreamActive ? (liveStream?.thinking?.trim() ? liveStream.thinking : undefined) : message.thinking)
|
|
420
420
|
: undefined
|
|
421
|
+
|
|
421
422
|
const sourceText = liveStreamActive ? (liveStream?.text || '') : message.text
|
|
422
423
|
const connectorDeliveryTranscript = !isUser && message.kind === 'connector-delivery'
|
|
423
424
|
? (message.source?.deliveryTranscript?.trim() || '')
|
|
@@ -100,13 +100,6 @@ const LiveStreamBubble = memo(function LiveStreamBubble(props: LiveStreamBubbleP
|
|
|
100
100
|
return <MessageBubble {...props} liveStream={liveStream} />
|
|
101
101
|
})
|
|
102
102
|
|
|
103
|
-
interface LastMessageMomentOverlayProps {
|
|
104
|
-
messages: Message[]
|
|
105
|
-
sessionId: string | null
|
|
106
|
-
agentId?: string | null
|
|
107
|
-
streaming: boolean
|
|
108
|
-
children: (momentOverlay: React.ReactNode, currentMoment: { kind: string } | null) => React.ReactNode
|
|
109
|
-
}
|
|
110
103
|
|
|
111
104
|
function useLastMessageMoment(messages: Message[], sessionId: string | null, agentId: string | null | undefined, streaming: boolean) {
|
|
112
105
|
type MomentType = { kind: 'heartbeat' } | { kind: 'tool'; key: string; name: string; input: string }
|
|
@@ -117,31 +110,24 @@ function useLastMessageMoment(messages: Message[], sessionId: string | null, age
|
|
|
117
110
|
setCurrentMoment({ kind: 'heartbeat' })
|
|
118
111
|
})
|
|
119
112
|
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
seededMomentSessionRef.current = sessionId
|
|
133
|
-
prevToolKeyRef.current = getLatestAssistantToolMoment(messages)?.key || null
|
|
134
|
-
setCurrentMoment(null)
|
|
135
|
-
}, [messages, sessionId])
|
|
136
|
-
|
|
137
|
-
useEffect(() => {
|
|
138
|
-
if (!sessionId || seededMomentSessionRef.current !== sessionId) return
|
|
113
|
+
const [trackedSessionId, setTrackedSessionId] = useState<string | null>(null)
|
|
114
|
+
const [trackedToolKey, setTrackedToolKey] = useState<string | null>(null)
|
|
115
|
+
|
|
116
|
+
// Render-time: respond to sessionId/messages changes (avoids set-state-in-effect)
|
|
117
|
+
if (sessionId !== trackedSessionId) {
|
|
118
|
+
setTrackedSessionId(sessionId)
|
|
119
|
+
const initialKey = sessionId
|
|
120
|
+
? getLatestAssistantToolMoment(messages)?.key || null
|
|
121
|
+
: null
|
|
122
|
+
setTrackedToolKey(initialKey)
|
|
123
|
+
if (currentMoment !== null) setCurrentMoment(null)
|
|
124
|
+
} else if (sessionId) {
|
|
139
125
|
const moment = getLatestAssistantToolMoment(messages)
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
126
|
+
if (moment && moment.key !== trackedToolKey) {
|
|
127
|
+
setTrackedToolKey(moment.key)
|
|
128
|
+
setCurrentMoment({ kind: 'tool', key: moment.key, name: moment.name, input: moment.input })
|
|
129
|
+
}
|
|
130
|
+
}
|
|
145
131
|
|
|
146
132
|
const momentOverlay = useMemo(() => {
|
|
147
133
|
if (streaming || !currentMoment) return null
|
|
@@ -161,13 +147,6 @@ function useLastMessageMoment(messages: Message[], sessionId: string | null, age
|
|
|
161
147
|
return { currentMoment, momentOverlay }
|
|
162
148
|
}
|
|
163
149
|
|
|
164
|
-
interface Props {
|
|
165
|
-
messages: Message[]
|
|
166
|
-
streaming: boolean
|
|
167
|
-
connectorFilter?: string | null
|
|
168
|
-
loading?: boolean
|
|
169
|
-
}
|
|
170
|
-
|
|
171
150
|
interface LiveThinkingLaneProps {
|
|
172
151
|
show: boolean
|
|
173
152
|
assistantName?: string
|
|
@@ -195,6 +174,13 @@ const LiveThinkingLane = memo(function LiveThinkingLane({
|
|
|
195
174
|
)
|
|
196
175
|
})
|
|
197
176
|
|
|
177
|
+
interface Props {
|
|
178
|
+
messages: Message[]
|
|
179
|
+
streaming: boolean
|
|
180
|
+
connectorFilter?: string | null
|
|
181
|
+
loading?: boolean
|
|
182
|
+
}
|
|
183
|
+
|
|
198
184
|
export function MessageList({ messages, streaming, connectorFilter = null, loading = false }: Props) {
|
|
199
185
|
const scrollRef = useRef<HTMLDivElement>(null)
|
|
200
186
|
const [showScrollToBottom, setShowScrollToBottom] = useState(false)
|
|
@@ -232,7 +218,7 @@ export function MessageList({ messages, streaming, connectorFilter = null, loadi
|
|
|
232
218
|
const showGatewayOverlay = isOpenClaw && gatewayStatus === 'disconnected'
|
|
233
219
|
|
|
234
220
|
// Moment overlay for last assistant message (heartbeat or tool events)
|
|
235
|
-
const {
|
|
221
|
+
const { momentOverlay: lastMomentOverlay } = useLastMessageMoment(messages, sessionId, agent?.id, streaming)
|
|
236
222
|
|
|
237
223
|
// Unread count tracking
|
|
238
224
|
const unreadRef = useRef(0)
|