@swarmclawai/swarmclaw 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/package.json +5 -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]/deploy/route.ts +11 -6
- package/src/app/api/chats/[id]/devserver/route.ts +17 -20
- package/src/app/api/chats/[id]/messages/route.ts +15 -11
- 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/credentials/[id]/route.ts +4 -1
- package/src/app/api/extensions/marketplace/route.ts +5 -2
- package/src/app/api/ip/route.ts +2 -2
- package/src/app/api/memory/maintenance/route.ts +5 -2
- package/src/app/api/preview-server/route.ts +15 -12
- package/src/app/api/projects/[id]/route.ts +7 -46
- 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/chat-area.tsx +45 -23
- package/src/components/chat/message-bubble.test.ts +35 -0
- package/src/components/chat/message-bubble.tsx +20 -9
- package/src/components/chat/message-list.tsx +62 -42
- package/src/components/chat/swarm-status-card.tsx +10 -3
- package/src/components/input/chat-input.tsx +34 -14
- 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/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/chat.ts +18 -2
- 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/anthropic.ts +6 -3
- package/src/lib/providers/claude-cli.ts +9 -3
- package/src/lib/providers/cli-utils.test.ts +124 -0
- 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/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 +23 -4
- 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 +14 -6
- 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/autonomy/supervisor-reflection.ts +14 -1
- 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 -1914
- 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/continuation-evaluator.ts +4 -3
- package/src/lib/server/chat-execution/continuation-limits.ts +6 -3
- package/src/lib/server/chat-execution/message-classifier.test.ts +329 -0
- package/src/lib/server/chat-execution/message-classifier.ts +5 -2
- package/src/lib/server/chat-execution/post-stream-finalization.ts +5 -2
- package/src/lib/server/chat-execution/prompt-builder.ts +22 -1
- package/src/lib/server/chat-execution/prompt-sections.ts +55 -13
- package/src/lib/server/chat-execution/response-completeness.ts +5 -2
- package/src/lib/server/chat-execution/situational-awareness.ts +12 -7
- package/src/lib/server/chat-execution/stream-agent-chat.ts +58 -25
- package/src/lib/server/chatrooms/chatroom-memory-bridge.ts +6 -3
- package/src/lib/server/chatrooms/chatroom-repository.ts +32 -0
- 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/connector-repository.ts +58 -0
- 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.test.ts +117 -0
- 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/credentials/credential-repository.ts +7 -0
- package/src/lib/server/embeddings.ts +4 -1
- package/src/lib/server/gateways/gateway-profile-repository.ts +4 -0
- package/src/lib/server/link-understanding.ts +4 -1
- package/src/lib/server/memory/memory-abstract.test.ts +59 -0
- 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-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 -2263
- package/src/lib/server/openclaw/gateway.ts +8 -5
- 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/project-utils.ts +13 -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-agent-turn.ts +5 -2
- package/src/lib/server/protocols/protocol-normalization.test.ts +6 -4
- 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 +8 -7
- 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 -1331
- 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 +78 -34
- package/src/lib/server/runtime/heartbeat-wake.ts +6 -4
- package/src/lib/server/runtime/idle-window.ts +6 -3
- package/src/lib/server/runtime/network.ts +11 -0
- package/src/lib/server/runtime/orchestrator-events.ts +2 -2
- 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/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 -2058
- 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 +13 -8
- 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 -1374
- 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/sandbox/bridge-auth-registry.ts +6 -0
- package/src/lib/server/sandbox/novnc-auth.ts +10 -0
- package/src/lib/server/schedules/schedule-repository.ts +42 -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/sessions/session-repository.ts +85 -0
- package/src/lib/server/settings/settings-repository.ts +25 -0
- 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-discovery.test.ts +2 -2
- package/src/lib/server/skills/skill-discovery.ts +2 -2
- package/src/lib/server/skills/skill-eligibility.ts +6 -0
- package/src/lib/server/skills/skill-repository.ts +14 -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 +32 -32
- package/src/lib/server/tasks/task-followups.ts +4 -1
- package/src/lib/server/tasks/task-repository.ts +54 -0
- 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/usage/usage-repository.ts +30 -0
- package/src/lib/server/wallet/wallet-portfolio.ts +29 -0
- package/src/lib/server/webhooks/webhook-repository.ts +10 -0
- package/src/lib/server/ws-hub.ts +5 -2
- package/src/lib/strip-internal-metadata.test.ts +78 -37
- package/src/lib/strip-internal-metadata.ts +20 -6
- package/src/stores/use-approval-store.ts +7 -1
- package/src/stores/use-chat-store.test.ts +54 -0
- package/src/stores/use-chat-store.ts +26 -6
- package/src/types/index.ts +6 -0
- /package/{bundled-skills → skills}/google-workspace/SKILL.md +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { genId } from '@/lib/id'
|
|
2
2
|
import type { RunEventRecord, SessionRunRecord, SessionRunStatus, SSEEvent } from '@/types'
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
deleteRuntimeRun,
|
|
5
|
+
deleteRuntimeRunEvent,
|
|
5
6
|
loadRuntimeRun,
|
|
6
7
|
loadRuntimeRunEvents,
|
|
7
8
|
loadRuntimeRunEventsByRunId,
|
|
@@ -9,7 +10,7 @@ import {
|
|
|
9
10
|
patchRuntimeRun,
|
|
10
11
|
upsertRuntimeRun,
|
|
11
12
|
upsertRuntimeRunEvent,
|
|
12
|
-
} from '@/lib/server/
|
|
13
|
+
} from '@/lib/server/runtime/run-repository'
|
|
13
14
|
|
|
14
15
|
const MAX_SUMMARY_CHARS = 240
|
|
15
16
|
const RESTART_RECOVERABLE_SOURCES = new Set([
|
|
@@ -137,7 +138,7 @@ export function pruneOldRuns(): { prunedRuns: number; prunedEvents: number } {
|
|
|
137
138
|
// Non-terminal (running/queued) — only prune if stuck for much longer
|
|
138
139
|
if (deadline - endTs < ORPHANED_RUN_RETENTION_MS) continue
|
|
139
140
|
}
|
|
140
|
-
|
|
141
|
+
deleteRuntimeRun(id)
|
|
141
142
|
prunedRunIds.add(id)
|
|
142
143
|
prunedRuns++
|
|
143
144
|
}
|
|
@@ -146,7 +147,7 @@ export function pruneOldRuns(): { prunedRuns: number; prunedEvents: number } {
|
|
|
146
147
|
const events = loadRuntimeRunEvents()
|
|
147
148
|
for (const [id, event] of Object.entries(events)) {
|
|
148
149
|
if (prunedRunIds.has(event.runId)) {
|
|
149
|
-
|
|
150
|
+
deleteRuntimeRunEvent(id)
|
|
150
151
|
prunedEvents++
|
|
151
152
|
continue
|
|
152
153
|
}
|
|
@@ -154,7 +155,7 @@ export function pruneOldRuns(): { prunedRuns: number; prunedEvents: number } {
|
|
|
154
155
|
const parentRun = runs[event.runId]
|
|
155
156
|
if (!parentRun || !TERMINAL_STATUSES.has(parentRun.status)) continue
|
|
156
157
|
if (deadline - event.timestamp < RUN_EVENT_RETENTION_MS) continue
|
|
157
|
-
|
|
158
|
+
deleteRuntimeRunEvent(id)
|
|
158
159
|
prunedEvents++
|
|
159
160
|
}
|
|
160
161
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { RunEventRecord, SessionRunRecord } from '@/types'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
deleteStoredItem,
|
|
5
|
+
loadRuntimeRun as loadStoredRuntimeRun,
|
|
6
|
+
loadRuntimeRunEvents as loadStoredRuntimeRunEvents,
|
|
7
|
+
loadRuntimeRunEventsByRunId as loadStoredRuntimeRunEventsByRunId,
|
|
8
|
+
loadRuntimeRuns as loadStoredRuntimeRuns,
|
|
9
|
+
patchRuntimeRun as patchStoredRuntimeRun,
|
|
10
|
+
saveRuntimeRunEvents as saveStoredRuntimeRunEvents,
|
|
11
|
+
saveRuntimeRuns as saveStoredRuntimeRuns,
|
|
12
|
+
upsertRuntimeRun as upsertStoredRuntimeRun,
|
|
13
|
+
upsertRuntimeRunEvent as upsertStoredRuntimeRunEvent,
|
|
14
|
+
} from '@/lib/server/storage'
|
|
15
|
+
import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
|
|
16
|
+
|
|
17
|
+
export const runRepository = createRecordRepository<SessionRunRecord>(
|
|
18
|
+
'runtime-runs',
|
|
19
|
+
{
|
|
20
|
+
get(id) {
|
|
21
|
+
return loadStoredRuntimeRun(id) as SessionRunRecord | null
|
|
22
|
+
},
|
|
23
|
+
list() {
|
|
24
|
+
return loadStoredRuntimeRuns() as Record<string, SessionRunRecord>
|
|
25
|
+
},
|
|
26
|
+
upsert(id, value) {
|
|
27
|
+
upsertStoredRuntimeRun(id, value as SessionRunRecord)
|
|
28
|
+
},
|
|
29
|
+
replace(data) {
|
|
30
|
+
saveStoredRuntimeRuns(data as Record<string, SessionRunRecord>)
|
|
31
|
+
},
|
|
32
|
+
patch(id, updater) {
|
|
33
|
+
return patchStoredRuntimeRun(id, updater as (current: SessionRunRecord | null) => SessionRunRecord | null) as SessionRunRecord | null
|
|
34
|
+
},
|
|
35
|
+
delete(id) {
|
|
36
|
+
deleteStoredItem('runtime_runs', id)
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
export const runEventRepository = createRecordRepository<RunEventRecord>(
|
|
42
|
+
'runtime-run-events',
|
|
43
|
+
{
|
|
44
|
+
get(id) {
|
|
45
|
+
return (loadStoredRuntimeRunEvents() as Record<string, RunEventRecord>)[id] || null
|
|
46
|
+
},
|
|
47
|
+
list() {
|
|
48
|
+
return loadStoredRuntimeRunEvents() as Record<string, RunEventRecord>
|
|
49
|
+
},
|
|
50
|
+
upsert(id, value) {
|
|
51
|
+
upsertStoredRuntimeRunEvent(id, value as RunEventRecord)
|
|
52
|
+
},
|
|
53
|
+
replace(data) {
|
|
54
|
+
saveStoredRuntimeRunEvents(data as Record<string, RunEventRecord>)
|
|
55
|
+
},
|
|
56
|
+
delete(id) {
|
|
57
|
+
deleteStoredItem('runtime_run_events', id)
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
export const loadRuntimeRuns = () => runRepository.list()
|
|
63
|
+
export const saveRuntimeRuns = (items: Record<string, SessionRunRecord | Record<string, unknown>>) => runRepository.replace(items as Record<string, SessionRunRecord>)
|
|
64
|
+
export const loadRuntimeRun = (id: string) => runRepository.get(id)
|
|
65
|
+
export const upsertRuntimeRun = (id: string, value: SessionRunRecord | Record<string, unknown>) => runRepository.upsert(id, value as SessionRunRecord)
|
|
66
|
+
export const patchRuntimeRun = (id: string, updater: (current: SessionRunRecord | null) => SessionRunRecord | null) => runRepository.patch(id, updater)
|
|
67
|
+
|
|
68
|
+
export const loadRuntimeRunEvents = () => runEventRepository.list()
|
|
69
|
+
export const saveRuntimeRunEvents = (items: Record<string, RunEventRecord | Record<string, unknown>>) => runEventRepository.replace(items as Record<string, RunEventRecord>)
|
|
70
|
+
export const upsertRuntimeRunEvent = (id: string, value: RunEventRecord | Record<string, unknown>) => runEventRepository.upsert(id, value as RunEventRecord)
|
|
71
|
+
export const loadRuntimeRunEventsByRunId = (runId: string) => loadStoredRuntimeRunEventsByRunId(runId)
|
|
72
|
+
export const deleteRuntimeRun = (id: string) => runRepository.delete(id)
|
|
73
|
+
export const deleteRuntimeRunEvent = (id: string) => runEventRepository.delete(id)
|
|
@@ -2,7 +2,7 @@ import type { LoopMode } from '@/types'
|
|
|
2
2
|
import {
|
|
3
3
|
normalizeRuntimeSettingFields,
|
|
4
4
|
} from '@/lib/runtime/runtime-loop'
|
|
5
|
-
import { loadSettings } from '@/lib/server/
|
|
5
|
+
import { loadSettings } from '@/lib/server/settings/settings-repository'
|
|
6
6
|
|
|
7
7
|
export interface RuntimeSettings {
|
|
8
8
|
loopMode: LoopMode
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { ChildProcess } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
import { hmrSingleton } from '@/lib/shared-utils'
|
|
4
|
+
|
|
5
|
+
export type ActiveSessionProcess = {
|
|
6
|
+
runId?: string | null
|
|
7
|
+
source?: string
|
|
8
|
+
kill: (signal?: NodeJS.Signals | number) => boolean | void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface DevServerRuntime {
|
|
12
|
+
proc: ChildProcess
|
|
13
|
+
url: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface RuntimeStateRegistry {
|
|
17
|
+
activeSessionProcesses: Map<string, ActiveSessionProcess>
|
|
18
|
+
devServers: Map<string, DevServerRuntime>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const state = hmrSingleton<RuntimeStateRegistry>('__swarmclaw_runtime_state__', () => ({
|
|
22
|
+
activeSessionProcesses: new Map<string, ActiveSessionProcess>(),
|
|
23
|
+
devServers: new Map<string, DevServerRuntime>(),
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
if (!state.activeSessionProcesses) state.activeSessionProcesses = new Map<string, ActiveSessionProcess>()
|
|
27
|
+
if (!state.devServers) state.devServers = new Map<string, DevServerRuntime>()
|
|
28
|
+
|
|
29
|
+
export const activeSessionProcesses = state.activeSessionProcesses
|
|
30
|
+
export const devServers = state.devServers
|
|
31
|
+
|
|
32
|
+
export function getActiveSessionProcess(sessionId: string): ActiveSessionProcess | undefined {
|
|
33
|
+
return state.activeSessionProcesses.get(sessionId)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function hasActiveSessionProcess(sessionId: string): boolean {
|
|
37
|
+
return state.activeSessionProcesses.has(sessionId)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function registerActiveSessionProcess(sessionId: string, process: ActiveSessionProcess): void {
|
|
41
|
+
state.activeSessionProcesses.set(sessionId, process)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function stopActiveSessionProcess(sessionId: string, signal?: NodeJS.Signals | number): boolean {
|
|
45
|
+
const process = state.activeSessionProcesses.get(sessionId)
|
|
46
|
+
if (!process) return false
|
|
47
|
+
try {
|
|
48
|
+
process.kill(signal)
|
|
49
|
+
} catch {
|
|
50
|
+
// Ignore process teardown errors during cleanup.
|
|
51
|
+
}
|
|
52
|
+
state.activeSessionProcesses.delete(sessionId)
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function clearActiveSessionProcess(sessionId: string): void {
|
|
57
|
+
state.activeSessionProcesses.delete(sessionId)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getDevServer(sessionId: string): DevServerRuntime | undefined {
|
|
61
|
+
return state.devServers.get(sessionId)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function hasDevServer(sessionId: string): boolean {
|
|
65
|
+
return state.devServers.has(sessionId)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function registerDevServer(sessionId: string, runtime: DevServerRuntime): void {
|
|
69
|
+
state.devServers.set(sessionId, runtime)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function updateDevServerUrl(sessionId: string, url: string): void {
|
|
73
|
+
const runtime = state.devServers.get(sessionId)
|
|
74
|
+
if (!runtime) return
|
|
75
|
+
runtime.url = url
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function stopDevServer(sessionId: string): boolean {
|
|
79
|
+
const runtime = state.devServers.get(sessionId)
|
|
80
|
+
if (!runtime) return false
|
|
81
|
+
try {
|
|
82
|
+
runtime.proc.kill('SIGTERM')
|
|
83
|
+
} catch {
|
|
84
|
+
// Ignore process teardown errors during cleanup.
|
|
85
|
+
}
|
|
86
|
+
if (typeof runtime.proc.pid === 'number') {
|
|
87
|
+
try {
|
|
88
|
+
process.kill(-runtime.proc.pid, 'SIGTERM')
|
|
89
|
+
} catch {
|
|
90
|
+
// Ignore process-group teardown errors when the child is already gone.
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
state.devServers.delete(sessionId)
|
|
94
|
+
return true
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function clearDevServer(sessionId: string): void {
|
|
98
|
+
state.devServers.delete(sessionId)
|
|
99
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { listAgents } from '@/lib/server/agents/agent-repository'
|
|
2
|
+
import { loadSchedules, upsertSchedule, upsertSchedules } from '@/lib/server/schedules/schedule-repository'
|
|
3
|
+
import { loadTasks, upsertTask } from '@/lib/server/tasks/task-repository'
|
|
2
4
|
import { enqueueTask } from '@/lib/server/runtime/queue'
|
|
3
5
|
import { CronExpressionParser } from 'cron-parser'
|
|
4
6
|
import { pushMainLoopEventToMainSessions } from '@/lib/server/agents/main-agent-loop'
|
|
@@ -11,8 +13,11 @@ import { ensureAgentThreadSession } from '@/lib/server/agents/agent-thread-sessi
|
|
|
11
13
|
import { ensureMissionForTask, noteScheduleMissionTriggered } from '@/lib/server/missions/mission-service'
|
|
12
14
|
import { hasActiveProtocolRunForSchedule, launchProtocolRunForSchedule } from '@/lib/server/protocols/protocol-service'
|
|
13
15
|
import { hmrSingleton } from '@/lib/shared-utils'
|
|
16
|
+
import { log } from '@/lib/server/logger'
|
|
14
17
|
import type { Schedule } from '@/types'
|
|
15
18
|
|
|
19
|
+
const TAG = 'scheduler'
|
|
20
|
+
|
|
16
21
|
const TICK_INTERVAL = 60_000 // 60 seconds
|
|
17
22
|
const schedulerState = hmrSingleton('__swarmclaw_scheduler_state__', () => ({
|
|
18
23
|
intervalId: null as ReturnType<typeof setInterval> | null,
|
|
@@ -45,7 +50,7 @@ function shouldLaunchScheduleProtocol(schedule: Schedule): boolean {
|
|
|
45
50
|
|
|
46
51
|
export function startScheduler() {
|
|
47
52
|
if (schedulerState.intervalId) return
|
|
48
|
-
|
|
53
|
+
log.info(TAG, 'Starting scheduler engine (60s tick)')
|
|
49
54
|
|
|
50
55
|
// Compute initial nextRunAt for cron schedules missing it
|
|
51
56
|
computeNextRuns()
|
|
@@ -57,7 +62,7 @@ export function stopScheduler() {
|
|
|
57
62
|
if (schedulerState.intervalId) {
|
|
58
63
|
clearInterval(schedulerState.intervalId)
|
|
59
64
|
schedulerState.intervalId = null
|
|
60
|
-
|
|
65
|
+
log.info(TAG, 'Stopped scheduler engine')
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
|
|
@@ -75,7 +80,7 @@ function computeNextRuns() {
|
|
|
75
80
|
schedule.nextRunAt = interval.next().getTime()
|
|
76
81
|
changedEntries.push([schedule.id, schedule])
|
|
77
82
|
} catch (err) {
|
|
78
|
-
|
|
83
|
+
log.error(TAG, `Invalid cron for ${schedule.id}:`, err)
|
|
79
84
|
schedule.status = 'failed'
|
|
80
85
|
changedEntries.push([schedule.id, schedule])
|
|
81
86
|
}
|
|
@@ -87,7 +92,7 @@ function computeNextRuns() {
|
|
|
87
92
|
async function tick(now = Date.now()) {
|
|
88
93
|
await processDueWatchJobs(now)
|
|
89
94
|
const schedules = loadSchedules()
|
|
90
|
-
const agents =
|
|
95
|
+
const agents = listAgents()
|
|
91
96
|
const tasks = loadTasks()
|
|
92
97
|
const inFlightScheduleKeys = new Set<string>(
|
|
93
98
|
Object.values(tasks as Record<string, ScheduleTaskLike>)
|
|
@@ -133,7 +138,7 @@ async function tick(now = Date.now()) {
|
|
|
133
138
|
|
|
134
139
|
const agent = agents[schedule.agentId]
|
|
135
140
|
if (!agent) {
|
|
136
|
-
|
|
141
|
+
log.error(TAG, `Agent ${schedule.agentId} not found for schedule ${schedule.id}`)
|
|
137
142
|
schedule.status = 'failed'
|
|
138
143
|
upsertSchedule(schedule.id, schedule)
|
|
139
144
|
pushMainLoopEventToMainSessions({
|
|
@@ -143,7 +148,7 @@ async function tick(now = Date.now()) {
|
|
|
143
148
|
continue
|
|
144
149
|
}
|
|
145
150
|
if (isAgentDisabled(agent)) {
|
|
146
|
-
|
|
151
|
+
log.warn(TAG, `Skipping schedule "${schedule.name}" (${schedule.id}) because agent ${schedule.agentId} is disabled`)
|
|
147
152
|
advanceSchedule(schedule)
|
|
148
153
|
upsertSchedule(schedule.id, schedule)
|
|
149
154
|
pushMainLoopEventToMainSessions({
|
|
@@ -153,7 +158,7 @@ async function tick(now = Date.now()) {
|
|
|
153
158
|
continue
|
|
154
159
|
}
|
|
155
160
|
|
|
156
|
-
|
|
161
|
+
log.info(TAG, `Firing schedule "${schedule.name}" (${schedule.id})`)
|
|
157
162
|
schedule.lastRunAt = now
|
|
158
163
|
schedule.runNumber = (schedule.runNumber || 0) + 1
|
|
159
164
|
// Compute next run
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { isInternalHeartbeatRun } from '@/lib/server/runtime/heartbeat-source'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
abortSessionRuntime,
|
|
5
|
+
decrementNonHeartbeatWork,
|
|
6
|
+
emitRunMeta,
|
|
7
|
+
now,
|
|
8
|
+
reconcileSessionActivityLease,
|
|
9
|
+
state,
|
|
10
|
+
syncRunRecord,
|
|
11
|
+
} from './state'
|
|
12
|
+
import type { SessionRunQueueEntry } from './types'
|
|
13
|
+
|
|
14
|
+
export function cancelPendingForSession(sessionId: string, reason: string): number {
|
|
15
|
+
let cancelled = 0
|
|
16
|
+
for (const [key, queue] of state.queueByExecution.entries()) {
|
|
17
|
+
if (!queue.length) continue
|
|
18
|
+
const keep: SessionRunQueueEntry[] = []
|
|
19
|
+
for (const entry of queue) {
|
|
20
|
+
if (entry.run.sessionId !== sessionId) {
|
|
21
|
+
keep.push(entry)
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
24
|
+
entry.run.status = 'cancelled'
|
|
25
|
+
entry.run.endedAt = now()
|
|
26
|
+
entry.run.error = reason
|
|
27
|
+
syncRunRecord(entry.run)
|
|
28
|
+
emitRunMeta(entry, 'cancelled', { reason })
|
|
29
|
+
entry.reject(new Error(reason))
|
|
30
|
+
decrementNonHeartbeatWork(entry)
|
|
31
|
+
cancelled += 1
|
|
32
|
+
}
|
|
33
|
+
if (keep.length > 0) state.queueByExecution.set(key, keep)
|
|
34
|
+
else state.queueByExecution.delete(key)
|
|
35
|
+
}
|
|
36
|
+
reconcileSessionActivityLease(sessionId)
|
|
37
|
+
return cancelled
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function cancelQueuedEntries(
|
|
41
|
+
matcher: (entry: SessionRunQueueEntry) => boolean,
|
|
42
|
+
reason: string,
|
|
43
|
+
): { cancelled: number; sessionIds: Set<string> } {
|
|
44
|
+
let cancelled = 0
|
|
45
|
+
const sessionIds = new Set<string>()
|
|
46
|
+
for (const [key, queue] of state.queueByExecution.entries()) {
|
|
47
|
+
if (!queue.length) continue
|
|
48
|
+
const keep: SessionRunQueueEntry[] = []
|
|
49
|
+
for (const entry of queue) {
|
|
50
|
+
if (!matcher(entry)) {
|
|
51
|
+
keep.push(entry)
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
entry.run.status = 'cancelled'
|
|
55
|
+
entry.run.endedAt = now()
|
|
56
|
+
entry.run.error = reason
|
|
57
|
+
syncRunRecord(entry.run)
|
|
58
|
+
emitRunMeta(entry, 'cancelled', { reason })
|
|
59
|
+
entry.reject(new Error(reason))
|
|
60
|
+
decrementNonHeartbeatWork(entry)
|
|
61
|
+
sessionIds.add(entry.run.sessionId)
|
|
62
|
+
cancelled += 1
|
|
63
|
+
}
|
|
64
|
+
if (keep.length > 0) state.queueByExecution.set(key, keep)
|
|
65
|
+
else state.queueByExecution.delete(key)
|
|
66
|
+
}
|
|
67
|
+
for (const sessionId of sessionIds) reconcileSessionActivityLease(sessionId)
|
|
68
|
+
return { cancelled, sessionIds }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function cancelAllHeartbeatRuns(reason = 'Heartbeat disabled globally'): { cancelledQueued: number; abortedRunning: number } {
|
|
72
|
+
let cancelledQueued = 0
|
|
73
|
+
let abortedRunning = 0
|
|
74
|
+
|
|
75
|
+
for (const [key, queue] of state.queueByExecution.entries()) {
|
|
76
|
+
if (!queue.length) continue
|
|
77
|
+
const keep: SessionRunQueueEntry[] = []
|
|
78
|
+
for (const entry of queue) {
|
|
79
|
+
const isHeartbeat = isInternalHeartbeatRun(entry.run.internal, entry.run.source)
|
|
80
|
+
if (!isHeartbeat) {
|
|
81
|
+
keep.push(entry)
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
entry.run.status = 'cancelled'
|
|
85
|
+
entry.run.endedAt = now()
|
|
86
|
+
entry.run.error = reason
|
|
87
|
+
syncRunRecord(entry.run)
|
|
88
|
+
emitRunMeta(entry, 'cancelled', { reason })
|
|
89
|
+
entry.reject(new Error(reason))
|
|
90
|
+
cancelledQueued += 1
|
|
91
|
+
}
|
|
92
|
+
if (keep.length > 0) state.queueByExecution.set(key, keep)
|
|
93
|
+
else state.queueByExecution.delete(key)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const entry of state.runningByExecution.values()) {
|
|
97
|
+
const isHeartbeat = isInternalHeartbeatRun(entry.run.internal, entry.run.source)
|
|
98
|
+
if (!isHeartbeat) continue
|
|
99
|
+
abortedRunning += 1
|
|
100
|
+
abortSessionRuntime(entry, reason)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { cancelledQueued, abortedRunning }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function cancelAllRuns(reason = 'Cancelled'): { cancelledQueued: number; abortedRunning: number } {
|
|
107
|
+
let cancelledQueued = 0
|
|
108
|
+
let abortedRunning = 0
|
|
109
|
+
|
|
110
|
+
for (const [key, queue] of state.queueByExecution.entries()) {
|
|
111
|
+
if (!queue.length) continue
|
|
112
|
+
for (const entry of queue) {
|
|
113
|
+
entry.run.status = 'cancelled'
|
|
114
|
+
entry.run.endedAt = now()
|
|
115
|
+
entry.run.error = reason
|
|
116
|
+
syncRunRecord(entry.run)
|
|
117
|
+
emitRunMeta(entry, 'cancelled', { reason })
|
|
118
|
+
entry.reject(new Error(reason))
|
|
119
|
+
cancelledQueued += 1
|
|
120
|
+
}
|
|
121
|
+
state.queueByExecution.delete(key)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const entry of state.runningByExecution.values()) {
|
|
125
|
+
abortedRunning += 1
|
|
126
|
+
abortSessionRuntime(entry, reason)
|
|
127
|
+
}
|
|
128
|
+
state.runningByExecution.clear()
|
|
129
|
+
state.nonHeartbeatWorkCount.clear()
|
|
130
|
+
|
|
131
|
+
return { cancelledQueued, abortedRunning }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function cancelQueuedRunById(runId: string, reason = 'Removed from queue'): boolean {
|
|
135
|
+
const result = cancelQueuedEntries((entry) => entry.run.id === runId, reason)
|
|
136
|
+
return result.cancelled > 0
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function cancelQueuedRunsForSession(sessionId: string, reason = 'Cleared queued messages'): number {
|
|
140
|
+
const result = cancelQueuedEntries((entry) => entry.run.sessionId === sessionId, reason)
|
|
141
|
+
return result.cancelled
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function cancelSessionRuns(sessionId: string, reason = 'Cancelled'): { cancelledQueued: number; cancelledRunning: boolean } {
|
|
145
|
+
const running = Array.from(state.runningByExecution.values())
|
|
146
|
+
.find((entry) => entry.run.sessionId === sessionId)
|
|
147
|
+
let cancelledRunning = false
|
|
148
|
+
if (running) {
|
|
149
|
+
cancelledRunning = true
|
|
150
|
+
abortSessionRuntime(running, reason)
|
|
151
|
+
state.runningByExecution.delete(running.executionKey)
|
|
152
|
+
decrementNonHeartbeatWork(running)
|
|
153
|
+
}
|
|
154
|
+
const cancelledQueued = cancelPendingForSession(sessionId, reason)
|
|
155
|
+
reconcileSessionActivityLease(sessionId)
|
|
156
|
+
return { cancelledQueued, cancelledRunning }
|
|
157
|
+
}
|