@swarmclawai/swarmclaw 1.2.8 → 1.3.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 +39 -6
- package/package.json +2 -2
- package/src/app/agents/[id]/page.tsx +1 -18
- package/src/app/api/activity/route.ts +9 -23
- package/src/app/api/agents/route.ts +17 -1
- package/src/app/api/agents/thread-route.test.ts +0 -1
- package/src/app/api/approvals/route.test.ts +6 -22
- package/src/app/api/approvals/route.ts +13 -5
- package/src/app/api/connectors/route.ts +2 -2
- package/src/app/api/credentials/[id]/route.ts +2 -0
- package/src/app/api/credentials/route.ts +4 -1
- package/src/app/api/goals/[id]/route.ts +28 -0
- package/src/app/api/goals/route.ts +33 -0
- package/src/app/api/portability/export/route.ts +8 -0
- package/src/app/api/portability/import/route.test.ts +80 -0
- package/src/app/api/portability/import/route.ts +28 -0
- package/src/app/api/protocols/templates/[id]/route.ts +2 -1
- package/src/app/api/protocols/templates/route.ts +2 -1
- package/src/app/api/settings/route.ts +13 -2
- package/src/app/api/wallets/[id]/route.ts +15 -157
- package/src/app/api/wallets/generate/route.ts +22 -0
- package/src/app/api/wallets/route.test.ts +147 -0
- package/src/app/api/wallets/route.ts +13 -95
- package/src/app/autonomy/page.tsx +2 -57
- package/src/app/home/page.tsx +3 -0
- package/src/app/protocols/page.tsx +2 -21
- package/src/app/settings/page.tsx +0 -9
- package/src/app/wallets/page.tsx +105 -5
- package/src/cli/index.js +32 -33
- package/src/cli/spec.js +26 -27
- package/src/components/agents/agent-sheet.tsx +2 -40
- package/src/components/agents/inspector-panel.tsx +0 -83
- package/src/components/chat/chat-card.tsx +0 -31
- package/src/components/chat/message-bubble.tsx +1 -108
- package/src/components/connectors/connector-sheet.tsx +25 -1
- package/src/components/layout/sidebar-rail.tsx +6 -10
- package/src/components/projects/project-detail.tsx +3 -35
- package/src/components/projects/tabs/overview-tab.tsx +3 -59
- package/src/components/projects/tabs/work-tab.tsx +7 -77
- package/src/components/protocols/structured-session-launcher.tsx +1 -22
- package/src/components/shared/connector-platform-icon.tsx +1 -0
- package/src/components/tasks/task-card.tsx +4 -34
- package/src/components/tasks/task-sheet.tsx +6 -36
- package/src/components/wallets/wallet-list.tsx +150 -0
- package/src/lib/app/navigation.test.ts +0 -13
- package/src/lib/app/navigation.ts +2 -7
- package/src/lib/app/view-constants.ts +14 -19
- package/src/lib/server/activity/activity-log.ts +16 -1
- package/src/lib/server/agents/agent-service.ts +24 -11
- package/src/lib/server/agents/agent-thread-session.ts +0 -1
- package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
- package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
- package/src/lib/server/agents/delegation-jobs.ts +0 -25
- package/src/lib/server/agents/main-agent-loop.ts +1 -49
- package/src/lib/server/agents/subagent-runtime.ts +0 -1
- package/src/lib/server/approval-match.ts +14 -85
- package/src/lib/server/approvals/approval-hooks.ts +81 -0
- package/src/lib/server/approvals.test.ts +6 -6
- package/src/lib/server/approvals.ts +11 -6
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
- package/src/lib/server/builtin-extensions.ts +0 -2
- package/src/lib/server/capability-router.test.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +14 -14
- package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -2
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +2 -22
- package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
- package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
- package/src/lib/server/chat-execution/message-classifier.ts +1 -16
- package/src/lib/server/chat-execution/prompt-builder.test.ts +0 -1
- package/src/lib/server/chat-execution/prompt-builder.ts +0 -30
- package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
- package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
- package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
- package/src/lib/server/chat-execution/stream-agent-chat.test.ts +8 -123
- package/src/lib/server/chat-execution/stream-agent-chat.ts +1 -5
- package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
- package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
- package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
- package/src/lib/server/chats/chat-session-service.ts +3 -5
- package/src/lib/server/connectors/connector-inbound.ts +0 -1
- package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
- package/src/lib/server/connectors/connector-service.ts +39 -9
- package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
- package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
- package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
- package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
- package/src/lib/server/connectors/swarmdock-tasks.ts +127 -0
- package/src/lib/server/connectors/swarmdock.ts +285 -0
- package/src/lib/server/execution-brief.test.ts +2 -25
- package/src/lib/server/execution-brief.ts +30 -35
- package/src/lib/server/execution-engine/task-attempt.ts +0 -1
- package/src/lib/server/goals/goal-repository.ts +19 -0
- package/src/lib/server/goals/goal-service.ts +143 -0
- package/src/lib/server/persistence/storage-context.ts +0 -5
- package/src/lib/server/portability/export.ts +109 -0
- package/src/lib/server/portability/import.ts +159 -0
- package/src/lib/server/protocols/protocol-normalization.ts +0 -4
- package/src/lib/server/protocols/protocol-queries.ts +0 -6
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
- package/src/lib/server/protocols/protocol-service.ts +0 -1
- package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
- package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
- package/src/lib/server/protocols/protocol-swarm.ts +0 -2
- package/src/lib/server/protocols/protocol-types.ts +0 -2
- package/src/lib/server/provider-health.ts +0 -9
- package/src/lib/server/runtime/daemon-state/core.ts +0 -9
- package/src/lib/server/runtime/daemon-state.test.ts +0 -35
- package/src/lib/server/runtime/heartbeat-service.ts +3 -23
- package/src/lib/server/runtime/queue/core.ts +11 -33
- package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
- package/src/lib/server/runtime/scheduler.ts +0 -13
- package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
- package/src/lib/server/runtime/session-run-manager/queries.ts +0 -1
- package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
- package/src/lib/server/runtime/session-run-manager.test.ts +0 -28
- package/src/lib/server/session-tools/crud.ts +0 -14
- package/src/lib/server/session-tools/delegate.ts +0 -4
- package/src/lib/server/session-tools/index.ts +0 -4
- package/src/lib/server/session-tools/team-context.ts +0 -3
- package/src/lib/server/storage-normalization.ts +13 -0
- package/src/lib/server/storage.ts +75 -45
- package/src/lib/server/tasks/task-checkout.ts +59 -0
- package/src/lib/server/tasks/task-lifecycle.ts +2 -0
- package/src/lib/server/tasks/task-route-service.ts +4 -26
- package/src/lib/server/tasks/task-service.ts +0 -7
- package/src/lib/server/tool-aliases.ts +0 -1
- package/src/lib/server/tool-capability-policy-advanced.test.ts +4 -4
- package/src/lib/server/tool-capability-policy.ts +0 -2
- package/src/lib/server/tool-planning.ts +0 -12
- package/src/lib/server/universal-tool-access.ts +0 -1
- package/src/lib/server/usage/cost-rollup.ts +124 -0
- package/src/lib/server/usage/usage-repository.ts +6 -0
- package/src/lib/server/wallets/wallet-crypto.ts +33 -0
- package/src/lib/server/wallets/wallet-repository.ts +24 -0
- package/src/lib/server/wallets/wallet-service.ts +119 -0
- package/src/lib/server/working-state/extraction.ts +8 -42
- package/src/lib/server/working-state/normalization.ts +10 -103
- package/src/lib/server/working-state/service.ts +12 -21
- package/src/lib/strip-internal-metadata.test.ts +1 -1
- package/src/lib/strip-internal-metadata.ts +1 -1
- package/src/lib/tool-definitions.ts +0 -1
- package/src/lib/validation/schemas.ts +36 -32
- package/src/lib/validation/server-schemas.ts +35 -0
- package/src/stores/slices/data-slice.ts +5 -1
- package/src/stores/slices/ui-slice.ts +0 -4
- package/src/types/agent.ts +10 -84
- package/src/types/app-settings.ts +6 -2
- package/src/types/approval.ts +3 -2
- package/src/types/connector.ts +1 -0
- package/src/types/goal.ts +30 -0
- package/src/types/index.ts +2 -1
- package/src/types/message.ts +0 -1
- package/src/types/misc.ts +2 -4
- package/src/types/protocol.ts +0 -2
- package/src/types/run.ts +0 -3
- package/src/types/session.ts +1 -51
- package/src/types/swarmdock.ts +29 -0
- package/src/types/task.ts +9 -3
- package/src/types/working-state.ts +2 -9
- package/src/views/settings/section-runtime-loop.tsx +0 -14
- package/src/app/api/canvas/[sessionId]/route.ts +0 -35
- package/src/app/api/missions/[id]/actions/route.ts +0 -31
- package/src/app/api/missions/[id]/events/route.ts +0 -14
- package/src/app/api/missions/[id]/route.ts +0 -10
- package/src/app/api/missions/route.test.ts +0 -244
- package/src/app/api/missions/route.ts +0 -57
- package/src/app/api/wallets/[id]/approve/route.ts +0 -79
- package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
- package/src/app/api/wallets/[id]/send/route.ts +0 -113
- package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
- package/src/app/missions/[id]/page.tsx +0 -3
- package/src/app/missions/page.tsx +0 -685
- package/src/components/canvas/canvas-panel.tsx +0 -267
- package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
- package/src/components/wallets/wallet-panel.tsx +0 -1010
- package/src/components/wallets/wallet-section.tsx +0 -260
- package/src/features/missions/queries.ts +0 -23
- package/src/lib/canvas-content.test.ts +0 -360
- package/src/lib/canvas-content.ts +0 -198
- package/src/lib/server/canvas-content.test.ts +0 -32
- package/src/lib/server/canvas-content.ts +0 -6
- package/src/lib/server/ethereum.ts +0 -591
- package/src/lib/server/evm-swap.ts +0 -476
- package/src/lib/server/missions/mission-intent.test.ts +0 -63
- package/src/lib/server/missions/mission-intent.ts +0 -569
- package/src/lib/server/missions/mission-repository.ts +0 -74
- package/src/lib/server/missions/mission-service/actions.ts +0 -6
- package/src/lib/server/missions/mission-service/bindings.ts +0 -9
- package/src/lib/server/missions/mission-service/context.ts +0 -4
- package/src/lib/server/missions/mission-service/core.ts +0 -2271
- package/src/lib/server/missions/mission-service/queries.ts +0 -12
- package/src/lib/server/missions/mission-service/recovery.ts +0 -5
- package/src/lib/server/missions/mission-service/ticks.ts +0 -9
- package/src/lib/server/missions/mission-service.test.ts +0 -888
- package/src/lib/server/missions/mission-service.ts +0 -6
- package/src/lib/server/session-tools/canvas.ts +0 -105
- package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
- package/src/lib/server/session-tools/wallet.ts +0 -1287
- package/src/lib/server/solana.ts +0 -327
- package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
- package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
- package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
- package/src/lib/server/wallet/wallet-service.test.ts +0 -81
- package/src/lib/server/wallet/wallet-service.ts +0 -225
- package/src/lib/wallet/wallet-transactions.test.ts +0 -75
- package/src/lib/wallet/wallet-transactions.ts +0 -43
- package/src/lib/wallet/wallet.test.ts +0 -333
- package/src/lib/wallet/wallet.ts +0 -183
- package/src/types/mission.ts +0 -185
- package/src/views/settings/section-wallets.tsx +0 -35
|
@@ -24,8 +24,6 @@ import type {
|
|
|
24
24
|
GuardianCheckpoint,
|
|
25
25
|
LearnedSkill,
|
|
26
26
|
Message,
|
|
27
|
-
Mission,
|
|
28
|
-
MissionEvent,
|
|
29
27
|
ProtocolTemplate,
|
|
30
28
|
ProtocolRun,
|
|
31
29
|
ProtocolRunEvent,
|
|
@@ -138,9 +136,6 @@ const COLLECTIONS = [
|
|
|
138
136
|
'webhook_retry_queue',
|
|
139
137
|
'notifications',
|
|
140
138
|
'chatrooms',
|
|
141
|
-
'wallets',
|
|
142
|
-
'wallet_transactions',
|
|
143
|
-
'wallet_balance_history',
|
|
144
139
|
'moderation_logs',
|
|
145
140
|
'connector_health',
|
|
146
141
|
'connector_outbox',
|
|
@@ -152,8 +147,6 @@ const COLLECTIONS = [
|
|
|
152
147
|
'watch_jobs',
|
|
153
148
|
'delegation_jobs',
|
|
154
149
|
'external_agents',
|
|
155
|
-
'missions',
|
|
156
|
-
'mission_events',
|
|
157
150
|
'protocol_templates',
|
|
158
151
|
'protocol_runs',
|
|
159
152
|
'protocol_run_events',
|
|
@@ -162,6 +155,9 @@ const COLLECTIONS = [
|
|
|
162
155
|
'main_loop_states',
|
|
163
156
|
'working_states',
|
|
164
157
|
'daemon_status',
|
|
158
|
+
'wallets',
|
|
159
|
+
'wallet_transactions',
|
|
160
|
+
'goals',
|
|
165
161
|
] as const
|
|
166
162
|
|
|
167
163
|
export type StorageCollection = (typeof COLLECTIONS)[number]
|
|
@@ -173,6 +169,10 @@ for (const table of COLLECTIONS) {
|
|
|
173
169
|
// Index for efficient protocol_run_events queries by runId
|
|
174
170
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_protocol_run_events_runid ON protocol_run_events (json_extract(data, '$.runId'))`)
|
|
175
171
|
|
|
172
|
+
// Indexes for efficient activity log queries
|
|
173
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_activity_timestamp ON activity (json_extract(data, '$.timestamp'))`)
|
|
174
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_activity_entity ON activity (json_extract(data, '$.entityType'), json_extract(data, '$.entityId'))`)
|
|
175
|
+
|
|
176
176
|
// Singleton tables (single row)
|
|
177
177
|
db.exec(`CREATE TABLE IF NOT EXISTS settings (id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL)`)
|
|
178
178
|
db.exec(`CREATE TABLE IF NOT EXISTS queue (id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL)`)
|
|
@@ -545,8 +545,6 @@ const JSON_FILES: Record<string, string> = {
|
|
|
545
545
|
documents: path.join(DATA_DIR, 'documents.json'),
|
|
546
546
|
webhooks: path.join(DATA_DIR, 'webhooks.json'),
|
|
547
547
|
external_agents: path.join(DATA_DIR, 'external-agents.json'),
|
|
548
|
-
missions: path.join(DATA_DIR, 'missions.json'),
|
|
549
|
-
mission_events: path.join(DATA_DIR, 'mission-events.json'),
|
|
550
548
|
protocol_templates: path.join(DATA_DIR, 'protocol-templates.json'),
|
|
551
549
|
protocol_runs: path.join(DATA_DIR, 'protocol-runs.json'),
|
|
552
550
|
protocol_run_events: path.join(DATA_DIR, 'protocol-run-events.json'),
|
|
@@ -1208,25 +1206,6 @@ export const loadProjects = projectsStore.load
|
|
|
1208
1206
|
export const saveProjects = projectsStore.save
|
|
1209
1207
|
export const deleteProject = projectsStore.deleteItem
|
|
1210
1208
|
|
|
1211
|
-
// --- Missions ---
|
|
1212
|
-
const missionsStore = createCollectionStore('missions')
|
|
1213
|
-
export const loadMissions = missionsStore.load as () => Record<string, Mission>
|
|
1214
|
-
export const saveMissions = missionsStore.save as (items: Record<string, Mission>) => void
|
|
1215
|
-
export const loadMission = missionsStore.loadItem as (id: string) => Mission | null
|
|
1216
|
-
export const upsertMission = missionsStore.upsert as (id: string, value: Mission) => void
|
|
1217
|
-
export const patchMission = missionsStore.patch as (
|
|
1218
|
-
id: string,
|
|
1219
|
-
updater: (current: Mission | null) => Mission | null,
|
|
1220
|
-
) => Mission | null
|
|
1221
|
-
export const deleteMission = missionsStore.deleteItem
|
|
1222
|
-
|
|
1223
|
-
const missionEventsStore = createCollectionStore('mission_events')
|
|
1224
|
-
export const loadMissionEvents = missionEventsStore.load as () => Record<string, MissionEvent>
|
|
1225
|
-
export const saveMissionEvents = missionEventsStore.save as (items: Record<string, MissionEvent>) => void
|
|
1226
|
-
export const loadMissionEvent = missionEventsStore.loadItem as (id: string) => MissionEvent | null
|
|
1227
|
-
export const upsertMissionEvent = missionEventsStore.upsert as (id: string, value: MissionEvent) => void
|
|
1228
|
-
export const upsertMissionEvents = missionEventsStore.upsertMany as (entries: Array<[string, MissionEvent]>) => void
|
|
1229
|
-
|
|
1230
1209
|
const protocolTemplatesStore = createCollectionStore('protocol_templates')
|
|
1231
1210
|
export const loadProtocolTemplates = protocolTemplatesStore.load as () => Record<string, ProtocolTemplate>
|
|
1232
1211
|
export const saveProtocolTemplates = protocolTemplatesStore.save as (items: Record<string, ProtocolTemplate>) => void
|
|
@@ -1491,6 +1470,51 @@ export function logActivity(entry: {
|
|
|
1491
1470
|
notify('activity')
|
|
1492
1471
|
}
|
|
1493
1472
|
|
|
1473
|
+
/** Paginated activity query using SQL WHERE + LIMIT/OFFSET instead of loading the full collection. */
|
|
1474
|
+
export function queryActivity(filters: {
|
|
1475
|
+
entityType?: string
|
|
1476
|
+
entityId?: string
|
|
1477
|
+
actor?: string
|
|
1478
|
+
action?: string
|
|
1479
|
+
since?: number
|
|
1480
|
+
limit?: number
|
|
1481
|
+
offset?: number
|
|
1482
|
+
}): unknown[] {
|
|
1483
|
+
const conditions: string[] = []
|
|
1484
|
+
const params: unknown[] = []
|
|
1485
|
+
|
|
1486
|
+
if (filters.entityType) {
|
|
1487
|
+
conditions.push(`json_extract(data, '$.entityType') = ?`)
|
|
1488
|
+
params.push(filters.entityType)
|
|
1489
|
+
}
|
|
1490
|
+
if (filters.entityId) {
|
|
1491
|
+
conditions.push(`json_extract(data, '$.entityId') = ?`)
|
|
1492
|
+
params.push(filters.entityId)
|
|
1493
|
+
}
|
|
1494
|
+
if (filters.actor) {
|
|
1495
|
+
conditions.push(`json_extract(data, '$.actor') = ?`)
|
|
1496
|
+
params.push(filters.actor)
|
|
1497
|
+
}
|
|
1498
|
+
if (filters.action) {
|
|
1499
|
+
conditions.push(`json_extract(data, '$.action') = ?`)
|
|
1500
|
+
params.push(filters.action)
|
|
1501
|
+
}
|
|
1502
|
+
if (typeof filters.since === 'number' && Number.isFinite(filters.since)) {
|
|
1503
|
+
conditions.push(`json_extract(data, '$.timestamp') >= ?`)
|
|
1504
|
+
params.push(filters.since)
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
|
1508
|
+
const limit = Math.min(200, Math.max(1, filters.limit ?? 50))
|
|
1509
|
+
const offset = Math.max(0, filters.offset ?? 0)
|
|
1510
|
+
|
|
1511
|
+
const sql = `SELECT data FROM activity ${where} ORDER BY json_extract(data, '$.timestamp') DESC LIMIT ? OFFSET ?`
|
|
1512
|
+
params.push(limit, offset)
|
|
1513
|
+
|
|
1514
|
+
const rows = db.prepare(sql).all(...params) as Array<{ data: string }>
|
|
1515
|
+
return rows.map((r) => JSON.parse(r.data))
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1494
1518
|
// --- Webhook Retry Queue ---
|
|
1495
1519
|
const webhookRetryQueueStore = createCollectionStore('webhook_retry_queue')
|
|
1496
1520
|
export const loadWebhookRetryQueue = webhookRetryQueueStore.load
|
|
@@ -1541,23 +1565,6 @@ export function markNotificationRead(id: string) {
|
|
|
1541
1565
|
}
|
|
1542
1566
|
}
|
|
1543
1567
|
|
|
1544
|
-
// --- Wallets ---
|
|
1545
|
-
const walletsStore = createCollectionStore('wallets')
|
|
1546
|
-
export const loadWallets = walletsStore.load
|
|
1547
|
-
export const upsertWallet = walletsStore.upsert
|
|
1548
|
-
export const deleteWallet = walletsStore.deleteItem
|
|
1549
|
-
|
|
1550
|
-
// --- Wallet Transactions ---
|
|
1551
|
-
const walletTransactionsStore = createCollectionStore('wallet_transactions')
|
|
1552
|
-
export const loadWalletTransactions = walletTransactionsStore.load
|
|
1553
|
-
export const upsertWalletTransaction = walletTransactionsStore.upsert
|
|
1554
|
-
export const deleteWalletTransaction = walletTransactionsStore.deleteItem
|
|
1555
|
-
|
|
1556
|
-
// --- Wallet Balance History ---
|
|
1557
|
-
const walletBalanceHistoryStore = createCollectionStore('wallet_balance_history')
|
|
1558
|
-
export const loadWalletBalanceHistory = walletBalanceHistoryStore.load
|
|
1559
|
-
export const upsertWalletBalanceSnapshot = walletBalanceHistoryStore.upsert
|
|
1560
|
-
|
|
1561
1568
|
// --- Moderation Logs ---
|
|
1562
1569
|
const moderationLogsStore = createCollectionStore('moderation_logs')
|
|
1563
1570
|
export const loadModerationLogs = moderationLogsStore.load
|
|
@@ -1613,6 +1620,29 @@ export const loadPersistedWorkingState = workingStatesStore.loadItem
|
|
|
1613
1620
|
export const upsertPersistedWorkingState = workingStatesStore.upsert
|
|
1614
1621
|
export const deletePersistedWorkingState = workingStatesStore.deleteItem
|
|
1615
1622
|
|
|
1623
|
+
// --- Wallets ---
|
|
1624
|
+
const walletsStore = createCollectionStore('wallets')
|
|
1625
|
+
export const loadWallets = walletsStore.load
|
|
1626
|
+
export const saveWallets = walletsStore.save
|
|
1627
|
+
export const loadWallet = walletsStore.loadItem
|
|
1628
|
+
export const upsertWallet = walletsStore.upsert
|
|
1629
|
+
export const deleteWalletItem = walletsStore.deleteItem
|
|
1630
|
+
|
|
1631
|
+
// --- Wallet Transactions ---
|
|
1632
|
+
const walletTransactionsStore = createCollectionStore('wallet_transactions')
|
|
1633
|
+
export const loadWalletTransactions = walletTransactionsStore.load
|
|
1634
|
+
export const saveWalletTransactions = walletTransactionsStore.save
|
|
1635
|
+
export const loadWalletTransaction = walletTransactionsStore.loadItem
|
|
1636
|
+
export const upsertWalletTransaction = walletTransactionsStore.upsert
|
|
1637
|
+
export const deleteWalletTransaction = walletTransactionsStore.deleteItem
|
|
1638
|
+
|
|
1639
|
+
// --- Goals ---
|
|
1640
|
+
const goalsStore = createCollectionStore('goals')
|
|
1641
|
+
export const loadGoals = goalsStore.load
|
|
1642
|
+
export const loadGoal = goalsStore.loadItem
|
|
1643
|
+
export const upsertGoal = goalsStore.upsert
|
|
1644
|
+
export const deleteGoalItem = goalsStore.deleteItem
|
|
1645
|
+
|
|
1616
1646
|
export function getSessionMessages(sessionId: string): Message[] {
|
|
1617
1647
|
const session = loadSession(sessionId)
|
|
1618
1648
|
return Array.isArray(session?.messages) ? session.messages : []
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { BoardTask } from '@/types'
|
|
2
|
+
import { withTransaction } from '@/lib/server/persistence/transaction'
|
|
3
|
+
import { loadTasks, saveTasks } from '@/lib/server/tasks/task-repository'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Atomically transition a task from queued → running with a checkout run ID.
|
|
7
|
+
*
|
|
8
|
+
* Uses a SQLite IMMEDIATE transaction to prevent two runners from starting the
|
|
9
|
+
* same task concurrently (Paperclip-inspired atomic checkout pattern).
|
|
10
|
+
*
|
|
11
|
+
* Returns the checked-out task on success, or null if the task was already
|
|
12
|
+
* taken, missing, or no longer in queued status.
|
|
13
|
+
*/
|
|
14
|
+
export function checkoutTask(
|
|
15
|
+
taskId: string,
|
|
16
|
+
runId: string,
|
|
17
|
+
): BoardTask | null {
|
|
18
|
+
return withTransaction(() => {
|
|
19
|
+
const tasks = loadTasks() as Record<string, BoardTask>
|
|
20
|
+
const task = tasks[taskId]
|
|
21
|
+
if (!task || task.status !== 'queued') return null
|
|
22
|
+
if (task.checkoutRunId) return null // already checked out
|
|
23
|
+
|
|
24
|
+
const now = Date.now()
|
|
25
|
+
task.status = 'running'
|
|
26
|
+
task.checkoutRunId = runId
|
|
27
|
+
task.startedAt = now
|
|
28
|
+
task.lastActivityAt = now
|
|
29
|
+
task.retryScheduledAt = null
|
|
30
|
+
task.deadLetteredAt = null
|
|
31
|
+
task.error = null
|
|
32
|
+
task.validation = null
|
|
33
|
+
task.updatedAt = now
|
|
34
|
+
|
|
35
|
+
saveTasks(tasks)
|
|
36
|
+
return { ...task }
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Release a checkout after task completion or failure.
|
|
42
|
+
* Only the holder of the checkout (matching runId) can release it.
|
|
43
|
+
*/
|
|
44
|
+
export function releaseCheckout(
|
|
45
|
+
taskId: string,
|
|
46
|
+
runId: string,
|
|
47
|
+
): boolean {
|
|
48
|
+
return withTransaction(() => {
|
|
49
|
+
const tasks = loadTasks() as Record<string, BoardTask>
|
|
50
|
+
const task = tasks[taskId]
|
|
51
|
+
if (!task) return false
|
|
52
|
+
if (task.checkoutRunId !== runId) return false
|
|
53
|
+
|
|
54
|
+
task.checkoutRunId = null
|
|
55
|
+
task.updatedAt = Date.now()
|
|
56
|
+
saveTasks(tasks)
|
|
57
|
+
return true
|
|
58
|
+
})
|
|
59
|
+
}
|
|
@@ -176,6 +176,7 @@ export function markValidatedTaskCompleted(
|
|
|
176
176
|
task.completedAt = options.preserveCompletedAt ? (task.completedAt || options.now) : options.now
|
|
177
177
|
task.updatedAt = options.now
|
|
178
178
|
task.error = null
|
|
179
|
+
task.checkoutRunId = null
|
|
179
180
|
return task
|
|
180
181
|
}
|
|
181
182
|
|
|
@@ -187,6 +188,7 @@ export function markInvalidCompletedTaskFailed(
|
|
|
187
188
|
task.status = 'failed'
|
|
188
189
|
task.completedAt = null
|
|
189
190
|
task.updatedAt = options.now
|
|
191
|
+
task.checkoutRunId = null
|
|
190
192
|
task.error = formatValidationFailure(validation.reasons).slice(0, 500)
|
|
191
193
|
if (options.comment) {
|
|
192
194
|
if (!task.comments) task.comments = []
|
|
@@ -6,11 +6,6 @@ import { logActivity } from '@/lib/server/activity/activity-log'
|
|
|
6
6
|
import { createNotification } from '@/lib/server/create-notification'
|
|
7
7
|
import { validateDag, cascadeUnblock } from '@/lib/server/dag-validation'
|
|
8
8
|
import { getExtensionManager } from '@/lib/server/extensions'
|
|
9
|
-
import {
|
|
10
|
-
enrichTaskWithMissionSummary,
|
|
11
|
-
ensureMissionForTask,
|
|
12
|
-
noteMissionTaskFinished,
|
|
13
|
-
} from '@/lib/server/missions/mission-service'
|
|
14
9
|
import {
|
|
15
10
|
disableSessionHeartbeat,
|
|
16
11
|
enqueueTask,
|
|
@@ -64,9 +59,7 @@ export function prepareTasksForListing() {
|
|
|
64
59
|
validateCompletedTasksQueue()
|
|
65
60
|
recoverStalledRunningTasks()
|
|
66
61
|
const allTasks = loadTasks()
|
|
67
|
-
return
|
|
68
|
-
Object.entries(allTasks).map(([id, task]) => [id, enrichTaskWithMissionSummary(task)]),
|
|
69
|
-
)
|
|
62
|
+
return allTasks
|
|
70
63
|
}
|
|
71
64
|
|
|
72
65
|
export function updateTaskFromRoute(id: string, body: Record<string, unknown>): ServiceResult<BoardTask> {
|
|
@@ -128,10 +121,6 @@ export function updateTaskFromRoute(id: string, body: Record<string, unknown>):
|
|
|
128
121
|
}
|
|
129
122
|
|
|
130
123
|
saveTask(id, tasks[id])
|
|
131
|
-
const mission = ensureMissionForTask(tasks[id], { source: 'manual' })
|
|
132
|
-
if (tasks[id].status === 'completed' || tasks[id].status === 'failed' || tasks[id].status === 'cancelled') {
|
|
133
|
-
noteMissionTaskFinished(tasks[id], tasks[id].status, tasks[id].id)
|
|
134
|
-
}
|
|
135
124
|
logActivity({ entityType: 'task', entityId: id, action: 'updated', actor: 'user', summary: `Task updated: "${tasks[id].title}" (${prevStatus} → ${tasks[id].status})` })
|
|
136
125
|
if (prevStatus !== tasks[id].status) {
|
|
137
126
|
pushMainLoopEventToMainSessions({
|
|
@@ -143,10 +132,7 @@ export function updateTaskFromRoute(id: string, body: Record<string, unknown>):
|
|
|
143
132
|
if (prevStatus !== tasks[id].status && tasks[id].status === 'cancelled') {
|
|
144
133
|
disableSessionHeartbeat(tasks[id].sessionId)
|
|
145
134
|
notify('tasks')
|
|
146
|
-
return serviceOk(
|
|
147
|
-
...tasks[id],
|
|
148
|
-
missionId: mission?.id || tasks[id].missionId || null,
|
|
149
|
-
}))
|
|
135
|
+
return serviceOk(tasks[id])
|
|
150
136
|
}
|
|
151
137
|
|
|
152
138
|
if (prevStatus !== tasks[id].status && (tasks[id].status === 'completed' || tasks[id].status === 'failed')) {
|
|
@@ -216,10 +202,7 @@ export function updateTaskFromRoute(id: string, body: Record<string, unknown>):
|
|
|
216
202
|
}
|
|
217
203
|
|
|
218
204
|
notify('tasks')
|
|
219
|
-
return serviceOk(
|
|
220
|
-
...tasks[id],
|
|
221
|
-
missionId: mission?.id || tasks[id].missionId || null,
|
|
222
|
-
}))
|
|
205
|
+
return serviceOk(tasks[id])
|
|
223
206
|
}
|
|
224
207
|
|
|
225
208
|
export function archiveTaskFromRoute(id: string): ServiceResult<BoardTask> {
|
|
@@ -346,11 +329,6 @@ export function createTaskFromRoute(body: Record<string, unknown>): ServiceResul
|
|
|
346
329
|
}
|
|
347
330
|
|
|
348
331
|
saveTask(id, task)
|
|
349
|
-
const mission = ensureMissionForTask(task, { source: 'manual' })
|
|
350
|
-
const finalTask = enrichTaskWithMissionSummary({
|
|
351
|
-
...task,
|
|
352
|
-
missionId: mission?.id || task.missionId || null,
|
|
353
|
-
})
|
|
354
332
|
logActivity({ entityType: 'task', entityId: id, action: 'created', actor: 'user', summary: `Task created: "${task.title}"` })
|
|
355
333
|
pushMainLoopEventToMainSessions({
|
|
356
334
|
type: 'task_created',
|
|
@@ -360,7 +338,7 @@ export function createTaskFromRoute(body: Record<string, unknown>): ServiceResul
|
|
|
360
338
|
enqueueTask(id)
|
|
361
339
|
}
|
|
362
340
|
notify('tasks')
|
|
363
|
-
return serviceOk(
|
|
341
|
+
return serviceOk(task)
|
|
364
342
|
}
|
|
365
343
|
|
|
366
344
|
export function bulkUpdateTasksFromRoute(body: Record<string, unknown>): ServiceResult<{ updated: number; ids: string[] }> {
|
|
@@ -106,13 +106,6 @@ export function applyTaskContinuationDefaults(
|
|
|
106
106
|
if (!Object.prototype.hasOwnProperty.call(explicit, 'cwd') && typeof sourceTask.cwd === 'string' && sourceTask.cwd.trim()) {
|
|
107
107
|
parsed.cwd = sourceTask.cwd.trim()
|
|
108
108
|
}
|
|
109
|
-
if (
|
|
110
|
-
!Object.prototype.hasOwnProperty.call(explicit, 'missionId')
|
|
111
|
-
&& typeof sourceTask.missionId === 'string'
|
|
112
|
-
&& sourceTask.missionId.trim()
|
|
113
|
-
) {
|
|
114
|
-
parsed.missionId = sourceTask.missionId.trim()
|
|
115
|
-
}
|
|
116
109
|
const sourceSessionId = typeof sourceTask.checkpoint?.lastSessionId === 'string' && sourceTask.checkpoint.lastSessionId.trim()
|
|
117
110
|
? sourceTask.checkpoint.lastSessionId.trim()
|
|
118
111
|
: typeof sourceTask.sessionId === 'string' && sourceTask.sessionId.trim()
|
|
@@ -22,7 +22,6 @@ const EXTENSION_ALIAS_GROUPS: string[][] = [
|
|
|
22
22
|
['schedule_wake', 'schedule'],
|
|
23
23
|
// http_request/http now aliased into web group above
|
|
24
24
|
['memory', 'memory_tool', 'memory_search', 'memory_get', 'memory_store', 'memory_update'],
|
|
25
|
-
['wallet', 'wallet_tool'],
|
|
26
25
|
['monitor', 'monitor_tool'],
|
|
27
26
|
['context_mgmt', 'context_status', 'context_summarize'],
|
|
28
27
|
['openclaw_workspace'],
|
|
@@ -423,17 +423,17 @@ describe('compound scenarios', () => {
|
|
|
423
423
|
assert.match(tasksBlock.reason, /task management is disabled/)
|
|
424
424
|
})
|
|
425
425
|
|
|
426
|
-
it('
|
|
426
|
+
it('19 tools requested: correctly partitioned into enabled vs blocked', () => {
|
|
427
427
|
const tools = [
|
|
428
428
|
'shell', 'files', 'web', 'web_search', 'web_fetch', 'browser',
|
|
429
429
|
'memory', 'delegate', 'manage_platform', 'manage_tasks',
|
|
430
|
-
'manage_schedules', 'wallet', 'delete_file',
|
|
430
|
+
'manage_schedules', 'wallet', 'delete_file',
|
|
431
431
|
'manage_connectors', 'git', 'sandbox', 'claude_code',
|
|
432
432
|
'monitor', 'http_request',
|
|
433
433
|
]
|
|
434
434
|
const d = resolveSessionToolPolicy(tools, { capabilityPolicyMode: 'strict' })
|
|
435
|
-
assert.equal(d.requestedExtensions.length,
|
|
436
|
-
assert.equal(d.enabledExtensions.length + d.blockedExtensions.length,
|
|
435
|
+
assert.equal(d.requestedExtensions.length, 19)
|
|
436
|
+
assert.equal(d.enabledExtensions.length + d.blockedExtensions.length, 19)
|
|
437
437
|
|
|
438
438
|
// memory, web, web_search, web_fetch should be enabled
|
|
439
439
|
assert.ok(d.enabledExtensions.includes('memory'))
|
|
@@ -60,8 +60,6 @@ const TOOL_DESCRIPTORS: Record<string, ToolDescriptor> = {
|
|
|
60
60
|
gemini_cli: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_gemini_cli'] },
|
|
61
61
|
memory: { categories: ['memory'], concreteTools: ['memory', 'memory_tool', 'memory_search', 'memory_get', 'memory_store', 'memory_update', 'context_status', 'context_summarize'] },
|
|
62
62
|
// http_request consolidated into web 'api' action — no separate descriptor
|
|
63
|
-
canvas: { categories: ['filesystem'], concreteTools: ['canvas'] },
|
|
64
|
-
wallet: { categories: ['outbound'], concreteTools: ['wallet', 'wallet_tool'] },
|
|
65
63
|
monitor: { categories: ['execution'], concreteTools: ['monitor', 'monitor_tool'] },
|
|
66
64
|
openclaw_workspace: { categories: ['filesystem', 'platform'], concreteTools: ['openclaw_workspace'] },
|
|
67
65
|
openclaw_nodes: { categories: ['platform'], concreteTools: ['openclaw_nodes'] },
|
|
@@ -13,8 +13,6 @@ export const TOOL_CAPABILITY = {
|
|
|
13
13
|
deliveryMessage: 'delivery.message',
|
|
14
14
|
deliveryMedia: 'delivery.media',
|
|
15
15
|
deliveryVoiceNote: 'delivery.voice_note',
|
|
16
|
-
walletInspect: 'wallet.inspect',
|
|
17
|
-
walletExecute: 'wallet.execute',
|
|
18
16
|
} as const
|
|
19
17
|
|
|
20
18
|
export interface ToolPlanningEntry {
|
|
@@ -373,16 +371,6 @@ const CORE_TOOL_PLANNING: Record<string, LegacyToolPlanningEntry[]> = {
|
|
|
373
371
|
requestMatchers: [],
|
|
374
372
|
},
|
|
375
373
|
],
|
|
376
|
-
wallet: [
|
|
377
|
-
{
|
|
378
|
-
toolName: 'wallet_tool',
|
|
379
|
-
capabilities: [TOOL_CAPABILITY.walletInspect, TOOL_CAPABILITY.walletExecute],
|
|
380
|
-
disciplineGuidance: [
|
|
381
|
-
'Inspect wallet state once, then act. Use `simulate_transaction` to validate before executing. Do not re-inspect balances between each operation unless the operation changes them.',
|
|
382
|
-
],
|
|
383
|
-
requestMatchers: [],
|
|
384
|
-
},
|
|
385
|
-
],
|
|
386
374
|
image_gen: [
|
|
387
375
|
{
|
|
388
376
|
toolName: 'generate_image',
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { Agent } from '@/types'
|
|
2
|
+
import { patchAgent } from '@/lib/server/agents/agent-repository'
|
|
3
|
+
import { logActivity } from '@/lib/server/activity/activity-log'
|
|
4
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
5
|
+
import { log } from '@/lib/server/logger'
|
|
6
|
+
|
|
7
|
+
const TAG = 'cost-rollup'
|
|
8
|
+
|
|
9
|
+
const ONE_HOUR_MS = 60 * 60 * 1000
|
|
10
|
+
|
|
11
|
+
function toWindowBoundaries(now: number) {
|
|
12
|
+
const d = new Date(now)
|
|
13
|
+
const dayStart = new Date(d)
|
|
14
|
+
dayStart.setUTCHours(0, 0, 0, 0)
|
|
15
|
+
const monthStart = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1))
|
|
16
|
+
return {
|
|
17
|
+
hourStartTs: now - ONE_HOUR_MS,
|
|
18
|
+
dayStartTs: dayStart.getTime(),
|
|
19
|
+
monthStartTs: monthStart.getTime(),
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Push-based cost rollup: atomically increments the agent's persisted spend
|
|
25
|
+
* fields and checks budgets. Called immediately after each usage record is appended.
|
|
26
|
+
*/
|
|
27
|
+
export function rollupCostToAgent(agentId: string, costUsd: number): void {
|
|
28
|
+
if (!agentId || !Number.isFinite(costUsd) || costUsd <= 0) return
|
|
29
|
+
|
|
30
|
+
const now = Date.now()
|
|
31
|
+
const costCents = Math.round(costUsd * 100)
|
|
32
|
+
const { hourStartTs, dayStartTs, monthStartTs } = toWindowBoundaries(now)
|
|
33
|
+
|
|
34
|
+
const updated = patchAgent(agentId, (current) => {
|
|
35
|
+
if (!current) return null
|
|
36
|
+
const lastRollup = current.lastSpendRollupAt ?? 0
|
|
37
|
+
const lastBounds = toWindowBoundaries(lastRollup)
|
|
38
|
+
|
|
39
|
+
// Reset windows that have rolled over since last rollup
|
|
40
|
+
let hourly = current.spentHourlyCents ?? 0
|
|
41
|
+
let daily = current.spentDailyCents ?? 0
|
|
42
|
+
let monthly = current.spentMonthlyCents ?? 0
|
|
43
|
+
|
|
44
|
+
if (lastRollup < hourStartTs) hourly = 0
|
|
45
|
+
if (lastBounds.dayStartTs < dayStartTs) daily = 0
|
|
46
|
+
if (lastBounds.monthStartTs < monthStartTs) monthly = 0
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
...current,
|
|
50
|
+
spentHourlyCents: hourly + costCents,
|
|
51
|
+
spentDailyCents: daily + costCents,
|
|
52
|
+
spentMonthlyCents: monthly + costCents,
|
|
53
|
+
lastSpendRollupAt: now,
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
if (!updated) return
|
|
58
|
+
|
|
59
|
+
// Check budgets and enforce
|
|
60
|
+
checkAndEnforceBudget(updated)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Checks agent's persisted spend against configured budgets and logs
|
|
65
|
+
* activity entries when thresholds are hit.
|
|
66
|
+
*/
|
|
67
|
+
function checkAndEnforceBudget(agent: Agent): void {
|
|
68
|
+
const WARNING_RATIO = 0.8
|
|
69
|
+
|
|
70
|
+
const windows = [
|
|
71
|
+
{ key: 'hourly' as const, budget: agent.hourlyBudget, spent: (agent.spentHourlyCents ?? 0) / 100 },
|
|
72
|
+
{ key: 'daily' as const, budget: agent.dailyBudget, spent: (agent.spentDailyCents ?? 0) / 100 },
|
|
73
|
+
{ key: 'monthly' as const, budget: agent.monthlyBudget, spent: (agent.spentMonthlyCents ?? 0) / 100 },
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
for (const { key, budget, spent } of windows) {
|
|
77
|
+
if (!budget || !Number.isFinite(budget) || budget <= 0) continue
|
|
78
|
+
const ratio = spent / budget
|
|
79
|
+
|
|
80
|
+
if (ratio >= 1) {
|
|
81
|
+
log.warn(TAG, `Agent "${agent.name}" exceeded ${key} budget: $${spent.toFixed(4)} / $${budget.toFixed(2)}`)
|
|
82
|
+
logActivity({
|
|
83
|
+
entityType: 'budget',
|
|
84
|
+
entityId: agent.id,
|
|
85
|
+
action: 'budget_exceeded',
|
|
86
|
+
actor: 'system',
|
|
87
|
+
summary: `Agent "${agent.name}" exceeded ${key} budget: $${spent.toFixed(4)} / $${budget.toFixed(2)}`,
|
|
88
|
+
detail: { window: key, spent, budget, ratio },
|
|
89
|
+
})
|
|
90
|
+
notify('agents')
|
|
91
|
+
} else if (ratio >= WARNING_RATIO) {
|
|
92
|
+
logActivity({
|
|
93
|
+
entityType: 'budget',
|
|
94
|
+
entityId: agent.id,
|
|
95
|
+
action: 'budget_warning',
|
|
96
|
+
actor: 'system',
|
|
97
|
+
summary: `Agent "${agent.name}" nearing ${key} budget: $${spent.toFixed(4)} / $${budget.toFixed(2)} (${Math.round(ratio * 100)}%)`,
|
|
98
|
+
detail: { window: key, spent, budget, ratio },
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Reset all agents' daily spend counters. Call at UTC midnight.
|
|
106
|
+
*/
|
|
107
|
+
export function resetDailySpends(agents: Record<string, Agent>): void {
|
|
108
|
+
for (const agent of Object.values(agents)) {
|
|
109
|
+
if ((agent.spentDailyCents ?? 0) > 0) {
|
|
110
|
+
patchAgent(agent.id, (current) => current ? { ...current, spentDailyCents: 0 } : null)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Reset all agents' monthly spend counters. Call at UTC month boundary.
|
|
117
|
+
*/
|
|
118
|
+
export function resetMonthlySpends(agents: Record<string, Agent>): void {
|
|
119
|
+
for (const agent of Object.values(agents)) {
|
|
120
|
+
if ((agent.spentMonthlyCents ?? 0) > 0) {
|
|
121
|
+
patchAgent(agent.id, (current) => current ? { ...current, spentMonthlyCents: 0 } : null)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
saveUsage as saveStoredUsage,
|
|
9
9
|
} from '@/lib/server/storage'
|
|
10
10
|
import { perf } from '@/lib/server/runtime/perf'
|
|
11
|
+
import { rollupCostToAgent } from './cost-rollup'
|
|
11
12
|
|
|
12
13
|
export function loadUsage(): Record<string, UsageRecord[]> {
|
|
13
14
|
return perf.measureSync('repository', 'usage.list', () => loadStoredUsage())
|
|
@@ -19,6 +20,11 @@ export function saveUsage(data: Record<string, UsageRecord[]>): void {
|
|
|
19
20
|
|
|
20
21
|
export function appendUsage(sessionId: string, record: unknown): void {
|
|
21
22
|
perf.measureSync('repository', 'usage.append', () => appendStoredUsage(sessionId, record), { sessionId })
|
|
23
|
+
// Push-based cost rollup: update persisted spend fields and check budgets
|
|
24
|
+
const rec = record as Partial<UsageRecord>
|
|
25
|
+
if (rec.agentId && typeof rec.estimatedCost === 'number' && rec.estimatedCost > 0) {
|
|
26
|
+
rollupCostToAgent(rec.agentId, rec.estimatedCost)
|
|
27
|
+
}
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
export function getUsageSpendSince(minTimestamp: number): number {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { encryptKey, decryptKey } from '@/lib/server/storage'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate a new Ethereum wallet (Base L2 compatible) with an encrypted private key.
|
|
5
|
+
* Uses ethers v6 for keypair generation and the existing AES-256-GCM encryption
|
|
6
|
+
* from storage.ts (CREDENTIAL_SECRET env var).
|
|
7
|
+
*/
|
|
8
|
+
export async function generateEthereumWallet(): Promise<{ address: string; encryptedPrivateKey: string }> {
|
|
9
|
+
const { Wallet } = await import('ethers')
|
|
10
|
+
const wallet = Wallet.createRandom()
|
|
11
|
+
const encryptedPrivateKey = encryptKey(wallet.privateKey)
|
|
12
|
+
return {
|
|
13
|
+
address: wallet.address,
|
|
14
|
+
encryptedPrivateKey,
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function normalizeEthereumAddress(address: string): Promise<string | null> {
|
|
19
|
+
const { getAddress } = await import('ethers')
|
|
20
|
+
try {
|
|
21
|
+
return getAddress(address.trim())
|
|
22
|
+
} catch {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Decrypt a wallet's private key for server-side use only.
|
|
29
|
+
* Never expose the result to API responses or the frontend.
|
|
30
|
+
*/
|
|
31
|
+
export function decryptWalletPrivateKey(encrypted: string): string {
|
|
32
|
+
return decryptKey(encrypted)
|
|
33
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadWallets as loadWalletsStore,
|
|
3
|
+
saveWallets as saveWalletsStore,
|
|
4
|
+
loadWallet as loadWalletItem,
|
|
5
|
+
upsertWallet,
|
|
6
|
+
deleteWalletItem,
|
|
7
|
+
} from '@/lib/server/storage'
|
|
8
|
+
import type { AgentWallet } from '@/types/swarmdock'
|
|
9
|
+
|
|
10
|
+
export function loadWallets(): Record<string, AgentWallet> {
|
|
11
|
+
return loadWalletsStore() as Record<string, AgentWallet>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function loadWallet(id: string): AgentWallet | null {
|
|
15
|
+
return loadWalletItem(id) as AgentWallet | null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function saveWallet(id: string, wallet: AgentWallet): void {
|
|
19
|
+
upsertWallet(id, wallet)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function deleteWallet(id: string): void {
|
|
23
|
+
deleteWalletItem(id)
|
|
24
|
+
}
|