@swarmclawai/swarmclaw 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -11
- package/bin/server-cmd.js +14 -7
- package/bin/swarmclaw.js +3 -1
- package/bin/update-cmd.js +120 -0
- package/next.config.ts +2 -0
- package/package.json +3 -1
- package/src/app/api/agents/[id]/route.ts +3 -0
- package/src/app/api/agents/[id]/thread/route.ts +2 -1
- package/src/app/api/agents/route.ts +5 -1
- package/src/app/api/auth/route.ts +3 -1
- package/src/app/api/claude-skills/route.ts +3 -1
- package/src/app/api/connectors/[id]/route.ts +4 -0
- package/src/app/api/connectors/route.ts +6 -1
- package/src/app/api/credentials/route.ts +3 -1
- package/src/app/api/daemon/route.ts +6 -1
- package/src/app/api/ip/route.ts +3 -1
- package/src/app/api/mcp-servers/route.ts +3 -1
- package/src/app/api/orchestrator/graph/route.ts +25 -0
- package/src/app/api/plugins/marketplace/route.ts +3 -1
- package/src/app/api/plugins/route.ts +3 -1
- package/src/app/api/providers/[id]/route.ts +3 -0
- package/src/app/api/providers/configs/route.ts +3 -1
- package/src/app/api/providers/route.ts +5 -1
- package/src/app/api/schedules/[id]/route.ts +3 -0
- package/src/app/api/schedules/route.ts +6 -1
- package/src/app/api/secrets/route.ts +3 -1
- package/src/app/api/sessions/[id]/chat/route.ts +5 -2
- package/src/app/api/sessions/route.ts +9 -2
- package/src/app/api/settings/route.ts +3 -1
- package/src/app/api/setup/doctor/route.ts +1 -0
- package/src/app/api/setup/openclaw-device/route.ts +3 -1
- package/src/app/api/skills/route.ts +3 -1
- package/src/app/api/tasks/[id]/approve/route.ts +73 -0
- package/src/app/api/tasks/[id]/route.ts +3 -0
- package/src/app/api/tasks/route.ts +3 -0
- package/src/app/api/usage/route.ts +3 -1
- package/src/app/api/version/route.ts +3 -1
- package/src/app/api/webhooks/[id]/route.ts +2 -1
- package/src/app/api/webhooks/route.ts +3 -1
- package/src/app/icon.svg +58 -0
- package/src/app/page.tsx +8 -2
- package/src/cli/index.js +1 -9
- package/src/cli/index.ts +51 -1
- package/src/cli/spec.js +0 -8
- package/src/components/agents/agent-card.tsx +1 -1
- package/src/components/agents/agent-sheet.tsx +63 -80
- package/src/components/chat/chat-area.tsx +44 -30
- package/src/components/chat/chat-tool-toggles.tsx +12 -53
- package/src/components/chat/message-bubble.tsx +110 -42
- package/src/components/chat/tool-call-bubble.tsx +41 -3
- package/src/components/chat/tool-request-banner.tsx +1 -9
- package/src/components/connectors/connector-list.tsx +3 -8
- package/src/components/connectors/connector-sheet.tsx +24 -29
- package/src/components/input/chat-input.tsx +72 -56
- package/src/components/knowledge/knowledge-list.tsx +27 -31
- package/src/components/layout/app-layout.tsx +92 -71
- package/src/components/layout/daemon-indicator.tsx +3 -5
- package/src/components/logs/log-list.tsx +5 -9
- package/src/components/mcp-servers/mcp-server-list.tsx +24 -2
- package/src/components/memory/memory-detail.tsx +1 -1
- package/src/components/plugins/plugin-list.tsx +227 -27
- package/src/components/providers/provider-list.tsx +46 -13
- package/src/components/providers/provider-sheet.tsx +0 -45
- package/src/components/runs/run-list.tsx +6 -15
- package/src/components/schedules/schedule-card.tsx +54 -4
- package/src/components/schedules/schedule-list.tsx +6 -3
- package/src/components/schedules/schedule-sheet.tsx +0 -47
- package/src/components/secrets/secrets-list.tsx +20 -2
- package/src/components/sessions/new-session-sheet.tsx +8 -9
- package/src/components/shared/connector-platform-icon.tsx +22 -20
- package/src/components/shared/model-combobox.tsx +148 -0
- package/src/components/shared/settings/section-heartbeat.tsx +7 -39
- package/src/components/shared/settings/section-orchestrator.tsx +8 -9
- package/src/components/skills/skill-list.tsx +260 -34
- package/src/components/skills/skill-sheet.tsx +0 -45
- package/src/components/tasks/task-board.tsx +3 -6
- package/src/components/tasks/task-card.tsx +43 -1
- package/src/components/tasks/task-list.tsx +3 -5
- package/src/components/tasks/task-sheet.tsx +0 -44
- package/src/components/usage/usage-list.tsx +12 -4
- package/src/hooks/use-ws.ts +66 -0
- package/src/instrumentation.ts +2 -0
- package/src/lib/chat.ts +14 -2
- package/src/lib/providers/anthropic.ts +1 -1
- package/src/lib/providers/index.ts +2 -0
- package/src/lib/providers/ollama.ts +1 -1
- package/src/lib/providers/openai.ts +33 -12
- package/src/lib/server/chat-execution.ts +19 -4
- package/src/lib/server/connectors/manager.ts +9 -3
- package/src/lib/server/context-manager.ts +1 -1
- package/src/lib/server/daemon-state.ts +3 -0
- package/src/lib/server/data-dir.ts +1 -0
- package/src/lib/server/heartbeat-service.ts +67 -3
- package/src/lib/server/langgraph-checkpoint.ts +274 -0
- package/src/lib/server/main-agent-loop.ts +61 -2
- package/src/lib/server/orchestrator-lg.ts +394 -13
- package/src/lib/server/orchestrator.ts +25 -5
- package/src/lib/server/queue.ts +17 -3
- package/src/lib/server/session-run-manager.ts +6 -1
- package/src/lib/server/session-tools/delegate.ts +2 -2
- package/src/lib/server/session-tools/index.ts +2 -0
- package/src/lib/server/session-tools/sandbox.ts +164 -0
- package/src/lib/server/storage-mcp.test.ts +25 -2
- package/src/lib/server/storage.ts +24 -7
- package/src/lib/server/stream-agent-chat.ts +77 -22
- package/src/lib/server/task-validation.test.ts +23 -0
- package/src/lib/server/task-validation.ts +5 -3
- package/src/lib/server/ws-hub.ts +85 -0
- package/src/lib/tool-definitions.ts +42 -0
- package/src/lib/upload.ts +7 -1
- package/src/lib/ws-client.ts +124 -0
- package/src/stores/use-chat-store.ts +33 -13
- package/src/types/index.ts +8 -1
- package/src/app/api/agents/generate/route.ts +0 -42
- package/src/app/api/generate/info/route.ts +0 -12
- package/src/app/api/generate/route.ts +0 -106
- package/src/app/favicon.ico +0 -0
- package/src/components/shared/ai-gen-block.tsx +0 -77
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { BaseCheckpointSaver } from '@langchain/langgraph'
|
|
2
|
+
import type { RunnableConfig } from '@langchain/core/runnables'
|
|
3
|
+
import type {
|
|
4
|
+
Checkpoint,
|
|
5
|
+
CheckpointTuple,
|
|
6
|
+
CheckpointListOptions,
|
|
7
|
+
} from '@langchain/langgraph-checkpoint'
|
|
8
|
+
import type {
|
|
9
|
+
CheckpointMetadata,
|
|
10
|
+
PendingWrite,
|
|
11
|
+
CheckpointPendingWrite,
|
|
12
|
+
} from '@langchain/langgraph-checkpoint'
|
|
13
|
+
import Database from 'better-sqlite3'
|
|
14
|
+
import path from 'path'
|
|
15
|
+
import { DATA_DIR } from './data-dir'
|
|
16
|
+
|
|
17
|
+
const DB_PATH = path.join(DATA_DIR, 'swarmclaw.db')
|
|
18
|
+
|
|
19
|
+
function getDb(): Database.Database {
|
|
20
|
+
const db = new Database(DB_PATH)
|
|
21
|
+
db.pragma('journal_mode = WAL')
|
|
22
|
+
return db
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Ensure tables exist
|
|
26
|
+
const initDb = getDb()
|
|
27
|
+
initDb.exec(`
|
|
28
|
+
CREATE TABLE IF NOT EXISTS langgraph_checkpoints (
|
|
29
|
+
thread_id TEXT NOT NULL,
|
|
30
|
+
checkpoint_ns TEXT NOT NULL DEFAULT '',
|
|
31
|
+
checkpoint_id TEXT NOT NULL,
|
|
32
|
+
parent_checkpoint_id TEXT,
|
|
33
|
+
type TEXT NOT NULL DEFAULT 'json',
|
|
34
|
+
checkpoint BLOB NOT NULL,
|
|
35
|
+
metadata BLOB NOT NULL DEFAULT '{}',
|
|
36
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
37
|
+
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id)
|
|
38
|
+
)
|
|
39
|
+
`)
|
|
40
|
+
initDb.exec(`
|
|
41
|
+
CREATE TABLE IF NOT EXISTS langgraph_writes (
|
|
42
|
+
thread_id TEXT NOT NULL,
|
|
43
|
+
checkpoint_ns TEXT NOT NULL DEFAULT '',
|
|
44
|
+
checkpoint_id TEXT NOT NULL,
|
|
45
|
+
task_id TEXT NOT NULL,
|
|
46
|
+
idx INTEGER NOT NULL,
|
|
47
|
+
channel TEXT NOT NULL,
|
|
48
|
+
type TEXT NOT NULL DEFAULT 'json',
|
|
49
|
+
value BLOB,
|
|
50
|
+
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx)
|
|
51
|
+
)
|
|
52
|
+
`)
|
|
53
|
+
initDb.close()
|
|
54
|
+
|
|
55
|
+
function getThreadId(config: RunnableConfig): string {
|
|
56
|
+
return (config.configurable?.thread_id as string) || ''
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getCheckpointNs(config: RunnableConfig): string {
|
|
60
|
+
return (config.configurable?.checkpoint_ns as string) || ''
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getCheckpointId(config: RunnableConfig): string | undefined {
|
|
64
|
+
return config.configurable?.checkpoint_id as string | undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class SqliteCheckpointSaver extends BaseCheckpointSaver {
|
|
68
|
+
private db: Database.Database
|
|
69
|
+
|
|
70
|
+
constructor() {
|
|
71
|
+
super()
|
|
72
|
+
this.db = getDb()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {
|
|
76
|
+
const threadId = getThreadId(config)
|
|
77
|
+
if (!threadId) return undefined
|
|
78
|
+
|
|
79
|
+
const ns = getCheckpointNs(config)
|
|
80
|
+
const checkpointId = getCheckpointId(config)
|
|
81
|
+
|
|
82
|
+
let row: any
|
|
83
|
+
if (checkpointId) {
|
|
84
|
+
row = this.db.prepare(
|
|
85
|
+
`SELECT * FROM langgraph_checkpoints WHERE thread_id = ? AND checkpoint_ns = ? AND checkpoint_id = ?`
|
|
86
|
+
).get(threadId, ns, checkpointId)
|
|
87
|
+
} else {
|
|
88
|
+
row = this.db.prepare(
|
|
89
|
+
`SELECT * FROM langgraph_checkpoints WHERE thread_id = ? AND checkpoint_ns = ? ORDER BY created_at DESC LIMIT 1`
|
|
90
|
+
).get(threadId, ns)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!row) return undefined
|
|
94
|
+
|
|
95
|
+
const checkpoint = JSON.parse(
|
|
96
|
+
typeof row.checkpoint === 'string' ? row.checkpoint : Buffer.from(row.checkpoint).toString()
|
|
97
|
+
) as Checkpoint
|
|
98
|
+
const metadata = JSON.parse(
|
|
99
|
+
typeof row.metadata === 'string' ? row.metadata : Buffer.from(row.metadata).toString()
|
|
100
|
+
) as CheckpointMetadata
|
|
101
|
+
|
|
102
|
+
const resultConfig: RunnableConfig = {
|
|
103
|
+
configurable: {
|
|
104
|
+
thread_id: threadId,
|
|
105
|
+
checkpoint_ns: ns,
|
|
106
|
+
checkpoint_id: row.checkpoint_id,
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const parentConfig = row.parent_checkpoint_id
|
|
111
|
+
? {
|
|
112
|
+
configurable: {
|
|
113
|
+
thread_id: threadId,
|
|
114
|
+
checkpoint_ns: ns,
|
|
115
|
+
checkpoint_id: row.parent_checkpoint_id,
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
: undefined
|
|
119
|
+
|
|
120
|
+
// Load pending writes
|
|
121
|
+
const writeRows = this.db.prepare(
|
|
122
|
+
`SELECT task_id, channel, value FROM langgraph_writes WHERE thread_id = ? AND checkpoint_ns = ? AND checkpoint_id = ? ORDER BY idx`
|
|
123
|
+
).all(threadId, ns, row.checkpoint_id) as any[]
|
|
124
|
+
|
|
125
|
+
const pendingWrites: CheckpointPendingWrite[] = writeRows.map((w) => [
|
|
126
|
+
w.task_id,
|
|
127
|
+
w.channel,
|
|
128
|
+
w.value ? JSON.parse(typeof w.value === 'string' ? w.value : Buffer.from(w.value).toString()) : undefined,
|
|
129
|
+
])
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
config: resultConfig,
|
|
133
|
+
checkpoint,
|
|
134
|
+
metadata,
|
|
135
|
+
parentConfig,
|
|
136
|
+
pendingWrites,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async *list(
|
|
141
|
+
config: RunnableConfig,
|
|
142
|
+
options?: CheckpointListOptions,
|
|
143
|
+
): AsyncGenerator<CheckpointTuple> {
|
|
144
|
+
const threadId = getThreadId(config)
|
|
145
|
+
if (!threadId) return
|
|
146
|
+
|
|
147
|
+
const ns = getCheckpointNs(config)
|
|
148
|
+
const limit = options?.limit ?? 100
|
|
149
|
+
|
|
150
|
+
let query = `SELECT * FROM langgraph_checkpoints WHERE thread_id = ? AND checkpoint_ns = ?`
|
|
151
|
+
const params: any[] = [threadId, ns]
|
|
152
|
+
|
|
153
|
+
if (options?.before?.configurable?.checkpoint_id) {
|
|
154
|
+
query += ` AND checkpoint_id < ?`
|
|
155
|
+
params.push(options.before.configurable.checkpoint_id)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
query += ` ORDER BY created_at DESC LIMIT ?`
|
|
159
|
+
params.push(limit)
|
|
160
|
+
|
|
161
|
+
const rows = this.db.prepare(query).all(...params) as any[]
|
|
162
|
+
|
|
163
|
+
for (const row of rows) {
|
|
164
|
+
const checkpoint = JSON.parse(
|
|
165
|
+
typeof row.checkpoint === 'string' ? row.checkpoint : Buffer.from(row.checkpoint).toString()
|
|
166
|
+
) as Checkpoint
|
|
167
|
+
const metadata = JSON.parse(
|
|
168
|
+
typeof row.metadata === 'string' ? row.metadata : Buffer.from(row.metadata).toString()
|
|
169
|
+
) as CheckpointMetadata
|
|
170
|
+
|
|
171
|
+
const resultConfig: RunnableConfig = {
|
|
172
|
+
configurable: {
|
|
173
|
+
thread_id: threadId,
|
|
174
|
+
checkpoint_ns: ns,
|
|
175
|
+
checkpoint_id: row.checkpoint_id,
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const parentConfig = row.parent_checkpoint_id
|
|
180
|
+
? {
|
|
181
|
+
configurable: {
|
|
182
|
+
thread_id: threadId,
|
|
183
|
+
checkpoint_ns: ns,
|
|
184
|
+
checkpoint_id: row.parent_checkpoint_id,
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
: undefined
|
|
188
|
+
|
|
189
|
+
yield {
|
|
190
|
+
config: resultConfig,
|
|
191
|
+
checkpoint,
|
|
192
|
+
metadata,
|
|
193
|
+
parentConfig,
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async put(
|
|
199
|
+
config: RunnableConfig,
|
|
200
|
+
checkpoint: Checkpoint,
|
|
201
|
+
metadata: CheckpointMetadata,
|
|
202
|
+
_newVersions: Record<string, number | string>,
|
|
203
|
+
): Promise<RunnableConfig> {
|
|
204
|
+
const threadId = getThreadId(config)
|
|
205
|
+
const ns = getCheckpointNs(config)
|
|
206
|
+
const parentId = getCheckpointId(config)
|
|
207
|
+
|
|
208
|
+
this.db.prepare(`
|
|
209
|
+
INSERT OR REPLACE INTO langgraph_checkpoints
|
|
210
|
+
(thread_id, checkpoint_ns, checkpoint_id, parent_checkpoint_id, checkpoint, metadata, created_at)
|
|
211
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
212
|
+
`).run(
|
|
213
|
+
threadId,
|
|
214
|
+
ns,
|
|
215
|
+
checkpoint.id,
|
|
216
|
+
parentId || null,
|
|
217
|
+
JSON.stringify(checkpoint),
|
|
218
|
+
JSON.stringify(metadata),
|
|
219
|
+
Date.now(),
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
configurable: {
|
|
224
|
+
thread_id: threadId,
|
|
225
|
+
checkpoint_ns: ns,
|
|
226
|
+
checkpoint_id: checkpoint.id,
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async putWrites(
|
|
232
|
+
config: RunnableConfig,
|
|
233
|
+
writes: PendingWrite[],
|
|
234
|
+
taskId: string,
|
|
235
|
+
): Promise<void> {
|
|
236
|
+
const threadId = getThreadId(config)
|
|
237
|
+
const ns = getCheckpointNs(config)
|
|
238
|
+
const checkpointId = getCheckpointId(config)
|
|
239
|
+
if (!checkpointId) return
|
|
240
|
+
|
|
241
|
+
const stmt = this.db.prepare(`
|
|
242
|
+
INSERT OR REPLACE INTO langgraph_writes
|
|
243
|
+
(thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, value)
|
|
244
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
245
|
+
`)
|
|
246
|
+
|
|
247
|
+
const insertMany = this.db.transaction((items: PendingWrite[]) => {
|
|
248
|
+
items.forEach(([channel, value], idx) => {
|
|
249
|
+
stmt.run(
|
|
250
|
+
threadId,
|
|
251
|
+
ns,
|
|
252
|
+
checkpointId,
|
|
253
|
+
taskId,
|
|
254
|
+
idx,
|
|
255
|
+
channel as string,
|
|
256
|
+
value !== undefined ? JSON.stringify(value) : null,
|
|
257
|
+
)
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
insertMany(writes)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async deleteThread(threadId: string): Promise<void> {
|
|
265
|
+
this.db.prepare(`DELETE FROM langgraph_checkpoints WHERE thread_id = ?`).run(threadId)
|
|
266
|
+
this.db.prepare(`DELETE FROM langgraph_writes WHERE thread_id = ?`).run(threadId)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let _saver: SqliteCheckpointSaver | undefined
|
|
271
|
+
export function getCheckpointSaver(): SqliteCheckpointSaver {
|
|
272
|
+
if (!_saver) _saver = new SqliteCheckpointSaver()
|
|
273
|
+
return _saver
|
|
274
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from 'crypto'
|
|
2
|
+
import { z } from 'zod'
|
|
2
3
|
import type { GoalContract, MessageToolEvent } from '@/types'
|
|
3
|
-
import { loadSessions, saveSessions, loadTasks, saveTasks } from './storage'
|
|
4
|
+
import { loadSessions, saveSessions, loadAgents, saveAgents, loadTasks, saveTasks } from './storage'
|
|
4
5
|
import { log } from './logger'
|
|
5
6
|
import { getMemoryDb } from './memory-db'
|
|
6
7
|
import {
|
|
@@ -18,6 +19,7 @@ const MEMORY_NOTE_MIN_INTERVAL_MS = 90 * 60 * 1000
|
|
|
18
19
|
const DEFAULT_FOLLOWUP_DELAY_SEC = 45
|
|
19
20
|
const MAX_FOLLOWUP_CHAIN = 6
|
|
20
21
|
const META_LINE_RE = /\[MAIN_LOOP_META\]\s*(\{[^\n]*\})/i
|
|
22
|
+
const AGENT_HEARTBEAT_META_RE = /\[AGENT_HEARTBEAT_META\]\s*(\{[^\n]*\})/i
|
|
21
23
|
const SCREENSHOT_GOAL_HINT = /\b(screenshot|screen shot|snapshot|capture)\b/i
|
|
22
24
|
const DELIVERY_GOAL_HINT = /\b(send|deliver|return|share|upload|post|message)\b/i
|
|
23
25
|
const SCHEDULE_GOAL_HINT = /\b(schedule|scheduled|every\s+\w+|interval|cron|recurr)\b/i
|
|
@@ -822,10 +824,67 @@ export function pushMainLoopEventToMainSessions(input: PushMainLoopEventInput):
|
|
|
822
824
|
return changed
|
|
823
825
|
}
|
|
824
826
|
|
|
827
|
+
const AgentHeartbeatMetaSchema = z.object({
|
|
828
|
+
goal: z.string().trim().optional(),
|
|
829
|
+
status: z.enum(['progress', 'ok', 'idle', 'blocked']).optional(),
|
|
830
|
+
next_action: z.string().trim().optional(),
|
|
831
|
+
}).passthrough()
|
|
832
|
+
|
|
833
|
+
type AgentHeartbeatMeta = z.infer<typeof AgentHeartbeatMetaSchema>
|
|
834
|
+
|
|
835
|
+
function parseAgentHeartbeatMeta(text: string): AgentHeartbeatMeta | null {
|
|
836
|
+
const raw = (text || '').trim()
|
|
837
|
+
if (!raw) return null
|
|
838
|
+
const match = raw.match(AGENT_HEARTBEAT_META_RE)
|
|
839
|
+
if (!match?.[1]) return null
|
|
840
|
+
try {
|
|
841
|
+
const parsed = JSON.parse(match[1])
|
|
842
|
+
return AgentHeartbeatMetaSchema.parse(parsed)
|
|
843
|
+
} catch {
|
|
844
|
+
return null
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function handleAgentHeartbeatResult(session: any, input: HandleMainLoopRunResultInput): null {
|
|
849
|
+
if (!input.internal || input.source !== 'heartbeat') return null
|
|
850
|
+
if (!session.agentId) return null
|
|
851
|
+
const text = input.resultText || ''
|
|
852
|
+
if (!text.trim()) return null
|
|
853
|
+
|
|
854
|
+
const meta = parseAgentHeartbeatMeta(text)
|
|
855
|
+
if (!meta) return null
|
|
856
|
+
|
|
857
|
+
const agents = loadAgents()
|
|
858
|
+
const agent = agents[session.agentId]
|
|
859
|
+
if (!agent) return null
|
|
860
|
+
|
|
861
|
+
let changed = false
|
|
862
|
+
if (meta.goal && meta.goal !== agent.heartbeatGoal) {
|
|
863
|
+
agent.heartbeatGoal = meta.goal
|
|
864
|
+
changed = true
|
|
865
|
+
log.info('agent-heartbeat', `Goal updated for agent ${agent.name}: ${meta.goal.slice(0, 120)}`)
|
|
866
|
+
}
|
|
867
|
+
if (meta.next_action) {
|
|
868
|
+
agent.heartbeatNextAction = meta.next_action
|
|
869
|
+
changed = true
|
|
870
|
+
}
|
|
871
|
+
if (meta.status) {
|
|
872
|
+
agent.heartbeatStatus = meta.status
|
|
873
|
+
changed = true
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (changed) {
|
|
877
|
+
agents[session.agentId] = agent
|
|
878
|
+
saveAgents(agents)
|
|
879
|
+
}
|
|
880
|
+
return null
|
|
881
|
+
}
|
|
882
|
+
|
|
825
883
|
export function handleMainLoopRunResult(input: HandleMainLoopRunResultInput): MainLoopFollowupRequest | null {
|
|
826
884
|
const sessions = loadSessions()
|
|
827
885
|
const session = sessions[input.sessionId]
|
|
828
|
-
if (!session
|
|
886
|
+
if (!session) return null
|
|
887
|
+
if (!isMainSession(session)) return handleAgentHeartbeatResult(session, input)
|
|
829
888
|
|
|
830
889
|
const now = Date.now()
|
|
831
890
|
const state = normalizeState(session.mainLoopState, now)
|